@bendyline/squisq-react 1.4.2 → 2.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/README.md +30 -3
- package/dist/index.d.ts +177 -28
- package/dist/index.js +1330 -611
- package/dist/index.js.map +1 -1
- package/dist/squisq-player.css +1 -1
- package/dist/squisq-player.css.map +1 -1
- package/dist/squisq-player.global.js +57 -37
- package/dist/squisq-player.global.js.map +1 -1
- package/dist/standalone-source.js +1 -1
- package/dist/styles/index.css +28 -0
- package/package.json +2 -2
- package/src/BlockRenderer.tsx +54 -17
- package/src/DocControlsSlideshow.tsx +222 -5
- package/src/DocPlayer.tsx +367 -183
- package/src/DocPlayerWithSidebar.tsx +4 -0
- package/src/DocProgressBar.tsx +40 -1
- package/src/LinearDocView.tsx +138 -62
- package/src/MarkdownRenderer.tsx +40 -97
- package/src/MediaClipLayer.tsx +12 -2
- package/src/__tests__/BlockRenderer.test.tsx +138 -8
- package/src/__tests__/DocControlsSlideshow.test.tsx +94 -1
- package/src/__tests__/DocPlayer.test.tsx +505 -0
- package/src/__tests__/DocProgressBar.test.tsx +28 -2
- package/src/__tests__/LinearDocView.test.tsx +104 -11
- package/src/__tests__/MapLayer.test.tsx +63 -0
- package/src/__tests__/MarkdownRenderer.test.tsx +16 -5
- package/src/__tests__/MediaClipLayer.test.tsx +70 -0
- package/src/__tests__/MediaContext.test.tsx +51 -0
- package/src/__tests__/PathLayer.test.tsx +12 -1
- package/src/__tests__/VideoLayer.test.tsx +94 -0
- package/src/__tests__/fillStyle.test.tsx +50 -2
- package/src/__tests__/standaloneEntry.test.tsx +103 -0
- package/src/__tests__/useAudioSync.test.ts +49 -0
- package/src/__tests__/useDocPlayback.transition.test.ts +48 -5
- package/src/__tests__/useViewportOrientation.test.ts +22 -0
- package/src/hooks/MediaContext.tsx +12 -3
- package/src/hooks/useAudioSync.ts +61 -12
- package/src/hooks/useDocPlayback.ts +40 -12
- package/src/hooks/useViewportOrientation.ts +2 -4
- package/src/index.ts +5 -2
- package/src/layers/ImageLayer.tsx +106 -1
- package/src/layers/MapLayer.tsx +7 -6
- package/src/layers/PathLayer.tsx +20 -11
- package/src/layers/ShapeLayer.tsx +33 -9
- package/src/layers/TextLayer.tsx +4 -3
- package/src/layers/TreeLayer.tsx +167 -0
- package/src/layers/VideoLayer.tsx +20 -6
- package/src/standalone-entry.tsx +91 -14
- package/src/styles/doc-animations.css +36 -0
- package/src/types.ts +13 -13
package/dist/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// src/DocPlayer.tsx
|
|
2
|
-
import { Fragment as Fragment3, useRef as
|
|
2
|
+
import { Fragment as Fragment3, useId as useId7, useRef as useRef9, useState as useState9, useEffect as useEffect10, useCallback as useCallback7, useMemo as useMemo9 } from "react";
|
|
3
3
|
import {
|
|
4
|
-
isTemplateBlock as
|
|
4
|
+
isTemplateBlock as isTemplateBlock3,
|
|
5
5
|
getCaptionAtTime as getCaptionAtTime2,
|
|
6
6
|
resolveMediaSchedule,
|
|
7
7
|
getDocPlaybackDuration
|
|
@@ -32,9 +32,15 @@ function useMediaUrl(relativePath, basePath) {
|
|
|
32
32
|
return;
|
|
33
33
|
}
|
|
34
34
|
let cancelled = false;
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
35
|
+
setUrl(fallback);
|
|
36
|
+
provider.resolveUrl(safePath).then(
|
|
37
|
+
(resolved) => {
|
|
38
|
+
if (!cancelled) setUrl(resolved);
|
|
39
|
+
},
|
|
40
|
+
() => {
|
|
41
|
+
if (!cancelled) setUrl(fallback);
|
|
42
|
+
}
|
|
43
|
+
);
|
|
38
44
|
return () => {
|
|
39
45
|
cancelled = true;
|
|
40
46
|
};
|
|
@@ -63,7 +69,8 @@ function MediaClipLayer({
|
|
|
63
69
|
currentTime,
|
|
64
70
|
isPlaying,
|
|
65
71
|
basePath,
|
|
66
|
-
renderMode = false
|
|
72
|
+
renderMode = false,
|
|
73
|
+
muted = false
|
|
67
74
|
}) {
|
|
68
75
|
const { renderClips, activeIds } = useMediaSchedule(schedule, currentTime);
|
|
69
76
|
if (renderClips.length === 0) return null;
|
|
@@ -75,7 +82,8 @@ function MediaClipLayer({
|
|
|
75
82
|
currentTime,
|
|
76
83
|
isPlaying,
|
|
77
84
|
basePath,
|
|
78
|
-
renderMode
|
|
85
|
+
renderMode,
|
|
86
|
+
muted
|
|
79
87
|
},
|
|
80
88
|
clip.id
|
|
81
89
|
)) });
|
|
@@ -86,7 +94,8 @@ function MediaClipElement({
|
|
|
86
94
|
currentTime,
|
|
87
95
|
isPlaying,
|
|
88
96
|
basePath,
|
|
89
|
-
renderMode
|
|
97
|
+
renderMode,
|
|
98
|
+
muted
|
|
90
99
|
}) {
|
|
91
100
|
const ref = useRef(null);
|
|
92
101
|
const src = useMediaUrl(clip.src, basePath);
|
|
@@ -111,7 +120,7 @@ function MediaClipElement({
|
|
|
111
120
|
} else {
|
|
112
121
|
el.pause();
|
|
113
122
|
}
|
|
114
|
-
}, [active, currentTime, isPlaying, renderMode, clip.sourceIn, clip.absoluteStart]);
|
|
123
|
+
}, [active, currentTime, isPlaying, renderMode, clip.sourceIn, clip.absoluteStart, src]);
|
|
115
124
|
const isVideo = clip.kind === "video";
|
|
116
125
|
const common = {
|
|
117
126
|
ref,
|
|
@@ -141,13 +150,21 @@ function MediaClipElement({
|
|
|
141
150
|
}
|
|
142
151
|
);
|
|
143
152
|
}
|
|
144
|
-
return /* @__PURE__ */ jsx(
|
|
153
|
+
return /* @__PURE__ */ jsx(
|
|
154
|
+
"audio",
|
|
155
|
+
{
|
|
156
|
+
...common,
|
|
157
|
+
muted: renderMode || muted,
|
|
158
|
+
style: { position: "absolute", width: 0, height: 0 }
|
|
159
|
+
}
|
|
160
|
+
);
|
|
145
161
|
}
|
|
146
162
|
|
|
147
163
|
// src/DocPlayer.tsx
|
|
148
164
|
import { applySurface as applySurface2 } from "@bendyline/squisq/schemas";
|
|
149
165
|
|
|
150
166
|
// src/BlockRenderer.tsx
|
|
167
|
+
import { useId as useId5 } from "react";
|
|
151
168
|
import { resolveTransitionDuration } from "@bendyline/squisq/schemas";
|
|
152
169
|
|
|
153
170
|
// src/layers/ImageLayer.tsx
|
|
@@ -190,7 +207,13 @@ function getAnchorOffset(anchor, width, height) {
|
|
|
190
207
|
|
|
191
208
|
// src/layers/ImageLayer.tsx
|
|
192
209
|
import { jsx as jsx2 } from "react/jsx-runtime";
|
|
193
|
-
function ImageLayer({
|
|
210
|
+
function ImageLayer({
|
|
211
|
+
layer,
|
|
212
|
+
basePath,
|
|
213
|
+
viewport,
|
|
214
|
+
blockTime,
|
|
215
|
+
animationsEnabled = true
|
|
216
|
+
}) {
|
|
194
217
|
const { content, position, animation } = layer;
|
|
195
218
|
const x = resolveValue(position.x, viewport.width);
|
|
196
219
|
const y = resolveValue(position.y, viewport.height);
|
|
@@ -205,6 +228,50 @@ function ImageLayer({ layer, basePath, viewport, blockTime }) {
|
|
|
205
228
|
const preserveAspectRatio = getPreserveAspectRatio(content.fit);
|
|
206
229
|
const isCover = content.fit === "cover";
|
|
207
230
|
const isSpatialAnim = animation && SPATIAL_ANIMATION_TYPES.has(animation.type);
|
|
231
|
+
const usesPortraitPan = isCover && shouldUsePortraitPan(viewport, width, height, animationsEnabled, animation);
|
|
232
|
+
if (usesPortraitPan) {
|
|
233
|
+
const panClass = getPortraitPanClass(animation);
|
|
234
|
+
const panStyle = getPortraitPanStyle(isSpatialAnim ? animation : void 0);
|
|
235
|
+
const containerAnim = isSpatialAnim ? { className: "", style: {} } : animStyle;
|
|
236
|
+
return /* @__PURE__ */ jsx2(
|
|
237
|
+
"g",
|
|
238
|
+
{
|
|
239
|
+
className: `block-layer block-layer--image ${containerAnim.className}`,
|
|
240
|
+
style: containerAnim.style,
|
|
241
|
+
"data-layer-id": layer.id,
|
|
242
|
+
"data-image-framing": "portrait-pan",
|
|
243
|
+
children: /* @__PURE__ */ jsx2("foreignObject", { x: finalX, y: finalY, width, height, children: /* @__PURE__ */ jsx2(
|
|
244
|
+
"div",
|
|
245
|
+
{
|
|
246
|
+
style: {
|
|
247
|
+
width: `${width}px`,
|
|
248
|
+
height: `${height}px`,
|
|
249
|
+
overflow: "hidden"
|
|
250
|
+
},
|
|
251
|
+
children: /* @__PURE__ */ jsx2(
|
|
252
|
+
"img",
|
|
253
|
+
{
|
|
254
|
+
src,
|
|
255
|
+
alt: content.alt || "",
|
|
256
|
+
className: panClass,
|
|
257
|
+
style: {
|
|
258
|
+
width: `${width}px`,
|
|
259
|
+
height: `${height}px`,
|
|
260
|
+
objectFit: "cover",
|
|
261
|
+
objectPosition: "center",
|
|
262
|
+
display: "block",
|
|
263
|
+
pointerEvents: "none",
|
|
264
|
+
...filter ? { filter } : {},
|
|
265
|
+
...content.blur && content.blur > 0 ? { transform: "scale(1.06)" } : {},
|
|
266
|
+
...panStyle
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
)
|
|
270
|
+
}
|
|
271
|
+
) })
|
|
272
|
+
}
|
|
273
|
+
);
|
|
274
|
+
}
|
|
208
275
|
if (isCover && isSpatialAnim && animation) {
|
|
209
276
|
const kbAnim = remapToKenBurns(animation);
|
|
210
277
|
const kbStyle = getAnimationStyle(kbAnim, blockTime);
|
|
@@ -300,6 +367,23 @@ function getPreserveAspectRatio(fit) {
|
|
|
300
367
|
}
|
|
301
368
|
}
|
|
302
369
|
var SPATIAL_ANIMATION_TYPES = /* @__PURE__ */ new Set(["panLeft", "panRight", "slowZoom", "zoomIn", "zoomOut"]);
|
|
370
|
+
var PORTRAIT_ASPECT_CUTOFF = 0.83;
|
|
371
|
+
function shouldUsePortraitPan(viewport, layerWidth, layerHeight, animationsEnabled, animation) {
|
|
372
|
+
if (!animationsEnabled || animation?.type === "none") return false;
|
|
373
|
+
if (viewport.width / viewport.height >= PORTRAIT_ASPECT_CUTOFF) return false;
|
|
374
|
+
return layerWidth >= viewport.width * 0.7 && layerHeight >= viewport.height * 0.7;
|
|
375
|
+
}
|
|
376
|
+
function getPortraitPanClass(animation) {
|
|
377
|
+
const pansBack = animation?.type === "panRight" || animation?.type === "slowZoom" && animation.panDirection === "right";
|
|
378
|
+
return pansBack ? "squisq-image--portrait-pan-left" : "squisq-image--portrait-pan-right";
|
|
379
|
+
}
|
|
380
|
+
function getPortraitPanStyle(animation) {
|
|
381
|
+
return {
|
|
382
|
+
"--portrait-pan-duration": `${animation?.duration ?? 12}s`,
|
|
383
|
+
"--portrait-pan-delay": `${animation?.delay ?? 0}s`,
|
|
384
|
+
"--portrait-pan-easing": animation?.easing ?? "ease-in-out"
|
|
385
|
+
};
|
|
386
|
+
}
|
|
303
387
|
function remapToKenBurns(anim) {
|
|
304
388
|
switch (anim.type) {
|
|
305
389
|
case "panLeft":
|
|
@@ -316,7 +400,7 @@ function remapToKenBurns(anim) {
|
|
|
316
400
|
}
|
|
317
401
|
|
|
318
402
|
// src/layers/TextLayer.tsx
|
|
319
|
-
import { useMemo as useMemo3 } from "react";
|
|
403
|
+
import { useId, useMemo as useMemo3 } from "react";
|
|
320
404
|
import { DEFAULT_DOC_FONT } from "@bendyline/squisq/schemas";
|
|
321
405
|
import {
|
|
322
406
|
parseHtmlToNodes,
|
|
@@ -505,6 +589,7 @@ function IconTextLayer({ layer, viewport, blockTime }) {
|
|
|
505
589
|
) });
|
|
506
590
|
}
|
|
507
591
|
function PlainTextLayer({ layer, viewport, blockTime }) {
|
|
592
|
+
const defsId = `${useId().replace(/:/g, "")}-${layer.id}`;
|
|
508
593
|
const { content, position, animation } = layer;
|
|
509
594
|
const { text, style } = content;
|
|
510
595
|
const rawX = resolveValue(position.x, viewport.width);
|
|
@@ -538,13 +623,13 @@ function PlainTextLayer({ layer, viewport, blockTime }) {
|
|
|
538
623
|
fill: style.color,
|
|
539
624
|
...animStyle.style
|
|
540
625
|
};
|
|
541
|
-
const filterId = style.shadow ? `shadow-${
|
|
626
|
+
const filterId = style.shadow ? `shadow-${defsId}` : void 0;
|
|
542
627
|
return /* @__PURE__ */ jsxs2("g", { className: `block-layer block-layer--text ${animStyle.className}`, "data-layer-id": layer.id, children: [
|
|
543
628
|
style.shadow && /* @__PURE__ */ jsx4("defs", { children: /* @__PURE__ */ jsx4("filter", { id: filterId, x: "-20%", y: "-20%", width: "140%", height: "140%", children: /* @__PURE__ */ jsx4("feDropShadow", { dx: "0", dy: "2", stdDeviation: "3", floodColor: "rgba(0,0,0,0.7)" }) }) }),
|
|
544
629
|
/* @__PURE__ */ jsx4(
|
|
545
630
|
TextBox,
|
|
546
631
|
{
|
|
547
|
-
layerId:
|
|
632
|
+
layerId: defsId,
|
|
548
633
|
style,
|
|
549
634
|
box: boxWidth != null && boxHeight != null ? {
|
|
550
635
|
x: rawX - anchorAxis(anchor, boxWidth, "x"),
|
|
@@ -746,9 +831,12 @@ function wrapText(text, fontSize, maxWidth) {
|
|
|
746
831
|
}
|
|
747
832
|
|
|
748
833
|
// src/layers/ShapeLayer.tsx
|
|
834
|
+
import { useId as useId2 } from "react";
|
|
749
835
|
import { jsx as jsx5, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
836
|
+
var FULL_BLEED_OVERSCAN = 1;
|
|
750
837
|
function ShapeLayer({ layer, viewport, blockTime }) {
|
|
751
838
|
const { content, position, animation } = layer;
|
|
839
|
+
const defsId = `${useId2().replace(/:/g, "")}-${layer.id}`;
|
|
752
840
|
const rawX = resolveValue(position.x, viewport.width);
|
|
753
841
|
const rawY = resolveValue(position.y, viewport.height);
|
|
754
842
|
const width = position.width ? resolveValue(position.width, viewport.width) : 100;
|
|
@@ -756,6 +844,12 @@ function ShapeLayer({ layer, viewport, blockTime }) {
|
|
|
756
844
|
const anchorOffset = getAnchorOffset(position.anchor, width, height);
|
|
757
845
|
const x = rawX + anchorOffset.x;
|
|
758
846
|
const y = rawY + anchorOffset.y;
|
|
847
|
+
const isUnborderedFullBleedRect = content.shape === "rect" && x === 0 && y === 0 && width === viewport.width && height === viewport.height && !content.stroke && !content.borderRadius;
|
|
848
|
+
const overscan = isUnborderedFullBleedRect ? FULL_BLEED_OVERSCAN : 0;
|
|
849
|
+
const paintX = x - overscan;
|
|
850
|
+
const paintY = y - overscan;
|
|
851
|
+
const paintWidth = width + overscan * 2;
|
|
852
|
+
const paintHeight = height + overscan * 2;
|
|
759
853
|
const animStyle = getAnimationStyle(animation, blockTime);
|
|
760
854
|
const fill = content.fill || "none";
|
|
761
855
|
const isCSSGradient = typeof fill === "string" && fill.includes("gradient(");
|
|
@@ -766,12 +860,12 @@ function ShapeLayer({ layer, viewport, blockTime }) {
|
|
|
766
860
|
className: `block-layer block-layer--shape ${animStyle.className}`,
|
|
767
861
|
style: animStyle.style,
|
|
768
862
|
"data-layer-id": layer.id,
|
|
769
|
-
children: /* @__PURE__ */ jsx5("foreignObject", { x, y, width, height, children: /* @__PURE__ */ jsx5(
|
|
863
|
+
children: /* @__PURE__ */ jsx5("foreignObject", { x: paintX, y: paintY, width: paintWidth, height: paintHeight, children: /* @__PURE__ */ jsx5(
|
|
770
864
|
"div",
|
|
771
865
|
{
|
|
772
866
|
style: {
|
|
773
|
-
width: `${
|
|
774
|
-
height: `${
|
|
867
|
+
width: `${paintWidth}px`,
|
|
868
|
+
height: `${paintHeight}px`,
|
|
775
869
|
background: fill,
|
|
776
870
|
borderRadius: content.borderRadius ? `${content.borderRadius}px` : void 0,
|
|
777
871
|
pointerEvents: "none"
|
|
@@ -782,12 +876,12 @@ function ShapeLayer({ layer, viewport, blockTime }) {
|
|
|
782
876
|
);
|
|
783
877
|
}
|
|
784
878
|
const { fill: fillValue, def: fillDef } = resolveFill(
|
|
785
|
-
|
|
879
|
+
defsId,
|
|
786
880
|
fill,
|
|
787
881
|
content.gradient,
|
|
788
882
|
content.pattern
|
|
789
883
|
);
|
|
790
|
-
const { filterAttr, def: filterDef } = resolveShapeFilter(
|
|
884
|
+
const { filterAttr, def: filterDef } = resolveShapeFilter(defsId, content.filter);
|
|
791
885
|
const dash = borderDashArray(content.borderStyle, content.strokeWidth);
|
|
792
886
|
const shapeProps = {
|
|
793
887
|
fill: fillValue,
|
|
@@ -811,10 +905,10 @@ function ShapeLayer({ layer, viewport, blockTime }) {
|
|
|
811
905
|
content.shape === "rect" && /* @__PURE__ */ jsx5(
|
|
812
906
|
"rect",
|
|
813
907
|
{
|
|
814
|
-
x,
|
|
815
|
-
y,
|
|
816
|
-
width,
|
|
817
|
-
height,
|
|
908
|
+
x: paintX,
|
|
909
|
+
y: paintY,
|
|
910
|
+
width: paintWidth,
|
|
911
|
+
height: paintHeight,
|
|
818
912
|
rx: content.borderRadius,
|
|
819
913
|
ry: content.borderRadius,
|
|
820
914
|
...shapeProps
|
|
@@ -847,6 +941,7 @@ function ShapeLayer({ layer, viewport, blockTime }) {
|
|
|
847
941
|
}
|
|
848
942
|
|
|
849
943
|
// src/layers/PathLayer.tsx
|
|
944
|
+
import { useId as useId3 } from "react";
|
|
850
945
|
import { markerPath, shapePath } from "@bendyline/squisq/doc";
|
|
851
946
|
import { jsx as jsx6, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
852
947
|
function effectivePath(layer, viewport) {
|
|
@@ -860,23 +955,28 @@ function effectivePath(layer, viewport) {
|
|
|
860
955
|
const derived = shapePath(content.shapeKind, rawX + anchor.x, rawY + anchor.y, w, h);
|
|
861
956
|
return derived ?? content.d;
|
|
862
957
|
}
|
|
863
|
-
function
|
|
958
|
+
function readLegacyArrow(content) {
|
|
959
|
+
return content.arrow;
|
|
960
|
+
}
|
|
961
|
+
function effectiveMarker(explicit, legacyArrow, end) {
|
|
864
962
|
if (explicit) return explicit;
|
|
865
|
-
const wants =
|
|
963
|
+
const wants = legacyArrow === "both" || legacyArrow === end;
|
|
866
964
|
return wants ? "arrow" : "none";
|
|
867
965
|
}
|
|
868
966
|
function PathLayer({ layer, viewport, blockTime }) {
|
|
869
967
|
const { content, animation, id } = layer;
|
|
968
|
+
const defsId = `${useId3().replace(/:/g, "")}-${id}`;
|
|
870
969
|
const d = effectivePath(layer, viewport);
|
|
871
970
|
const stroke = content.stroke ?? "#1e293b";
|
|
872
971
|
const strokeWidth = content.strokeWidth ?? 2;
|
|
873
|
-
const { fill, def: fillDef } = resolveFill(
|
|
972
|
+
const { fill, def: fillDef } = resolveFill(defsId, content.fill ?? "none", content.gradient);
|
|
874
973
|
const dash = content.borderStyle ? borderDashArray(content.borderStyle, strokeWidth) : content.dasharray;
|
|
875
974
|
const animStyle = getAnimationStyle(animation, blockTime);
|
|
876
|
-
const startId = `marker-start-${
|
|
877
|
-
const endId = `marker-end-${
|
|
878
|
-
const
|
|
879
|
-
const
|
|
975
|
+
const startId = `marker-start-${defsId}`;
|
|
976
|
+
const endId = `marker-end-${defsId}`;
|
|
977
|
+
const legacyArrow = readLegacyArrow(content);
|
|
978
|
+
const start = markerPath(effectiveMarker(content.startMarker, legacyArrow, "start"), "start");
|
|
979
|
+
const end = markerPath(effectiveMarker(content.endMarker, legacyArrow, "end"), "end");
|
|
880
980
|
return /* @__PURE__ */ jsxs4(
|
|
881
981
|
"g",
|
|
882
982
|
{
|
|
@@ -938,7 +1038,7 @@ function MarkerDef({
|
|
|
938
1038
|
}
|
|
939
1039
|
|
|
940
1040
|
// src/layers/MapLayer.tsx
|
|
941
|
-
import { useState as useState2, useEffect as useEffect3 } from "react";
|
|
1041
|
+
import { useId as useId4, useState as useState2, useEffect as useEffect3 } from "react";
|
|
942
1042
|
|
|
943
1043
|
// src/utils/mapTileUtils.ts
|
|
944
1044
|
var TILE_PROVIDERS = {
|
|
@@ -1124,6 +1224,7 @@ function drawAttribution(ctx, text, width, height) {
|
|
|
1124
1224
|
import { jsx as jsx7, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
1125
1225
|
function MapLayer({ layer, basePath, viewport, blockTime }) {
|
|
1126
1226
|
const { content, position, animation } = layer;
|
|
1227
|
+
const clipId = `map-clip-${useId4().replace(/:/g, "")}-${layer.id}`;
|
|
1127
1228
|
const [mapImageUrl, setMapImageUrl] = useState2(null);
|
|
1128
1229
|
const [isLoading, setIsLoading] = useState2(true);
|
|
1129
1230
|
const [error, setError] = useState2(null);
|
|
@@ -1168,11 +1269,12 @@ function MapLayer({ layer, basePath, viewport, blockTime }) {
|
|
|
1168
1269
|
cancelled = true;
|
|
1169
1270
|
};
|
|
1170
1271
|
}, [
|
|
1171
|
-
content.center
|
|
1172
|
-
content.center.lng,
|
|
1272
|
+
content.center,
|
|
1173
1273
|
content.zoom,
|
|
1174
1274
|
content.style,
|
|
1175
1275
|
content.staticSrc,
|
|
1276
|
+
content.markers,
|
|
1277
|
+
content.showAttribution,
|
|
1176
1278
|
width,
|
|
1177
1279
|
height,
|
|
1178
1280
|
basePath
|
|
@@ -1237,8 +1339,8 @@ function MapLayer({ layer, basePath, viewport, blockTime }) {
|
|
|
1237
1339
|
style: animStyle.style,
|
|
1238
1340
|
"data-layer-id": layer.id,
|
|
1239
1341
|
children: [
|
|
1240
|
-
/* @__PURE__ */ jsx7("defs", { children: /* @__PURE__ */ jsx7("clipPath", { id:
|
|
1241
|
-
/* @__PURE__ */ jsx7("g", { clipPath: `url(
|
|
1342
|
+
/* @__PURE__ */ jsx7("defs", { children: /* @__PURE__ */ jsx7("clipPath", { id: clipId, children: /* @__PURE__ */ jsx7("rect", { x: finalX, y: finalY, width, height }) }) }),
|
|
1343
|
+
/* @__PURE__ */ jsx7("g", { clipPath: `url(#${clipId})`, children: /* @__PURE__ */ jsx7(
|
|
1242
1344
|
"image",
|
|
1243
1345
|
{
|
|
1244
1346
|
href: mapImageUrl,
|
|
@@ -1258,6 +1360,7 @@ function MapLayer({ layer, basePath, viewport, blockTime }) {
|
|
|
1258
1360
|
// src/layers/VideoLayer.tsx
|
|
1259
1361
|
import { useRef as useRef2, useEffect as useEffect4 } from "react";
|
|
1260
1362
|
import { jsx as jsx8 } from "react/jsx-runtime";
|
|
1363
|
+
var VIDEO_SYNC_DRIFT_SECONDS = 0.2;
|
|
1261
1364
|
function VideoLayer({ layer, basePath, viewport, blockTime, isPlaying }) {
|
|
1262
1365
|
const { content, position } = layer;
|
|
1263
1366
|
const videoRef = useRef2(null);
|
|
@@ -1297,16 +1400,22 @@ function VideoLayer({ layer, basePath, viewport, blockTime, isPlaying }) {
|
|
|
1297
1400
|
video.removeEventListener("timeupdate", handleTimeUpdate);
|
|
1298
1401
|
video.pause();
|
|
1299
1402
|
};
|
|
1300
|
-
}, [
|
|
1403
|
+
}, [src, content.clipStart, content.clipEnd]);
|
|
1301
1404
|
useEffect4(() => {
|
|
1302
1405
|
const video = videoRef.current;
|
|
1303
1406
|
if (!video || !hasStartedRef.current) return;
|
|
1407
|
+
const targetTime = gated ? content.clipStart : Math.min(content.clipEnd, content.clipStart + Math.max(0, blockTime - startAt));
|
|
1408
|
+
if (Math.abs(video.currentTime - targetTime) > VIDEO_SYNC_DRIFT_SECONDS) {
|
|
1409
|
+
video.currentTime = targetTime;
|
|
1410
|
+
}
|
|
1304
1411
|
if (gated) {
|
|
1305
1412
|
video.pause();
|
|
1306
|
-
video.currentTime = content.clipStart;
|
|
1307
1413
|
return;
|
|
1308
1414
|
}
|
|
1309
|
-
if (
|
|
1415
|
+
if (targetTime >= content.clipEnd) {
|
|
1416
|
+
video.pause();
|
|
1417
|
+
return;
|
|
1418
|
+
}
|
|
1310
1419
|
if (isPlaying) {
|
|
1311
1420
|
const playPromise = video.play();
|
|
1312
1421
|
if (playPromise) {
|
|
@@ -1316,7 +1425,7 @@ function VideoLayer({ layer, basePath, viewport, blockTime, isPlaying }) {
|
|
|
1316
1425
|
} else {
|
|
1317
1426
|
video.pause();
|
|
1318
1427
|
}
|
|
1319
|
-
}, [isPlaying, gated, content.clipStart, content.clipEnd]);
|
|
1428
|
+
}, [isPlaying, gated, blockTime, startAt, src, content.clipStart, content.clipEnd]);
|
|
1320
1429
|
return /* @__PURE__ */ jsx8("g", { className: "block-layer block-layer--video", "data-layer-id": layer.id, children: /* @__PURE__ */ jsx8("foreignObject", { x: finalX, y: finalY, width, height, children: /* @__PURE__ */ jsx8(
|
|
1321
1430
|
"video",
|
|
1322
1431
|
{
|
|
@@ -1425,9 +1534,137 @@ function TableLayer({ layer, viewport, blockTime }) {
|
|
|
1425
1534
|
) });
|
|
1426
1535
|
}
|
|
1427
1536
|
|
|
1428
|
-
// src/
|
|
1537
|
+
// src/layers/TreeLayer.tsx
|
|
1538
|
+
import { useState as useState3 } from "react";
|
|
1429
1539
|
import { jsx as jsx10, jsxs as jsxs7 } from "react/jsx-runtime";
|
|
1430
|
-
|
|
1540
|
+
function faClass(token, fallback) {
|
|
1541
|
+
const name = token && token.trim() ? token.trim() : fallback;
|
|
1542
|
+
const colon = name.indexOf(":");
|
|
1543
|
+
if (colon > 0) {
|
|
1544
|
+
const family = name.slice(0, colon).replace(/^fa-/, "");
|
|
1545
|
+
return `fa-${family} fa-${name.slice(colon + 1)}`;
|
|
1546
|
+
}
|
|
1547
|
+
return `fa-solid fa-${name}`;
|
|
1548
|
+
}
|
|
1549
|
+
function TreeLayer({ layer, viewport, blockTime }) {
|
|
1550
|
+
const { content, position, animation } = layer;
|
|
1551
|
+
const { items, style } = content;
|
|
1552
|
+
const x = resolveValue(position.x, viewport.width);
|
|
1553
|
+
const y = resolveValue(position.y, viewport.height);
|
|
1554
|
+
const width = position.width ? resolveValue(position.width, viewport.width) : viewport.width;
|
|
1555
|
+
const height = position.height ? resolveValue(position.height, viewport.height) : viewport.height;
|
|
1556
|
+
const offset = getAnchorOffset(position.anchor, width, height);
|
|
1557
|
+
const animStyle = animation ? getAnimationStyle(animation, blockTime) : {};
|
|
1558
|
+
return /* @__PURE__ */ jsx10(
|
|
1559
|
+
"foreignObject",
|
|
1560
|
+
{
|
|
1561
|
+
x: x + offset.x,
|
|
1562
|
+
y: y + offset.y,
|
|
1563
|
+
width,
|
|
1564
|
+
height,
|
|
1565
|
+
style: animStyle,
|
|
1566
|
+
children: /* @__PURE__ */ jsx10(
|
|
1567
|
+
"div",
|
|
1568
|
+
{
|
|
1569
|
+
...{ xmlns: "http://www.w3.org/1999/xhtml" },
|
|
1570
|
+
className: "squisq-treelayer",
|
|
1571
|
+
style: {
|
|
1572
|
+
width: `${width}px`,
|
|
1573
|
+
height: `${height}px`,
|
|
1574
|
+
display: "flex",
|
|
1575
|
+
flexDirection: "column",
|
|
1576
|
+
justifyContent: "center",
|
|
1577
|
+
padding: "24px 32px",
|
|
1578
|
+
boxSizing: "border-box",
|
|
1579
|
+
fontFamily: style.fontFamily ?? "system-ui, sans-serif",
|
|
1580
|
+
fontSize: `${style.fontSize}px`,
|
|
1581
|
+
lineHeight: 1.7,
|
|
1582
|
+
overflow: "hidden"
|
|
1583
|
+
},
|
|
1584
|
+
children: /* @__PURE__ */ jsx10(TreeList, { items, depth: 0, style })
|
|
1585
|
+
}
|
|
1586
|
+
)
|
|
1587
|
+
}
|
|
1588
|
+
);
|
|
1589
|
+
}
|
|
1590
|
+
function TreeList({
|
|
1591
|
+
items,
|
|
1592
|
+
depth,
|
|
1593
|
+
style
|
|
1594
|
+
}) {
|
|
1595
|
+
return /* @__PURE__ */ jsx10(
|
|
1596
|
+
"ul",
|
|
1597
|
+
{
|
|
1598
|
+
style: {
|
|
1599
|
+
listStyle: "none",
|
|
1600
|
+
margin: 0,
|
|
1601
|
+
padding: 0,
|
|
1602
|
+
paddingLeft: depth === 0 ? 0 : `${style.indentPx}px`,
|
|
1603
|
+
borderLeft: depth === 0 ? "none" : `1px solid ${style.connectorColor}`
|
|
1604
|
+
},
|
|
1605
|
+
children: items.map((item) => /* @__PURE__ */ jsx10(TreeRow, { item, style }, item.id))
|
|
1606
|
+
}
|
|
1607
|
+
);
|
|
1608
|
+
}
|
|
1609
|
+
function TreeRow({
|
|
1610
|
+
item,
|
|
1611
|
+
style
|
|
1612
|
+
}) {
|
|
1613
|
+
const hasChildren = item.children.length > 0;
|
|
1614
|
+
const [collapsed, setCollapsed] = useState3(false);
|
|
1615
|
+
const isDir = item.isDir || hasChildren;
|
|
1616
|
+
const iconCls = isDir ? faClass(style.folderIcon, collapsed ? "folder" : "folder-open") : faClass(style.fileIcon, "file");
|
|
1617
|
+
return /* @__PURE__ */ jsxs7("li", { style: { position: "relative" }, children: [
|
|
1618
|
+
/* @__PURE__ */ jsxs7("div", { style: { display: "flex", alignItems: "baseline", gap: "8px", padding: "1px 0" }, children: [
|
|
1619
|
+
hasChildren ? /* @__PURE__ */ jsx10(
|
|
1620
|
+
"button",
|
|
1621
|
+
{
|
|
1622
|
+
type: "button",
|
|
1623
|
+
"aria-label": collapsed ? "Expand" : "Collapse",
|
|
1624
|
+
onClick: () => setCollapsed((c) => !c),
|
|
1625
|
+
style: {
|
|
1626
|
+
flex: "0 0 auto",
|
|
1627
|
+
width: "1em",
|
|
1628
|
+
border: "none",
|
|
1629
|
+
background: "transparent",
|
|
1630
|
+
cursor: "pointer",
|
|
1631
|
+
color: style.connectorColor,
|
|
1632
|
+
padding: 0,
|
|
1633
|
+
fontSize: "0.8em"
|
|
1634
|
+
},
|
|
1635
|
+
children: /* @__PURE__ */ jsx10(
|
|
1636
|
+
"i",
|
|
1637
|
+
{
|
|
1638
|
+
className: `fa-solid ${collapsed ? "fa-chevron-right" : "fa-chevron-down"}`,
|
|
1639
|
+
"aria-hidden": "true"
|
|
1640
|
+
}
|
|
1641
|
+
)
|
|
1642
|
+
}
|
|
1643
|
+
) : /* @__PURE__ */ jsx10("span", { style: { flex: "0 0 auto", width: "1em" } }),
|
|
1644
|
+
/* @__PURE__ */ jsx10(
|
|
1645
|
+
"i",
|
|
1646
|
+
{
|
|
1647
|
+
className: iconCls,
|
|
1648
|
+
"aria-hidden": "true",
|
|
1649
|
+
style: { flex: "0 0 auto", color: style.iconColor, width: "1.2em", textAlign: "center" }
|
|
1650
|
+
}
|
|
1651
|
+
),
|
|
1652
|
+
/* @__PURE__ */ jsx10(
|
|
1653
|
+
"span",
|
|
1654
|
+
{
|
|
1655
|
+
style: { color: isDir ? style.dirColor : style.rowColor, fontWeight: isDir ? 600 : 400 },
|
|
1656
|
+
children: item.label
|
|
1657
|
+
}
|
|
1658
|
+
),
|
|
1659
|
+
item.comment ? /* @__PURE__ */ jsx10("span", { style: { color: style.commentColor, fontSize: "0.85em", fontStyle: "italic" }, children: item.comment }) : null
|
|
1660
|
+
] }),
|
|
1661
|
+
hasChildren && !collapsed ? /* @__PURE__ */ jsx10(TreeList, { items: item.children, depth: 1, style }) : null
|
|
1662
|
+
] });
|
|
1663
|
+
}
|
|
1664
|
+
|
|
1665
|
+
// src/BlockRenderer.tsx
|
|
1666
|
+
import { jsx as jsx11, jsxs as jsxs8 } from "react/jsx-runtime";
|
|
1667
|
+
var DEFAULT_VIEWPORT = {
|
|
1431
1668
|
width: 1920,
|
|
1432
1669
|
height: 1080
|
|
1433
1670
|
};
|
|
@@ -1438,12 +1675,13 @@ function BlockRenderer({
|
|
|
1438
1675
|
isEntering = false,
|
|
1439
1676
|
isExiting = false,
|
|
1440
1677
|
transition,
|
|
1441
|
-
viewport =
|
|
1442
|
-
isPlaying
|
|
1678
|
+
viewport = DEFAULT_VIEWPORT,
|
|
1679
|
+
isPlaying,
|
|
1680
|
+
animationsEnabled = true
|
|
1443
1681
|
}) {
|
|
1444
1682
|
let transitionClass = "";
|
|
1445
1683
|
const transitionStyle = {};
|
|
1446
|
-
const activeTransition = transition ?? block.transition;
|
|
1684
|
+
const activeTransition = animationsEnabled ? transition ?? block.transition : void 0;
|
|
1447
1685
|
if (activeTransition && isEntering) {
|
|
1448
1686
|
transitionClass = getTransitionClass(activeTransition.type, true, activeTransition.direction);
|
|
1449
1687
|
transitionStyle["--transition-duration"] = `${resolveTransitionDuration(activeTransition)}s`;
|
|
@@ -1451,8 +1689,9 @@ function BlockRenderer({
|
|
|
1451
1689
|
transitionClass = getTransitionClass(activeTransition.type, false, activeTransition.direction);
|
|
1452
1690
|
transitionStyle["--transition-duration"] = `${resolveTransitionDuration(activeTransition)}s`;
|
|
1453
1691
|
}
|
|
1454
|
-
const
|
|
1455
|
-
|
|
1692
|
+
const instanceId = useId5().replace(/:/g, "");
|
|
1693
|
+
const clipId = `vb-clip-${instanceId}-${block.id}`;
|
|
1694
|
+
return /* @__PURE__ */ jsxs8(
|
|
1456
1695
|
"svg",
|
|
1457
1696
|
{
|
|
1458
1697
|
className: `block-svg ${transitionClass}`,
|
|
@@ -1462,15 +1701,16 @@ function BlockRenderer({
|
|
|
1462
1701
|
overflow: "hidden",
|
|
1463
1702
|
"data-block-id": block.id,
|
|
1464
1703
|
children: [
|
|
1465
|
-
/* @__PURE__ */
|
|
1466
|
-
/* @__PURE__ */
|
|
1704
|
+
/* @__PURE__ */ jsx11("defs", { children: /* @__PURE__ */ jsx11("clipPath", { id: clipId, children: /* @__PURE__ */ jsx11("rect", { x: "0", y: "0", width: viewport.width, height: viewport.height }) }) }),
|
|
1705
|
+
/* @__PURE__ */ jsx11("g", { clipPath: `url(#${clipId})`, children: (block.layers ?? []).map((layer) => /* @__PURE__ */ jsx11(
|
|
1467
1706
|
LayerRenderer,
|
|
1468
1707
|
{
|
|
1469
1708
|
layer,
|
|
1470
1709
|
basePath,
|
|
1471
1710
|
viewport,
|
|
1472
1711
|
blockTime,
|
|
1473
|
-
isPlaying
|
|
1712
|
+
isPlaying,
|
|
1713
|
+
animationsEnabled
|
|
1474
1714
|
},
|
|
1475
1715
|
layer.id
|
|
1476
1716
|
)) })
|
|
@@ -1478,23 +1718,48 @@ function BlockRenderer({
|
|
|
1478
1718
|
}
|
|
1479
1719
|
);
|
|
1480
1720
|
}
|
|
1481
|
-
function LayerRenderer({
|
|
1482
|
-
|
|
1721
|
+
function LayerRenderer({
|
|
1722
|
+
layer,
|
|
1723
|
+
basePath,
|
|
1724
|
+
viewport,
|
|
1725
|
+
blockTime,
|
|
1726
|
+
isPlaying,
|
|
1727
|
+
animationsEnabled
|
|
1728
|
+
}) {
|
|
1729
|
+
const renderedLayer = animationsEnabled || !layer.animation ? layer : { ...layer, animation: void 0 };
|
|
1730
|
+
switch (renderedLayer.type) {
|
|
1483
1731
|
case "image":
|
|
1484
|
-
return /* @__PURE__ */
|
|
1732
|
+
return /* @__PURE__ */ jsx11(
|
|
1733
|
+
ImageLayer,
|
|
1734
|
+
{
|
|
1735
|
+
layer: renderedLayer,
|
|
1736
|
+
basePath,
|
|
1737
|
+
viewport,
|
|
1738
|
+
blockTime,
|
|
1739
|
+
animationsEnabled
|
|
1740
|
+
}
|
|
1741
|
+
);
|
|
1485
1742
|
case "text":
|
|
1486
|
-
return /* @__PURE__ */
|
|
1743
|
+
return /* @__PURE__ */ jsx11(TextLayer, { layer: renderedLayer, viewport, blockTime });
|
|
1487
1744
|
case "shape":
|
|
1488
|
-
return /* @__PURE__ */
|
|
1745
|
+
return /* @__PURE__ */ jsx11(ShapeLayer, { layer: renderedLayer, viewport, blockTime });
|
|
1489
1746
|
case "path":
|
|
1490
|
-
return /* @__PURE__ */
|
|
1747
|
+
return /* @__PURE__ */ jsx11(PathLayer, { layer: renderedLayer, viewport, blockTime });
|
|
1491
1748
|
case "map":
|
|
1492
|
-
return /* @__PURE__ */
|
|
1749
|
+
return /* @__PURE__ */ jsx11(
|
|
1750
|
+
MapLayer,
|
|
1751
|
+
{
|
|
1752
|
+
layer: renderedLayer,
|
|
1753
|
+
basePath,
|
|
1754
|
+
viewport,
|
|
1755
|
+
blockTime
|
|
1756
|
+
}
|
|
1757
|
+
);
|
|
1493
1758
|
case "video":
|
|
1494
|
-
return /* @__PURE__ */
|
|
1759
|
+
return /* @__PURE__ */ jsx11(
|
|
1495
1760
|
VideoLayer,
|
|
1496
1761
|
{
|
|
1497
|
-
layer,
|
|
1762
|
+
layer: renderedLayer,
|
|
1498
1763
|
basePath,
|
|
1499
1764
|
viewport,
|
|
1500
1765
|
blockTime,
|
|
@@ -1502,9 +1767,11 @@ function LayerRenderer({ layer, basePath, viewport, blockTime, isPlaying }) {
|
|
|
1502
1767
|
}
|
|
1503
1768
|
);
|
|
1504
1769
|
case "table":
|
|
1505
|
-
return /* @__PURE__ */
|
|
1770
|
+
return /* @__PURE__ */ jsx11(TableLayer, { layer: renderedLayer, viewport, blockTime });
|
|
1771
|
+
case "tree":
|
|
1772
|
+
return /* @__PURE__ */ jsx11(TreeLayer, { layer: renderedLayer, viewport, blockTime });
|
|
1506
1773
|
default:
|
|
1507
|
-
console.warn(`Unknown layer type: ${
|
|
1774
|
+
console.warn(`Unknown layer type: ${renderedLayer.type}`);
|
|
1508
1775
|
return null;
|
|
1509
1776
|
}
|
|
1510
1777
|
}
|
|
@@ -1515,7 +1782,7 @@ import { getCaptionAtTime } from "@bendyline/squisq/schemas";
|
|
|
1515
1782
|
// src/SocialCaptionOverlay.tsx
|
|
1516
1783
|
import { useMemo as useMemo4 } from "react";
|
|
1517
1784
|
import { resolveFontFamily } from "@bendyline/squisq/schemas";
|
|
1518
|
-
import { jsx as
|
|
1785
|
+
import { jsx as jsx12 } from "react/jsx-runtime";
|
|
1519
1786
|
var TARGET_CHUNK_SIZE = 4;
|
|
1520
1787
|
var MIN_CHUNK_SIZE = 2;
|
|
1521
1788
|
var MAX_CHUNK_SIZE = 6;
|
|
@@ -1574,7 +1841,7 @@ function SocialCaptionOverlay({
|
|
|
1574
1841
|
[captions]
|
|
1575
1842
|
);
|
|
1576
1843
|
if (!enabled || chunks.length === 0) {
|
|
1577
|
-
return /* @__PURE__ */
|
|
1844
|
+
return /* @__PURE__ */ jsx12(
|
|
1578
1845
|
"div",
|
|
1579
1846
|
{
|
|
1580
1847
|
className: "social-caption-overlay",
|
|
@@ -1636,7 +1903,7 @@ function SocialCaptionOverlay({
|
|
|
1636
1903
|
const viewportHeight = viewport?.height ?? 720;
|
|
1637
1904
|
const baseFontSize = Math.round(viewportHeight * 0.055);
|
|
1638
1905
|
const fontSize = Math.max(24, Math.min(72, baseFontSize));
|
|
1639
|
-
return /* @__PURE__ */
|
|
1906
|
+
return /* @__PURE__ */ jsx12(
|
|
1640
1907
|
"div",
|
|
1641
1908
|
{
|
|
1642
1909
|
className: "social-caption-overlay",
|
|
@@ -1653,7 +1920,7 @@ function SocialCaptionOverlay({
|
|
|
1653
1920
|
opacity: 1,
|
|
1654
1921
|
transition: "opacity 0.15s ease-in-out"
|
|
1655
1922
|
},
|
|
1656
|
-
children: /* @__PURE__ */
|
|
1923
|
+
children: /* @__PURE__ */ jsx12(
|
|
1657
1924
|
"div",
|
|
1658
1925
|
{
|
|
1659
1926
|
style: {
|
|
@@ -1662,7 +1929,7 @@ function SocialCaptionOverlay({
|
|
|
1662
1929
|
},
|
|
1663
1930
|
children: activeChunk.words.map((word, i) => {
|
|
1664
1931
|
const isActive = i === activeWordIndex;
|
|
1665
|
-
return /* @__PURE__ */
|
|
1932
|
+
return /* @__PURE__ */ jsx12(
|
|
1666
1933
|
"span",
|
|
1667
1934
|
{
|
|
1668
1935
|
style: {
|
|
@@ -1687,7 +1954,7 @@ function SocialCaptionOverlay({
|
|
|
1687
1954
|
}
|
|
1688
1955
|
|
|
1689
1956
|
// src/CaptionOverlay.tsx
|
|
1690
|
-
import { jsx as
|
|
1957
|
+
import { jsx as jsx13 } from "react/jsx-runtime";
|
|
1691
1958
|
function CaptionOverlay({
|
|
1692
1959
|
captions,
|
|
1693
1960
|
currentTime,
|
|
@@ -1698,7 +1965,7 @@ function CaptionOverlay({
|
|
|
1698
1965
|
viewport
|
|
1699
1966
|
}) {
|
|
1700
1967
|
if (captionStyle === "social") {
|
|
1701
|
-
return /* @__PURE__ */
|
|
1968
|
+
return /* @__PURE__ */ jsx13(
|
|
1702
1969
|
SocialCaptionOverlay,
|
|
1703
1970
|
{
|
|
1704
1971
|
captions,
|
|
@@ -1711,7 +1978,7 @@ function CaptionOverlay({
|
|
|
1711
1978
|
}
|
|
1712
1979
|
const phrase = enabled && captions ? getCaptionAtTime(captions, currentTime) : null;
|
|
1713
1980
|
const captionText = phrase?.text ?? null;
|
|
1714
|
-
return /* @__PURE__ */
|
|
1981
|
+
return /* @__PURE__ */ jsx13(
|
|
1715
1982
|
"div",
|
|
1716
1983
|
{
|
|
1717
1984
|
className: "caption-overlay",
|
|
@@ -1730,7 +1997,7 @@ function CaptionOverlay({
|
|
|
1730
1997
|
padding: "0 4px",
|
|
1731
1998
|
boxSizing: "border-box"
|
|
1732
1999
|
},
|
|
1733
|
-
children: captionText && /* @__PURE__ */
|
|
2000
|
+
children: captionText && /* @__PURE__ */ jsx13(
|
|
1734
2001
|
"div",
|
|
1735
2002
|
{
|
|
1736
2003
|
style: {
|
|
@@ -1740,7 +2007,7 @@ function CaptionOverlay({
|
|
|
1740
2007
|
borderRadius: "4px",
|
|
1741
2008
|
backdropFilter: "blur(4px)"
|
|
1742
2009
|
},
|
|
1743
|
-
children: /* @__PURE__ */
|
|
2010
|
+
children: /* @__PURE__ */ jsx13(
|
|
1744
2011
|
"span",
|
|
1745
2012
|
{
|
|
1746
2013
|
style: {
|
|
@@ -1786,22 +2053,40 @@ function useAutoSurface(enabled) {
|
|
|
1786
2053
|
}
|
|
1787
2054
|
|
|
1788
2055
|
// src/hooks/useAudioSync.ts
|
|
1789
|
-
import { useState as
|
|
1790
|
-
function
|
|
1791
|
-
|
|
1792
|
-
|
|
1793
|
-
|
|
1794
|
-
|
|
1795
|
-
|
|
1796
|
-
const [
|
|
2056
|
+
import { useState as useState4, useEffect as useEffect5, useRef as useRef3, useCallback as useCallback2 } from "react";
|
|
2057
|
+
function resolveAudioUrl(src, basePath) {
|
|
2058
|
+
if (!src || /^(?:[a-z][a-z0-9+.-]*:|\/\/|\/)/i.test(src)) return src;
|
|
2059
|
+
if (!basePath) return src;
|
|
2060
|
+
return `${basePath.replace(/\/$/, "")}/${src.replace(/^\//, "")}`;
|
|
2061
|
+
}
|
|
2062
|
+
function useAudioSync(audioRef, audioTrack, basePath = "", enabled = true) {
|
|
2063
|
+
const [currentTime, setCurrentTime] = useState4(0);
|
|
2064
|
+
const [isPlaying, setIsPlaying] = useState4(false);
|
|
2065
|
+
const [currentSegment, setCurrentSegment] = useState4(0);
|
|
2066
|
+
const [isEnded, setIsEnded] = useState4(false);
|
|
2067
|
+
const [isAudioReady, setIsAudioReady] = useState4(false);
|
|
2068
|
+
const [totalDuration, setTotalDuration] = useState4(0);
|
|
1797
2069
|
const segmentStarts = useRef3([]);
|
|
1798
2070
|
const pendingSeekTime = useRef3(null);
|
|
1799
2071
|
const shouldPlayAfterLoad = useRef3(false);
|
|
1800
2072
|
const blobUrls = useRef3(/* @__PURE__ */ new Map());
|
|
1801
2073
|
const loadingPromises = useRef3(/* @__PURE__ */ new Map());
|
|
2074
|
+
const abortControllers = useRef3(/* @__PURE__ */ new Set());
|
|
2075
|
+
const loadGeneration = useRef3(0);
|
|
1802
2076
|
const fallbackMode = useRef3(false);
|
|
1803
2077
|
useEffect5(() => {
|
|
1804
|
-
|
|
2078
|
+
loadGeneration.current += 1;
|
|
2079
|
+
pendingSeekTime.current = null;
|
|
2080
|
+
shouldPlayAfterLoad.current = false;
|
|
2081
|
+
fallbackMode.current = false;
|
|
2082
|
+
setCurrentTime(0);
|
|
2083
|
+
setCurrentSegment(0);
|
|
2084
|
+
setIsPlaying(false);
|
|
2085
|
+
setIsEnded(false);
|
|
2086
|
+
setIsAudioReady(false);
|
|
2087
|
+
if (!enabled || !audioTrack?.segments) {
|
|
2088
|
+
segmentStarts.current = [];
|
|
2089
|
+
setTotalDuration(0);
|
|
1805
2090
|
return;
|
|
1806
2091
|
}
|
|
1807
2092
|
let time = 0;
|
|
@@ -1811,27 +2096,35 @@ function useAudioSync(audioRef, audioTrack, basePath = "") {
|
|
|
1811
2096
|
return start;
|
|
1812
2097
|
});
|
|
1813
2098
|
setTotalDuration(time);
|
|
1814
|
-
}, [audioTrack]);
|
|
2099
|
+
}, [audioTrack, enabled]);
|
|
1815
2100
|
const preloadAudio = useCallback2(
|
|
1816
2101
|
async (src) => {
|
|
1817
|
-
const audioUrl =
|
|
2102
|
+
const audioUrl = resolveAudioUrl(src, basePath);
|
|
1818
2103
|
if (blobUrls.current.has(src)) {
|
|
1819
2104
|
return blobUrls.current.get(src);
|
|
1820
2105
|
}
|
|
1821
2106
|
if (loadingPromises.current.has(src)) {
|
|
1822
2107
|
return loadingPromises.current.get(src);
|
|
1823
2108
|
}
|
|
2109
|
+
const controller = new AbortController();
|
|
2110
|
+
abortControllers.current.add(controller);
|
|
2111
|
+
const generation = loadGeneration.current;
|
|
1824
2112
|
const loadPromise = (async () => {
|
|
1825
2113
|
try {
|
|
1826
|
-
const response = await fetch(audioUrl);
|
|
2114
|
+
const response = await fetch(audioUrl, { signal: controller.signal });
|
|
1827
2115
|
if (!response.ok) throw new Error(`HTTP ${response.status}`);
|
|
1828
2116
|
const blob = await response.blob();
|
|
1829
2117
|
const blobUrl = URL.createObjectURL(blob);
|
|
2118
|
+
if (controller.signal.aborted || generation !== loadGeneration.current) {
|
|
2119
|
+
URL.revokeObjectURL(blobUrl);
|
|
2120
|
+
return audioUrl;
|
|
2121
|
+
}
|
|
1830
2122
|
blobUrls.current.set(src, blobUrl);
|
|
1831
2123
|
return blobUrl;
|
|
1832
2124
|
} catch {
|
|
1833
2125
|
return audioUrl;
|
|
1834
2126
|
} finally {
|
|
2127
|
+
abortControllers.current.delete(controller);
|
|
1835
2128
|
loadingPromises.current.delete(src);
|
|
1836
2129
|
}
|
|
1837
2130
|
})();
|
|
@@ -1841,19 +2134,26 @@ function useAudioSync(audioRef, audioTrack, basePath = "") {
|
|
|
1841
2134
|
[basePath]
|
|
1842
2135
|
);
|
|
1843
2136
|
useEffect5(() => {
|
|
1844
|
-
if (!audioTrack?.segments) return;
|
|
2137
|
+
if (!enabled || !audioTrack?.segments) return;
|
|
1845
2138
|
audioTrack.segments.forEach((segment) => {
|
|
1846
2139
|
preloadAudio(segment.src);
|
|
1847
2140
|
});
|
|
1848
2141
|
const currentBlobUrls = blobUrls.current;
|
|
2142
|
+
const currentAbortControllers = abortControllers.current;
|
|
2143
|
+
const currentLoadingPromises = loadingPromises.current;
|
|
1849
2144
|
return () => {
|
|
2145
|
+
loadGeneration.current += 1;
|
|
2146
|
+
currentAbortControllers.forEach((controller) => controller.abort());
|
|
2147
|
+
currentAbortControllers.clear();
|
|
2148
|
+
currentLoadingPromises.clear();
|
|
1850
2149
|
currentBlobUrls.forEach((url) => {
|
|
1851
2150
|
URL.revokeObjectURL(url);
|
|
1852
2151
|
});
|
|
1853
2152
|
currentBlobUrls.clear();
|
|
1854
2153
|
};
|
|
1855
|
-
}, [audioTrack, preloadAudio]);
|
|
2154
|
+
}, [audioTrack, preloadAudio, enabled]);
|
|
1856
2155
|
useEffect5(() => {
|
|
2156
|
+
if (!enabled) return;
|
|
1857
2157
|
const audio = audioRef.current;
|
|
1858
2158
|
if (!audio) return;
|
|
1859
2159
|
const handleTimeUpdate = () => {
|
|
@@ -1890,8 +2190,9 @@ function useAudioSync(audioRef, audioTrack, basePath = "") {
|
|
|
1890
2190
|
audio.removeEventListener("ended", handleEnded);
|
|
1891
2191
|
audio.removeEventListener("error", handleError);
|
|
1892
2192
|
};
|
|
1893
|
-
}, [audioRef, currentSegment, audioTrack]);
|
|
2193
|
+
}, [audioRef, currentSegment, audioTrack, enabled]);
|
|
1894
2194
|
useEffect5(() => {
|
|
2195
|
+
if (!enabled) return;
|
|
1895
2196
|
const audio = audioRef.current;
|
|
1896
2197
|
if (!audio || !audioTrack?.segments) return;
|
|
1897
2198
|
const segment = audioTrack.segments[currentSegment];
|
|
@@ -1913,29 +2214,37 @@ function useAudioSync(audioRef, audioTrack, basePath = "") {
|
|
|
1913
2214
|
const currentSrc = audio.src;
|
|
1914
2215
|
const cachedBlobUrl = blobUrls.current.get(segment.src);
|
|
1915
2216
|
const isSameSource = currentSrc && (currentSrc === cachedBlobUrl || currentSrc.endsWith(segment.src));
|
|
2217
|
+
let cancelled = false;
|
|
2218
|
+
let handleCanPlay = null;
|
|
1916
2219
|
if (!isSameSource) {
|
|
1917
2220
|
const loadAndPlay = async () => {
|
|
1918
2221
|
const blobUrl = await preloadAudio(segment.src);
|
|
1919
|
-
|
|
2222
|
+
if (cancelled) return;
|
|
2223
|
+
handleCanPlay = () => {
|
|
2224
|
+
if (cancelled) return;
|
|
1920
2225
|
setIsAudioReady(true);
|
|
1921
2226
|
applyPendingSeek();
|
|
1922
|
-
audio.removeEventListener("canplay", handleCanPlay);
|
|
2227
|
+
if (handleCanPlay) audio.removeEventListener("canplay", handleCanPlay);
|
|
1923
2228
|
};
|
|
1924
2229
|
audio.addEventListener("canplay", handleCanPlay);
|
|
1925
2230
|
audio.src = blobUrl;
|
|
1926
2231
|
audio.load();
|
|
1927
2232
|
await Promise.resolve();
|
|
1928
2233
|
if (audio.readyState >= 3) {
|
|
1929
|
-
audio.removeEventListener("canplay", handleCanPlay);
|
|
2234
|
+
if (handleCanPlay) audio.removeEventListener("canplay", handleCanPlay);
|
|
1930
2235
|
setIsAudioReady(true);
|
|
1931
2236
|
applyPendingSeek();
|
|
1932
2237
|
}
|
|
1933
2238
|
};
|
|
1934
|
-
loadAndPlay();
|
|
2239
|
+
void loadAndPlay();
|
|
1935
2240
|
} else {
|
|
1936
2241
|
applyPendingSeek();
|
|
1937
2242
|
}
|
|
1938
|
-
|
|
2243
|
+
return () => {
|
|
2244
|
+
cancelled = true;
|
|
2245
|
+
if (handleCanPlay) audio.removeEventListener("canplay", handleCanPlay);
|
|
2246
|
+
};
|
|
2247
|
+
}, [audioRef, currentSegment, audioTrack, preloadAudio, enabled]);
|
|
1939
2248
|
const play = useCallback2(() => {
|
|
1940
2249
|
const audio = audioRef.current;
|
|
1941
2250
|
if (audio) {
|
|
@@ -2072,8 +2381,8 @@ import {
|
|
|
2072
2381
|
resolvePersistentLayers,
|
|
2073
2382
|
VIEWPORT_PRESETS
|
|
2074
2383
|
} from "@bendyline/squisq/doc";
|
|
2075
|
-
function useDocPlayback(script, currentTime,
|
|
2076
|
-
|
|
2384
|
+
function useDocPlayback(script, currentTime, options = {}) {
|
|
2385
|
+
const { viewport = VIEWPORT_PRESETS.landscape, theme, onSeek } = options;
|
|
2077
2386
|
const blocks = useMemo6(() => {
|
|
2078
2387
|
if (!script?.blocks) {
|
|
2079
2388
|
return [];
|
|
@@ -2136,8 +2445,19 @@ function useDocPlayback(script, currentTime, viewport = VIEWPORT_PRESETS.landsca
|
|
|
2136
2445
|
const outgoingBlockRef = useRef4(null);
|
|
2137
2446
|
const activeBlockIdRef = useRef4(null);
|
|
2138
2447
|
const lastRenderedBlockRef = useRef4(null);
|
|
2448
|
+
const suppressOutgoingTargetRef = useRef4(null);
|
|
2449
|
+
const suppressOutgoingForNextBlock = useCallback3((blockId) => {
|
|
2450
|
+
if (activeBlockIdRef.current === blockId) {
|
|
2451
|
+
outgoingBlockRef.current = null;
|
|
2452
|
+
suppressOutgoingTargetRef.current = null;
|
|
2453
|
+
return;
|
|
2454
|
+
}
|
|
2455
|
+
suppressOutgoingTargetRef.current = blockId;
|
|
2456
|
+
}, []);
|
|
2139
2457
|
if (currentBlock && currentBlock.id !== activeBlockIdRef.current) {
|
|
2140
|
-
|
|
2458
|
+
const suppressOutgoing = suppressOutgoingTargetRef.current === currentBlock.id;
|
|
2459
|
+
outgoingBlockRef.current = suppressOutgoing ? null : lastRenderedBlockRef.current;
|
|
2460
|
+
suppressOutgoingTargetRef.current = null;
|
|
2141
2461
|
activeBlockIdRef.current = currentBlock.id;
|
|
2142
2462
|
}
|
|
2143
2463
|
lastRenderedBlockRef.current = currentBlock;
|
|
@@ -2151,10 +2471,10 @@ function useDocPlayback(script, currentTime, viewport = VIEWPORT_PRESETS.landsca
|
|
|
2151
2471
|
if (!script || index < 0 || index >= blocks.length) return;
|
|
2152
2472
|
const targetBlock = blocks[index];
|
|
2153
2473
|
if (targetBlock) {
|
|
2154
|
-
|
|
2474
|
+
onSeek?.(targetBlock.startTime);
|
|
2155
2475
|
}
|
|
2156
2476
|
},
|
|
2157
|
-
[script, blocks]
|
|
2477
|
+
[script, blocks, onSeek]
|
|
2158
2478
|
);
|
|
2159
2479
|
const nextBlock = useCallback3(() => {
|
|
2160
2480
|
if (currentBlockIndex < blocks.length - 1) {
|
|
@@ -2178,13 +2498,14 @@ function useDocPlayback(script, currentTime, viewport = VIEWPORT_PRESETS.landsca
|
|
|
2178
2498
|
nextBlock,
|
|
2179
2499
|
prevBlock,
|
|
2180
2500
|
goToBlock,
|
|
2501
|
+
suppressOutgoingForNextBlock,
|
|
2181
2502
|
/** Expanded blocks (templates converted to full blocks with layers) */
|
|
2182
2503
|
blocks
|
|
2183
2504
|
};
|
|
2184
2505
|
}
|
|
2185
2506
|
|
|
2186
2507
|
// src/hooks/useViewportOrientation.ts
|
|
2187
|
-
import { useState as
|
|
2508
|
+
import { useState as useState5, useEffect as useEffect6, useMemo as useMemo7 } from "react";
|
|
2188
2509
|
import {
|
|
2189
2510
|
VIEWPORT_PRESETS as VIEWPORT_PRESETS2
|
|
2190
2511
|
} from "@bendyline/squisq/doc";
|
|
@@ -2195,7 +2516,7 @@ function getOrientationFromWindow(width, height) {
|
|
|
2195
2516
|
} else if (ratio < 0.83) {
|
|
2196
2517
|
return "portrait";
|
|
2197
2518
|
} else {
|
|
2198
|
-
return "
|
|
2519
|
+
return "square";
|
|
2199
2520
|
}
|
|
2200
2521
|
}
|
|
2201
2522
|
function getViewportForOrientation(orientation) {
|
|
@@ -2210,7 +2531,7 @@ function getViewportForOrientation(orientation) {
|
|
|
2210
2531
|
}
|
|
2211
2532
|
}
|
|
2212
2533
|
function useViewportOrientation() {
|
|
2213
|
-
const [windowSize, setWindowSize] =
|
|
2534
|
+
const [windowSize, setWindowSize] = useState5(() => ({
|
|
2214
2535
|
width: typeof window !== "undefined" ? window.innerWidth : 1920,
|
|
2215
2536
|
height: typeof window !== "undefined" ? window.innerHeight : 1080
|
|
2216
2537
|
}));
|
|
@@ -2246,7 +2567,7 @@ function useViewportOrientation() {
|
|
|
2246
2567
|
}
|
|
2247
2568
|
|
|
2248
2569
|
// src/hooks/useSlideSwipe.ts
|
|
2249
|
-
import { useCallback as useCallback4, useEffect as useEffect7, useRef as useRef5, useState as
|
|
2570
|
+
import { useCallback as useCallback4, useEffect as useEffect7, useRef as useRef5, useState as useState6 } from "react";
|
|
2250
2571
|
var DISTANCE_RATIO = 0.3;
|
|
2251
2572
|
var FLICK_VELOCITY = 0.5;
|
|
2252
2573
|
var MIN_FLICK_DISTANCE = 12;
|
|
@@ -2270,8 +2591,8 @@ function decideSwipe({
|
|
|
2270
2591
|
}
|
|
2271
2592
|
function useSlideSwipe(opts) {
|
|
2272
2593
|
const settleMs = opts.settleMs ?? DEFAULT_SETTLE_MS;
|
|
2273
|
-
const [offsetPx, setOffsetPx] =
|
|
2274
|
-
const [phase, setPhase] =
|
|
2594
|
+
const [offsetPx, setOffsetPx] = useState6(0);
|
|
2595
|
+
const [phase, setPhase] = useState6("idle");
|
|
2275
2596
|
const optsRef = useRef5(opts);
|
|
2276
2597
|
optsRef.current = opts;
|
|
2277
2598
|
const dragRef = useRef5(null);
|
|
@@ -2402,7 +2723,7 @@ import {
|
|
|
2402
2723
|
import { parseMarkdown as parseMarkdown2 } from "@bendyline/squisq/markdown";
|
|
2403
2724
|
|
|
2404
2725
|
// src/DocProgressBar.tsx
|
|
2405
|
-
import { useRef as useRef6, useState as
|
|
2726
|
+
import { useRef as useRef6, useState as useState7, useCallback as useCallback5 } from "react";
|
|
2406
2727
|
|
|
2407
2728
|
// src/types.ts
|
|
2408
2729
|
function formatTime(seconds) {
|
|
@@ -2412,7 +2733,7 @@ function formatTime(seconds) {
|
|
|
2412
2733
|
}
|
|
2413
2734
|
|
|
2414
2735
|
// src/DocProgressBar.tsx
|
|
2415
|
-
import { jsx as
|
|
2736
|
+
import { jsx as jsx14, jsxs as jsxs9 } from "react/jsx-runtime";
|
|
2416
2737
|
function DocProgressBar({
|
|
2417
2738
|
state,
|
|
2418
2739
|
actions,
|
|
@@ -2421,7 +2742,7 @@ function DocProgressBar({
|
|
|
2421
2742
|
getBlockTitle
|
|
2422
2743
|
}) {
|
|
2423
2744
|
const progressBarRef = useRef6(null);
|
|
2424
|
-
const [hoverPosition, setHoverPosition] =
|
|
2745
|
+
const [hoverPosition, setHoverPosition] = useState7(null);
|
|
2425
2746
|
const playProgress = state.totalDuration > 0 ? Math.max(0, Math.min(1, state.currentTime / state.totalDuration)) : 0;
|
|
2426
2747
|
const handleProgressHover = useCallback5((e) => {
|
|
2427
2748
|
const bar = progressBarRef.current;
|
|
@@ -2434,6 +2755,31 @@ function DocProgressBar({
|
|
|
2434
2755
|
const handleProgressLeave = useCallback5(() => {
|
|
2435
2756
|
setHoverPosition(null);
|
|
2436
2757
|
}, []);
|
|
2758
|
+
const handleProgressKeyDown = useCallback5(
|
|
2759
|
+
(e) => {
|
|
2760
|
+
let next = null;
|
|
2761
|
+
switch (e.key) {
|
|
2762
|
+
case "ArrowLeft":
|
|
2763
|
+
case "ArrowDown":
|
|
2764
|
+
next = state.currentTime - 5;
|
|
2765
|
+
break;
|
|
2766
|
+
case "ArrowRight":
|
|
2767
|
+
case "ArrowUp":
|
|
2768
|
+
next = state.currentTime + 5;
|
|
2769
|
+
break;
|
|
2770
|
+
case "Home":
|
|
2771
|
+
next = 0;
|
|
2772
|
+
break;
|
|
2773
|
+
case "End":
|
|
2774
|
+
next = state.totalDuration;
|
|
2775
|
+
break;
|
|
2776
|
+
}
|
|
2777
|
+
if (next == null) return;
|
|
2778
|
+
e.preventDefault();
|
|
2779
|
+
actions.seekTo(Math.max(0, Math.min(state.totalDuration, next)));
|
|
2780
|
+
},
|
|
2781
|
+
[actions, state.currentTime, state.totalDuration]
|
|
2782
|
+
);
|
|
2437
2783
|
const getBlockAtTimeLocal = useCallback5(
|
|
2438
2784
|
(time) => {
|
|
2439
2785
|
for (let i = expandedBlocks.length - 1; i >= 0; i--) {
|
|
@@ -2446,10 +2792,12 @@ function DocProgressBar({
|
|
|
2446
2792
|
},
|
|
2447
2793
|
[expandedBlocks]
|
|
2448
2794
|
);
|
|
2449
|
-
return /* @__PURE__ */
|
|
2795
|
+
return /* @__PURE__ */ jsxs9(
|
|
2450
2796
|
"div",
|
|
2451
2797
|
{
|
|
2452
2798
|
ref: progressBarRef,
|
|
2799
|
+
role: "group",
|
|
2800
|
+
"aria-label": "Playback timeline",
|
|
2453
2801
|
style: {
|
|
2454
2802
|
flex: 1,
|
|
2455
2803
|
height: "24px",
|
|
@@ -2467,9 +2815,17 @@ function DocProgressBar({
|
|
|
2467
2815
|
onMouseMove: handleProgressHover,
|
|
2468
2816
|
onMouseLeave: handleProgressLeave,
|
|
2469
2817
|
children: [
|
|
2470
|
-
/* @__PURE__ */
|
|
2818
|
+
/* @__PURE__ */ jsx14(
|
|
2471
2819
|
"div",
|
|
2472
2820
|
{
|
|
2821
|
+
role: "slider",
|
|
2822
|
+
tabIndex: 0,
|
|
2823
|
+
"aria-label": "Playback position",
|
|
2824
|
+
"aria-valuemin": 0,
|
|
2825
|
+
"aria-valuemax": state.totalDuration,
|
|
2826
|
+
"aria-valuenow": Math.max(0, Math.min(state.totalDuration, state.currentTime)),
|
|
2827
|
+
"aria-valuetext": `${formatTime(state.currentTime)} of ${formatTime(state.totalDuration)}`,
|
|
2828
|
+
onKeyDown: handleProgressKeyDown,
|
|
2473
2829
|
style: {
|
|
2474
2830
|
position: "absolute",
|
|
2475
2831
|
left: 0,
|
|
@@ -2480,7 +2836,7 @@ function DocProgressBar({
|
|
|
2480
2836
|
}
|
|
2481
2837
|
}
|
|
2482
2838
|
),
|
|
2483
|
-
/* @__PURE__ */
|
|
2839
|
+
/* @__PURE__ */ jsx14(
|
|
2484
2840
|
"div",
|
|
2485
2841
|
{
|
|
2486
2842
|
"data-testid": "doc-progress-fill",
|
|
@@ -2494,9 +2850,10 @@ function DocProgressBar({
|
|
|
2494
2850
|
}
|
|
2495
2851
|
}
|
|
2496
2852
|
),
|
|
2497
|
-
blockMarkers.map((marker, i) => /* @__PURE__ */
|
|
2498
|
-
"
|
|
2853
|
+
blockMarkers.map((marker, i) => /* @__PURE__ */ jsx14(
|
|
2854
|
+
"button",
|
|
2499
2855
|
{
|
|
2856
|
+
type: "button",
|
|
2500
2857
|
style: {
|
|
2501
2858
|
position: "absolute",
|
|
2502
2859
|
left: `${marker.position}%`,
|
|
@@ -2506,11 +2863,13 @@ function DocProgressBar({
|
|
|
2506
2863
|
borderRadius: "50%",
|
|
2507
2864
|
background: marker.index === state.currentBlockIndex ? "#ffffff" : "rgba(255,255,255,0.5)",
|
|
2508
2865
|
border: "2px solid #5b9bd5",
|
|
2866
|
+
padding: 0,
|
|
2509
2867
|
cursor: "pointer",
|
|
2510
2868
|
zIndex: 2,
|
|
2511
2869
|
transition: "transform 0.15s, background 0.15s"
|
|
2512
2870
|
},
|
|
2513
2871
|
title: marker.title,
|
|
2872
|
+
"aria-label": `Seek to ${marker.title}`,
|
|
2514
2873
|
onClick: (e) => {
|
|
2515
2874
|
e.stopPropagation();
|
|
2516
2875
|
actions.seekTo(marker.block.startTime);
|
|
@@ -2524,7 +2883,7 @@ function DocProgressBar({
|
|
|
2524
2883
|
},
|
|
2525
2884
|
`${marker.block.id}-${i}`
|
|
2526
2885
|
)),
|
|
2527
|
-
hoverPosition !== null && /* @__PURE__ */
|
|
2886
|
+
hoverPosition !== null && /* @__PURE__ */ jsxs9(
|
|
2528
2887
|
"div",
|
|
2529
2888
|
{
|
|
2530
2889
|
style: {
|
|
@@ -2541,19 +2900,19 @@ function DocProgressBar({
|
|
|
2541
2900
|
zIndex: 10
|
|
2542
2901
|
},
|
|
2543
2902
|
children: [
|
|
2544
|
-
/* @__PURE__ */
|
|
2903
|
+
/* @__PURE__ */ jsx14("div", { style: { color: "white", fontSize: "12px", fontFamily: "monospace" }, children: formatTime(hoverPosition * state.totalDuration) }),
|
|
2545
2904
|
(() => {
|
|
2546
2905
|
const hoverTime = hoverPosition * state.totalDuration;
|
|
2547
2906
|
const slideInfo = getBlockAtTimeLocal(hoverTime);
|
|
2548
2907
|
if (slideInfo && getBlockTitle) {
|
|
2549
|
-
return /* @__PURE__ */
|
|
2908
|
+
return /* @__PURE__ */ jsx14("div", { style: { color: "rgba(255,255,255,0.7)", fontSize: "11px", marginTop: "2px" }, children: getBlockTitle(slideInfo.block) });
|
|
2550
2909
|
}
|
|
2551
2910
|
return null;
|
|
2552
2911
|
})()
|
|
2553
2912
|
]
|
|
2554
2913
|
}
|
|
2555
2914
|
),
|
|
2556
|
-
hoverPosition !== null && /* @__PURE__ */
|
|
2915
|
+
hoverPosition !== null && /* @__PURE__ */ jsx14(
|
|
2557
2916
|
"div",
|
|
2558
2917
|
{
|
|
2559
2918
|
style: {
|
|
@@ -2575,7 +2934,7 @@ function DocProgressBar({
|
|
|
2575
2934
|
}
|
|
2576
2935
|
|
|
2577
2936
|
// src/DocControlsOverlay.tsx
|
|
2578
|
-
import { jsx as
|
|
2937
|
+
import { jsx as jsx15, jsxs as jsxs10 } from "react/jsx-runtime";
|
|
2579
2938
|
function DocControlsOverlay({
|
|
2580
2939
|
state,
|
|
2581
2940
|
actions,
|
|
@@ -2583,7 +2942,7 @@ function DocControlsOverlay({
|
|
|
2583
2942
|
expandedBlocks,
|
|
2584
2943
|
getBlockTitle
|
|
2585
2944
|
}) {
|
|
2586
|
-
return /* @__PURE__ */
|
|
2945
|
+
return /* @__PURE__ */ jsxs10(
|
|
2587
2946
|
"div",
|
|
2588
2947
|
{
|
|
2589
2948
|
className: "doc-player__controls",
|
|
@@ -2600,7 +2959,7 @@ function DocControlsOverlay({
|
|
|
2600
2959
|
zIndex: 100
|
|
2601
2960
|
},
|
|
2602
2961
|
children: [
|
|
2603
|
-
/* @__PURE__ */
|
|
2962
|
+
/* @__PURE__ */ jsx15(
|
|
2604
2963
|
"button",
|
|
2605
2964
|
{
|
|
2606
2965
|
onClick: actions.restart,
|
|
@@ -2616,10 +2975,10 @@ function DocControlsOverlay({
|
|
|
2616
2975
|
},
|
|
2617
2976
|
title: "Restart",
|
|
2618
2977
|
"aria-label": "Restart from beginning",
|
|
2619
|
-
children: /* @__PURE__ */
|
|
2978
|
+
children: /* @__PURE__ */ jsx15("svg", { viewBox: "0 0 24 24", fill: "currentColor", width: "18", height: "18", children: /* @__PURE__ */ jsx15("path", { d: "M12 5V1L7 6l5 5V7c3.31 0 6 2.69 6 6s-2.69 6-6 6-6-2.69-6-6H4c0 4.42 3.58 8 8 8s8-3.58 8-8-3.58-8-8-8z" }) })
|
|
2620
2979
|
}
|
|
2621
2980
|
),
|
|
2622
|
-
/* @__PURE__ */
|
|
2981
|
+
/* @__PURE__ */ jsx15(
|
|
2623
2982
|
"button",
|
|
2624
2983
|
{
|
|
2625
2984
|
onClick: actions.toggle,
|
|
@@ -2638,15 +2997,15 @@ function DocControlsOverlay({
|
|
|
2638
2997
|
height: "40px"
|
|
2639
2998
|
},
|
|
2640
2999
|
"aria-label": state.isPlaying ? "Pause" : "Play",
|
|
2641
|
-
children: state.isPlaying ? /* @__PURE__ */
|
|
3000
|
+
children: state.isPlaying ? /* @__PURE__ */ jsx15("svg", { viewBox: "0 0 24 24", fill: "currentColor", width: "20", height: "20", children: /* @__PURE__ */ jsx15("path", { d: "M6 19h4V5H6v14zm8-14v14h4V5h-4z" }) }) : /* @__PURE__ */ jsx15("svg", { viewBox: "0 0 24 24", fill: "currentColor", width: "20", height: "20", children: /* @__PURE__ */ jsx15("path", { d: "M8 5v14l11-7z" }) })
|
|
2642
3001
|
}
|
|
2643
3002
|
),
|
|
2644
|
-
/* @__PURE__ */
|
|
3003
|
+
/* @__PURE__ */ jsxs10("span", { style: { color: "white", fontSize: "12px", minWidth: "80px", fontFamily: "monospace" }, children: [
|
|
2645
3004
|
formatTime(state.currentTime),
|
|
2646
3005
|
" / ",
|
|
2647
3006
|
formatTime(state.totalDuration)
|
|
2648
3007
|
] }),
|
|
2649
|
-
/* @__PURE__ */
|
|
3008
|
+
/* @__PURE__ */ jsx15(
|
|
2650
3009
|
DocProgressBar,
|
|
2651
3010
|
{
|
|
2652
3011
|
state,
|
|
@@ -2656,12 +3015,12 @@ function DocControlsOverlay({
|
|
|
2656
3015
|
getBlockTitle
|
|
2657
3016
|
}
|
|
2658
3017
|
),
|
|
2659
|
-
/* @__PURE__ */
|
|
3018
|
+
/* @__PURE__ */ jsxs10("span", { style: { color: "rgba(255,255,255,0.5)", fontSize: "11px", whiteSpace: "nowrap" }, children: [
|
|
2660
3019
|
state.currentBlockIndex + 1,
|
|
2661
3020
|
"/",
|
|
2662
3021
|
state.totalBlocks
|
|
2663
3022
|
] }),
|
|
2664
|
-
state.hasCaptions && /* @__PURE__ */
|
|
3023
|
+
state.hasCaptions && /* @__PURE__ */ jsxs10(
|
|
2665
3024
|
"button",
|
|
2666
3025
|
{
|
|
2667
3026
|
onClick: () => actions.cycleCaptionMode(),
|
|
@@ -2680,12 +3039,12 @@ function DocControlsOverlay({
|
|
|
2680
3039
|
title: state.captionMode === "off" ? "Captions: Off (click for Standard)" : state.captionMode === "standard" ? "Captions: Standard (click for Social)" : "Captions: Social (click to turn off)",
|
|
2681
3040
|
"aria-label": "Cycle caption style",
|
|
2682
3041
|
children: [
|
|
2683
|
-
/* @__PURE__ */
|
|
2684
|
-
state.captionMode !== "off" && /* @__PURE__ */
|
|
3042
|
+
/* @__PURE__ */ jsx15("svg", { viewBox: "0 0 24 24", fill: "currentColor", width: "18", height: "18", children: /* @__PURE__ */ jsx15("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" }) }),
|
|
3043
|
+
state.captionMode !== "off" && /* @__PURE__ */ jsx15("span", { style: { fontSize: "10px", textTransform: "uppercase", letterSpacing: "0.5px" }, children: state.captionMode === "standard" ? "CC" : "SM" })
|
|
2685
3044
|
]
|
|
2686
3045
|
}
|
|
2687
3046
|
),
|
|
2688
|
-
actions.toggleFullscreen && /* @__PURE__ */
|
|
3047
|
+
actions.toggleFullscreen && /* @__PURE__ */ jsx15(
|
|
2689
3048
|
"button",
|
|
2690
3049
|
{
|
|
2691
3050
|
onClick: actions.toggleFullscreen,
|
|
@@ -2701,7 +3060,7 @@ function DocControlsOverlay({
|
|
|
2701
3060
|
},
|
|
2702
3061
|
title: state.isFullscreen ? "Exit fullscreen (F)" : "Fullscreen (F)",
|
|
2703
3062
|
"aria-label": state.isFullscreen ? "Exit fullscreen" : "Enter fullscreen",
|
|
2704
|
-
children: state.isFullscreen ? /* @__PURE__ */
|
|
3063
|
+
children: state.isFullscreen ? /* @__PURE__ */ jsx15("svg", { viewBox: "0 0 24 24", fill: "currentColor", width: "18", height: "18", children: /* @__PURE__ */ jsx15("path", { d: "M5 16h3v3h2v-5H5v2zm3-8H5v2h5V5H8v3zm6 11h2v-3h3v-2h-5v5zm2-11V5h-2v5h5V8h-3z" }) }) : /* @__PURE__ */ jsx15("svg", { viewBox: "0 0 24 24", fill: "currentColor", width: "18", height: "18", children: /* @__PURE__ */ jsx15("path", { d: "M7 14H5v5h5v-2H7v-3zm-2-4h2V7h3V5H5v5zm12 7h-3v2h5v-5h-2v3zM14 5v2h3v3h2V5h-5z" }) })
|
|
2705
3064
|
}
|
|
2706
3065
|
)
|
|
2707
3066
|
]
|
|
@@ -2710,8 +3069,29 @@ function DocControlsOverlay({
|
|
|
2710
3069
|
}
|
|
2711
3070
|
|
|
2712
3071
|
// src/DocControlsSlideshow.tsx
|
|
2713
|
-
import {
|
|
2714
|
-
|
|
3072
|
+
import { useCallback as useCallback6, useEffect as useEffect8, useId as useId6, useLayoutEffect, useRef as useRef7, useState as useState8 } from "react";
|
|
3073
|
+
import { jsx as jsx16, jsxs as jsxs11 } from "react/jsx-runtime";
|
|
3074
|
+
function DocControlsSlideshow({
|
|
3075
|
+
state,
|
|
3076
|
+
slideNav,
|
|
3077
|
+
slides = [],
|
|
3078
|
+
pickerOpen,
|
|
3079
|
+
onPickerOpenChange
|
|
3080
|
+
}) {
|
|
3081
|
+
const [uncontrolledPickerOpen, setUncontrolledPickerOpen] = useState8(false);
|
|
3082
|
+
const isPickerOpen = pickerOpen ?? uncontrolledPickerOpen;
|
|
3083
|
+
const setPickerOpen = useCallback6(
|
|
3084
|
+
(open) => {
|
|
3085
|
+
if (pickerOpen === void 0) setUncontrolledPickerOpen(open);
|
|
3086
|
+
onPickerOpenChange?.(open);
|
|
3087
|
+
},
|
|
3088
|
+
[pickerOpen, onPickerOpenChange]
|
|
3089
|
+
);
|
|
3090
|
+
const [pickerMaxHeight, setPickerMaxHeight] = useState8(280);
|
|
3091
|
+
const controlsRef = useRef7(null);
|
|
3092
|
+
const triggerRef = useRef7(null);
|
|
3093
|
+
const menuRef = useRef7(null);
|
|
3094
|
+
const menuId = useId6();
|
|
2715
3095
|
const {
|
|
2716
3096
|
currentBlockIndex,
|
|
2717
3097
|
currentSlideLabel,
|
|
@@ -2722,9 +3102,68 @@ function DocControlsSlideshow({ state, slideNav }) {
|
|
|
2722
3102
|
const isFirst = currentBlockIndex <= 0;
|
|
2723
3103
|
const isLast = currentBlockIndex >= totalBlocks - 1;
|
|
2724
3104
|
const counterText = totalBlocks > 0 ? currentSlideLabel ?? `${currentSlideNumber ?? currentBlockIndex + 1} / ${totalSlideNumber ?? totalBlocks}` : "\u2014";
|
|
2725
|
-
|
|
3105
|
+
useEffect8(() => {
|
|
3106
|
+
if (!isPickerOpen) return;
|
|
3107
|
+
const handlePointerDown = (event) => {
|
|
3108
|
+
if (!controlsRef.current?.contains(event.target)) setPickerOpen(false);
|
|
3109
|
+
};
|
|
3110
|
+
const handleKeyDown = (event) => {
|
|
3111
|
+
if (event.key !== "Escape") return;
|
|
3112
|
+
event.preventDefault();
|
|
3113
|
+
setPickerOpen(false);
|
|
3114
|
+
triggerRef.current?.focus();
|
|
3115
|
+
};
|
|
3116
|
+
document.addEventListener("pointerdown", handlePointerDown);
|
|
3117
|
+
document.addEventListener("keydown", handleKeyDown);
|
|
3118
|
+
return () => {
|
|
3119
|
+
document.removeEventListener("pointerdown", handlePointerDown);
|
|
3120
|
+
document.removeEventListener("keydown", handleKeyDown);
|
|
3121
|
+
};
|
|
3122
|
+
}, [isPickerOpen, setPickerOpen]);
|
|
3123
|
+
useLayoutEffect(() => {
|
|
3124
|
+
if (!isPickerOpen) return;
|
|
3125
|
+
const controls = controlsRef.current;
|
|
3126
|
+
const player = controls?.closest(".doc-player") ?? controls?.parentElement;
|
|
3127
|
+
if (!controls || !player) return;
|
|
3128
|
+
const updateMaxHeight = () => {
|
|
3129
|
+
const controlsBounds = controls.getBoundingClientRect();
|
|
3130
|
+
const playerBounds = player.getBoundingClientRect();
|
|
3131
|
+
const availableHeight = Math.floor(controlsBounds.top - playerBounds.top - 8 - 16);
|
|
3132
|
+
setPickerMaxHeight(Math.max(72, availableHeight));
|
|
3133
|
+
};
|
|
3134
|
+
updateMaxHeight();
|
|
3135
|
+
const resizeObserver = typeof ResizeObserver === "undefined" ? null : new ResizeObserver(updateMaxHeight);
|
|
3136
|
+
resizeObserver?.observe(player);
|
|
3137
|
+
resizeObserver?.observe(controls);
|
|
3138
|
+
window.addEventListener("resize", updateMaxHeight);
|
|
3139
|
+
return () => {
|
|
3140
|
+
resizeObserver?.disconnect();
|
|
3141
|
+
window.removeEventListener("resize", updateMaxHeight);
|
|
3142
|
+
};
|
|
3143
|
+
}, [isPickerOpen]);
|
|
3144
|
+
useEffect8(() => {
|
|
3145
|
+
if (!isPickerOpen) return;
|
|
3146
|
+
menuRef.current?.querySelector('[aria-current="true"]')?.focus();
|
|
3147
|
+
}, [isPickerOpen]);
|
|
3148
|
+
const handleMenuKeyDown = (event) => {
|
|
3149
|
+
if (!["ArrowDown", "ArrowUp", "Home", "End"].includes(event.key)) return;
|
|
3150
|
+
const items = Array.from(
|
|
3151
|
+
menuRef.current?.querySelectorAll('[role="menuitem"]') ?? []
|
|
3152
|
+
);
|
|
3153
|
+
if (items.length === 0) return;
|
|
3154
|
+
event.preventDefault();
|
|
3155
|
+
const focusedIndex = items.indexOf(document.activeElement);
|
|
3156
|
+
let nextIndex = focusedIndex;
|
|
3157
|
+
if (event.key === "Home") nextIndex = 0;
|
|
3158
|
+
else if (event.key === "End") nextIndex = items.length - 1;
|
|
3159
|
+
else if (event.key === "ArrowDown") nextIndex = (focusedIndex + 1) % items.length;
|
|
3160
|
+
else nextIndex = (focusedIndex - 1 + items.length) % items.length;
|
|
3161
|
+
items[nextIndex]?.focus();
|
|
3162
|
+
};
|
|
3163
|
+
return /* @__PURE__ */ jsxs11(
|
|
2726
3164
|
"div",
|
|
2727
3165
|
{
|
|
3166
|
+
ref: controlsRef,
|
|
2728
3167
|
className: "doc-controls-slideshow",
|
|
2729
3168
|
"data-testid": "slideshow-controls",
|
|
2730
3169
|
style: {
|
|
@@ -2743,7 +3182,7 @@ function DocControlsSlideshow({ state, slideNav }) {
|
|
|
2743
3182
|
WebkitBackdropFilter: "blur(8px)"
|
|
2744
3183
|
},
|
|
2745
3184
|
children: [
|
|
2746
|
-
/* @__PURE__ */
|
|
3185
|
+
/* @__PURE__ */ jsx16(
|
|
2747
3186
|
"button",
|
|
2748
3187
|
{
|
|
2749
3188
|
onClick: (e) => {
|
|
@@ -2772,27 +3211,138 @@ function DocControlsSlideshow({ state, slideNav }) {
|
|
|
2772
3211
|
onMouseLeave: (e) => {
|
|
2773
3212
|
e.currentTarget.style.background = "none";
|
|
2774
3213
|
},
|
|
2775
|
-
children: /* @__PURE__ */
|
|
3214
|
+
children: /* @__PURE__ */ jsx16("svg", { viewBox: "0 0 24 24", fill: "currentColor", width: "18", height: "18", children: /* @__PURE__ */ jsx16("path", { d: "M15.41 7.41L14 6l-6 6 6 6 1.41-1.41L10.83 12z" }) })
|
|
2776
3215
|
}
|
|
2777
3216
|
),
|
|
2778
|
-
/* @__PURE__ */
|
|
2779
|
-
"
|
|
3217
|
+
/* @__PURE__ */ jsx16(
|
|
3218
|
+
"button",
|
|
2780
3219
|
{
|
|
3220
|
+
ref: triggerRef,
|
|
3221
|
+
type: "button",
|
|
2781
3222
|
"data-testid": "slide-counter",
|
|
3223
|
+
"aria-label": `Choose slide, current ${counterText}`,
|
|
3224
|
+
"aria-haspopup": "menu",
|
|
3225
|
+
"aria-expanded": isPickerOpen,
|
|
3226
|
+
"aria-controls": isPickerOpen ? menuId : void 0,
|
|
3227
|
+
title: "Choose slide",
|
|
3228
|
+
disabled: slides.length === 0,
|
|
3229
|
+
onClick: (event) => {
|
|
3230
|
+
event.stopPropagation();
|
|
3231
|
+
setPickerOpen(!isPickerOpen);
|
|
3232
|
+
},
|
|
2782
3233
|
style: {
|
|
3234
|
+
background: isPickerOpen ? "rgba(255,255,255,0.12)" : "none",
|
|
3235
|
+
border: "none",
|
|
2783
3236
|
color: "rgba(255,255,255,0.9)",
|
|
3237
|
+
cursor: slides.length > 0 ? "pointer" : "default",
|
|
2784
3238
|
fontSize: "13px",
|
|
2785
3239
|
fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif',
|
|
2786
3240
|
fontVariantNumeric: "tabular-nums",
|
|
2787
3241
|
minWidth: "48px",
|
|
2788
3242
|
textAlign: "center",
|
|
2789
|
-
padding: "
|
|
2790
|
-
letterSpacing: "0.02em"
|
|
3243
|
+
padding: "6px 4px",
|
|
3244
|
+
letterSpacing: "0.02em",
|
|
3245
|
+
borderRadius: "4px",
|
|
3246
|
+
transition: "background 0.15s"
|
|
2791
3247
|
},
|
|
2792
3248
|
children: counterText
|
|
2793
3249
|
}
|
|
2794
3250
|
),
|
|
2795
|
-
/* @__PURE__ */
|
|
3251
|
+
isPickerOpen && /* @__PURE__ */ jsx16(
|
|
3252
|
+
"div",
|
|
3253
|
+
{
|
|
3254
|
+
ref: menuRef,
|
|
3255
|
+
id: menuId,
|
|
3256
|
+
role: "menu",
|
|
3257
|
+
"aria-label": "Choose a slide",
|
|
3258
|
+
"data-testid": "slide-picker",
|
|
3259
|
+
onClick: (event) => event.stopPropagation(),
|
|
3260
|
+
onKeyDown: handleMenuKeyDown,
|
|
3261
|
+
style: {
|
|
3262
|
+
position: "absolute",
|
|
3263
|
+
right: 0,
|
|
3264
|
+
bottom: "calc(100% + 8px)",
|
|
3265
|
+
width: "min(280px, calc(100vw - 32px))",
|
|
3266
|
+
maxHeight: `${pickerMaxHeight}px`,
|
|
3267
|
+
overflowY: "auto",
|
|
3268
|
+
padding: "6px",
|
|
3269
|
+
background: "rgba(20, 20, 20, 0.94)",
|
|
3270
|
+
border: "1px solid rgba(255,255,255,0.14)",
|
|
3271
|
+
borderRadius: "8px",
|
|
3272
|
+
boxShadow: "0 10px 30px rgba(0,0,0,0.38)",
|
|
3273
|
+
backdropFilter: "blur(12px)",
|
|
3274
|
+
WebkitBackdropFilter: "blur(12px)"
|
|
3275
|
+
},
|
|
3276
|
+
children: slides.map((slide, index) => {
|
|
3277
|
+
const isCurrent = index === currentBlockIndex;
|
|
3278
|
+
return /* @__PURE__ */ jsxs11(
|
|
3279
|
+
"button",
|
|
3280
|
+
{
|
|
3281
|
+
type: "button",
|
|
3282
|
+
role: "menuitem",
|
|
3283
|
+
"aria-current": isCurrent ? "true" : void 0,
|
|
3284
|
+
"data-testid": `slide-picker-item-${index}`,
|
|
3285
|
+
onClick: () => {
|
|
3286
|
+
slideNav.goToSlide(index);
|
|
3287
|
+
setPickerOpen(false);
|
|
3288
|
+
},
|
|
3289
|
+
style: {
|
|
3290
|
+
display: "grid",
|
|
3291
|
+
gridTemplateColumns: "42px minmax(0, 1fr)",
|
|
3292
|
+
alignItems: "center",
|
|
3293
|
+
gap: "8px",
|
|
3294
|
+
width: "100%",
|
|
3295
|
+
padding: "8px 10px",
|
|
3296
|
+
background: isCurrent ? "rgba(255,255,255,0.14)" : "transparent",
|
|
3297
|
+
border: "none",
|
|
3298
|
+
borderRadius: "5px",
|
|
3299
|
+
color: "rgba(255,255,255,0.94)",
|
|
3300
|
+
cursor: "pointer",
|
|
3301
|
+
fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif',
|
|
3302
|
+
textAlign: "left"
|
|
3303
|
+
},
|
|
3304
|
+
onMouseEnter: (event) => {
|
|
3305
|
+
event.currentTarget.style.background = "rgba(255,255,255,0.1)";
|
|
3306
|
+
},
|
|
3307
|
+
onMouseLeave: (event) => {
|
|
3308
|
+
event.currentTarget.style.background = isCurrent ? "rgba(255,255,255,0.14)" : "transparent";
|
|
3309
|
+
},
|
|
3310
|
+
children: [
|
|
3311
|
+
/* @__PURE__ */ jsx16(
|
|
3312
|
+
"span",
|
|
3313
|
+
{
|
|
3314
|
+
style: {
|
|
3315
|
+
color: isCurrent ? "#fff" : "rgba(255,255,255,0.58)",
|
|
3316
|
+
fontSize: "12px",
|
|
3317
|
+
fontVariantNumeric: "tabular-nums",
|
|
3318
|
+
textAlign: "right"
|
|
3319
|
+
},
|
|
3320
|
+
children: slide.label
|
|
3321
|
+
}
|
|
3322
|
+
),
|
|
3323
|
+
/* @__PURE__ */ jsx16(
|
|
3324
|
+
"span",
|
|
3325
|
+
{
|
|
3326
|
+
style: {
|
|
3327
|
+
minWidth: 0,
|
|
3328
|
+
overflow: "hidden",
|
|
3329
|
+
color: isCurrent ? "#fff" : "rgba(255,255,255,0.82)",
|
|
3330
|
+
fontSize: "13px",
|
|
3331
|
+
lineHeight: 1.3,
|
|
3332
|
+
textOverflow: "ellipsis",
|
|
3333
|
+
whiteSpace: "nowrap"
|
|
3334
|
+
},
|
|
3335
|
+
children: slide.summary
|
|
3336
|
+
}
|
|
3337
|
+
)
|
|
3338
|
+
]
|
|
3339
|
+
},
|
|
3340
|
+
`${slide.id}-${index}`
|
|
3341
|
+
);
|
|
3342
|
+
})
|
|
3343
|
+
}
|
|
3344
|
+
),
|
|
3345
|
+
/* @__PURE__ */ jsx16(
|
|
2796
3346
|
"button",
|
|
2797
3347
|
{
|
|
2798
3348
|
onClick: (e) => {
|
|
@@ -2821,7 +3371,7 @@ function DocControlsSlideshow({ state, slideNav }) {
|
|
|
2821
3371
|
onMouseLeave: (e) => {
|
|
2822
3372
|
e.currentTarget.style.background = "none";
|
|
2823
3373
|
},
|
|
2824
|
-
children: /* @__PURE__ */
|
|
3374
|
+
children: /* @__PURE__ */ jsx16("svg", { viewBox: "0 0 24 24", fill: "currentColor", width: "18", height: "18", children: /* @__PURE__ */ jsx16("path", { d: "M8.59 16.59L10 18l6-6-6-6-1.41 1.41L13.17 12z" }) })
|
|
2825
3375
|
}
|
|
2826
3376
|
)
|
|
2827
3377
|
]
|
|
@@ -2830,18 +3380,18 @@ function DocControlsSlideshow({ state, slideNav }) {
|
|
|
2830
3380
|
}
|
|
2831
3381
|
|
|
2832
3382
|
// src/LinearDocView.tsx
|
|
2833
|
-
import { useMemo as useMemo8 } from "react";
|
|
3383
|
+
import { useEffect as useEffect9, useMemo as useMemo8, useRef as useRef8 } from "react";
|
|
2834
3384
|
import {
|
|
2835
3385
|
applySurface,
|
|
2836
3386
|
resolveFontFamily as resolveFontFamily2
|
|
2837
3387
|
} from "@bendyline/squisq/schemas";
|
|
2838
3388
|
import { VIEWPORT_PRESETS as VIEWPORT_PRESETS3 } from "@bendyline/squisq/schemas";
|
|
2839
3389
|
import {
|
|
2840
|
-
|
|
2841
|
-
hasTemplate,
|
|
3390
|
+
materializeBlockLayers,
|
|
2842
3391
|
markdownToDoc,
|
|
2843
3392
|
DEFAULT_THEME as DEFAULT_THEME2,
|
|
2844
|
-
deriveTemplateInputs
|
|
3393
|
+
deriveTemplateInputs,
|
|
3394
|
+
isTemplateBlock as isTemplateBlock2
|
|
2845
3395
|
} from "@bendyline/squisq/doc";
|
|
2846
3396
|
import { extractPlainText, parseMarkdown } from "@bendyline/squisq/markdown";
|
|
2847
3397
|
|
|
@@ -2853,7 +3403,7 @@ import {
|
|
|
2853
3403
|
} from "@bendyline/squisq/markdown";
|
|
2854
3404
|
|
|
2855
3405
|
// src/InlineVideoPlayer.tsx
|
|
2856
|
-
import { jsx as
|
|
3406
|
+
import { jsx as jsx17 } from "react/jsx-runtime";
|
|
2857
3407
|
function InlineVideoPlayer({
|
|
2858
3408
|
src,
|
|
2859
3409
|
basePath = "",
|
|
@@ -2868,7 +3418,7 @@ function InlineVideoPlayer({
|
|
|
2868
3418
|
const resolvedPoster = useMediaUrl(poster ?? "", basePath);
|
|
2869
3419
|
const posterUrl = poster ? resolvedPoster : void 0;
|
|
2870
3420
|
if (!resolvedSrc) return null;
|
|
2871
|
-
return /* @__PURE__ */
|
|
3421
|
+
return /* @__PURE__ */ jsx17("span", { className: `squisq-inline-video-player ${className ?? ""}`.trim(), children: /* @__PURE__ */ jsx17(
|
|
2872
3422
|
"video",
|
|
2873
3423
|
{
|
|
2874
3424
|
src: resolvedSrc,
|
|
@@ -2883,7 +3433,7 @@ function InlineVideoPlayer({
|
|
|
2883
3433
|
}
|
|
2884
3434
|
|
|
2885
3435
|
// src/InlineAudioPlayer.tsx
|
|
2886
|
-
import { jsx as
|
|
3436
|
+
import { jsx as jsx18 } from "react/jsx-runtime";
|
|
2887
3437
|
function InlineAudioPlayer({
|
|
2888
3438
|
src,
|
|
2889
3439
|
basePath = "",
|
|
@@ -2893,11 +3443,11 @@ function InlineAudioPlayer({
|
|
|
2893
3443
|
}) {
|
|
2894
3444
|
const resolvedSrc = useMediaUrl(src, basePath);
|
|
2895
3445
|
if (!resolvedSrc) return null;
|
|
2896
|
-
return /* @__PURE__ */
|
|
3446
|
+
return /* @__PURE__ */ jsx18("span", { className: `squisq-inline-audio-player ${className ?? ""}`.trim(), children: /* @__PURE__ */ jsx18("audio", { src: resolvedSrc, controls, preload }) });
|
|
2897
3447
|
}
|
|
2898
3448
|
|
|
2899
3449
|
// src/MarkdownRenderer.tsx
|
|
2900
|
-
import { jsx as
|
|
3450
|
+
import { jsx as jsx19, jsxs as jsxs12 } from "react/jsx-runtime";
|
|
2901
3451
|
var DEFAULT_CTX = { htmlPolicy: "sanitize" };
|
|
2902
3452
|
function renderInline(nodes, keyPrefix = "", ctx = DEFAULT_CTX) {
|
|
2903
3453
|
return nodes.map((node, i) => {
|
|
@@ -2905,28 +3455,28 @@ function renderInline(nodes, keyPrefix = "", ctx = DEFAULT_CTX) {
|
|
|
2905
3455
|
switch (node.type) {
|
|
2906
3456
|
case "text": {
|
|
2907
3457
|
if (!node.value.includes("\n")) {
|
|
2908
|
-
return /* @__PURE__ */
|
|
3458
|
+
return /* @__PURE__ */ jsx19(Fragment2, { children: node.value }, key);
|
|
2909
3459
|
}
|
|
2910
3460
|
const parts = node.value.split("\n");
|
|
2911
|
-
return /* @__PURE__ */
|
|
2912
|
-
j > 0 && /* @__PURE__ */
|
|
3461
|
+
return /* @__PURE__ */ jsx19(Fragment2, { children: parts.map((part, j) => /* @__PURE__ */ jsxs12(Fragment2, { children: [
|
|
3462
|
+
j > 0 && /* @__PURE__ */ jsx19("br", {}),
|
|
2913
3463
|
part
|
|
2914
3464
|
] }, j)) }, key);
|
|
2915
3465
|
}
|
|
2916
3466
|
case "emphasis":
|
|
2917
|
-
return /* @__PURE__ */
|
|
3467
|
+
return /* @__PURE__ */ jsx19("em", { className: "squisq-md-em", children: renderInline(node.children, key, ctx) }, key);
|
|
2918
3468
|
case "strong":
|
|
2919
|
-
return /* @__PURE__ */
|
|
3469
|
+
return /* @__PURE__ */ jsx19("strong", { className: "squisq-md-strong", children: renderInline(node.children, key, ctx) }, key);
|
|
2920
3470
|
case "delete":
|
|
2921
|
-
return /* @__PURE__ */
|
|
3471
|
+
return /* @__PURE__ */ jsx19("del", { className: "squisq-md-del", children: renderInline(node.children, key, ctx) }, key);
|
|
2922
3472
|
case "inlineCode":
|
|
2923
|
-
return /* @__PURE__ */
|
|
3473
|
+
return /* @__PURE__ */ jsx19("code", { className: "squisq-md-inline-code", children: node.value }, key);
|
|
2924
3474
|
case "link": {
|
|
2925
3475
|
const href = sanitizeUrl(node.url, "link", { extraLinkSchemes: ctx.linkSchemes });
|
|
2926
3476
|
if (!href) {
|
|
2927
|
-
return /* @__PURE__ */
|
|
3477
|
+
return /* @__PURE__ */ jsx19("span", { className: "squisq-md-link squisq-md-link--blocked", children: renderInline(node.children, key, ctx) }, key);
|
|
2928
3478
|
}
|
|
2929
|
-
return /* @__PURE__ */
|
|
3479
|
+
return /* @__PURE__ */ jsx19(
|
|
2930
3480
|
"a",
|
|
2931
3481
|
{
|
|
2932
3482
|
className: "squisq-md-link",
|
|
@@ -2940,42 +3490,32 @@ function renderInline(nodes, keyPrefix = "", ctx = DEFAULT_CTX) {
|
|
|
2940
3490
|
);
|
|
2941
3491
|
}
|
|
2942
3492
|
case "image":
|
|
2943
|
-
return /* @__PURE__ */
|
|
3493
|
+
return /* @__PURE__ */ jsx19(MdImage, { src: node.url, alt: node.alt ?? "", title: node.title ?? void 0 }, key);
|
|
2944
3494
|
case "break":
|
|
2945
|
-
return /* @__PURE__ */
|
|
3495
|
+
return /* @__PURE__ */ jsx19("br", {}, key);
|
|
2946
3496
|
case "inlineMath":
|
|
2947
|
-
return /* @__PURE__ */
|
|
3497
|
+
return /* @__PURE__ */ jsx19("code", { className: "squisq-md-inline-math", children: node.value }, key);
|
|
2948
3498
|
case "htmlInline":
|
|
2949
3499
|
if (ctx.htmlPolicy === "strip") return null;
|
|
2950
|
-
|
|
2951
|
-
return /* @__PURE__ */ jsx18(
|
|
2952
|
-
"span",
|
|
2953
|
-
{
|
|
2954
|
-
className: "squisq-md-html-inline",
|
|
2955
|
-
dangerouslySetInnerHTML: { __html: node.rawHtml }
|
|
2956
|
-
},
|
|
2957
|
-
key
|
|
2958
|
-
);
|
|
2959
|
-
}
|
|
2960
|
-
return /* @__PURE__ */ jsx18("span", { className: "squisq-md-html-inline", children: renderHtmlNodes(resolveHtmlNodes(node.htmlChildren, ctx.htmlPolicy), `${key}h`) }, key);
|
|
3500
|
+
return /* @__PURE__ */ jsx19("span", { className: "squisq-md-html-inline", children: renderHtmlNodes(resolveHtmlNodes(node.htmlChildren, ctx.htmlPolicy), `${key}h`, ctx) }, key);
|
|
2961
3501
|
case "footnoteReference":
|
|
2962
|
-
return /* @__PURE__ */
|
|
3502
|
+
return /* @__PURE__ */ jsx19("sup", { className: "squisq-md-footnote-ref", children: /* @__PURE__ */ jsxs12("a", { href: `#fn-${node.identifier}`, children: [
|
|
2963
3503
|
"[",
|
|
2964
3504
|
node.label ?? node.identifier,
|
|
2965
3505
|
"]"
|
|
2966
3506
|
] }) }, key);
|
|
2967
3507
|
case "linkReference":
|
|
2968
|
-
return /* @__PURE__ */
|
|
3508
|
+
return /* @__PURE__ */ jsx19("span", { className: "squisq-md-link-ref", children: renderInline(node.children, key, ctx) }, key);
|
|
2969
3509
|
case "imageReference":
|
|
2970
|
-
return /* @__PURE__ */
|
|
3510
|
+
return /* @__PURE__ */ jsxs12("span", { className: "squisq-md-image-ref", children: [
|
|
2971
3511
|
"[",
|
|
2972
3512
|
node.alt ?? node.identifier,
|
|
2973
3513
|
"]"
|
|
2974
3514
|
] }, key);
|
|
2975
3515
|
case "textDirective":
|
|
2976
|
-
return /* @__PURE__ */
|
|
3516
|
+
return /* @__PURE__ */ jsx19("span", { className: "squisq-md-text-directive", "data-directive": node.name, children: renderInline(node.children, key, ctx) }, key);
|
|
2977
3517
|
case "mention":
|
|
2978
|
-
return /* @__PURE__ */
|
|
3518
|
+
return /* @__PURE__ */ jsxs12(
|
|
2979
3519
|
"span",
|
|
2980
3520
|
{
|
|
2981
3521
|
className: "squisq-md-mention mention",
|
|
@@ -2998,61 +3538,51 @@ function renderInline(nodes, keyPrefix = "", ctx = DEFAULT_CTX) {
|
|
|
2998
3538
|
function renderBlock(node, key, ctx = DEFAULT_CTX) {
|
|
2999
3539
|
switch (node.type) {
|
|
3000
3540
|
case "paragraph":
|
|
3001
|
-
return /* @__PURE__ */
|
|
3541
|
+
return /* @__PURE__ */ jsx19("p", { className: "squisq-md-p", children: renderInline(node.children, key, ctx) }, key);
|
|
3002
3542
|
case "heading": {
|
|
3003
3543
|
const Tag = `h${node.depth}`;
|
|
3004
|
-
return /* @__PURE__ */
|
|
3544
|
+
return /* @__PURE__ */ jsx19(Tag, { className: `squisq-md-heading squisq-md-h${node.depth}`, children: renderInline(node.children, key, ctx) }, key);
|
|
3005
3545
|
}
|
|
3006
3546
|
case "blockquote":
|
|
3007
|
-
return /* @__PURE__ */
|
|
3547
|
+
return /* @__PURE__ */ jsx19("blockquote", { className: "squisq-md-blockquote", children: renderBlocks(node.children, key, ctx) }, key);
|
|
3008
3548
|
case "list":
|
|
3009
3549
|
if (node.ordered) {
|
|
3010
|
-
return /* @__PURE__ */
|
|
3550
|
+
return /* @__PURE__ */ jsx19("ol", { className: "squisq-md-list squisq-md-ol", start: node.start ?? void 0, children: node.children.map((item, i) => renderListItem(item, `${key}li${i}`, ctx)) }, key);
|
|
3011
3551
|
}
|
|
3012
|
-
return /* @__PURE__ */
|
|
3552
|
+
return /* @__PURE__ */ jsx19("ul", { className: "squisq-md-list squisq-md-ul", children: node.children.map((item, i) => renderListItem(item, `${key}li${i}`, ctx)) }, key);
|
|
3013
3553
|
case "code":
|
|
3014
|
-
return /* @__PURE__ */
|
|
3554
|
+
return /* @__PURE__ */ jsx19("pre", { className: "squisq-md-code-block", children: /* @__PURE__ */ jsx19("code", { className: node.lang ? `language-${node.lang}` : void 0, children: node.value }) }, key);
|
|
3015
3555
|
case "thematicBreak":
|
|
3016
|
-
return /* @__PURE__ */
|
|
3556
|
+
return /* @__PURE__ */ jsx19("hr", { className: "squisq-md-hr" }, key);
|
|
3017
3557
|
case "table":
|
|
3018
3558
|
return renderTable(node.children, node.align, key, ctx);
|
|
3019
3559
|
case "htmlBlock":
|
|
3020
3560
|
if (ctx.htmlPolicy === "strip") return null;
|
|
3021
|
-
|
|
3022
|
-
return /* @__PURE__ */ jsx18(
|
|
3023
|
-
"div",
|
|
3024
|
-
{
|
|
3025
|
-
className: "squisq-md-html-block",
|
|
3026
|
-
dangerouslySetInnerHTML: { __html: node.rawHtml }
|
|
3027
|
-
},
|
|
3028
|
-
key
|
|
3029
|
-
);
|
|
3030
|
-
}
|
|
3031
|
-
return /* @__PURE__ */ jsx18("div", { className: "squisq-md-html-block", children: renderHtmlNodes(resolveHtmlNodes(node.htmlChildren, ctx.htmlPolicy), `${key}h`) }, key);
|
|
3561
|
+
return /* @__PURE__ */ jsx19("div", { className: "squisq-md-html-block", children: renderHtmlNodes(resolveHtmlNodes(node.htmlChildren, ctx.htmlPolicy), `${key}h`, ctx) }, key);
|
|
3032
3562
|
case "math":
|
|
3033
|
-
return /* @__PURE__ */
|
|
3563
|
+
return /* @__PURE__ */ jsx19("pre", { className: "squisq-md-math-block", children: /* @__PURE__ */ jsx19("code", { children: node.value }) }, key);
|
|
3034
3564
|
case "definition":
|
|
3035
3565
|
return null;
|
|
3036
3566
|
case "footnoteDefinition":
|
|
3037
|
-
return /* @__PURE__ */
|
|
3038
|
-
/* @__PURE__ */
|
|
3567
|
+
return /* @__PURE__ */ jsxs12("div", { className: "squisq-md-footnote-def", id: `fn-${node.identifier}`, children: [
|
|
3568
|
+
/* @__PURE__ */ jsx19("sup", { children: node.label ?? node.identifier }),
|
|
3039
3569
|
renderBlocks(node.children, key, ctx)
|
|
3040
3570
|
] }, key);
|
|
3041
3571
|
case "containerDirective":
|
|
3042
|
-
return /* @__PURE__ */
|
|
3572
|
+
return /* @__PURE__ */ jsxs12(
|
|
3043
3573
|
"div",
|
|
3044
3574
|
{
|
|
3045
3575
|
className: `squisq-md-directive squisq-md-directive-${node.name}`,
|
|
3046
3576
|
"data-directive": node.name,
|
|
3047
3577
|
children: [
|
|
3048
|
-
node.label && /* @__PURE__ */
|
|
3578
|
+
node.label && /* @__PURE__ */ jsx19("div", { className: "squisq-md-directive-label", children: node.label }),
|
|
3049
3579
|
renderBlocks(node.children, key, ctx)
|
|
3050
3580
|
]
|
|
3051
3581
|
},
|
|
3052
3582
|
key
|
|
3053
3583
|
);
|
|
3054
3584
|
case "leafDirective":
|
|
3055
|
-
return /* @__PURE__ */
|
|
3585
|
+
return /* @__PURE__ */ jsx19(
|
|
3056
3586
|
"div",
|
|
3057
3587
|
{
|
|
3058
3588
|
className: `squisq-md-directive squisq-md-directive-${node.name}`,
|
|
@@ -3062,11 +3592,11 @@ function renderBlock(node, key, ctx = DEFAULT_CTX) {
|
|
|
3062
3592
|
key
|
|
3063
3593
|
);
|
|
3064
3594
|
case "definitionList":
|
|
3065
|
-
return /* @__PURE__ */
|
|
3595
|
+
return /* @__PURE__ */ jsx19("dl", { className: "squisq-md-dl", children: node.children.map((child, i) => {
|
|
3066
3596
|
if (child.type === "definitionTerm") {
|
|
3067
|
-
return /* @__PURE__ */
|
|
3597
|
+
return /* @__PURE__ */ jsx19("dt", { className: "squisq-md-dt", children: renderInline(child.children, `${key}dt${i}`, ctx) }, `${key}dt${i}`);
|
|
3068
3598
|
}
|
|
3069
|
-
return /* @__PURE__ */
|
|
3599
|
+
return /* @__PURE__ */ jsx19("dd", { className: "squisq-md-dd", children: renderBlocks(child.children, `${key}dd${i}`, ctx) }, `${key}dd${i}`);
|
|
3070
3600
|
}) }, key);
|
|
3071
3601
|
default:
|
|
3072
3602
|
return null;
|
|
@@ -3074,15 +3604,15 @@ function renderBlock(node, key, ctx = DEFAULT_CTX) {
|
|
|
3074
3604
|
}
|
|
3075
3605
|
function renderListItem(item, key, ctx = DEFAULT_CTX) {
|
|
3076
3606
|
const isTask = item.checked !== null && item.checked !== void 0;
|
|
3077
|
-
return /* @__PURE__ */
|
|
3078
|
-
isTask && /* @__PURE__ */
|
|
3607
|
+
return /* @__PURE__ */ jsxs12("li", { className: `squisq-md-li${isTask ? " squisq-md-task" : ""}`, children: [
|
|
3608
|
+
isTask && /* @__PURE__ */ jsx19("input", { type: "checkbox", checked: !!item.checked, readOnly: true, className: "squisq-md-checkbox" }),
|
|
3079
3609
|
renderBlocks(item.children, key, ctx)
|
|
3080
3610
|
] }, key);
|
|
3081
3611
|
}
|
|
3082
3612
|
function renderTable(rows, align, key, ctx = DEFAULT_CTX) {
|
|
3083
3613
|
const [headerRow, ...bodyRows] = rows;
|
|
3084
|
-
return /* @__PURE__ */
|
|
3085
|
-
headerRow && /* @__PURE__ */
|
|
3614
|
+
return /* @__PURE__ */ jsxs12("table", { className: "squisq-md-table", children: [
|
|
3615
|
+
headerRow && /* @__PURE__ */ jsx19("thead", { children: /* @__PURE__ */ jsx19("tr", { children: headerRow.children.map((cell, ci) => /* @__PURE__ */ jsx19(
|
|
3086
3616
|
"th",
|
|
3087
3617
|
{
|
|
3088
3618
|
className: "squisq-md-th",
|
|
@@ -3091,7 +3621,7 @@ function renderTable(rows, align, key, ctx = DEFAULT_CTX) {
|
|
|
3091
3621
|
},
|
|
3092
3622
|
`${key}th${ci}`
|
|
3093
3623
|
)) }) }),
|
|
3094
|
-
bodyRows.length > 0 && /* @__PURE__ */
|
|
3624
|
+
bodyRows.length > 0 && /* @__PURE__ */ jsx19("tbody", { children: bodyRows.map((row, ri) => /* @__PURE__ */ jsx19("tr", { children: row.children.map((cell, ci) => /* @__PURE__ */ jsx19(
|
|
3095
3625
|
"td",
|
|
3096
3626
|
{
|
|
3097
3627
|
className: "squisq-md-td",
|
|
@@ -3109,22 +3639,13 @@ function MdImage({ src, alt, title }) {
|
|
|
3109
3639
|
const safeSrc = sanitizeUrl(src, "media");
|
|
3110
3640
|
const resolved = useMediaUrl(safeSrc ?? "", ".");
|
|
3111
3641
|
if (!safeSrc) return null;
|
|
3112
|
-
return /* @__PURE__ */
|
|
3642
|
+
return /* @__PURE__ */ jsx19("img", { className: "squisq-md-image", src: resolved, alt, title });
|
|
3113
3643
|
}
|
|
3114
3644
|
function resolveHtmlNodes(nodes, htmlPolicy) {
|
|
3115
3645
|
if (htmlPolicy === "strip") return [];
|
|
3116
3646
|
if (htmlPolicy === "trusted") return nodes;
|
|
3117
3647
|
return sanitizeHtmlNodes2(nodes);
|
|
3118
3648
|
}
|
|
3119
|
-
function containsMediaTag(nodes) {
|
|
3120
|
-
for (const node of nodes) {
|
|
3121
|
-
if (node.type !== "htmlElement") continue;
|
|
3122
|
-
const tagName = node.tagName.toLowerCase();
|
|
3123
|
-
if (tagName === "video" || tagName === "audio") return true;
|
|
3124
|
-
if (containsMediaTag(node.children)) return true;
|
|
3125
|
-
}
|
|
3126
|
-
return false;
|
|
3127
|
-
}
|
|
3128
3649
|
var DANGEROUS_HTML_TAGS = /* @__PURE__ */ new Set([
|
|
3129
3650
|
"base",
|
|
3130
3651
|
"embed",
|
|
@@ -3136,18 +3657,6 @@ var DANGEROUS_HTML_TAGS = /* @__PURE__ */ new Set([
|
|
|
3136
3657
|
"style",
|
|
3137
3658
|
"title"
|
|
3138
3659
|
]);
|
|
3139
|
-
function containsDangerousTag(nodes) {
|
|
3140
|
-
for (const node of nodes) {
|
|
3141
|
-
if (node.type !== "htmlElement") continue;
|
|
3142
|
-
if (DANGEROUS_HTML_TAGS.has(node.tagName.toLowerCase())) return true;
|
|
3143
|
-
if (containsDangerousTag(node.children)) return true;
|
|
3144
|
-
}
|
|
3145
|
-
return false;
|
|
3146
|
-
}
|
|
3147
|
-
var DANGEROUS_RAW_HTML_RE = /<\s*\/?\s*(?:base|embed|iframe|link|meta|object|script|style|title)\b/i;
|
|
3148
|
-
function hasDangerousRawHtml(rawHtml) {
|
|
3149
|
-
return DANGEROUS_RAW_HTML_RE.test(rawHtml);
|
|
3150
|
-
}
|
|
3151
3660
|
var PASSTHROUGH_ATTRS = {
|
|
3152
3661
|
// common
|
|
3153
3662
|
class: "className",
|
|
@@ -3166,7 +3675,7 @@ var PASSTHROUGH_ATTRS = {
|
|
|
3166
3675
|
target: "target",
|
|
3167
3676
|
rel: "rel"
|
|
3168
3677
|
};
|
|
3169
|
-
function reactPropsFromAttrs(attrs) {
|
|
3678
|
+
function reactPropsFromAttrs(attrs, ctx) {
|
|
3170
3679
|
const out = {};
|
|
3171
3680
|
for (const [name, value] of Object.entries(attrs)) {
|
|
3172
3681
|
const propName = PASSTHROUGH_ATTRS[name];
|
|
@@ -3175,21 +3684,33 @@ function reactPropsFromAttrs(attrs) {
|
|
|
3175
3684
|
out["data-style"] = value;
|
|
3176
3685
|
continue;
|
|
3177
3686
|
}
|
|
3687
|
+
if (propName === "href") {
|
|
3688
|
+
const href = sanitizeUrl(value, "link", { extraLinkSchemes: ctx.linkSchemes });
|
|
3689
|
+
if (href) out.href = href;
|
|
3690
|
+
continue;
|
|
3691
|
+
}
|
|
3692
|
+
if (propName === "src") {
|
|
3693
|
+
const src = sanitizeUrl(value, "media");
|
|
3694
|
+
if (src) out.src = src;
|
|
3695
|
+
continue;
|
|
3696
|
+
}
|
|
3178
3697
|
out[propName] = value;
|
|
3179
3698
|
}
|
|
3180
3699
|
return out;
|
|
3181
3700
|
}
|
|
3182
|
-
function renderHtmlElement(el, key) {
|
|
3701
|
+
function renderHtmlElement(el, key, ctx) {
|
|
3183
3702
|
const tagName = el.tagName.toLowerCase();
|
|
3184
3703
|
if (DANGEROUS_HTML_TAGS.has(tagName)) return null;
|
|
3185
3704
|
if (tagName === "video") {
|
|
3186
|
-
|
|
3705
|
+
const src = sanitizeUrl(el.attributes.src ?? "", "media") ?? "";
|
|
3706
|
+
const poster = sanitizeUrl(el.attributes.poster ?? "", "media") ?? void 0;
|
|
3707
|
+
return /* @__PURE__ */ jsx19(
|
|
3187
3708
|
InlineVideoPlayer,
|
|
3188
3709
|
{
|
|
3189
|
-
src
|
|
3710
|
+
src,
|
|
3190
3711
|
width: el.attributes.width,
|
|
3191
3712
|
height: el.attributes.height,
|
|
3192
|
-
poster
|
|
3713
|
+
poster,
|
|
3193
3714
|
controls: "controls" in el.attributes,
|
|
3194
3715
|
preload: el.attributes.preload === "none" || el.attributes.preload === "metadata" || el.attributes.preload === "auto" ? el.attributes.preload : void 0
|
|
3195
3716
|
},
|
|
@@ -3197,10 +3718,11 @@ function renderHtmlElement(el, key) {
|
|
|
3197
3718
|
);
|
|
3198
3719
|
}
|
|
3199
3720
|
if (tagName === "audio") {
|
|
3200
|
-
|
|
3721
|
+
const src = sanitizeUrl(el.attributes.src ?? "", "media") ?? "";
|
|
3722
|
+
return /* @__PURE__ */ jsx19(
|
|
3201
3723
|
InlineAudioPlayer,
|
|
3202
3724
|
{
|
|
3203
|
-
src
|
|
3725
|
+
src,
|
|
3204
3726
|
controls: "controls" in el.attributes,
|
|
3205
3727
|
preload: el.attributes.preload === "none" || el.attributes.preload === "metadata" || el.attributes.preload === "auto" ? el.attributes.preload : void 0
|
|
3206
3728
|
},
|
|
@@ -3208,20 +3730,20 @@ function renderHtmlElement(el, key) {
|
|
|
3208
3730
|
);
|
|
3209
3731
|
}
|
|
3210
3732
|
const Tag = tagName;
|
|
3211
|
-
const props = reactPropsFromAttrs(el.attributes);
|
|
3733
|
+
const props = reactPropsFromAttrs(el.attributes, ctx);
|
|
3212
3734
|
if (el.selfClosing) {
|
|
3213
|
-
return /* @__PURE__ */
|
|
3735
|
+
return /* @__PURE__ */ jsx19(Tag, { ...props }, key);
|
|
3214
3736
|
}
|
|
3215
|
-
return /* @__PURE__ */
|
|
3737
|
+
return /* @__PURE__ */ jsx19(Tag, { ...props, children: renderHtmlNodes(el.children, `${key}c`, ctx) }, key);
|
|
3216
3738
|
}
|
|
3217
|
-
function renderHtmlNodes(nodes, keyPrefix) {
|
|
3739
|
+
function renderHtmlNodes(nodes, keyPrefix, ctx = DEFAULT_CTX) {
|
|
3218
3740
|
return nodes.map((node, i) => {
|
|
3219
3741
|
const key = `${keyPrefix}${i}`;
|
|
3220
3742
|
switch (node.type) {
|
|
3221
3743
|
case "htmlElement":
|
|
3222
|
-
return renderHtmlElement(node, key);
|
|
3744
|
+
return renderHtmlElement(node, key, ctx);
|
|
3223
3745
|
case "htmlText":
|
|
3224
|
-
return /* @__PURE__ */
|
|
3746
|
+
return /* @__PURE__ */ jsx19(Fragment2, { children: node.value }, key);
|
|
3225
3747
|
case "htmlComment":
|
|
3226
3748
|
return null;
|
|
3227
3749
|
default:
|
|
@@ -3236,25 +3758,16 @@ function MarkdownRenderer({
|
|
|
3236
3758
|
linkSchemes
|
|
3237
3759
|
}) {
|
|
3238
3760
|
if (!nodes || nodes.length === 0) return null;
|
|
3239
|
-
return /* @__PURE__ */
|
|
3761
|
+
return /* @__PURE__ */ jsx19("div", { className: `squisq-md ${className || ""}`, children: renderBlocks(nodes, "", { htmlPolicy, linkSchemes }) });
|
|
3240
3762
|
}
|
|
3241
3763
|
|
|
3242
3764
|
// src/LinearDocView.tsx
|
|
3243
|
-
import { jsx as
|
|
3244
|
-
var warnedUnknownTemplates = /* @__PURE__ */ new Set();
|
|
3765
|
+
import { jsx as jsx20, jsxs as jsxs13 } from "react/jsx-runtime";
|
|
3245
3766
|
function isAnnotatedBlock(block) {
|
|
3246
|
-
|
|
3247
|
-
|
|
3248
|
-
|
|
3249
|
-
|
|
3250
|
-
warnedUnknownTemplates.add(annotation.template);
|
|
3251
|
-
console.warn(
|
|
3252
|
-
`[squisq] Unknown template "${annotation.template}" \u2014 rendering the block as plain markdown.`
|
|
3253
|
-
);
|
|
3254
|
-
}
|
|
3255
|
-
return false;
|
|
3256
|
-
}
|
|
3257
|
-
return true;
|
|
3767
|
+
return !!block.sourceHeading?.templateAnnotation?.template || !block.sourceHeading && isTemplateBlock2(block);
|
|
3768
|
+
}
|
|
3769
|
+
function visualTemplateName(block) {
|
|
3770
|
+
return block.sourceHeading?.templateAnnotation?.template ?? block.template;
|
|
3258
3771
|
}
|
|
3259
3772
|
function countAll(blocks) {
|
|
3260
3773
|
let count = 0;
|
|
@@ -3264,50 +3777,65 @@ function countAll(blocks) {
|
|
|
3264
3777
|
}
|
|
3265
3778
|
return count;
|
|
3266
3779
|
}
|
|
3267
|
-
function BlockSection({
|
|
3780
|
+
function BlockSection({
|
|
3781
|
+
block,
|
|
3782
|
+
basePath,
|
|
3783
|
+
viewport,
|
|
3784
|
+
renderContext,
|
|
3785
|
+
blockIndex,
|
|
3786
|
+
blockIndices,
|
|
3787
|
+
animationsEnabled
|
|
3788
|
+
}) {
|
|
3268
3789
|
const isAnnotated = isAnnotatedBlock(block);
|
|
3269
3790
|
const visualBlock = useMemo8(() => {
|
|
3270
3791
|
if (!isAnnotated) return null;
|
|
3271
|
-
const annotation = block.sourceHeading
|
|
3272
|
-
const
|
|
3273
|
-
const templateBlock = {
|
|
3274
|
-
|
|
3275
|
-
|
|
3276
|
-
|
|
3277
|
-
|
|
3278
|
-
|
|
3279
|
-
|
|
3280
|
-
|
|
3281
|
-
|
|
3282
|
-
|
|
3283
|
-
block.
|
|
3284
|
-
{
|
|
3792
|
+
const annotation = block.sourceHeading?.templateAnnotation;
|
|
3793
|
+
const templateName = visualTemplateName(block) ?? "sectionHeader";
|
|
3794
|
+
const templateBlock = annotation ? (() => {
|
|
3795
|
+
const headingText = extractPlainText(block.sourceHeading);
|
|
3796
|
+
return {
|
|
3797
|
+
id: block.id,
|
|
3798
|
+
template: templateName,
|
|
3799
|
+
startTime: 0,
|
|
3800
|
+
duration: 1,
|
|
3801
|
+
audioSegment: 0,
|
|
3802
|
+
title: headingText,
|
|
3803
|
+
contents: block.contents,
|
|
3804
|
+
children: block.children,
|
|
3805
|
+
...deriveTemplateInputs(templateName, headingText, block.contents, {
|
|
3285
3806
|
placeholders: true
|
|
3286
|
-
}
|
|
3287
|
-
|
|
3288
|
-
|
|
3289
|
-
|
|
3807
|
+
}) ?? {},
|
|
3808
|
+
...annotation.params,
|
|
3809
|
+
...block.templateOverrides
|
|
3810
|
+
};
|
|
3811
|
+
})() : {
|
|
3812
|
+
...block,
|
|
3813
|
+
startTime: block.startTime ?? 0,
|
|
3814
|
+
duration: block.duration ?? 1,
|
|
3815
|
+
audioSegment: block.audioSegment ?? 0,
|
|
3816
|
+
template: templateName
|
|
3290
3817
|
};
|
|
3291
3818
|
const ctx = {
|
|
3292
3819
|
...renderContext,
|
|
3293
3820
|
blockIndex
|
|
3294
3821
|
};
|
|
3295
|
-
const layers =
|
|
3822
|
+
const { layers } = materializeBlockLayers(templateBlock, ctx);
|
|
3296
3823
|
return {
|
|
3297
3824
|
...block,
|
|
3298
3825
|
layers,
|
|
3299
|
-
template:
|
|
3826
|
+
template: templateName
|
|
3300
3827
|
};
|
|
3301
3828
|
}, [block, isAnnotated, renderContext, blockIndex]);
|
|
3302
|
-
return /* @__PURE__ */
|
|
3829
|
+
return /* @__PURE__ */ jsxs13(
|
|
3303
3830
|
"div",
|
|
3304
3831
|
{
|
|
3305
3832
|
className: "squisq-linear-section",
|
|
3306
3833
|
"data-block-id": block.id,
|
|
3307
|
-
"data-
|
|
3834
|
+
"data-block-index": blockIndex,
|
|
3835
|
+
"data-template": isAnnotated ? visualTemplateName(block) : void 0,
|
|
3308
3836
|
children: [
|
|
3309
|
-
block.sourceHeading && !isAnnotated && /* @__PURE__ */
|
|
3310
|
-
isAnnotated && visualBlock && /* @__PURE__ */
|
|
3837
|
+
block.sourceHeading && !isAnnotated && /* @__PURE__ */ jsx20(MarkdownRenderer, { nodes: [block.sourceHeading] }),
|
|
3838
|
+
isAnnotated && visualBlock && /* @__PURE__ */ jsx20("div", { className: "squisq-linear-card", children: /* @__PURE__ */ jsx20(
|
|
3311
3839
|
"div",
|
|
3312
3840
|
{
|
|
3313
3841
|
className: "squisq-linear-card-svg",
|
|
@@ -3317,26 +3845,29 @@ function BlockSection({ block, basePath, viewport, renderContext, blockIndex })
|
|
|
3317
3845
|
overflow: "hidden",
|
|
3318
3846
|
marginBottom: "1em"
|
|
3319
3847
|
},
|
|
3320
|
-
children: /* @__PURE__ */
|
|
3848
|
+
children: /* @__PURE__ */ jsx20(
|
|
3321
3849
|
BlockRenderer,
|
|
3322
3850
|
{
|
|
3323
3851
|
block: visualBlock,
|
|
3324
3852
|
blockTime: 0,
|
|
3325
3853
|
basePath,
|
|
3326
|
-
viewport
|
|
3854
|
+
viewport,
|
|
3855
|
+
animationsEnabled
|
|
3327
3856
|
}
|
|
3328
3857
|
)
|
|
3329
3858
|
}
|
|
3330
3859
|
) }),
|
|
3331
|
-
!isAnnotated && block.contents && block.contents.length > 0 && /* @__PURE__ */
|
|
3332
|
-
block.children && block.children.length > 0 && /* @__PURE__ */
|
|
3860
|
+
!isAnnotated && block.contents && block.contents.length > 0 && /* @__PURE__ */ jsx20(MarkdownRenderer, { nodes: block.contents }),
|
|
3861
|
+
block.children && block.children.length > 0 && /* @__PURE__ */ jsx20("div", { className: "squisq-linear-children", children: block.children.map((child, i) => /* @__PURE__ */ jsx20(
|
|
3333
3862
|
BlockSection,
|
|
3334
3863
|
{
|
|
3335
3864
|
block: child,
|
|
3336
3865
|
basePath,
|
|
3337
3866
|
viewport,
|
|
3338
3867
|
renderContext,
|
|
3339
|
-
blockIndex: blockIndex + i + 1
|
|
3868
|
+
blockIndex: blockIndices.get(child) ?? blockIndex + i + 1,
|
|
3869
|
+
blockIndices,
|
|
3870
|
+
animationsEnabled
|
|
3340
3871
|
},
|
|
3341
3872
|
child.id
|
|
3342
3873
|
)) })
|
|
@@ -3352,9 +3883,12 @@ function LinearDocView({
|
|
|
3352
3883
|
className,
|
|
3353
3884
|
theme,
|
|
3354
3885
|
surface,
|
|
3886
|
+
animationsEnabled = true,
|
|
3355
3887
|
thinMargins = false,
|
|
3356
|
-
imageDisplayMode = "inline"
|
|
3888
|
+
imageDisplayMode = "inline",
|
|
3889
|
+
globalKeyboardShortcuts = false
|
|
3357
3890
|
}) {
|
|
3891
|
+
const scrollRef = useRef8(null);
|
|
3358
3892
|
const activeViewport = viewport ?? VIEWPORT_PRESETS3.landscape;
|
|
3359
3893
|
const markdownDoc = useMemo8(
|
|
3360
3894
|
() => !doc && markdown !== void 0 ? markdownToDoc(parseMarkdown(markdown)) : void 0,
|
|
@@ -3365,6 +3899,18 @@ function LinearDocView({
|
|
|
3365
3899
|
() => resolvedDoc ? countAll(resolvedDoc.blocks) : 0,
|
|
3366
3900
|
[resolvedDoc]
|
|
3367
3901
|
);
|
|
3902
|
+
const blockIndices = useMemo8(() => {
|
|
3903
|
+
const indices = /* @__PURE__ */ new Map();
|
|
3904
|
+
let index = 0;
|
|
3905
|
+
const visit = (blocks) => {
|
|
3906
|
+
for (const block of blocks) {
|
|
3907
|
+
indices.set(block, index++);
|
|
3908
|
+
if (block.children) visit(block.children);
|
|
3909
|
+
}
|
|
3910
|
+
};
|
|
3911
|
+
if (resolvedDoc) visit(resolvedDoc.blocks);
|
|
3912
|
+
return indices;
|
|
3913
|
+
}, [resolvedDoc]);
|
|
3368
3914
|
const autoSurface = useAutoSurface(surface === "auto");
|
|
3369
3915
|
const resolvedSurface = surface === "auto" ? autoSurface : surface;
|
|
3370
3916
|
const renderContext = useMemo8(() => {
|
|
@@ -3376,12 +3922,37 @@ function LinearDocView({
|
|
|
3376
3922
|
totalBlocks,
|
|
3377
3923
|
// Theme atmosphere (vignette/grain/gradient persistent layers) shows
|
|
3378
3924
|
// on the inline template cards so they match the player's look.
|
|
3379
|
-
persistentLayers: effectiveTheme.persistentLayers
|
|
3925
|
+
persistentLayers: effectiveTheme.persistentLayers,
|
|
3926
|
+
customTemplates: resolvedDoc?.customTemplates
|
|
3380
3927
|
};
|
|
3381
|
-
}, [activeViewport, totalBlocks, theme, resolvedSurface]);
|
|
3928
|
+
}, [activeViewport, resolvedDoc?.customTemplates, totalBlocks, theme, resolvedSurface]);
|
|
3382
3929
|
const activeTheme = renderContext.theme;
|
|
3930
|
+
useEffect9(() => {
|
|
3931
|
+
if (!globalKeyboardShortcuts) return;
|
|
3932
|
+
const handleKeyDown = (event) => {
|
|
3933
|
+
if (event.defaultPrevented || event.altKey || event.ctrlKey || event.metaKey || event.shiftKey || event.key !== "ArrowDown" && event.key !== "ArrowUp") {
|
|
3934
|
+
return;
|
|
3935
|
+
}
|
|
3936
|
+
const target = event.target instanceof Element ? event.target : null;
|
|
3937
|
+
if (target?.closest(
|
|
3938
|
+
'input, textarea, select, [contenteditable]:not([contenteditable="false"]), [role="textbox"], [role="combobox"], [role="listbox"], [role="menu"], [role="dialog"], [aria-modal="true"], .monaco-editor'
|
|
3939
|
+
)) {
|
|
3940
|
+
return;
|
|
3941
|
+
}
|
|
3942
|
+
const scroller = scrollRef.current;
|
|
3943
|
+
if (!scroller) return;
|
|
3944
|
+
event.preventDefault();
|
|
3945
|
+
const distance = Math.max(64, Math.round(scroller.clientHeight * 0.12));
|
|
3946
|
+
scroller.scrollBy({
|
|
3947
|
+
top: event.key === "ArrowDown" ? distance : -distance,
|
|
3948
|
+
behavior: "smooth"
|
|
3949
|
+
});
|
|
3950
|
+
};
|
|
3951
|
+
document.addEventListener("keydown", handleKeyDown);
|
|
3952
|
+
return () => document.removeEventListener("keydown", handleKeyDown);
|
|
3953
|
+
}, [globalKeyboardShortcuts]);
|
|
3383
3954
|
if (!resolvedDoc) {
|
|
3384
|
-
return /* @__PURE__ */
|
|
3955
|
+
return /* @__PURE__ */ jsx20("div", { ref: scrollRef, className: `squisq-linear squisq-linear--empty ${className || ""}` });
|
|
3385
3956
|
}
|
|
3386
3957
|
const bgColor = activeTheme.colors.background;
|
|
3387
3958
|
const textColor = activeTheme.colors.text;
|
|
@@ -3390,9 +3961,10 @@ function LinearDocView({
|
|
|
3390
3961
|
const bodyFont = resolveFontFamily2(activeTheme.typography.bodyFont, "system-ui, sans-serif");
|
|
3391
3962
|
const titleFont = resolveFontFamily2(activeTheme.typography.titleFont, "Georgia, serif");
|
|
3392
3963
|
const lineHt = activeTheme.typography.lineHeight ?? 1.7;
|
|
3393
|
-
return /* @__PURE__ */
|
|
3964
|
+
return /* @__PURE__ */ jsx20(
|
|
3394
3965
|
"div",
|
|
3395
3966
|
{
|
|
3967
|
+
ref: scrollRef,
|
|
3396
3968
|
className: `squisq-linear ${className || ""}`,
|
|
3397
3969
|
style: {
|
|
3398
3970
|
width: "100%",
|
|
@@ -3406,7 +3978,7 @@ function LinearDocView({
|
|
|
3406
3978
|
overflowX: "hidden",
|
|
3407
3979
|
background: bgColor
|
|
3408
3980
|
},
|
|
3409
|
-
children: /* @__PURE__ */
|
|
3981
|
+
children: /* @__PURE__ */ jsxs13(
|
|
3410
3982
|
"div",
|
|
3411
3983
|
{
|
|
3412
3984
|
className: `squisq-linear-content squisq-md${thinMargins ? " squisq-linear-content--thin" : ""}${imageDisplayMode === "thumbnail" ? " squisq-linear-content--thumbnail-images" : ""}`,
|
|
@@ -3431,7 +4003,7 @@ function LinearDocView({
|
|
|
3431
4003
|
"--squisq-linear-bg": bgColor
|
|
3432
4004
|
},
|
|
3433
4005
|
children: [
|
|
3434
|
-
/* @__PURE__ */
|
|
4006
|
+
/* @__PURE__ */ jsx20("style", { children: `
|
|
3435
4007
|
.squisq-linear-content h1,
|
|
3436
4008
|
.squisq-linear-content h2,
|
|
3437
4009
|
.squisq-linear-content h3,
|
|
@@ -3449,6 +4021,9 @@ function LinearDocView({
|
|
|
3449
4021
|
.squisq-linear-content p {
|
|
3450
4022
|
margin-bottom: 0.75em;
|
|
3451
4023
|
}
|
|
4024
|
+
.squisq-linear-content p + p {
|
|
4025
|
+
margin-top: 1.25em;
|
|
4026
|
+
}
|
|
3452
4027
|
.squisq-linear-content ul,
|
|
3453
4028
|
.squisq-linear-content ol {
|
|
3454
4029
|
padding-left: 2em;
|
|
@@ -3543,14 +4118,16 @@ function LinearDocView({
|
|
|
3543
4118
|
background: color-mix(in srgb, var(--squisq-linear-primary) 8%, transparent);
|
|
3544
4119
|
}
|
|
3545
4120
|
` }),
|
|
3546
|
-
resolvedDoc.blocks.map((block, i) => /* @__PURE__ */
|
|
4121
|
+
resolvedDoc.blocks.map((block, i) => /* @__PURE__ */ jsx20(
|
|
3547
4122
|
BlockSection,
|
|
3548
4123
|
{
|
|
3549
4124
|
block,
|
|
3550
4125
|
basePath,
|
|
3551
4126
|
viewport: activeViewport,
|
|
3552
4127
|
renderContext,
|
|
3553
|
-
blockIndex: i
|
|
4128
|
+
blockIndex: blockIndices.get(block) ?? i,
|
|
4129
|
+
blockIndices,
|
|
4130
|
+
animationsEnabled
|
|
3554
4131
|
},
|
|
3555
4132
|
block.id
|
|
3556
4133
|
))
|
|
@@ -3562,7 +4139,7 @@ function LinearDocView({
|
|
|
3562
4139
|
}
|
|
3563
4140
|
|
|
3564
4141
|
// src/DocPlayer.tsx
|
|
3565
|
-
import { jsx as
|
|
4142
|
+
import { jsx as jsx21, jsxs as jsxs14 } from "react/jsx-runtime";
|
|
3566
4143
|
var SMALL_WORDS = /* @__PURE__ */ new Set([
|
|
3567
4144
|
"a",
|
|
3568
4145
|
"an",
|
|
@@ -3583,7 +4160,7 @@ var SMALL_WORDS = /* @__PURE__ */ new Set([
|
|
|
3583
4160
|
function buildSegmentTitleMap(doc) {
|
|
3584
4161
|
const map = /* @__PURE__ */ new Map();
|
|
3585
4162
|
for (const block of doc.blocks) {
|
|
3586
|
-
if (
|
|
4163
|
+
if (isTemplateBlock3(block) && block.template === "sectionHeader" && "title" in block) {
|
|
3587
4164
|
const segIdx = block.audioSegment;
|
|
3588
4165
|
if (!map.has(segIdx)) {
|
|
3589
4166
|
map.set(segIdx, block.title);
|
|
@@ -3624,14 +4201,15 @@ function DocPlayer(props) {
|
|
|
3624
4201
|
);
|
|
3625
4202
|
const resolvedDoc = doc ?? markdownDoc;
|
|
3626
4203
|
if (!resolvedDoc) {
|
|
3627
|
-
return /* @__PURE__ */
|
|
4204
|
+
return /* @__PURE__ */ jsx21("div", { className: "doc-player doc-player--empty" });
|
|
3628
4205
|
}
|
|
3629
|
-
return /* @__PURE__ */
|
|
4206
|
+
return /* @__PURE__ */ jsx21(DocPlayerContent, { ...props, doc: resolvedDoc });
|
|
3630
4207
|
}
|
|
3631
4208
|
function DocPlayerContent({
|
|
3632
4209
|
doc,
|
|
3633
4210
|
basePath = ".",
|
|
3634
4211
|
renderMode = false,
|
|
4212
|
+
animationsEnabled = true,
|
|
3635
4213
|
autoPlay = false,
|
|
3636
4214
|
onEnded,
|
|
3637
4215
|
onTimeUpdate,
|
|
@@ -3643,23 +4221,27 @@ function DocPlayerContent({
|
|
|
3643
4221
|
onCaptionsToggle,
|
|
3644
4222
|
onPlaybackStateChange,
|
|
3645
4223
|
onControlsReady,
|
|
4224
|
+
onRenderAPIReady,
|
|
3646
4225
|
isFullscreen = false,
|
|
3647
4226
|
onFullscreenToggle,
|
|
3648
4227
|
onBlockMarkers,
|
|
3649
4228
|
forceViewport,
|
|
3650
4229
|
displayMode = "video",
|
|
3651
4230
|
showCoverSlide = true,
|
|
4231
|
+
coverVisible,
|
|
3652
4232
|
theme,
|
|
3653
4233
|
surface,
|
|
3654
4234
|
captionStyle = "standard",
|
|
3655
|
-
enableSwipe = true
|
|
4235
|
+
enableSwipe = true,
|
|
4236
|
+
globalKeyboardShortcuts = false
|
|
3656
4237
|
}) {
|
|
3657
4238
|
const isSlideshowMode = displayMode === "slideshow";
|
|
3658
4239
|
const isLinearMode = displayMode === "linear";
|
|
3659
|
-
const audioRef =
|
|
3660
|
-
const containerRef =
|
|
3661
|
-
const
|
|
3662
|
-
const
|
|
4240
|
+
const audioRef = useRef9(null);
|
|
4241
|
+
const containerRef = useRef9(null);
|
|
4242
|
+
const playerId = `squisq-player-${useId7().replace(/:/g, "")}`;
|
|
4243
|
+
const [tapFeedback, setTapFeedback] = useState9(null);
|
|
4244
|
+
const tapFeedbackTimer = useRef9();
|
|
3663
4245
|
const { viewport, orientation } = useViewportOrientation();
|
|
3664
4246
|
const activeViewport = forceViewport || (renderMode ? VIEWPORT_PRESETS4.landscape : viewport);
|
|
3665
4247
|
const isDebugMode = useMemo9(() => {
|
|
@@ -3667,9 +4249,9 @@ function DocPlayerContent({
|
|
|
3667
4249
|
const params = new URLSearchParams(window.location.search);
|
|
3668
4250
|
return params.get("debug") === "true";
|
|
3669
4251
|
}, []);
|
|
3670
|
-
const internalAudio = useAudioSync(audioRef, doc.audio, basePath);
|
|
4252
|
+
const internalAudio = useAudioSync(audioRef, doc.audio, basePath, !externalAudioController);
|
|
3671
4253
|
const audio = externalAudioController || internalAudio;
|
|
3672
|
-
|
|
4254
|
+
useEffect10(() => {
|
|
3673
4255
|
if (warnedMissingStyles || !isDevEnvironment()) return;
|
|
3674
4256
|
const el = containerRef.current;
|
|
3675
4257
|
if (!el || typeof getComputedStyle !== "function") return;
|
|
@@ -3698,21 +4280,28 @@ function DocPlayerContent({
|
|
|
3698
4280
|
restart
|
|
3699
4281
|
} = audio;
|
|
3700
4282
|
const mediaSchedule = useMemo9(() => resolveMediaSchedule(doc), [doc]);
|
|
3701
|
-
const currentTimeRef =
|
|
4283
|
+
const currentTimeRef = useRef9(currentTime);
|
|
3702
4284
|
currentTimeRef.current = currentTime;
|
|
3703
|
-
const totalDurationRef =
|
|
4285
|
+
const totalDurationRef = useRef9(totalDuration);
|
|
3704
4286
|
totalDurationRef.current = totalDuration;
|
|
3705
|
-
const expandedBlocksLenRef =
|
|
3706
|
-
const handleContainerClick =
|
|
4287
|
+
const expandedBlocksLenRef = useRef9(0);
|
|
4288
|
+
const handleContainerClick = useCallback7(
|
|
3707
4289
|
(e) => {
|
|
3708
|
-
if (renderMode ||
|
|
4290
|
+
if (renderMode || isLinearMode) return;
|
|
3709
4291
|
const target = e.target;
|
|
4292
|
+
if (isSlideshowMode) {
|
|
4293
|
+
if (!target.closest('button, a, input, textarea, select, [contenteditable="true"]')) {
|
|
4294
|
+
containerRef.current?.focus({ preventScroll: true });
|
|
4295
|
+
}
|
|
4296
|
+
return;
|
|
4297
|
+
}
|
|
3710
4298
|
if (target.closest(
|
|
3711
|
-
|
|
4299
|
+
'button, a, input, textarea, select, [contenteditable="true"], .doc-player__controls, .doc-player__scrubber, .doc-controls-sidebar, .doc-controls-slideshow'
|
|
3712
4300
|
))
|
|
3713
4301
|
return;
|
|
4302
|
+
containerRef.current?.focus({ preventScroll: true });
|
|
3714
4303
|
toggle();
|
|
3715
|
-
const nextState = isPlaying ? "
|
|
4304
|
+
const nextState = isPlaying ? "pause" : "play";
|
|
3716
4305
|
setTapFeedback(nextState);
|
|
3717
4306
|
clearTimeout(tapFeedbackTimer.current);
|
|
3718
4307
|
tapFeedbackTimer.current = setTimeout(() => setTapFeedback(null), 600);
|
|
@@ -3736,8 +4325,13 @@ function DocPlayerContent({
|
|
|
3736
4325
|
docProgress,
|
|
3737
4326
|
nextBlock: _nextBlock,
|
|
3738
4327
|
prevBlock: _prevBlock,
|
|
3739
|
-
blocks: expandedBlocks
|
|
3740
|
-
|
|
4328
|
+
blocks: expandedBlocks,
|
|
4329
|
+
suppressOutgoingForNextBlock
|
|
4330
|
+
} = useDocPlayback(doc, currentTime, {
|
|
4331
|
+
viewport: activeViewport,
|
|
4332
|
+
theme: effectiveTheme,
|
|
4333
|
+
onSeek: seekTo
|
|
4334
|
+
});
|
|
3741
4335
|
const coverBlock = useMemo9(() => {
|
|
3742
4336
|
const startBlockConfig = doc.startBlock;
|
|
3743
4337
|
if (!showCoverSlide) return null;
|
|
@@ -3755,9 +4349,13 @@ function DocPlayerContent({
|
|
|
3755
4349
|
};
|
|
3756
4350
|
}, [doc.startBlock, activeViewport, effectiveTheme, showCoverSlide]);
|
|
3757
4351
|
const hasManagedCover = !!coverBlock;
|
|
3758
|
-
const [slideshowCoverVisible, setSlideshowCoverVisible] =
|
|
3759
|
-
const
|
|
3760
|
-
|
|
4352
|
+
const [slideshowCoverVisible, setSlideshowCoverVisible] = useState9(false);
|
|
4353
|
+
const [isSlideshowPickerOpen, setIsSlideshowPickerOpen] = useState9(false);
|
|
4354
|
+
const slideshowCoverInitKeyRef = useRef9("");
|
|
4355
|
+
useEffect10(() => {
|
|
4356
|
+
slideshowCoverInitKeyRef.current = "";
|
|
4357
|
+
}, [doc]);
|
|
4358
|
+
useEffect10(() => {
|
|
3761
4359
|
const initKey = `${isSlideshowMode}:${hasManagedCover}:${renderMode}`;
|
|
3762
4360
|
if (slideshowCoverInitKeyRef.current === initKey) return;
|
|
3763
4361
|
slideshowCoverInitKeyRef.current = initKey;
|
|
@@ -3768,14 +4366,21 @@ function DocPlayerContent({
|
|
|
3768
4366
|
setSlideshowCoverVisible(false);
|
|
3769
4367
|
}
|
|
3770
4368
|
}, [isSlideshowMode, hasManagedCover, renderMode, pause]);
|
|
3771
|
-
const [coverForced, setCoverForced] =
|
|
3772
|
-
const [coverGraceActive, setCoverGraceActive] =
|
|
3773
|
-
const coverGraceTimer =
|
|
3774
|
-
const coverWasShowing =
|
|
3775
|
-
const hasPlayedOnce =
|
|
4369
|
+
const [coverForced, setCoverForced] = useState9(false);
|
|
4370
|
+
const [coverGraceActive, setCoverGraceActive] = useState9(false);
|
|
4371
|
+
const coverGraceTimer = useRef9();
|
|
4372
|
+
const coverWasShowing = useRef9(false);
|
|
4373
|
+
const hasPlayedOnce = useRef9(false);
|
|
4374
|
+
useEffect10(() => {
|
|
4375
|
+
hasPlayedOnce.current = false;
|
|
4376
|
+
coverWasShowing.current = false;
|
|
4377
|
+
clearTimeout(coverGraceTimer.current);
|
|
4378
|
+
setCoverGraceActive(false);
|
|
4379
|
+
setCoverForced(false);
|
|
4380
|
+
}, [doc]);
|
|
3776
4381
|
const atRest = !!(coverBlock && !isSlideshowMode && !isPlaying && currentTime === 0 && !hasPlayedOnce.current && !renderMode && !autoPlay);
|
|
3777
4382
|
if (atRest) coverWasShowing.current = true;
|
|
3778
|
-
|
|
4383
|
+
useEffect10(() => {
|
|
3779
4384
|
if (isPlaying && coverWasShowing.current && coverBlock && !renderMode && !isSlideshowMode) {
|
|
3780
4385
|
coverWasShowing.current = false;
|
|
3781
4386
|
hasPlayedOnce.current = true;
|
|
@@ -3783,88 +4388,96 @@ function DocPlayerContent({
|
|
|
3783
4388
|
coverGraceTimer.current = setTimeout(() => setCoverGraceActive(false), 3e3);
|
|
3784
4389
|
}
|
|
3785
4390
|
}, [isPlaying, coverBlock, renderMode, isSlideshowMode]);
|
|
3786
|
-
|
|
4391
|
+
useEffect10(() => () => clearTimeout(coverGraceTimer.current), []);
|
|
3787
4392
|
const showVideoCoverBlock = !isSlideshowMode && !isLinearMode && !!coverBlock && (coverForced || coverGraceActive || !isPlaying && currentTime === 0 && !hasPlayedOnce.current && !renderMode && !autoPlay);
|
|
3788
|
-
const
|
|
3789
|
-
const
|
|
4393
|
+
const effectiveSlideshowCoverVisible = coverVisible ?? slideshowCoverVisible;
|
|
4394
|
+
const showSlideshowCover = !!(isSlideshowMode && !isLinearMode && !renderMode && coverBlock && effectiveSlideshowCoverVisible);
|
|
4395
|
+
const showCoverBlock = coverVisible === void 0 ? showVideoCoverBlock || showSlideshowCover : !!coverBlock && coverVisible;
|
|
3790
4396
|
const slideshowHasCover = !!(isSlideshowMode && !renderMode && coverBlock);
|
|
3791
|
-
const slideshowSlideIndex = slideshowHasCover ?
|
|
4397
|
+
const slideshowSlideIndex = slideshowHasCover ? effectiveSlideshowCoverVisible ? 0 : currentBlockIndex + 1 : currentBlockIndex;
|
|
3792
4398
|
const slideshowTotalSlides = slideshowHasCover ? expandedBlocks.length + 1 : expandedBlocks.length;
|
|
3793
|
-
const hasAutoPlayed =
|
|
3794
|
-
|
|
4399
|
+
const hasAutoPlayed = useRef9(false);
|
|
4400
|
+
useEffect10(() => {
|
|
4401
|
+
hasAutoPlayed.current = false;
|
|
4402
|
+
}, [doc]);
|
|
4403
|
+
useEffect10(() => {
|
|
3795
4404
|
if (isAudioReady && autoPlay && !hasAutoPlayed.current) {
|
|
3796
4405
|
hasAutoPlayed.current = true;
|
|
3797
4406
|
play();
|
|
3798
4407
|
}
|
|
3799
4408
|
}, [isAudioReady, autoPlay, play]);
|
|
3800
|
-
|
|
4409
|
+
useEffect10(() => {
|
|
3801
4410
|
onTimeUpdate?.(currentTime);
|
|
3802
4411
|
}, [currentTime, onTimeUpdate]);
|
|
3803
|
-
|
|
4412
|
+
useEffect10(() => {
|
|
3804
4413
|
if (isEnded) {
|
|
3805
4414
|
onEnded?.();
|
|
3806
4415
|
}
|
|
3807
4416
|
}, [isEnded, onEnded]);
|
|
3808
|
-
|
|
3809
|
-
|
|
3810
|
-
|
|
3811
|
-
|
|
3812
|
-
|
|
3813
|
-
|
|
3814
|
-
|
|
3815
|
-
|
|
3816
|
-
|
|
3817
|
-
|
|
3818
|
-
|
|
3819
|
-
|
|
3820
|
-
|
|
4417
|
+
const liveRenderAPIRef = useRef9(null);
|
|
4418
|
+
const stableRenderAPIRef = useRef9(null);
|
|
4419
|
+
if (!stableRenderAPIRef.current) {
|
|
4420
|
+
const current = () => {
|
|
4421
|
+
const api = liveRenderAPIRef.current;
|
|
4422
|
+
if (!api) throw new Error("Squisq render API is not currently available.");
|
|
4423
|
+
return api;
|
|
4424
|
+
};
|
|
4425
|
+
stableRenderAPIRef.current = {
|
|
4426
|
+
seekTo: (time) => current().seekTo(time),
|
|
4427
|
+
getDuration: () => current().getDuration(),
|
|
4428
|
+
getBlocks: () => current().getBlocks(),
|
|
4429
|
+
getAudioSegments: () => current().getAudioSegments(),
|
|
4430
|
+
getCaptions: () => current().getCaptions(),
|
|
4431
|
+
getChapters: () => current().getChapters(),
|
|
4432
|
+
showCover: () => current().showCover(),
|
|
4433
|
+
hideCover: () => current().hideCover(),
|
|
4434
|
+
hasCoverBlock: () => current().hasCoverBlock()
|
|
4435
|
+
};
|
|
4436
|
+
}
|
|
4437
|
+
const stableRenderAPI = stableRenderAPIRef.current;
|
|
4438
|
+
useEffect10(() => {
|
|
4439
|
+
if (!renderMode && !isDebugMode) {
|
|
4440
|
+
liveRenderAPIRef.current = null;
|
|
4441
|
+
return;
|
|
4442
|
+
}
|
|
4443
|
+
const root = containerRef.current;
|
|
4444
|
+
if (!root) {
|
|
4445
|
+
liveRenderAPIRef.current = null;
|
|
4446
|
+
return;
|
|
4447
|
+
}
|
|
4448
|
+
const renderSeekTo = (time) => {
|
|
4449
|
+
seekTo(time);
|
|
4450
|
+
return new Promise((resolve) => {
|
|
4451
|
+
requestAnimationFrame(() => {
|
|
4452
|
+
let blockStartTime = 0;
|
|
4453
|
+
for (let i = expandedBlocks.length - 1; i >= 0; i--) {
|
|
4454
|
+
if (time >= expandedBlocks[i].startTime) {
|
|
4455
|
+
blockStartTime = expandedBlocks[i].startTime;
|
|
4456
|
+
break;
|
|
3821
4457
|
}
|
|
3822
|
-
|
|
3823
|
-
|
|
3824
|
-
|
|
3825
|
-
|
|
3826
|
-
|
|
3827
|
-
|
|
3828
|
-
|
|
3829
|
-
|
|
3830
|
-
|
|
3831
|
-
});
|
|
3832
|
-
const blockElapsed = time - blockStartTime;
|
|
3833
|
-
const videoSeekPromises = [];
|
|
3834
|
-
const activeBlockEl = document.querySelector(".doc-player__block--active");
|
|
3835
|
-
if (activeBlockEl) {
|
|
3836
|
-
const videos = activeBlockEl.querySelectorAll("video[data-clip-start]");
|
|
3837
|
-
videos.forEach((el) => {
|
|
3838
|
-
const video = el;
|
|
3839
|
-
const clipStart = parseFloat(video.dataset.clipStart || "0");
|
|
3840
|
-
const clipEnd = parseFloat(video.dataset.clipEnd || "0");
|
|
3841
|
-
const startAt = parseFloat(video.dataset.startAt || "0");
|
|
3842
|
-
const targetTime = Math.min(
|
|
3843
|
-
clipStart + Math.max(0, blockElapsed - startAt),
|
|
3844
|
-
clipEnd
|
|
3845
|
-
);
|
|
3846
|
-
video.pause();
|
|
3847
|
-
video.currentTime = targetTime;
|
|
3848
|
-
videoSeekPromises.push(
|
|
3849
|
-
new Promise((r) => {
|
|
3850
|
-
if (Math.abs(video.currentTime - targetTime) < 0.1) {
|
|
3851
|
-
r();
|
|
3852
|
-
} else {
|
|
3853
|
-
video.addEventListener("seeked", () => r(), { once: true });
|
|
3854
|
-
setTimeout(r, 200);
|
|
3855
|
-
}
|
|
3856
|
-
})
|
|
3857
|
-
);
|
|
3858
|
-
});
|
|
4458
|
+
}
|
|
4459
|
+
const elapsedMs = (time - blockStartTime) * 1e3;
|
|
4460
|
+
(root.getAnimations?.() ?? []).forEach((anim) => {
|
|
4461
|
+
const target = anim.effect?.target;
|
|
4462
|
+
if (!target) return;
|
|
4463
|
+
if (target.closest(".doc-player__block--active")) {
|
|
4464
|
+
anim.currentTime = Math.max(0, elapsedMs);
|
|
4465
|
+
} else if (target.closest(".doc-player__block--previous")) {
|
|
4466
|
+
anim.currentTime = Math.max(0, elapsedMs);
|
|
3859
4467
|
}
|
|
3860
|
-
|
|
4468
|
+
});
|
|
4469
|
+
const blockElapsed = time - blockStartTime;
|
|
4470
|
+
const videoSeekPromises = [];
|
|
4471
|
+
const activeBlockEl = root.querySelector(".doc-player__block--active");
|
|
4472
|
+
if (activeBlockEl) {
|
|
4473
|
+
const videos = activeBlockEl.querySelectorAll("video[data-clip-start]");
|
|
4474
|
+
videos.forEach((el) => {
|
|
3861
4475
|
const video = el;
|
|
3862
|
-
const
|
|
3863
|
-
const
|
|
3864
|
-
const
|
|
4476
|
+
const clipStart = parseFloat(video.dataset.clipStart || "0");
|
|
4477
|
+
const clipEnd = parseFloat(video.dataset.clipEnd || "0");
|
|
4478
|
+
const startAt = parseFloat(video.dataset.startAt || "0");
|
|
4479
|
+
const targetTime = Math.min(clipStart + Math.max(0, blockElapsed - startAt), clipEnd);
|
|
3865
4480
|
video.pause();
|
|
3866
|
-
if (time < absStart || time >= absEnd) return;
|
|
3867
|
-
const targetTime = sourceIn + (time - absStart);
|
|
3868
4481
|
video.currentTime = targetTime;
|
|
3869
4482
|
videoSeekPromises.push(
|
|
3870
4483
|
new Promise((r) => {
|
|
@@ -3877,82 +4490,111 @@ function DocPlayerContent({
|
|
|
3877
4490
|
})
|
|
3878
4491
|
);
|
|
3879
4492
|
});
|
|
3880
|
-
|
|
3881
|
-
|
|
3882
|
-
|
|
4493
|
+
}
|
|
4494
|
+
root.querySelectorAll("video[data-clip-id]").forEach((el) => {
|
|
4495
|
+
const video = el;
|
|
4496
|
+
const absStart = parseFloat(video.dataset.absStart || "0");
|
|
4497
|
+
const absEnd = parseFloat(video.dataset.absEnd || "0");
|
|
4498
|
+
const sourceIn = parseFloat(video.dataset.sourceIn || "0");
|
|
4499
|
+
video.pause();
|
|
4500
|
+
if (time < absStart || time >= absEnd) return;
|
|
4501
|
+
const targetTime = sourceIn + (time - absStart);
|
|
4502
|
+
video.currentTime = targetTime;
|
|
4503
|
+
videoSeekPromises.push(
|
|
4504
|
+
new Promise((r) => {
|
|
4505
|
+
if (Math.abs(video.currentTime - targetTime) < 0.1) {
|
|
4506
|
+
r();
|
|
4507
|
+
} else {
|
|
4508
|
+
video.addEventListener("seeked", () => r(), { once: true });
|
|
4509
|
+
setTimeout(r, 200);
|
|
4510
|
+
}
|
|
4511
|
+
})
|
|
4512
|
+
);
|
|
4513
|
+
});
|
|
4514
|
+
Promise.all(videoSeekPromises).then(() => {
|
|
4515
|
+
requestAnimationFrame(() => resolve());
|
|
3883
4516
|
});
|
|
3884
4517
|
});
|
|
3885
|
-
};
|
|
3886
|
-
|
|
3887
|
-
|
|
3888
|
-
|
|
3889
|
-
|
|
3890
|
-
|
|
3891
|
-
|
|
3892
|
-
|
|
3893
|
-
|
|
3894
|
-
|
|
3895
|
-
|
|
3896
|
-
|
|
3897
|
-
|
|
3898
|
-
|
|
3899
|
-
|
|
3900
|
-
|
|
3901
|
-
|
|
4518
|
+
});
|
|
4519
|
+
};
|
|
4520
|
+
const getDuration = () => {
|
|
4521
|
+
const mediaDuration = getDocPlaybackDuration(doc);
|
|
4522
|
+
if (totalDuration > 0) return Math.max(totalDuration, mediaDuration);
|
|
4523
|
+
return mediaDuration;
|
|
4524
|
+
};
|
|
4525
|
+
const getBlocks = () => expandedBlocks.map((s) => ({
|
|
4526
|
+
id: s.id,
|
|
4527
|
+
template: s.template ?? "raw",
|
|
4528
|
+
startTime: s.startTime,
|
|
4529
|
+
duration: s.duration
|
|
4530
|
+
}));
|
|
4531
|
+
const getAudioSegments = () => doc.audio.segments.map((seg) => ({
|
|
4532
|
+
src: seg.src,
|
|
4533
|
+
name: seg.name,
|
|
4534
|
+
duration: seg.duration,
|
|
4535
|
+
startTime: seg.startTime
|
|
4536
|
+
}));
|
|
4537
|
+
const getCaptions = () => doc.captions?.phrases?.map((p) => ({
|
|
4538
|
+
text: p.text,
|
|
4539
|
+
startTime: p.startTime,
|
|
4540
|
+
endTime: p.endTime
|
|
4541
|
+
})) || [];
|
|
4542
|
+
const getChapters = () => {
|
|
4543
|
+
const titleMap = buildSegmentTitleMap(doc);
|
|
4544
|
+
return doc.audio.segments.map((seg, i) => ({
|
|
4545
|
+
title: titleMap.get(i) || seg.name,
|
|
4546
|
+
startTime: seg.startTime,
|
|
4547
|
+
duration: seg.duration
|
|
3902
4548
|
}));
|
|
3903
|
-
|
|
3904
|
-
|
|
3905
|
-
|
|
3906
|
-
|
|
3907
|
-
|
|
3908
|
-
|
|
3909
|
-
|
|
3910
|
-
|
|
3911
|
-
|
|
3912
|
-
|
|
3913
|
-
|
|
3914
|
-
|
|
3915
|
-
|
|
3916
|
-
|
|
3917
|
-
|
|
3918
|
-
|
|
3919
|
-
|
|
3920
|
-
|
|
3921
|
-
|
|
3922
|
-
|
|
3923
|
-
|
|
3924
|
-
|
|
3925
|
-
}
|
|
4549
|
+
};
|
|
4550
|
+
const showCover = () => {
|
|
4551
|
+
setCoverForced(true);
|
|
4552
|
+
return new Promise((resolve) => requestAnimationFrame(() => resolve()));
|
|
4553
|
+
};
|
|
4554
|
+
const hideCover = () => {
|
|
4555
|
+
setCoverForced(false);
|
|
4556
|
+
return new Promise((resolve) => requestAnimationFrame(() => resolve()));
|
|
4557
|
+
};
|
|
4558
|
+
const hasCoverBlock = () => !!coverBlock;
|
|
4559
|
+
const api = {
|
|
4560
|
+
seekTo: renderSeekTo,
|
|
4561
|
+
getDuration,
|
|
4562
|
+
getBlocks,
|
|
4563
|
+
getAudioSegments,
|
|
4564
|
+
getCaptions,
|
|
4565
|
+
getChapters,
|
|
4566
|
+
showCover,
|
|
4567
|
+
hideCover,
|
|
4568
|
+
hasCoverBlock
|
|
4569
|
+
};
|
|
4570
|
+
liveRenderAPIRef.current = api;
|
|
3926
4571
|
return () => {
|
|
3927
|
-
if (
|
|
3928
|
-
const w = window;
|
|
3929
|
-
delete w.seekTo;
|
|
3930
|
-
delete w.getDuration;
|
|
3931
|
-
delete w.getBlocks;
|
|
3932
|
-
delete w.getAudioSegments;
|
|
3933
|
-
delete w.getCaptions;
|
|
3934
|
-
delete w.getChapters;
|
|
3935
|
-
delete w.showCover;
|
|
3936
|
-
delete w.hideCover;
|
|
3937
|
-
delete w.hasCoverBlock;
|
|
3938
|
-
}
|
|
4572
|
+
if (liveRenderAPIRef.current === api) liveRenderAPIRef.current = null;
|
|
3939
4573
|
};
|
|
3940
|
-
}, [renderMode, isDebugMode, seekTo, totalDuration, expandedBlocks, coverBlock]);
|
|
4574
|
+
}, [renderMode, isDebugMode, seekTo, totalDuration, expandedBlocks, coverBlock, doc]);
|
|
4575
|
+
useEffect10(() => {
|
|
4576
|
+
if (!renderMode && !isDebugMode || !containerRef.current) {
|
|
4577
|
+
onRenderAPIReady?.(null);
|
|
4578
|
+
return;
|
|
4579
|
+
}
|
|
4580
|
+
onRenderAPIReady?.(stableRenderAPI);
|
|
4581
|
+
return () => onRenderAPIReady?.(null);
|
|
4582
|
+
}, [renderMode, isDebugMode, onRenderAPIReady, stableRenderAPI]);
|
|
3941
4583
|
const defaultMode = captionsEnabledProp === false ? "off" : captionStyle || "standard";
|
|
3942
|
-
const [captionMode, setCaptionMode] =
|
|
3943
|
-
|
|
4584
|
+
const [captionMode, setCaptionMode] = useState9(defaultMode);
|
|
4585
|
+
useEffect10(() => {
|
|
3944
4586
|
setCaptionMode(defaultMode);
|
|
3945
4587
|
}, [defaultMode]);
|
|
3946
4588
|
const captionsEnabled = captionMode !== "off";
|
|
3947
4589
|
const activeCaptionStyle = captionMode === "social" ? "social" : "standard";
|
|
3948
|
-
const setCaptionsEnabled =
|
|
4590
|
+
const setCaptionsEnabled = useCallback7(
|
|
3949
4591
|
(enabled) => {
|
|
3950
4592
|
setCaptionMode(enabled ? captionStyle || "standard" : "off");
|
|
3951
4593
|
onCaptionsToggle?.(enabled);
|
|
3952
4594
|
},
|
|
3953
4595
|
[onCaptionsToggle, captionStyle]
|
|
3954
4596
|
);
|
|
3955
|
-
const cycleCaptionMode =
|
|
4597
|
+
const cycleCaptionMode = useCallback7(() => {
|
|
3956
4598
|
setCaptionMode((prev) => {
|
|
3957
4599
|
const next = prev === "off" ? "standard" : prev === "standard" ? "social" : "off";
|
|
3958
4600
|
onCaptionsToggle?.(next !== "off");
|
|
@@ -3966,6 +4608,7 @@ function DocPlayerContent({
|
|
|
3966
4608
|
isPlaying,
|
|
3967
4609
|
currentTime,
|
|
3968
4610
|
totalDuration,
|
|
4611
|
+
isCoverVisible: showCoverBlock,
|
|
3969
4612
|
currentBlockIndex: slideshowSlideIndex,
|
|
3970
4613
|
totalBlocks: slideshowTotalSlides,
|
|
3971
4614
|
docProgress,
|
|
@@ -3985,6 +4628,7 @@ function DocPlayerContent({
|
|
|
3985
4628
|
isPlaying,
|
|
3986
4629
|
currentTime,
|
|
3987
4630
|
totalDuration,
|
|
4631
|
+
showCoverBlock,
|
|
3988
4632
|
slideshowSlideIndex,
|
|
3989
4633
|
slideshowTotalSlides,
|
|
3990
4634
|
docProgress,
|
|
@@ -4078,23 +4722,39 @@ function DocPlayerContent({
|
|
|
4078
4722
|
[currentBlockIndex, expandedBlocks, seekTo, pause, slideshowHasCover, slideshowCoverVisible]
|
|
4079
4723
|
);
|
|
4080
4724
|
const swipeEnabled = isSlideshowMode && !isLinearMode && !renderMode && enableSwipe;
|
|
4725
|
+
const armContextFreeSwipeEntry = useCallback7(
|
|
4726
|
+
(destinationSlideIndex) => {
|
|
4727
|
+
const destinationBlockIndex = destinationSlideIndex - (slideshowHasCover ? 1 : 0);
|
|
4728
|
+
const destinationBlock = expandedBlocks[destinationBlockIndex];
|
|
4729
|
+
if (destinationBlock) suppressOutgoingForNextBlock(destinationBlock.id);
|
|
4730
|
+
},
|
|
4731
|
+
[expandedBlocks, slideshowHasCover, suppressOutgoingForNextBlock]
|
|
4732
|
+
);
|
|
4733
|
+
const handleSwipeNext = useCallback7(() => {
|
|
4734
|
+
armContextFreeSwipeEntry(slideshowSlideIndex + 1);
|
|
4735
|
+
slideNavActions.nextSlide();
|
|
4736
|
+
}, [armContextFreeSwipeEntry, slideshowSlideIndex, slideNavActions]);
|
|
4737
|
+
const handleSwipePrev = useCallback7(() => {
|
|
4738
|
+
armContextFreeSwipeEntry(slideshowSlideIndex - 1);
|
|
4739
|
+
slideNavActions.prevSlide();
|
|
4740
|
+
}, [armContextFreeSwipeEntry, slideshowSlideIndex, slideNavActions]);
|
|
4081
4741
|
const swipe = useSlideSwipe({
|
|
4082
4742
|
enabled: swipeEnabled,
|
|
4083
4743
|
containerRef,
|
|
4084
4744
|
canGoNext: slideshowSlideIndex < slideshowTotalSlides - 1,
|
|
4085
4745
|
canGoPrev: slideshowSlideIndex > 0,
|
|
4086
|
-
onNext:
|
|
4087
|
-
onPrev:
|
|
4746
|
+
onNext: handleSwipeNext,
|
|
4747
|
+
onPrev: handleSwipePrev
|
|
4088
4748
|
});
|
|
4089
|
-
|
|
4749
|
+
useEffect10(() => {
|
|
4090
4750
|
onPlaybackStateChange?.(playbackState);
|
|
4091
4751
|
}, [playbackState, onPlaybackStateChange]);
|
|
4092
|
-
|
|
4752
|
+
useEffect10(() => {
|
|
4093
4753
|
onControlsReady?.({ play, pause, ...playbackActions });
|
|
4094
4754
|
}, [play, pause, playbackActions, onControlsReady]);
|
|
4095
|
-
const getBlockTitle =
|
|
4755
|
+
const getBlockTitle = useCallback7((block) => {
|
|
4096
4756
|
const docBlock = block;
|
|
4097
|
-
if (
|
|
4757
|
+
if (isTemplateBlock3(docBlock)) {
|
|
4098
4758
|
const props = docBlock;
|
|
4099
4759
|
if (typeof props.title === "string") return props.title;
|
|
4100
4760
|
if (typeof props.stat === "string") return props.stat;
|
|
@@ -4116,6 +4776,22 @@ function DocPlayerContent({
|
|
|
4116
4776
|
}
|
|
4117
4777
|
return block.id.replace(/-/g, " ").replace(/\b\w/g, (c) => c.toUpperCase());
|
|
4118
4778
|
}, []);
|
|
4779
|
+
const slideshowPickerItems = useMemo9(() => {
|
|
4780
|
+
const blockItems = expandedBlocks.map((block, index) => ({
|
|
4781
|
+
id: block.id,
|
|
4782
|
+
label: String(index + 1),
|
|
4783
|
+
summary: getBlockTitle(block)
|
|
4784
|
+
}));
|
|
4785
|
+
if (!slideshowHasCover || !coverBlock) return blockItems;
|
|
4786
|
+
return [
|
|
4787
|
+
{
|
|
4788
|
+
id: "__cover__",
|
|
4789
|
+
label: "Cover",
|
|
4790
|
+
summary: getBlockTitle(coverBlock)
|
|
4791
|
+
},
|
|
4792
|
+
...blockItems
|
|
4793
|
+
];
|
|
4794
|
+
}, [coverBlock, expandedBlocks, getBlockTitle, slideshowHasCover]);
|
|
4119
4795
|
const blockMarkers = useMemo9(() => {
|
|
4120
4796
|
if (!totalDuration || !expandedBlocks.length) return [];
|
|
4121
4797
|
let prevSegment = -1;
|
|
@@ -4131,16 +4807,26 @@ function DocPlayerContent({
|
|
|
4131
4807
|
};
|
|
4132
4808
|
});
|
|
4133
4809
|
}, [expandedBlocks, totalDuration, getBlockTitle]);
|
|
4134
|
-
|
|
4810
|
+
useEffect10(() => {
|
|
4135
4811
|
if (blockMarkers.length > 0) {
|
|
4136
4812
|
onBlockMarkers?.(blockMarkers);
|
|
4137
4813
|
}
|
|
4138
4814
|
}, [blockMarkers, onBlockMarkers]);
|
|
4139
4815
|
expandedBlocksLenRef.current = isSlideshowMode ? slideshowTotalSlides : expandedBlocks.length;
|
|
4140
|
-
const
|
|
4141
|
-
(e) => {
|
|
4142
|
-
|
|
4143
|
-
|
|
4816
|
+
const handleKeyboardShortcut = useCallback7(
|
|
4817
|
+
(e, global) => {
|
|
4818
|
+
if (e.defaultPrevented || e.altKey || e.ctrlKey || e.metaKey || e.shiftKey) return;
|
|
4819
|
+
const target = e.target instanceof Element ? e.target : null;
|
|
4820
|
+
const isEditableTarget = !!target?.closest(
|
|
4821
|
+
'input, textarea, select, [contenteditable]:not([contenteditable="false"]), [role="textbox"], [role="combobox"], [role="listbox"], [role="slider"], [role="spinbutton"], .monaco-editor'
|
|
4822
|
+
);
|
|
4823
|
+
const isOpenInteractionTarget = !!target?.closest(
|
|
4824
|
+
'[role="menu"], [role="dialog"], [aria-modal="true"]'
|
|
4825
|
+
);
|
|
4826
|
+
const isSlideshowToolbarTarget = isSlideshowMode && !!target?.closest(".doc-controls-slideshow") && !target.closest('[role="menu"]');
|
|
4827
|
+
if (isEditableTarget || global && isOpenInteractionTarget || !global && !!target?.closest(
|
|
4828
|
+
'input, textarea, select, button, a, [contenteditable]:not([contenteditable="false"]), [role="textbox"]'
|
|
4829
|
+
) && !isSlideshowToolbarTarget) {
|
|
4144
4830
|
return;
|
|
4145
4831
|
}
|
|
4146
4832
|
if (isLinearMode) return;
|
|
@@ -4153,10 +4839,13 @@ function DocPlayerContent({
|
|
|
4153
4839
|
slideNavActions.nextSlide();
|
|
4154
4840
|
break;
|
|
4155
4841
|
case "ArrowLeft":
|
|
4156
|
-
case "ArrowUp":
|
|
4157
4842
|
e.preventDefault();
|
|
4158
4843
|
slideNavActions.prevSlide();
|
|
4159
4844
|
break;
|
|
4845
|
+
case "ArrowUp":
|
|
4846
|
+
e.preventDefault();
|
|
4847
|
+
setIsSlideshowPickerOpen(true);
|
|
4848
|
+
break;
|
|
4160
4849
|
case "Home":
|
|
4161
4850
|
e.preventDefault();
|
|
4162
4851
|
slideNavActions.goToSlide(0);
|
|
@@ -4173,9 +4862,11 @@ function DocPlayerContent({
|
|
|
4173
4862
|
toggle();
|
|
4174
4863
|
break;
|
|
4175
4864
|
case "ArrowRight":
|
|
4865
|
+
e.preventDefault();
|
|
4176
4866
|
seekTo(Math.min(currentTimeRef.current + 10, totalDurationRef.current));
|
|
4177
4867
|
break;
|
|
4178
4868
|
case "ArrowLeft":
|
|
4869
|
+
e.preventDefault();
|
|
4179
4870
|
seekTo(Math.max(currentTimeRef.current - 10, 0));
|
|
4180
4871
|
break;
|
|
4181
4872
|
}
|
|
@@ -4183,16 +4874,24 @@ function DocPlayerContent({
|
|
|
4183
4874
|
},
|
|
4184
4875
|
[isSlideshowMode, isLinearMode, toggle, seekTo, slideNavActions]
|
|
4185
4876
|
);
|
|
4186
|
-
|
|
4187
|
-
|
|
4188
|
-
|
|
4189
|
-
|
|
4190
|
-
|
|
4877
|
+
const handleKeyDown = useCallback7(
|
|
4878
|
+
(e) => handleKeyboardShortcut(e, false),
|
|
4879
|
+
[handleKeyboardShortcut]
|
|
4880
|
+
);
|
|
4881
|
+
useEffect10(() => {
|
|
4882
|
+
if (!globalKeyboardShortcuts || renderMode || isLinearMode) return;
|
|
4883
|
+
const handleDocumentKeyDown = (event) => {
|
|
4884
|
+
handleKeyboardShortcut(event, true);
|
|
4885
|
+
};
|
|
4886
|
+
document.addEventListener("keydown", handleDocumentKeyDown);
|
|
4887
|
+
return () => document.removeEventListener("keydown", handleDocumentKeyDown);
|
|
4888
|
+
}, [globalKeyboardShortcuts, handleKeyboardShortcut, isLinearMode, renderMode]);
|
|
4191
4889
|
if (isLinearMode) {
|
|
4192
|
-
return /* @__PURE__ */
|
|
4890
|
+
return /* @__PURE__ */ jsx21(
|
|
4193
4891
|
"div",
|
|
4194
4892
|
{
|
|
4195
4893
|
ref: containerRef,
|
|
4894
|
+
"data-player-id": playerId,
|
|
4196
4895
|
className: "doc-player doc-player--linear",
|
|
4197
4896
|
style: {
|
|
4198
4897
|
position: "relative",
|
|
@@ -4200,23 +4899,28 @@ function DocPlayerContent({
|
|
|
4200
4899
|
height: "100%",
|
|
4201
4900
|
overflow: "hidden"
|
|
4202
4901
|
},
|
|
4203
|
-
children: /* @__PURE__ */
|
|
4902
|
+
children: /* @__PURE__ */ jsx21(
|
|
4204
4903
|
LinearDocView,
|
|
4205
4904
|
{
|
|
4206
4905
|
doc,
|
|
4207
4906
|
basePath,
|
|
4208
4907
|
viewport: activeViewport,
|
|
4209
4908
|
theme,
|
|
4210
|
-
surface
|
|
4909
|
+
surface,
|
|
4910
|
+
animationsEnabled
|
|
4211
4911
|
}
|
|
4212
4912
|
)
|
|
4213
4913
|
}
|
|
4214
4914
|
);
|
|
4215
4915
|
}
|
|
4216
|
-
return /* @__PURE__ */
|
|
4916
|
+
return /* @__PURE__ */ jsxs14(
|
|
4217
4917
|
"div",
|
|
4218
4918
|
{
|
|
4219
4919
|
ref: containerRef,
|
|
4920
|
+
"data-player-id": playerId,
|
|
4921
|
+
tabIndex: renderMode ? -1 : 0,
|
|
4922
|
+
"aria-label": "Document player",
|
|
4923
|
+
onKeyDown: renderMode ? void 0 : handleKeyDown,
|
|
4220
4924
|
className: `doc-player${swipeEnabled ? " doc-player--swipe" : ""}${swipe.phase === "dragging" ? " doc-player--grabbing" : ""}`,
|
|
4221
4925
|
onClick: handleContainerClick,
|
|
4222
4926
|
onPointerDown: swipe.onPointerDown,
|
|
@@ -4232,33 +4936,35 @@ function DocPlayerContent({
|
|
|
4232
4936
|
touchAction: swipeEnabled ? "pan-y" : void 0
|
|
4233
4937
|
},
|
|
4234
4938
|
children: [
|
|
4235
|
-
/* @__PURE__ */
|
|
4236
|
-
/* @__PURE__ */
|
|
4939
|
+
/* @__PURE__ */ jsx21("audio", { ref: audioRef, preload: "auto", muted }),
|
|
4940
|
+
/* @__PURE__ */ jsx21(
|
|
4237
4941
|
MediaClipLayer,
|
|
4238
4942
|
{
|
|
4239
4943
|
schedule: mediaSchedule,
|
|
4240
4944
|
currentTime,
|
|
4241
4945
|
isPlaying,
|
|
4242
4946
|
basePath,
|
|
4243
|
-
renderMode
|
|
4947
|
+
renderMode,
|
|
4948
|
+
muted
|
|
4244
4949
|
}
|
|
4245
4950
|
),
|
|
4246
|
-
/* @__PURE__ */
|
|
4247
|
-
showCoverBlock && coverBlock && /* @__PURE__ */
|
|
4951
|
+
/* @__PURE__ */ jsxs14("div", { className: "doc-player__viewport", children: [
|
|
4952
|
+
showCoverBlock && coverBlock && /* @__PURE__ */ jsx21("div", { className: "doc-player__block doc-player__block--cover", children: /* @__PURE__ */ jsx21(
|
|
4248
4953
|
BlockRenderer,
|
|
4249
4954
|
{
|
|
4250
4955
|
block: coverBlock,
|
|
4251
4956
|
blockTime: 0,
|
|
4252
4957
|
basePath,
|
|
4253
4958
|
isEntering: false,
|
|
4254
|
-
viewport: activeViewport
|
|
4959
|
+
viewport: activeViewport,
|
|
4960
|
+
animationsEnabled
|
|
4255
4961
|
}
|
|
4256
4962
|
) }),
|
|
4257
|
-
!showCoverBlock && previousBlock && isExiting && // Keyed by block id so each block is its own DOM subtree: React never
|
|
4963
|
+
animationsEnabled && !showCoverBlock && previousBlock && isExiting && // Keyed by block id so each block is its own DOM subtree: React never
|
|
4258
4964
|
// reconciles one block's layers onto another's (templates reuse layer
|
|
4259
4965
|
// ids like `title`/`background`), which would otherwise reuse stale
|
|
4260
4966
|
// DOM / skip entrance animations mid-transition.
|
|
4261
|
-
/* @__PURE__ */
|
|
4967
|
+
/* @__PURE__ */ jsx21("div", { className: "doc-player__block doc-player__block--previous", children: /* @__PURE__ */ jsx21(
|
|
4262
4968
|
BlockRenderer,
|
|
4263
4969
|
{
|
|
4264
4970
|
block: previousBlock,
|
|
@@ -4266,29 +4972,31 @@ function DocPlayerContent({
|
|
|
4266
4972
|
basePath,
|
|
4267
4973
|
isExiting: true,
|
|
4268
4974
|
transition: currentBlock?.transition,
|
|
4269
|
-
viewport: activeViewport
|
|
4975
|
+
viewport: activeViewport,
|
|
4976
|
+
animationsEnabled
|
|
4270
4977
|
}
|
|
4271
4978
|
) }, previousBlock.id),
|
|
4272
|
-
!showCoverBlock && currentBlock && /* @__PURE__ */
|
|
4979
|
+
!showCoverBlock && currentBlock && /* @__PURE__ */ jsx21(
|
|
4273
4980
|
"div",
|
|
4274
4981
|
{
|
|
4275
4982
|
className: `doc-player__block doc-player__block--active${swipe.phase !== "idle" ? ` doc-player__block--${swipe.phase}` : ""}`,
|
|
4276
4983
|
style: swipe.phase !== "idle" ? { transform: `translateX(${swipe.offsetPx}px)` } : void 0,
|
|
4277
|
-
children: /* @__PURE__ */
|
|
4984
|
+
children: /* @__PURE__ */ jsx21(
|
|
4278
4985
|
BlockRenderer,
|
|
4279
4986
|
{
|
|
4280
4987
|
block: currentBlock,
|
|
4281
4988
|
blockTime,
|
|
4282
4989
|
basePath,
|
|
4283
|
-
isEntering,
|
|
4990
|
+
isEntering: animationsEnabled && isEntering,
|
|
4284
4991
|
viewport: activeViewport,
|
|
4285
|
-
isPlaying
|
|
4992
|
+
isPlaying,
|
|
4993
|
+
animationsEnabled
|
|
4286
4994
|
}
|
|
4287
4995
|
)
|
|
4288
4996
|
},
|
|
4289
4997
|
currentBlock.id
|
|
4290
4998
|
),
|
|
4291
|
-
hasCaptions && (renderMode ? captionsEnabled : true) && /* @__PURE__ */
|
|
4999
|
+
hasCaptions && (renderMode ? captionsEnabled : true) && /* @__PURE__ */ jsx21(
|
|
4292
5000
|
CaptionOverlay,
|
|
4293
5001
|
{
|
|
4294
5002
|
captions: doc.captions,
|
|
@@ -4300,7 +5008,7 @@ function DocPlayerContent({
|
|
|
4300
5008
|
viewport: activeViewport
|
|
4301
5009
|
}
|
|
4302
5010
|
),
|
|
4303
|
-
isDebugMode && /* @__PURE__ */
|
|
5011
|
+
isDebugMode && /* @__PURE__ */ jsxs14(
|
|
4304
5012
|
"div",
|
|
4305
5013
|
{
|
|
4306
5014
|
className: "doc-player__debug",
|
|
@@ -4321,27 +5029,27 @@ function DocPlayerContent({
|
|
|
4321
5029
|
textAlign: "left"
|
|
4322
5030
|
},
|
|
4323
5031
|
children: [
|
|
4324
|
-
/* @__PURE__ */
|
|
4325
|
-
/* @__PURE__ */
|
|
4326
|
-
/* @__PURE__ */
|
|
5032
|
+
/* @__PURE__ */ jsx21("div", { style: { color: "#ffcc00", fontWeight: "bold", marginBottom: "4px" }, children: "DEBUG MODE" }),
|
|
5033
|
+
/* @__PURE__ */ jsxs14("div", { children: [
|
|
5034
|
+
/* @__PURE__ */ jsx21("span", { style: { color: "#888" }, children: "template:" }),
|
|
4327
5035
|
" ",
|
|
4328
|
-
/* @__PURE__ */
|
|
5036
|
+
/* @__PURE__ */ jsx21("span", { style: { color: "#ff6b6b" }, children: currentBlock?.template ?? "raw" })
|
|
4329
5037
|
] }),
|
|
4330
|
-
/* @__PURE__ */
|
|
4331
|
-
/* @__PURE__ */
|
|
5038
|
+
/* @__PURE__ */ jsxs14("div", { children: [
|
|
5039
|
+
/* @__PURE__ */ jsx21("span", { style: { color: "#888" }, children: "block:" }),
|
|
4332
5040
|
" ",
|
|
4333
5041
|
currentBlockIndex + 1,
|
|
4334
5042
|
"/",
|
|
4335
5043
|
expandedBlocks.length,
|
|
4336
5044
|
" ",
|
|
4337
|
-
/* @__PURE__ */
|
|
5045
|
+
/* @__PURE__ */ jsxs14("span", { style: { color: "#666" }, children: [
|
|
4338
5046
|
"(",
|
|
4339
5047
|
currentBlock?.id || "none",
|
|
4340
5048
|
")"
|
|
4341
5049
|
] })
|
|
4342
5050
|
] }),
|
|
4343
|
-
/* @__PURE__ */
|
|
4344
|
-
/* @__PURE__ */
|
|
5051
|
+
/* @__PURE__ */ jsxs14("div", { children: [
|
|
5052
|
+
/* @__PURE__ */ jsx21("span", { style: { color: "#888" }, children: "time:" }),
|
|
4345
5053
|
" ",
|
|
4346
5054
|
currentTime.toFixed(2),
|
|
4347
5055
|
"s /",
|
|
@@ -4349,7 +5057,7 @@ function DocPlayerContent({
|
|
|
4349
5057
|
totalDuration.toFixed(1),
|
|
4350
5058
|
"s",
|
|
4351
5059
|
" ",
|
|
4352
|
-
/* @__PURE__ */
|
|
5060
|
+
/* @__PURE__ */ jsxs14("span", { style: { color: "#666" }, children: [
|
|
4353
5061
|
"(progress: ",
|
|
4354
5062
|
(docProgress * 100).toFixed(1),
|
|
4355
5063
|
"%, scriptDur: ",
|
|
@@ -4357,8 +5065,8 @@ function DocPlayerContent({
|
|
|
4357
5065
|
")"
|
|
4358
5066
|
] })
|
|
4359
5067
|
] }),
|
|
4360
|
-
/* @__PURE__ */
|
|
4361
|
-
/* @__PURE__ */
|
|
5068
|
+
/* @__PURE__ */ jsxs14("div", { children: [
|
|
5069
|
+
/* @__PURE__ */ jsx21("span", { style: { color: "#888" }, children: "blockTime:" }),
|
|
4362
5070
|
" ",
|
|
4363
5071
|
blockTime.toFixed(2),
|
|
4364
5072
|
"s /",
|
|
@@ -4366,58 +5074,58 @@ function DocPlayerContent({
|
|
|
4366
5074
|
(currentBlock?.duration || 0).toFixed(1),
|
|
4367
5075
|
"s"
|
|
4368
5076
|
] }),
|
|
4369
|
-
/* @__PURE__ */
|
|
4370
|
-
/* @__PURE__ */
|
|
5077
|
+
/* @__PURE__ */ jsxs14("div", { children: [
|
|
5078
|
+
/* @__PURE__ */ jsx21("span", { style: { color: "#888" }, children: "segment:" }),
|
|
4371
5079
|
" ",
|
|
4372
5080
|
currentSegment,
|
|
4373
5081
|
"/",
|
|
4374
5082
|
doc.audio.segments.length - 1,
|
|
4375
5083
|
" ",
|
|
4376
|
-
/* @__PURE__ */
|
|
5084
|
+
/* @__PURE__ */ jsxs14("span", { style: { color: "#666" }, children: [
|
|
4377
5085
|
"(",
|
|
4378
5086
|
doc.audio.segments[currentSegment]?.name || "none",
|
|
4379
5087
|
")"
|
|
4380
5088
|
] })
|
|
4381
5089
|
] }),
|
|
4382
|
-
/* @__PURE__ */
|
|
4383
|
-
/* @__PURE__ */
|
|
5090
|
+
/* @__PURE__ */ jsxs14("div", { children: [
|
|
5091
|
+
/* @__PURE__ */ jsx21("span", { style: { color: "#888" }, children: "viewport:" }),
|
|
4384
5092
|
" ",
|
|
4385
5093
|
activeViewport.name || `${activeViewport.width}x${activeViewport.height}`,
|
|
4386
5094
|
" ",
|
|
4387
|
-
/* @__PURE__ */
|
|
5095
|
+
/* @__PURE__ */ jsxs14("span", { style: { color: "#666" }, children: [
|
|
4388
5096
|
"(",
|
|
4389
5097
|
orientation,
|
|
4390
5098
|
")"
|
|
4391
5099
|
] })
|
|
4392
5100
|
] }),
|
|
4393
|
-
/* @__PURE__ */
|
|
4394
|
-
/* @__PURE__ */
|
|
5101
|
+
/* @__PURE__ */ jsxs14("div", { children: [
|
|
5102
|
+
/* @__PURE__ */ jsx21("span", { style: { color: "#888" }, children: "playing:" }),
|
|
4395
5103
|
" ",
|
|
4396
|
-
/* @__PURE__ */
|
|
4397
|
-
showCoverBlock && /* @__PURE__ */
|
|
5104
|
+
/* @__PURE__ */ jsx21("span", { style: { color: isPlaying ? "#4ade80" : "#f87171" }, children: isPlaying ? "yes" : "no" }),
|
|
5105
|
+
showCoverBlock && /* @__PURE__ */ jsx21("span", { style: { color: "#60a5fa" }, children: " (cover)" })
|
|
4398
5106
|
] }),
|
|
4399
5107
|
hasCaptions && (() => {
|
|
4400
5108
|
const debugPhrase = getCaptionAtTime2(doc.captions, currentTime);
|
|
4401
5109
|
const debugEnabled = captionsEnabled && (isPlaying || currentTime > 0);
|
|
4402
|
-
return /* @__PURE__ */
|
|
4403
|
-
/* @__PURE__ */
|
|
4404
|
-
/* @__PURE__ */
|
|
5110
|
+
return /* @__PURE__ */ jsxs14(Fragment3, { children: [
|
|
5111
|
+
/* @__PURE__ */ jsxs14("div", { children: [
|
|
5112
|
+
/* @__PURE__ */ jsx21("span", { style: { color: "#888" }, children: "captions:" }),
|
|
4405
5113
|
" ",
|
|
4406
5114
|
doc.captions?.phrases.length || 0,
|
|
4407
5115
|
" phrases",
|
|
4408
5116
|
" ",
|
|
4409
|
-
/* @__PURE__ */
|
|
5117
|
+
/* @__PURE__ */ jsxs14("span", { style: { color: captionsEnabled ? "#4ade80" : "#666" }, children: [
|
|
4410
5118
|
"(",
|
|
4411
5119
|
captionsEnabled ? "on" : "off",
|
|
4412
5120
|
")"
|
|
4413
5121
|
] })
|
|
4414
5122
|
] }),
|
|
4415
|
-
/* @__PURE__ */
|
|
4416
|
-
/* @__PURE__ */
|
|
5123
|
+
/* @__PURE__ */ jsxs14("div", { children: [
|
|
5124
|
+
/* @__PURE__ */ jsx21("span", { style: { color: "#888" }, children: "cc.enabled:" }),
|
|
4417
5125
|
" ",
|
|
4418
|
-
/* @__PURE__ */
|
|
5126
|
+
/* @__PURE__ */ jsx21("span", { style: { color: debugEnabled ? "#4ade80" : "#f87171" }, children: String(debugEnabled) }),
|
|
4419
5127
|
" ",
|
|
4420
|
-
/* @__PURE__ */
|
|
5128
|
+
/* @__PURE__ */ jsxs14("span", { style: { color: "#666" }, children: [
|
|
4421
5129
|
"(playing=",
|
|
4422
5130
|
String(isPlaying),
|
|
4423
5131
|
" t>0=",
|
|
@@ -4425,15 +5133,15 @@ function DocPlayerContent({
|
|
|
4425
5133
|
")"
|
|
4426
5134
|
] })
|
|
4427
5135
|
] }),
|
|
4428
|
-
/* @__PURE__ */
|
|
4429
|
-
/* @__PURE__ */
|
|
5136
|
+
/* @__PURE__ */ jsxs14("div", { children: [
|
|
5137
|
+
/* @__PURE__ */ jsx21("span", { style: { color: "#888" }, children: "cc.phrase:" }),
|
|
4430
5138
|
" ",
|
|
4431
|
-
/* @__PURE__ */
|
|
5139
|
+
/* @__PURE__ */ jsx21("span", { style: { color: debugPhrase ? "#4ade80" : "#f87171" }, children: debugPhrase ? `"${debugPhrase.text.slice(0, 30)}..."` : "null" })
|
|
4432
5140
|
] }),
|
|
4433
|
-
debugPhrase && /* @__PURE__ */
|
|
4434
|
-
/* @__PURE__ */
|
|
5141
|
+
debugPhrase && /* @__PURE__ */ jsxs14("div", { children: [
|
|
5142
|
+
/* @__PURE__ */ jsx21("span", { style: { color: "#888" }, children: "cc.range:" }),
|
|
4435
5143
|
" ",
|
|
4436
|
-
/* @__PURE__ */
|
|
5144
|
+
/* @__PURE__ */ jsxs14("span", { style: { color: "#60a5fa" }, children: [
|
|
4437
5145
|
debugPhrase.startTime.toFixed(2),
|
|
4438
5146
|
"-",
|
|
4439
5147
|
debugPhrase.endTime.toFixed(2)
|
|
@@ -4445,7 +5153,7 @@ function DocPlayerContent({
|
|
|
4445
5153
|
}
|
|
4446
5154
|
)
|
|
4447
5155
|
] }),
|
|
4448
|
-
!isAvailable && unavailableMessage && /* @__PURE__ */
|
|
5156
|
+
!isAvailable && unavailableMessage && /* @__PURE__ */ jsxs14(
|
|
4449
5157
|
"div",
|
|
4450
5158
|
{
|
|
4451
5159
|
className: "doc-player__unavailable",
|
|
@@ -4466,12 +5174,12 @@ function DocPlayerContent({
|
|
|
4466
5174
|
zIndex: 50
|
|
4467
5175
|
},
|
|
4468
5176
|
children: [
|
|
4469
|
-
/* @__PURE__ */
|
|
4470
|
-
/* @__PURE__ */
|
|
5177
|
+
/* @__PURE__ */ jsx21("span", { style: { fontSize: "32px" }, children: "\u{1F50A}" }),
|
|
5178
|
+
/* @__PURE__ */ jsx21("span", { children: unavailableMessage })
|
|
4471
5179
|
]
|
|
4472
5180
|
}
|
|
4473
5181
|
),
|
|
4474
|
-
!renderMode && !isSlideshowMode && showControls && /* @__PURE__ */
|
|
5182
|
+
!renderMode && !isSlideshowMode && showControls && /* @__PURE__ */ jsx21(
|
|
4475
5183
|
DocControlsOverlay,
|
|
4476
5184
|
{
|
|
4477
5185
|
state: playbackState,
|
|
@@ -4481,7 +5189,7 @@ function DocPlayerContent({
|
|
|
4481
5189
|
getBlockTitle
|
|
4482
5190
|
}
|
|
4483
5191
|
),
|
|
4484
|
-
!renderMode && !isSlideshowMode && !showControls && showScrubber && /* @__PURE__ */
|
|
5192
|
+
!renderMode && !isSlideshowMode && !showControls && showScrubber && /* @__PURE__ */ jsx21(
|
|
4485
5193
|
"div",
|
|
4486
5194
|
{
|
|
4487
5195
|
className: "doc-player__scrubber",
|
|
@@ -4496,7 +5204,7 @@ function DocPlayerContent({
|
|
|
4496
5204
|
alignItems: "center",
|
|
4497
5205
|
zIndex: 100
|
|
4498
5206
|
},
|
|
4499
|
-
children: /* @__PURE__ */
|
|
5207
|
+
children: /* @__PURE__ */ jsx21(
|
|
4500
5208
|
DocProgressBar,
|
|
4501
5209
|
{
|
|
4502
5210
|
state: playbackState,
|
|
@@ -4508,15 +5216,24 @@ function DocPlayerContent({
|
|
|
4508
5216
|
)
|
|
4509
5217
|
}
|
|
4510
5218
|
),
|
|
4511
|
-
!renderMode && isSlideshowMode && /* @__PURE__ */
|
|
4512
|
-
|
|
5219
|
+
!renderMode && isSlideshowMode && showControls && /* @__PURE__ */ jsx21(
|
|
5220
|
+
DocControlsSlideshow,
|
|
5221
|
+
{
|
|
5222
|
+
state: playbackState,
|
|
5223
|
+
slideNav: slideNavActions,
|
|
5224
|
+
slides: slideshowPickerItems,
|
|
5225
|
+
pickerOpen: isSlideshowPickerOpen,
|
|
5226
|
+
onPickerOpenChange: setIsSlideshowPickerOpen
|
|
5227
|
+
}
|
|
5228
|
+
),
|
|
5229
|
+
!isSlideshowMode && tapFeedback && /* @__PURE__ */ jsx21("div", { className: "doc-player__tap-feedback", children: /* @__PURE__ */ jsx21("svg", { viewBox: "0 0 24 24", fill: "white", width: "48", height: "48", children: tapFeedback === "pause" ? /* @__PURE__ */ jsx21("path", { d: "M6 19h4V5H6v14zm8-14v14h4V5h-4z" }) : /* @__PURE__ */ jsx21("path", { d: "M8 5v14l11-7z" }) }) }, tapFeedback)
|
|
4513
5230
|
]
|
|
4514
5231
|
}
|
|
4515
5232
|
);
|
|
4516
5233
|
}
|
|
4517
5234
|
|
|
4518
5235
|
// src/DocControlsBottom.tsx
|
|
4519
|
-
import { jsx as
|
|
5236
|
+
import { jsx as jsx22, jsxs as jsxs15 } from "react/jsx-runtime";
|
|
4520
5237
|
function DocControlsBottom({
|
|
4521
5238
|
state,
|
|
4522
5239
|
actions,
|
|
@@ -4524,32 +5241,32 @@ function DocControlsBottom({
|
|
|
4524
5241
|
expandedBlocks,
|
|
4525
5242
|
getBlockTitle
|
|
4526
5243
|
}) {
|
|
4527
|
-
return /* @__PURE__ */
|
|
4528
|
-
/* @__PURE__ */
|
|
5244
|
+
return /* @__PURE__ */ jsxs15("div", { className: "doc-controls-bottom", children: [
|
|
5245
|
+
/* @__PURE__ */ jsx22(
|
|
4529
5246
|
"button",
|
|
4530
5247
|
{
|
|
4531
5248
|
className: "bottom-ctrl-btn",
|
|
4532
5249
|
onClick: actions.restart,
|
|
4533
5250
|
title: "Restart",
|
|
4534
5251
|
"aria-label": "Restart from beginning",
|
|
4535
|
-
children: /* @__PURE__ */
|
|
5252
|
+
children: /* @__PURE__ */ jsx22("svg", { viewBox: "0 0 24 24", fill: "currentColor", width: "18", height: "18", children: /* @__PURE__ */ jsx22("path", { d: "M12 5V1L7 6l5 5V7c3.31 0 6 2.69 6 6s-2.69 6-6 6-6-2.69-6-6H4c0 4.42 3.58 8 8 8s8-3.58 8-8-3.58-8-8-8z" }) })
|
|
4536
5253
|
}
|
|
4537
5254
|
),
|
|
4538
|
-
/* @__PURE__ */
|
|
5255
|
+
/* @__PURE__ */ jsx22(
|
|
4539
5256
|
"button",
|
|
4540
5257
|
{
|
|
4541
5258
|
className: "bottom-ctrl-btn bottom-play-btn",
|
|
4542
5259
|
onClick: actions.toggle,
|
|
4543
5260
|
"aria-label": state.isPlaying ? "Pause" : "Play",
|
|
4544
|
-
children: state.isPlaying ? /* @__PURE__ */
|
|
5261
|
+
children: state.isPlaying ? /* @__PURE__ */ jsx22("svg", { viewBox: "0 0 24 24", fill: "currentColor", width: "20", height: "20", children: /* @__PURE__ */ jsx22("path", { d: "M6 19h4V5H6v14zm8-14v14h4V5h-4z" }) }) : /* @__PURE__ */ jsx22("svg", { viewBox: "0 0 24 24", fill: "currentColor", width: "20", height: "20", children: /* @__PURE__ */ jsx22("path", { d: "M8 5v14l11-7z" }) })
|
|
4545
5262
|
}
|
|
4546
5263
|
),
|
|
4547
|
-
/* @__PURE__ */
|
|
5264
|
+
/* @__PURE__ */ jsxs15("span", { className: "bottom-time", children: [
|
|
4548
5265
|
formatTime(state.currentTime),
|
|
4549
5266
|
" / ",
|
|
4550
5267
|
formatTime(state.totalDuration)
|
|
4551
5268
|
] }),
|
|
4552
|
-
/* @__PURE__ */
|
|
5269
|
+
/* @__PURE__ */ jsx22(
|
|
4553
5270
|
DocProgressBar,
|
|
4554
5271
|
{
|
|
4555
5272
|
state,
|
|
@@ -4559,82 +5276,82 @@ function DocControlsBottom({
|
|
|
4559
5276
|
getBlockTitle
|
|
4560
5277
|
}
|
|
4561
5278
|
),
|
|
4562
|
-
/* @__PURE__ */
|
|
5279
|
+
/* @__PURE__ */ jsxs15("span", { className: "bottom-segment", children: [
|
|
4563
5280
|
state.currentBlockIndex + 1,
|
|
4564
5281
|
"/",
|
|
4565
5282
|
state.totalBlocks
|
|
4566
5283
|
] }),
|
|
4567
|
-
state.hasCaptions && /* @__PURE__ */
|
|
5284
|
+
state.hasCaptions && /* @__PURE__ */ jsx22(
|
|
4568
5285
|
"button",
|
|
4569
5286
|
{
|
|
4570
5287
|
className: `bottom-ctrl-btn ${state.captionMode !== "off" ? "bottom-ctrl-btn--active" : ""}`,
|
|
4571
5288
|
onClick: () => actions.cycleCaptionMode(),
|
|
4572
5289
|
title: state.captionMode === "off" ? "Captions: Off" : state.captionMode === "standard" ? "Captions: Standard" : "Captions: Social",
|
|
4573
5290
|
"aria-label": "Cycle caption style",
|
|
4574
|
-
children: /* @__PURE__ */
|
|
5291
|
+
children: /* @__PURE__ */ jsx22("svg", { viewBox: "0 0 24 24", fill: "currentColor", width: "18", height: "18", children: /* @__PURE__ */ jsx22("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" }) })
|
|
4575
5292
|
}
|
|
4576
5293
|
)
|
|
4577
5294
|
] });
|
|
4578
5295
|
}
|
|
4579
5296
|
|
|
4580
5297
|
// src/DocControlsSidebar.tsx
|
|
4581
|
-
import { jsx as
|
|
5298
|
+
import { jsx as jsx23, jsxs as jsxs16 } from "react/jsx-runtime";
|
|
4582
5299
|
function DocControlsSidebar({ state, actions }) {
|
|
4583
|
-
return /* @__PURE__ */
|
|
4584
|
-
/* @__PURE__ */
|
|
5300
|
+
return /* @__PURE__ */ jsxs16("div", { className: "doc-controls-sidebar", children: [
|
|
5301
|
+
/* @__PURE__ */ jsx23(
|
|
4585
5302
|
"button",
|
|
4586
5303
|
{
|
|
4587
5304
|
className: "sidebar-ctrl-btn",
|
|
4588
5305
|
onClick: actions.restart,
|
|
4589
5306
|
title: "Restart",
|
|
4590
5307
|
"aria-label": "Restart from beginning",
|
|
4591
|
-
children: /* @__PURE__ */
|
|
5308
|
+
children: /* @__PURE__ */ jsx23("svg", { viewBox: "0 0 24 24", fill: "currentColor", width: "20", height: "20", children: /* @__PURE__ */ jsx23("path", { d: "M12 5V1L7 6l5 5V7c3.31 0 6 2.69 6 6s-2.69 6-6 6-6-2.69-6-6H4c0 4.42 3.58 8 8 8s8-3.58 8-8-3.58-8-8-8z" }) })
|
|
4592
5309
|
}
|
|
4593
5310
|
),
|
|
4594
|
-
/* @__PURE__ */
|
|
5311
|
+
/* @__PURE__ */ jsx23(
|
|
4595
5312
|
"button",
|
|
4596
5313
|
{
|
|
4597
5314
|
className: "sidebar-ctrl-btn sidebar-play-btn",
|
|
4598
5315
|
onClick: actions.toggle,
|
|
4599
5316
|
"aria-label": state.isPlaying ? "Pause" : "Play",
|
|
4600
|
-
children: state.isPlaying ? /* @__PURE__ */
|
|
5317
|
+
children: state.isPlaying ? /* @__PURE__ */ jsx23("svg", { viewBox: "0 0 24 24", fill: "currentColor", width: "22", height: "22", children: /* @__PURE__ */ jsx23("path", { d: "M6 19h4V5H6v14zm8-14v14h4V5h-4z" }) }) : /* @__PURE__ */ jsx23("svg", { viewBox: "0 0 24 24", fill: "currentColor", width: "22", height: "22", children: /* @__PURE__ */ jsx23("path", { d: "M8 5v14l11-7z" }) })
|
|
4601
5318
|
}
|
|
4602
5319
|
),
|
|
4603
|
-
/* @__PURE__ */
|
|
4604
|
-
/* @__PURE__ */
|
|
4605
|
-
/* @__PURE__ */
|
|
5320
|
+
/* @__PURE__ */ jsxs16("div", { className: "sidebar-time", children: [
|
|
5321
|
+
/* @__PURE__ */ jsx23("div", { children: formatTime(state.currentTime) }),
|
|
5322
|
+
/* @__PURE__ */ jsx23("div", { className: "sidebar-time-total", children: formatTime(state.totalDuration) })
|
|
4606
5323
|
] }),
|
|
4607
|
-
/* @__PURE__ */
|
|
5324
|
+
/* @__PURE__ */ jsxs16("div", { className: "sidebar-segment", children: [
|
|
4608
5325
|
state.currentBlockIndex + 1,
|
|
4609
5326
|
"/",
|
|
4610
5327
|
state.totalBlocks
|
|
4611
5328
|
] }),
|
|
4612
|
-
state.hasCaptions && /* @__PURE__ */
|
|
5329
|
+
state.hasCaptions && /* @__PURE__ */ jsx23(
|
|
4613
5330
|
"button",
|
|
4614
5331
|
{
|
|
4615
5332
|
className: `sidebar-ctrl-btn ${state.captionMode !== "off" ? "sidebar-ctrl-btn--active" : ""}`,
|
|
4616
5333
|
onClick: () => actions.cycleCaptionMode(),
|
|
4617
5334
|
title: state.captionMode === "off" ? "Captions: Off" : state.captionMode === "standard" ? "Captions: Standard" : "Captions: Social",
|
|
4618
5335
|
"aria-label": "Cycle caption style",
|
|
4619
|
-
children: /* @__PURE__ */
|
|
5336
|
+
children: /* @__PURE__ */ jsx23("svg", { viewBox: "0 0 24 24", fill: "currentColor", width: "20", height: "20", children: /* @__PURE__ */ jsx23("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" }) })
|
|
4620
5337
|
}
|
|
4621
5338
|
),
|
|
4622
|
-
actions.toggleFullscreen && /* @__PURE__ */
|
|
5339
|
+
actions.toggleFullscreen && /* @__PURE__ */ jsx23(
|
|
4623
5340
|
"button",
|
|
4624
5341
|
{
|
|
4625
5342
|
className: `sidebar-ctrl-btn ${state.isFullscreen ? "sidebar-ctrl-btn--active" : ""}`,
|
|
4626
5343
|
onClick: actions.toggleFullscreen,
|
|
4627
5344
|
title: state.isFullscreen ? "Exit fullscreen (F)" : "Fullscreen (F)",
|
|
4628
5345
|
"aria-label": state.isFullscreen ? "Exit fullscreen" : "Enter fullscreen",
|
|
4629
|
-
children: state.isFullscreen ? /* @__PURE__ */
|
|
5346
|
+
children: state.isFullscreen ? /* @__PURE__ */ jsx23("svg", { viewBox: "0 0 24 24", fill: "currentColor", width: "20", height: "20", children: /* @__PURE__ */ jsx23("path", { d: "M5 16h3v3h2v-5H5v2zm3-8H5v2h5V5H8v3zm6 11h2v-3h3v-2h-5v5zm2-11V5h-2v5h5V8h-3z" }) }) : /* @__PURE__ */ jsx23("svg", { viewBox: "0 0 24 24", fill: "currentColor", width: "20", height: "20", children: /* @__PURE__ */ jsx23("path", { d: "M7 14H5v5h5v-2H7v-3zm-2-4h2V7h3V5H5v5zm12 7h-3v2h5v-5h-2v3zM14 5v2h3v3h2V5h-5z" }) })
|
|
4630
5347
|
}
|
|
4631
5348
|
)
|
|
4632
5349
|
] });
|
|
4633
5350
|
}
|
|
4634
5351
|
|
|
4635
5352
|
// src/DocPlayerWithSidebar.tsx
|
|
4636
|
-
import { useRef as
|
|
4637
|
-
import { jsx as
|
|
5353
|
+
import { useRef as useRef10, useState as useState10, useCallback as useCallback8, useEffect as useEffect11 } from "react";
|
|
5354
|
+
import { jsx as jsx24, jsxs as jsxs17 } from "react/jsx-runtime";
|
|
4638
5355
|
var DEFAULT_STATE = {
|
|
4639
5356
|
isPlaying: false,
|
|
4640
5357
|
currentTime: 0,
|
|
@@ -4656,6 +5373,7 @@ function DocPlayerWithSidebar({
|
|
|
4656
5373
|
onEnded,
|
|
4657
5374
|
onTimeUpdate,
|
|
4658
5375
|
audioController,
|
|
5376
|
+
animationsEnabled = true,
|
|
4659
5377
|
muted,
|
|
4660
5378
|
captionsEnabled,
|
|
4661
5379
|
isFullscreen,
|
|
@@ -4664,11 +5382,11 @@ function DocPlayerWithSidebar({
|
|
|
4664
5382
|
onPlayingChange,
|
|
4665
5383
|
theme
|
|
4666
5384
|
}) {
|
|
4667
|
-
const stateRef =
|
|
4668
|
-
const actionsRef =
|
|
4669
|
-
const wasPlayingRef =
|
|
4670
|
-
const [, setTick] =
|
|
4671
|
-
const handleStateChange =
|
|
5385
|
+
const stateRef = useRef10(DEFAULT_STATE);
|
|
5386
|
+
const actionsRef = useRef10(null);
|
|
5387
|
+
const wasPlayingRef = useRef10(false);
|
|
5388
|
+
const [, setTick] = useState10(0);
|
|
5389
|
+
const handleStateChange = useCallback8(
|
|
4672
5390
|
(state) => {
|
|
4673
5391
|
stateRef.current = state;
|
|
4674
5392
|
if (onPlayingChange && state.isPlaying !== wasPlayingRef.current) {
|
|
@@ -4678,7 +5396,7 @@ function DocPlayerWithSidebar({
|
|
|
4678
5396
|
},
|
|
4679
5397
|
[onPlayingChange]
|
|
4680
5398
|
);
|
|
4681
|
-
const handleControlsReady =
|
|
5399
|
+
const handleControlsReady = useCallback8(
|
|
4682
5400
|
(controls) => {
|
|
4683
5401
|
const isFirst = !actionsRef.current;
|
|
4684
5402
|
actionsRef.current = controls;
|
|
@@ -4686,14 +5404,14 @@ function DocPlayerWithSidebar({
|
|
|
4686
5404
|
},
|
|
4687
5405
|
[]
|
|
4688
5406
|
);
|
|
4689
|
-
|
|
5407
|
+
useEffect11(() => {
|
|
4690
5408
|
const interval = setInterval(() => {
|
|
4691
5409
|
setTick((t) => t + 1);
|
|
4692
5410
|
}, 250);
|
|
4693
5411
|
return () => clearInterval(interval);
|
|
4694
5412
|
}, []);
|
|
4695
|
-
return /* @__PURE__ */
|
|
4696
|
-
/* @__PURE__ */
|
|
5413
|
+
return /* @__PURE__ */ jsxs17("div", { className: "doc-player-sidebar-layout", children: [
|
|
5414
|
+
/* @__PURE__ */ jsx24("div", { className: "doc-player-sidebar-layout__video", children: /* @__PURE__ */ jsx24(
|
|
4697
5415
|
DocPlayer,
|
|
4698
5416
|
{
|
|
4699
5417
|
doc,
|
|
@@ -4703,6 +5421,7 @@ function DocPlayerWithSidebar({
|
|
|
4703
5421
|
onEnded,
|
|
4704
5422
|
onTimeUpdate,
|
|
4705
5423
|
audioController,
|
|
5424
|
+
animationsEnabled,
|
|
4706
5425
|
muted,
|
|
4707
5426
|
captionsEnabled,
|
|
4708
5427
|
showControls: isFullscreen,
|
|
@@ -4714,7 +5433,7 @@ function DocPlayerWithSidebar({
|
|
|
4714
5433
|
forceViewport
|
|
4715
5434
|
}
|
|
4716
5435
|
) }),
|
|
4717
|
-
actionsRef.current && /* @__PURE__ */
|
|
5436
|
+
actionsRef.current && /* @__PURE__ */ jsx24(DocControlsSidebar, { state: stateRef.current, actions: actionsRef.current })
|
|
4718
5437
|
] });
|
|
4719
5438
|
}
|
|
4720
5439
|
|
|
@@ -4745,18 +5464,18 @@ import {
|
|
|
4745
5464
|
arrayItemKind
|
|
4746
5465
|
} from "@bendyline/squisq/jsonForm";
|
|
4747
5466
|
import { parseMarkdown as parseMarkdown3 } from "@bendyline/squisq/markdown";
|
|
4748
|
-
import { Fragment as Fragment5, jsx as
|
|
5467
|
+
import { Fragment as Fragment5, jsx as jsx25, jsxs as jsxs18 } from "react/jsx-runtime";
|
|
4749
5468
|
function TextViewer({ value }) {
|
|
4750
5469
|
if (value === void 0 || value === null || value === "") {
|
|
4751
|
-
return /* @__PURE__ */
|
|
5470
|
+
return /* @__PURE__ */ jsx25("span", { className: "squisq-jv-empty", children: "\u2014" });
|
|
4752
5471
|
}
|
|
4753
|
-
return /* @__PURE__ */
|
|
5472
|
+
return /* @__PURE__ */ jsx25("span", { className: "squisq-jv-value", children: String(value) });
|
|
4754
5473
|
}
|
|
4755
5474
|
function MultilineViewer({ value }) {
|
|
4756
5475
|
if (value === void 0 || value === null || value === "") {
|
|
4757
|
-
return /* @__PURE__ */
|
|
5476
|
+
return /* @__PURE__ */ jsx25("span", { className: "squisq-jv-empty", children: "\u2014" });
|
|
4758
5477
|
}
|
|
4759
|
-
return /* @__PURE__ */
|
|
5478
|
+
return /* @__PURE__ */ jsx25("span", { className: "squisq-jv-value squisq-jv-value--multiline", children: String(value) });
|
|
4760
5479
|
}
|
|
4761
5480
|
function RichTextViewer({ value }) {
|
|
4762
5481
|
const nodes = useMemo11(() => {
|
|
@@ -4768,39 +5487,39 @@ function RichTextViewer({ value }) {
|
|
|
4768
5487
|
return null;
|
|
4769
5488
|
}
|
|
4770
5489
|
}, [value]);
|
|
4771
|
-
if (!nodes) return /* @__PURE__ */
|
|
4772
|
-
return /* @__PURE__ */
|
|
5490
|
+
if (!nodes) return /* @__PURE__ */ jsx25("span", { className: "squisq-jv-empty", children: "\u2014" });
|
|
5491
|
+
return /* @__PURE__ */ jsx25("div", { className: "squisq-jv-richtext", children: /* @__PURE__ */ jsx25(MarkdownRenderer, { nodes }) });
|
|
4773
5492
|
}
|
|
4774
5493
|
function NumberViewer({ value }) {
|
|
4775
5494
|
if (value === void 0 || value === null) {
|
|
4776
|
-
return /* @__PURE__ */
|
|
5495
|
+
return /* @__PURE__ */ jsx25("span", { className: "squisq-jv-empty", children: "\u2014" });
|
|
4777
5496
|
}
|
|
4778
|
-
return /* @__PURE__ */
|
|
5497
|
+
return /* @__PURE__ */ jsx25("span", { className: "squisq-jv-value", children: String(value) });
|
|
4779
5498
|
}
|
|
4780
5499
|
function BooleanViewer({ value }) {
|
|
4781
5500
|
const on = Boolean(value);
|
|
4782
|
-
return /* @__PURE__ */
|
|
5501
|
+
return /* @__PURE__ */ jsx25("span", { className: `squisq-jv-toggle squisq-jv-toggle--${on ? "on" : "off"}`, children: on ? "On" : "Off" });
|
|
4783
5502
|
}
|
|
4784
5503
|
function EnumViewer({ value, schema }) {
|
|
4785
5504
|
if (value === void 0 || value === null || value === "") {
|
|
4786
|
-
return /* @__PURE__ */
|
|
5505
|
+
return /* @__PURE__ */ jsx25("span", { className: "squisq-jv-empty", children: "\u2014" });
|
|
4787
5506
|
}
|
|
4788
5507
|
const labels = schema.squisq?.enumLabels;
|
|
4789
5508
|
const display = labels && typeof value === "string" ? labels[value] ?? value : String(value);
|
|
4790
|
-
return /* @__PURE__ */
|
|
5509
|
+
return /* @__PURE__ */ jsx25("span", { className: "squisq-jv-value", children: display });
|
|
4791
5510
|
}
|
|
4792
5511
|
function ColorViewer({ value }) {
|
|
4793
5512
|
if (typeof value !== "string" || value === "") {
|
|
4794
|
-
return /* @__PURE__ */
|
|
5513
|
+
return /* @__PURE__ */ jsx25("span", { className: "squisq-jv-empty", children: "\u2014" });
|
|
4795
5514
|
}
|
|
4796
|
-
return /* @__PURE__ */
|
|
4797
|
-
/* @__PURE__ */
|
|
4798
|
-
/* @__PURE__ */
|
|
5515
|
+
return /* @__PURE__ */ jsxs18("span", { className: "squisq-jv-color", children: [
|
|
5516
|
+
/* @__PURE__ */ jsx25("span", { className: "squisq-jv-color__swatch", style: { background: value }, "aria-hidden": true }),
|
|
5517
|
+
/* @__PURE__ */ jsx25("span", { className: "squisq-jv-color__hex", children: value })
|
|
4799
5518
|
] });
|
|
4800
5519
|
}
|
|
4801
5520
|
function DateViewer({ value, schema }) {
|
|
4802
5521
|
if (typeof value !== "string" || value === "") {
|
|
4803
|
-
return /* @__PURE__ */
|
|
5522
|
+
return /* @__PURE__ */ jsx25("span", { className: "squisq-jv-empty", children: "\u2014" });
|
|
4804
5523
|
}
|
|
4805
5524
|
const fmt = schema.format;
|
|
4806
5525
|
let display = value;
|
|
@@ -4817,31 +5536,31 @@ function DateViewer({ value, schema }) {
|
|
|
4817
5536
|
}
|
|
4818
5537
|
} catch {
|
|
4819
5538
|
}
|
|
4820
|
-
return /* @__PURE__ */
|
|
5539
|
+
return /* @__PURE__ */ jsx25("span", { className: "squisq-jv-value", children: display });
|
|
4821
5540
|
}
|
|
4822
5541
|
function ChipBinViewer({ value, schema }) {
|
|
4823
5542
|
if (!Array.isArray(value) || value.length === 0) {
|
|
4824
|
-
return /* @__PURE__ */
|
|
5543
|
+
return /* @__PURE__ */ jsx25("span", { className: "squisq-jv-empty", children: "\u2014" });
|
|
4825
5544
|
}
|
|
4826
5545
|
const itemSchema = Array.isArray(schema.items) ? schema.items[0] : schema.items;
|
|
4827
5546
|
const labels = itemSchema?.squisq?.enumLabels;
|
|
4828
|
-
return /* @__PURE__ */
|
|
5547
|
+
return /* @__PURE__ */ jsx25("div", { className: "squisq-jv-chip-bin", children: value.map((item, i) => {
|
|
4829
5548
|
const label = labels && typeof item === "string" ? labels[item] ?? String(item) : String(item);
|
|
4830
|
-
return /* @__PURE__ */
|
|
5549
|
+
return /* @__PURE__ */ jsx25("span", { className: "squisq-jv-chip", children: label }, i);
|
|
4831
5550
|
}) });
|
|
4832
5551
|
}
|
|
4833
5552
|
function CardStackViewer(props) {
|
|
4834
5553
|
const { value, schema, rootSchema, rootData, pointer, density } = props;
|
|
4835
5554
|
if (!Array.isArray(value) || value.length === 0) {
|
|
4836
|
-
return /* @__PURE__ */
|
|
5555
|
+
return /* @__PURE__ */ jsx25("span", { className: "squisq-jv-empty", children: "No items" });
|
|
4837
5556
|
}
|
|
4838
5557
|
const itemSchema = (Array.isArray(schema.items) ? schema.items[0] : schema.items) ?? {};
|
|
4839
5558
|
const itemLabel = itemSchema.squisq?.itemLabel;
|
|
4840
|
-
return /* @__PURE__ */
|
|
5559
|
+
return /* @__PURE__ */ jsx25("div", { className: "squisq-jv-card-stack", children: value.map((item, i) => {
|
|
4841
5560
|
const title = resolveItemTitle(itemLabel, item, i);
|
|
4842
|
-
return /* @__PURE__ */
|
|
4843
|
-
title ? /* @__PURE__ */
|
|
4844
|
-
/* @__PURE__ */
|
|
5561
|
+
return /* @__PURE__ */ jsxs18("div", { className: "squisq-jv-card", children: [
|
|
5562
|
+
title ? /* @__PURE__ */ jsx25("h4", { className: "squisq-jv-card__title", children: title }) : null,
|
|
5563
|
+
/* @__PURE__ */ jsx25(
|
|
4845
5564
|
RenderNode,
|
|
4846
5565
|
{
|
|
4847
5566
|
value: item,
|
|
@@ -4872,16 +5591,16 @@ function GroupViewer(props) {
|
|
|
4872
5591
|
const help = schema.squisq?.help ?? schema.description;
|
|
4873
5592
|
const obj = (value && typeof value === "object" ? value : {}) ?? {};
|
|
4874
5593
|
const propEntries = Object.entries(schema.properties ?? {});
|
|
4875
|
-
return /* @__PURE__ */
|
|
4876
|
-
title ? /* @__PURE__ */
|
|
4877
|
-
help ? /* @__PURE__ */
|
|
4878
|
-
propEntries.map(([key, propSchema]) => /* @__PURE__ */
|
|
5594
|
+
return /* @__PURE__ */ jsxs18("section", { className: "squisq-jv-group", children: [
|
|
5595
|
+
title ? /* @__PURE__ */ jsx25("h3", { className: "squisq-jv-group__title", children: title }) : null,
|
|
5596
|
+
help ? /* @__PURE__ */ jsx25("p", { className: "squisq-jv-group__help", children: help }) : null,
|
|
5597
|
+
propEntries.map(([key, propSchema]) => /* @__PURE__ */ jsx25(Fragment4, { children: /* @__PURE__ */ jsx25(
|
|
4879
5598
|
RowOrSection,
|
|
4880
5599
|
{
|
|
4881
5600
|
label: propSchema.squisq?.label ?? propSchema.title ?? key,
|
|
4882
5601
|
help: propSchema.squisq?.help ?? propSchema.description,
|
|
4883
5602
|
kindHint: propSchema,
|
|
4884
|
-
children: /* @__PURE__ */
|
|
5603
|
+
children: /* @__PURE__ */ jsx25(
|
|
4885
5604
|
RenderNode,
|
|
4886
5605
|
{
|
|
4887
5606
|
value: obj[key],
|
|
@@ -4905,11 +5624,11 @@ function RowOrSection({
|
|
|
4905
5624
|
}) {
|
|
4906
5625
|
const composite = isCompositeKind(kindHint);
|
|
4907
5626
|
if (composite) {
|
|
4908
|
-
return /* @__PURE__ */
|
|
5627
|
+
return /* @__PURE__ */ jsx25(Fragment5, { children });
|
|
4909
5628
|
}
|
|
4910
|
-
return /* @__PURE__ */
|
|
4911
|
-
/* @__PURE__ */
|
|
4912
|
-
/* @__PURE__ */
|
|
5629
|
+
return /* @__PURE__ */ jsxs18("div", { className: "squisq-jv-row", children: [
|
|
5630
|
+
/* @__PURE__ */ jsx25("div", { className: "squisq-jv-label", title: help, children: label }),
|
|
5631
|
+
/* @__PURE__ */ jsx25("div", { children })
|
|
4913
5632
|
] });
|
|
4914
5633
|
}
|
|
4915
5634
|
function isCompositeKind(schema) {
|
|
@@ -4930,11 +5649,11 @@ function TabsViewer(props) {
|
|
|
4930
5649
|
const matchedIndex = pickMatchingBranch(branches, value);
|
|
4931
5650
|
const branch = branches[matchedIndex];
|
|
4932
5651
|
if (!branch) {
|
|
4933
|
-
return /* @__PURE__ */
|
|
5652
|
+
return /* @__PURE__ */ jsx25(TextViewer, { ...props });
|
|
4934
5653
|
}
|
|
4935
|
-
return /* @__PURE__ */
|
|
4936
|
-
/* @__PURE__ */
|
|
4937
|
-
/* @__PURE__ */
|
|
5654
|
+
return /* @__PURE__ */ jsxs18("div", { className: "squisq-jv-tabs", children: [
|
|
5655
|
+
/* @__PURE__ */ jsx25("span", { className: "squisq-jv-tabs__discriminator", children: branch.squisq?.label ?? branch.title ?? `Option ${matchedIndex + 1}` }),
|
|
5656
|
+
/* @__PURE__ */ jsx25(
|
|
4938
5657
|
RenderNode,
|
|
4939
5658
|
{
|
|
4940
5659
|
value,
|
|
@@ -4998,7 +5717,7 @@ var VIEWERS = {
|
|
|
4998
5717
|
};
|
|
4999
5718
|
|
|
5000
5719
|
// src/jsonView/RenderNode.tsx
|
|
5001
|
-
import { jsx as
|
|
5720
|
+
import { jsx as jsx26 } from "react/jsx-runtime";
|
|
5002
5721
|
function RenderNode(props) {
|
|
5003
5722
|
const resolved = resolveRef(props.schema, props.rootSchema) ?? props.schema;
|
|
5004
5723
|
if (resolveFlag(resolved.squisq?.hidden, props.rootData)) return null;
|
|
@@ -5014,18 +5733,18 @@ function RenderNode(props) {
|
|
|
5014
5733
|
};
|
|
5015
5734
|
if (kind === "group" || kind === "card") {
|
|
5016
5735
|
const Group = Viewer;
|
|
5017
|
-
return /* @__PURE__ */
|
|
5736
|
+
return /* @__PURE__ */ jsx26(Group, { ...viewerProps, suppressTitle: props.suppressTopGroupTitle });
|
|
5018
5737
|
}
|
|
5019
|
-
return /* @__PURE__ */
|
|
5738
|
+
return /* @__PURE__ */ jsx26(Viewer, { ...viewerProps });
|
|
5020
5739
|
}
|
|
5021
5740
|
|
|
5022
5741
|
// src/jsonView/JsonView.tsx
|
|
5023
|
-
import { jsx as
|
|
5742
|
+
import { jsx as jsx27 } from "react/jsx-runtime";
|
|
5024
5743
|
function JsonView(props) {
|
|
5025
5744
|
const { schema, value, theme, surface, density = "comfortable", className } = props;
|
|
5026
5745
|
const { style } = useJsonViewTokens(theme, surface);
|
|
5027
5746
|
const cls = "squisq-json-view" + (density === "compact" ? " squisq-json-view--compact" : "") + (className ? ` ${className}` : "");
|
|
5028
|
-
return /* @__PURE__ */
|
|
5747
|
+
return /* @__PURE__ */ jsx27("div", { className: cls, style, children: /* @__PURE__ */ jsx27(
|
|
5029
5748
|
RenderNode,
|
|
5030
5749
|
{
|
|
5031
5750
|
value,
|
|
@@ -5061,7 +5780,7 @@ export {
|
|
|
5061
5780
|
SocialCaptionOverlay,
|
|
5062
5781
|
TableLayer,
|
|
5063
5782
|
TextLayer,
|
|
5064
|
-
|
|
5783
|
+
TreeLayer,
|
|
5065
5784
|
VideoLayer,
|
|
5066
5785
|
formatTime,
|
|
5067
5786
|
getAnimationStyle,
|