@bycrux/editor 0.4.1
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/README.md +165 -0
- package/package.json +46 -0
- package/src/__tests__/adapter-contract.test.ts +123 -0
- package/src/__tests__/adapter.test.ts +185 -0
- package/src/__tests__/schema.test.ts +104 -0
- package/src/__tests__/video-adapter-contract.test.ts +89 -0
- package/src/carousel/AddElementMenu.tsx +211 -0
- package/src/carousel/CarouselEditor.tsx +545 -0
- package/src/carousel/CarouselRenderModal.tsx +243 -0
- package/src/carousel/OverlayErrorBoundary.tsx +99 -0
- package/src/carousel/OverlayPicker.tsx +145 -0
- package/src/carousel/ReadOnlySlide.tsx +90 -0
- package/src/carousel/SlideCanvas.tsx +637 -0
- package/src/carousel/SlidePropertyPanel.tsx +387 -0
- package/src/carousel/__tests__/CarouselEditor.test.tsx +291 -0
- package/src/carousel/__tests__/ReadOnlySlide.test.tsx +139 -0
- package/src/carousel/__tests__/SlideCanvasCrop.test.tsx +95 -0
- package/src/carousel/__tests__/SlideCanvasFonts.test.tsx +82 -0
- package/src/crop/CanvasCropOverlay.tsx +193 -0
- package/src/crop/__tests__/crop-math.test.ts +174 -0
- package/src/crop/crop-math.ts +125 -0
- package/src/gestures/helpers/__tests__/element-transform.test.ts +30 -0
- package/src/gestures/helpers/drag.ts +24 -0
- package/src/gestures/helpers/element-transform.ts +15 -0
- package/src/gestures/helpers/resize.ts +60 -0
- package/src/gestures/helpers/rotate.ts +44 -0
- package/src/gestures/helpers/snap.ts +64 -0
- package/src/gestures/hooks/useOverlayDrag.ts +106 -0
- package/src/gestures/hooks/useOverlayResize.ts +67 -0
- package/src/gestures/hooks/useOverlayRotate.ts +64 -0
- package/src/gestures/index.ts +16 -0
- package/src/index.ts +136 -0
- package/src/lib/google-fonts.ts +28 -0
- package/src/overlays/contract.ts +41 -0
- package/src/preview/OverlayPreview.tsx +196 -0
- package/src/preview/__tests__/OverlayPreview.test.tsx +169 -0
- package/src/schema.ts +201 -0
- package/src/state/__tests__/project-reducer.test.ts +957 -0
- package/src/state/__tests__/use-project-state.test.tsx +258 -0
- package/src/state/mutation-queue.ts +62 -0
- package/src/state/project-reducer.ts +328 -0
- package/src/state/use-project-state.ts +442 -0
- package/src/test-setup.ts +1 -0
- package/src/text/FontPicker.tsx +218 -0
- package/src/text/InlineTextEditor.tsx +92 -0
- package/src/text/TextFormattingToolbar.tsx +248 -0
- package/src/text/__tests__/InlineTextEditor.test.tsx +139 -0
- package/src/text/__tests__/TextFormattingToolbar.test.tsx +416 -0
- package/src/theme.ts +93 -0
- package/src/types.ts +486 -0
- package/src/ui/__tests__/button.test.tsx +17 -0
- package/src/ui/badge.tsx +32 -0
- package/src/ui/button.tsx +32 -0
- package/src/ui/index.ts +16 -0
- package/src/ui/input.tsx +15 -0
- package/src/ui/label.tsx +10 -0
- package/src/ui/select.tsx +23 -0
- package/src/ui/switch.tsx +31 -0
- package/src/ui/textarea.tsx +15 -0
- package/src/ui/utils.ts +7 -0
- package/src/video/RenderModal.tsx +252 -0
- package/src/video/VersionPanel.tsx +83 -0
- package/src/video/VideoEditor.tsx +508 -0
- package/src/video/__tests__/VideoEditor.test.tsx +213 -0
- package/src/video/__tests__/captionRepair.test.ts +134 -0
- package/src/video/__tests__/cuts.test.ts +198 -0
- package/src/video/captionRepair.ts +41 -0
- package/src/video/cuts.ts +369 -0
- package/src/video/design-canvas.ts +11 -0
- package/src/video/preview/CaptionPreview.tsx +83 -0
- package/src/video/preview/CarouselPreview.tsx +35 -0
- package/src/video/preview/OverlayItemsLayer.tsx +584 -0
- package/src/video/preview/PreviewPlayer.tsx +178 -0
- package/src/video/preview/useDragOverlay.ts +167 -0
- package/src/video/preview/useVideoPlayback.ts +761 -0
- package/src/video/timeline/AudioTrackRow.tsx +406 -0
- package/src/video/timeline/AudioWaveformLayer.tsx +117 -0
- package/src/video/timeline/EditableSegment.tsx +30 -0
- package/src/video/timeline/Scrubber.tsx +184 -0
- package/src/video/timeline/Timeline.tsx +375 -0
- package/src/video/timeline/TimelineContext.ts +25 -0
- package/src/video/timeline/TranscriptModal.tsx +63 -0
- package/src/video/timeline/TranscriptPanel.tsx +86 -0
- package/src/video/timeline/VisualTrackRow.tsx +293 -0
- package/src/video/timeline/makeCaptionEdit.ts +32 -0
- package/src/video/timeline/multiSelectOps.ts +157 -0
- package/src/video/timeline/useItemDragDrop.ts +190 -0
- package/src/video/timeline/useTimelineZoom.ts +48 -0
- package/src/video/timeline/utils.ts +17 -0
package/src/schema.ts
ADDED
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
// Editor-facing schema for the @bycrux/editor package.
|
|
2
|
+
//
|
|
3
|
+
// These types describe the slice of a Montaj project the carousel editor reads
|
|
4
|
+
// and writes. They are intentionally self-contained: the package owns no
|
|
5
|
+
// pipeline/agent types and depends on nothing from Montaj. The host app
|
|
6
|
+
// (Montaj, Hub, …) extends EditorProject with its own pipeline fields.
|
|
7
|
+
|
|
8
|
+
export interface Word {
|
|
9
|
+
word: string
|
|
10
|
+
start: number
|
|
11
|
+
end: number
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export interface AudioTrack {
|
|
15
|
+
id: string
|
|
16
|
+
type?: 'voiceover' | 'music' | 'sfx' | 'audio'
|
|
17
|
+
src: string
|
|
18
|
+
start: number // position on project timeline (seconds)
|
|
19
|
+
end: number
|
|
20
|
+
volume?: number // 0.0–2.0, default 1.0
|
|
21
|
+
inPoint?: number // offset into source file (seconds)
|
|
22
|
+
outPoint?: number // end offset in source file (seconds)
|
|
23
|
+
label?: string // display name, defaults to filename
|
|
24
|
+
muted?: boolean
|
|
25
|
+
ducking?: {
|
|
26
|
+
enabled: boolean
|
|
27
|
+
depth?: number // dB, default -12
|
|
28
|
+
attack?: number // seconds, default 0.3
|
|
29
|
+
release?: number // seconds, default 0.5
|
|
30
|
+
}
|
|
31
|
+
fadeIn?: number // fade-in duration in seconds (0 = no fade)
|
|
32
|
+
fadeOut?: number // fade-out duration in seconds (0 = no fade)
|
|
33
|
+
sourceDuration?: number // intrinsic duration of the source file in seconds
|
|
34
|
+
lane?: number // visual grouping — tracks sharing a lane render in the same row
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export interface CaptionSegment {
|
|
38
|
+
id?: string
|
|
39
|
+
text: string
|
|
40
|
+
start: number
|
|
41
|
+
end: number
|
|
42
|
+
words?: Word[]
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export interface Captions {
|
|
46
|
+
style: 'word-by-word' | 'pop' | 'karaoke' | 'subtitle'
|
|
47
|
+
segments: CaptionSegment[]
|
|
48
|
+
// ffmpeg-drawtext render params — ignored by JSX preview, used by render.js ffmpeg branch
|
|
49
|
+
position?: 'center' | 'top-left' | 'bottom-left'
|
|
50
|
+
color?: string
|
|
51
|
+
fontsize?: number
|
|
52
|
+
bgColor?: string
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export interface VisualItem {
|
|
56
|
+
id: string
|
|
57
|
+
type: 'overlay' | 'image' | 'video'
|
|
58
|
+
src?: string
|
|
59
|
+
start: number
|
|
60
|
+
end: number
|
|
61
|
+
sourceDuration?: number // video type only — used for right-edge drag guard
|
|
62
|
+
inPoint?: number // video type only
|
|
63
|
+
outPoint?: number // video type only
|
|
64
|
+
loop?: boolean // video type only — loop source clip within project window
|
|
65
|
+
transition?: { type: string; duration: number } // video type only — transition into next clip
|
|
66
|
+
offsetX?: number
|
|
67
|
+
offsetY?: number
|
|
68
|
+
scale?: number
|
|
69
|
+
opacity?: number // 0.0–1.0
|
|
70
|
+
fit?: 'cover' | 'contain' | 'fill' // image type only — how the source fills its box. Default 'cover' (AR-preserving fill+crop). 'contain' letterboxes; 'fill' is legacy stretch (no AR).
|
|
71
|
+
volume?: number // video audio level 0.0–2.0, default 1.0 (ignored for images)
|
|
72
|
+
rotation?: number // degrees, clockwise
|
|
73
|
+
opaque?: boolean // legacy boolean kept for old overlay items
|
|
74
|
+
props?: Record<string, unknown> // overlay type only
|
|
75
|
+
googleFonts?: string[] // overlay type only — Google Fonts family specs (e.g. ["Syne:wght@800"])
|
|
76
|
+
remove_bg?: boolean // video type only
|
|
77
|
+
nobg_src?: string // video type only — ProRes 4444 .mov for final render
|
|
78
|
+
nobg_preview_src?: string // video type only — VP9 WebM with alpha for browser preview
|
|
79
|
+
muted?: boolean // video type only — suppress audio in preview and render
|
|
80
|
+
generation?: { // ai_video only — frozen provenance from Kling generation
|
|
81
|
+
// Single-shot fields (present when multiShot is falsy).
|
|
82
|
+
sceneId?: string
|
|
83
|
+
prompt?: string
|
|
84
|
+
refImages?: string[]
|
|
85
|
+
duration?: number
|
|
86
|
+
// Shared fields.
|
|
87
|
+
provider?: string
|
|
88
|
+
model?: string
|
|
89
|
+
attempts?: Array<{ ts: string; prompt: string; src: string }>
|
|
90
|
+
eval?: {
|
|
91
|
+
pass: boolean
|
|
92
|
+
scores: Record<string, number>
|
|
93
|
+
attempt: number
|
|
94
|
+
}
|
|
95
|
+
// Multi-shot / batched fields. When multiShot is true, the clip represents a
|
|
96
|
+
// batch of up to 6 scenes generated in ONE Kling call. The outer sceneId/
|
|
97
|
+
// prompt/refImages fields are replaced by batchShots[] which carries the
|
|
98
|
+
// per-scene mapping inside the concatenated output video.
|
|
99
|
+
multiShot?: boolean
|
|
100
|
+
shotType?: 'customize' | 'intelligence'
|
|
101
|
+
batchShots?: Array<{
|
|
102
|
+
sceneId: string
|
|
103
|
+
index: number // 1-based, matches Kling's multi_prompt[].index
|
|
104
|
+
prompt: string // combined prompt for this shot (styleAnchor + scene prose + tokens)
|
|
105
|
+
start: number // shot start, seconds, RELATIVE to the batch clip
|
|
106
|
+
end: number // shot end, seconds, RELATIVE to the batch clip
|
|
107
|
+
duration: number
|
|
108
|
+
}>
|
|
109
|
+
}
|
|
110
|
+
// Legacy fields for old text overlay items (pre-schema migration)
|
|
111
|
+
position?: string
|
|
112
|
+
text?: string
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
export interface Asset {
|
|
116
|
+
id: string
|
|
117
|
+
src: string
|
|
118
|
+
type: 'image'
|
|
119
|
+
name?: string
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// ── Carousel types ─────────────────────────────────────────────────────────
|
|
123
|
+
export interface ImageElement {
|
|
124
|
+
id: string
|
|
125
|
+
type: 'image'
|
|
126
|
+
src: string
|
|
127
|
+
x: number
|
|
128
|
+
y: number
|
|
129
|
+
w: number
|
|
130
|
+
h: number
|
|
131
|
+
rotation: number
|
|
132
|
+
/**
|
|
133
|
+
* Optional non-destructive crop expressed as a sub-rectangle of the source
|
|
134
|
+
* image in 0–1 fractions. The editor (mission-control) is the sole enforcer
|
|
135
|
+
* of the aspect-lock invariant (crop pixel aspect == element pixel aspect);
|
|
136
|
+
* the server validates only bounds. The renderer's object-fit: cover acts as
|
|
137
|
+
* a graceful-degradation safety net when the invariant is violated by a
|
|
138
|
+
* manual project.json edit. Absent = no crop = current behavior.
|
|
139
|
+
*/
|
|
140
|
+
crop?: { x: number; y: number; w: number; h: number }
|
|
141
|
+
/**
|
|
142
|
+
* Optional passthrough field used by host apps (e.g. Hub) to link this
|
|
143
|
+
* image element to an external media record. Montaj preserves the value
|
|
144
|
+
* through load→save round-trips but never interprets it; the host app's
|
|
145
|
+
* adapter (e.g. resolveImageSrc) is responsible for resolving the actual URL.
|
|
146
|
+
*/
|
|
147
|
+
mediaId?: string
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
export interface OverlayElement {
|
|
151
|
+
id: string
|
|
152
|
+
type: 'overlay'
|
|
153
|
+
overlay: { template: string; props: Record<string, unknown> }
|
|
154
|
+
frame: number
|
|
155
|
+
x: number
|
|
156
|
+
y: number
|
|
157
|
+
w: number
|
|
158
|
+
h: number
|
|
159
|
+
rotation: number
|
|
160
|
+
/**
|
|
161
|
+
* Optional Google Fonts family specs (e.g. ["Syne:wght@800"]) the overlay
|
|
162
|
+
* renders with. Mirrors the video-side `VisualItem.googleFonts`. The carousel
|
|
163
|
+
* overlay render path injects these so the preview uses the same glyphs and
|
|
164
|
+
* metrics as the renderer; absent = no custom fonts loaded.
|
|
165
|
+
*/
|
|
166
|
+
googleFonts?: string[]
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
export type CarouselElement = ImageElement | OverlayElement
|
|
170
|
+
|
|
171
|
+
export interface Slide {
|
|
172
|
+
id: string
|
|
173
|
+
base_color: string
|
|
174
|
+
elements: CarouselElement[]
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* The editor-facing view of a Montaj project. Captures only the fields the
|
|
179
|
+
* carousel editor reads or writes. Field types mirror the host Project
|
|
180
|
+
* interface exactly so that a full host Project is assignable to EditorProject.
|
|
181
|
+
*
|
|
182
|
+
* The index signature lets host-only / pipeline fields (workflow, storyboard,
|
|
183
|
+
* regenQueue, version, …) pass through at the type level without the package
|
|
184
|
+
* needing to know about them.
|
|
185
|
+
*/
|
|
186
|
+
export interface EditorProject {
|
|
187
|
+
id: string
|
|
188
|
+
status: 'pending' | 'storyboard_ready' | 'draft' | 'final'
|
|
189
|
+
settings: { resolution: [number, number]; fps?: number; brandKit?: string }
|
|
190
|
+
name?: string | null
|
|
191
|
+
editingPrompt?: string
|
|
192
|
+
slides?: Slide[]
|
|
193
|
+
tracks?: VisualItem[][]
|
|
194
|
+
captions?: Captions
|
|
195
|
+
audio?: { tracks: AudioTrack[] }
|
|
196
|
+
assets?: Asset[]
|
|
197
|
+
carousel?: { aspect: string }
|
|
198
|
+
profile?: string
|
|
199
|
+
// Host-only / pipeline fields pass through at the type level.
|
|
200
|
+
[key: string]: unknown
|
|
201
|
+
}
|