@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.
Files changed (89) hide show
  1. package/README.md +165 -0
  2. package/package.json +46 -0
  3. package/src/__tests__/adapter-contract.test.ts +123 -0
  4. package/src/__tests__/adapter.test.ts +185 -0
  5. package/src/__tests__/schema.test.ts +104 -0
  6. package/src/__tests__/video-adapter-contract.test.ts +89 -0
  7. package/src/carousel/AddElementMenu.tsx +211 -0
  8. package/src/carousel/CarouselEditor.tsx +545 -0
  9. package/src/carousel/CarouselRenderModal.tsx +243 -0
  10. package/src/carousel/OverlayErrorBoundary.tsx +99 -0
  11. package/src/carousel/OverlayPicker.tsx +145 -0
  12. package/src/carousel/ReadOnlySlide.tsx +90 -0
  13. package/src/carousel/SlideCanvas.tsx +637 -0
  14. package/src/carousel/SlidePropertyPanel.tsx +387 -0
  15. package/src/carousel/__tests__/CarouselEditor.test.tsx +291 -0
  16. package/src/carousel/__tests__/ReadOnlySlide.test.tsx +139 -0
  17. package/src/carousel/__tests__/SlideCanvasCrop.test.tsx +95 -0
  18. package/src/carousel/__tests__/SlideCanvasFonts.test.tsx +82 -0
  19. package/src/crop/CanvasCropOverlay.tsx +193 -0
  20. package/src/crop/__tests__/crop-math.test.ts +174 -0
  21. package/src/crop/crop-math.ts +125 -0
  22. package/src/gestures/helpers/__tests__/element-transform.test.ts +30 -0
  23. package/src/gestures/helpers/drag.ts +24 -0
  24. package/src/gestures/helpers/element-transform.ts +15 -0
  25. package/src/gestures/helpers/resize.ts +60 -0
  26. package/src/gestures/helpers/rotate.ts +44 -0
  27. package/src/gestures/helpers/snap.ts +64 -0
  28. package/src/gestures/hooks/useOverlayDrag.ts +106 -0
  29. package/src/gestures/hooks/useOverlayResize.ts +67 -0
  30. package/src/gestures/hooks/useOverlayRotate.ts +64 -0
  31. package/src/gestures/index.ts +16 -0
  32. package/src/index.ts +136 -0
  33. package/src/lib/google-fonts.ts +28 -0
  34. package/src/overlays/contract.ts +41 -0
  35. package/src/preview/OverlayPreview.tsx +196 -0
  36. package/src/preview/__tests__/OverlayPreview.test.tsx +169 -0
  37. package/src/schema.ts +201 -0
  38. package/src/state/__tests__/project-reducer.test.ts +957 -0
  39. package/src/state/__tests__/use-project-state.test.tsx +258 -0
  40. package/src/state/mutation-queue.ts +62 -0
  41. package/src/state/project-reducer.ts +328 -0
  42. package/src/state/use-project-state.ts +442 -0
  43. package/src/test-setup.ts +1 -0
  44. package/src/text/FontPicker.tsx +218 -0
  45. package/src/text/InlineTextEditor.tsx +92 -0
  46. package/src/text/TextFormattingToolbar.tsx +248 -0
  47. package/src/text/__tests__/InlineTextEditor.test.tsx +139 -0
  48. package/src/text/__tests__/TextFormattingToolbar.test.tsx +416 -0
  49. package/src/theme.ts +93 -0
  50. package/src/types.ts +486 -0
  51. package/src/ui/__tests__/button.test.tsx +17 -0
  52. package/src/ui/badge.tsx +32 -0
  53. package/src/ui/button.tsx +32 -0
  54. package/src/ui/index.ts +16 -0
  55. package/src/ui/input.tsx +15 -0
  56. package/src/ui/label.tsx +10 -0
  57. package/src/ui/select.tsx +23 -0
  58. package/src/ui/switch.tsx +31 -0
  59. package/src/ui/textarea.tsx +15 -0
  60. package/src/ui/utils.ts +7 -0
  61. package/src/video/RenderModal.tsx +252 -0
  62. package/src/video/VersionPanel.tsx +83 -0
  63. package/src/video/VideoEditor.tsx +508 -0
  64. package/src/video/__tests__/VideoEditor.test.tsx +213 -0
  65. package/src/video/__tests__/captionRepair.test.ts +134 -0
  66. package/src/video/__tests__/cuts.test.ts +198 -0
  67. package/src/video/captionRepair.ts +41 -0
  68. package/src/video/cuts.ts +369 -0
  69. package/src/video/design-canvas.ts +11 -0
  70. package/src/video/preview/CaptionPreview.tsx +83 -0
  71. package/src/video/preview/CarouselPreview.tsx +35 -0
  72. package/src/video/preview/OverlayItemsLayer.tsx +584 -0
  73. package/src/video/preview/PreviewPlayer.tsx +178 -0
  74. package/src/video/preview/useDragOverlay.ts +167 -0
  75. package/src/video/preview/useVideoPlayback.ts +761 -0
  76. package/src/video/timeline/AudioTrackRow.tsx +406 -0
  77. package/src/video/timeline/AudioWaveformLayer.tsx +117 -0
  78. package/src/video/timeline/EditableSegment.tsx +30 -0
  79. package/src/video/timeline/Scrubber.tsx +184 -0
  80. package/src/video/timeline/Timeline.tsx +375 -0
  81. package/src/video/timeline/TimelineContext.ts +25 -0
  82. package/src/video/timeline/TranscriptModal.tsx +63 -0
  83. package/src/video/timeline/TranscriptPanel.tsx +86 -0
  84. package/src/video/timeline/VisualTrackRow.tsx +293 -0
  85. package/src/video/timeline/makeCaptionEdit.ts +32 -0
  86. package/src/video/timeline/multiSelectOps.ts +157 -0
  87. package/src/video/timeline/useItemDragDrop.ts +190 -0
  88. package/src/video/timeline/useTimelineZoom.ts +48 -0
  89. 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
+ }