@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.
- package/dist/assets/index-B7notge7.css +1 -0
- package/dist/assets/index-BBE6Ta9L.js +52 -0
- package/dist/index.html +13 -0
- package/package.json +45 -0
- package/src/api.ts +289 -0
- package/src/app/App.tsx +394 -0
- package/src/audio/index.tsx +74 -0
- package/src/audio/preview.ts +364 -0
- package/src/bridge.ts +45 -0
- package/src/bridgeTypes.ts +582 -0
- package/src/channels.ts +40 -0
- package/src/main.tsx +15 -0
- package/src/panels/ResizeHandle.tsx +113 -0
- package/src/panels/agent/AgentPanel.tsx +478 -0
- package/src/panels/agent/agent.css +322 -0
- package/src/panels/export/ExportDialog.tsx +284 -0
- package/src/panels/files/FilePanel.tsx +564 -0
- package/src/panels/files/McpSection.tsx +66 -0
- package/src/panels/files/icons.tsx +118 -0
- package/src/panels/header/DocSwitcher.tsx +85 -0
- package/src/panels/header/Header.tsx +132 -0
- package/src/panels/header/HistoryControls.tsx +20 -0
- package/src/panels/header/StatusBadges.tsx +49 -0
- package/src/panels/header/TransportBar.tsx +86 -0
- package/src/panels/header/ZoomControls.tsx +38 -0
- package/src/panels/help/HelpSection.tsx +190 -0
- package/src/panels/inspector/FramingRows.tsx +69 -0
- package/src/panels/inspector/InspectorPanel.tsx +178 -0
- package/src/panels/inspector/MarkerSection.tsx +63 -0
- package/src/panels/inspector/ParamRow.tsx +71 -0
- package/src/panels/inspector/animate/BlockTiming.tsx +341 -0
- package/src/panels/inspector/animate/DuringBlocks.tsx +85 -0
- package/src/panels/inspector/animate/InOutPresets.tsx +57 -0
- package/src/panels/inspector/animate/ResolvedChannels.tsx +150 -0
- package/src/panels/inspector/animate/ShotSection.tsx +126 -0
- package/src/panels/inspector/animate/useBlocks.ts +43 -0
- package/src/panels/inspector/design/AlignRow.tsx +63 -0
- package/src/panels/inspector/design/AppearanceSection.tsx +49 -0
- package/src/panels/inspector/design/ClonerSection.tsx +142 -0
- package/src/panels/inspector/design/ComponentSection.tsx +94 -0
- package/src/panels/inspector/design/CornerSection.tsx +52 -0
- package/src/panels/inspector/design/FillSection.tsx +89 -0
- package/src/panels/inspector/design/HtmlSection.tsx +50 -0
- package/src/panels/inspector/design/InstanceSection.tsx +302 -0
- package/src/panels/inspector/design/MediaSection.tsx +251 -0
- package/src/panels/inspector/design/MeshSection.tsx +172 -0
- package/src/panels/inspector/design/ShadowSection.tsx +73 -0
- package/src/panels/inspector/design/StrokeSection.tsx +85 -0
- package/src/panels/inspector/design/TransformSection.tsx +91 -0
- package/src/panels/inspector/design/TypographySection.tsx +170 -0
- package/src/panels/inspector/scene/CameraSection.tsx +59 -0
- package/src/panels/inspector/scene/EffectsSection.tsx +123 -0
- package/src/panels/inspector/scene/SceneSection.tsx +71 -0
- package/src/panels/inspector/scene/TransitionSection.tsx +136 -0
- package/src/panels/inspector/useDocWrite.ts +268 -0
- package/src/panels/material/MaterialPicker.tsx +172 -0
- package/src/panels/stage/StageMenu.tsx +38 -0
- package/src/panels/stage/StagePanel.tsx +131 -0
- package/src/panels/timeline/AudioLane.tsx +340 -0
- package/src/panels/timeline/BlockRow.tsx +242 -0
- package/src/panels/timeline/ChannelRow.tsx +266 -0
- package/src/panels/timeline/LifespanRow.tsx +165 -0
- package/src/panels/timeline/MarkerLane.tsx +146 -0
- package/src/panels/timeline/SceneStrip.tsx +145 -0
- package/src/panels/timeline/TimelineDock.tsx +488 -0
- package/src/panels/timeline/model.ts +641 -0
- package/src/panels/timeline/snap.ts +191 -0
- package/src/panels/toolbar/AddMenu.tsx +230 -0
- package/src/panels/toolbar/CameraViewBar.tsx +70 -0
- package/src/panels/toolbar/ShadingBar.tsx +42 -0
- package/src/panels/toolbar/ToolDockBar.tsx +110 -0
- package/src/panels/toolbar/ViewModeBar.tsx +49 -0
- package/src/shotTokens.css +131 -0
- package/src/stage/FrustumGizmo.tsx +184 -0
- package/src/stage/GridPlane.tsx +274 -0
- package/src/stage/MoveGizmo.tsx +411 -0
- package/src/stage/PerfHud.tsx +206 -0
- package/src/stage/SplitRenderer.tsx +511 -0
- package/src/stage/StageApp.tsx +3423 -0
- package/src/stage/StageTools.tsx +581 -0
- package/src/stage/bridgeClient.ts +278 -0
- package/src/stage/feed.ts +243 -0
- package/src/stage/frustumGeometry.ts +172 -0
- package/src/stage/gizmoMath.ts +494 -0
- package/src/stage/handles.ts +151 -0
- package/src/stage/instanceModules.ts +152 -0
- package/src/stage/interactions/move.ts +172 -0
- package/src/stage/interactions/panZoom.ts +60 -0
- package/src/stage/isolation.ts +155 -0
- package/src/stage/main.tsx +110 -0
- package/src/stage/modalState.ts +427 -0
- package/src/stage/overlays/Overlays.tsx +1046 -0
- package/src/stage/panes.ts +121 -0
- package/src/stage/penState.ts +112 -0
- package/src/stage/project.ts +402 -0
- package/src/stage/shading.ts +55 -0
- package/src/stage/tokenColor.ts +26 -0
- package/src/stage/view.ts +182 -0
- package/src/stage/workingView.ts +636 -0
- package/src/state/agentActions.ts +203 -0
- package/src/state/clientId.ts +25 -0
- package/src/state/context.tsx +33 -0
- package/src/state/insert.ts +507 -0
- package/src/state/keymap.ts +560 -0
- package/src/state/menu.ts +141 -0
- package/src/state/panelSize.ts +26 -0
- package/src/state/renderEvents.ts +100 -0
- package/src/state/store.ts +4094 -0
- package/src/state/triangles.ts +70 -0
- package/src/state/types.ts +384 -0
- package/src/styles.css +2948 -0
- 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
|
+
}
|