@bendyline/squisq-react 1.0.2 → 1.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/BlockRenderer.d.ts.map +1 -1
- package/dist/BlockRenderer.js +3 -0
- package/dist/BlockRenderer.js.map +1 -1
- package/dist/CaptionOverlay.d.ts +16 -7
- package/dist/CaptionOverlay.d.ts.map +1 -1
- package/dist/CaptionOverlay.js +7 -3
- package/dist/CaptionOverlay.js.map +1 -1
- package/dist/DocControlsBottom.d.ts.map +1 -1
- package/dist/DocControlsBottom.js +5 -1
- package/dist/DocControlsBottom.js.map +1 -1
- package/dist/DocControlsOverlay.d.ts.map +1 -1
- package/dist/DocControlsOverlay.js +9 -4
- package/dist/DocControlsOverlay.js.map +1 -1
- package/dist/DocControlsSidebar.d.ts.map +1 -1
- package/dist/DocControlsSidebar.js +5 -1
- package/dist/DocControlsSidebar.js.map +1 -1
- package/dist/DocPlayer.d.ts +5 -2
- package/dist/DocPlayer.d.ts.map +1 -1
- package/dist/DocPlayer.js +36 -7
- package/dist/DocPlayer.js.map +1 -1
- package/dist/DocPlayerWithSidebar.d.ts.map +1 -1
- package/dist/DocPlayerWithSidebar.js +1 -0
- package/dist/DocPlayerWithSidebar.js.map +1 -1
- package/dist/DocProgressBar.js +2 -2
- package/dist/LinearDocView.d.ts.map +1 -1
- package/dist/LinearDocView.js +130 -6
- package/dist/LinearDocView.js.map +1 -1
- package/dist/SocialCaptionOverlay.d.ts +27 -0
- package/dist/SocialCaptionOverlay.d.ts.map +1 -0
- package/dist/SocialCaptionOverlay.js +184 -0
- package/dist/SocialCaptionOverlay.js.map +1 -0
- package/dist/__tests__/DocControlsSlideshow.test.js +1 -0
- package/dist/__tests__/DocControlsSlideshow.test.js.map +1 -1
- package/dist/__tests__/TableLayer.test.d.ts +2 -0
- package/dist/__tests__/TableLayer.test.d.ts.map +1 -0
- package/dist/__tests__/TableLayer.test.js +121 -0
- package/dist/__tests__/TableLayer.test.js.map +1 -0
- package/dist/hooks/useAudioSync.d.ts.map +1 -1
- package/dist/hooks/useAudioSync.js +4 -2
- package/dist/hooks/useAudioSync.js.map +1 -1
- package/dist/hooks/useDocPlayback.d.ts.map +1 -1
- package/dist/hooks/useDocPlayback.js +7 -4
- package/dist/hooks/useDocPlayback.js.map +1 -1
- package/dist/index.d.ts +3 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/layers/ShapeLayer.d.ts.map +1 -1
- package/dist/layers/ShapeLayer.js +7 -3
- package/dist/layers/ShapeLayer.js.map +1 -1
- package/dist/layers/TableLayer.d.ts +23 -0
- package/dist/layers/TableLayer.d.ts.map +1 -0
- package/dist/layers/TableLayer.js +58 -0
- package/dist/layers/TableLayer.js.map +1 -0
- package/dist/layers/TextLayer.js +1 -1
- package/dist/layers/TextLayer.js.map +1 -1
- package/dist/squisq-player.global.js +91 -5
- package/dist/squisq-player.global.js.map +1 -1
- package/dist/standalone-entry.d.ts +8 -0
- package/dist/standalone-entry.d.ts.map +1 -1
- package/dist/standalone-entry.js +6 -3
- package/dist/standalone-entry.js.map +1 -1
- package/dist/standalone-source.js +1 -1
- package/dist/types.d.ts +19 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/package.json +2 -2
- package/src/BlockRenderer.tsx +3 -0
- package/src/CaptionOverlay.tsx +33 -8
- package/src/DocControlsBottom.tsx +11 -5
- package/src/DocControlsOverlay.tsx +18 -6
- package/src/DocControlsSidebar.tsx +11 -5
- package/src/DocPlayer.tsx +56 -9
- package/src/DocPlayerWithSidebar.tsx +1 -0
- package/src/DocProgressBar.tsx +2 -2
- package/src/LinearDocView.tsx +149 -12
- package/src/SocialCaptionOverlay.tsx +254 -0
- package/src/__tests__/DocControlsSlideshow.test.tsx +1 -0
- package/src/__tests__/TableLayer.test.tsx +142 -0
- package/src/hooks/useAudioSync.ts +5 -2
- package/src/hooks/useDocPlayback.ts +8 -3
- package/src/index.ts +4 -0
- package/src/layers/ShapeLayer.tsx +8 -3
- package/src/layers/TableLayer.tsx +129 -0
- package/src/layers/TextLayer.tsx +1 -1
- package/src/standalone-entry.tsx +23 -3
- package/src/types.ts +21 -0
package/dist/types.d.ts
CHANGED
|
@@ -28,6 +28,21 @@ export type ControlsLayout = 'overlay' | 'sidebar' | 'bottom';
|
|
|
28
28
|
* No audio, no timeline, no controls — just a scrollable page.
|
|
29
29
|
*/
|
|
30
30
|
export type DisplayMode = 'video' | 'slideshow' | 'linear';
|
|
31
|
+
/**
|
|
32
|
+
* Caption display style.
|
|
33
|
+
*
|
|
34
|
+
* - `'standard'` — Traditional broadcast-style captions: small white text
|
|
35
|
+
* on a semi-transparent black badge at the top of the player.
|
|
36
|
+
* - `'social'` — Social media-style (Instagram/TikTok): large centered words
|
|
37
|
+
* showing 3-5 words at a time with the active word highlighted in the
|
|
38
|
+
* theme's primary color. Font and colors pulled from the active theme.
|
|
39
|
+
*/
|
|
40
|
+
export type CaptionStyle = 'standard' | 'social';
|
|
41
|
+
/**
|
|
42
|
+
* Caption display mode — combines enable/disable with style selection.
|
|
43
|
+
* The CC button cycles through: off → standard → social → off.
|
|
44
|
+
*/
|
|
45
|
+
export type CaptionMode = 'off' | 'standard' | 'social';
|
|
31
46
|
/** Slide navigation actions for slideshow display mode */
|
|
32
47
|
export interface SlideNavActions {
|
|
33
48
|
/** Navigate to the next slide */
|
|
@@ -47,6 +62,8 @@ export interface PlaybackState {
|
|
|
47
62
|
docProgress: number;
|
|
48
63
|
hasCaptions: boolean;
|
|
49
64
|
captionsEnabled: boolean;
|
|
65
|
+
/** Current caption display mode (off, standard, social). */
|
|
66
|
+
captionMode: CaptionMode;
|
|
50
67
|
isFullscreen?: boolean;
|
|
51
68
|
/** Current audio segment index (0-based) */
|
|
52
69
|
currentSegmentIndex: number;
|
|
@@ -61,6 +78,8 @@ export interface PlaybackActions {
|
|
|
61
78
|
restart: () => void;
|
|
62
79
|
seekTo: (time: number) => void;
|
|
63
80
|
setCaptionsEnabled: (enabled: boolean) => void;
|
|
81
|
+
/** Cycle caption mode: off → standard → social → off */
|
|
82
|
+
cycleCaptionMode: () => void;
|
|
64
83
|
toggleFullscreen?: () => void;
|
|
65
84
|
}
|
|
66
85
|
/** Block marker data for the progress bar */
|
package/dist/types.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
|
package/dist/types.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AA0JH,6CAA6C;AAC7C,MAAM,UAAU,UAAU,CAAC,OAAe;IACxC,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,GAAG,EAAE,CAAC,CAAC;IACtC,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,GAAG,EAAE,CAAC,CAAC;IACtC,OAAO,GAAG,IAAI,IAAI,IAAI,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,CAAC;AACvD,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bendyline/squisq-react",
|
|
3
|
-
"version": "1.0
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"description": "React component library for doc playback, block rendering, and media layers",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Bendyline",
|
|
@@ -52,7 +52,7 @@
|
|
|
52
52
|
"react-dom": "^18.0.0 || ^19.0.0"
|
|
53
53
|
},
|
|
54
54
|
"dependencies": {
|
|
55
|
-
"@bendyline/squisq": "1.
|
|
55
|
+
"@bendyline/squisq": "1.2.0"
|
|
56
56
|
},
|
|
57
57
|
"devDependencies": {
|
|
58
58
|
"@types/react": "^18.0.0",
|
package/src/BlockRenderer.tsx
CHANGED
|
@@ -12,6 +12,7 @@ import { TextLayer } from './layers/TextLayer';
|
|
|
12
12
|
import { ShapeLayer } from './layers/ShapeLayer';
|
|
13
13
|
import { MapLayer } from './layers/MapLayer';
|
|
14
14
|
import { VideoLayer } from './layers/VideoLayer';
|
|
15
|
+
import { TableLayer } from './layers/TableLayer';
|
|
15
16
|
import { getTransitionClass } from './utils/animationUtils';
|
|
16
17
|
|
|
17
18
|
/** Default viewport dimensions (1080p landscape) - for backwards compatibility */
|
|
@@ -137,6 +138,8 @@ function LayerRenderer({ layer, basePath, viewport, blockTime, isPlaying }: Laye
|
|
|
137
138
|
isPlaying={isPlaying}
|
|
138
139
|
/>
|
|
139
140
|
);
|
|
141
|
+
case 'table':
|
|
142
|
+
return <TableLayer layer={layer} viewport={viewport} blockTime={blockTime} />;
|
|
140
143
|
default:
|
|
141
144
|
console.warn(`Unknown layer type: ${(layer as Layer).type}`);
|
|
142
145
|
return null;
|
package/src/CaptionOverlay.tsx
CHANGED
|
@@ -2,15 +2,19 @@
|
|
|
2
2
|
* CaptionOverlay Component
|
|
3
3
|
*
|
|
4
4
|
* Displays closed captions synchronized with audio playback.
|
|
5
|
-
*
|
|
6
|
-
* persistent element (no mount/unmount flicker).
|
|
5
|
+
* Supports two styles:
|
|
7
6
|
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
7
|
+
* - `'standard'` (default): Traditional broadcast captions — small white text
|
|
8
|
+
* on a semi-transparent black badge at the top of the player.
|
|
9
|
+
* - `'social'`: Social media-style (Instagram/TikTok) — large centered words
|
|
10
|
+
* with the active word highlighted in the theme's primary color.
|
|
10
11
|
*/
|
|
11
12
|
|
|
12
|
-
import type { CaptionTrack } from '@bendyline/squisq/schemas';
|
|
13
|
+
import type { CaptionTrack, ViewportConfig } from '@bendyline/squisq/schemas';
|
|
13
14
|
import { getCaptionAtTime } from '@bendyline/squisq/schemas';
|
|
15
|
+
import type { Theme } from '@bendyline/squisq/schemas';
|
|
16
|
+
import type { CaptionStyle } from './types';
|
|
17
|
+
import { SocialCaptionOverlay } from './SocialCaptionOverlay';
|
|
14
18
|
|
|
15
19
|
interface CaptionOverlayProps {
|
|
16
20
|
/** Caption track with timestamped phrases */
|
|
@@ -19,8 +23,14 @@ interface CaptionOverlayProps {
|
|
|
19
23
|
currentTime: number;
|
|
20
24
|
/** Whether captions are enabled */
|
|
21
25
|
enabled?: boolean;
|
|
22
|
-
/** Font size in pixels (default: 16) */
|
|
26
|
+
/** Font size in pixels for standard style (default: 16) */
|
|
23
27
|
fontSize?: number;
|
|
28
|
+
/** Caption display style (default: 'standard') */
|
|
29
|
+
captionStyle?: CaptionStyle;
|
|
30
|
+
/** Theme for social-style caption colors and fonts */
|
|
31
|
+
theme?: Theme;
|
|
32
|
+
/** Viewport config for social-style font scaling */
|
|
33
|
+
viewport?: ViewportConfig;
|
|
24
34
|
}
|
|
25
35
|
|
|
26
36
|
export function CaptionOverlay({
|
|
@@ -28,9 +38,24 @@ export function CaptionOverlay({
|
|
|
28
38
|
currentTime,
|
|
29
39
|
enabled = true,
|
|
30
40
|
fontSize = 16,
|
|
41
|
+
captionStyle = 'standard',
|
|
42
|
+
theme,
|
|
43
|
+
viewport,
|
|
31
44
|
}: CaptionOverlayProps) {
|
|
32
|
-
//
|
|
33
|
-
|
|
45
|
+
// Delegate to social overlay when that style is active
|
|
46
|
+
if (captionStyle === 'social') {
|
|
47
|
+
return (
|
|
48
|
+
<SocialCaptionOverlay
|
|
49
|
+
captions={captions}
|
|
50
|
+
currentTime={currentTime}
|
|
51
|
+
enabled={enabled}
|
|
52
|
+
theme={theme}
|
|
53
|
+
viewport={viewport}
|
|
54
|
+
/>
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Standard caption rendering
|
|
34
59
|
const phrase = enabled && captions ? getCaptionAtTime(captions, currentTime) : null;
|
|
35
60
|
const captionText = phrase?.text ?? null;
|
|
36
61
|
|
|
@@ -85,13 +85,19 @@ export function DocControlsBottom({
|
|
|
85
85
|
{state.currentBlockIndex + 1}/{state.totalBlocks}
|
|
86
86
|
</span>
|
|
87
87
|
|
|
88
|
-
{/* Caption
|
|
88
|
+
{/* Caption mode cycle */}
|
|
89
89
|
{state.hasCaptions && (
|
|
90
90
|
<button
|
|
91
|
-
className={`bottom-ctrl-btn ${state.
|
|
92
|
-
onClick={() => actions.
|
|
93
|
-
title={
|
|
94
|
-
|
|
91
|
+
className={`bottom-ctrl-btn ${state.captionMode !== 'off' ? 'bottom-ctrl-btn--active' : ''}`}
|
|
92
|
+
onClick={() => actions.cycleCaptionMode()}
|
|
93
|
+
title={
|
|
94
|
+
state.captionMode === 'off'
|
|
95
|
+
? 'Captions: Off'
|
|
96
|
+
: state.captionMode === 'standard'
|
|
97
|
+
? 'Captions: Standard'
|
|
98
|
+
: 'Captions: Social'
|
|
99
|
+
}
|
|
100
|
+
aria-label="Cycle caption style"
|
|
95
101
|
>
|
|
96
102
|
<svg viewBox="0 0 24 24" fill="currentColor" width="18" height="18">
|
|
97
103
|
<path d="M19 4H5a2 2 0 00-2 2v12a2 2 0 002 2h14a2 2 0 002-2V6a2 2 0 00-2-2zm-8 7H9.5v-.5h-2v3h2V13H11v1a1 1 0 01-1 1H7a1 1 0 01-1-1v-4a1 1 0 011-1h3a1 1 0 011 1v1zm7 0h-1.5v-.5h-2v3h2V13H18v1a1 1 0 01-1 1h-3a1 1 0 01-1-1v-4a1 1 0 011-1h3a1 1 0 011 1v1z" />
|
|
@@ -121,27 +121,39 @@ export function DocControlsOverlay({
|
|
|
121
121
|
{state.currentBlockIndex + 1}/{state.totalBlocks}
|
|
122
122
|
</span>
|
|
123
123
|
|
|
124
|
-
{/* Caption
|
|
124
|
+
{/* Caption mode cycle button: off → standard → social → off */}
|
|
125
125
|
{state.hasCaptions && (
|
|
126
126
|
<button
|
|
127
|
-
onClick={() => actions.
|
|
127
|
+
onClick={() => actions.cycleCaptionMode()}
|
|
128
128
|
style={{
|
|
129
|
-
background: state.
|
|
129
|
+
background: state.captionMode !== 'off' ? 'rgba(255,255,255,0.2)' : 'none',
|
|
130
130
|
border: 'none',
|
|
131
|
-
color: state.
|
|
131
|
+
color: state.captionMode !== 'off' ? 'white' : 'rgba(255,255,255,0.5)',
|
|
132
132
|
cursor: 'pointer',
|
|
133
133
|
padding: '8px',
|
|
134
134
|
fontSize: '12px',
|
|
135
135
|
display: 'flex',
|
|
136
136
|
alignItems: 'center',
|
|
137
|
+
gap: '4px',
|
|
137
138
|
borderRadius: '4px',
|
|
138
139
|
}}
|
|
139
|
-
title={
|
|
140
|
-
|
|
140
|
+
title={
|
|
141
|
+
state.captionMode === 'off'
|
|
142
|
+
? 'Captions: Off (click for Standard)'
|
|
143
|
+
: state.captionMode === 'standard'
|
|
144
|
+
? 'Captions: Standard (click for Social)'
|
|
145
|
+
: 'Captions: Social (click to turn off)'
|
|
146
|
+
}
|
|
147
|
+
aria-label="Cycle caption style"
|
|
141
148
|
>
|
|
142
149
|
<svg viewBox="0 0 24 24" fill="currentColor" width="18" height="18">
|
|
143
150
|
<path d="M19 4H5a2 2 0 00-2 2v12a2 2 0 002 2h14a2 2 0 002-2V6a2 2 0 00-2-2zm-8 7H9.5v-.5h-2v3h2V13H11v1a1 1 0 01-1 1H7a1 1 0 01-1-1v-4a1 1 0 011-1h3a1 1 0 011 1v1zm7 0h-1.5v-.5h-2v3h2V13H18v1a1 1 0 01-1 1h-3a1 1 0 01-1-1v-4a1 1 0 011-1h3a1 1 0 011 1v1z" />
|
|
144
151
|
</svg>
|
|
152
|
+
{state.captionMode !== 'off' && (
|
|
153
|
+
<span style={{ fontSize: '10px', textTransform: 'uppercase', letterSpacing: '0.5px' }}>
|
|
154
|
+
{state.captionMode === 'standard' ? 'CC' : 'SM'}
|
|
155
|
+
</span>
|
|
156
|
+
)}
|
|
145
157
|
</button>
|
|
146
158
|
)}
|
|
147
159
|
|
|
@@ -69,13 +69,19 @@ export function DocControlsSidebar({ state, actions }: DocControlsSidebarProps)
|
|
|
69
69
|
{state.currentBlockIndex + 1}/{state.totalBlocks}
|
|
70
70
|
</div>
|
|
71
71
|
|
|
72
|
-
{/* Caption
|
|
72
|
+
{/* Caption mode cycle */}
|
|
73
73
|
{state.hasCaptions && (
|
|
74
74
|
<button
|
|
75
|
-
className={`sidebar-ctrl-btn ${state.
|
|
76
|
-
onClick={() => actions.
|
|
77
|
-
title={
|
|
78
|
-
|
|
75
|
+
className={`sidebar-ctrl-btn ${state.captionMode !== 'off' ? 'sidebar-ctrl-btn--active' : ''}`}
|
|
76
|
+
onClick={() => actions.cycleCaptionMode()}
|
|
77
|
+
title={
|
|
78
|
+
state.captionMode === 'off'
|
|
79
|
+
? 'Captions: Off'
|
|
80
|
+
: state.captionMode === 'standard'
|
|
81
|
+
? 'Captions: Standard'
|
|
82
|
+
: 'Captions: Social'
|
|
83
|
+
}
|
|
84
|
+
aria-label="Cycle caption style"
|
|
79
85
|
>
|
|
80
86
|
<svg viewBox="0 0 24 24" fill="currentColor" width="20" height="20">
|
|
81
87
|
<path d="M19 4H5a2 2 0 00-2 2v12a2 2 0 002 2h14a2 2 0 002-2V6a2 2 0 00-2-2zm-8 7H9.5v-.5h-2v3h2V13H11v1a1 1 0 01-1 1H7a1 1 0 01-1-1v-4a1 1 0 011-1h3a1 1 0 011 1v1zm7 0h-1.5v-.5h-2v3h2V13H18v1a1 1 0 01-1 1h-3a1 1 0 01-1-1v-4a1 1 0 011-1h3a1 1 0 011 1v1z" />
|
package/src/DocPlayer.tsx
CHANGED
|
@@ -48,6 +48,8 @@ import type {
|
|
|
48
48
|
PlaybackActions,
|
|
49
49
|
BlockMarker,
|
|
50
50
|
DisplayMode,
|
|
51
|
+
CaptionStyle,
|
|
52
|
+
CaptionMode,
|
|
51
53
|
SlideNavActions,
|
|
52
54
|
SquisqWindow,
|
|
53
55
|
} from './types';
|
|
@@ -169,6 +171,9 @@ interface DocPlayerProps {
|
|
|
169
171
|
* template-annotated sections as inline SVG cards. No audio, no timeline.
|
|
170
172
|
*/
|
|
171
173
|
displayMode?: DisplayMode;
|
|
174
|
+
/** Caption display style (default: 'standard').
|
|
175
|
+
* 'social' shows large centered words with the active word highlighted. */
|
|
176
|
+
captionStyle?: CaptionStyle;
|
|
172
177
|
}
|
|
173
178
|
|
|
174
179
|
export function DocPlayer({
|
|
@@ -192,6 +197,7 @@ export function DocPlayer({
|
|
|
192
197
|
forceViewport,
|
|
193
198
|
displayMode = 'video',
|
|
194
199
|
theme,
|
|
200
|
+
captionStyle = 'standard',
|
|
195
201
|
}: DocPlayerProps) {
|
|
196
202
|
const isSlideshowMode = displayMode === 'slideshow';
|
|
197
203
|
const isLinearMode = displayMode === 'linear';
|
|
@@ -433,7 +439,16 @@ export function DocPlayer({
|
|
|
433
439
|
});
|
|
434
440
|
});
|
|
435
441
|
};
|
|
436
|
-
w.getDuration = () =>
|
|
442
|
+
w.getDuration = () => {
|
|
443
|
+
// When audio is present totalDuration comes from audio segments.
|
|
444
|
+
// For audio-less docs, compute from block timings instead.
|
|
445
|
+
if (totalDuration > 0) return totalDuration;
|
|
446
|
+
if (expandedBlocks.length > 0) {
|
|
447
|
+
const last = expandedBlocks[expandedBlocks.length - 1];
|
|
448
|
+
return last.startTime + last.duration;
|
|
449
|
+
}
|
|
450
|
+
return 0;
|
|
451
|
+
};
|
|
437
452
|
// Expose block metadata for testing -- allows tests to find specific templates
|
|
438
453
|
w.getBlocks = () =>
|
|
439
454
|
expandedBlocks.map((s: Block) => ({
|
|
@@ -494,14 +509,36 @@ export function DocPlayer({
|
|
|
494
509
|
// eslint-disable-next-line react-hooks/exhaustive-deps -- script is a stable prop; re-registering on every script change is unnecessary
|
|
495
510
|
}, [renderMode, isDebugMode, seekTo, totalDuration, expandedBlocks, coverBlock]);
|
|
496
511
|
|
|
497
|
-
//
|
|
498
|
-
|
|
512
|
+
// Caption mode state: cycles through off → standard → social → off
|
|
513
|
+
// The captionStyle prop sets the default active style; captionsEnabledProp
|
|
514
|
+
// can override the initial on/off state.
|
|
515
|
+
const defaultMode: CaptionMode =
|
|
516
|
+
captionsEnabledProp === false ? 'off' : captionStyle || 'standard';
|
|
517
|
+
const [captionMode, setCaptionMode] = useState<CaptionMode>(defaultMode);
|
|
518
|
+
|
|
519
|
+
// Derive captionsEnabled and active style from the mode
|
|
520
|
+
const captionsEnabled = captionMode !== 'off';
|
|
521
|
+
const activeCaptionStyle: CaptionStyle = captionMode === 'social' ? 'social' : 'standard';
|
|
522
|
+
|
|
499
523
|
const setCaptionsEnabled = useCallback(
|
|
500
524
|
(enabled: boolean) => {
|
|
525
|
+
// When re-enabling, restore the prop-specified style rather than
|
|
526
|
+
// always defaulting to 'standard'
|
|
527
|
+
setCaptionMode(enabled ? captionStyle || 'standard' : 'off');
|
|
501
528
|
onCaptionsToggle?.(enabled);
|
|
502
529
|
},
|
|
503
|
-
[onCaptionsToggle],
|
|
530
|
+
[onCaptionsToggle, captionStyle],
|
|
504
531
|
);
|
|
532
|
+
|
|
533
|
+
const cycleCaptionMode = useCallback(() => {
|
|
534
|
+
setCaptionMode((prev) => {
|
|
535
|
+
const next: CaptionMode =
|
|
536
|
+
prev === 'off' ? 'standard' : prev === 'standard' ? 'social' : 'off';
|
|
537
|
+
onCaptionsToggle?.(next !== 'off');
|
|
538
|
+
return next;
|
|
539
|
+
});
|
|
540
|
+
}, [onCaptionsToggle]);
|
|
541
|
+
|
|
505
542
|
const hasCaptions = script.captions && script.captions.phrases.length > 0;
|
|
506
543
|
|
|
507
544
|
// Map segment indices to human-readable titles (from sectionHeader blocks)
|
|
@@ -518,6 +555,7 @@ export function DocPlayer({
|
|
|
518
555
|
docProgress,
|
|
519
556
|
hasCaptions: !!hasCaptions,
|
|
520
557
|
captionsEnabled,
|
|
558
|
+
captionMode,
|
|
521
559
|
isFullscreen,
|
|
522
560
|
currentSegmentIndex: currentSegment,
|
|
523
561
|
currentSegmentName:
|
|
@@ -534,6 +572,7 @@ export function DocPlayer({
|
|
|
534
572
|
docProgress,
|
|
535
573
|
hasCaptions,
|
|
536
574
|
captionsEnabled,
|
|
575
|
+
captionMode,
|
|
537
576
|
isFullscreen,
|
|
538
577
|
currentSegment,
|
|
539
578
|
segmentTitleMap,
|
|
@@ -548,9 +587,10 @@ export function DocPlayer({
|
|
|
548
587
|
restart,
|
|
549
588
|
seekTo,
|
|
550
589
|
setCaptionsEnabled,
|
|
590
|
+
cycleCaptionMode,
|
|
551
591
|
toggleFullscreen: onFullscreenToggle,
|
|
552
592
|
}),
|
|
553
|
-
[toggle, restart, seekTo, setCaptionsEnabled, onFullscreenToggle],
|
|
593
|
+
[toggle, restart, seekTo, setCaptionsEnabled, cycleCaptionMode, onFullscreenToggle],
|
|
554
594
|
);
|
|
555
595
|
|
|
556
596
|
// Slide navigation actions for slideshow mode
|
|
@@ -803,13 +843,16 @@ export function DocPlayer({
|
|
|
803
843
|
</div>
|
|
804
844
|
)}
|
|
805
845
|
|
|
806
|
-
{/* Caption overlay --
|
|
807
|
-
{hasCaptions &&
|
|
846
|
+
{/* Caption overlay -- shown during playback and in render mode when captions are enabled */}
|
|
847
|
+
{hasCaptions && (renderMode ? captionsEnabled : true) && (
|
|
808
848
|
<CaptionOverlay
|
|
809
849
|
captions={script.captions}
|
|
810
850
|
currentTime={currentTime}
|
|
811
|
-
enabled={captionsEnabled && (isPlaying || currentTime > 0)}
|
|
851
|
+
enabled={captionsEnabled && (renderMode || isPlaying || currentTime > 0)}
|
|
812
852
|
fontSize={16}
|
|
853
|
+
captionStyle={activeCaptionStyle}
|
|
854
|
+
theme={theme}
|
|
855
|
+
viewport={activeViewport}
|
|
813
856
|
/>
|
|
814
857
|
)}
|
|
815
858
|
|
|
@@ -850,7 +893,11 @@ export function DocPlayer({
|
|
|
850
893
|
</div>
|
|
851
894
|
<div>
|
|
852
895
|
<span style={{ color: '#888' }}>time:</span> {currentTime.toFixed(2)}s /{' '}
|
|
853
|
-
{totalDuration.toFixed(1)}s
|
|
896
|
+
{totalDuration.toFixed(1)}s{' '}
|
|
897
|
+
<span style={{ color: '#666' }}>
|
|
898
|
+
(progress: {(docProgress * 100).toFixed(1)}%, scriptDur:{' '}
|
|
899
|
+
{script.duration.toFixed(1)})
|
|
900
|
+
</span>
|
|
854
901
|
</div>
|
|
855
902
|
<div>
|
|
856
903
|
<span style={{ color: '#888' }}>blockTime:</span> {blockTime.toFixed(2)}s /{' '}
|
package/src/DocProgressBar.tsx
CHANGED
|
@@ -106,7 +106,7 @@ export function DocProgressBar({
|
|
|
106
106
|
left: 0,
|
|
107
107
|
width: `${state.docProgress * 100}%`,
|
|
108
108
|
height: '6px',
|
|
109
|
-
background: '#
|
|
109
|
+
background: '#5b9bd5',
|
|
110
110
|
borderRadius: '3px',
|
|
111
111
|
transition: 'width 0.1s',
|
|
112
112
|
}}
|
|
@@ -125,7 +125,7 @@ export function DocProgressBar({
|
|
|
125
125
|
borderRadius: '50%',
|
|
126
126
|
background:
|
|
127
127
|
marker.index === state.currentBlockIndex ? '#ffffff' : 'rgba(255,255,255,0.5)',
|
|
128
|
-
border: '2px solid #
|
|
128
|
+
border: '2px solid #5b9bd5',
|
|
129
129
|
cursor: 'pointer',
|
|
130
130
|
zIndex: 2,
|
|
131
131
|
transition: 'transform 0.15s, background 0.15s',
|
package/src/LinearDocView.tsx
CHANGED
|
@@ -25,7 +25,7 @@ import { VIEWPORT_PRESETS } from '@bendyline/squisq/schemas';
|
|
|
25
25
|
import { getLayers, hasTemplate, DEFAULT_THEME } from '@bendyline/squisq/doc';
|
|
26
26
|
import type { RenderContext } from '@bendyline/squisq/doc';
|
|
27
27
|
import { extractPlainText } from '@bendyline/squisq/markdown';
|
|
28
|
-
import type { MarkdownBlockNode, MarkdownList } from '@bendyline/squisq/markdown';
|
|
28
|
+
import type { MarkdownBlockNode, MarkdownList, MarkdownTable } from '@bendyline/squisq/markdown';
|
|
29
29
|
import { BlockRenderer } from './BlockRenderer';
|
|
30
30
|
import { MarkdownRenderer } from './MarkdownRenderer';
|
|
31
31
|
|
|
@@ -55,7 +55,7 @@ export interface LinearDocViewProps {
|
|
|
55
55
|
function isAnnotatedBlock(block: Block): boolean {
|
|
56
56
|
const annotation = block.sourceHeading?.templateAnnotation;
|
|
57
57
|
if (!annotation) return false;
|
|
58
|
-
return hasTemplate(annotation.template);
|
|
58
|
+
return !!annotation.template && hasTemplate(annotation.template);
|
|
59
59
|
}
|
|
60
60
|
|
|
61
61
|
/**
|
|
@@ -103,7 +103,12 @@ function BlockSection({ block, basePath, viewport, renderContext, blockIndex }:
|
|
|
103
103
|
duration: 1,
|
|
104
104
|
audioSegment: 0,
|
|
105
105
|
title: headingText,
|
|
106
|
-
...getTemplateDefaults(
|
|
106
|
+
...getTemplateDefaults(
|
|
107
|
+
annotation.template ?? 'sectionHeader',
|
|
108
|
+
headingText,
|
|
109
|
+
bodyText,
|
|
110
|
+
block.contents,
|
|
111
|
+
),
|
|
107
112
|
...annotation.params,
|
|
108
113
|
...block.templateOverrides,
|
|
109
114
|
};
|
|
@@ -211,6 +216,26 @@ function extractListItems(contents?: MarkdownBlockNode[]): string[] {
|
|
|
211
216
|
return items;
|
|
212
217
|
}
|
|
213
218
|
|
|
219
|
+
/** Extract table data (headers, rows, alignment) from block contents. */
|
|
220
|
+
function extractTableData(contents?: MarkdownBlockNode[]): {
|
|
221
|
+
headers: string[];
|
|
222
|
+
rows: string[][];
|
|
223
|
+
align?: (('left' | 'right' | 'center') | null)[];
|
|
224
|
+
} | null {
|
|
225
|
+
if (!contents) return null;
|
|
226
|
+
for (const node of contents) {
|
|
227
|
+
if (node.type === 'table') {
|
|
228
|
+
const table = node as MarkdownTable;
|
|
229
|
+
const [headerRow, ...bodyRows] = table.children;
|
|
230
|
+
if (!headerRow) return null;
|
|
231
|
+
const headers = headerRow.children.map((cell) => extractPlainText(cell).trim());
|
|
232
|
+
const rows = bodyRows.map((row) => row.children.map((cell) => extractPlainText(cell).trim()));
|
|
233
|
+
return { headers, rows, align: table.align };
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
return null;
|
|
237
|
+
}
|
|
238
|
+
|
|
214
239
|
/**
|
|
215
240
|
* Provide sensible default fields for templates that require more than
|
|
216
241
|
* just a `title`. Prevents crashes from undefined required fields.
|
|
@@ -238,6 +263,10 @@ function getTemplateDefaults(
|
|
|
238
263
|
return { term: headingText, definition: bodyText || headingText };
|
|
239
264
|
case 'dateEvent':
|
|
240
265
|
return { date: headingText, description: bodyText || headingText };
|
|
266
|
+
case 'dataTable': {
|
|
267
|
+
const tableData = extractTableData(contents);
|
|
268
|
+
return tableData ?? { headers: ['Column'], rows: [['Data']] };
|
|
269
|
+
}
|
|
241
270
|
default:
|
|
242
271
|
return {};
|
|
243
272
|
}
|
|
@@ -276,6 +305,15 @@ export function LinearDocView({
|
|
|
276
305
|
[activeViewport, totalBlocks, theme],
|
|
277
306
|
);
|
|
278
307
|
|
|
308
|
+
const activeTheme = renderContext.theme!;
|
|
309
|
+
const bgColor = activeTheme.colors.background;
|
|
310
|
+
const textColor = activeTheme.colors.text;
|
|
311
|
+
const mutedColor = activeTheme.colors.textMuted;
|
|
312
|
+
const primaryColor = activeTheme.colors.primary;
|
|
313
|
+
const bodyFont = activeTheme.typography.bodyFontFamily;
|
|
314
|
+
const titleFont = activeTheme.typography.titleFontFamily;
|
|
315
|
+
const lineHt = activeTheme.typography.lineHeight ?? 1.7;
|
|
316
|
+
|
|
279
317
|
return (
|
|
280
318
|
<div
|
|
281
319
|
className={`squisq-linear ${className || ''}`}
|
|
@@ -284,19 +322,118 @@ export function LinearDocView({
|
|
|
284
322
|
height: '100%',
|
|
285
323
|
overflowY: 'auto',
|
|
286
324
|
overflowX: 'hidden',
|
|
325
|
+
background: bgColor,
|
|
287
326
|
}}
|
|
288
327
|
>
|
|
289
328
|
<div
|
|
290
|
-
className="squisq-linear-content"
|
|
291
|
-
style={
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
329
|
+
className="squisq-linear-content squisq-md"
|
|
330
|
+
style={
|
|
331
|
+
{
|
|
332
|
+
maxWidth: '720px',
|
|
333
|
+
margin: '0 auto',
|
|
334
|
+
padding: '24px 16px',
|
|
335
|
+
lineHeight: lineHt,
|
|
336
|
+
fontSize: '16px',
|
|
337
|
+
fontFamily: bodyFont,
|
|
338
|
+
color: textColor,
|
|
339
|
+
// CSS custom properties for MarkdownRenderer / nested elements
|
|
340
|
+
'--squisq-linear-title-font': titleFont,
|
|
341
|
+
'--squisq-linear-body-font': bodyFont,
|
|
342
|
+
'--squisq-linear-text': textColor,
|
|
343
|
+
'--squisq-linear-muted': mutedColor,
|
|
344
|
+
'--squisq-linear-primary': primaryColor,
|
|
345
|
+
'--squisq-linear-bg': bgColor,
|
|
346
|
+
} as React.CSSProperties
|
|
347
|
+
}
|
|
299
348
|
>
|
|
349
|
+
{/* Theme-aware typography and layout for document mode */}
|
|
350
|
+
<style>{`
|
|
351
|
+
.squisq-linear-content h1,
|
|
352
|
+
.squisq-linear-content h2,
|
|
353
|
+
.squisq-linear-content h3,
|
|
354
|
+
.squisq-linear-content h4,
|
|
355
|
+
.squisq-linear-content h5,
|
|
356
|
+
.squisq-linear-content h6 {
|
|
357
|
+
font-family: var(--squisq-linear-title-font);
|
|
358
|
+
color: var(--squisq-linear-text);
|
|
359
|
+
margin-top: 1.2em;
|
|
360
|
+
margin-bottom: 0.4em;
|
|
361
|
+
}
|
|
362
|
+
.squisq-linear-content h1 { font-size: 2em; }
|
|
363
|
+
.squisq-linear-content h2 { font-size: 1.5em; }
|
|
364
|
+
.squisq-linear-content h3 { font-size: 1.25em; }
|
|
365
|
+
.squisq-linear-content p {
|
|
366
|
+
margin-bottom: 0.75em;
|
|
367
|
+
}
|
|
368
|
+
.squisq-linear-content ul,
|
|
369
|
+
.squisq-linear-content ol {
|
|
370
|
+
padding-left: 2em;
|
|
371
|
+
margin-bottom: 0.75em;
|
|
372
|
+
}
|
|
373
|
+
.squisq-linear-content li {
|
|
374
|
+
margin-bottom: 0.3em;
|
|
375
|
+
}
|
|
376
|
+
.squisq-linear-content a {
|
|
377
|
+
color: var(--squisq-linear-primary);
|
|
378
|
+
}
|
|
379
|
+
.squisq-linear-content code {
|
|
380
|
+
color: var(--squisq-linear-primary);
|
|
381
|
+
font-size: 0.9em;
|
|
382
|
+
padding: 0.15em 0.3em;
|
|
383
|
+
border-radius: 3px;
|
|
384
|
+
background: rgba(128, 128, 128, 0.15);
|
|
385
|
+
}
|
|
386
|
+
.squisq-linear-content pre {
|
|
387
|
+
padding: 1em;
|
|
388
|
+
border-radius: 6px;
|
|
389
|
+
background: rgba(0, 0, 0, 0.2);
|
|
390
|
+
overflow-x: auto;
|
|
391
|
+
margin-bottom: 0.75em;
|
|
392
|
+
}
|
|
393
|
+
.squisq-linear-content pre code {
|
|
394
|
+
padding: 0;
|
|
395
|
+
background: none;
|
|
396
|
+
}
|
|
397
|
+
.squisq-linear-content blockquote {
|
|
398
|
+
border-left: 3px solid var(--squisq-linear-muted);
|
|
399
|
+
color: var(--squisq-linear-muted);
|
|
400
|
+
padding-left: 1em;
|
|
401
|
+
margin-left: 0;
|
|
402
|
+
margin-bottom: 0.75em;
|
|
403
|
+
}
|
|
404
|
+
.squisq-linear-content hr {
|
|
405
|
+
border: none;
|
|
406
|
+
border-top: 1px solid var(--squisq-linear-muted);
|
|
407
|
+
margin: 1.5em 0;
|
|
408
|
+
}
|
|
409
|
+
.squisq-linear-content strong {
|
|
410
|
+
font-weight: 700;
|
|
411
|
+
}
|
|
412
|
+
.squisq-linear-content em {
|
|
413
|
+
font-style: italic;
|
|
414
|
+
}
|
|
415
|
+
.squisq-linear-content table {
|
|
416
|
+
width: 100%;
|
|
417
|
+
border-collapse: collapse;
|
|
418
|
+
margin: 1em 0;
|
|
419
|
+
font-size: 0.95em;
|
|
420
|
+
}
|
|
421
|
+
.squisq-linear-content thead th {
|
|
422
|
+
background: var(--squisq-linear-primary);
|
|
423
|
+
color: var(--squisq-linear-bg);
|
|
424
|
+
font-family: var(--squisq-linear-title-font);
|
|
425
|
+
font-weight: 600;
|
|
426
|
+
padding: 10px 14px;
|
|
427
|
+
text-align: left;
|
|
428
|
+
}
|
|
429
|
+
.squisq-linear-content tbody td {
|
|
430
|
+
padding: 8px 14px;
|
|
431
|
+
border-bottom: 1px solid color-mix(in srgb, var(--squisq-linear-muted) 30%, transparent);
|
|
432
|
+
}
|
|
433
|
+
.squisq-linear-content tbody tr:hover {
|
|
434
|
+
background: color-mix(in srgb, var(--squisq-linear-primary) 8%, transparent);
|
|
435
|
+
}
|
|
436
|
+
`}</style>
|
|
300
437
|
{doc.blocks.map((block, i) => (
|
|
301
438
|
<BlockSection
|
|
302
439
|
key={block.id}
|