@bycrux/editor 0.8.9 → 0.10.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/package.json +1 -1
- package/src/carousel/CarouselEditor.tsx +34 -14
- package/src/carousel/SlidePropertyPanel.tsx +1 -35
- package/src/index.ts +4 -0
- package/src/schema.ts +11 -3
- package/src/ui/SwatchInput.tsx +57 -0
- package/src/ui/index.ts +2 -0
- package/src/video/VideoEditor.tsx +107 -14
- package/src/video/__tests__/VideoEditor.test.tsx +34 -0
- package/src/video/__tests__/playback-clock.test.tsx +45 -0
- package/src/video/playback-clock.ts +31 -0
- package/src/video/preview/OverlayItemsLayer.tsx +36 -13
- package/src/video/preview/OverlayPropsModal.tsx +292 -0
- package/src/video/preview/PreviewPlayer.tsx +14 -6
- package/src/video/preview/__tests__/OverlayItemsLayer.edit.test.tsx +106 -0
- package/src/video/preview/__tests__/OverlayPropsModal.test.tsx +32 -0
- package/src/video/preview/__tests__/overlay-prop-fields.test.ts +44 -0
- package/src/video/preview/overlay-prop-fields.ts +39 -0
- package/src/video/preview/useDragOverlay.ts +21 -1
- package/src/video/preview/useVideoPlayback.ts +12 -3
- package/src/video/timeline/AudioTrackRow.tsx +6 -8
- package/src/video/timeline/PlayheadLine.tsx +18 -0
- package/src/video/timeline/Scrubber.tsx +7 -5
- package/src/video/timeline/Timeline.tsx +64 -29
- package/src/video/timeline/TimelineContext.ts +2 -2
- package/src/video/timeline/TranscriptPanel.tsx +57 -0
- package/src/video/timeline/VisualTrackRow.tsx +21 -14
- package/src/video/timeline/__tests__/PlayheadLine.test.tsx +60 -0
- package/src/video/timeline/__tests__/TranscriptPanel.test.tsx +85 -0
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/// <reference types="vitest/globals" />
|
|
2
|
+
import { render, act } from '@testing-library/react'
|
|
3
|
+
import { createPlaybackClock } from '../../playback-clock'
|
|
4
|
+
import { TimelineContext, type TimelineContextValue } from '../TimelineContext'
|
|
5
|
+
import PlayheadLine from '../PlayheadLine'
|
|
6
|
+
|
|
7
|
+
function makeCtx(overrides: Partial<TimelineContextValue> = {}): TimelineContextValue {
|
|
8
|
+
return {
|
|
9
|
+
totalDuration: 10,
|
|
10
|
+
contentDuration: 10,
|
|
11
|
+
snapBoundaries: [],
|
|
12
|
+
zoom: 1,
|
|
13
|
+
zoomRef: { current: 1 },
|
|
14
|
+
scrollRef: { current: null },
|
|
15
|
+
scrubberRef: { current: null },
|
|
16
|
+
overlayDraggedRef: { current: false },
|
|
17
|
+
clock: createPlaybackClock(),
|
|
18
|
+
markers: [null, null],
|
|
19
|
+
setMarkers: () => {},
|
|
20
|
+
selection: null,
|
|
21
|
+
...overrides,
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
test('PlayheadLine moves on clock.set without re-rendering a sibling row', () => {
|
|
26
|
+
const ctx = makeCtx()
|
|
27
|
+
let siblingRenders = 0
|
|
28
|
+
|
|
29
|
+
function Sibling() {
|
|
30
|
+
siblingRenders++
|
|
31
|
+
return null
|
|
32
|
+
}
|
|
33
|
+
function Root() {
|
|
34
|
+
return (
|
|
35
|
+
<TimelineContext.Provider value={ctx}>
|
|
36
|
+
<PlayheadLine />
|
|
37
|
+
<Sibling />
|
|
38
|
+
</TimelineContext.Provider>
|
|
39
|
+
)
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const { container } = render(<Root />)
|
|
43
|
+
const before = siblingRenders
|
|
44
|
+
|
|
45
|
+
act(() => { ctx.clock.set(4) })
|
|
46
|
+
|
|
47
|
+
const line = container.querySelector('div') as HTMLDivElement
|
|
48
|
+
expect(line.style.left).toBe('40%')
|
|
49
|
+
expect(siblingRenders).toBe(before)
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
test('renders nothing when totalDuration is 0', () => {
|
|
53
|
+
const ctx = makeCtx({ totalDuration: 0 })
|
|
54
|
+
const { container } = render(
|
|
55
|
+
<TimelineContext.Provider value={ctx}>
|
|
56
|
+
<PlayheadLine />
|
|
57
|
+
</TimelineContext.Provider>,
|
|
58
|
+
)
|
|
59
|
+
expect(container.firstChild).toBeNull()
|
|
60
|
+
})
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { describe, it, expect, vi, afterEach } from 'vitest'
|
|
2
|
+
import { render, screen, fireEvent, cleanup } from '@testing-library/react'
|
|
3
|
+
import type { Project } from '../../../types'
|
|
4
|
+
import type { Captions } from '../../../schema'
|
|
5
|
+
import TranscriptPanel from '../TranscriptPanel'
|
|
6
|
+
|
|
7
|
+
afterEach(() => cleanup())
|
|
8
|
+
|
|
9
|
+
function makeProject(style: Captions['style'], extra: Partial<Captions> = {}): Project {
|
|
10
|
+
return { id: 'p1', captions: { style, segments: [], ...extra } } as unknown as Project
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function renderPanel(style: Captions['style'], extra: Partial<Captions> = {}) {
|
|
14
|
+
const onCaptionEdit = vi.fn()
|
|
15
|
+
const onProjectChange = vi.fn()
|
|
16
|
+
const project = makeProject(style, extra)
|
|
17
|
+
render(
|
|
18
|
+
<TranscriptPanel
|
|
19
|
+
project={project}
|
|
20
|
+
captionTrack={project.captions}
|
|
21
|
+
currentTime={0}
|
|
22
|
+
onCaptionEdit={onCaptionEdit}
|
|
23
|
+
onProjectChange={onProjectChange}
|
|
24
|
+
onExpand={() => {}}
|
|
25
|
+
/>,
|
|
26
|
+
)
|
|
27
|
+
return { onCaptionEdit, onProjectChange }
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
describe('TranscriptPanel caption color controls', () => {
|
|
31
|
+
it('previews live on change and commits on blur, writing captions.color', () => {
|
|
32
|
+
const { onCaptionEdit, onProjectChange } = renderPanel('karaoke')
|
|
33
|
+
const input = screen.getByLabelText('Caption text color') as HTMLInputElement
|
|
34
|
+
|
|
35
|
+
fireEvent.change(input, { target: { value: '#76b900' } })
|
|
36
|
+
expect(onProjectChange).toHaveBeenCalledTimes(1)
|
|
37
|
+
expect(onProjectChange.mock.calls[0][0].captions.color).toBe('#76b900')
|
|
38
|
+
expect(onCaptionEdit).not.toHaveBeenCalled() // live preview must not persist
|
|
39
|
+
|
|
40
|
+
fireEvent.blur(input, { target: { value: '#76b900' } })
|
|
41
|
+
expect(onCaptionEdit).toHaveBeenCalledTimes(1)
|
|
42
|
+
expect(onCaptionEdit.mock.calls[0][0].captions.color).toBe('#76b900')
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
it('writes the accent to the field the active style actually reads', () => {
|
|
46
|
+
// karaoke → highlightColor
|
|
47
|
+
let h = renderPanel('karaoke')
|
|
48
|
+
fireEvent.blur(screen.getByLabelText('Caption highlight color'), { target: { value: '#111111' } })
|
|
49
|
+
expect(h.onCaptionEdit.mock.calls[0][0].captions.highlightColor).toBe('#111111')
|
|
50
|
+
cleanup()
|
|
51
|
+
|
|
52
|
+
// pop → activeColor
|
|
53
|
+
h = renderPanel('pop')
|
|
54
|
+
fireEvent.blur(screen.getByLabelText('Caption active color'), { target: { value: '#222222' } })
|
|
55
|
+
expect(h.onCaptionEdit.mock.calls[0][0].captions.activeColor).toBe('#222222')
|
|
56
|
+
cleanup()
|
|
57
|
+
|
|
58
|
+
// subtitle → backgroundColor
|
|
59
|
+
h = renderPanel('subtitle')
|
|
60
|
+
fireEvent.blur(screen.getByLabelText('Caption box color'), { target: { value: '#333333' } })
|
|
61
|
+
expect(h.onCaptionEdit.mock.calls[0][0].captions.backgroundColor).toBe('#333333')
|
|
62
|
+
cleanup()
|
|
63
|
+
|
|
64
|
+
// highlight-box → accentColor
|
|
65
|
+
h = renderPanel('highlight-box')
|
|
66
|
+
fireEvent.blur(screen.getByLabelText('Caption accent color'), { target: { value: '#444444' } })
|
|
67
|
+
expect(h.onCaptionEdit.mock.calls[0][0].captions.accentColor).toBe('#444444')
|
|
68
|
+
})
|
|
69
|
+
|
|
70
|
+
it('hides the accent swatch for styles with no accent', () => {
|
|
71
|
+
renderPanel('clean')
|
|
72
|
+
expect(screen.getByLabelText('Caption text color')).toBeTruthy()
|
|
73
|
+
expect(screen.queryByLabelText(/(accent|highlight|active|box) color/i)).toBeNull()
|
|
74
|
+
cleanup()
|
|
75
|
+
|
|
76
|
+
renderPanel('word-by-word')
|
|
77
|
+
expect(screen.queryByLabelText(/(accent|highlight|active|box) color/i)).toBeNull()
|
|
78
|
+
})
|
|
79
|
+
|
|
80
|
+
it('reflects the stored hex value on the swatch input', () => {
|
|
81
|
+
renderPanel('karaoke', { color: '#abcdef', highlightColor: '#00ff00' })
|
|
82
|
+
expect((screen.getByLabelText('Caption text color') as HTMLInputElement).value).toBe('#abcdef')
|
|
83
|
+
expect((screen.getByLabelText('Caption highlight color') as HTMLInputElement).value).toBe('#00ff00')
|
|
84
|
+
})
|
|
85
|
+
})
|