@bycrux/editor 0.11.2 → 1.0.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/package.json +3 -1
- package/src/ControlsInfoModal.tsx +251 -61
- package/src/__tests__/ControlsInfoModal.test.tsx +43 -0
- package/src/__tests__/adapter.test.ts +59 -1
- package/src/__tests__/schema-assignability.test.ts +93 -0
- package/src/__tests__/schema.test.ts +15 -0
- package/src/__tests__/video-adapter-contract.test.ts +128 -5
- package/src/carousel/AddElementMenu.tsx +10 -4
- package/src/carousel/CarouselEditor.tsx +46 -8
- package/src/carousel/CarouselRenderModal.tsx +15 -10
- package/src/carousel/OverlayPicker.tsx +10 -3
- package/src/carousel/SlidePropertyPanel.tsx +43 -21
- package/src/components/FilmstripScrubber.tsx +277 -0
- package/src/components/__tests__/FilmstripScrubber.test.tsx +216 -0
- package/src/engine/__tests__/audio-clock.test.ts +655 -0
- package/src/engine/__tests__/audio-worklet-source.test.ts +316 -0
- package/src/engine/__tests__/batch-planner.test.ts +298 -0
- package/src/engine/__tests__/decode-worker-source.test.ts +249 -0
- package/src/engine/__tests__/demux-ranged.test.ts +495 -0
- package/src/engine/__tests__/demux-truncated.test.ts +136 -0
- package/src/engine/__tests__/demux.test.ts +305 -0
- package/src/engine/__tests__/eligibility.test.ts +146 -0
- package/src/engine/__tests__/engine-recovery.test.ts +263 -0
- package/src/engine/__tests__/engine.test.ts +326 -0
- package/src/engine/__tests__/frame-server-ranged.test.ts +261 -0
- package/src/engine/__tests__/frame-server.test.ts +326 -0
- package/src/engine/__tests__/media-loader-ranged.test.ts +289 -0
- package/src/engine/__tests__/media-loader.test.ts +100 -0
- package/src/engine/__tests__/scheduler-crop.test.ts +353 -0
- package/src/engine/__tests__/scheduler.test.ts +1310 -0
- package/src/engine/__tests__/scrub-resolve.test.ts +96 -0
- package/src/engine/__tests__/scrub-source.test.ts +195 -0
- package/src/engine/__tests__/source-host.test.ts +455 -0
- package/src/engine/__tests__/time-stretch.test.ts +182 -0
- package/src/engine/audio-clock.ts +1179 -0
- package/src/engine/audio-worklet-source.ts +160 -0
- package/src/engine/batch-planner.ts +308 -0
- package/src/engine/debug-hud.tsx +54 -0
- package/src/engine/decode-worker-source.ts +142 -0
- package/src/engine/demux.ts +900 -0
- package/src/engine/eligibility.ts +140 -0
- package/src/engine/frame-server.ts +644 -0
- package/src/engine/index.ts +950 -0
- package/src/engine/media-loader.ts +380 -0
- package/src/engine/mp4box.d.ts +214 -0
- package/src/engine/scheduler.ts +1324 -0
- package/src/engine/scrub-resolve.ts +66 -0
- package/src/engine/scrub-source.ts +496 -0
- package/src/engine/time-stretch.ts +224 -0
- package/src/index.ts +83 -1
- package/src/preview/OverlayPreview.tsx +2 -24
- package/src/schema.ts +146 -3
- package/src/state/__tests__/use-project-sync.test.tsx +56 -0
- package/src/state/use-project-sync.ts +17 -0
- package/src/test-setup.ts +32 -0
- package/src/text/FontPicker.tsx +85 -51
- package/src/text/TextFormattingToolbar.tsx +6 -1
- package/src/text/__tests__/FontPicker.options.test.ts +43 -0
- package/src/text/__tests__/TextFormattingToolbar.test.tsx +4 -4
- package/src/theme.ts +108 -3
- package/src/types.ts +601 -22
- package/src/ui/Loader.tsx +64 -0
- package/src/ui/NumberField.tsx +254 -0
- package/src/ui/Slider.tsx +128 -0
- package/src/ui/Tooltip.tsx +118 -0
- package/src/ui/__tests__/NumberField.test.tsx +298 -0
- package/src/ui/__tests__/Slider.test.tsx +150 -0
- package/src/ui/__tests__/Tooltip.test.tsx +105 -0
- package/src/ui/__tests__/usePersistentState.test.tsx +112 -0
- package/src/ui/badge.tsx +12 -4
- package/src/ui/index.ts +6 -0
- package/src/ui/input.tsx +1 -1
- package/src/ui/select.tsx +1 -1
- package/src/ui/switch.tsx +12 -2
- package/src/ui/textarea.tsx +1 -1
- package/src/ui/usePersistentState.ts +63 -0
- package/src/video/AudioPolishModal.tsx +983 -0
- package/src/video/CaptionListPanel.test.tsx +944 -0
- package/src/video/CaptionListPanel.tsx +1106 -0
- package/src/video/CaptionRegenModal.tsx +37 -9
- package/src/video/CaptionSpecimen.tsx +160 -0
- package/src/video/CaptionStyleGallery.tsx +466 -0
- package/src/video/CommandPalette.tsx +165 -0
- package/src/video/ImageToneMenu.tsx +137 -0
- package/src/video/OverlayInspector.tsx +977 -0
- package/src/video/RenderModal.tsx +1035 -62
- package/src/video/VersionCompare.tsx +258 -0
- package/src/video/VersionPanel.tsx +117 -42
- package/src/video/VideoEditor.tsx +2229 -243
- package/src/video/__tests__/AudioPolishModal.test.tsx +825 -0
- package/src/video/__tests__/CaptionListPanel.font.test.tsx +397 -0
- package/src/video/__tests__/CaptionListPanel.generate.test.tsx +153 -0
- package/src/video/__tests__/CaptionRegenModal.test.tsx +29 -0
- package/src/video/__tests__/CaptionSpecimen.test.tsx +213 -0
- package/src/video/__tests__/CaptionStyleGallery.test.tsx +362 -0
- package/src/video/__tests__/CommandPalette.test.tsx +119 -0
- package/src/video/__tests__/OverlayInspector.test.tsx +1367 -0
- package/src/video/__tests__/RenderModal.exportControls.test.tsx +319 -0
- package/src/video/__tests__/RenderModal.options.test.tsx +562 -0
- package/src/video/__tests__/RenderModal.progress.test.ts +65 -0
- package/src/video/__tests__/VersionCompare.test.tsx +182 -0
- package/src/video/__tests__/VersionPanel.test.tsx +279 -0
- package/src/video/__tests__/VideoEditor.audioPolish.test.tsx +241 -0
- package/src/video/__tests__/VideoEditor.captionDelete.test.tsx +147 -0
- package/src/video/__tests__/VideoEditor.captionGesture.test.tsx +170 -0
- package/src/video/__tests__/VideoEditor.captionSeam.test.tsx +134 -0
- package/src/video/__tests__/VideoEditor.clipKeyframes.test.tsx +59 -0
- package/src/video/__tests__/VideoEditor.context.test.tsx +44 -0
- package/src/video/__tests__/VideoEditor.editFocus.test.tsx +55 -0
- package/src/video/__tests__/VideoEditor.keymap.test.tsx +725 -0
- package/src/video/__tests__/VideoEditor.layout.test.tsx +338 -0
- package/src/video/__tests__/VideoEditor.propertiesPanel.test.tsx +765 -0
- package/src/video/__tests__/VideoEditor.rippleDeleteCaptions.test.tsx +159 -0
- package/src/video/__tests__/VideoEditor.sourcePreview.test.tsx +122 -0
- package/src/video/__tests__/VideoEditor.test.tsx +403 -50
- package/src/video/__tests__/audioMagnet.test.ts +158 -0
- package/src/video/__tests__/audioPolish.test.ts +1202 -0
- package/src/video/__tests__/captionActiveWord.test.ts +107 -0
- package/src/video/__tests__/captionLanes.test.ts +262 -0
- package/src/video/__tests__/captionPositioning.test.tsx +8 -3
- package/src/video/__tests__/captionWordFloor.test.ts +228 -0
- package/src/video/__tests__/clipboard-ops.test.ts +430 -0
- package/src/video/__tests__/cuts.insert.test.ts +244 -0
- package/src/video/__tests__/cuts.test.ts +1213 -34
- package/src/video/__tests__/export-limits.test.ts +195 -0
- package/src/video/__tests__/hover-scrub.test.ts +163 -0
- package/src/video/__tests__/keyframeOps.canKeyframeProp.test.ts +61 -0
- package/src/video/__tests__/keyframeOps.test.ts +643 -0
- package/src/video/__tests__/keymap.test.tsx +171 -0
- package/src/video/__tests__/render-progress.test.tsx +20 -4
- package/src/video/__tests__/shuttle.test.ts +210 -0
- package/src/video/__tests__/source-preview.test.ts +60 -0
- package/src/video/__tests__/timecode.test.ts +77 -0
- package/src/video/__tests__/use-report-context.test.tsx +101 -0
- package/src/video/audioMagnet.ts +72 -0
- package/src/video/audioPolish.ts +774 -0
- package/src/video/captionActiveWord.ts +74 -0
- package/src/video/captionLanes.ts +202 -0
- package/src/video/captionRepair.ts +9 -5
- package/src/video/captionStyleDefaults.ts +100 -0
- package/src/video/captionWordFloor.ts +83 -0
- package/src/video/clipboard-ops.ts +377 -0
- package/src/video/cuts.ts +713 -37
- package/src/video/export-limits.ts +102 -0
- package/src/video/hover-scrub.ts +102 -0
- package/src/video/imageTone.ts +58 -0
- package/src/video/imageToneExamples.ts +10 -0
- package/src/video/keyframeOps.ts +384 -0
- package/src/video/keymap.ts +146 -0
- package/src/video/panels/ClipPropertiesPanel.tsx +706 -0
- package/src/video/panels/LeftPanelTabs.tsx +187 -0
- package/src/video/panels/OverlayContentPanel.tsx +351 -0
- package/src/video/panels/TabNav.tsx +60 -0
- package/src/video/panels/__tests__/ClipPropertiesPanel.test.tsx +645 -0
- package/src/video/panels/__tests__/LeftPanelTabs.test.tsx +189 -0
- package/src/video/panels/__tests__/OverlayContentPanel.test.tsx +222 -0
- package/src/video/panels/__tests__/TabNav.test.tsx +71 -0
- package/src/video/preview/CaptionPreview.tsx +57 -69
- package/src/video/preview/EngineSurface.tsx +103 -0
- package/src/video/preview/OverlayItemsLayer.tsx +315 -103
- package/src/video/preview/PreviewPlayer.tsx +337 -56
- package/src/video/preview/SocialPreviewMenu.tsx +214 -0
- package/src/video/preview/SocialSafeZoneOverlay.tsx +478 -0
- package/src/video/preview/__tests__/CaptionPreview.fonts.test.tsx +97 -0
- package/src/video/preview/__tests__/EngineSurface.test.tsx +114 -0
- package/src/video/preview/__tests__/OverlayItemsLayer.edit.test.tsx +4 -2
- package/src/video/preview/__tests__/OverlayItemsLayer.keyframes.test.tsx +363 -0
- package/src/video/preview/__tests__/OverlayItemsLayer.selection.test.tsx +329 -0
- package/src/video/preview/__tests__/PreviewPlayer.engine.test.tsx +139 -0
- package/src/video/preview/__tests__/SocialPreviewMenu.test.tsx +121 -0
- package/src/video/preview/__tests__/SocialSafeZoneOverlay.test.tsx +165 -0
- package/src/video/preview/__tests__/captionDragState.test.ts +120 -1
- package/src/video/preview/__tests__/latencyCompensation.test.tsx +451 -0
- package/src/video/preview/__tests__/proxySupport.test.ts +75 -0
- package/src/video/preview/__tests__/transformStyle.test.ts +29 -1
- package/src/video/preview/__tests__/useDragOverlay.perAxis.test.ts +197 -0
- package/src/video/preview/__tests__/useEnginePlayback.test.tsx +530 -0
- package/src/video/preview/__tests__/useVideoPlayback.corpus.test.ts +313 -0
- package/src/video/preview/__tests__/useVideoPlayback.test.ts +38 -5
- package/src/video/preview/__tests__/useVideoPlayback.trackAudio.test.ts +278 -0
- package/src/video/preview/audio-context.ts +111 -0
- package/src/video/preview/captionDragState.ts +91 -1
- package/src/video/preview/proxySupport.ts +86 -0
- package/src/video/preview/transformStyle.ts +22 -12
- package/src/video/preview/useDragOverlay.ts +92 -18
- package/src/video/preview/useEnginePlayback.ts +625 -0
- package/src/video/preview/useVideoPlayback.ts +211 -167
- package/src/video/sdrCurves.ts +56 -0
- package/src/video/shuttle.ts +159 -0
- package/src/video/source-preview.ts +66 -0
- package/src/video/timecode.ts +59 -0
- package/src/video/timeline/EditableSegment.tsx +1 -1
- package/src/video/timeline/Scrubber.tsx +46 -174
- package/src/video/timeline/SpeedControl.tsx +95 -0
- package/src/video/timeline/Timeline.tsx +1061 -328
- package/src/video/timeline/TimelineContext.ts +17 -10
- package/src/video/timeline/TrackGutter.tsx +560 -0
- package/src/video/timeline/TrackSettingsPopover.tsx +228 -0
- package/src/video/timeline/VolumeControl.tsx +113 -0
- package/src/video/timeline/__tests__/Timeline.backgroundClick.test.tsx +110 -0
- package/src/video/timeline/__tests__/Timeline.crossfade.test.tsx +104 -0
- package/src/video/timeline/__tests__/Timeline.fadeCurveMenu.test.tsx +174 -0
- package/src/video/timeline/__tests__/Timeline.keyframeDelete.test.tsx +273 -0
- package/src/video/timeline/__tests__/Timeline.keyframeFollow.test.tsx +215 -0
- package/src/video/timeline/__tests__/Timeline.keyframeMenu.test.tsx +253 -0
- package/src/video/timeline/__tests__/Timeline.keymap.test.tsx +372 -0
- package/src/video/timeline/__tests__/Timeline.subcutRegen.test.tsx +351 -0
- package/src/video/timeline/__tests__/TrackGutter.test.tsx +372 -0
- package/src/video/timeline/__tests__/_canvasSelect.test.tsx +273 -0
- package/src/video/timeline/__tests__/_canvasSelect.ts +414 -0
- package/src/video/timeline/__tests__/dragdrop-math.test.ts +135 -0
- package/src/video/timeline/__tests__/effectiveItemAudio.test.ts +50 -0
- package/src/video/timeline/__tests__/enabledTrackItems.test.ts +164 -0
- package/src/video/timeline/__tests__/moveItemAcrossTracks.test.ts +363 -0
- package/src/video/timeline/__tests__/multiSelectOps.test.ts +447 -0
- package/src/video/timeline/__tests__/placement.test.ts +278 -0
- package/src/video/timeline/__tests__/resizeWindowedItem.test.ts +140 -0
- package/src/video/timeline/__tests__/timeline-model.test.ts +576 -0
- package/src/video/timeline/__tests__/visualItemLabel.test.ts +69 -0
- package/src/video/timeline/canvas/TimelineCanvas.tsx +1447 -0
- package/src/video/timeline/canvas/__tests__/TimelineCanvas.drop.test.tsx +439 -0
- package/src/video/timeline/canvas/__tests__/TimelineCanvas.edgeScroll.test.tsx +346 -0
- package/src/video/timeline/canvas/__tests__/TimelineCanvas.panefill.test.tsx +122 -0
- package/src/video/timeline/canvas/__tests__/TimelineCanvas.pendingDrops.test.tsx +316 -0
- package/src/video/timeline/canvas/__tests__/TimelineCanvas.pointer.test.tsx +606 -0
- package/src/video/timeline/canvas/__tests__/TimelineCanvas.test.tsx +407 -0
- package/src/video/timeline/canvas/__tests__/clip-bands.test.ts +77 -0
- package/src/video/timeline/canvas/__tests__/draw.test.ts +2198 -0
- package/src/video/timeline/canvas/__tests__/fade-curve.test.ts +187 -0
- package/src/video/timeline/canvas/__tests__/filmstrips.test.ts +561 -0
- package/src/video/timeline/canvas/__tests__/hit-test.test.ts +818 -0
- package/src/video/timeline/canvas/__tests__/pending-drop.test.ts +210 -0
- package/src/video/timeline/canvas/__tests__/pointer-machine.test.ts +3358 -0
- package/src/video/timeline/canvas/__tests__/snap.test.ts +257 -0
- package/src/video/timeline/canvas/__tests__/viewport.test.ts +399 -0
- package/src/video/timeline/canvas/__tests__/waveforms.test.ts +946 -0
- package/src/video/timeline/canvas/clip-bands.ts +56 -0
- package/src/video/timeline/canvas/draw.ts +2187 -0
- package/src/video/timeline/canvas/fade-curve.ts +111 -0
- package/src/video/timeline/canvas/filmstrips.ts +418 -0
- package/src/video/timeline/canvas/hit-test.ts +501 -0
- package/src/video/timeline/canvas/keyframe-strip.ts +73 -0
- package/src/video/timeline/canvas/pointer-machine.ts +1828 -0
- package/src/video/timeline/canvas/snap.ts +232 -0
- package/src/video/timeline/canvas/viewport.ts +457 -0
- package/src/video/timeline/canvas/waveforms.ts +664 -0
- package/src/video/timeline/makeCaptionEdit.ts +5 -1
- package/src/video/timeline/multiSelectOps.ts +214 -55
- package/src/video/timeline/placement.ts +282 -0
- package/src/video/timeline/timeline-model.ts +919 -0
- package/src/video/timeline/useItemDragDrop.ts +151 -178
- package/src/video/timeline/useTimelineZoom.ts +25 -60
- package/src/video/timeline/utils.ts +0 -12
- package/src/video/use-report-context.ts +75 -0
- package/src/video/preview/OverlayPropsModal.tsx +0 -292
- package/src/video/preview/__tests__/OverlayPropsModal.test.tsx +0 -32
- package/src/video/timeline/AudioTrackRow.tsx +0 -404
- package/src/video/timeline/AudioWaveformLayer.tsx +0 -117
- package/src/video/timeline/CaptionTrackRow.tsx +0 -235
- package/src/video/timeline/PlayheadLine.tsx +0 -18
- package/src/video/timeline/TranscriptModal.tsx +0 -70
- package/src/video/timeline/TranscriptPanel.tsx +0 -273
- package/src/video/timeline/VisualTrackRow.tsx +0 -300
- package/src/video/timeline/__tests__/CaptionTrackRow.test.tsx +0 -241
- package/src/video/timeline/__tests__/PlayheadLine.test.tsx +0 -60
- package/src/video/timeline/__tests__/TranscriptModal.test.tsx +0 -41
- package/src/video/timeline/__tests__/TranscriptPanel.test.tsx +0 -184
- package/src/video/timeline/__tests__/useItemDragDrop.test.ts +0 -72
|
@@ -8,19 +8,28 @@ interface CaptionRegenModalProps<P extends Project = Project> {
|
|
|
8
8
|
/** Adapter driving the caption-regeneration stream. Must implement
|
|
9
9
|
* `generateCaptions` — callers gate rendering on its presence. */
|
|
10
10
|
adapter: EditorAdapter<P>
|
|
11
|
+
/** Caption rows the project has right now (`maxCaptionLane(segments) + 1`,
|
|
12
|
+
* so 1 for a lane-less or empty track). Regeneration replaces the whole
|
|
13
|
+
* track with a single fresh row, so this is the count the warning banner
|
|
14
|
+
* below reports as about to be discarded. */
|
|
15
|
+
existingRowCount: number
|
|
11
16
|
/** Fired on terminal success with the freshly transcribed caption track. The
|
|
12
17
|
* caller patches `project.captions` from this; the modal then closes. */
|
|
13
18
|
onDone: (captions: Captions) => void
|
|
14
19
|
/** Fired when the modal closes (cancel, error dismiss, or post-done). */
|
|
15
20
|
onClose: () => void
|
|
21
|
+
/** Editor theme mode — light/dark. The panel and log box follow
|
|
22
|
+
* `--editor-surface`, so log/status hues need to darken in light mode.
|
|
23
|
+
* Absent -> dark, matching every existing caller. */
|
|
24
|
+
mode?: 'light' | 'dark'
|
|
16
25
|
}
|
|
17
26
|
|
|
18
|
-
function LogLine({ text }: { text: string }) {
|
|
27
|
+
function LogLine({ text, mode = 'dark' }: { text: string; mode?: 'light' | 'dark' }) {
|
|
19
28
|
let color = 'text-[var(--editor-text)]/60'
|
|
20
|
-
if (/ready|complete|done|transcribed/i.test(text)) color = 'text-green-400'
|
|
21
|
-
else if (/transcrib|detecting|loading|model/i.test(text)) color = 'text-sky-400'
|
|
22
|
-
else if (/extract|building|composing/i.test(text))
|
|
23
|
-
else if (/error|fail|warn/i.test(text)) color = 'text-red-400'
|
|
29
|
+
if (/ready|complete|done|transcribed/i.test(text)) color = mode === 'light' ? 'text-green-700' : 'text-green-400'
|
|
30
|
+
else if (/transcrib|detecting|loading|model/i.test(text)) color = mode === 'light' ? 'text-sky-700' : 'text-sky-400'
|
|
31
|
+
else if (/extract|building|composing|mixing/i.test(text)) color = mode === 'light' ? 'text-amber-700' : 'text-amber-400'
|
|
32
|
+
else if (/error|fail|warn/i.test(text)) color = mode === 'light' ? 'text-red-600' : 'text-red-400'
|
|
24
33
|
|
|
25
34
|
return (
|
|
26
35
|
<span className={`leading-relaxed whitespace-pre-wrap break-all ${color}`}>
|
|
@@ -29,7 +38,7 @@ function LogLine({ text }: { text: string }) {
|
|
|
29
38
|
)
|
|
30
39
|
}
|
|
31
40
|
|
|
32
|
-
export default function CaptionRegenModal<P extends Project = Project>({ projectId, adapter, onDone, onClose }: CaptionRegenModalProps<P>) {
|
|
41
|
+
export default function CaptionRegenModal<P extends Project = Project>({ projectId, adapter, existingRowCount, onDone, onClose, mode = 'dark' }: CaptionRegenModalProps<P>) {
|
|
33
42
|
const [logs, setLogs] = useState<string[]>([])
|
|
34
43
|
const [status, setStatus] = useState<'running' | 'done' | 'error'>('running')
|
|
35
44
|
const [errorMsg, setError] = useState<string | null>(null)
|
|
@@ -124,6 +133,15 @@ export default function CaptionRegenModal<P extends Project = Project>({ project
|
|
|
124
133
|
: status === 'done' ? 'Captions regenerated'
|
|
125
134
|
: 'Caption regeneration failed'}
|
|
126
135
|
</h2>
|
|
136
|
+
{/* What gets transcribed is not obvious from the button that
|
|
137
|
+
opened this modal, and mute is the one control the editor
|
|
138
|
+
has over it — worth one line here rather than a support
|
|
139
|
+
question about why a clip's audio showed up in the
|
|
140
|
+
captions. Read at generation time only: unmuting later just
|
|
141
|
+
means the next run sees it. */}
|
|
142
|
+
<p className="text-[11px] text-[var(--editor-text)]/50">
|
|
143
|
+
Transcribes everything audible on the timeline. Muted clips and tracks are skipped.
|
|
144
|
+
</p>
|
|
127
145
|
</div>
|
|
128
146
|
</div>
|
|
129
147
|
{status !== 'running' && (
|
|
@@ -131,6 +149,16 @@ export default function CaptionRegenModal<P extends Project = Project>({ project
|
|
|
131
149
|
)}
|
|
132
150
|
</div>
|
|
133
151
|
|
|
152
|
+
{/* Discard warning — only when there is more than one row to lose.
|
|
153
|
+
Regeneration always replaces project.captions wholesale with a
|
|
154
|
+
single fresh row (see onDone below), so a hand-built second or
|
|
155
|
+
third row (a title card, a call-out) is silently gone otherwise. */}
|
|
156
|
+
{existingRowCount > 1 && (
|
|
157
|
+
<div className={`px-5 py-2 text-[11px] leading-snug border-b border-[var(--editor-border)] ${mode === 'light' ? 'text-amber-700' : 'text-amber-400/90'}`}>
|
|
158
|
+
This will replace all {existingRowCount} caption rows with a single new row.
|
|
159
|
+
</div>
|
|
160
|
+
)}
|
|
161
|
+
|
|
134
162
|
{/* Log output */}
|
|
135
163
|
<div className="relative">
|
|
136
164
|
<button
|
|
@@ -148,10 +176,10 @@ export default function CaptionRegenModal<P extends Project = Project>({ project
|
|
|
148
176
|
<span className="text-[var(--editor-text)]/40 italic">Starting transcription…</span>
|
|
149
177
|
)}
|
|
150
178
|
{logs.map((line, i) => (
|
|
151
|
-
<LogLine key={i} text={line} />
|
|
179
|
+
<LogLine key={i} text={line} mode={mode} />
|
|
152
180
|
))}
|
|
153
181
|
{status === 'error' && errorMsg && (
|
|
154
|
-
<span className=
|
|
182
|
+
<span className={`mt-1 ${mode === 'light' ? 'text-red-600' : 'text-red-400'}`}>{errorMsg}</span>
|
|
155
183
|
)}
|
|
156
184
|
</div>
|
|
157
185
|
</div>
|
|
@@ -161,7 +189,7 @@ export default function CaptionRegenModal<P extends Project = Project>({ project
|
|
|
161
189
|
{status === 'running' ? (
|
|
162
190
|
<button
|
|
163
191
|
onClick={handleCancel}
|
|
164
|
-
className=
|
|
192
|
+
className={`text-sm px-4 py-1.5 rounded-md bg-[var(--editor-surface)] border border-[var(--editor-border)] text-[var(--editor-text)]/80 transition-colors ${mode === 'light' ? 'hover:bg-red-50 hover:border-red-300 hover:text-red-700' : 'hover:bg-red-900/40 hover:border-red-700 hover:text-red-300'}`}
|
|
165
193
|
>
|
|
166
194
|
Cancel
|
|
167
195
|
</button>
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CaptionSpecimen — the live font-and-size specimen strip for the caption
|
|
3
|
+
* styling panel. Shows the ACTIVE WORD from the user's own video (via
|
|
4
|
+
* `captionActiveWord`), rendered in the candidate typeface at a size
|
|
5
|
+
* proportional to the real caption render size, so a font choice is judged
|
|
6
|
+
* against real content and real scale rather than fixed-size placeholder
|
|
7
|
+
* text.
|
|
8
|
+
*
|
|
9
|
+
* This is deliberately NOT a duplicate of `FontFamilyPicker`'s dropdown
|
|
10
|
+
* (src/text/FontPicker.tsx), which already previews every option in its own
|
|
11
|
+
* face, at a small fixed UI size, inside the list itself. This strip is the
|
|
12
|
+
* larger, in-context confirmation of the CURRENT pick — real word, real
|
|
13
|
+
* relative size.
|
|
14
|
+
*
|
|
15
|
+
* Presentational only: takes caption/time/style data in, owns no state
|
|
16
|
+
* beyond the width measurement below, and never touches the project.
|
|
17
|
+
*/
|
|
18
|
+
import { useEffect, useRef, useState } from 'react'
|
|
19
|
+
import type { Captions } from '../schema'
|
|
20
|
+
import { activeCaptionWord } from './captionActiveWord'
|
|
21
|
+
import { findFontOption } from '../text/FontPicker'
|
|
22
|
+
import { CAPTION_STYLE_FONT_FAMILY, CAPTION_STYLE_FONT_WEIGHT } from './captionStyleDefaults'
|
|
23
|
+
|
|
24
|
+
// Captions render against a fixed 1080-wide frame — a `fontsize` of 46 is
|
|
25
|
+
// 46px OF THAT 1080-wide frame, not 46 real screen px (see RENDER_W in
|
|
26
|
+
// preview/CaptionPreview.tsx, the source of truth for this constant).
|
|
27
|
+
// Duplicated rather than imported: CaptionPreview.tsx is being edited
|
|
28
|
+
// elsewhere in this change and must not gain a new export for this.
|
|
29
|
+
const RENDER_W = 1080
|
|
30
|
+
|
|
31
|
+
// Readable floor / blow-out ceiling for the specimen word's on-screen size.
|
|
32
|
+
// The floor covers two degenerate cases: before the first ResizeObserver
|
|
33
|
+
// callback fires the measured width is 0, and in a test environment where
|
|
34
|
+
// no callback ever fires (or jsdom, which never lays anything out) it STAYS
|
|
35
|
+
// 0 — both would otherwise compute a 0px, invisible specimen. The ceiling
|
|
36
|
+
// keeps an unexpectedly wide host row from blowing the specimen past a
|
|
37
|
+
// normal caption-panel row height.
|
|
38
|
+
const MIN_SPECIMEN_PX = 11
|
|
39
|
+
const MAX_SPECIMEN_PX = 64
|
|
40
|
+
|
|
41
|
+
export interface CaptionSpecimenProps {
|
|
42
|
+
captions: Captions | undefined
|
|
43
|
+
currentTime: number
|
|
44
|
+
selectedSegmentId?: string
|
|
45
|
+
/** The candidate family being previewed — `captions.fontFamily`, or the
|
|
46
|
+
* style's default when unset. */
|
|
47
|
+
fontFamily?: string
|
|
48
|
+
/** The caption's real render font size, in project pixels (`captions.fontsize`). */
|
|
49
|
+
fontSize: number
|
|
50
|
+
textTransform?: string
|
|
51
|
+
letterSpacing?: string
|
|
52
|
+
/** `captions.fontWeight` — absent means the active style's own designed
|
|
53
|
+
* weight (the browser/template default), so leaving this unset is what
|
|
54
|
+
* the specimen should show for "bold on". */
|
|
55
|
+
fontWeight?: number | string
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export default function CaptionSpecimen({
|
|
59
|
+
captions,
|
|
60
|
+
currentTime,
|
|
61
|
+
selectedSegmentId,
|
|
62
|
+
fontFamily,
|
|
63
|
+
fontSize,
|
|
64
|
+
textTransform,
|
|
65
|
+
letterSpacing,
|
|
66
|
+
fontWeight,
|
|
67
|
+
}: CaptionSpecimenProps) {
|
|
68
|
+
const wrapRef = useRef<HTMLDivElement>(null)
|
|
69
|
+
const [measuredWidth, setMeasuredWidth] = useState(0)
|
|
70
|
+
|
|
71
|
+
// Measure the strip's own width so the specimen can be scaled the same way
|
|
72
|
+
// CaptionPreview scales the caption layer: `measuredWidth / RENDER_W`.
|
|
73
|
+
useEffect(() => {
|
|
74
|
+
const el = wrapRef.current
|
|
75
|
+
if (!el) return
|
|
76
|
+
const obs = new ResizeObserver(([entry]) => {
|
|
77
|
+
setMeasuredWidth(entry.contentRect.width)
|
|
78
|
+
})
|
|
79
|
+
obs.observe(el)
|
|
80
|
+
return () => obs.disconnect()
|
|
81
|
+
}, [])
|
|
82
|
+
|
|
83
|
+
const active = activeCaptionWord(captions, currentTime, selectedSegmentId)
|
|
84
|
+
|
|
85
|
+
// Mirrors captionActiveWord's own `currentTime >= seg.start && currentTime
|
|
86
|
+
// < seg.end` predicate (captionActiveWord.ts) rather than importing a
|
|
87
|
+
// helper for it — used only to tell whether the resolved word came from
|
|
88
|
+
// directly under the playhead (some segment is active) or from the
|
|
89
|
+
// selectedSegmentId fallback (activeCaptionWord only reaches for that
|
|
90
|
+
// fallback when no segment is active at all).
|
|
91
|
+
const anySegmentActive = (captions?.segments ?? []).some(
|
|
92
|
+
(seg) => currentTime >= seg.start && currentTime < seg.end,
|
|
93
|
+
)
|
|
94
|
+
const isFallback = !!active && !anySegmentActive
|
|
95
|
+
|
|
96
|
+
const scale = measuredWidth / RENDER_W
|
|
97
|
+
const specimenPx = Math.min(MAX_SPECIMEN_PX, Math.max(MIN_SPECIMEN_PX, fontSize * scale))
|
|
98
|
+
|
|
99
|
+
const fontOption = fontFamily ? findFontOption(fontFamily) : null
|
|
100
|
+
const familyLabel = fontFamily ? fontOption?.label ?? 'Custom' : 'Default'
|
|
101
|
+
|
|
102
|
+
// The specimen must show what will ACTUALLY render, not just what was
|
|
103
|
+
// explicitly picked — `fontWeight`/`fontFamily` are absent by default
|
|
104
|
+
// (each style's own designed weight/face), so falling back to the raw
|
|
105
|
+
// props here would always preview weight 400 in the editor's own UI font,
|
|
106
|
+
// never the style's real look. `captionStyleDefaults.ts` is the duplicated
|
|
107
|
+
// source of truth for what each style's template actually defaults to; a
|
|
108
|
+
// style string this table doesn't recognize (or no `captions` at all)
|
|
109
|
+
// resolves to `undefined` here, same as before this fallback existed.
|
|
110
|
+
const styleDefaultWeight = captions ? CAPTION_STYLE_FONT_WEIGHT[captions.style] : undefined
|
|
111
|
+
const styleDefaultFamily = captions ? CAPTION_STYLE_FONT_FAMILY[captions.style] : undefined
|
|
112
|
+
const resolvedFontWeight = fontWeight ?? styleDefaultWeight
|
|
113
|
+
const resolvedFontFamily = fontFamily ?? styleDefaultFamily
|
|
114
|
+
|
|
115
|
+
return (
|
|
116
|
+
<div
|
|
117
|
+
ref={wrapRef}
|
|
118
|
+
data-testid="caption-specimen"
|
|
119
|
+
className="flex flex-col gap-1 rounded-md border border-[var(--editor-border)] bg-[var(--editor-surface)] px-2.5 py-2"
|
|
120
|
+
>
|
|
121
|
+
<div className="flex items-center justify-between gap-2">
|
|
122
|
+
{active ? (
|
|
123
|
+
<span
|
|
124
|
+
data-testid="caption-specimen-word"
|
|
125
|
+
className="flex-1 min-w-0 truncate leading-tight text-[var(--editor-text)]"
|
|
126
|
+
style={{
|
|
127
|
+
fontFamily: resolvedFontFamily,
|
|
128
|
+
fontSize: `${specimenPx}px`,
|
|
129
|
+
fontWeight: resolvedFontWeight,
|
|
130
|
+
textTransform,
|
|
131
|
+
letterSpacing,
|
|
132
|
+
}}
|
|
133
|
+
>
|
|
134
|
+
{active.word}
|
|
135
|
+
</span>
|
|
136
|
+
) : (
|
|
137
|
+
<span
|
|
138
|
+
data-testid="caption-specimen-word"
|
|
139
|
+
className="flex-1 min-w-0 truncate text-xs italic text-[var(--editor-text)] opacity-50"
|
|
140
|
+
>
|
|
141
|
+
Move the playhead over a caption to preview it
|
|
142
|
+
</span>
|
|
143
|
+
)}
|
|
144
|
+
{isFallback && (
|
|
145
|
+
<span className="shrink-0 rounded border border-[var(--editor-border)] px-1 py-px text-[9px] text-[var(--editor-text)] opacity-60">
|
|
146
|
+
Selected caption
|
|
147
|
+
</span>
|
|
148
|
+
)}
|
|
149
|
+
</div>
|
|
150
|
+
|
|
151
|
+
{/* Family + size stay visible in the empty state too, so the row does
|
|
152
|
+
not change height when the playhead moves on/off a caption. */}
|
|
153
|
+
<div className="flex items-center gap-1.5 text-[10px] text-[var(--editor-text)] opacity-50">
|
|
154
|
+
<span className="truncate">{familyLabel}</span>
|
|
155
|
+
<span aria-hidden="true">·</span>
|
|
156
|
+
<span className="font-mono">{fontSize}px</span>
|
|
157
|
+
</div>
|
|
158
|
+
</div>
|
|
159
|
+
)
|
|
160
|
+
}
|