@dailies-studio/shot-editor 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.
Files changed (112) hide show
  1. package/dist/assets/index-B7notge7.css +1 -0
  2. package/dist/assets/index-BBE6Ta9L.js +52 -0
  3. package/dist/index.html +13 -0
  4. package/package.json +45 -0
  5. package/src/api.ts +289 -0
  6. package/src/app/App.tsx +394 -0
  7. package/src/audio/index.tsx +74 -0
  8. package/src/audio/preview.ts +364 -0
  9. package/src/bridge.ts +45 -0
  10. package/src/bridgeTypes.ts +582 -0
  11. package/src/channels.ts +40 -0
  12. package/src/main.tsx +15 -0
  13. package/src/panels/ResizeHandle.tsx +113 -0
  14. package/src/panels/agent/AgentPanel.tsx +478 -0
  15. package/src/panels/agent/agent.css +322 -0
  16. package/src/panels/export/ExportDialog.tsx +284 -0
  17. package/src/panels/files/FilePanel.tsx +564 -0
  18. package/src/panels/files/McpSection.tsx +66 -0
  19. package/src/panels/files/icons.tsx +118 -0
  20. package/src/panels/header/DocSwitcher.tsx +85 -0
  21. package/src/panels/header/Header.tsx +132 -0
  22. package/src/panels/header/HistoryControls.tsx +20 -0
  23. package/src/panels/header/StatusBadges.tsx +49 -0
  24. package/src/panels/header/TransportBar.tsx +86 -0
  25. package/src/panels/header/ZoomControls.tsx +38 -0
  26. package/src/panels/help/HelpSection.tsx +190 -0
  27. package/src/panels/inspector/FramingRows.tsx +69 -0
  28. package/src/panels/inspector/InspectorPanel.tsx +178 -0
  29. package/src/panels/inspector/MarkerSection.tsx +63 -0
  30. package/src/panels/inspector/ParamRow.tsx +71 -0
  31. package/src/panels/inspector/animate/BlockTiming.tsx +341 -0
  32. package/src/panels/inspector/animate/DuringBlocks.tsx +85 -0
  33. package/src/panels/inspector/animate/InOutPresets.tsx +57 -0
  34. package/src/panels/inspector/animate/ResolvedChannels.tsx +150 -0
  35. package/src/panels/inspector/animate/ShotSection.tsx +126 -0
  36. package/src/panels/inspector/animate/useBlocks.ts +43 -0
  37. package/src/panels/inspector/design/AlignRow.tsx +63 -0
  38. package/src/panels/inspector/design/AppearanceSection.tsx +49 -0
  39. package/src/panels/inspector/design/ClonerSection.tsx +142 -0
  40. package/src/panels/inspector/design/ComponentSection.tsx +94 -0
  41. package/src/panels/inspector/design/CornerSection.tsx +52 -0
  42. package/src/panels/inspector/design/FillSection.tsx +89 -0
  43. package/src/panels/inspector/design/HtmlSection.tsx +50 -0
  44. package/src/panels/inspector/design/InstanceSection.tsx +302 -0
  45. package/src/panels/inspector/design/MediaSection.tsx +251 -0
  46. package/src/panels/inspector/design/MeshSection.tsx +172 -0
  47. package/src/panels/inspector/design/ShadowSection.tsx +73 -0
  48. package/src/panels/inspector/design/StrokeSection.tsx +85 -0
  49. package/src/panels/inspector/design/TransformSection.tsx +91 -0
  50. package/src/panels/inspector/design/TypographySection.tsx +170 -0
  51. package/src/panels/inspector/scene/CameraSection.tsx +59 -0
  52. package/src/panels/inspector/scene/EffectsSection.tsx +123 -0
  53. package/src/panels/inspector/scene/SceneSection.tsx +71 -0
  54. package/src/panels/inspector/scene/TransitionSection.tsx +136 -0
  55. package/src/panels/inspector/useDocWrite.ts +268 -0
  56. package/src/panels/material/MaterialPicker.tsx +172 -0
  57. package/src/panels/stage/StageMenu.tsx +38 -0
  58. package/src/panels/stage/StagePanel.tsx +131 -0
  59. package/src/panels/timeline/AudioLane.tsx +340 -0
  60. package/src/panels/timeline/BlockRow.tsx +242 -0
  61. package/src/panels/timeline/ChannelRow.tsx +266 -0
  62. package/src/panels/timeline/LifespanRow.tsx +165 -0
  63. package/src/panels/timeline/MarkerLane.tsx +146 -0
  64. package/src/panels/timeline/SceneStrip.tsx +145 -0
  65. package/src/panels/timeline/TimelineDock.tsx +488 -0
  66. package/src/panels/timeline/model.ts +641 -0
  67. package/src/panels/timeline/snap.ts +191 -0
  68. package/src/panels/toolbar/AddMenu.tsx +230 -0
  69. package/src/panels/toolbar/CameraViewBar.tsx +70 -0
  70. package/src/panels/toolbar/ShadingBar.tsx +42 -0
  71. package/src/panels/toolbar/ToolDockBar.tsx +110 -0
  72. package/src/panels/toolbar/ViewModeBar.tsx +49 -0
  73. package/src/shotTokens.css +131 -0
  74. package/src/stage/FrustumGizmo.tsx +184 -0
  75. package/src/stage/GridPlane.tsx +274 -0
  76. package/src/stage/MoveGizmo.tsx +411 -0
  77. package/src/stage/PerfHud.tsx +206 -0
  78. package/src/stage/SplitRenderer.tsx +511 -0
  79. package/src/stage/StageApp.tsx +3423 -0
  80. package/src/stage/StageTools.tsx +581 -0
  81. package/src/stage/bridgeClient.ts +278 -0
  82. package/src/stage/feed.ts +243 -0
  83. package/src/stage/frustumGeometry.ts +172 -0
  84. package/src/stage/gizmoMath.ts +494 -0
  85. package/src/stage/handles.ts +151 -0
  86. package/src/stage/instanceModules.ts +152 -0
  87. package/src/stage/interactions/move.ts +172 -0
  88. package/src/stage/interactions/panZoom.ts +60 -0
  89. package/src/stage/isolation.ts +155 -0
  90. package/src/stage/main.tsx +110 -0
  91. package/src/stage/modalState.ts +427 -0
  92. package/src/stage/overlays/Overlays.tsx +1046 -0
  93. package/src/stage/panes.ts +121 -0
  94. package/src/stage/penState.ts +112 -0
  95. package/src/stage/project.ts +402 -0
  96. package/src/stage/shading.ts +55 -0
  97. package/src/stage/tokenColor.ts +26 -0
  98. package/src/stage/view.ts +182 -0
  99. package/src/stage/workingView.ts +636 -0
  100. package/src/state/agentActions.ts +203 -0
  101. package/src/state/clientId.ts +25 -0
  102. package/src/state/context.tsx +33 -0
  103. package/src/state/insert.ts +507 -0
  104. package/src/state/keymap.ts +560 -0
  105. package/src/state/menu.ts +141 -0
  106. package/src/state/panelSize.ts +26 -0
  107. package/src/state/renderEvents.ts +100 -0
  108. package/src/state/store.ts +4094 -0
  109. package/src/state/triangles.ts +70 -0
  110. package/src/state/types.ts +384 -0
  111. package/src/styles.css +2948 -0
  112. package/src/tools.ts +181 -0
@@ -0,0 +1,55 @@
1
+ // The working pane's shading modes, as PLAIN DATA both documents share.
2
+ //
3
+ // Like `panes.ts`, this module is three-free arithmetic-and-tables on purpose:
4
+ // the chrome needs the vocabulary (the ShadingBar, the keymap's cycle, the
5
+ // store's pref) and the stage needs the draw policy, and a second declaration
6
+ // of "what wireframe means" in either document would be a second truth. The
7
+ // GL half — which materials, which pipeline — stays in SplitRenderer; what
8
+ // lives here is only the DECISION, which is what a GPU-free test can pin.
9
+ //
10
+ // The camera pane is always the graded render and is deliberately not in this
11
+ // vocabulary at all: a shading mode is a statement about the WORKING pane, the
12
+ // workspace's clay model of the scene, never about the shot.
13
+
14
+ export type ShadingMode = "solid" | "wireframe" | "material" | "rendered";
15
+
16
+ /** Bar order, which is also Z's cycle order: cheapest first, truest last. */
17
+ export const SHADING_MODES: readonly ShadingMode[] = ["solid", "wireframe", "material", "rendered"];
18
+
19
+ /** Z's cycle, wrapping. */
20
+ export function nextShading(mode: ShadingMode): ShadingMode {
21
+ const at = SHADING_MODES.indexOf(mode);
22
+ return SHADING_MODES[(at + 1) % SHADING_MODES.length]!;
23
+ }
24
+
25
+ /** Boundary clamp, the `clampSplitRatio` rationale: the stage does not get to
26
+ * assume its caller was careful, and garbage means the default, not a throw. */
27
+ export function clampShading(mode: unknown): ShadingMode {
28
+ return SHADING_MODES.includes(mode as ShadingMode) ? (mode as ShadingMode) : "solid";
29
+ }
30
+
31
+ /**
32
+ * What the working pane does this frame, per mode.
33
+ *
34
+ * `override` names which override material covers the scene ("clay" is
35
+ * today's flat grey, "wire" its wireframe twin, null draws the real layer
36
+ * materials). `graded` runs the scene through the post chain from the working
37
+ * camera — and it is only ever true when the scene HAS a chain, so "rendered"
38
+ * on an effect-less scene degrades to the material plan rather than paying
39
+ * for a pipeline that would output its own input.
40
+ */
41
+ export function workingDrawPlan(
42
+ mode: ShadingMode,
43
+ hasChain: boolean,
44
+ ): { override: "clay" | "wire" | null; graded: boolean } {
45
+ switch (mode) {
46
+ case "solid":
47
+ return { override: "clay", graded: false };
48
+ case "wireframe":
49
+ return { override: "wire", graded: false };
50
+ case "material":
51
+ return { override: null, graded: false };
52
+ case "rendered":
53
+ return { override: null, graded: hasChain };
54
+ }
55
+ }
@@ -0,0 +1,26 @@
1
+ // A palette token, resolved into a three `Color`.
2
+ //
3
+ // Every editor-only object in the working pane is drawn with a MATERIAL colour,
4
+ // and a material colour cannot read a CSS custom property the way an SVG fill
5
+ // can. Resolving one at mount, inside the stage document — which imports
6
+ // shotTokens.css — is what lets the gizmo, the grid and the SVG overlay share
7
+ // the single definition in that file instead of each carrying its own literal.
8
+ //
9
+ // Not for GL CLEAR colours: SplitRenderer's clay backdrop is hard-coded because
10
+ // it is written before any style has resolved.
11
+ import { Color } from "three";
12
+
13
+ export function tokenColor(name: string, fallback: string): Color {
14
+ const backstop = new Color(fallback);
15
+ if (typeof document === "undefined") return backstop;
16
+ const token = getComputedStyle(document.documentElement).getPropertyValue(name).trim();
17
+ if (token.length === 0) return backstop;
18
+ try {
19
+ return new Color(token);
20
+ } catch {
21
+ // A token that is not a colour three can parse (a `color-mix`, a var chain
22
+ // that resolved to nothing) is a stylesheet bug, and drawing in the
23
+ // fallback is what keeps it a cosmetic one rather than a mount that throws.
24
+ return backstop;
25
+ }
26
+ }
@@ -0,0 +1,182 @@
1
+ // The working area: one camera, a wider render than the export frame, and the
2
+ // frame's screen rect derived from the same numbers.
3
+ //
4
+ // ── WHY THERE IS ONLY ONE CAMERA ─────────────────────────────────────────
5
+ //
6
+ // Raylight's authoring view IS the shot. "A bright border marks the exportable
7
+ // frame; you can work off-canvas" describes a view WIDER THAN THE FRAME, not a
8
+ // different camera — and that is exactly `camera.setViewOffset`.
9
+ //
10
+ // A second, flat "design camera" would let an author position a layer in a view
11
+ // the export never shows. That is the class of bug the 2D lane's address rule
12
+ // was written to eliminate, and it is not worth reintroducing for a pan.
13
+ //
14
+ // ── THE MODEL ────────────────────────────────────────────────────────────
15
+ //
16
+ // The camera's BASE frustum is the export frame's: `aspect = W/H`, the scene's
17
+ // fov, and `planeDistance` deriving the distance — so with no view offset it
18
+ // renders the frame and nothing else. `setViewOffset` then carves out the
19
+ // sub-rectangle the viewport actually shows, measured in DESIGN PIXELS of that
20
+ // frame. Everything below is that one conversion, in both directions.
21
+ //
22
+ // Because the offset lives in the camera's projection matrix, `project.ts`
23
+ // needs to know nothing about pan or zoom: `Vector3.project` already carries
24
+ // them, so the overlay cannot drift from what was drawn. That is the whole
25
+ // reason to do it this way rather than with a CSS transform over the canvas.
26
+ //
27
+ // KEEP THIS FILE SMALL. Its 2D counterpart (`measure.ts`, 558 lines) is the
28
+ // file everyone dreads, and three consumers — the frame border, the snap
29
+ // thresholds and the marquee — have to agree with it to the sub-pixel.
30
+
31
+ /** Where the export frame sits in the working area. */
32
+ export interface ShotView {
33
+ /** Screen px per design px at z = 0. */
34
+ zoom: number;
35
+ /** Screen px the frame's CENTRE is displaced from the viewport's centre. */
36
+ panX: number;
37
+ panY: number;
38
+ }
39
+
40
+ /** The viewport the stage actually has, in CSS px. */
41
+ export interface Viewport {
42
+ width: number;
43
+ height: number;
44
+ }
45
+
46
+ /** The design canvas, in design px. */
47
+ export interface FrameSize {
48
+ width: number;
49
+ height: number;
50
+ }
51
+
52
+ /** three's `setViewOffset` arguments, in the frame's own design pixels. */
53
+ export interface ViewOffset {
54
+ fullWidth: number;
55
+ fullHeight: number;
56
+ offsetX: number;
57
+ offsetY: number;
58
+ width: number;
59
+ height: number;
60
+ }
61
+
62
+ export const MIN_ZOOM = 0.02;
63
+ export const MAX_ZOOM = 32;
64
+
65
+ export function clampZoom(zoom: number): number {
66
+ if (!Number.isFinite(zoom) || zoom <= 0) return 1;
67
+ return Math.max(MIN_ZOOM, Math.min(MAX_ZOOM, zoom));
68
+ }
69
+
70
+ /**
71
+ * The camera view offset for a view.
72
+ *
73
+ * `fullWidth/fullHeight` are the FRAME in design px, so the sub-rect is read
74
+ * directly in design px too: the viewport is `viewport.width / zoom` design px
75
+ * wide, and it starts wherever the pan puts it.
76
+ */
77
+ export function viewOffsetFor(view: ShotView, viewport: Viewport, frame: FrameSize): ViewOffset {
78
+ const zoom = clampZoom(view.zoom);
79
+ const width = viewport.width / zoom;
80
+ const height = viewport.height / zoom;
81
+ return {
82
+ fullWidth: frame.width,
83
+ fullHeight: frame.height,
84
+ // A pan that moves the frame RIGHT on screen moves the visible window LEFT
85
+ // within the frame, hence the subtraction. Getting this sign wrong makes
86
+ // the canvas drag the wrong way, which is unmistakable and therefore cheap.
87
+ offsetX: (frame.width - width) / 2 - view.panX / zoom,
88
+ offsetY: (frame.height - height) / 2 - view.panY / zoom,
89
+ width,
90
+ height,
91
+ };
92
+ }
93
+
94
+ /**
95
+ * The exportable frame's rectangle on screen, in CSS px from the canvas
96
+ * top-left.
97
+ *
98
+ * DERIVED FROM THE SAME NUMBERS as the view offset, deliberately: the frame
99
+ * border, the snap thresholds and the marquee all read this, and a border
100
+ * computed independently would disagree with the render by a sub-pixel at some
101
+ * zooms — which reads as "snapping feels sticky in one direction" rather than
102
+ * as a projection bug.
103
+ */
104
+ export function frameRect(
105
+ view: ShotView,
106
+ viewport: Viewport,
107
+ frame: FrameSize,
108
+ ): { x: number; y: number; width: number; height: number } {
109
+ const zoom = clampZoom(view.zoom);
110
+ const offset = viewOffsetFor(view, viewport, frame);
111
+ return {
112
+ x: -offset.offsetX * zoom,
113
+ y: -offset.offsetY * zoom,
114
+ width: frame.width * zoom,
115
+ height: frame.height * zoom,
116
+ };
117
+ }
118
+
119
+ /** Screen px (canvas top-left) -> design px of the z = 0 plane. The inverse of
120
+ * `frameRect`, and the two are tested against each other. */
121
+ export function screenToDesign(
122
+ point: readonly [number, number],
123
+ view: ShotView,
124
+ viewport: Viewport,
125
+ frame: FrameSize,
126
+ ): [number, number] {
127
+ const rect = frameRect(view, viewport, frame);
128
+ const zoom = clampZoom(view.zoom);
129
+ return [(point[0] - rect.x) / zoom, (point[1] - rect.y) / zoom];
130
+ }
131
+
132
+ /** Design px of the z = 0 plane -> screen px. */
133
+ export function designToScreen(
134
+ point: readonly [number, number],
135
+ view: ShotView,
136
+ viewport: Viewport,
137
+ frame: FrameSize,
138
+ ): [number, number] {
139
+ const rect = frameRect(view, viewport, frame);
140
+ const zoom = clampZoom(view.zoom);
141
+ return [rect.x + point[0] * zoom, rect.y + point[1] * zoom];
142
+ }
143
+
144
+ /** Space left around the frame when fitting, as a fraction of the viewport. */
145
+ export const FIT_MARGIN = 0.08;
146
+
147
+ /** The view that shows the whole frame with a margin. Pure in the viewport and
148
+ * the frame, so the chrome's "fit" button and a window resize agree. */
149
+ export function fitView(viewport: Viewport, frame: FrameSize): ShotView {
150
+ if (viewport.width <= 0 || viewport.height <= 0) return { zoom: 1, panX: 0, panY: 0 };
151
+ const usable = 1 - FIT_MARGIN * 2;
152
+ const zoom = Math.min(
153
+ (viewport.width * usable) / frame.width,
154
+ (viewport.height * usable) / frame.height,
155
+ );
156
+ return { zoom: clampZoom(zoom), panX: 0, panY: 0 };
157
+ }
158
+
159
+ /**
160
+ * Zoom about a screen point: the design pixel under the cursor stays under the
161
+ * cursor.
162
+ *
163
+ * That anchoring is the whole difference between a zoom that feels like a
164
+ * camera and one that feels like a slider — and it is one equation, so there is
165
+ * no reason to ship the slider version first.
166
+ */
167
+ export function zoomAt(
168
+ view: ShotView,
169
+ viewport: Viewport,
170
+ frame: FrameSize,
171
+ screenPoint: readonly [number, number],
172
+ nextZoom: number,
173
+ ): ShotView {
174
+ const zoom = clampZoom(nextZoom);
175
+ const anchor = screenToDesign(screenPoint, view, viewport, frame);
176
+ const after = designToScreen(anchor, { ...view, zoom }, viewport, frame);
177
+ return {
178
+ zoom,
179
+ panX: view.panX + (screenPoint[0] - after[0]),
180
+ panY: view.panY + (screenPoint[1] - after[1]),
181
+ };
182
+ }