@cueplusplus/ui 0.1.0
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.
- package/dist/charts/_token.js +39 -0
- package/dist/charts/container.d.ts +58 -0
- package/dist/charts/container.js +77 -0
- package/dist/charts/index.d.ts +5 -0
- package/dist/charts/index.js +5 -0
- package/dist/charts/ramps.d.ts +140 -0
- package/dist/charts/ramps.js +615 -0
- package/dist/charts/theme.d.ts +112 -0
- package/dist/charts/theme.js +165 -0
- package/dist/charts/tones.d.ts +111 -0
- package/dist/charts/tones.js +127 -0
- package/dist/chat/_glyphs.js +55 -0
- package/dist/chat/agent-color.d.ts +84 -0
- package/dist/chat/agent-color.js +101 -0
- package/dist/chat/agent-pile.d.ts +55 -0
- package/dist/chat/agent-pile.js +121 -0
- package/dist/chat/ask-box.d.ts +88 -0
- package/dist/chat/ask-box.js +341 -0
- package/dist/chat/composer.d.ts +66 -0
- package/dist/chat/composer.js +144 -0
- package/dist/chat/delegation-card.d.ts +60 -0
- package/dist/chat/delegation-card.js +129 -0
- package/dist/chat/index.d.ts +8 -0
- package/dist/chat/index.js +8 -0
- package/dist/chat/message-list.d.ts +39 -0
- package/dist/chat/message-list.js +105 -0
- package/dist/chat/message.d.ts +47 -0
- package/dist/chat/message.js +108 -0
- package/dist/chrome/_glyphs.js +101 -0
- package/dist/chrome/_tabs-scroll.d.ts +62 -0
- package/dist/chrome/_tabs-scroll.js +68 -0
- package/dist/chrome/_tree.d.ts +75 -0
- package/dist/chrome/_tree.js +49 -0
- package/dist/chrome/empty-state.d.ts +37 -0
- package/dist/chrome/empty-state.js +57 -0
- package/dist/chrome/eyebrow.d.ts +29 -0
- package/dist/chrome/eyebrow.js +36 -0
- package/dist/chrome/index.d.ts +17 -0
- package/dist/chrome/index.js +17 -0
- package/dist/chrome/navigation-menu.d.ts +80 -0
- package/dist/chrome/navigation-menu.js +121 -0
- package/dist/chrome/page-shell.d.ts +38 -0
- package/dist/chrome/page-shell.js +66 -0
- package/dist/chrome/panel.d.ts +55 -0
- package/dist/chrome/panel.js +67 -0
- package/dist/chrome/row.d.ts +82 -0
- package/dist/chrome/row.js +106 -0
- package/dist/chrome/scroll-area.d.ts +39 -0
- package/dist/chrome/scroll-area.js +70 -0
- package/dist/chrome/section-header.d.ts +42 -0
- package/dist/chrome/section-header.js +79 -0
- package/dist/chrome/segmented-control.d.ts +58 -0
- package/dist/chrome/segmented-control.js +76 -0
- package/dist/chrome/status-bar.d.ts +46 -0
- package/dist/chrome/status-bar.js +56 -0
- package/dist/chrome/tabs.d.ts +80 -0
- package/dist/chrome/tabs.js +176 -0
- package/dist/chrome/title-bar.d.ts +37 -0
- package/dist/chrome/title-bar.js +45 -0
- package/dist/chrome/toolbar.d.ts +46 -0
- package/dist/chrome/toolbar.js +62 -0
- package/dist/chrome/tree.d.ts +98 -0
- package/dist/chrome/tree.js +248 -0
- package/dist/color/_checker.js +14 -0
- package/dist/color/_convert.d.ts +52 -0
- package/dist/color/_convert.js +66 -0
- package/dist/color/_glyphs.js +28 -0
- package/dist/color/_thumb.js +15 -0
- package/dist/color/_value.js +28 -0
- package/dist/color/color-area.d.ts +27 -0
- package/dist/color/color-area.js +40 -0
- package/dist/color/color-field.d.ts +32 -0
- package/dist/color/color-field.js +43 -0
- package/dist/color/color-picker.d.ts +71 -0
- package/dist/color/color-picker.js +178 -0
- package/dist/color/color-slider.d.ts +31 -0
- package/dist/color/color-slider.js +51 -0
- package/dist/color/color-swatch.d.ts +32 -0
- package/dist/color/color-swatch.js +44 -0
- package/dist/color/index.d.ts +7 -0
- package/dist/color/index.js +7 -0
- package/dist/configurator/_export.d.ts +78 -0
- package/dist/configurator/_export.js +261 -0
- package/dist/configurator/_glyphs.js +92 -0
- package/dist/configurator/_overrides.d.ts +247 -0
- package/dist/configurator/_overrides.js +514 -0
- package/dist/configurator/configurator.d.ts +71 -0
- package/dist/configurator/configurator.js +279 -0
- package/dist/configurator/export-dialog.d.ts +30 -0
- package/dist/configurator/export-dialog.js +121 -0
- package/dist/configurator/index.d.ts +8 -0
- package/dist/configurator/index.js +8 -0
- package/dist/configurator/panel-sections.d.ts +86 -0
- package/dist/configurator/panel-sections.js +316 -0
- package/dist/configurator/token-editor.d.ts +74 -0
- package/dist/configurator/token-editor.js +149 -0
- package/dist/configurator/use-zoom.d.ts +87 -0
- package/dist/configurator/use-zoom.js +113 -0
- package/dist/date/_glyphs.js +34 -0
- package/dist/date/_segments.js +55 -0
- package/dist/date/_trigger.js +31 -0
- package/dist/date/calendar.d.ts +25 -0
- package/dist/date/calendar.js +125 -0
- package/dist/date/date-field.d.ts +34 -0
- package/dist/date/date-field.js +41 -0
- package/dist/date/date-picker.d.ts +69 -0
- package/dist/date/date-picker.js +89 -0
- package/dist/date/date-range-picker.d.ts +65 -0
- package/dist/date/date-range-picker.js +92 -0
- package/dist/date/index.d.ts +6 -0
- package/dist/date/index.js +6 -0
- package/dist/date/time-field.d.ts +29 -0
- package/dist/date/time-field.js +36 -0
- package/dist/dmx/_ramp.d.ts +77 -0
- package/dist/dmx/_ramp.js +169 -0
- package/dist/dmx/_readout.js +53 -0
- package/dist/dmx/_universe.d.ts +105 -0
- package/dist/dmx/_universe.js +112 -0
- package/dist/dmx/channel-matrix.d.ts +81 -0
- package/dist/dmx/channel-matrix.js +285 -0
- package/dist/dmx/dmx-bar.d.ts +50 -0
- package/dist/dmx/dmx-bar.js +107 -0
- package/dist/dmx/dmx-strip.d.ts +41 -0
- package/dist/dmx/dmx-strip.js +196 -0
- package/dist/dmx/index.d.ts +7 -0
- package/dist/dmx/index.js +7 -0
- package/dist/dmx/universe-grid.d.ts +61 -0
- package/dist/dmx/universe-grid.js +182 -0
- package/dist/flow/_frame.d.ts +52 -0
- package/dist/flow/_frame.js +38 -0
- package/dist/flow/controls.d.ts +41 -0
- package/dist/flow/controls.js +53 -0
- package/dist/flow/flow.css +121 -0
- package/dist/flow/handles.d.ts +40 -0
- package/dist/flow/handles.js +61 -0
- package/dist/flow/index.d.ts +7 -0
- package/dist/flow/index.js +7 -0
- package/dist/flow/node-card.d.ts +71 -0
- package/dist/flow/node-card.js +102 -0
- package/dist/flow/signal-edge.d.ts +60 -0
- package/dist/flow/signal-edge.js +108 -0
- package/dist/flow/use-flow-theme.d.ts +69 -0
- package/dist/flow/use-flow-theme.js +109 -0
- package/dist/forms/_chassis.d.ts +64 -0
- package/dist/forms/_chassis.js +59 -0
- package/dist/forms/_glyphs.js +143 -0
- package/dist/forms/_listbox.js +55 -0
- package/dist/forms/autocomplete.d.ts +61 -0
- package/dist/forms/autocomplete.js +88 -0
- package/dist/forms/button-group.d.ts +47 -0
- package/dist/forms/button-group.js +52 -0
- package/dist/forms/checkbox.d.ts +51 -0
- package/dist/forms/checkbox.js +98 -0
- package/dist/forms/combobox.d.ts +73 -0
- package/dist/forms/combobox.js +108 -0
- package/dist/forms/env-var-input.d.ts +74 -0
- package/dist/forms/env-var-input.js +116 -0
- package/dist/forms/field.d.ts +89 -0
- package/dist/forms/field.js +102 -0
- package/dist/forms/file-upload.d.ts +113 -0
- package/dist/forms/file-upload.js +232 -0
- package/dist/forms/index.d.ts +26 -0
- package/dist/forms/index.js +26 -0
- package/dist/forms/input-group.d.ts +31 -0
- package/dist/forms/input-group.js +54 -0
- package/dist/forms/input.d.ts +33 -0
- package/dist/forms/input.js +36 -0
- package/dist/forms/multi-select.d.ts +62 -0
- package/dist/forms/multi-select.js +107 -0
- package/dist/forms/number-field.d.ts +38 -0
- package/dist/forms/number-field.js +64 -0
- package/dist/forms/otp-field.d.ts +54 -0
- package/dist/forms/otp-field.js +59 -0
- package/dist/forms/password-input.d.ts +46 -0
- package/dist/forms/password-input.js +62 -0
- package/dist/forms/radio-group.d.ts +50 -0
- package/dist/forms/radio-group.js +64 -0
- package/dist/forms/rating.d.ts +69 -0
- package/dist/forms/rating.js +103 -0
- package/dist/forms/scrub-input.d.ts +81 -0
- package/dist/forms/scrub-input.js +249 -0
- package/dist/forms/search-input.d.ts +54 -0
- package/dist/forms/search-input.js +112 -0
- package/dist/forms/select.d.ts +75 -0
- package/dist/forms/select.js +91 -0
- package/dist/forms/slider.d.ts +44 -0
- package/dist/forms/slider.js +65 -0
- package/dist/forms/switch.d.ts +36 -0
- package/dist/forms/switch.js +71 -0
- package/dist/forms/tags-input.d.ts +69 -0
- package/dist/forms/tags-input.js +148 -0
- package/dist/forms/textarea.d.ts +27 -0
- package/dist/forms/textarea.js +37 -0
- package/dist/forms/toggle-group.d.ts +41 -0
- package/dist/forms/toggle-group.js +56 -0
- package/dist/forms/toggle.d.ts +41 -0
- package/dist/forms/toggle.js +100 -0
- package/dist/index.d.ts +112 -0
- package/dist/index.js +104 -0
- package/dist/instruments/_ansi.js +116 -0
- package/dist/instruments/_glyphs.js +90 -0
- package/dist/instruments/_motion.js +56 -0
- package/dist/instruments/_rem.js +21 -0
- package/dist/instruments/app-window-frame.d.ts +48 -0
- package/dist/instruments/app-window-frame.js +76 -0
- package/dist/instruments/copy-button.d.ts +41 -0
- package/dist/instruments/copy-button.js +112 -0
- package/dist/instruments/data-table.d.ts +168 -0
- package/dist/instruments/data-table.js +212 -0
- package/dist/instruments/group-bar.d.ts +63 -0
- package/dist/instruments/group-bar.js +122 -0
- package/dist/instruments/index.d.ts +13 -0
- package/dist/instruments/index.js +13 -0
- package/dist/instruments/log-viewer.d.ts +74 -0
- package/dist/instruments/log-viewer.js +215 -0
- package/dist/instruments/meter.d.ts +77 -0
- package/dist/instruments/meter.js +96 -0
- package/dist/instruments/progress.d.ts +45 -0
- package/dist/instruments/progress.js +61 -0
- package/dist/instruments/seam-grid.d.ts +66 -0
- package/dist/instruments/seam-grid.js +78 -0
- package/dist/instruments/sparkline.d.ts +39 -0
- package/dist/instruments/sparkline.js +85 -0
- package/dist/instruments/stat.d.ts +35 -0
- package/dist/instruments/stat.js +59 -0
- package/dist/instruments/table.d.ts +51 -0
- package/dist/instruments/table.js +92 -0
- package/dist/instruments/terminal-frame.d.ts +89 -0
- package/dist/instruments/terminal-frame.js +256 -0
- package/dist/instruments/two-step-button.d.ts +47 -0
- package/dist/instruments/two-step-button.js +147 -0
- package/dist/instruments/usage-chart.d.ts +55 -0
- package/dist/instruments/usage-chart.js +235 -0
- package/dist/layout/_collapse.js +46 -0
- package/dist/layout/_glyphs.js +45 -0
- package/dist/layout/accordion.d.ts +52 -0
- package/dist/layout/accordion.js +81 -0
- package/dist/layout/breadcrumb.d.ts +35 -0
- package/dist/layout/breadcrumb.js +67 -0
- package/dist/layout/card.d.ts +51 -0
- package/dist/layout/card.js +76 -0
- package/dist/layout/carousel.d.ts +96 -0
- package/dist/layout/carousel.js +211 -0
- package/dist/layout/collapsible.d.ts +74 -0
- package/dist/layout/collapsible.js +126 -0
- package/dist/layout/container.d.ts +27 -0
- package/dist/layout/container.js +40 -0
- package/dist/layout/description-list.d.ts +24 -0
- package/dist/layout/description-list.js +37 -0
- package/dist/layout/grid.d.ts +40 -0
- package/dist/layout/grid.js +48 -0
- package/dist/layout/index.d.ts +15 -0
- package/dist/layout/index.js +15 -0
- package/dist/layout/item.d.ts +65 -0
- package/dist/layout/item.js +100 -0
- package/dist/layout/link.d.ts +48 -0
- package/dist/layout/link.js +63 -0
- package/dist/layout/pagination.d.ts +54 -0
- package/dist/layout/pagination.js +121 -0
- package/dist/layout/resizable.d.ts +38 -0
- package/dist/layout/resizable.js +68 -0
- package/dist/layout/sidebar.d.ts +67 -0
- package/dist/layout/sidebar.js +129 -0
- package/dist/layout/stack.d.ts +46 -0
- package/dist/layout/stack.js +62 -0
- package/dist/layout/stepper.d.ts +49 -0
- package/dist/layout/stepper.js +81 -0
- package/dist/layout/timeline.d.ts +43 -0
- package/dist/layout/timeline.js +63 -0
- package/dist/lib/aria-class.js +24 -0
- package/dist/lib/cn.js +18 -0
- package/dist/lib/compose.js +38 -0
- package/dist/lib/cva.d.ts +18 -0
- package/dist/lib/cva.js +65 -0
- package/dist/midi/_notes.d.ts +101 -0
- package/dist/midi/_notes.js +183 -0
- package/dist/midi/_ruler.d.ts +100 -0
- package/dist/midi/_ruler.js +105 -0
- package/dist/midi/_time.d.ts +107 -0
- package/dist/midi/_time.js +130 -0
- package/dist/midi/index.d.ts +9 -0
- package/dist/midi/index.js +9 -0
- package/dist/midi/musical-time-input.d.ts +48 -0
- package/dist/midi/musical-time-input.js +154 -0
- package/dist/midi/piano-keyboard.d.ts +50 -0
- package/dist/midi/piano-keyboard.js +193 -0
- package/dist/midi/spectrum-visualizer.d.ts +65 -0
- package/dist/midi/spectrum-visualizer.js +213 -0
- package/dist/midi/threshold-rail.d.ts +72 -0
- package/dist/midi/threshold-rail.js +214 -0
- package/dist/midi/timeline-ruler.d.ts +83 -0
- package/dist/midi/timeline-ruler.js +152 -0
- package/dist/overlays/_debounce.js +34 -0
- package/dist/overlays/_glyphs.js +59 -0
- package/dist/overlays/_surface.d.ts +70 -0
- package/dist/overlays/_surface.js +70 -0
- package/dist/overlays/_wheel.js +42 -0
- package/dist/overlays/alert-dialog.d.ts +67 -0
- package/dist/overlays/alert-dialog.js +117 -0
- package/dist/overlays/command-palette.d.ts +93 -0
- package/dist/overlays/command-palette.js +193 -0
- package/dist/overlays/context-menu.d.ts +64 -0
- package/dist/overlays/context-menu.js +78 -0
- package/dist/overlays/dialog.d.ts +72 -0
- package/dist/overlays/dialog.js +124 -0
- package/dist/overlays/drawer.d.ts +75 -0
- package/dist/overlays/drawer.js +151 -0
- package/dist/overlays/dropdown-menu.d.ts +79 -0
- package/dist/overlays/dropdown-menu.js +156 -0
- package/dist/overlays/hover-card.d.ts +58 -0
- package/dist/overlays/hover-card.js +91 -0
- package/dist/overlays/index.d.ts +15 -0
- package/dist/overlays/index.js +15 -0
- package/dist/overlays/info-tip.d.ts +41 -0
- package/dist/overlays/info-tip.js +45 -0
- package/dist/overlays/menubar.d.ts +72 -0
- package/dist/overlays/menubar.js +100 -0
- package/dist/overlays/popover.d.ts +66 -0
- package/dist/overlays/popover.js +114 -0
- package/dist/overlays/sheet.d.ts +78 -0
- package/dist/overlays/sheet.js +131 -0
- package/dist/overlays/toast.d.ts +106 -0
- package/dist/overlays/toast.js +146 -0
- package/dist/overlays/tooltip.d.ts +57 -0
- package/dist/overlays/tooltip.js +95 -0
- package/dist/primitives/avatar.d.ts +51 -0
- package/dist/primitives/avatar.js +93 -0
- package/dist/primitives/button.d.ts +46 -0
- package/dist/primitives/button.js +94 -0
- package/dist/primitives/chip.d.ts +47 -0
- package/dist/primitives/chip.js +152 -0
- package/dist/primitives/icon-button.d.ts +41 -0
- package/dist/primitives/icon-button.js +59 -0
- package/dist/primitives/index.d.ts +10 -0
- package/dist/primitives/index.js +10 -0
- package/dist/primitives/kbd.d.ts +17 -0
- package/dist/primitives/kbd.js +24 -0
- package/dist/primitives/separator.d.ts +25 -0
- package/dist/primitives/separator.js +30 -0
- package/dist/primitives/skeleton.d.ts +20 -0
- package/dist/primitives/skeleton.js +27 -0
- package/dist/primitives/spinner.d.ts +26 -0
- package/dist/primitives/spinner.js +31 -0
- package/dist/primitives/status-dot.d.ts +44 -0
- package/dist/primitives/status-dot.js +50 -0
- package/dist/styles.css +19 -0
- package/dist/system/density.d.ts +33 -0
- package/dist/system/density.js +49 -0
- package/dist/system/index.d.ts +8 -0
- package/dist/system/index.js +7 -0
- package/dist/system/portal.d.ts +51 -0
- package/dist/system/portal.js +122 -0
- package/dist/system/prepaint.d.ts +38 -0
- package/dist/system/prepaint.js +37 -0
- package/dist/system/theme-provider.d.ts +72 -0
- package/dist/system/theme-provider.js +252 -0
- package/dist/system/use-density.d.ts +33 -0
- package/dist/system/use-density.js +84 -0
- package/dist/system/use-isomorphic-layout-effect.js +15 -0
- package/dist/system/use-theme.d.ts +19 -0
- package/dist/system/use-theme.js +25 -0
- package/dist/theming/_presets.d.ts +11 -0
- package/dist/theming/_presets.js +487 -0
- package/dist/theming/contrast.d.ts +110 -0
- package/dist/theming/contrast.js +167 -0
- package/dist/theming/create-theme.d.ts +165 -0
- package/dist/theming/create-theme.js +442 -0
- package/dist/theming/index.d.ts +5 -0
- package/dist/theming/index.js +5 -0
- package/dist/theming/serialize.d.ts +145 -0
- package/dist/theming/serialize.js +165 -0
- package/package.json +216 -0
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { cn } from "../lib/cn.js";
|
|
3
|
+
import { useIsomorphicLayoutEffect } from "../system/use-isomorphic-layout-effect.js";
|
|
4
|
+
import { useCuePortalProps } from "../system/portal.js";
|
|
5
|
+
import { toRem } from "../instruments/_rem.js";
|
|
6
|
+
import { mulberry32, usePrefersReducedMotion } from "../instruments/_motion.js";
|
|
7
|
+
import { dmxPalette, dmxPaletteColor } from "./_ramp.js";
|
|
8
|
+
import { DmxReadout } from "./_readout.js";
|
|
9
|
+
import * as React from "react";
|
|
10
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
11
|
+
//#region src/dmx/dmx-strip.tsx
|
|
12
|
+
/** Channels per simulated fixture — a small mover's worth. */
|
|
13
|
+
const FIXTURE_CHANNELS = 6;
|
|
14
|
+
/** Default column count: a strip, not a universe. */
|
|
15
|
+
const DEFAULT_CELLS = 128;
|
|
16
|
+
/** Default strip height in px at the 16px root. */
|
|
17
|
+
const DEFAULT_HEIGHT_PX = 14;
|
|
18
|
+
/** Level units a channel crossfades per tick — the desk's fade rate. */
|
|
19
|
+
const CROSSFADE_STEP = 24;
|
|
20
|
+
/** Minimum ms between ticks. Below this the crossfade outruns the eye. */
|
|
21
|
+
const TICK_MS = 70;
|
|
22
|
+
/** How often, in ms, some fixture is re-cued. */
|
|
23
|
+
const RECUE_MS = 150;
|
|
24
|
+
/** Spread the seed across the state space; consecutive seeds otherwise correlate. */
|
|
25
|
+
const SEED_MIX = 2654435761;
|
|
26
|
+
/**
|
|
27
|
+
* `DmxBar`'s picture with no console behind it — a lighting rig, simulated.
|
|
28
|
+
*
|
|
29
|
+
* This is the marketing device from dmxhub's site: the same 1px-tall pixelated
|
|
30
|
+
* canvas the bridge uses for real output, driven by a model of a plausible rig
|
|
31
|
+
* instead of a wire. Channels are grouped into six-channel fixtures; most sit
|
|
32
|
+
* where they were left, some are dark, and every so often one re-cues and
|
|
33
|
+
* crossfades to its new level. That grouping is what makes it read as *lighting*
|
|
34
|
+
* rather than as noise — a random 512 bytes looks like static, and a rig does
|
|
35
|
+
* not.
|
|
36
|
+
*
|
|
37
|
+
* Everything about it is seeded, including the re-cues the original left to
|
|
38
|
+
* `Math.random()`. A screenshot, a visual-regression run and a test all see the
|
|
39
|
+
* same rig from the same `seed`.
|
|
40
|
+
*
|
|
41
|
+
* Under `prefers-reduced-motion` the strip paints its first frame and stops.
|
|
42
|
+
* There is no information in the animation — the point of the component is the
|
|
43
|
+
* texture — so stilling it costs the reader nothing.
|
|
44
|
+
*
|
|
45
|
+
* @example
|
|
46
|
+
* <DmxStrip seed={7} />
|
|
47
|
+
* @example
|
|
48
|
+
* <DmxStrip cells={256} simulate={false} label="Rig activity" />
|
|
49
|
+
*/
|
|
50
|
+
const DmxStrip = React.forwardRef(function DmxStrip({ className, cells = DEFAULT_CELLS, height = DEFAULT_HEIGHT_PX, seed = 1, simulate = true, label, ...elementProps }, ref) {
|
|
51
|
+
const canvasRef = React.useRef(null);
|
|
52
|
+
const simulationRef = React.useRef(null);
|
|
53
|
+
const hoverCellRef = React.useRef(null);
|
|
54
|
+
const valueElementRef = React.useRef(null);
|
|
55
|
+
const [hover, setHover] = React.useState(null);
|
|
56
|
+
const portal = useCuePortalProps();
|
|
57
|
+
const reduced = usePrefersReducedMotion();
|
|
58
|
+
const animating = simulate && !reduced;
|
|
59
|
+
useIsomorphicLayoutEffect(() => {
|
|
60
|
+
const context = canvasRef.current?.getContext("2d");
|
|
61
|
+
if (!context) return;
|
|
62
|
+
const values = new Float32Array(cells);
|
|
63
|
+
const targets = new Float32Array(cells);
|
|
64
|
+
const palette = dmxPalette("mono");
|
|
65
|
+
const random = mulberry32(seed * SEED_MIX >>> 0);
|
|
66
|
+
const cue = (fixture) => {
|
|
67
|
+
const level = random() < .58 ? random() < .45 ? 1 : .3 + random() * .6 : 0;
|
|
68
|
+
for (let channel = 0; channel < FIXTURE_CHANNELS; channel += 1) {
|
|
69
|
+
const slot = fixture * FIXTURE_CHANNELS + channel;
|
|
70
|
+
if (slot >= cells) break;
|
|
71
|
+
if (level === 0) targets[slot] = random() < .1 ? random() * 36 : 0;
|
|
72
|
+
else if (channel === 0) targets[slot] = level * 255;
|
|
73
|
+
else {
|
|
74
|
+
const roll = random();
|
|
75
|
+
const raw = roll < .34 ? 0 : roll < .58 ? 255 : roll < .78 ? 128 : Math.floor(random() * 255);
|
|
76
|
+
targets[slot] = raw * (.5 + level * .5);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
};
|
|
80
|
+
const syncReadout = () => {
|
|
81
|
+
const cell = hoverCellRef.current;
|
|
82
|
+
const element = valueElementRef.current;
|
|
83
|
+
if (cell === null || element === null) return;
|
|
84
|
+
element.textContent = String(Math.round(values[cell]));
|
|
85
|
+
};
|
|
86
|
+
const paint = () => {
|
|
87
|
+
for (let slot = 0; slot < cells; slot += 1) {
|
|
88
|
+
context.fillStyle = dmxPaletteColor(palette, values[slot]);
|
|
89
|
+
context.fillRect(slot, 0, 1, 1);
|
|
90
|
+
}
|
|
91
|
+
syncReadout();
|
|
92
|
+
};
|
|
93
|
+
const fixtures = Math.ceil(cells / FIXTURE_CHANNELS);
|
|
94
|
+
for (let fixture = 0; fixture < fixtures; fixture += 1) cue(fixture);
|
|
95
|
+
values.set(targets);
|
|
96
|
+
simulationRef.current = {
|
|
97
|
+
values,
|
|
98
|
+
targets,
|
|
99
|
+
palette,
|
|
100
|
+
random,
|
|
101
|
+
cue,
|
|
102
|
+
paint
|
|
103
|
+
};
|
|
104
|
+
paint();
|
|
105
|
+
return () => {
|
|
106
|
+
simulationRef.current = null;
|
|
107
|
+
};
|
|
108
|
+
}, [cells, seed]);
|
|
109
|
+
React.useEffect(() => {
|
|
110
|
+
if (!animating) return;
|
|
111
|
+
const fixtures = Math.ceil(cells / FIXTURE_CHANNELS);
|
|
112
|
+
let frame = 0;
|
|
113
|
+
let last = 0;
|
|
114
|
+
let sinceRecue = 0;
|
|
115
|
+
const loop = (now) => {
|
|
116
|
+
frame = requestAnimationFrame(loop);
|
|
117
|
+
const simulation = simulationRef.current;
|
|
118
|
+
if (simulation === null) return;
|
|
119
|
+
const elapsed = now - last;
|
|
120
|
+
if (elapsed < TICK_MS) return;
|
|
121
|
+
last = now;
|
|
122
|
+
const { values, targets } = simulation;
|
|
123
|
+
for (let slot = 0; slot < values.length; slot += 1) {
|
|
124
|
+
const delta = targets[slot] - values[slot];
|
|
125
|
+
if (delta !== 0) values[slot] = values[slot] + (Math.abs(delta) < CROSSFADE_STEP ? delta : Math.sign(delta) * CROSSFADE_STEP);
|
|
126
|
+
}
|
|
127
|
+
sinceRecue += elapsed;
|
|
128
|
+
if (sinceRecue > RECUE_MS) {
|
|
129
|
+
sinceRecue = 0;
|
|
130
|
+
const changes = 1 + Math.floor(simulation.random() * 2);
|
|
131
|
+
for (let change = 0; change < changes; change += 1) simulation.cue(Math.floor(simulation.random() * fixtures));
|
|
132
|
+
}
|
|
133
|
+
simulation.paint();
|
|
134
|
+
};
|
|
135
|
+
frame = requestAnimationFrame(loop);
|
|
136
|
+
return () => cancelAnimationFrame(frame);
|
|
137
|
+
}, [
|
|
138
|
+
animating,
|
|
139
|
+
cells,
|
|
140
|
+
seed
|
|
141
|
+
]);
|
|
142
|
+
const onMouseMove = (event) => {
|
|
143
|
+
const box = event.currentTarget.getBoundingClientRect();
|
|
144
|
+
if (box.width === 0) return;
|
|
145
|
+
const fraction = Math.min(.999, Math.max(0, (event.clientX - box.left) / box.width));
|
|
146
|
+
const cell = Math.min(cells - 1, Math.floor(fraction * cells));
|
|
147
|
+
hoverCellRef.current = cell;
|
|
148
|
+
setHover({
|
|
149
|
+
cell,
|
|
150
|
+
percent: fraction * 100,
|
|
151
|
+
x: event.clientX,
|
|
152
|
+
y: box.top
|
|
153
|
+
});
|
|
154
|
+
};
|
|
155
|
+
const onMouseLeave = () => {
|
|
156
|
+
hoverCellRef.current = null;
|
|
157
|
+
setHover(null);
|
|
158
|
+
};
|
|
159
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
160
|
+
ref,
|
|
161
|
+
"data-slot": "dmx-strip",
|
|
162
|
+
"data-animating": String(animating),
|
|
163
|
+
className: cn("relative min-w-0", className),
|
|
164
|
+
onMouseMove,
|
|
165
|
+
onMouseLeave,
|
|
166
|
+
...elementProps,
|
|
167
|
+
children: [
|
|
168
|
+
/* @__PURE__ */ jsx("canvas", {
|
|
169
|
+
ref: canvasRef,
|
|
170
|
+
width: cells,
|
|
171
|
+
height: 1,
|
|
172
|
+
role: "img",
|
|
173
|
+
"aria-label": label ?? "Simulated DMX activity",
|
|
174
|
+
"data-slot": "dmx-strip-canvas",
|
|
175
|
+
className: "block w-full cursor-crosshair rounded-(--radius-control) border border-border bg-(--cue-data-ground) [image-rendering:pixelated]",
|
|
176
|
+
style: { height: toRem(height) }
|
|
177
|
+
}),
|
|
178
|
+
hover === null ? null : /* @__PURE__ */ jsx("span", {
|
|
179
|
+
"aria-hidden": "true",
|
|
180
|
+
"data-slot": "dmx-strip-crosshair",
|
|
181
|
+
className: "pointer-events-none absolute -inset-y-px w-px bg-accent",
|
|
182
|
+
style: { left: `${hover.percent}%` }
|
|
183
|
+
}),
|
|
184
|
+
hover === null ? null : /* @__PURE__ */ jsx(DmxReadout, {
|
|
185
|
+
container: portal.container,
|
|
186
|
+
channel: hover.cell + 1,
|
|
187
|
+
value: Math.round(simulationRef.current?.values[hover.cell] ?? 0),
|
|
188
|
+
x: hover.x,
|
|
189
|
+
y: hover.y,
|
|
190
|
+
valueRef: valueElementRef
|
|
191
|
+
})
|
|
192
|
+
]
|
|
193
|
+
});
|
|
194
|
+
});
|
|
195
|
+
//#endregion
|
|
196
|
+
export { DmxStrip };
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { DMX_RAMP_NAMES, DMX_SLOTS, DmxRamp, VALUE_RAMP_TOKENS, activeChannels, dmxPalette, dmxPaletteColor, dmxPaletteInk } from "./_ramp.js";
|
|
2
|
+
import { AllocationBlock, ChannelRange, DmxAllocation, DmxTone, UNIVERSE_TICKS, UniverseLayout, channelPercent, universeLayout } from "./_universe.js";
|
|
3
|
+
import { CHANNEL_MATRIX_COLUMNS, CHANNEL_MATRIX_ROWS, ChannelMatrix, ChannelMatrixMode, ChannelMatrixProps } from "./channel-matrix.js";
|
|
4
|
+
import { DmxBar, DmxBarProps } from "./dmx-bar.js";
|
|
5
|
+
import { DmxStrip, DmxStripProps } from "./dmx-strip.js";
|
|
6
|
+
import { UniverseGrid, UniverseGridProps } from "./universe-grid.js";
|
|
7
|
+
export { type AllocationBlock, CHANNEL_MATRIX_COLUMNS, CHANNEL_MATRIX_ROWS, ChannelMatrix, type ChannelMatrixMode, type ChannelMatrixProps, type ChannelRange, DMX_RAMP_NAMES, DMX_SLOTS, type DmxAllocation, DmxBar, type DmxBarProps, type DmxRamp, DmxStrip, type DmxStripProps, type DmxTone, UNIVERSE_TICKS, UniverseGrid, type UniverseGridProps, type UniverseLayout, VALUE_RAMP_TOKENS, activeChannels, channelPercent, dmxPalette, dmxPaletteColor, dmxPaletteInk, universeLayout };
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { DMX_RAMP_NAMES, DMX_SLOTS, VALUE_RAMP_TOKENS, activeChannels, dmxPalette, dmxPaletteColor, dmxPaletteInk } from "./_ramp.js";
|
|
2
|
+
import { UNIVERSE_TICKS, channelPercent, universeLayout } from "./_universe.js";
|
|
3
|
+
import { CHANNEL_MATRIX_COLUMNS, CHANNEL_MATRIX_ROWS, ChannelMatrix } from "./channel-matrix.js";
|
|
4
|
+
import { DmxBar } from "./dmx-bar.js";
|
|
5
|
+
import { DmxStrip } from "./dmx-strip.js";
|
|
6
|
+
import { UniverseGrid } from "./universe-grid.js";
|
|
7
|
+
export { CHANNEL_MATRIX_COLUMNS, CHANNEL_MATRIX_ROWS, ChannelMatrix, DMX_RAMP_NAMES, DMX_SLOTS, DmxBar, DmxStrip, UNIVERSE_TICKS, UniverseGrid, VALUE_RAMP_TOKENS, activeChannels, channelPercent, dmxPalette, dmxPaletteColor, dmxPaletteInk, universeLayout };
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { DmxAllocation } from "./_universe.js";
|
|
2
|
+
import * as React from "react";
|
|
3
|
+
//#region src/dmx/universe-grid.d.ts
|
|
4
|
+
interface UniverseGridProps extends Omit<React.ComponentPropsWithoutRef<"div">, "children" | "onSelect"> {
|
|
5
|
+
/** The patch. Order is display order; overlaps are found, not assumed away. */
|
|
6
|
+
allocations: readonly DmxAllocation[];
|
|
7
|
+
/** Which universe this is. Names the graphic; also the group label when selectable. */
|
|
8
|
+
label?: string;
|
|
9
|
+
/** Frame length. Defaults to a full universe of 512. */
|
|
10
|
+
slots?: number;
|
|
11
|
+
/** Bar height in px at the 16px root, emitted as `rem`. Defaults to `18`. */
|
|
12
|
+
height?: number;
|
|
13
|
+
/** Draw the address ruler. Defaults to `true`. */
|
|
14
|
+
ruler?: boolean;
|
|
15
|
+
/** Draw the legend row. Defaults to `true`. */
|
|
16
|
+
legend?: boolean;
|
|
17
|
+
/** Legend entries before the tail collapses. Defaults to `6`. */
|
|
18
|
+
legendMax?: number;
|
|
19
|
+
/**
|
|
20
|
+
* Make the blocks buttons. Without it they are decoration inside one
|
|
21
|
+
* `role="img"`, which is what keeps a 40-fixture patch from becoming 40 tab
|
|
22
|
+
* stops.
|
|
23
|
+
*/
|
|
24
|
+
onSelect?: (allocation: DmxAllocation) => void;
|
|
25
|
+
/** What to say when nothing is patched. Defaults to `"No patch assignments"`. */
|
|
26
|
+
emptyLabel?: string;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* A universe as 512 slots of occupancy: who holds what, and where two fixtures
|
|
30
|
+
* were given the same address.
|
|
31
|
+
*
|
|
32
|
+
* Ported from stagetool's patching panel, which is the only audited product with
|
|
33
|
+
* a real patch model behind it. What it draws is deliberately not a table:
|
|
34
|
+
*
|
|
35
|
+
* - **Position is the address.** A block's left edge *is* channel `start`, so the
|
|
36
|
+
* shape of a patch — the gaps, the drift, the fixture that starts at 497 —
|
|
37
|
+
* reads without anyone counting.
|
|
38
|
+
* - **A one-channel fixture is still visible.** Blocks have a two-pixel floor,
|
|
39
|
+
* which is the whole reason the bar can honestly claim to show everything
|
|
40
|
+
* patched.
|
|
41
|
+
* - **A conflict is drawn twice.** Both offending blocks get a danger ring *and*
|
|
42
|
+
* the contested slots get a band, because "these two overlap" and "here is
|
|
43
|
+
* where" are two different questions.
|
|
44
|
+
*
|
|
45
|
+
* Accessibility follows the `GroupBar` rule: colour is never the only channel.
|
|
46
|
+
* Without `onSelect` the bar is one `role="img"` whose name spells the whole
|
|
47
|
+
* patch out; with it, each block becomes a named button and the bar becomes a
|
|
48
|
+
* group.
|
|
49
|
+
*
|
|
50
|
+
* @example
|
|
51
|
+
* <UniverseGrid
|
|
52
|
+
* label="Universe 1"
|
|
53
|
+
* allocations={[
|
|
54
|
+
* { label: "wash", start: 1, size: 24, tone: "accent" },
|
|
55
|
+
* { label: "movers", start: 129, size: 32, tone: "info" },
|
|
56
|
+
* ]}
|
|
57
|
+
* />
|
|
58
|
+
*/
|
|
59
|
+
declare const UniverseGrid: React.ForwardRefExoticComponent<UniverseGridProps & React.RefAttributes<HTMLDivElement>>;
|
|
60
|
+
//#endregion
|
|
61
|
+
export { UniverseGrid, UniverseGridProps };
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { cn } from "../lib/cn.js";
|
|
3
|
+
import { toRem } from "../instruments/_rem.js";
|
|
4
|
+
import "./_ramp.js";
|
|
5
|
+
import { UNIVERSE_TICKS, channelPercent, universeLayout } from "./_universe.js";
|
|
6
|
+
import * as React from "react";
|
|
7
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
8
|
+
//#region src/dmx/universe-grid.tsx
|
|
9
|
+
const BLOCK_TONES = {
|
|
10
|
+
neutral: "bg-fg-subtle",
|
|
11
|
+
accent: "bg-accent",
|
|
12
|
+
ok: "bg-ok",
|
|
13
|
+
busy: "bg-busy",
|
|
14
|
+
warn: "bg-warn",
|
|
15
|
+
danger: "bg-danger",
|
|
16
|
+
info: "bg-info"
|
|
17
|
+
};
|
|
18
|
+
/** Guides every 64 channels — the ruler's addresses, minus the two edges. */
|
|
19
|
+
const GRIDLINES = UNIVERSE_TICKS.slice(1, -1);
|
|
20
|
+
/** Default bar height in px at the 16px root: tall enough to carry a fixture name. */
|
|
21
|
+
const DEFAULT_HEIGHT_PX = 18;
|
|
22
|
+
/** Legend entries before the tail collapses into `+N more`. */
|
|
23
|
+
const DEFAULT_LEGEND_MAX = 6;
|
|
24
|
+
/** `1 conflict` / `3 conflicts`, or nothing at all. */
|
|
25
|
+
function conflictLabel(count) {
|
|
26
|
+
return `${count} conflict${count === 1 ? "" : "s"}`;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* A universe as 512 slots of occupancy: who holds what, and where two fixtures
|
|
30
|
+
* were given the same address.
|
|
31
|
+
*
|
|
32
|
+
* Ported from stagetool's patching panel, which is the only audited product with
|
|
33
|
+
* a real patch model behind it. What it draws is deliberately not a table:
|
|
34
|
+
*
|
|
35
|
+
* - **Position is the address.** A block's left edge *is* channel `start`, so the
|
|
36
|
+
* shape of a patch — the gaps, the drift, the fixture that starts at 497 —
|
|
37
|
+
* reads without anyone counting.
|
|
38
|
+
* - **A one-channel fixture is still visible.** Blocks have a two-pixel floor,
|
|
39
|
+
* which is the whole reason the bar can honestly claim to show everything
|
|
40
|
+
* patched.
|
|
41
|
+
* - **A conflict is drawn twice.** Both offending blocks get a danger ring *and*
|
|
42
|
+
* the contested slots get a band, because "these two overlap" and "here is
|
|
43
|
+
* where" are two different questions.
|
|
44
|
+
*
|
|
45
|
+
* Accessibility follows the `GroupBar` rule: colour is never the only channel.
|
|
46
|
+
* Without `onSelect` the bar is one `role="img"` whose name spells the whole
|
|
47
|
+
* patch out; with it, each block becomes a named button and the bar becomes a
|
|
48
|
+
* group.
|
|
49
|
+
*
|
|
50
|
+
* @example
|
|
51
|
+
* <UniverseGrid
|
|
52
|
+
* label="Universe 1"
|
|
53
|
+
* allocations={[
|
|
54
|
+
* { label: "wash", start: 1, size: 24, tone: "accent" },
|
|
55
|
+
* { label: "movers", start: 129, size: 32, tone: "info" },
|
|
56
|
+
* ]}
|
|
57
|
+
* />
|
|
58
|
+
*/
|
|
59
|
+
const UniverseGrid = React.forwardRef(function UniverseGrid({ className, allocations, label, slots = 512, height = DEFAULT_HEIGHT_PX, ruler = true, legend = true, legendMax = DEFAULT_LEGEND_MAX, onSelect, emptyLabel = "No patch assignments", ...elementProps }, ref) {
|
|
60
|
+
const layout = React.useMemo(() => universeLayout(allocations, slots), [allocations, slots]);
|
|
61
|
+
const interactive = onSelect !== void 0;
|
|
62
|
+
const empty = layout.blocks.length === 0;
|
|
63
|
+
const shown = layout.blocks.slice(0, Math.max(0, legendMax));
|
|
64
|
+
const hidden = layout.blocks.length - shown.length;
|
|
65
|
+
const summary = [...layout.blocks.map((block) => `${block.label} ch ${block.start}-${block.end}`), ...layout.conflicts.length > 0 ? [conflictLabel(layout.conflicts.length)] : []].join(", ");
|
|
66
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
67
|
+
ref,
|
|
68
|
+
"data-slot": "universe-grid",
|
|
69
|
+
"data-empty": empty ? "true" : void 0,
|
|
70
|
+
className: cn("flex min-w-0 flex-col gap-(--cue-space-2)", className),
|
|
71
|
+
...elementProps,
|
|
72
|
+
children: [
|
|
73
|
+
ruler ? /* @__PURE__ */ jsx("div", {
|
|
74
|
+
"aria-hidden": "true",
|
|
75
|
+
"data-slot": "universe-grid-ruler",
|
|
76
|
+
className: "relative h-(--cue-space-4) w-full",
|
|
77
|
+
children: UNIVERSE_TICKS.filter((channel) => channel <= slots).map((channel) => /* @__PURE__ */ jsx("span", {
|
|
78
|
+
"data-slot": "universe-grid-tick",
|
|
79
|
+
className: "absolute -translate-x-1/2 font-mono text-(length:--cue-text-micro) tabular-nums text-fg-subtle",
|
|
80
|
+
style: { left: `${channelPercent(channel, slots)}%` },
|
|
81
|
+
children: channel
|
|
82
|
+
}, channel))
|
|
83
|
+
}) : null,
|
|
84
|
+
/* @__PURE__ */ jsxs("div", {
|
|
85
|
+
role: interactive ? "group" : "img",
|
|
86
|
+
"aria-label": interactive ? label ?? "Universe" : label === void 0 ? summary : `${label}: ${summary}`,
|
|
87
|
+
"data-slot": "universe-grid-track",
|
|
88
|
+
className: "relative w-full overflow-hidden rounded-(--radius-control) border border-border bg-sunken",
|
|
89
|
+
style: { height: toRem(height) },
|
|
90
|
+
children: [
|
|
91
|
+
GRIDLINES.filter((channel) => channel <= slots).map((channel) => /* @__PURE__ */ jsx("span", {
|
|
92
|
+
"aria-hidden": "true",
|
|
93
|
+
"data-slot": "universe-grid-gridline",
|
|
94
|
+
className: "absolute inset-y-0 w-px bg-border",
|
|
95
|
+
style: { left: `${channelPercent(channel, slots)}%` }
|
|
96
|
+
}, channel)),
|
|
97
|
+
layout.conflicts.map((range) => /* @__PURE__ */ jsx("span", {
|
|
98
|
+
"aria-hidden": "true",
|
|
99
|
+
"data-slot": "universe-grid-conflict",
|
|
100
|
+
className: "pointer-events-none absolute inset-y-0 border-x border-danger bg-(--cue-danger-soft)",
|
|
101
|
+
style: {
|
|
102
|
+
left: `${channelPercent(range.start, slots)}%`,
|
|
103
|
+
width: `${(range.end - range.start + 1) / slots * 100}%`
|
|
104
|
+
}
|
|
105
|
+
}, `${range.start}-${range.end}`)),
|
|
106
|
+
layout.blocks.map((block) => interactive ? /* @__PURE__ */ jsx("button", {
|
|
107
|
+
type: "button",
|
|
108
|
+
"data-slot": "universe-grid-block",
|
|
109
|
+
"data-tone": block.tone,
|
|
110
|
+
"data-conflict": block.conflict ? "true" : void 0,
|
|
111
|
+
"aria-label": `${block.label}: channels ${block.start} to ${block.end}`,
|
|
112
|
+
onClick: () => onSelect(block.allocation),
|
|
113
|
+
className: cn("absolute inset-y-0 min-w-[2px] cursor-pointer rounded-[2px] outline-none transition-opacity duration-150 hover:opacity-85 focus-visible:outline-2 focus-visible:outline-solid focus-visible:outline-offset-1 focus-visible:outline-accent motion-reduce:transition-none", BLOCK_TONES[block.tone], block.conflict ? "z-10 ring-1 ring-danger" : void 0),
|
|
114
|
+
style: {
|
|
115
|
+
left: `${block.left}%`,
|
|
116
|
+
width: `${block.width}%`
|
|
117
|
+
}
|
|
118
|
+
}, block.key) : /* @__PURE__ */ jsx("span", {
|
|
119
|
+
"aria-hidden": "true",
|
|
120
|
+
"data-slot": "universe-grid-block",
|
|
121
|
+
"data-tone": block.tone,
|
|
122
|
+
"data-conflict": block.conflict ? "true" : void 0,
|
|
123
|
+
className: cn("absolute inset-y-0 min-w-[2px] rounded-[2px]", BLOCK_TONES[block.tone], block.conflict ? "z-10 ring-1 ring-danger" : void 0),
|
|
124
|
+
style: {
|
|
125
|
+
left: `${block.left}%`,
|
|
126
|
+
width: `${block.width}%`
|
|
127
|
+
}
|
|
128
|
+
}, block.key))
|
|
129
|
+
]
|
|
130
|
+
}),
|
|
131
|
+
empty ? /* @__PURE__ */ jsx("p", {
|
|
132
|
+
"data-slot": "universe-grid-empty",
|
|
133
|
+
className: "font-mono text-(length:--cue-text-label) text-fg-subtle",
|
|
134
|
+
children: emptyLabel
|
|
135
|
+
}) : null,
|
|
136
|
+
legend && !empty ? /* @__PURE__ */ jsxs("div", {
|
|
137
|
+
"aria-hidden": "true",
|
|
138
|
+
"data-slot": "universe-grid-legend",
|
|
139
|
+
className: "flex flex-wrap items-center gap-x-(--cue-space-5) gap-y-(--cue-space-2) font-mono text-(length:--cue-text-label) text-fg-muted",
|
|
140
|
+
children: [
|
|
141
|
+
shown.map((block) => /* @__PURE__ */ jsxs("span", {
|
|
142
|
+
className: "inline-flex items-center gap-(--cue-space-2)",
|
|
143
|
+
children: [
|
|
144
|
+
/* @__PURE__ */ jsx("span", { className: cn("size-[0.375rem] shrink-0 rounded-full", BLOCK_TONES[block.tone]) }),
|
|
145
|
+
/* @__PURE__ */ jsx("span", {
|
|
146
|
+
className: "truncate",
|
|
147
|
+
children: block.label
|
|
148
|
+
}),
|
|
149
|
+
/* @__PURE__ */ jsxs("span", {
|
|
150
|
+
className: "tabular-nums text-fg-subtle",
|
|
151
|
+
children: [
|
|
152
|
+
block.start,
|
|
153
|
+
"-",
|
|
154
|
+
block.end
|
|
155
|
+
]
|
|
156
|
+
})
|
|
157
|
+
]
|
|
158
|
+
}, block.key)),
|
|
159
|
+
hidden > 0 ? /* @__PURE__ */ jsxs("span", {
|
|
160
|
+
className: "text-fg-subtle",
|
|
161
|
+
children: [
|
|
162
|
+
"+",
|
|
163
|
+
hidden,
|
|
164
|
+
" more"
|
|
165
|
+
]
|
|
166
|
+
}) : null,
|
|
167
|
+
/* @__PURE__ */ jsx("span", { className: "flex-1" }),
|
|
168
|
+
layout.conflicts.length > 0 ? /* @__PURE__ */ jsx("span", {
|
|
169
|
+
className: "tabular-nums text-danger",
|
|
170
|
+
children: conflictLabel(layout.conflicts.length)
|
|
171
|
+
}) : null,
|
|
172
|
+
/* @__PURE__ */ jsxs("span", {
|
|
173
|
+
className: "tabular-nums text-fg-subtle",
|
|
174
|
+
children: [layout.utilisation, "% patched"]
|
|
175
|
+
})
|
|
176
|
+
]
|
|
177
|
+
}) : null
|
|
178
|
+
]
|
|
179
|
+
});
|
|
180
|
+
});
|
|
181
|
+
//#endregion
|
|
182
|
+
export { UniverseGrid };
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
//#region src/flow/_frame.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* On a graph canvas the frame *is* the state.
|
|
4
|
+
*
|
|
5
|
+
* A node in a signal graph has to answer two questions from across the room:
|
|
6
|
+
* what kind of thing is this, and what is it doing right now. Text answers
|
|
7
|
+
* neither at 60% zoom. A coloured frame answers both, which is why every console
|
|
8
|
+
* this group is ported from — dmxhub's bridge above all — encodes node state as
|
|
9
|
+
* the colour of a one-pixel rim and nothing else.
|
|
10
|
+
*
|
|
11
|
+
* Two axes, resolved in one order:
|
|
12
|
+
*
|
|
13
|
+
* - **State wins over tone.** A selected node is a selected node whether it is an
|
|
14
|
+
* input or a merge; a node carrying live signal is the accent whatever it does
|
|
15
|
+
* with that signal. Identity only shows through when the node has nothing more
|
|
16
|
+
* urgent to say.
|
|
17
|
+
* - **Tone is the resting identity.** Input reads as `info` (it comes from
|
|
18
|
+
* elsewhere), output as `accent` (it is what this console emits), and anything
|
|
19
|
+
* in between as the quiet grey that also serves as the default.
|
|
20
|
+
*
|
|
21
|
+
* The colour is published as a custom property rather than painted directly, so
|
|
22
|
+
* a React Flow node wrapper can set it once (`useFlowTheme().nodeStyles`) and
|
|
23
|
+
* every card inside inherits it — and so a card that says nothing about tone or
|
|
24
|
+
* state stays inheritable instead of overriding its own graph.
|
|
25
|
+
*/
|
|
26
|
+
/** What kind of node this is: where it sits in the signal path. */
|
|
27
|
+
type FlowNodeTone = "input" | "output" | "processing";
|
|
28
|
+
/** What the node is doing right now. Outranks {@link FlowNodeTone}. */
|
|
29
|
+
type FlowNodeState = "idle" | "live" | "selected" | "disabled";
|
|
30
|
+
/** The custom property a node's frame colour travels on. */
|
|
31
|
+
declare const FLOW_NODE_FRAME_PROPERTY = "--cue-node-frame";
|
|
32
|
+
/** The frame of a node that has published no colour of its own. */
|
|
33
|
+
declare const FLOW_NODE_FRAME_FALLBACK = "var(--cue-fg-subtle)";
|
|
34
|
+
/** `outline-color` for any node frame: the published colour, else the grey. */
|
|
35
|
+
declare const FLOW_NODE_FRAME_COLOR = "var(--cue-node-frame, var(--cue-fg-subtle))";
|
|
36
|
+
/**
|
|
37
|
+
* The frame colour for a node, as a token reference.
|
|
38
|
+
*
|
|
39
|
+
* Returns `undefined` when neither axis says anything — the caller then leaves
|
|
40
|
+
* the property unset and inherits whatever the node wrapper published, which is
|
|
41
|
+
* what makes `nodeStyles` work.
|
|
42
|
+
*
|
|
43
|
+
* @param tone - Where the node sits in the signal path.
|
|
44
|
+
* @param state - What it is doing right now. Outranks `tone`.
|
|
45
|
+
* @returns A `var(--cue-*)` reference, or `undefined` to inherit.
|
|
46
|
+
* @example
|
|
47
|
+
* flowFrameColor("input"); // "var(--cue-info)"
|
|
48
|
+
* flowFrameColor("input", "selected"); // "var(--cue-fg)"
|
|
49
|
+
*/
|
|
50
|
+
declare function flowFrameColor(tone?: FlowNodeTone, state?: FlowNodeState): string | undefined;
|
|
51
|
+
//#endregion
|
|
52
|
+
export { FLOW_NODE_FRAME_COLOR, FLOW_NODE_FRAME_FALLBACK, FLOW_NODE_FRAME_PROPERTY, FlowNodeState, FlowNodeTone, flowFrameColor };
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
//#region src/flow/_frame.ts
|
|
2
|
+
/** The custom property a node's frame colour travels on. */
|
|
3
|
+
const FLOW_NODE_FRAME_PROPERTY = "--cue-node-frame";
|
|
4
|
+
/** The frame of a node that has published no colour of its own. */
|
|
5
|
+
const FLOW_NODE_FRAME_FALLBACK = "var(--cue-fg-subtle)";
|
|
6
|
+
/** `outline-color` for any node frame: the published colour, else the grey. */
|
|
7
|
+
const FLOW_NODE_FRAME_COLOR = `var(${FLOW_NODE_FRAME_PROPERTY}, ${FLOW_NODE_FRAME_FALLBACK})`;
|
|
8
|
+
const TONE_FRAMES = {
|
|
9
|
+
input: "var(--cue-info)",
|
|
10
|
+
output: "var(--cue-accent)",
|
|
11
|
+
processing: FLOW_NODE_FRAME_FALLBACK
|
|
12
|
+
};
|
|
13
|
+
/** `idle` is deliberately absent: an idle node falls through to its tone. */
|
|
14
|
+
const STATE_FRAMES = {
|
|
15
|
+
selected: "var(--cue-fg)",
|
|
16
|
+
live: "var(--cue-accent)",
|
|
17
|
+
disabled: FLOW_NODE_FRAME_FALLBACK
|
|
18
|
+
};
|
|
19
|
+
/**
|
|
20
|
+
* The frame colour for a node, as a token reference.
|
|
21
|
+
*
|
|
22
|
+
* Returns `undefined` when neither axis says anything — the caller then leaves
|
|
23
|
+
* the property unset and inherits whatever the node wrapper published, which is
|
|
24
|
+
* what makes `nodeStyles` work.
|
|
25
|
+
*
|
|
26
|
+
* @param tone - Where the node sits in the signal path.
|
|
27
|
+
* @param state - What it is doing right now. Outranks `tone`.
|
|
28
|
+
* @returns A `var(--cue-*)` reference, or `undefined` to inherit.
|
|
29
|
+
* @example
|
|
30
|
+
* flowFrameColor("input"); // "var(--cue-info)"
|
|
31
|
+
* flowFrameColor("input", "selected"); // "var(--cue-fg)"
|
|
32
|
+
*/
|
|
33
|
+
function flowFrameColor(tone, state) {
|
|
34
|
+
if (state !== void 0 && state in STATE_FRAMES) return STATE_FRAMES[state];
|
|
35
|
+
return tone === void 0 ? void 0 : TONE_FRAMES[tone];
|
|
36
|
+
}
|
|
37
|
+
//#endregion
|
|
38
|
+
export { FLOW_NODE_FRAME_COLOR, FLOW_NODE_FRAME_FALLBACK, FLOW_NODE_FRAME_PROPERTY, flowFrameColor };
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { Background, Controls } from "@xyflow/react";
|
|
3
|
+
//#region src/flow/controls.d.ts
|
|
4
|
+
/**
|
|
5
|
+
* The two library panels, wearing the token map.
|
|
6
|
+
*
|
|
7
|
+
* Both do the same small thing: publish {@link FLOW_THEME_VARIABLES} on their own
|
|
8
|
+
* root so the library's `var(--xy-*)` chains resolve to tokens, whether or not
|
|
9
|
+
* the app imported `flow.css` and whether or not it spread `theme.style` on the
|
|
10
|
+
* canvas. Custom properties are inherited and cost nothing to declare twice, and
|
|
11
|
+
* the alternative — Tailwind utilities — cannot win here: the library's
|
|
12
|
+
* stylesheet is unlayered, and every layered utility loses to an unlayered rule
|
|
13
|
+
* of equal specificity no matter which file loads last.
|
|
14
|
+
*/
|
|
15
|
+
interface FlowControlsProps extends React.ComponentProps<typeof Controls> {}
|
|
16
|
+
/**
|
|
17
|
+
* The zoom / fit / lock capsule, restyled as an overlay surface.
|
|
18
|
+
*
|
|
19
|
+
* Shape is inline for the same reason the colours are: `border-radius` and
|
|
20
|
+
* `overflow` have no `--xy-*` hook, and the library's own rule would otherwise
|
|
21
|
+
* win. `overflow: hidden` is what makes the first and last buttons take the
|
|
22
|
+
* capsule's corners instead of squaring them off.
|
|
23
|
+
*
|
|
24
|
+
* @example
|
|
25
|
+
* <ReactFlow …><FlowControls /></ReactFlow>
|
|
26
|
+
*/
|
|
27
|
+
declare function FlowControls({ style, ...controlsProps }: FlowControlsProps): React.JSX.Element;
|
|
28
|
+
interface FlowBackgroundProps extends React.ComponentProps<typeof Background> {}
|
|
29
|
+
/**
|
|
30
|
+
* The canvas grid: dots one rung off the ground, at the system's own spacing.
|
|
31
|
+
*
|
|
32
|
+
* Leave `color` unset. It writes `--xy-background-pattern-color-props`, which
|
|
33
|
+
* outranks the theme map — passing a colour is how you opt *out* of theming, not
|
|
34
|
+
* how you theme it.
|
|
35
|
+
*
|
|
36
|
+
* @example
|
|
37
|
+
* <ReactFlow …><FlowBackground /></ReactFlow>
|
|
38
|
+
*/
|
|
39
|
+
declare function FlowBackground({ gap, style, ...backgroundProps }: FlowBackgroundProps): React.JSX.Element;
|
|
40
|
+
//#endregion
|
|
41
|
+
export { FlowBackground, FlowBackgroundProps, FlowControls, FlowControlsProps };
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { FLOW_THEME_VARIABLES } from "./use-flow-theme.js";
|
|
3
|
+
import "react";
|
|
4
|
+
import { jsx } from "react/jsx-runtime";
|
|
5
|
+
import { Background, Controls } from "@xyflow/react";
|
|
6
|
+
//#region src/flow/controls.tsx
|
|
7
|
+
/** Dots every 16 flow units — the same 1rem rhythm the rest of the system is on. */
|
|
8
|
+
const DOT_GAP = 16;
|
|
9
|
+
/**
|
|
10
|
+
* The zoom / fit / lock capsule, restyled as an overlay surface.
|
|
11
|
+
*
|
|
12
|
+
* Shape is inline for the same reason the colours are: `border-radius` and
|
|
13
|
+
* `overflow` have no `--xy-*` hook, and the library's own rule would otherwise
|
|
14
|
+
* win. `overflow: hidden` is what makes the first and last buttons take the
|
|
15
|
+
* capsule's corners instead of squaring them off.
|
|
16
|
+
*
|
|
17
|
+
* @example
|
|
18
|
+
* <ReactFlow …><FlowControls /></ReactFlow>
|
|
19
|
+
*/
|
|
20
|
+
function FlowControls({ style, ...controlsProps }) {
|
|
21
|
+
return /* @__PURE__ */ jsx(Controls, {
|
|
22
|
+
style: {
|
|
23
|
+
...FLOW_THEME_VARIABLES,
|
|
24
|
+
border: "1px solid var(--cue-border-overlay)",
|
|
25
|
+
borderRadius: "var(--cue-radius-overlay)",
|
|
26
|
+
overflow: "hidden",
|
|
27
|
+
...style
|
|
28
|
+
},
|
|
29
|
+
...controlsProps
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* The canvas grid: dots one rung off the ground, at the system's own spacing.
|
|
34
|
+
*
|
|
35
|
+
* Leave `color` unset. It writes `--xy-background-pattern-color-props`, which
|
|
36
|
+
* outranks the theme map — passing a colour is how you opt *out* of theming, not
|
|
37
|
+
* how you theme it.
|
|
38
|
+
*
|
|
39
|
+
* @example
|
|
40
|
+
* <ReactFlow …><FlowBackground /></ReactFlow>
|
|
41
|
+
*/
|
|
42
|
+
function FlowBackground({ gap = DOT_GAP, style, ...backgroundProps }) {
|
|
43
|
+
return /* @__PURE__ */ jsx(Background, {
|
|
44
|
+
gap,
|
|
45
|
+
style: {
|
|
46
|
+
...FLOW_THEME_VARIABLES,
|
|
47
|
+
...style
|
|
48
|
+
},
|
|
49
|
+
...backgroundProps
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
//#endregion
|
|
53
|
+
export { FlowBackground, FlowControls };
|