@bendyline/squisq-react 1.4.1 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +30 -3
- package/dist/index.d.ts +185 -27
- package/dist/index.js +1323 -614
- package/dist/index.js.map +1 -1
- package/dist/squisq-player.global.js +54 -37
- package/dist/squisq-player.global.js.map +1 -1
- package/dist/standalone-source.js +1 -1
- package/package.json +2 -2
- package/src/BlockRenderer.tsx +53 -17
- package/src/DocControlsSlideshow.tsx +235 -7
- package/src/DocPlayer.tsx +462 -195
- package/src/DocPlayerWithSidebar.tsx +4 -0
- package/src/DocProgressBar.tsx +40 -1
- package/src/LinearDocView.tsx +135 -62
- package/src/MarkdownRenderer.tsx +40 -97
- package/src/MediaClipLayer.tsx +12 -2
- package/src/__tests__/BlockRenderer.test.tsx +79 -8
- package/src/__tests__/DocControlsSlideshow.test.tsx +94 -1
- package/src/__tests__/DocPlayer.test.tsx +556 -2
- package/src/__tests__/DocProgressBar.test.tsx +28 -2
- package/src/__tests__/LinearDocView.test.tsx +91 -11
- package/src/__tests__/MapLayer.test.tsx +63 -0
- package/src/__tests__/MarkdownRenderer.test.tsx +13 -2
- 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 +3 -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/MapLayer.tsx +7 -6
- package/src/layers/PathLayer.tsx +20 -11
- package/src/layers/ShapeLayer.tsx +4 -2
- 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/types.ts +19 -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
|
|
@@ -316,7 +333,7 @@ function remapToKenBurns(anim) {
|
|
|
316
333
|
}
|
|
317
334
|
|
|
318
335
|
// src/layers/TextLayer.tsx
|
|
319
|
-
import { useMemo as useMemo3 } from "react";
|
|
336
|
+
import { useId, useMemo as useMemo3 } from "react";
|
|
320
337
|
import { DEFAULT_DOC_FONT } from "@bendyline/squisq/schemas";
|
|
321
338
|
import {
|
|
322
339
|
parseHtmlToNodes,
|
|
@@ -505,6 +522,7 @@ function IconTextLayer({ layer, viewport, blockTime }) {
|
|
|
505
522
|
) });
|
|
506
523
|
}
|
|
507
524
|
function PlainTextLayer({ layer, viewport, blockTime }) {
|
|
525
|
+
const defsId = `${useId().replace(/:/g, "")}-${layer.id}`;
|
|
508
526
|
const { content, position, animation } = layer;
|
|
509
527
|
const { text, style } = content;
|
|
510
528
|
const rawX = resolveValue(position.x, viewport.width);
|
|
@@ -538,13 +556,13 @@ function PlainTextLayer({ layer, viewport, blockTime }) {
|
|
|
538
556
|
fill: style.color,
|
|
539
557
|
...animStyle.style
|
|
540
558
|
};
|
|
541
|
-
const filterId = style.shadow ? `shadow-${
|
|
559
|
+
const filterId = style.shadow ? `shadow-${defsId}` : void 0;
|
|
542
560
|
return /* @__PURE__ */ jsxs2("g", { className: `block-layer block-layer--text ${animStyle.className}`, "data-layer-id": layer.id, children: [
|
|
543
561
|
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
562
|
/* @__PURE__ */ jsx4(
|
|
545
563
|
TextBox,
|
|
546
564
|
{
|
|
547
|
-
layerId:
|
|
565
|
+
layerId: defsId,
|
|
548
566
|
style,
|
|
549
567
|
box: boxWidth != null && boxHeight != null ? {
|
|
550
568
|
x: rawX - anchorAxis(anchor, boxWidth, "x"),
|
|
@@ -746,9 +764,11 @@ function wrapText(text, fontSize, maxWidth) {
|
|
|
746
764
|
}
|
|
747
765
|
|
|
748
766
|
// src/layers/ShapeLayer.tsx
|
|
767
|
+
import { useId as useId2 } from "react";
|
|
749
768
|
import { jsx as jsx5, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
750
769
|
function ShapeLayer({ layer, viewport, blockTime }) {
|
|
751
770
|
const { content, position, animation } = layer;
|
|
771
|
+
const defsId = `${useId2().replace(/:/g, "")}-${layer.id}`;
|
|
752
772
|
const rawX = resolveValue(position.x, viewport.width);
|
|
753
773
|
const rawY = resolveValue(position.y, viewport.height);
|
|
754
774
|
const width = position.width ? resolveValue(position.width, viewport.width) : 100;
|
|
@@ -782,12 +802,12 @@ function ShapeLayer({ layer, viewport, blockTime }) {
|
|
|
782
802
|
);
|
|
783
803
|
}
|
|
784
804
|
const { fill: fillValue, def: fillDef } = resolveFill(
|
|
785
|
-
|
|
805
|
+
defsId,
|
|
786
806
|
fill,
|
|
787
807
|
content.gradient,
|
|
788
808
|
content.pattern
|
|
789
809
|
);
|
|
790
|
-
const { filterAttr, def: filterDef } = resolveShapeFilter(
|
|
810
|
+
const { filterAttr, def: filterDef } = resolveShapeFilter(defsId, content.filter);
|
|
791
811
|
const dash = borderDashArray(content.borderStyle, content.strokeWidth);
|
|
792
812
|
const shapeProps = {
|
|
793
813
|
fill: fillValue,
|
|
@@ -847,6 +867,7 @@ function ShapeLayer({ layer, viewport, blockTime }) {
|
|
|
847
867
|
}
|
|
848
868
|
|
|
849
869
|
// src/layers/PathLayer.tsx
|
|
870
|
+
import { useId as useId3 } from "react";
|
|
850
871
|
import { markerPath, shapePath } from "@bendyline/squisq/doc";
|
|
851
872
|
import { jsx as jsx6, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
852
873
|
function effectivePath(layer, viewport) {
|
|
@@ -860,23 +881,28 @@ function effectivePath(layer, viewport) {
|
|
|
860
881
|
const derived = shapePath(content.shapeKind, rawX + anchor.x, rawY + anchor.y, w, h);
|
|
861
882
|
return derived ?? content.d;
|
|
862
883
|
}
|
|
863
|
-
function
|
|
884
|
+
function readLegacyArrow(content) {
|
|
885
|
+
return content.arrow;
|
|
886
|
+
}
|
|
887
|
+
function effectiveMarker(explicit, legacyArrow, end) {
|
|
864
888
|
if (explicit) return explicit;
|
|
865
|
-
const wants =
|
|
889
|
+
const wants = legacyArrow === "both" || legacyArrow === end;
|
|
866
890
|
return wants ? "arrow" : "none";
|
|
867
891
|
}
|
|
868
892
|
function PathLayer({ layer, viewport, blockTime }) {
|
|
869
893
|
const { content, animation, id } = layer;
|
|
894
|
+
const defsId = `${useId3().replace(/:/g, "")}-${id}`;
|
|
870
895
|
const d = effectivePath(layer, viewport);
|
|
871
896
|
const stroke = content.stroke ?? "#1e293b";
|
|
872
897
|
const strokeWidth = content.strokeWidth ?? 2;
|
|
873
|
-
const { fill, def: fillDef } = resolveFill(
|
|
898
|
+
const { fill, def: fillDef } = resolveFill(defsId, content.fill ?? "none", content.gradient);
|
|
874
899
|
const dash = content.borderStyle ? borderDashArray(content.borderStyle, strokeWidth) : content.dasharray;
|
|
875
900
|
const animStyle = getAnimationStyle(animation, blockTime);
|
|
876
|
-
const startId = `marker-start-${
|
|
877
|
-
const endId = `marker-end-${
|
|
878
|
-
const
|
|
879
|
-
const
|
|
901
|
+
const startId = `marker-start-${defsId}`;
|
|
902
|
+
const endId = `marker-end-${defsId}`;
|
|
903
|
+
const legacyArrow = readLegacyArrow(content);
|
|
904
|
+
const start = markerPath(effectiveMarker(content.startMarker, legacyArrow, "start"), "start");
|
|
905
|
+
const end = markerPath(effectiveMarker(content.endMarker, legacyArrow, "end"), "end");
|
|
880
906
|
return /* @__PURE__ */ jsxs4(
|
|
881
907
|
"g",
|
|
882
908
|
{
|
|
@@ -938,7 +964,7 @@ function MarkerDef({
|
|
|
938
964
|
}
|
|
939
965
|
|
|
940
966
|
// src/layers/MapLayer.tsx
|
|
941
|
-
import { useState as useState2, useEffect as useEffect3 } from "react";
|
|
967
|
+
import { useId as useId4, useState as useState2, useEffect as useEffect3 } from "react";
|
|
942
968
|
|
|
943
969
|
// src/utils/mapTileUtils.ts
|
|
944
970
|
var TILE_PROVIDERS = {
|
|
@@ -1124,6 +1150,7 @@ function drawAttribution(ctx, text, width, height) {
|
|
|
1124
1150
|
import { jsx as jsx7, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
1125
1151
|
function MapLayer({ layer, basePath, viewport, blockTime }) {
|
|
1126
1152
|
const { content, position, animation } = layer;
|
|
1153
|
+
const clipId = `map-clip-${useId4().replace(/:/g, "")}-${layer.id}`;
|
|
1127
1154
|
const [mapImageUrl, setMapImageUrl] = useState2(null);
|
|
1128
1155
|
const [isLoading, setIsLoading] = useState2(true);
|
|
1129
1156
|
const [error, setError] = useState2(null);
|
|
@@ -1168,11 +1195,12 @@ function MapLayer({ layer, basePath, viewport, blockTime }) {
|
|
|
1168
1195
|
cancelled = true;
|
|
1169
1196
|
};
|
|
1170
1197
|
}, [
|
|
1171
|
-
content.center
|
|
1172
|
-
content.center.lng,
|
|
1198
|
+
content.center,
|
|
1173
1199
|
content.zoom,
|
|
1174
1200
|
content.style,
|
|
1175
1201
|
content.staticSrc,
|
|
1202
|
+
content.markers,
|
|
1203
|
+
content.showAttribution,
|
|
1176
1204
|
width,
|
|
1177
1205
|
height,
|
|
1178
1206
|
basePath
|
|
@@ -1237,8 +1265,8 @@ function MapLayer({ layer, basePath, viewport, blockTime }) {
|
|
|
1237
1265
|
style: animStyle.style,
|
|
1238
1266
|
"data-layer-id": layer.id,
|
|
1239
1267
|
children: [
|
|
1240
|
-
/* @__PURE__ */ jsx7("defs", { children: /* @__PURE__ */ jsx7("clipPath", { id:
|
|
1241
|
-
/* @__PURE__ */ jsx7("g", { clipPath: `url(
|
|
1268
|
+
/* @__PURE__ */ jsx7("defs", { children: /* @__PURE__ */ jsx7("clipPath", { id: clipId, children: /* @__PURE__ */ jsx7("rect", { x: finalX, y: finalY, width, height }) }) }),
|
|
1269
|
+
/* @__PURE__ */ jsx7("g", { clipPath: `url(#${clipId})`, children: /* @__PURE__ */ jsx7(
|
|
1242
1270
|
"image",
|
|
1243
1271
|
{
|
|
1244
1272
|
href: mapImageUrl,
|
|
@@ -1258,6 +1286,7 @@ function MapLayer({ layer, basePath, viewport, blockTime }) {
|
|
|
1258
1286
|
// src/layers/VideoLayer.tsx
|
|
1259
1287
|
import { useRef as useRef2, useEffect as useEffect4 } from "react";
|
|
1260
1288
|
import { jsx as jsx8 } from "react/jsx-runtime";
|
|
1289
|
+
var VIDEO_SYNC_DRIFT_SECONDS = 0.2;
|
|
1261
1290
|
function VideoLayer({ layer, basePath, viewport, blockTime, isPlaying }) {
|
|
1262
1291
|
const { content, position } = layer;
|
|
1263
1292
|
const videoRef = useRef2(null);
|
|
@@ -1297,16 +1326,22 @@ function VideoLayer({ layer, basePath, viewport, blockTime, isPlaying }) {
|
|
|
1297
1326
|
video.removeEventListener("timeupdate", handleTimeUpdate);
|
|
1298
1327
|
video.pause();
|
|
1299
1328
|
};
|
|
1300
|
-
}, [
|
|
1329
|
+
}, [src, content.clipStart, content.clipEnd]);
|
|
1301
1330
|
useEffect4(() => {
|
|
1302
1331
|
const video = videoRef.current;
|
|
1303
1332
|
if (!video || !hasStartedRef.current) return;
|
|
1333
|
+
const targetTime = gated ? content.clipStart : Math.min(content.clipEnd, content.clipStart + Math.max(0, blockTime - startAt));
|
|
1334
|
+
if (Math.abs(video.currentTime - targetTime) > VIDEO_SYNC_DRIFT_SECONDS) {
|
|
1335
|
+
video.currentTime = targetTime;
|
|
1336
|
+
}
|
|
1304
1337
|
if (gated) {
|
|
1305
1338
|
video.pause();
|
|
1306
|
-
video.currentTime = content.clipStart;
|
|
1307
1339
|
return;
|
|
1308
1340
|
}
|
|
1309
|
-
if (
|
|
1341
|
+
if (targetTime >= content.clipEnd) {
|
|
1342
|
+
video.pause();
|
|
1343
|
+
return;
|
|
1344
|
+
}
|
|
1310
1345
|
if (isPlaying) {
|
|
1311
1346
|
const playPromise = video.play();
|
|
1312
1347
|
if (playPromise) {
|
|
@@ -1316,7 +1351,7 @@ function VideoLayer({ layer, basePath, viewport, blockTime, isPlaying }) {
|
|
|
1316
1351
|
} else {
|
|
1317
1352
|
video.pause();
|
|
1318
1353
|
}
|
|
1319
|
-
}, [isPlaying, gated, content.clipStart, content.clipEnd]);
|
|
1354
|
+
}, [isPlaying, gated, blockTime, startAt, src, content.clipStart, content.clipEnd]);
|
|
1320
1355
|
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
1356
|
"video",
|
|
1322
1357
|
{
|
|
@@ -1425,9 +1460,137 @@ function TableLayer({ layer, viewport, blockTime }) {
|
|
|
1425
1460
|
) });
|
|
1426
1461
|
}
|
|
1427
1462
|
|
|
1428
|
-
// src/
|
|
1463
|
+
// src/layers/TreeLayer.tsx
|
|
1464
|
+
import { useState as useState3 } from "react";
|
|
1429
1465
|
import { jsx as jsx10, jsxs as jsxs7 } from "react/jsx-runtime";
|
|
1430
|
-
|
|
1466
|
+
function faClass(token, fallback) {
|
|
1467
|
+
const name = token && token.trim() ? token.trim() : fallback;
|
|
1468
|
+
const colon = name.indexOf(":");
|
|
1469
|
+
if (colon > 0) {
|
|
1470
|
+
const family = name.slice(0, colon).replace(/^fa-/, "");
|
|
1471
|
+
return `fa-${family} fa-${name.slice(colon + 1)}`;
|
|
1472
|
+
}
|
|
1473
|
+
return `fa-solid fa-${name}`;
|
|
1474
|
+
}
|
|
1475
|
+
function TreeLayer({ layer, viewport, blockTime }) {
|
|
1476
|
+
const { content, position, animation } = layer;
|
|
1477
|
+
const { items, style } = content;
|
|
1478
|
+
const x = resolveValue(position.x, viewport.width);
|
|
1479
|
+
const y = resolveValue(position.y, viewport.height);
|
|
1480
|
+
const width = position.width ? resolveValue(position.width, viewport.width) : viewport.width;
|
|
1481
|
+
const height = position.height ? resolveValue(position.height, viewport.height) : viewport.height;
|
|
1482
|
+
const offset = getAnchorOffset(position.anchor, width, height);
|
|
1483
|
+
const animStyle = animation ? getAnimationStyle(animation, blockTime) : {};
|
|
1484
|
+
return /* @__PURE__ */ jsx10(
|
|
1485
|
+
"foreignObject",
|
|
1486
|
+
{
|
|
1487
|
+
x: x + offset.x,
|
|
1488
|
+
y: y + offset.y,
|
|
1489
|
+
width,
|
|
1490
|
+
height,
|
|
1491
|
+
style: animStyle,
|
|
1492
|
+
children: /* @__PURE__ */ jsx10(
|
|
1493
|
+
"div",
|
|
1494
|
+
{
|
|
1495
|
+
...{ xmlns: "http://www.w3.org/1999/xhtml" },
|
|
1496
|
+
className: "squisq-treelayer",
|
|
1497
|
+
style: {
|
|
1498
|
+
width: `${width}px`,
|
|
1499
|
+
height: `${height}px`,
|
|
1500
|
+
display: "flex",
|
|
1501
|
+
flexDirection: "column",
|
|
1502
|
+
justifyContent: "center",
|
|
1503
|
+
padding: "24px 32px",
|
|
1504
|
+
boxSizing: "border-box",
|
|
1505
|
+
fontFamily: style.fontFamily ?? "system-ui, sans-serif",
|
|
1506
|
+
fontSize: `${style.fontSize}px`,
|
|
1507
|
+
lineHeight: 1.7,
|
|
1508
|
+
overflow: "hidden"
|
|
1509
|
+
},
|
|
1510
|
+
children: /* @__PURE__ */ jsx10(TreeList, { items, depth: 0, style })
|
|
1511
|
+
}
|
|
1512
|
+
)
|
|
1513
|
+
}
|
|
1514
|
+
);
|
|
1515
|
+
}
|
|
1516
|
+
function TreeList({
|
|
1517
|
+
items,
|
|
1518
|
+
depth,
|
|
1519
|
+
style
|
|
1520
|
+
}) {
|
|
1521
|
+
return /* @__PURE__ */ jsx10(
|
|
1522
|
+
"ul",
|
|
1523
|
+
{
|
|
1524
|
+
style: {
|
|
1525
|
+
listStyle: "none",
|
|
1526
|
+
margin: 0,
|
|
1527
|
+
padding: 0,
|
|
1528
|
+
paddingLeft: depth === 0 ? 0 : `${style.indentPx}px`,
|
|
1529
|
+
borderLeft: depth === 0 ? "none" : `1px solid ${style.connectorColor}`
|
|
1530
|
+
},
|
|
1531
|
+
children: items.map((item) => /* @__PURE__ */ jsx10(TreeRow, { item, style }, item.id))
|
|
1532
|
+
}
|
|
1533
|
+
);
|
|
1534
|
+
}
|
|
1535
|
+
function TreeRow({
|
|
1536
|
+
item,
|
|
1537
|
+
style
|
|
1538
|
+
}) {
|
|
1539
|
+
const hasChildren = item.children.length > 0;
|
|
1540
|
+
const [collapsed, setCollapsed] = useState3(false);
|
|
1541
|
+
const isDir = item.isDir || hasChildren;
|
|
1542
|
+
const iconCls = isDir ? faClass(style.folderIcon, collapsed ? "folder" : "folder-open") : faClass(style.fileIcon, "file");
|
|
1543
|
+
return /* @__PURE__ */ jsxs7("li", { style: { position: "relative" }, children: [
|
|
1544
|
+
/* @__PURE__ */ jsxs7("div", { style: { display: "flex", alignItems: "baseline", gap: "8px", padding: "1px 0" }, children: [
|
|
1545
|
+
hasChildren ? /* @__PURE__ */ jsx10(
|
|
1546
|
+
"button",
|
|
1547
|
+
{
|
|
1548
|
+
type: "button",
|
|
1549
|
+
"aria-label": collapsed ? "Expand" : "Collapse",
|
|
1550
|
+
onClick: () => setCollapsed((c) => !c),
|
|
1551
|
+
style: {
|
|
1552
|
+
flex: "0 0 auto",
|
|
1553
|
+
width: "1em",
|
|
1554
|
+
border: "none",
|
|
1555
|
+
background: "transparent",
|
|
1556
|
+
cursor: "pointer",
|
|
1557
|
+
color: style.connectorColor,
|
|
1558
|
+
padding: 0,
|
|
1559
|
+
fontSize: "0.8em"
|
|
1560
|
+
},
|
|
1561
|
+
children: /* @__PURE__ */ jsx10(
|
|
1562
|
+
"i",
|
|
1563
|
+
{
|
|
1564
|
+
className: `fa-solid ${collapsed ? "fa-chevron-right" : "fa-chevron-down"}`,
|
|
1565
|
+
"aria-hidden": "true"
|
|
1566
|
+
}
|
|
1567
|
+
)
|
|
1568
|
+
}
|
|
1569
|
+
) : /* @__PURE__ */ jsx10("span", { style: { flex: "0 0 auto", width: "1em" } }),
|
|
1570
|
+
/* @__PURE__ */ jsx10(
|
|
1571
|
+
"i",
|
|
1572
|
+
{
|
|
1573
|
+
className: iconCls,
|
|
1574
|
+
"aria-hidden": "true",
|
|
1575
|
+
style: { flex: "0 0 auto", color: style.iconColor, width: "1.2em", textAlign: "center" }
|
|
1576
|
+
}
|
|
1577
|
+
),
|
|
1578
|
+
/* @__PURE__ */ jsx10(
|
|
1579
|
+
"span",
|
|
1580
|
+
{
|
|
1581
|
+
style: { color: isDir ? style.dirColor : style.rowColor, fontWeight: isDir ? 600 : 400 },
|
|
1582
|
+
children: item.label
|
|
1583
|
+
}
|
|
1584
|
+
),
|
|
1585
|
+
item.comment ? /* @__PURE__ */ jsx10("span", { style: { color: style.commentColor, fontSize: "0.85em", fontStyle: "italic" }, children: item.comment }) : null
|
|
1586
|
+
] }),
|
|
1587
|
+
hasChildren && !collapsed ? /* @__PURE__ */ jsx10(TreeList, { items: item.children, depth: 1, style }) : null
|
|
1588
|
+
] });
|
|
1589
|
+
}
|
|
1590
|
+
|
|
1591
|
+
// src/BlockRenderer.tsx
|
|
1592
|
+
import { jsx as jsx11, jsxs as jsxs8 } from "react/jsx-runtime";
|
|
1593
|
+
var DEFAULT_VIEWPORT = {
|
|
1431
1594
|
width: 1920,
|
|
1432
1595
|
height: 1080
|
|
1433
1596
|
};
|
|
@@ -1438,12 +1601,13 @@ function BlockRenderer({
|
|
|
1438
1601
|
isEntering = false,
|
|
1439
1602
|
isExiting = false,
|
|
1440
1603
|
transition,
|
|
1441
|
-
viewport =
|
|
1442
|
-
isPlaying
|
|
1604
|
+
viewport = DEFAULT_VIEWPORT,
|
|
1605
|
+
isPlaying,
|
|
1606
|
+
animationsEnabled = true
|
|
1443
1607
|
}) {
|
|
1444
1608
|
let transitionClass = "";
|
|
1445
1609
|
const transitionStyle = {};
|
|
1446
|
-
const activeTransition = transition ?? block.transition;
|
|
1610
|
+
const activeTransition = animationsEnabled ? transition ?? block.transition : void 0;
|
|
1447
1611
|
if (activeTransition && isEntering) {
|
|
1448
1612
|
transitionClass = getTransitionClass(activeTransition.type, true, activeTransition.direction);
|
|
1449
1613
|
transitionStyle["--transition-duration"] = `${resolveTransitionDuration(activeTransition)}s`;
|
|
@@ -1451,8 +1615,9 @@ function BlockRenderer({
|
|
|
1451
1615
|
transitionClass = getTransitionClass(activeTransition.type, false, activeTransition.direction);
|
|
1452
1616
|
transitionStyle["--transition-duration"] = `${resolveTransitionDuration(activeTransition)}s`;
|
|
1453
1617
|
}
|
|
1454
|
-
const
|
|
1455
|
-
|
|
1618
|
+
const instanceId = useId5().replace(/:/g, "");
|
|
1619
|
+
const clipId = `vb-clip-${instanceId}-${block.id}`;
|
|
1620
|
+
return /* @__PURE__ */ jsxs8(
|
|
1456
1621
|
"svg",
|
|
1457
1622
|
{
|
|
1458
1623
|
className: `block-svg ${transitionClass}`,
|
|
@@ -1462,15 +1627,16 @@ function BlockRenderer({
|
|
|
1462
1627
|
overflow: "hidden",
|
|
1463
1628
|
"data-block-id": block.id,
|
|
1464
1629
|
children: [
|
|
1465
|
-
/* @__PURE__ */
|
|
1466
|
-
/* @__PURE__ */
|
|
1630
|
+
/* @__PURE__ */ jsx11("defs", { children: /* @__PURE__ */ jsx11("clipPath", { id: clipId, children: /* @__PURE__ */ jsx11("rect", { x: "0", y: "0", width: viewport.width, height: viewport.height }) }) }),
|
|
1631
|
+
/* @__PURE__ */ jsx11("g", { clipPath: `url(#${clipId})`, children: (block.layers ?? []).map((layer) => /* @__PURE__ */ jsx11(
|
|
1467
1632
|
LayerRenderer,
|
|
1468
1633
|
{
|
|
1469
1634
|
layer,
|
|
1470
1635
|
basePath,
|
|
1471
1636
|
viewport,
|
|
1472
1637
|
blockTime,
|
|
1473
|
-
isPlaying
|
|
1638
|
+
isPlaying,
|
|
1639
|
+
animationsEnabled
|
|
1474
1640
|
},
|
|
1475
1641
|
layer.id
|
|
1476
1642
|
)) })
|
|
@@ -1478,23 +1644,47 @@ function BlockRenderer({
|
|
|
1478
1644
|
}
|
|
1479
1645
|
);
|
|
1480
1646
|
}
|
|
1481
|
-
function LayerRenderer({
|
|
1482
|
-
|
|
1647
|
+
function LayerRenderer({
|
|
1648
|
+
layer,
|
|
1649
|
+
basePath,
|
|
1650
|
+
viewport,
|
|
1651
|
+
blockTime,
|
|
1652
|
+
isPlaying,
|
|
1653
|
+
animationsEnabled
|
|
1654
|
+
}) {
|
|
1655
|
+
const renderedLayer = animationsEnabled || !layer.animation ? layer : { ...layer, animation: void 0 };
|
|
1656
|
+
switch (renderedLayer.type) {
|
|
1483
1657
|
case "image":
|
|
1484
|
-
return /* @__PURE__ */
|
|
1658
|
+
return /* @__PURE__ */ jsx11(
|
|
1659
|
+
ImageLayer,
|
|
1660
|
+
{
|
|
1661
|
+
layer: renderedLayer,
|
|
1662
|
+
basePath,
|
|
1663
|
+
viewport,
|
|
1664
|
+
blockTime
|
|
1665
|
+
}
|
|
1666
|
+
);
|
|
1485
1667
|
case "text":
|
|
1486
|
-
return /* @__PURE__ */
|
|
1668
|
+
return /* @__PURE__ */ jsx11(TextLayer, { layer: renderedLayer, viewport, blockTime });
|
|
1487
1669
|
case "shape":
|
|
1488
|
-
return /* @__PURE__ */
|
|
1670
|
+
return /* @__PURE__ */ jsx11(ShapeLayer, { layer: renderedLayer, viewport, blockTime });
|
|
1489
1671
|
case "path":
|
|
1490
|
-
return /* @__PURE__ */
|
|
1672
|
+
return /* @__PURE__ */ jsx11(PathLayer, { layer: renderedLayer, viewport, blockTime });
|
|
1491
1673
|
case "map":
|
|
1492
|
-
return /* @__PURE__ */
|
|
1674
|
+
return /* @__PURE__ */ jsx11(
|
|
1675
|
+
MapLayer,
|
|
1676
|
+
{
|
|
1677
|
+
layer: renderedLayer,
|
|
1678
|
+
basePath,
|
|
1679
|
+
viewport,
|
|
1680
|
+
blockTime
|
|
1681
|
+
}
|
|
1682
|
+
);
|
|
1493
1683
|
case "video":
|
|
1494
|
-
return /* @__PURE__ */
|
|
1684
|
+
return /* @__PURE__ */ jsx11(
|
|
1495
1685
|
VideoLayer,
|
|
1496
1686
|
{
|
|
1497
|
-
layer,
|
|
1687
|
+
layer: renderedLayer,
|
|
1498
1688
|
basePath,
|
|
1499
1689
|
viewport,
|
|
1500
1690
|
blockTime,
|
|
@@ -1502,9 +1692,11 @@ function LayerRenderer({ layer, basePath, viewport, blockTime, isPlaying }) {
|
|
|
1502
1692
|
}
|
|
1503
1693
|
);
|
|
1504
1694
|
case "table":
|
|
1505
|
-
return /* @__PURE__ */
|
|
1695
|
+
return /* @__PURE__ */ jsx11(TableLayer, { layer: renderedLayer, viewport, blockTime });
|
|
1696
|
+
case "tree":
|
|
1697
|
+
return /* @__PURE__ */ jsx11(TreeLayer, { layer: renderedLayer, viewport, blockTime });
|
|
1506
1698
|
default:
|
|
1507
|
-
console.warn(`Unknown layer type: ${
|
|
1699
|
+
console.warn(`Unknown layer type: ${renderedLayer.type}`);
|
|
1508
1700
|
return null;
|
|
1509
1701
|
}
|
|
1510
1702
|
}
|
|
@@ -1515,7 +1707,7 @@ import { getCaptionAtTime } from "@bendyline/squisq/schemas";
|
|
|
1515
1707
|
// src/SocialCaptionOverlay.tsx
|
|
1516
1708
|
import { useMemo as useMemo4 } from "react";
|
|
1517
1709
|
import { resolveFontFamily } from "@bendyline/squisq/schemas";
|
|
1518
|
-
import { jsx as
|
|
1710
|
+
import { jsx as jsx12 } from "react/jsx-runtime";
|
|
1519
1711
|
var TARGET_CHUNK_SIZE = 4;
|
|
1520
1712
|
var MIN_CHUNK_SIZE = 2;
|
|
1521
1713
|
var MAX_CHUNK_SIZE = 6;
|
|
@@ -1574,7 +1766,7 @@ function SocialCaptionOverlay({
|
|
|
1574
1766
|
[captions]
|
|
1575
1767
|
);
|
|
1576
1768
|
if (!enabled || chunks.length === 0) {
|
|
1577
|
-
return /* @__PURE__ */
|
|
1769
|
+
return /* @__PURE__ */ jsx12(
|
|
1578
1770
|
"div",
|
|
1579
1771
|
{
|
|
1580
1772
|
className: "social-caption-overlay",
|
|
@@ -1636,7 +1828,7 @@ function SocialCaptionOverlay({
|
|
|
1636
1828
|
const viewportHeight = viewport?.height ?? 720;
|
|
1637
1829
|
const baseFontSize = Math.round(viewportHeight * 0.055);
|
|
1638
1830
|
const fontSize = Math.max(24, Math.min(72, baseFontSize));
|
|
1639
|
-
return /* @__PURE__ */
|
|
1831
|
+
return /* @__PURE__ */ jsx12(
|
|
1640
1832
|
"div",
|
|
1641
1833
|
{
|
|
1642
1834
|
className: "social-caption-overlay",
|
|
@@ -1653,7 +1845,7 @@ function SocialCaptionOverlay({
|
|
|
1653
1845
|
opacity: 1,
|
|
1654
1846
|
transition: "opacity 0.15s ease-in-out"
|
|
1655
1847
|
},
|
|
1656
|
-
children: /* @__PURE__ */
|
|
1848
|
+
children: /* @__PURE__ */ jsx12(
|
|
1657
1849
|
"div",
|
|
1658
1850
|
{
|
|
1659
1851
|
style: {
|
|
@@ -1662,7 +1854,7 @@ function SocialCaptionOverlay({
|
|
|
1662
1854
|
},
|
|
1663
1855
|
children: activeChunk.words.map((word, i) => {
|
|
1664
1856
|
const isActive = i === activeWordIndex;
|
|
1665
|
-
return /* @__PURE__ */
|
|
1857
|
+
return /* @__PURE__ */ jsx12(
|
|
1666
1858
|
"span",
|
|
1667
1859
|
{
|
|
1668
1860
|
style: {
|
|
@@ -1687,7 +1879,7 @@ function SocialCaptionOverlay({
|
|
|
1687
1879
|
}
|
|
1688
1880
|
|
|
1689
1881
|
// src/CaptionOverlay.tsx
|
|
1690
|
-
import { jsx as
|
|
1882
|
+
import { jsx as jsx13 } from "react/jsx-runtime";
|
|
1691
1883
|
function CaptionOverlay({
|
|
1692
1884
|
captions,
|
|
1693
1885
|
currentTime,
|
|
@@ -1698,7 +1890,7 @@ function CaptionOverlay({
|
|
|
1698
1890
|
viewport
|
|
1699
1891
|
}) {
|
|
1700
1892
|
if (captionStyle === "social") {
|
|
1701
|
-
return /* @__PURE__ */
|
|
1893
|
+
return /* @__PURE__ */ jsx13(
|
|
1702
1894
|
SocialCaptionOverlay,
|
|
1703
1895
|
{
|
|
1704
1896
|
captions,
|
|
@@ -1711,7 +1903,7 @@ function CaptionOverlay({
|
|
|
1711
1903
|
}
|
|
1712
1904
|
const phrase = enabled && captions ? getCaptionAtTime(captions, currentTime) : null;
|
|
1713
1905
|
const captionText = phrase?.text ?? null;
|
|
1714
|
-
return /* @__PURE__ */
|
|
1906
|
+
return /* @__PURE__ */ jsx13(
|
|
1715
1907
|
"div",
|
|
1716
1908
|
{
|
|
1717
1909
|
className: "caption-overlay",
|
|
@@ -1730,7 +1922,7 @@ function CaptionOverlay({
|
|
|
1730
1922
|
padding: "0 4px",
|
|
1731
1923
|
boxSizing: "border-box"
|
|
1732
1924
|
},
|
|
1733
|
-
children: captionText && /* @__PURE__ */
|
|
1925
|
+
children: captionText && /* @__PURE__ */ jsx13(
|
|
1734
1926
|
"div",
|
|
1735
1927
|
{
|
|
1736
1928
|
style: {
|
|
@@ -1740,7 +1932,7 @@ function CaptionOverlay({
|
|
|
1740
1932
|
borderRadius: "4px",
|
|
1741
1933
|
backdropFilter: "blur(4px)"
|
|
1742
1934
|
},
|
|
1743
|
-
children: /* @__PURE__ */
|
|
1935
|
+
children: /* @__PURE__ */ jsx13(
|
|
1744
1936
|
"span",
|
|
1745
1937
|
{
|
|
1746
1938
|
style: {
|
|
@@ -1786,22 +1978,40 @@ function useAutoSurface(enabled) {
|
|
|
1786
1978
|
}
|
|
1787
1979
|
|
|
1788
1980
|
// src/hooks/useAudioSync.ts
|
|
1789
|
-
import { useState as
|
|
1790
|
-
function
|
|
1791
|
-
|
|
1792
|
-
|
|
1793
|
-
|
|
1794
|
-
|
|
1795
|
-
|
|
1796
|
-
const [
|
|
1981
|
+
import { useState as useState4, useEffect as useEffect5, useRef as useRef3, useCallback as useCallback2 } from "react";
|
|
1982
|
+
function resolveAudioUrl(src, basePath) {
|
|
1983
|
+
if (!src || /^(?:[a-z][a-z0-9+.-]*:|\/\/|\/)/i.test(src)) return src;
|
|
1984
|
+
if (!basePath) return src;
|
|
1985
|
+
return `${basePath.replace(/\/$/, "")}/${src.replace(/^\//, "")}`;
|
|
1986
|
+
}
|
|
1987
|
+
function useAudioSync(audioRef, audioTrack, basePath = "", enabled = true) {
|
|
1988
|
+
const [currentTime, setCurrentTime] = useState4(0);
|
|
1989
|
+
const [isPlaying, setIsPlaying] = useState4(false);
|
|
1990
|
+
const [currentSegment, setCurrentSegment] = useState4(0);
|
|
1991
|
+
const [isEnded, setIsEnded] = useState4(false);
|
|
1992
|
+
const [isAudioReady, setIsAudioReady] = useState4(false);
|
|
1993
|
+
const [totalDuration, setTotalDuration] = useState4(0);
|
|
1797
1994
|
const segmentStarts = useRef3([]);
|
|
1798
1995
|
const pendingSeekTime = useRef3(null);
|
|
1799
1996
|
const shouldPlayAfterLoad = useRef3(false);
|
|
1800
1997
|
const blobUrls = useRef3(/* @__PURE__ */ new Map());
|
|
1801
1998
|
const loadingPromises = useRef3(/* @__PURE__ */ new Map());
|
|
1999
|
+
const abortControllers = useRef3(/* @__PURE__ */ new Set());
|
|
2000
|
+
const loadGeneration = useRef3(0);
|
|
1802
2001
|
const fallbackMode = useRef3(false);
|
|
1803
2002
|
useEffect5(() => {
|
|
1804
|
-
|
|
2003
|
+
loadGeneration.current += 1;
|
|
2004
|
+
pendingSeekTime.current = null;
|
|
2005
|
+
shouldPlayAfterLoad.current = false;
|
|
2006
|
+
fallbackMode.current = false;
|
|
2007
|
+
setCurrentTime(0);
|
|
2008
|
+
setCurrentSegment(0);
|
|
2009
|
+
setIsPlaying(false);
|
|
2010
|
+
setIsEnded(false);
|
|
2011
|
+
setIsAudioReady(false);
|
|
2012
|
+
if (!enabled || !audioTrack?.segments) {
|
|
2013
|
+
segmentStarts.current = [];
|
|
2014
|
+
setTotalDuration(0);
|
|
1805
2015
|
return;
|
|
1806
2016
|
}
|
|
1807
2017
|
let time = 0;
|
|
@@ -1811,27 +2021,35 @@ function useAudioSync(audioRef, audioTrack, basePath = "") {
|
|
|
1811
2021
|
return start;
|
|
1812
2022
|
});
|
|
1813
2023
|
setTotalDuration(time);
|
|
1814
|
-
}, [audioTrack]);
|
|
2024
|
+
}, [audioTrack, enabled]);
|
|
1815
2025
|
const preloadAudio = useCallback2(
|
|
1816
2026
|
async (src) => {
|
|
1817
|
-
const audioUrl =
|
|
2027
|
+
const audioUrl = resolveAudioUrl(src, basePath);
|
|
1818
2028
|
if (blobUrls.current.has(src)) {
|
|
1819
2029
|
return blobUrls.current.get(src);
|
|
1820
2030
|
}
|
|
1821
2031
|
if (loadingPromises.current.has(src)) {
|
|
1822
2032
|
return loadingPromises.current.get(src);
|
|
1823
2033
|
}
|
|
2034
|
+
const controller = new AbortController();
|
|
2035
|
+
abortControllers.current.add(controller);
|
|
2036
|
+
const generation = loadGeneration.current;
|
|
1824
2037
|
const loadPromise = (async () => {
|
|
1825
2038
|
try {
|
|
1826
|
-
const response = await fetch(audioUrl);
|
|
2039
|
+
const response = await fetch(audioUrl, { signal: controller.signal });
|
|
1827
2040
|
if (!response.ok) throw new Error(`HTTP ${response.status}`);
|
|
1828
2041
|
const blob = await response.blob();
|
|
1829
2042
|
const blobUrl = URL.createObjectURL(blob);
|
|
2043
|
+
if (controller.signal.aborted || generation !== loadGeneration.current) {
|
|
2044
|
+
URL.revokeObjectURL(blobUrl);
|
|
2045
|
+
return audioUrl;
|
|
2046
|
+
}
|
|
1830
2047
|
blobUrls.current.set(src, blobUrl);
|
|
1831
2048
|
return blobUrl;
|
|
1832
2049
|
} catch {
|
|
1833
2050
|
return audioUrl;
|
|
1834
2051
|
} finally {
|
|
2052
|
+
abortControllers.current.delete(controller);
|
|
1835
2053
|
loadingPromises.current.delete(src);
|
|
1836
2054
|
}
|
|
1837
2055
|
})();
|
|
@@ -1841,19 +2059,26 @@ function useAudioSync(audioRef, audioTrack, basePath = "") {
|
|
|
1841
2059
|
[basePath]
|
|
1842
2060
|
);
|
|
1843
2061
|
useEffect5(() => {
|
|
1844
|
-
if (!audioTrack?.segments) return;
|
|
2062
|
+
if (!enabled || !audioTrack?.segments) return;
|
|
1845
2063
|
audioTrack.segments.forEach((segment) => {
|
|
1846
2064
|
preloadAudio(segment.src);
|
|
1847
2065
|
});
|
|
1848
2066
|
const currentBlobUrls = blobUrls.current;
|
|
2067
|
+
const currentAbortControllers = abortControllers.current;
|
|
2068
|
+
const currentLoadingPromises = loadingPromises.current;
|
|
1849
2069
|
return () => {
|
|
2070
|
+
loadGeneration.current += 1;
|
|
2071
|
+
currentAbortControllers.forEach((controller) => controller.abort());
|
|
2072
|
+
currentAbortControllers.clear();
|
|
2073
|
+
currentLoadingPromises.clear();
|
|
1850
2074
|
currentBlobUrls.forEach((url) => {
|
|
1851
2075
|
URL.revokeObjectURL(url);
|
|
1852
2076
|
});
|
|
1853
2077
|
currentBlobUrls.clear();
|
|
1854
2078
|
};
|
|
1855
|
-
}, [audioTrack, preloadAudio]);
|
|
2079
|
+
}, [audioTrack, preloadAudio, enabled]);
|
|
1856
2080
|
useEffect5(() => {
|
|
2081
|
+
if (!enabled) return;
|
|
1857
2082
|
const audio = audioRef.current;
|
|
1858
2083
|
if (!audio) return;
|
|
1859
2084
|
const handleTimeUpdate = () => {
|
|
@@ -1890,8 +2115,9 @@ function useAudioSync(audioRef, audioTrack, basePath = "") {
|
|
|
1890
2115
|
audio.removeEventListener("ended", handleEnded);
|
|
1891
2116
|
audio.removeEventListener("error", handleError);
|
|
1892
2117
|
};
|
|
1893
|
-
}, [audioRef, currentSegment, audioTrack]);
|
|
2118
|
+
}, [audioRef, currentSegment, audioTrack, enabled]);
|
|
1894
2119
|
useEffect5(() => {
|
|
2120
|
+
if (!enabled) return;
|
|
1895
2121
|
const audio = audioRef.current;
|
|
1896
2122
|
if (!audio || !audioTrack?.segments) return;
|
|
1897
2123
|
const segment = audioTrack.segments[currentSegment];
|
|
@@ -1913,29 +2139,37 @@ function useAudioSync(audioRef, audioTrack, basePath = "") {
|
|
|
1913
2139
|
const currentSrc = audio.src;
|
|
1914
2140
|
const cachedBlobUrl = blobUrls.current.get(segment.src);
|
|
1915
2141
|
const isSameSource = currentSrc && (currentSrc === cachedBlobUrl || currentSrc.endsWith(segment.src));
|
|
2142
|
+
let cancelled = false;
|
|
2143
|
+
let handleCanPlay = null;
|
|
1916
2144
|
if (!isSameSource) {
|
|
1917
2145
|
const loadAndPlay = async () => {
|
|
1918
2146
|
const blobUrl = await preloadAudio(segment.src);
|
|
1919
|
-
|
|
2147
|
+
if (cancelled) return;
|
|
2148
|
+
handleCanPlay = () => {
|
|
2149
|
+
if (cancelled) return;
|
|
1920
2150
|
setIsAudioReady(true);
|
|
1921
2151
|
applyPendingSeek();
|
|
1922
|
-
audio.removeEventListener("canplay", handleCanPlay);
|
|
2152
|
+
if (handleCanPlay) audio.removeEventListener("canplay", handleCanPlay);
|
|
1923
2153
|
};
|
|
1924
2154
|
audio.addEventListener("canplay", handleCanPlay);
|
|
1925
2155
|
audio.src = blobUrl;
|
|
1926
2156
|
audio.load();
|
|
1927
2157
|
await Promise.resolve();
|
|
1928
2158
|
if (audio.readyState >= 3) {
|
|
1929
|
-
audio.removeEventListener("canplay", handleCanPlay);
|
|
2159
|
+
if (handleCanPlay) audio.removeEventListener("canplay", handleCanPlay);
|
|
1930
2160
|
setIsAudioReady(true);
|
|
1931
2161
|
applyPendingSeek();
|
|
1932
2162
|
}
|
|
1933
2163
|
};
|
|
1934
|
-
loadAndPlay();
|
|
2164
|
+
void loadAndPlay();
|
|
1935
2165
|
} else {
|
|
1936
2166
|
applyPendingSeek();
|
|
1937
2167
|
}
|
|
1938
|
-
|
|
2168
|
+
return () => {
|
|
2169
|
+
cancelled = true;
|
|
2170
|
+
if (handleCanPlay) audio.removeEventListener("canplay", handleCanPlay);
|
|
2171
|
+
};
|
|
2172
|
+
}, [audioRef, currentSegment, audioTrack, preloadAudio, enabled]);
|
|
1939
2173
|
const play = useCallback2(() => {
|
|
1940
2174
|
const audio = audioRef.current;
|
|
1941
2175
|
if (audio) {
|
|
@@ -2072,8 +2306,8 @@ import {
|
|
|
2072
2306
|
resolvePersistentLayers,
|
|
2073
2307
|
VIEWPORT_PRESETS
|
|
2074
2308
|
} from "@bendyline/squisq/doc";
|
|
2075
|
-
function useDocPlayback(script, currentTime,
|
|
2076
|
-
|
|
2309
|
+
function useDocPlayback(script, currentTime, options = {}) {
|
|
2310
|
+
const { viewport = VIEWPORT_PRESETS.landscape, theme, onSeek } = options;
|
|
2077
2311
|
const blocks = useMemo6(() => {
|
|
2078
2312
|
if (!script?.blocks) {
|
|
2079
2313
|
return [];
|
|
@@ -2136,8 +2370,19 @@ function useDocPlayback(script, currentTime, viewport = VIEWPORT_PRESETS.landsca
|
|
|
2136
2370
|
const outgoingBlockRef = useRef4(null);
|
|
2137
2371
|
const activeBlockIdRef = useRef4(null);
|
|
2138
2372
|
const lastRenderedBlockRef = useRef4(null);
|
|
2373
|
+
const suppressOutgoingTargetRef = useRef4(null);
|
|
2374
|
+
const suppressOutgoingForNextBlock = useCallback3((blockId) => {
|
|
2375
|
+
if (activeBlockIdRef.current === blockId) {
|
|
2376
|
+
outgoingBlockRef.current = null;
|
|
2377
|
+
suppressOutgoingTargetRef.current = null;
|
|
2378
|
+
return;
|
|
2379
|
+
}
|
|
2380
|
+
suppressOutgoingTargetRef.current = blockId;
|
|
2381
|
+
}, []);
|
|
2139
2382
|
if (currentBlock && currentBlock.id !== activeBlockIdRef.current) {
|
|
2140
|
-
|
|
2383
|
+
const suppressOutgoing = suppressOutgoingTargetRef.current === currentBlock.id;
|
|
2384
|
+
outgoingBlockRef.current = suppressOutgoing ? null : lastRenderedBlockRef.current;
|
|
2385
|
+
suppressOutgoingTargetRef.current = null;
|
|
2141
2386
|
activeBlockIdRef.current = currentBlock.id;
|
|
2142
2387
|
}
|
|
2143
2388
|
lastRenderedBlockRef.current = currentBlock;
|
|
@@ -2151,10 +2396,10 @@ function useDocPlayback(script, currentTime, viewport = VIEWPORT_PRESETS.landsca
|
|
|
2151
2396
|
if (!script || index < 0 || index >= blocks.length) return;
|
|
2152
2397
|
const targetBlock = blocks[index];
|
|
2153
2398
|
if (targetBlock) {
|
|
2154
|
-
|
|
2399
|
+
onSeek?.(targetBlock.startTime);
|
|
2155
2400
|
}
|
|
2156
2401
|
},
|
|
2157
|
-
[script, blocks]
|
|
2402
|
+
[script, blocks, onSeek]
|
|
2158
2403
|
);
|
|
2159
2404
|
const nextBlock = useCallback3(() => {
|
|
2160
2405
|
if (currentBlockIndex < blocks.length - 1) {
|
|
@@ -2178,13 +2423,14 @@ function useDocPlayback(script, currentTime, viewport = VIEWPORT_PRESETS.landsca
|
|
|
2178
2423
|
nextBlock,
|
|
2179
2424
|
prevBlock,
|
|
2180
2425
|
goToBlock,
|
|
2426
|
+
suppressOutgoingForNextBlock,
|
|
2181
2427
|
/** Expanded blocks (templates converted to full blocks with layers) */
|
|
2182
2428
|
blocks
|
|
2183
2429
|
};
|
|
2184
2430
|
}
|
|
2185
2431
|
|
|
2186
2432
|
// src/hooks/useViewportOrientation.ts
|
|
2187
|
-
import { useState as
|
|
2433
|
+
import { useState as useState5, useEffect as useEffect6, useMemo as useMemo7 } from "react";
|
|
2188
2434
|
import {
|
|
2189
2435
|
VIEWPORT_PRESETS as VIEWPORT_PRESETS2
|
|
2190
2436
|
} from "@bendyline/squisq/doc";
|
|
@@ -2195,7 +2441,7 @@ function getOrientationFromWindow(width, height) {
|
|
|
2195
2441
|
} else if (ratio < 0.83) {
|
|
2196
2442
|
return "portrait";
|
|
2197
2443
|
} else {
|
|
2198
|
-
return "
|
|
2444
|
+
return "square";
|
|
2199
2445
|
}
|
|
2200
2446
|
}
|
|
2201
2447
|
function getViewportForOrientation(orientation) {
|
|
@@ -2210,7 +2456,7 @@ function getViewportForOrientation(orientation) {
|
|
|
2210
2456
|
}
|
|
2211
2457
|
}
|
|
2212
2458
|
function useViewportOrientation() {
|
|
2213
|
-
const [windowSize, setWindowSize] =
|
|
2459
|
+
const [windowSize, setWindowSize] = useState5(() => ({
|
|
2214
2460
|
width: typeof window !== "undefined" ? window.innerWidth : 1920,
|
|
2215
2461
|
height: typeof window !== "undefined" ? window.innerHeight : 1080
|
|
2216
2462
|
}));
|
|
@@ -2246,7 +2492,7 @@ function useViewportOrientation() {
|
|
|
2246
2492
|
}
|
|
2247
2493
|
|
|
2248
2494
|
// src/hooks/useSlideSwipe.ts
|
|
2249
|
-
import { useCallback as useCallback4, useEffect as useEffect7, useRef as useRef5, useState as
|
|
2495
|
+
import { useCallback as useCallback4, useEffect as useEffect7, useRef as useRef5, useState as useState6 } from "react";
|
|
2250
2496
|
var DISTANCE_RATIO = 0.3;
|
|
2251
2497
|
var FLICK_VELOCITY = 0.5;
|
|
2252
2498
|
var MIN_FLICK_DISTANCE = 12;
|
|
@@ -2270,8 +2516,8 @@ function decideSwipe({
|
|
|
2270
2516
|
}
|
|
2271
2517
|
function useSlideSwipe(opts) {
|
|
2272
2518
|
const settleMs = opts.settleMs ?? DEFAULT_SETTLE_MS;
|
|
2273
|
-
const [offsetPx, setOffsetPx] =
|
|
2274
|
-
const [phase, setPhase] =
|
|
2519
|
+
const [offsetPx, setOffsetPx] = useState6(0);
|
|
2520
|
+
const [phase, setPhase] = useState6("idle");
|
|
2275
2521
|
const optsRef = useRef5(opts);
|
|
2276
2522
|
optsRef.current = opts;
|
|
2277
2523
|
const dragRef = useRef5(null);
|
|
@@ -2402,7 +2648,7 @@ import {
|
|
|
2402
2648
|
import { parseMarkdown as parseMarkdown2 } from "@bendyline/squisq/markdown";
|
|
2403
2649
|
|
|
2404
2650
|
// src/DocProgressBar.tsx
|
|
2405
|
-
import { useRef as useRef6, useState as
|
|
2651
|
+
import { useRef as useRef6, useState as useState7, useCallback as useCallback5 } from "react";
|
|
2406
2652
|
|
|
2407
2653
|
// src/types.ts
|
|
2408
2654
|
function formatTime(seconds) {
|
|
@@ -2412,7 +2658,7 @@ function formatTime(seconds) {
|
|
|
2412
2658
|
}
|
|
2413
2659
|
|
|
2414
2660
|
// src/DocProgressBar.tsx
|
|
2415
|
-
import { jsx as
|
|
2661
|
+
import { jsx as jsx14, jsxs as jsxs9 } from "react/jsx-runtime";
|
|
2416
2662
|
function DocProgressBar({
|
|
2417
2663
|
state,
|
|
2418
2664
|
actions,
|
|
@@ -2421,7 +2667,7 @@ function DocProgressBar({
|
|
|
2421
2667
|
getBlockTitle
|
|
2422
2668
|
}) {
|
|
2423
2669
|
const progressBarRef = useRef6(null);
|
|
2424
|
-
const [hoverPosition, setHoverPosition] =
|
|
2670
|
+
const [hoverPosition, setHoverPosition] = useState7(null);
|
|
2425
2671
|
const playProgress = state.totalDuration > 0 ? Math.max(0, Math.min(1, state.currentTime / state.totalDuration)) : 0;
|
|
2426
2672
|
const handleProgressHover = useCallback5((e) => {
|
|
2427
2673
|
const bar = progressBarRef.current;
|
|
@@ -2434,6 +2680,31 @@ function DocProgressBar({
|
|
|
2434
2680
|
const handleProgressLeave = useCallback5(() => {
|
|
2435
2681
|
setHoverPosition(null);
|
|
2436
2682
|
}, []);
|
|
2683
|
+
const handleProgressKeyDown = useCallback5(
|
|
2684
|
+
(e) => {
|
|
2685
|
+
let next = null;
|
|
2686
|
+
switch (e.key) {
|
|
2687
|
+
case "ArrowLeft":
|
|
2688
|
+
case "ArrowDown":
|
|
2689
|
+
next = state.currentTime - 5;
|
|
2690
|
+
break;
|
|
2691
|
+
case "ArrowRight":
|
|
2692
|
+
case "ArrowUp":
|
|
2693
|
+
next = state.currentTime + 5;
|
|
2694
|
+
break;
|
|
2695
|
+
case "Home":
|
|
2696
|
+
next = 0;
|
|
2697
|
+
break;
|
|
2698
|
+
case "End":
|
|
2699
|
+
next = state.totalDuration;
|
|
2700
|
+
break;
|
|
2701
|
+
}
|
|
2702
|
+
if (next == null) return;
|
|
2703
|
+
e.preventDefault();
|
|
2704
|
+
actions.seekTo(Math.max(0, Math.min(state.totalDuration, next)));
|
|
2705
|
+
},
|
|
2706
|
+
[actions, state.currentTime, state.totalDuration]
|
|
2707
|
+
);
|
|
2437
2708
|
const getBlockAtTimeLocal = useCallback5(
|
|
2438
2709
|
(time) => {
|
|
2439
2710
|
for (let i = expandedBlocks.length - 1; i >= 0; i--) {
|
|
@@ -2446,10 +2717,12 @@ function DocProgressBar({
|
|
|
2446
2717
|
},
|
|
2447
2718
|
[expandedBlocks]
|
|
2448
2719
|
);
|
|
2449
|
-
return /* @__PURE__ */
|
|
2720
|
+
return /* @__PURE__ */ jsxs9(
|
|
2450
2721
|
"div",
|
|
2451
2722
|
{
|
|
2452
2723
|
ref: progressBarRef,
|
|
2724
|
+
role: "group",
|
|
2725
|
+
"aria-label": "Playback timeline",
|
|
2453
2726
|
style: {
|
|
2454
2727
|
flex: 1,
|
|
2455
2728
|
height: "24px",
|
|
@@ -2467,9 +2740,17 @@ function DocProgressBar({
|
|
|
2467
2740
|
onMouseMove: handleProgressHover,
|
|
2468
2741
|
onMouseLeave: handleProgressLeave,
|
|
2469
2742
|
children: [
|
|
2470
|
-
/* @__PURE__ */
|
|
2743
|
+
/* @__PURE__ */ jsx14(
|
|
2471
2744
|
"div",
|
|
2472
2745
|
{
|
|
2746
|
+
role: "slider",
|
|
2747
|
+
tabIndex: 0,
|
|
2748
|
+
"aria-label": "Playback position",
|
|
2749
|
+
"aria-valuemin": 0,
|
|
2750
|
+
"aria-valuemax": state.totalDuration,
|
|
2751
|
+
"aria-valuenow": Math.max(0, Math.min(state.totalDuration, state.currentTime)),
|
|
2752
|
+
"aria-valuetext": `${formatTime(state.currentTime)} of ${formatTime(state.totalDuration)}`,
|
|
2753
|
+
onKeyDown: handleProgressKeyDown,
|
|
2473
2754
|
style: {
|
|
2474
2755
|
position: "absolute",
|
|
2475
2756
|
left: 0,
|
|
@@ -2480,7 +2761,7 @@ function DocProgressBar({
|
|
|
2480
2761
|
}
|
|
2481
2762
|
}
|
|
2482
2763
|
),
|
|
2483
|
-
/* @__PURE__ */
|
|
2764
|
+
/* @__PURE__ */ jsx14(
|
|
2484
2765
|
"div",
|
|
2485
2766
|
{
|
|
2486
2767
|
"data-testid": "doc-progress-fill",
|
|
@@ -2494,9 +2775,10 @@ function DocProgressBar({
|
|
|
2494
2775
|
}
|
|
2495
2776
|
}
|
|
2496
2777
|
),
|
|
2497
|
-
blockMarkers.map((marker, i) => /* @__PURE__ */
|
|
2498
|
-
"
|
|
2778
|
+
blockMarkers.map((marker, i) => /* @__PURE__ */ jsx14(
|
|
2779
|
+
"button",
|
|
2499
2780
|
{
|
|
2781
|
+
type: "button",
|
|
2500
2782
|
style: {
|
|
2501
2783
|
position: "absolute",
|
|
2502
2784
|
left: `${marker.position}%`,
|
|
@@ -2506,11 +2788,13 @@ function DocProgressBar({
|
|
|
2506
2788
|
borderRadius: "50%",
|
|
2507
2789
|
background: marker.index === state.currentBlockIndex ? "#ffffff" : "rgba(255,255,255,0.5)",
|
|
2508
2790
|
border: "2px solid #5b9bd5",
|
|
2791
|
+
padding: 0,
|
|
2509
2792
|
cursor: "pointer",
|
|
2510
2793
|
zIndex: 2,
|
|
2511
2794
|
transition: "transform 0.15s, background 0.15s"
|
|
2512
2795
|
},
|
|
2513
2796
|
title: marker.title,
|
|
2797
|
+
"aria-label": `Seek to ${marker.title}`,
|
|
2514
2798
|
onClick: (e) => {
|
|
2515
2799
|
e.stopPropagation();
|
|
2516
2800
|
actions.seekTo(marker.block.startTime);
|
|
@@ -2524,7 +2808,7 @@ function DocProgressBar({
|
|
|
2524
2808
|
},
|
|
2525
2809
|
`${marker.block.id}-${i}`
|
|
2526
2810
|
)),
|
|
2527
|
-
hoverPosition !== null && /* @__PURE__ */
|
|
2811
|
+
hoverPosition !== null && /* @__PURE__ */ jsxs9(
|
|
2528
2812
|
"div",
|
|
2529
2813
|
{
|
|
2530
2814
|
style: {
|
|
@@ -2541,19 +2825,19 @@ function DocProgressBar({
|
|
|
2541
2825
|
zIndex: 10
|
|
2542
2826
|
},
|
|
2543
2827
|
children: [
|
|
2544
|
-
/* @__PURE__ */
|
|
2828
|
+
/* @__PURE__ */ jsx14("div", { style: { color: "white", fontSize: "12px", fontFamily: "monospace" }, children: formatTime(hoverPosition * state.totalDuration) }),
|
|
2545
2829
|
(() => {
|
|
2546
2830
|
const hoverTime = hoverPosition * state.totalDuration;
|
|
2547
2831
|
const slideInfo = getBlockAtTimeLocal(hoverTime);
|
|
2548
2832
|
if (slideInfo && getBlockTitle) {
|
|
2549
|
-
return /* @__PURE__ */
|
|
2833
|
+
return /* @__PURE__ */ jsx14("div", { style: { color: "rgba(255,255,255,0.7)", fontSize: "11px", marginTop: "2px" }, children: getBlockTitle(slideInfo.block) });
|
|
2550
2834
|
}
|
|
2551
2835
|
return null;
|
|
2552
2836
|
})()
|
|
2553
2837
|
]
|
|
2554
2838
|
}
|
|
2555
2839
|
),
|
|
2556
|
-
hoverPosition !== null && /* @__PURE__ */
|
|
2840
|
+
hoverPosition !== null && /* @__PURE__ */ jsx14(
|
|
2557
2841
|
"div",
|
|
2558
2842
|
{
|
|
2559
2843
|
style: {
|
|
@@ -2575,7 +2859,7 @@ function DocProgressBar({
|
|
|
2575
2859
|
}
|
|
2576
2860
|
|
|
2577
2861
|
// src/DocControlsOverlay.tsx
|
|
2578
|
-
import { jsx as
|
|
2862
|
+
import { jsx as jsx15, jsxs as jsxs10 } from "react/jsx-runtime";
|
|
2579
2863
|
function DocControlsOverlay({
|
|
2580
2864
|
state,
|
|
2581
2865
|
actions,
|
|
@@ -2583,7 +2867,7 @@ function DocControlsOverlay({
|
|
|
2583
2867
|
expandedBlocks,
|
|
2584
2868
|
getBlockTitle
|
|
2585
2869
|
}) {
|
|
2586
|
-
return /* @__PURE__ */
|
|
2870
|
+
return /* @__PURE__ */ jsxs10(
|
|
2587
2871
|
"div",
|
|
2588
2872
|
{
|
|
2589
2873
|
className: "doc-player__controls",
|
|
@@ -2600,7 +2884,7 @@ function DocControlsOverlay({
|
|
|
2600
2884
|
zIndex: 100
|
|
2601
2885
|
},
|
|
2602
2886
|
children: [
|
|
2603
|
-
/* @__PURE__ */
|
|
2887
|
+
/* @__PURE__ */ jsx15(
|
|
2604
2888
|
"button",
|
|
2605
2889
|
{
|
|
2606
2890
|
onClick: actions.restart,
|
|
@@ -2616,10 +2900,10 @@ function DocControlsOverlay({
|
|
|
2616
2900
|
},
|
|
2617
2901
|
title: "Restart",
|
|
2618
2902
|
"aria-label": "Restart from beginning",
|
|
2619
|
-
children: /* @__PURE__ */
|
|
2903
|
+
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
2904
|
}
|
|
2621
2905
|
),
|
|
2622
|
-
/* @__PURE__ */
|
|
2906
|
+
/* @__PURE__ */ jsx15(
|
|
2623
2907
|
"button",
|
|
2624
2908
|
{
|
|
2625
2909
|
onClick: actions.toggle,
|
|
@@ -2638,15 +2922,15 @@ function DocControlsOverlay({
|
|
|
2638
2922
|
height: "40px"
|
|
2639
2923
|
},
|
|
2640
2924
|
"aria-label": state.isPlaying ? "Pause" : "Play",
|
|
2641
|
-
children: state.isPlaying ? /* @__PURE__ */
|
|
2925
|
+
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
2926
|
}
|
|
2643
2927
|
),
|
|
2644
|
-
/* @__PURE__ */
|
|
2928
|
+
/* @__PURE__ */ jsxs10("span", { style: { color: "white", fontSize: "12px", minWidth: "80px", fontFamily: "monospace" }, children: [
|
|
2645
2929
|
formatTime(state.currentTime),
|
|
2646
2930
|
" / ",
|
|
2647
2931
|
formatTime(state.totalDuration)
|
|
2648
2932
|
] }),
|
|
2649
|
-
/* @__PURE__ */
|
|
2933
|
+
/* @__PURE__ */ jsx15(
|
|
2650
2934
|
DocProgressBar,
|
|
2651
2935
|
{
|
|
2652
2936
|
state,
|
|
@@ -2656,12 +2940,12 @@ function DocControlsOverlay({
|
|
|
2656
2940
|
getBlockTitle
|
|
2657
2941
|
}
|
|
2658
2942
|
),
|
|
2659
|
-
/* @__PURE__ */
|
|
2943
|
+
/* @__PURE__ */ jsxs10("span", { style: { color: "rgba(255,255,255,0.5)", fontSize: "11px", whiteSpace: "nowrap" }, children: [
|
|
2660
2944
|
state.currentBlockIndex + 1,
|
|
2661
2945
|
"/",
|
|
2662
2946
|
state.totalBlocks
|
|
2663
2947
|
] }),
|
|
2664
|
-
state.hasCaptions && /* @__PURE__ */
|
|
2948
|
+
state.hasCaptions && /* @__PURE__ */ jsxs10(
|
|
2665
2949
|
"button",
|
|
2666
2950
|
{
|
|
2667
2951
|
onClick: () => actions.cycleCaptionMode(),
|
|
@@ -2680,12 +2964,12 @@ function DocControlsOverlay({
|
|
|
2680
2964
|
title: state.captionMode === "off" ? "Captions: Off (click for Standard)" : state.captionMode === "standard" ? "Captions: Standard (click for Social)" : "Captions: Social (click to turn off)",
|
|
2681
2965
|
"aria-label": "Cycle caption style",
|
|
2682
2966
|
children: [
|
|
2683
|
-
/* @__PURE__ */
|
|
2684
|
-
state.captionMode !== "off" && /* @__PURE__ */
|
|
2967
|
+
/* @__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" }) }),
|
|
2968
|
+
state.captionMode !== "off" && /* @__PURE__ */ jsx15("span", { style: { fontSize: "10px", textTransform: "uppercase", letterSpacing: "0.5px" }, children: state.captionMode === "standard" ? "CC" : "SM" })
|
|
2685
2969
|
]
|
|
2686
2970
|
}
|
|
2687
2971
|
),
|
|
2688
|
-
actions.toggleFullscreen && /* @__PURE__ */
|
|
2972
|
+
actions.toggleFullscreen && /* @__PURE__ */ jsx15(
|
|
2689
2973
|
"button",
|
|
2690
2974
|
{
|
|
2691
2975
|
onClick: actions.toggleFullscreen,
|
|
@@ -2701,7 +2985,7 @@ function DocControlsOverlay({
|
|
|
2701
2985
|
},
|
|
2702
2986
|
title: state.isFullscreen ? "Exit fullscreen (F)" : "Fullscreen (F)",
|
|
2703
2987
|
"aria-label": state.isFullscreen ? "Exit fullscreen" : "Enter fullscreen",
|
|
2704
|
-
children: state.isFullscreen ? /* @__PURE__ */
|
|
2988
|
+
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
2989
|
}
|
|
2706
2990
|
)
|
|
2707
2991
|
]
|
|
@@ -2710,14 +2994,101 @@ function DocControlsOverlay({
|
|
|
2710
2994
|
}
|
|
2711
2995
|
|
|
2712
2996
|
// src/DocControlsSlideshow.tsx
|
|
2713
|
-
import {
|
|
2714
|
-
|
|
2715
|
-
|
|
2997
|
+
import { useCallback as useCallback6, useEffect as useEffect8, useId as useId6, useLayoutEffect, useRef as useRef7, useState as useState8 } from "react";
|
|
2998
|
+
import { jsx as jsx16, jsxs as jsxs11 } from "react/jsx-runtime";
|
|
2999
|
+
function DocControlsSlideshow({
|
|
3000
|
+
state,
|
|
3001
|
+
slideNav,
|
|
3002
|
+
slides = [],
|
|
3003
|
+
pickerOpen,
|
|
3004
|
+
onPickerOpenChange
|
|
3005
|
+
}) {
|
|
3006
|
+
const [uncontrolledPickerOpen, setUncontrolledPickerOpen] = useState8(false);
|
|
3007
|
+
const isPickerOpen = pickerOpen ?? uncontrolledPickerOpen;
|
|
3008
|
+
const setPickerOpen = useCallback6(
|
|
3009
|
+
(open) => {
|
|
3010
|
+
if (pickerOpen === void 0) setUncontrolledPickerOpen(open);
|
|
3011
|
+
onPickerOpenChange?.(open);
|
|
3012
|
+
},
|
|
3013
|
+
[pickerOpen, onPickerOpenChange]
|
|
3014
|
+
);
|
|
3015
|
+
const [pickerMaxHeight, setPickerMaxHeight] = useState8(280);
|
|
3016
|
+
const controlsRef = useRef7(null);
|
|
3017
|
+
const triggerRef = useRef7(null);
|
|
3018
|
+
const menuRef = useRef7(null);
|
|
3019
|
+
const menuId = useId6();
|
|
3020
|
+
const {
|
|
3021
|
+
currentBlockIndex,
|
|
3022
|
+
currentSlideLabel,
|
|
3023
|
+
currentSlideNumber,
|
|
3024
|
+
totalBlocks,
|
|
3025
|
+
totalSlideNumber
|
|
3026
|
+
} = state;
|
|
2716
3027
|
const isFirst = currentBlockIndex <= 0;
|
|
2717
3028
|
const isLast = currentBlockIndex >= totalBlocks - 1;
|
|
2718
|
-
|
|
3029
|
+
const counterText = totalBlocks > 0 ? currentSlideLabel ?? `${currentSlideNumber ?? currentBlockIndex + 1} / ${totalSlideNumber ?? totalBlocks}` : "\u2014";
|
|
3030
|
+
useEffect8(() => {
|
|
3031
|
+
if (!isPickerOpen) return;
|
|
3032
|
+
const handlePointerDown = (event) => {
|
|
3033
|
+
if (!controlsRef.current?.contains(event.target)) setPickerOpen(false);
|
|
3034
|
+
};
|
|
3035
|
+
const handleKeyDown = (event) => {
|
|
3036
|
+
if (event.key !== "Escape") return;
|
|
3037
|
+
event.preventDefault();
|
|
3038
|
+
setPickerOpen(false);
|
|
3039
|
+
triggerRef.current?.focus();
|
|
3040
|
+
};
|
|
3041
|
+
document.addEventListener("pointerdown", handlePointerDown);
|
|
3042
|
+
document.addEventListener("keydown", handleKeyDown);
|
|
3043
|
+
return () => {
|
|
3044
|
+
document.removeEventListener("pointerdown", handlePointerDown);
|
|
3045
|
+
document.removeEventListener("keydown", handleKeyDown);
|
|
3046
|
+
};
|
|
3047
|
+
}, [isPickerOpen, setPickerOpen]);
|
|
3048
|
+
useLayoutEffect(() => {
|
|
3049
|
+
if (!isPickerOpen) return;
|
|
3050
|
+
const controls = controlsRef.current;
|
|
3051
|
+
const player = controls?.closest(".doc-player") ?? controls?.parentElement;
|
|
3052
|
+
if (!controls || !player) return;
|
|
3053
|
+
const updateMaxHeight = () => {
|
|
3054
|
+
const controlsBounds = controls.getBoundingClientRect();
|
|
3055
|
+
const playerBounds = player.getBoundingClientRect();
|
|
3056
|
+
const availableHeight = Math.floor(controlsBounds.top - playerBounds.top - 8 - 16);
|
|
3057
|
+
setPickerMaxHeight(Math.max(72, availableHeight));
|
|
3058
|
+
};
|
|
3059
|
+
updateMaxHeight();
|
|
3060
|
+
const resizeObserver = typeof ResizeObserver === "undefined" ? null : new ResizeObserver(updateMaxHeight);
|
|
3061
|
+
resizeObserver?.observe(player);
|
|
3062
|
+
resizeObserver?.observe(controls);
|
|
3063
|
+
window.addEventListener("resize", updateMaxHeight);
|
|
3064
|
+
return () => {
|
|
3065
|
+
resizeObserver?.disconnect();
|
|
3066
|
+
window.removeEventListener("resize", updateMaxHeight);
|
|
3067
|
+
};
|
|
3068
|
+
}, [isPickerOpen]);
|
|
3069
|
+
useEffect8(() => {
|
|
3070
|
+
if (!isPickerOpen) return;
|
|
3071
|
+
menuRef.current?.querySelector('[aria-current="true"]')?.focus();
|
|
3072
|
+
}, [isPickerOpen]);
|
|
3073
|
+
const handleMenuKeyDown = (event) => {
|
|
3074
|
+
if (!["ArrowDown", "ArrowUp", "Home", "End"].includes(event.key)) return;
|
|
3075
|
+
const items = Array.from(
|
|
3076
|
+
menuRef.current?.querySelectorAll('[role="menuitem"]') ?? []
|
|
3077
|
+
);
|
|
3078
|
+
if (items.length === 0) return;
|
|
3079
|
+
event.preventDefault();
|
|
3080
|
+
const focusedIndex = items.indexOf(document.activeElement);
|
|
3081
|
+
let nextIndex = focusedIndex;
|
|
3082
|
+
if (event.key === "Home") nextIndex = 0;
|
|
3083
|
+
else if (event.key === "End") nextIndex = items.length - 1;
|
|
3084
|
+
else if (event.key === "ArrowDown") nextIndex = (focusedIndex + 1) % items.length;
|
|
3085
|
+
else nextIndex = (focusedIndex - 1 + items.length) % items.length;
|
|
3086
|
+
items[nextIndex]?.focus();
|
|
3087
|
+
};
|
|
3088
|
+
return /* @__PURE__ */ jsxs11(
|
|
2719
3089
|
"div",
|
|
2720
3090
|
{
|
|
3091
|
+
ref: controlsRef,
|
|
2721
3092
|
className: "doc-controls-slideshow",
|
|
2722
3093
|
"data-testid": "slideshow-controls",
|
|
2723
3094
|
style: {
|
|
@@ -2736,7 +3107,7 @@ function DocControlsSlideshow({ state, slideNav }) {
|
|
|
2736
3107
|
WebkitBackdropFilter: "blur(8px)"
|
|
2737
3108
|
},
|
|
2738
3109
|
children: [
|
|
2739
|
-
/* @__PURE__ */
|
|
3110
|
+
/* @__PURE__ */ jsx16(
|
|
2740
3111
|
"button",
|
|
2741
3112
|
{
|
|
2742
3113
|
onClick: (e) => {
|
|
@@ -2765,27 +3136,138 @@ function DocControlsSlideshow({ state, slideNav }) {
|
|
|
2765
3136
|
onMouseLeave: (e) => {
|
|
2766
3137
|
e.currentTarget.style.background = "none";
|
|
2767
3138
|
},
|
|
2768
|
-
children: /* @__PURE__ */
|
|
3139
|
+
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" }) })
|
|
2769
3140
|
}
|
|
2770
3141
|
),
|
|
2771
|
-
/* @__PURE__ */
|
|
2772
|
-
"
|
|
3142
|
+
/* @__PURE__ */ jsx16(
|
|
3143
|
+
"button",
|
|
2773
3144
|
{
|
|
3145
|
+
ref: triggerRef,
|
|
3146
|
+
type: "button",
|
|
2774
3147
|
"data-testid": "slide-counter",
|
|
3148
|
+
"aria-label": `Choose slide, current ${counterText}`,
|
|
3149
|
+
"aria-haspopup": "menu",
|
|
3150
|
+
"aria-expanded": isPickerOpen,
|
|
3151
|
+
"aria-controls": isPickerOpen ? menuId : void 0,
|
|
3152
|
+
title: "Choose slide",
|
|
3153
|
+
disabled: slides.length === 0,
|
|
3154
|
+
onClick: (event) => {
|
|
3155
|
+
event.stopPropagation();
|
|
3156
|
+
setPickerOpen(!isPickerOpen);
|
|
3157
|
+
},
|
|
2775
3158
|
style: {
|
|
3159
|
+
background: isPickerOpen ? "rgba(255,255,255,0.12)" : "none",
|
|
3160
|
+
border: "none",
|
|
2776
3161
|
color: "rgba(255,255,255,0.9)",
|
|
3162
|
+
cursor: slides.length > 0 ? "pointer" : "default",
|
|
2777
3163
|
fontSize: "13px",
|
|
2778
3164
|
fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif',
|
|
2779
3165
|
fontVariantNumeric: "tabular-nums",
|
|
2780
3166
|
minWidth: "48px",
|
|
2781
3167
|
textAlign: "center",
|
|
2782
|
-
padding: "
|
|
2783
|
-
letterSpacing: "0.02em"
|
|
3168
|
+
padding: "6px 4px",
|
|
3169
|
+
letterSpacing: "0.02em",
|
|
3170
|
+
borderRadius: "4px",
|
|
3171
|
+
transition: "background 0.15s"
|
|
2784
3172
|
},
|
|
2785
|
-
children:
|
|
3173
|
+
children: counterText
|
|
2786
3174
|
}
|
|
2787
3175
|
),
|
|
2788
|
-
/* @__PURE__ */
|
|
3176
|
+
isPickerOpen && /* @__PURE__ */ jsx16(
|
|
3177
|
+
"div",
|
|
3178
|
+
{
|
|
3179
|
+
ref: menuRef,
|
|
3180
|
+
id: menuId,
|
|
3181
|
+
role: "menu",
|
|
3182
|
+
"aria-label": "Choose a slide",
|
|
3183
|
+
"data-testid": "slide-picker",
|
|
3184
|
+
onClick: (event) => event.stopPropagation(),
|
|
3185
|
+
onKeyDown: handleMenuKeyDown,
|
|
3186
|
+
style: {
|
|
3187
|
+
position: "absolute",
|
|
3188
|
+
right: 0,
|
|
3189
|
+
bottom: "calc(100% + 8px)",
|
|
3190
|
+
width: "min(280px, calc(100vw - 32px))",
|
|
3191
|
+
maxHeight: `${pickerMaxHeight}px`,
|
|
3192
|
+
overflowY: "auto",
|
|
3193
|
+
padding: "6px",
|
|
3194
|
+
background: "rgba(20, 20, 20, 0.94)",
|
|
3195
|
+
border: "1px solid rgba(255,255,255,0.14)",
|
|
3196
|
+
borderRadius: "8px",
|
|
3197
|
+
boxShadow: "0 10px 30px rgba(0,0,0,0.38)",
|
|
3198
|
+
backdropFilter: "blur(12px)",
|
|
3199
|
+
WebkitBackdropFilter: "blur(12px)"
|
|
3200
|
+
},
|
|
3201
|
+
children: slides.map((slide, index) => {
|
|
3202
|
+
const isCurrent = index === currentBlockIndex;
|
|
3203
|
+
return /* @__PURE__ */ jsxs11(
|
|
3204
|
+
"button",
|
|
3205
|
+
{
|
|
3206
|
+
type: "button",
|
|
3207
|
+
role: "menuitem",
|
|
3208
|
+
"aria-current": isCurrent ? "true" : void 0,
|
|
3209
|
+
"data-testid": `slide-picker-item-${index}`,
|
|
3210
|
+
onClick: () => {
|
|
3211
|
+
slideNav.goToSlide(index);
|
|
3212
|
+
setPickerOpen(false);
|
|
3213
|
+
},
|
|
3214
|
+
style: {
|
|
3215
|
+
display: "grid",
|
|
3216
|
+
gridTemplateColumns: "42px minmax(0, 1fr)",
|
|
3217
|
+
alignItems: "center",
|
|
3218
|
+
gap: "8px",
|
|
3219
|
+
width: "100%",
|
|
3220
|
+
padding: "8px 10px",
|
|
3221
|
+
background: isCurrent ? "rgba(255,255,255,0.14)" : "transparent",
|
|
3222
|
+
border: "none",
|
|
3223
|
+
borderRadius: "5px",
|
|
3224
|
+
color: "rgba(255,255,255,0.94)",
|
|
3225
|
+
cursor: "pointer",
|
|
3226
|
+
fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif',
|
|
3227
|
+
textAlign: "left"
|
|
3228
|
+
},
|
|
3229
|
+
onMouseEnter: (event) => {
|
|
3230
|
+
event.currentTarget.style.background = "rgba(255,255,255,0.1)";
|
|
3231
|
+
},
|
|
3232
|
+
onMouseLeave: (event) => {
|
|
3233
|
+
event.currentTarget.style.background = isCurrent ? "rgba(255,255,255,0.14)" : "transparent";
|
|
3234
|
+
},
|
|
3235
|
+
children: [
|
|
3236
|
+
/* @__PURE__ */ jsx16(
|
|
3237
|
+
"span",
|
|
3238
|
+
{
|
|
3239
|
+
style: {
|
|
3240
|
+
color: isCurrent ? "#fff" : "rgba(255,255,255,0.58)",
|
|
3241
|
+
fontSize: "12px",
|
|
3242
|
+
fontVariantNumeric: "tabular-nums",
|
|
3243
|
+
textAlign: "right"
|
|
3244
|
+
},
|
|
3245
|
+
children: slide.label
|
|
3246
|
+
}
|
|
3247
|
+
),
|
|
3248
|
+
/* @__PURE__ */ jsx16(
|
|
3249
|
+
"span",
|
|
3250
|
+
{
|
|
3251
|
+
style: {
|
|
3252
|
+
minWidth: 0,
|
|
3253
|
+
overflow: "hidden",
|
|
3254
|
+
color: isCurrent ? "#fff" : "rgba(255,255,255,0.82)",
|
|
3255
|
+
fontSize: "13px",
|
|
3256
|
+
lineHeight: 1.3,
|
|
3257
|
+
textOverflow: "ellipsis",
|
|
3258
|
+
whiteSpace: "nowrap"
|
|
3259
|
+
},
|
|
3260
|
+
children: slide.summary
|
|
3261
|
+
}
|
|
3262
|
+
)
|
|
3263
|
+
]
|
|
3264
|
+
},
|
|
3265
|
+
`${slide.id}-${index}`
|
|
3266
|
+
);
|
|
3267
|
+
})
|
|
3268
|
+
}
|
|
3269
|
+
),
|
|
3270
|
+
/* @__PURE__ */ jsx16(
|
|
2789
3271
|
"button",
|
|
2790
3272
|
{
|
|
2791
3273
|
onClick: (e) => {
|
|
@@ -2814,7 +3296,7 @@ function DocControlsSlideshow({ state, slideNav }) {
|
|
|
2814
3296
|
onMouseLeave: (e) => {
|
|
2815
3297
|
e.currentTarget.style.background = "none";
|
|
2816
3298
|
},
|
|
2817
|
-
children: /* @__PURE__ */
|
|
3299
|
+
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" }) })
|
|
2818
3300
|
}
|
|
2819
3301
|
)
|
|
2820
3302
|
]
|
|
@@ -2823,18 +3305,18 @@ function DocControlsSlideshow({ state, slideNav }) {
|
|
|
2823
3305
|
}
|
|
2824
3306
|
|
|
2825
3307
|
// src/LinearDocView.tsx
|
|
2826
|
-
import { useMemo as useMemo8 } from "react";
|
|
3308
|
+
import { useEffect as useEffect9, useMemo as useMemo8, useRef as useRef8 } from "react";
|
|
2827
3309
|
import {
|
|
2828
3310
|
applySurface,
|
|
2829
3311
|
resolveFontFamily as resolveFontFamily2
|
|
2830
3312
|
} from "@bendyline/squisq/schemas";
|
|
2831
3313
|
import { VIEWPORT_PRESETS as VIEWPORT_PRESETS3 } from "@bendyline/squisq/schemas";
|
|
2832
3314
|
import {
|
|
2833
|
-
|
|
2834
|
-
hasTemplate,
|
|
3315
|
+
materializeBlockLayers,
|
|
2835
3316
|
markdownToDoc,
|
|
2836
3317
|
DEFAULT_THEME as DEFAULT_THEME2,
|
|
2837
|
-
deriveTemplateInputs
|
|
3318
|
+
deriveTemplateInputs,
|
|
3319
|
+
isTemplateBlock as isTemplateBlock2
|
|
2838
3320
|
} from "@bendyline/squisq/doc";
|
|
2839
3321
|
import { extractPlainText, parseMarkdown } from "@bendyline/squisq/markdown";
|
|
2840
3322
|
|
|
@@ -2846,7 +3328,7 @@ import {
|
|
|
2846
3328
|
} from "@bendyline/squisq/markdown";
|
|
2847
3329
|
|
|
2848
3330
|
// src/InlineVideoPlayer.tsx
|
|
2849
|
-
import { jsx as
|
|
3331
|
+
import { jsx as jsx17 } from "react/jsx-runtime";
|
|
2850
3332
|
function InlineVideoPlayer({
|
|
2851
3333
|
src,
|
|
2852
3334
|
basePath = "",
|
|
@@ -2861,7 +3343,7 @@ function InlineVideoPlayer({
|
|
|
2861
3343
|
const resolvedPoster = useMediaUrl(poster ?? "", basePath);
|
|
2862
3344
|
const posterUrl = poster ? resolvedPoster : void 0;
|
|
2863
3345
|
if (!resolvedSrc) return null;
|
|
2864
|
-
return /* @__PURE__ */
|
|
3346
|
+
return /* @__PURE__ */ jsx17("span", { className: `squisq-inline-video-player ${className ?? ""}`.trim(), children: /* @__PURE__ */ jsx17(
|
|
2865
3347
|
"video",
|
|
2866
3348
|
{
|
|
2867
3349
|
src: resolvedSrc,
|
|
@@ -2876,7 +3358,7 @@ function InlineVideoPlayer({
|
|
|
2876
3358
|
}
|
|
2877
3359
|
|
|
2878
3360
|
// src/InlineAudioPlayer.tsx
|
|
2879
|
-
import { jsx as
|
|
3361
|
+
import { jsx as jsx18 } from "react/jsx-runtime";
|
|
2880
3362
|
function InlineAudioPlayer({
|
|
2881
3363
|
src,
|
|
2882
3364
|
basePath = "",
|
|
@@ -2886,11 +3368,11 @@ function InlineAudioPlayer({
|
|
|
2886
3368
|
}) {
|
|
2887
3369
|
const resolvedSrc = useMediaUrl(src, basePath);
|
|
2888
3370
|
if (!resolvedSrc) return null;
|
|
2889
|
-
return /* @__PURE__ */
|
|
3371
|
+
return /* @__PURE__ */ jsx18("span", { className: `squisq-inline-audio-player ${className ?? ""}`.trim(), children: /* @__PURE__ */ jsx18("audio", { src: resolvedSrc, controls, preload }) });
|
|
2890
3372
|
}
|
|
2891
3373
|
|
|
2892
3374
|
// src/MarkdownRenderer.tsx
|
|
2893
|
-
import { jsx as
|
|
3375
|
+
import { jsx as jsx19, jsxs as jsxs12 } from "react/jsx-runtime";
|
|
2894
3376
|
var DEFAULT_CTX = { htmlPolicy: "sanitize" };
|
|
2895
3377
|
function renderInline(nodes, keyPrefix = "", ctx = DEFAULT_CTX) {
|
|
2896
3378
|
return nodes.map((node, i) => {
|
|
@@ -2898,28 +3380,28 @@ function renderInline(nodes, keyPrefix = "", ctx = DEFAULT_CTX) {
|
|
|
2898
3380
|
switch (node.type) {
|
|
2899
3381
|
case "text": {
|
|
2900
3382
|
if (!node.value.includes("\n")) {
|
|
2901
|
-
return /* @__PURE__ */
|
|
3383
|
+
return /* @__PURE__ */ jsx19(Fragment2, { children: node.value }, key);
|
|
2902
3384
|
}
|
|
2903
3385
|
const parts = node.value.split("\n");
|
|
2904
|
-
return /* @__PURE__ */
|
|
2905
|
-
j > 0 && /* @__PURE__ */
|
|
3386
|
+
return /* @__PURE__ */ jsx19(Fragment2, { children: parts.map((part, j) => /* @__PURE__ */ jsxs12(Fragment2, { children: [
|
|
3387
|
+
j > 0 && /* @__PURE__ */ jsx19("br", {}),
|
|
2906
3388
|
part
|
|
2907
3389
|
] }, j)) }, key);
|
|
2908
3390
|
}
|
|
2909
3391
|
case "emphasis":
|
|
2910
|
-
return /* @__PURE__ */
|
|
3392
|
+
return /* @__PURE__ */ jsx19("em", { className: "squisq-md-em", children: renderInline(node.children, key, ctx) }, key);
|
|
2911
3393
|
case "strong":
|
|
2912
|
-
return /* @__PURE__ */
|
|
3394
|
+
return /* @__PURE__ */ jsx19("strong", { className: "squisq-md-strong", children: renderInline(node.children, key, ctx) }, key);
|
|
2913
3395
|
case "delete":
|
|
2914
|
-
return /* @__PURE__ */
|
|
3396
|
+
return /* @__PURE__ */ jsx19("del", { className: "squisq-md-del", children: renderInline(node.children, key, ctx) }, key);
|
|
2915
3397
|
case "inlineCode":
|
|
2916
|
-
return /* @__PURE__ */
|
|
3398
|
+
return /* @__PURE__ */ jsx19("code", { className: "squisq-md-inline-code", children: node.value }, key);
|
|
2917
3399
|
case "link": {
|
|
2918
3400
|
const href = sanitizeUrl(node.url, "link", { extraLinkSchemes: ctx.linkSchemes });
|
|
2919
3401
|
if (!href) {
|
|
2920
|
-
return /* @__PURE__ */
|
|
3402
|
+
return /* @__PURE__ */ jsx19("span", { className: "squisq-md-link squisq-md-link--blocked", children: renderInline(node.children, key, ctx) }, key);
|
|
2921
3403
|
}
|
|
2922
|
-
return /* @__PURE__ */
|
|
3404
|
+
return /* @__PURE__ */ jsx19(
|
|
2923
3405
|
"a",
|
|
2924
3406
|
{
|
|
2925
3407
|
className: "squisq-md-link",
|
|
@@ -2933,42 +3415,32 @@ function renderInline(nodes, keyPrefix = "", ctx = DEFAULT_CTX) {
|
|
|
2933
3415
|
);
|
|
2934
3416
|
}
|
|
2935
3417
|
case "image":
|
|
2936
|
-
return /* @__PURE__ */
|
|
3418
|
+
return /* @__PURE__ */ jsx19(MdImage, { src: node.url, alt: node.alt ?? "", title: node.title ?? void 0 }, key);
|
|
2937
3419
|
case "break":
|
|
2938
|
-
return /* @__PURE__ */
|
|
3420
|
+
return /* @__PURE__ */ jsx19("br", {}, key);
|
|
2939
3421
|
case "inlineMath":
|
|
2940
|
-
return /* @__PURE__ */
|
|
3422
|
+
return /* @__PURE__ */ jsx19("code", { className: "squisq-md-inline-math", children: node.value }, key);
|
|
2941
3423
|
case "htmlInline":
|
|
2942
3424
|
if (ctx.htmlPolicy === "strip") return null;
|
|
2943
|
-
|
|
2944
|
-
return /* @__PURE__ */ jsx18(
|
|
2945
|
-
"span",
|
|
2946
|
-
{
|
|
2947
|
-
className: "squisq-md-html-inline",
|
|
2948
|
-
dangerouslySetInnerHTML: { __html: node.rawHtml }
|
|
2949
|
-
},
|
|
2950
|
-
key
|
|
2951
|
-
);
|
|
2952
|
-
}
|
|
2953
|
-
return /* @__PURE__ */ jsx18("span", { className: "squisq-md-html-inline", children: renderHtmlNodes(resolveHtmlNodes(node.htmlChildren, ctx.htmlPolicy), `${key}h`) }, key);
|
|
3425
|
+
return /* @__PURE__ */ jsx19("span", { className: "squisq-md-html-inline", children: renderHtmlNodes(resolveHtmlNodes(node.htmlChildren, ctx.htmlPolicy), `${key}h`, ctx) }, key);
|
|
2954
3426
|
case "footnoteReference":
|
|
2955
|
-
return /* @__PURE__ */
|
|
3427
|
+
return /* @__PURE__ */ jsx19("sup", { className: "squisq-md-footnote-ref", children: /* @__PURE__ */ jsxs12("a", { href: `#fn-${node.identifier}`, children: [
|
|
2956
3428
|
"[",
|
|
2957
3429
|
node.label ?? node.identifier,
|
|
2958
3430
|
"]"
|
|
2959
3431
|
] }) }, key);
|
|
2960
3432
|
case "linkReference":
|
|
2961
|
-
return /* @__PURE__ */
|
|
3433
|
+
return /* @__PURE__ */ jsx19("span", { className: "squisq-md-link-ref", children: renderInline(node.children, key, ctx) }, key);
|
|
2962
3434
|
case "imageReference":
|
|
2963
|
-
return /* @__PURE__ */
|
|
3435
|
+
return /* @__PURE__ */ jsxs12("span", { className: "squisq-md-image-ref", children: [
|
|
2964
3436
|
"[",
|
|
2965
3437
|
node.alt ?? node.identifier,
|
|
2966
3438
|
"]"
|
|
2967
3439
|
] }, key);
|
|
2968
3440
|
case "textDirective":
|
|
2969
|
-
return /* @__PURE__ */
|
|
3441
|
+
return /* @__PURE__ */ jsx19("span", { className: "squisq-md-text-directive", "data-directive": node.name, children: renderInline(node.children, key, ctx) }, key);
|
|
2970
3442
|
case "mention":
|
|
2971
|
-
return /* @__PURE__ */
|
|
3443
|
+
return /* @__PURE__ */ jsxs12(
|
|
2972
3444
|
"span",
|
|
2973
3445
|
{
|
|
2974
3446
|
className: "squisq-md-mention mention",
|
|
@@ -2991,61 +3463,51 @@ function renderInline(nodes, keyPrefix = "", ctx = DEFAULT_CTX) {
|
|
|
2991
3463
|
function renderBlock(node, key, ctx = DEFAULT_CTX) {
|
|
2992
3464
|
switch (node.type) {
|
|
2993
3465
|
case "paragraph":
|
|
2994
|
-
return /* @__PURE__ */
|
|
3466
|
+
return /* @__PURE__ */ jsx19("p", { className: "squisq-md-p", children: renderInline(node.children, key, ctx) }, key);
|
|
2995
3467
|
case "heading": {
|
|
2996
3468
|
const Tag = `h${node.depth}`;
|
|
2997
|
-
return /* @__PURE__ */
|
|
3469
|
+
return /* @__PURE__ */ jsx19(Tag, { className: `squisq-md-heading squisq-md-h${node.depth}`, children: renderInline(node.children, key, ctx) }, key);
|
|
2998
3470
|
}
|
|
2999
3471
|
case "blockquote":
|
|
3000
|
-
return /* @__PURE__ */
|
|
3472
|
+
return /* @__PURE__ */ jsx19("blockquote", { className: "squisq-md-blockquote", children: renderBlocks(node.children, key, ctx) }, key);
|
|
3001
3473
|
case "list":
|
|
3002
3474
|
if (node.ordered) {
|
|
3003
|
-
return /* @__PURE__ */
|
|
3475
|
+
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);
|
|
3004
3476
|
}
|
|
3005
|
-
return /* @__PURE__ */
|
|
3477
|
+
return /* @__PURE__ */ jsx19("ul", { className: "squisq-md-list squisq-md-ul", children: node.children.map((item, i) => renderListItem(item, `${key}li${i}`, ctx)) }, key);
|
|
3006
3478
|
case "code":
|
|
3007
|
-
return /* @__PURE__ */
|
|
3479
|
+
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);
|
|
3008
3480
|
case "thematicBreak":
|
|
3009
|
-
return /* @__PURE__ */
|
|
3481
|
+
return /* @__PURE__ */ jsx19("hr", { className: "squisq-md-hr" }, key);
|
|
3010
3482
|
case "table":
|
|
3011
3483
|
return renderTable(node.children, node.align, key, ctx);
|
|
3012
3484
|
case "htmlBlock":
|
|
3013
3485
|
if (ctx.htmlPolicy === "strip") return null;
|
|
3014
|
-
|
|
3015
|
-
return /* @__PURE__ */ jsx18(
|
|
3016
|
-
"div",
|
|
3017
|
-
{
|
|
3018
|
-
className: "squisq-md-html-block",
|
|
3019
|
-
dangerouslySetInnerHTML: { __html: node.rawHtml }
|
|
3020
|
-
},
|
|
3021
|
-
key
|
|
3022
|
-
);
|
|
3023
|
-
}
|
|
3024
|
-
return /* @__PURE__ */ jsx18("div", { className: "squisq-md-html-block", children: renderHtmlNodes(resolveHtmlNodes(node.htmlChildren, ctx.htmlPolicy), `${key}h`) }, key);
|
|
3486
|
+
return /* @__PURE__ */ jsx19("div", { className: "squisq-md-html-block", children: renderHtmlNodes(resolveHtmlNodes(node.htmlChildren, ctx.htmlPolicy), `${key}h`, ctx) }, key);
|
|
3025
3487
|
case "math":
|
|
3026
|
-
return /* @__PURE__ */
|
|
3488
|
+
return /* @__PURE__ */ jsx19("pre", { className: "squisq-md-math-block", children: /* @__PURE__ */ jsx19("code", { children: node.value }) }, key);
|
|
3027
3489
|
case "definition":
|
|
3028
3490
|
return null;
|
|
3029
3491
|
case "footnoteDefinition":
|
|
3030
|
-
return /* @__PURE__ */
|
|
3031
|
-
/* @__PURE__ */
|
|
3492
|
+
return /* @__PURE__ */ jsxs12("div", { className: "squisq-md-footnote-def", id: `fn-${node.identifier}`, children: [
|
|
3493
|
+
/* @__PURE__ */ jsx19("sup", { children: node.label ?? node.identifier }),
|
|
3032
3494
|
renderBlocks(node.children, key, ctx)
|
|
3033
3495
|
] }, key);
|
|
3034
3496
|
case "containerDirective":
|
|
3035
|
-
return /* @__PURE__ */
|
|
3497
|
+
return /* @__PURE__ */ jsxs12(
|
|
3036
3498
|
"div",
|
|
3037
3499
|
{
|
|
3038
3500
|
className: `squisq-md-directive squisq-md-directive-${node.name}`,
|
|
3039
3501
|
"data-directive": node.name,
|
|
3040
3502
|
children: [
|
|
3041
|
-
node.label && /* @__PURE__ */
|
|
3503
|
+
node.label && /* @__PURE__ */ jsx19("div", { className: "squisq-md-directive-label", children: node.label }),
|
|
3042
3504
|
renderBlocks(node.children, key, ctx)
|
|
3043
3505
|
]
|
|
3044
3506
|
},
|
|
3045
3507
|
key
|
|
3046
3508
|
);
|
|
3047
3509
|
case "leafDirective":
|
|
3048
|
-
return /* @__PURE__ */
|
|
3510
|
+
return /* @__PURE__ */ jsx19(
|
|
3049
3511
|
"div",
|
|
3050
3512
|
{
|
|
3051
3513
|
className: `squisq-md-directive squisq-md-directive-${node.name}`,
|
|
@@ -3055,11 +3517,11 @@ function renderBlock(node, key, ctx = DEFAULT_CTX) {
|
|
|
3055
3517
|
key
|
|
3056
3518
|
);
|
|
3057
3519
|
case "definitionList":
|
|
3058
|
-
return /* @__PURE__ */
|
|
3520
|
+
return /* @__PURE__ */ jsx19("dl", { className: "squisq-md-dl", children: node.children.map((child, i) => {
|
|
3059
3521
|
if (child.type === "definitionTerm") {
|
|
3060
|
-
return /* @__PURE__ */
|
|
3522
|
+
return /* @__PURE__ */ jsx19("dt", { className: "squisq-md-dt", children: renderInline(child.children, `${key}dt${i}`, ctx) }, `${key}dt${i}`);
|
|
3061
3523
|
}
|
|
3062
|
-
return /* @__PURE__ */
|
|
3524
|
+
return /* @__PURE__ */ jsx19("dd", { className: "squisq-md-dd", children: renderBlocks(child.children, `${key}dd${i}`, ctx) }, `${key}dd${i}`);
|
|
3063
3525
|
}) }, key);
|
|
3064
3526
|
default:
|
|
3065
3527
|
return null;
|
|
@@ -3067,15 +3529,15 @@ function renderBlock(node, key, ctx = DEFAULT_CTX) {
|
|
|
3067
3529
|
}
|
|
3068
3530
|
function renderListItem(item, key, ctx = DEFAULT_CTX) {
|
|
3069
3531
|
const isTask = item.checked !== null && item.checked !== void 0;
|
|
3070
|
-
return /* @__PURE__ */
|
|
3071
|
-
isTask && /* @__PURE__ */
|
|
3532
|
+
return /* @__PURE__ */ jsxs12("li", { className: `squisq-md-li${isTask ? " squisq-md-task" : ""}`, children: [
|
|
3533
|
+
isTask && /* @__PURE__ */ jsx19("input", { type: "checkbox", checked: !!item.checked, readOnly: true, className: "squisq-md-checkbox" }),
|
|
3072
3534
|
renderBlocks(item.children, key, ctx)
|
|
3073
3535
|
] }, key);
|
|
3074
3536
|
}
|
|
3075
3537
|
function renderTable(rows, align, key, ctx = DEFAULT_CTX) {
|
|
3076
3538
|
const [headerRow, ...bodyRows] = rows;
|
|
3077
|
-
return /* @__PURE__ */
|
|
3078
|
-
headerRow && /* @__PURE__ */
|
|
3539
|
+
return /* @__PURE__ */ jsxs12("table", { className: "squisq-md-table", children: [
|
|
3540
|
+
headerRow && /* @__PURE__ */ jsx19("thead", { children: /* @__PURE__ */ jsx19("tr", { children: headerRow.children.map((cell, ci) => /* @__PURE__ */ jsx19(
|
|
3079
3541
|
"th",
|
|
3080
3542
|
{
|
|
3081
3543
|
className: "squisq-md-th",
|
|
@@ -3084,7 +3546,7 @@ function renderTable(rows, align, key, ctx = DEFAULT_CTX) {
|
|
|
3084
3546
|
},
|
|
3085
3547
|
`${key}th${ci}`
|
|
3086
3548
|
)) }) }),
|
|
3087
|
-
bodyRows.length > 0 && /* @__PURE__ */
|
|
3549
|
+
bodyRows.length > 0 && /* @__PURE__ */ jsx19("tbody", { children: bodyRows.map((row, ri) => /* @__PURE__ */ jsx19("tr", { children: row.children.map((cell, ci) => /* @__PURE__ */ jsx19(
|
|
3088
3550
|
"td",
|
|
3089
3551
|
{
|
|
3090
3552
|
className: "squisq-md-td",
|
|
@@ -3102,22 +3564,13 @@ function MdImage({ src, alt, title }) {
|
|
|
3102
3564
|
const safeSrc = sanitizeUrl(src, "media");
|
|
3103
3565
|
const resolved = useMediaUrl(safeSrc ?? "", ".");
|
|
3104
3566
|
if (!safeSrc) return null;
|
|
3105
|
-
return /* @__PURE__ */
|
|
3567
|
+
return /* @__PURE__ */ jsx19("img", { className: "squisq-md-image", src: resolved, alt, title });
|
|
3106
3568
|
}
|
|
3107
3569
|
function resolveHtmlNodes(nodes, htmlPolicy) {
|
|
3108
3570
|
if (htmlPolicy === "strip") return [];
|
|
3109
3571
|
if (htmlPolicy === "trusted") return nodes;
|
|
3110
3572
|
return sanitizeHtmlNodes2(nodes);
|
|
3111
3573
|
}
|
|
3112
|
-
function containsMediaTag(nodes) {
|
|
3113
|
-
for (const node of nodes) {
|
|
3114
|
-
if (node.type !== "htmlElement") continue;
|
|
3115
|
-
const tagName = node.tagName.toLowerCase();
|
|
3116
|
-
if (tagName === "video" || tagName === "audio") return true;
|
|
3117
|
-
if (containsMediaTag(node.children)) return true;
|
|
3118
|
-
}
|
|
3119
|
-
return false;
|
|
3120
|
-
}
|
|
3121
3574
|
var DANGEROUS_HTML_TAGS = /* @__PURE__ */ new Set([
|
|
3122
3575
|
"base",
|
|
3123
3576
|
"embed",
|
|
@@ -3129,18 +3582,6 @@ var DANGEROUS_HTML_TAGS = /* @__PURE__ */ new Set([
|
|
|
3129
3582
|
"style",
|
|
3130
3583
|
"title"
|
|
3131
3584
|
]);
|
|
3132
|
-
function containsDangerousTag(nodes) {
|
|
3133
|
-
for (const node of nodes) {
|
|
3134
|
-
if (node.type !== "htmlElement") continue;
|
|
3135
|
-
if (DANGEROUS_HTML_TAGS.has(node.tagName.toLowerCase())) return true;
|
|
3136
|
-
if (containsDangerousTag(node.children)) return true;
|
|
3137
|
-
}
|
|
3138
|
-
return false;
|
|
3139
|
-
}
|
|
3140
|
-
var DANGEROUS_RAW_HTML_RE = /<\s*\/?\s*(?:base|embed|iframe|link|meta|object|script|style|title)\b/i;
|
|
3141
|
-
function hasDangerousRawHtml(rawHtml) {
|
|
3142
|
-
return DANGEROUS_RAW_HTML_RE.test(rawHtml);
|
|
3143
|
-
}
|
|
3144
3585
|
var PASSTHROUGH_ATTRS = {
|
|
3145
3586
|
// common
|
|
3146
3587
|
class: "className",
|
|
@@ -3159,7 +3600,7 @@ var PASSTHROUGH_ATTRS = {
|
|
|
3159
3600
|
target: "target",
|
|
3160
3601
|
rel: "rel"
|
|
3161
3602
|
};
|
|
3162
|
-
function reactPropsFromAttrs(attrs) {
|
|
3603
|
+
function reactPropsFromAttrs(attrs, ctx) {
|
|
3163
3604
|
const out = {};
|
|
3164
3605
|
for (const [name, value] of Object.entries(attrs)) {
|
|
3165
3606
|
const propName = PASSTHROUGH_ATTRS[name];
|
|
@@ -3168,21 +3609,33 @@ function reactPropsFromAttrs(attrs) {
|
|
|
3168
3609
|
out["data-style"] = value;
|
|
3169
3610
|
continue;
|
|
3170
3611
|
}
|
|
3612
|
+
if (propName === "href") {
|
|
3613
|
+
const href = sanitizeUrl(value, "link", { extraLinkSchemes: ctx.linkSchemes });
|
|
3614
|
+
if (href) out.href = href;
|
|
3615
|
+
continue;
|
|
3616
|
+
}
|
|
3617
|
+
if (propName === "src") {
|
|
3618
|
+
const src = sanitizeUrl(value, "media");
|
|
3619
|
+
if (src) out.src = src;
|
|
3620
|
+
continue;
|
|
3621
|
+
}
|
|
3171
3622
|
out[propName] = value;
|
|
3172
3623
|
}
|
|
3173
3624
|
return out;
|
|
3174
3625
|
}
|
|
3175
|
-
function renderHtmlElement(el, key) {
|
|
3626
|
+
function renderHtmlElement(el, key, ctx) {
|
|
3176
3627
|
const tagName = el.tagName.toLowerCase();
|
|
3177
3628
|
if (DANGEROUS_HTML_TAGS.has(tagName)) return null;
|
|
3178
3629
|
if (tagName === "video") {
|
|
3179
|
-
|
|
3630
|
+
const src = sanitizeUrl(el.attributes.src ?? "", "media") ?? "";
|
|
3631
|
+
const poster = sanitizeUrl(el.attributes.poster ?? "", "media") ?? void 0;
|
|
3632
|
+
return /* @__PURE__ */ jsx19(
|
|
3180
3633
|
InlineVideoPlayer,
|
|
3181
3634
|
{
|
|
3182
|
-
src
|
|
3635
|
+
src,
|
|
3183
3636
|
width: el.attributes.width,
|
|
3184
3637
|
height: el.attributes.height,
|
|
3185
|
-
poster
|
|
3638
|
+
poster,
|
|
3186
3639
|
controls: "controls" in el.attributes,
|
|
3187
3640
|
preload: el.attributes.preload === "none" || el.attributes.preload === "metadata" || el.attributes.preload === "auto" ? el.attributes.preload : void 0
|
|
3188
3641
|
},
|
|
@@ -3190,10 +3643,11 @@ function renderHtmlElement(el, key) {
|
|
|
3190
3643
|
);
|
|
3191
3644
|
}
|
|
3192
3645
|
if (tagName === "audio") {
|
|
3193
|
-
|
|
3646
|
+
const src = sanitizeUrl(el.attributes.src ?? "", "media") ?? "";
|
|
3647
|
+
return /* @__PURE__ */ jsx19(
|
|
3194
3648
|
InlineAudioPlayer,
|
|
3195
3649
|
{
|
|
3196
|
-
src
|
|
3650
|
+
src,
|
|
3197
3651
|
controls: "controls" in el.attributes,
|
|
3198
3652
|
preload: el.attributes.preload === "none" || el.attributes.preload === "metadata" || el.attributes.preload === "auto" ? el.attributes.preload : void 0
|
|
3199
3653
|
},
|
|
@@ -3201,20 +3655,20 @@ function renderHtmlElement(el, key) {
|
|
|
3201
3655
|
);
|
|
3202
3656
|
}
|
|
3203
3657
|
const Tag = tagName;
|
|
3204
|
-
const props = reactPropsFromAttrs(el.attributes);
|
|
3658
|
+
const props = reactPropsFromAttrs(el.attributes, ctx);
|
|
3205
3659
|
if (el.selfClosing) {
|
|
3206
|
-
return /* @__PURE__ */
|
|
3660
|
+
return /* @__PURE__ */ jsx19(Tag, { ...props }, key);
|
|
3207
3661
|
}
|
|
3208
|
-
return /* @__PURE__ */
|
|
3662
|
+
return /* @__PURE__ */ jsx19(Tag, { ...props, children: renderHtmlNodes(el.children, `${key}c`, ctx) }, key);
|
|
3209
3663
|
}
|
|
3210
|
-
function renderHtmlNodes(nodes, keyPrefix) {
|
|
3664
|
+
function renderHtmlNodes(nodes, keyPrefix, ctx = DEFAULT_CTX) {
|
|
3211
3665
|
return nodes.map((node, i) => {
|
|
3212
3666
|
const key = `${keyPrefix}${i}`;
|
|
3213
3667
|
switch (node.type) {
|
|
3214
3668
|
case "htmlElement":
|
|
3215
|
-
return renderHtmlElement(node, key);
|
|
3669
|
+
return renderHtmlElement(node, key, ctx);
|
|
3216
3670
|
case "htmlText":
|
|
3217
|
-
return /* @__PURE__ */
|
|
3671
|
+
return /* @__PURE__ */ jsx19(Fragment2, { children: node.value }, key);
|
|
3218
3672
|
case "htmlComment":
|
|
3219
3673
|
return null;
|
|
3220
3674
|
default:
|
|
@@ -3229,25 +3683,16 @@ function MarkdownRenderer({
|
|
|
3229
3683
|
linkSchemes
|
|
3230
3684
|
}) {
|
|
3231
3685
|
if (!nodes || nodes.length === 0) return null;
|
|
3232
|
-
return /* @__PURE__ */
|
|
3686
|
+
return /* @__PURE__ */ jsx19("div", { className: `squisq-md ${className || ""}`, children: renderBlocks(nodes, "", { htmlPolicy, linkSchemes }) });
|
|
3233
3687
|
}
|
|
3234
3688
|
|
|
3235
3689
|
// src/LinearDocView.tsx
|
|
3236
|
-
import { jsx as
|
|
3237
|
-
var warnedUnknownTemplates = /* @__PURE__ */ new Set();
|
|
3690
|
+
import { jsx as jsx20, jsxs as jsxs13 } from "react/jsx-runtime";
|
|
3238
3691
|
function isAnnotatedBlock(block) {
|
|
3239
|
-
|
|
3240
|
-
|
|
3241
|
-
|
|
3242
|
-
|
|
3243
|
-
warnedUnknownTemplates.add(annotation.template);
|
|
3244
|
-
console.warn(
|
|
3245
|
-
`[squisq] Unknown template "${annotation.template}" \u2014 rendering the block as plain markdown.`
|
|
3246
|
-
);
|
|
3247
|
-
}
|
|
3248
|
-
return false;
|
|
3249
|
-
}
|
|
3250
|
-
return true;
|
|
3692
|
+
return !!block.sourceHeading?.templateAnnotation?.template || !block.sourceHeading && isTemplateBlock2(block);
|
|
3693
|
+
}
|
|
3694
|
+
function visualTemplateName(block) {
|
|
3695
|
+
return block.sourceHeading?.templateAnnotation?.template ?? block.template;
|
|
3251
3696
|
}
|
|
3252
3697
|
function countAll(blocks) {
|
|
3253
3698
|
let count = 0;
|
|
@@ -3257,50 +3702,65 @@ function countAll(blocks) {
|
|
|
3257
3702
|
}
|
|
3258
3703
|
return count;
|
|
3259
3704
|
}
|
|
3260
|
-
function BlockSection({
|
|
3705
|
+
function BlockSection({
|
|
3706
|
+
block,
|
|
3707
|
+
basePath,
|
|
3708
|
+
viewport,
|
|
3709
|
+
renderContext,
|
|
3710
|
+
blockIndex,
|
|
3711
|
+
blockIndices,
|
|
3712
|
+
animationsEnabled
|
|
3713
|
+
}) {
|
|
3261
3714
|
const isAnnotated = isAnnotatedBlock(block);
|
|
3262
3715
|
const visualBlock = useMemo8(() => {
|
|
3263
3716
|
if (!isAnnotated) return null;
|
|
3264
|
-
const annotation = block.sourceHeading
|
|
3265
|
-
const
|
|
3266
|
-
const templateBlock = {
|
|
3267
|
-
|
|
3268
|
-
|
|
3269
|
-
|
|
3270
|
-
|
|
3271
|
-
|
|
3272
|
-
|
|
3273
|
-
|
|
3274
|
-
|
|
3275
|
-
|
|
3276
|
-
block.
|
|
3277
|
-
{
|
|
3717
|
+
const annotation = block.sourceHeading?.templateAnnotation;
|
|
3718
|
+
const templateName = visualTemplateName(block) ?? "sectionHeader";
|
|
3719
|
+
const templateBlock = annotation ? (() => {
|
|
3720
|
+
const headingText = extractPlainText(block.sourceHeading);
|
|
3721
|
+
return {
|
|
3722
|
+
id: block.id,
|
|
3723
|
+
template: templateName,
|
|
3724
|
+
startTime: 0,
|
|
3725
|
+
duration: 1,
|
|
3726
|
+
audioSegment: 0,
|
|
3727
|
+
title: headingText,
|
|
3728
|
+
contents: block.contents,
|
|
3729
|
+
children: block.children,
|
|
3730
|
+
...deriveTemplateInputs(templateName, headingText, block.contents, {
|
|
3278
3731
|
placeholders: true
|
|
3279
|
-
}
|
|
3280
|
-
|
|
3281
|
-
|
|
3282
|
-
|
|
3732
|
+
}) ?? {},
|
|
3733
|
+
...annotation.params,
|
|
3734
|
+
...block.templateOverrides
|
|
3735
|
+
};
|
|
3736
|
+
})() : {
|
|
3737
|
+
...block,
|
|
3738
|
+
startTime: block.startTime ?? 0,
|
|
3739
|
+
duration: block.duration ?? 1,
|
|
3740
|
+
audioSegment: block.audioSegment ?? 0,
|
|
3741
|
+
template: templateName
|
|
3283
3742
|
};
|
|
3284
3743
|
const ctx = {
|
|
3285
3744
|
...renderContext,
|
|
3286
3745
|
blockIndex
|
|
3287
3746
|
};
|
|
3288
|
-
const layers =
|
|
3747
|
+
const { layers } = materializeBlockLayers(templateBlock, ctx);
|
|
3289
3748
|
return {
|
|
3290
3749
|
...block,
|
|
3291
3750
|
layers,
|
|
3292
|
-
template:
|
|
3751
|
+
template: templateName
|
|
3293
3752
|
};
|
|
3294
3753
|
}, [block, isAnnotated, renderContext, blockIndex]);
|
|
3295
|
-
return /* @__PURE__ */
|
|
3754
|
+
return /* @__PURE__ */ jsxs13(
|
|
3296
3755
|
"div",
|
|
3297
3756
|
{
|
|
3298
3757
|
className: "squisq-linear-section",
|
|
3299
3758
|
"data-block-id": block.id,
|
|
3300
|
-
"data-
|
|
3759
|
+
"data-block-index": blockIndex,
|
|
3760
|
+
"data-template": isAnnotated ? visualTemplateName(block) : void 0,
|
|
3301
3761
|
children: [
|
|
3302
|
-
block.sourceHeading && !isAnnotated && /* @__PURE__ */
|
|
3303
|
-
isAnnotated && visualBlock && /* @__PURE__ */
|
|
3762
|
+
block.sourceHeading && !isAnnotated && /* @__PURE__ */ jsx20(MarkdownRenderer, { nodes: [block.sourceHeading] }),
|
|
3763
|
+
isAnnotated && visualBlock && /* @__PURE__ */ jsx20("div", { className: "squisq-linear-card", children: /* @__PURE__ */ jsx20(
|
|
3304
3764
|
"div",
|
|
3305
3765
|
{
|
|
3306
3766
|
className: "squisq-linear-card-svg",
|
|
@@ -3310,26 +3770,29 @@ function BlockSection({ block, basePath, viewport, renderContext, blockIndex })
|
|
|
3310
3770
|
overflow: "hidden",
|
|
3311
3771
|
marginBottom: "1em"
|
|
3312
3772
|
},
|
|
3313
|
-
children: /* @__PURE__ */
|
|
3773
|
+
children: /* @__PURE__ */ jsx20(
|
|
3314
3774
|
BlockRenderer,
|
|
3315
3775
|
{
|
|
3316
3776
|
block: visualBlock,
|
|
3317
3777
|
blockTime: 0,
|
|
3318
3778
|
basePath,
|
|
3319
|
-
viewport
|
|
3779
|
+
viewport,
|
|
3780
|
+
animationsEnabled
|
|
3320
3781
|
}
|
|
3321
3782
|
)
|
|
3322
3783
|
}
|
|
3323
3784
|
) }),
|
|
3324
|
-
!isAnnotated && block.contents && block.contents.length > 0 && /* @__PURE__ */
|
|
3325
|
-
block.children && block.children.length > 0 && /* @__PURE__ */
|
|
3785
|
+
!isAnnotated && block.contents && block.contents.length > 0 && /* @__PURE__ */ jsx20(MarkdownRenderer, { nodes: block.contents }),
|
|
3786
|
+
block.children && block.children.length > 0 && /* @__PURE__ */ jsx20("div", { className: "squisq-linear-children", children: block.children.map((child, i) => /* @__PURE__ */ jsx20(
|
|
3326
3787
|
BlockSection,
|
|
3327
3788
|
{
|
|
3328
3789
|
block: child,
|
|
3329
3790
|
basePath,
|
|
3330
3791
|
viewport,
|
|
3331
3792
|
renderContext,
|
|
3332
|
-
blockIndex: blockIndex + i + 1
|
|
3793
|
+
blockIndex: blockIndices.get(child) ?? blockIndex + i + 1,
|
|
3794
|
+
blockIndices,
|
|
3795
|
+
animationsEnabled
|
|
3333
3796
|
},
|
|
3334
3797
|
child.id
|
|
3335
3798
|
)) })
|
|
@@ -3345,9 +3808,12 @@ function LinearDocView({
|
|
|
3345
3808
|
className,
|
|
3346
3809
|
theme,
|
|
3347
3810
|
surface,
|
|
3811
|
+
animationsEnabled = true,
|
|
3348
3812
|
thinMargins = false,
|
|
3349
|
-
imageDisplayMode = "inline"
|
|
3813
|
+
imageDisplayMode = "inline",
|
|
3814
|
+
globalKeyboardShortcuts = false
|
|
3350
3815
|
}) {
|
|
3816
|
+
const scrollRef = useRef8(null);
|
|
3351
3817
|
const activeViewport = viewport ?? VIEWPORT_PRESETS3.landscape;
|
|
3352
3818
|
const markdownDoc = useMemo8(
|
|
3353
3819
|
() => !doc && markdown !== void 0 ? markdownToDoc(parseMarkdown(markdown)) : void 0,
|
|
@@ -3358,6 +3824,18 @@ function LinearDocView({
|
|
|
3358
3824
|
() => resolvedDoc ? countAll(resolvedDoc.blocks) : 0,
|
|
3359
3825
|
[resolvedDoc]
|
|
3360
3826
|
);
|
|
3827
|
+
const blockIndices = useMemo8(() => {
|
|
3828
|
+
const indices = /* @__PURE__ */ new Map();
|
|
3829
|
+
let index = 0;
|
|
3830
|
+
const visit = (blocks) => {
|
|
3831
|
+
for (const block of blocks) {
|
|
3832
|
+
indices.set(block, index++);
|
|
3833
|
+
if (block.children) visit(block.children);
|
|
3834
|
+
}
|
|
3835
|
+
};
|
|
3836
|
+
if (resolvedDoc) visit(resolvedDoc.blocks);
|
|
3837
|
+
return indices;
|
|
3838
|
+
}, [resolvedDoc]);
|
|
3361
3839
|
const autoSurface = useAutoSurface(surface === "auto");
|
|
3362
3840
|
const resolvedSurface = surface === "auto" ? autoSurface : surface;
|
|
3363
3841
|
const renderContext = useMemo8(() => {
|
|
@@ -3369,12 +3847,37 @@ function LinearDocView({
|
|
|
3369
3847
|
totalBlocks,
|
|
3370
3848
|
// Theme atmosphere (vignette/grain/gradient persistent layers) shows
|
|
3371
3849
|
// on the inline template cards so they match the player's look.
|
|
3372
|
-
persistentLayers: effectiveTheme.persistentLayers
|
|
3850
|
+
persistentLayers: effectiveTheme.persistentLayers,
|
|
3851
|
+
customTemplates: resolvedDoc?.customTemplates
|
|
3373
3852
|
};
|
|
3374
|
-
}, [activeViewport, totalBlocks, theme, resolvedSurface]);
|
|
3853
|
+
}, [activeViewport, resolvedDoc?.customTemplates, totalBlocks, theme, resolvedSurface]);
|
|
3375
3854
|
const activeTheme = renderContext.theme;
|
|
3855
|
+
useEffect9(() => {
|
|
3856
|
+
if (!globalKeyboardShortcuts) return;
|
|
3857
|
+
const handleKeyDown = (event) => {
|
|
3858
|
+
if (event.defaultPrevented || event.altKey || event.ctrlKey || event.metaKey || event.shiftKey || event.key !== "ArrowDown" && event.key !== "ArrowUp") {
|
|
3859
|
+
return;
|
|
3860
|
+
}
|
|
3861
|
+
const target = event.target instanceof Element ? event.target : null;
|
|
3862
|
+
if (target?.closest(
|
|
3863
|
+
'input, textarea, select, [contenteditable]:not([contenteditable="false"]), [role="textbox"], [role="combobox"], [role="listbox"], [role="menu"], [role="dialog"], [aria-modal="true"], .monaco-editor'
|
|
3864
|
+
)) {
|
|
3865
|
+
return;
|
|
3866
|
+
}
|
|
3867
|
+
const scroller = scrollRef.current;
|
|
3868
|
+
if (!scroller) return;
|
|
3869
|
+
event.preventDefault();
|
|
3870
|
+
const distance = Math.max(64, Math.round(scroller.clientHeight * 0.12));
|
|
3871
|
+
scroller.scrollBy({
|
|
3872
|
+
top: event.key === "ArrowDown" ? distance : -distance,
|
|
3873
|
+
behavior: "smooth"
|
|
3874
|
+
});
|
|
3875
|
+
};
|
|
3876
|
+
document.addEventListener("keydown", handleKeyDown);
|
|
3877
|
+
return () => document.removeEventListener("keydown", handleKeyDown);
|
|
3878
|
+
}, [globalKeyboardShortcuts]);
|
|
3376
3879
|
if (!resolvedDoc) {
|
|
3377
|
-
return /* @__PURE__ */
|
|
3880
|
+
return /* @__PURE__ */ jsx20("div", { ref: scrollRef, className: `squisq-linear squisq-linear--empty ${className || ""}` });
|
|
3378
3881
|
}
|
|
3379
3882
|
const bgColor = activeTheme.colors.background;
|
|
3380
3883
|
const textColor = activeTheme.colors.text;
|
|
@@ -3383,9 +3886,10 @@ function LinearDocView({
|
|
|
3383
3886
|
const bodyFont = resolveFontFamily2(activeTheme.typography.bodyFont, "system-ui, sans-serif");
|
|
3384
3887
|
const titleFont = resolveFontFamily2(activeTheme.typography.titleFont, "Georgia, serif");
|
|
3385
3888
|
const lineHt = activeTheme.typography.lineHeight ?? 1.7;
|
|
3386
|
-
return /* @__PURE__ */
|
|
3889
|
+
return /* @__PURE__ */ jsx20(
|
|
3387
3890
|
"div",
|
|
3388
3891
|
{
|
|
3892
|
+
ref: scrollRef,
|
|
3389
3893
|
className: `squisq-linear ${className || ""}`,
|
|
3390
3894
|
style: {
|
|
3391
3895
|
width: "100%",
|
|
@@ -3399,7 +3903,7 @@ function LinearDocView({
|
|
|
3399
3903
|
overflowX: "hidden",
|
|
3400
3904
|
background: bgColor
|
|
3401
3905
|
},
|
|
3402
|
-
children: /* @__PURE__ */
|
|
3906
|
+
children: /* @__PURE__ */ jsxs13(
|
|
3403
3907
|
"div",
|
|
3404
3908
|
{
|
|
3405
3909
|
className: `squisq-linear-content squisq-md${thinMargins ? " squisq-linear-content--thin" : ""}${imageDisplayMode === "thumbnail" ? " squisq-linear-content--thumbnail-images" : ""}`,
|
|
@@ -3424,7 +3928,7 @@ function LinearDocView({
|
|
|
3424
3928
|
"--squisq-linear-bg": bgColor
|
|
3425
3929
|
},
|
|
3426
3930
|
children: [
|
|
3427
|
-
/* @__PURE__ */
|
|
3931
|
+
/* @__PURE__ */ jsx20("style", { children: `
|
|
3428
3932
|
.squisq-linear-content h1,
|
|
3429
3933
|
.squisq-linear-content h2,
|
|
3430
3934
|
.squisq-linear-content h3,
|
|
@@ -3536,14 +4040,16 @@ function LinearDocView({
|
|
|
3536
4040
|
background: color-mix(in srgb, var(--squisq-linear-primary) 8%, transparent);
|
|
3537
4041
|
}
|
|
3538
4042
|
` }),
|
|
3539
|
-
resolvedDoc.blocks.map((block, i) => /* @__PURE__ */
|
|
4043
|
+
resolvedDoc.blocks.map((block, i) => /* @__PURE__ */ jsx20(
|
|
3540
4044
|
BlockSection,
|
|
3541
4045
|
{
|
|
3542
4046
|
block,
|
|
3543
4047
|
basePath,
|
|
3544
4048
|
viewport: activeViewport,
|
|
3545
4049
|
renderContext,
|
|
3546
|
-
blockIndex: i
|
|
4050
|
+
blockIndex: blockIndices.get(block) ?? i,
|
|
4051
|
+
blockIndices,
|
|
4052
|
+
animationsEnabled
|
|
3547
4053
|
},
|
|
3548
4054
|
block.id
|
|
3549
4055
|
))
|
|
@@ -3555,7 +4061,7 @@ function LinearDocView({
|
|
|
3555
4061
|
}
|
|
3556
4062
|
|
|
3557
4063
|
// src/DocPlayer.tsx
|
|
3558
|
-
import { jsx as
|
|
4064
|
+
import { jsx as jsx21, jsxs as jsxs14 } from "react/jsx-runtime";
|
|
3559
4065
|
var SMALL_WORDS = /* @__PURE__ */ new Set([
|
|
3560
4066
|
"a",
|
|
3561
4067
|
"an",
|
|
@@ -3576,7 +4082,7 @@ var SMALL_WORDS = /* @__PURE__ */ new Set([
|
|
|
3576
4082
|
function buildSegmentTitleMap(doc) {
|
|
3577
4083
|
const map = /* @__PURE__ */ new Map();
|
|
3578
4084
|
for (const block of doc.blocks) {
|
|
3579
|
-
if (
|
|
4085
|
+
if (isTemplateBlock3(block) && block.template === "sectionHeader" && "title" in block) {
|
|
3580
4086
|
const segIdx = block.audioSegment;
|
|
3581
4087
|
if (!map.has(segIdx)) {
|
|
3582
4088
|
map.set(segIdx, block.title);
|
|
@@ -3617,14 +4123,15 @@ function DocPlayer(props) {
|
|
|
3617
4123
|
);
|
|
3618
4124
|
const resolvedDoc = doc ?? markdownDoc;
|
|
3619
4125
|
if (!resolvedDoc) {
|
|
3620
|
-
return /* @__PURE__ */
|
|
4126
|
+
return /* @__PURE__ */ jsx21("div", { className: "doc-player doc-player--empty" });
|
|
3621
4127
|
}
|
|
3622
|
-
return /* @__PURE__ */
|
|
4128
|
+
return /* @__PURE__ */ jsx21(DocPlayerContent, { ...props, doc: resolvedDoc });
|
|
3623
4129
|
}
|
|
3624
4130
|
function DocPlayerContent({
|
|
3625
4131
|
doc,
|
|
3626
4132
|
basePath = ".",
|
|
3627
4133
|
renderMode = false,
|
|
4134
|
+
animationsEnabled = true,
|
|
3628
4135
|
autoPlay = false,
|
|
3629
4136
|
onEnded,
|
|
3630
4137
|
onTimeUpdate,
|
|
@@ -3636,22 +4143,27 @@ function DocPlayerContent({
|
|
|
3636
4143
|
onCaptionsToggle,
|
|
3637
4144
|
onPlaybackStateChange,
|
|
3638
4145
|
onControlsReady,
|
|
4146
|
+
onRenderAPIReady,
|
|
3639
4147
|
isFullscreen = false,
|
|
3640
4148
|
onFullscreenToggle,
|
|
3641
4149
|
onBlockMarkers,
|
|
3642
4150
|
forceViewport,
|
|
3643
4151
|
displayMode = "video",
|
|
4152
|
+
showCoverSlide = true,
|
|
4153
|
+
coverVisible,
|
|
3644
4154
|
theme,
|
|
3645
4155
|
surface,
|
|
3646
4156
|
captionStyle = "standard",
|
|
3647
|
-
enableSwipe = true
|
|
4157
|
+
enableSwipe = true,
|
|
4158
|
+
globalKeyboardShortcuts = false
|
|
3648
4159
|
}) {
|
|
3649
4160
|
const isSlideshowMode = displayMode === "slideshow";
|
|
3650
4161
|
const isLinearMode = displayMode === "linear";
|
|
3651
|
-
const audioRef =
|
|
3652
|
-
const containerRef =
|
|
3653
|
-
const
|
|
3654
|
-
const
|
|
4162
|
+
const audioRef = useRef9(null);
|
|
4163
|
+
const containerRef = useRef9(null);
|
|
4164
|
+
const playerId = `squisq-player-${useId7().replace(/:/g, "")}`;
|
|
4165
|
+
const [tapFeedback, setTapFeedback] = useState9(null);
|
|
4166
|
+
const tapFeedbackTimer = useRef9();
|
|
3655
4167
|
const { viewport, orientation } = useViewportOrientation();
|
|
3656
4168
|
const activeViewport = forceViewport || (renderMode ? VIEWPORT_PRESETS4.landscape : viewport);
|
|
3657
4169
|
const isDebugMode = useMemo9(() => {
|
|
@@ -3659,9 +4171,9 @@ function DocPlayerContent({
|
|
|
3659
4171
|
const params = new URLSearchParams(window.location.search);
|
|
3660
4172
|
return params.get("debug") === "true";
|
|
3661
4173
|
}, []);
|
|
3662
|
-
const internalAudio = useAudioSync(audioRef, doc.audio, basePath);
|
|
4174
|
+
const internalAudio = useAudioSync(audioRef, doc.audio, basePath, !externalAudioController);
|
|
3663
4175
|
const audio = externalAudioController || internalAudio;
|
|
3664
|
-
|
|
4176
|
+
useEffect10(() => {
|
|
3665
4177
|
if (warnedMissingStyles || !isDevEnvironment()) return;
|
|
3666
4178
|
const el = containerRef.current;
|
|
3667
4179
|
if (!el || typeof getComputedStyle !== "function") return;
|
|
@@ -3690,21 +4202,28 @@ function DocPlayerContent({
|
|
|
3690
4202
|
restart
|
|
3691
4203
|
} = audio;
|
|
3692
4204
|
const mediaSchedule = useMemo9(() => resolveMediaSchedule(doc), [doc]);
|
|
3693
|
-
const currentTimeRef =
|
|
4205
|
+
const currentTimeRef = useRef9(currentTime);
|
|
3694
4206
|
currentTimeRef.current = currentTime;
|
|
3695
|
-
const totalDurationRef =
|
|
4207
|
+
const totalDurationRef = useRef9(totalDuration);
|
|
3696
4208
|
totalDurationRef.current = totalDuration;
|
|
3697
|
-
const expandedBlocksLenRef =
|
|
3698
|
-
const handleContainerClick =
|
|
4209
|
+
const expandedBlocksLenRef = useRef9(0);
|
|
4210
|
+
const handleContainerClick = useCallback7(
|
|
3699
4211
|
(e) => {
|
|
3700
|
-
if (renderMode ||
|
|
4212
|
+
if (renderMode || isLinearMode) return;
|
|
3701
4213
|
const target = e.target;
|
|
4214
|
+
if (isSlideshowMode) {
|
|
4215
|
+
if (!target.closest('button, a, input, textarea, select, [contenteditable="true"]')) {
|
|
4216
|
+
containerRef.current?.focus({ preventScroll: true });
|
|
4217
|
+
}
|
|
4218
|
+
return;
|
|
4219
|
+
}
|
|
3702
4220
|
if (target.closest(
|
|
3703
|
-
|
|
4221
|
+
'button, a, input, textarea, select, [contenteditable="true"], .doc-player__controls, .doc-player__scrubber, .doc-controls-sidebar, .doc-controls-slideshow'
|
|
3704
4222
|
))
|
|
3705
4223
|
return;
|
|
4224
|
+
containerRef.current?.focus({ preventScroll: true });
|
|
3706
4225
|
toggle();
|
|
3707
|
-
const nextState = isPlaying ? "
|
|
4226
|
+
const nextState = isPlaying ? "pause" : "play";
|
|
3708
4227
|
setTapFeedback(nextState);
|
|
3709
4228
|
clearTimeout(tapFeedbackTimer.current);
|
|
3710
4229
|
tapFeedbackTimer.current = setTimeout(() => setTapFeedback(null), 600);
|
|
@@ -3728,10 +4247,16 @@ function DocPlayerContent({
|
|
|
3728
4247
|
docProgress,
|
|
3729
4248
|
nextBlock: _nextBlock,
|
|
3730
4249
|
prevBlock: _prevBlock,
|
|
3731
|
-
blocks: expandedBlocks
|
|
3732
|
-
|
|
4250
|
+
blocks: expandedBlocks,
|
|
4251
|
+
suppressOutgoingForNextBlock
|
|
4252
|
+
} = useDocPlayback(doc, currentTime, {
|
|
4253
|
+
viewport: activeViewport,
|
|
4254
|
+
theme: effectiveTheme,
|
|
4255
|
+
onSeek: seekTo
|
|
4256
|
+
});
|
|
3733
4257
|
const coverBlock = useMemo9(() => {
|
|
3734
4258
|
const startBlockConfig = doc.startBlock;
|
|
4259
|
+
if (!showCoverSlide) return null;
|
|
3735
4260
|
if (!startBlockConfig) return null;
|
|
3736
4261
|
const context = createTemplateContext(effectiveTheme, 0, 1, activeViewport);
|
|
3737
4262
|
const layers = expandCoverBlock(startBlockConfig, context);
|
|
@@ -3744,99 +4269,137 @@ function DocPlayerContent({
|
|
|
3744
4269
|
audioSegment: -1,
|
|
3745
4270
|
layers
|
|
3746
4271
|
};
|
|
3747
|
-
}, [doc.startBlock, activeViewport, effectiveTheme]);
|
|
3748
|
-
const
|
|
3749
|
-
const [
|
|
3750
|
-
const
|
|
3751
|
-
const
|
|
3752
|
-
|
|
3753
|
-
|
|
4272
|
+
}, [doc.startBlock, activeViewport, effectiveTheme, showCoverSlide]);
|
|
4273
|
+
const hasManagedCover = !!coverBlock;
|
|
4274
|
+
const [slideshowCoverVisible, setSlideshowCoverVisible] = useState9(false);
|
|
4275
|
+
const [isSlideshowPickerOpen, setIsSlideshowPickerOpen] = useState9(false);
|
|
4276
|
+
const slideshowCoverInitKeyRef = useRef9("");
|
|
4277
|
+
useEffect10(() => {
|
|
4278
|
+
slideshowCoverInitKeyRef.current = "";
|
|
4279
|
+
}, [doc]);
|
|
4280
|
+
useEffect10(() => {
|
|
4281
|
+
const initKey = `${isSlideshowMode}:${hasManagedCover}:${renderMode}`;
|
|
4282
|
+
if (slideshowCoverInitKeyRef.current === initKey) return;
|
|
4283
|
+
slideshowCoverInitKeyRef.current = initKey;
|
|
4284
|
+
if (isSlideshowMode && hasManagedCover && !renderMode) {
|
|
4285
|
+
setSlideshowCoverVisible(true);
|
|
4286
|
+
pause();
|
|
4287
|
+
} else {
|
|
4288
|
+
setSlideshowCoverVisible(false);
|
|
4289
|
+
}
|
|
4290
|
+
}, [isSlideshowMode, hasManagedCover, renderMode, pause]);
|
|
4291
|
+
const [coverForced, setCoverForced] = useState9(false);
|
|
4292
|
+
const [coverGraceActive, setCoverGraceActive] = useState9(false);
|
|
4293
|
+
const coverGraceTimer = useRef9();
|
|
4294
|
+
const coverWasShowing = useRef9(false);
|
|
4295
|
+
const hasPlayedOnce = useRef9(false);
|
|
4296
|
+
useEffect10(() => {
|
|
4297
|
+
hasPlayedOnce.current = false;
|
|
4298
|
+
coverWasShowing.current = false;
|
|
4299
|
+
clearTimeout(coverGraceTimer.current);
|
|
4300
|
+
setCoverGraceActive(false);
|
|
4301
|
+
setCoverForced(false);
|
|
4302
|
+
}, [doc]);
|
|
4303
|
+
const atRest = !!(coverBlock && !isSlideshowMode && !isPlaying && currentTime === 0 && !hasPlayedOnce.current && !renderMode && !autoPlay);
|
|
3754
4304
|
if (atRest) coverWasShowing.current = true;
|
|
3755
|
-
|
|
3756
|
-
if (isPlaying && coverWasShowing.current && coverBlock && !renderMode) {
|
|
4305
|
+
useEffect10(() => {
|
|
4306
|
+
if (isPlaying && coverWasShowing.current && coverBlock && !renderMode && !isSlideshowMode) {
|
|
3757
4307
|
coverWasShowing.current = false;
|
|
3758
4308
|
hasPlayedOnce.current = true;
|
|
3759
4309
|
setCoverGraceActive(true);
|
|
3760
4310
|
coverGraceTimer.current = setTimeout(() => setCoverGraceActive(false), 3e3);
|
|
3761
4311
|
}
|
|
3762
|
-
}, [isPlaying, coverBlock, renderMode]);
|
|
3763
|
-
|
|
3764
|
-
const
|
|
3765
|
-
const
|
|
3766
|
-
|
|
4312
|
+
}, [isPlaying, coverBlock, renderMode, isSlideshowMode]);
|
|
4313
|
+
useEffect10(() => () => clearTimeout(coverGraceTimer.current), []);
|
|
4314
|
+
const showVideoCoverBlock = !isSlideshowMode && !isLinearMode && !!coverBlock && (coverForced || coverGraceActive || !isPlaying && currentTime === 0 && !hasPlayedOnce.current && !renderMode && !autoPlay);
|
|
4315
|
+
const effectiveSlideshowCoverVisible = coverVisible ?? slideshowCoverVisible;
|
|
4316
|
+
const showSlideshowCover = !!(isSlideshowMode && !isLinearMode && !renderMode && coverBlock && effectiveSlideshowCoverVisible);
|
|
4317
|
+
const showCoverBlock = coverVisible === void 0 ? showVideoCoverBlock || showSlideshowCover : !!coverBlock && coverVisible;
|
|
4318
|
+
const slideshowHasCover = !!(isSlideshowMode && !renderMode && coverBlock);
|
|
4319
|
+
const slideshowSlideIndex = slideshowHasCover ? effectiveSlideshowCoverVisible ? 0 : currentBlockIndex + 1 : currentBlockIndex;
|
|
4320
|
+
const slideshowTotalSlides = slideshowHasCover ? expandedBlocks.length + 1 : expandedBlocks.length;
|
|
4321
|
+
const hasAutoPlayed = useRef9(false);
|
|
4322
|
+
useEffect10(() => {
|
|
4323
|
+
hasAutoPlayed.current = false;
|
|
4324
|
+
}, [doc]);
|
|
4325
|
+
useEffect10(() => {
|
|
3767
4326
|
if (isAudioReady && autoPlay && !hasAutoPlayed.current) {
|
|
3768
4327
|
hasAutoPlayed.current = true;
|
|
3769
4328
|
play();
|
|
3770
4329
|
}
|
|
3771
4330
|
}, [isAudioReady, autoPlay, play]);
|
|
3772
|
-
|
|
4331
|
+
useEffect10(() => {
|
|
3773
4332
|
onTimeUpdate?.(currentTime);
|
|
3774
4333
|
}, [currentTime, onTimeUpdate]);
|
|
3775
|
-
|
|
4334
|
+
useEffect10(() => {
|
|
3776
4335
|
if (isEnded) {
|
|
3777
4336
|
onEnded?.();
|
|
3778
4337
|
}
|
|
3779
4338
|
}, [isEnded, onEnded]);
|
|
3780
|
-
|
|
3781
|
-
|
|
3782
|
-
|
|
3783
|
-
|
|
3784
|
-
|
|
3785
|
-
|
|
3786
|
-
|
|
3787
|
-
|
|
3788
|
-
|
|
3789
|
-
|
|
3790
|
-
|
|
3791
|
-
|
|
3792
|
-
|
|
4339
|
+
const liveRenderAPIRef = useRef9(null);
|
|
4340
|
+
const stableRenderAPIRef = useRef9(null);
|
|
4341
|
+
if (!stableRenderAPIRef.current) {
|
|
4342
|
+
const current = () => {
|
|
4343
|
+
const api = liveRenderAPIRef.current;
|
|
4344
|
+
if (!api) throw new Error("Squisq render API is not currently available.");
|
|
4345
|
+
return api;
|
|
4346
|
+
};
|
|
4347
|
+
stableRenderAPIRef.current = {
|
|
4348
|
+
seekTo: (time) => current().seekTo(time),
|
|
4349
|
+
getDuration: () => current().getDuration(),
|
|
4350
|
+
getBlocks: () => current().getBlocks(),
|
|
4351
|
+
getAudioSegments: () => current().getAudioSegments(),
|
|
4352
|
+
getCaptions: () => current().getCaptions(),
|
|
4353
|
+
getChapters: () => current().getChapters(),
|
|
4354
|
+
showCover: () => current().showCover(),
|
|
4355
|
+
hideCover: () => current().hideCover(),
|
|
4356
|
+
hasCoverBlock: () => current().hasCoverBlock()
|
|
4357
|
+
};
|
|
4358
|
+
}
|
|
4359
|
+
const stableRenderAPI = stableRenderAPIRef.current;
|
|
4360
|
+
useEffect10(() => {
|
|
4361
|
+
if (!renderMode && !isDebugMode) {
|
|
4362
|
+
liveRenderAPIRef.current = null;
|
|
4363
|
+
return;
|
|
4364
|
+
}
|
|
4365
|
+
const root = containerRef.current;
|
|
4366
|
+
if (!root) {
|
|
4367
|
+
liveRenderAPIRef.current = null;
|
|
4368
|
+
return;
|
|
4369
|
+
}
|
|
4370
|
+
const renderSeekTo = (time) => {
|
|
4371
|
+
seekTo(time);
|
|
4372
|
+
return new Promise((resolve) => {
|
|
4373
|
+
requestAnimationFrame(() => {
|
|
4374
|
+
let blockStartTime = 0;
|
|
4375
|
+
for (let i = expandedBlocks.length - 1; i >= 0; i--) {
|
|
4376
|
+
if (time >= expandedBlocks[i].startTime) {
|
|
4377
|
+
blockStartTime = expandedBlocks[i].startTime;
|
|
4378
|
+
break;
|
|
3793
4379
|
}
|
|
3794
|
-
|
|
3795
|
-
|
|
3796
|
-
|
|
3797
|
-
|
|
3798
|
-
|
|
3799
|
-
|
|
3800
|
-
|
|
3801
|
-
|
|
3802
|
-
|
|
3803
|
-
});
|
|
3804
|
-
const blockElapsed = time - blockStartTime;
|
|
3805
|
-
const videoSeekPromises = [];
|
|
3806
|
-
const activeBlockEl = document.querySelector(".doc-player__block--active");
|
|
3807
|
-
if (activeBlockEl) {
|
|
3808
|
-
const videos = activeBlockEl.querySelectorAll("video[data-clip-start]");
|
|
3809
|
-
videos.forEach((el) => {
|
|
3810
|
-
const video = el;
|
|
3811
|
-
const clipStart = parseFloat(video.dataset.clipStart || "0");
|
|
3812
|
-
const clipEnd = parseFloat(video.dataset.clipEnd || "0");
|
|
3813
|
-
const startAt = parseFloat(video.dataset.startAt || "0");
|
|
3814
|
-
const targetTime = Math.min(
|
|
3815
|
-
clipStart + Math.max(0, blockElapsed - startAt),
|
|
3816
|
-
clipEnd
|
|
3817
|
-
);
|
|
3818
|
-
video.pause();
|
|
3819
|
-
video.currentTime = targetTime;
|
|
3820
|
-
videoSeekPromises.push(
|
|
3821
|
-
new Promise((r) => {
|
|
3822
|
-
if (Math.abs(video.currentTime - targetTime) < 0.1) {
|
|
3823
|
-
r();
|
|
3824
|
-
} else {
|
|
3825
|
-
video.addEventListener("seeked", () => r(), { once: true });
|
|
3826
|
-
setTimeout(r, 200);
|
|
3827
|
-
}
|
|
3828
|
-
})
|
|
3829
|
-
);
|
|
3830
|
-
});
|
|
4380
|
+
}
|
|
4381
|
+
const elapsedMs = (time - blockStartTime) * 1e3;
|
|
4382
|
+
(root.getAnimations?.() ?? []).forEach((anim) => {
|
|
4383
|
+
const target = anim.effect?.target;
|
|
4384
|
+
if (!target) return;
|
|
4385
|
+
if (target.closest(".doc-player__block--active")) {
|
|
4386
|
+
anim.currentTime = Math.max(0, elapsedMs);
|
|
4387
|
+
} else if (target.closest(".doc-player__block--previous")) {
|
|
4388
|
+
anim.currentTime = Math.max(0, elapsedMs);
|
|
3831
4389
|
}
|
|
3832
|
-
|
|
4390
|
+
});
|
|
4391
|
+
const blockElapsed = time - blockStartTime;
|
|
4392
|
+
const videoSeekPromises = [];
|
|
4393
|
+
const activeBlockEl = root.querySelector(".doc-player__block--active");
|
|
4394
|
+
if (activeBlockEl) {
|
|
4395
|
+
const videos = activeBlockEl.querySelectorAll("video[data-clip-start]");
|
|
4396
|
+
videos.forEach((el) => {
|
|
3833
4397
|
const video = el;
|
|
3834
|
-
const
|
|
3835
|
-
const
|
|
3836
|
-
const
|
|
4398
|
+
const clipStart = parseFloat(video.dataset.clipStart || "0");
|
|
4399
|
+
const clipEnd = parseFloat(video.dataset.clipEnd || "0");
|
|
4400
|
+
const startAt = parseFloat(video.dataset.startAt || "0");
|
|
4401
|
+
const targetTime = Math.min(clipStart + Math.max(0, blockElapsed - startAt), clipEnd);
|
|
3837
4402
|
video.pause();
|
|
3838
|
-
if (time < absStart || time >= absEnd) return;
|
|
3839
|
-
const targetTime = sourceIn + (time - absStart);
|
|
3840
4403
|
video.currentTime = targetTime;
|
|
3841
4404
|
videoSeekPromises.push(
|
|
3842
4405
|
new Promise((r) => {
|
|
@@ -3849,82 +4412,111 @@ function DocPlayerContent({
|
|
|
3849
4412
|
})
|
|
3850
4413
|
);
|
|
3851
4414
|
});
|
|
3852
|
-
|
|
3853
|
-
|
|
3854
|
-
|
|
4415
|
+
}
|
|
4416
|
+
root.querySelectorAll("video[data-clip-id]").forEach((el) => {
|
|
4417
|
+
const video = el;
|
|
4418
|
+
const absStart = parseFloat(video.dataset.absStart || "0");
|
|
4419
|
+
const absEnd = parseFloat(video.dataset.absEnd || "0");
|
|
4420
|
+
const sourceIn = parseFloat(video.dataset.sourceIn || "0");
|
|
4421
|
+
video.pause();
|
|
4422
|
+
if (time < absStart || time >= absEnd) return;
|
|
4423
|
+
const targetTime = sourceIn + (time - absStart);
|
|
4424
|
+
video.currentTime = targetTime;
|
|
4425
|
+
videoSeekPromises.push(
|
|
4426
|
+
new Promise((r) => {
|
|
4427
|
+
if (Math.abs(video.currentTime - targetTime) < 0.1) {
|
|
4428
|
+
r();
|
|
4429
|
+
} else {
|
|
4430
|
+
video.addEventListener("seeked", () => r(), { once: true });
|
|
4431
|
+
setTimeout(r, 200);
|
|
4432
|
+
}
|
|
4433
|
+
})
|
|
4434
|
+
);
|
|
4435
|
+
});
|
|
4436
|
+
Promise.all(videoSeekPromises).then(() => {
|
|
4437
|
+
requestAnimationFrame(() => resolve());
|
|
3855
4438
|
});
|
|
3856
4439
|
});
|
|
3857
|
-
};
|
|
3858
|
-
|
|
3859
|
-
|
|
3860
|
-
|
|
3861
|
-
|
|
3862
|
-
|
|
3863
|
-
|
|
3864
|
-
|
|
3865
|
-
|
|
3866
|
-
|
|
3867
|
-
|
|
3868
|
-
|
|
3869
|
-
|
|
3870
|
-
|
|
3871
|
-
|
|
3872
|
-
|
|
3873
|
-
|
|
4440
|
+
});
|
|
4441
|
+
};
|
|
4442
|
+
const getDuration = () => {
|
|
4443
|
+
const mediaDuration = getDocPlaybackDuration(doc);
|
|
4444
|
+
if (totalDuration > 0) return Math.max(totalDuration, mediaDuration);
|
|
4445
|
+
return mediaDuration;
|
|
4446
|
+
};
|
|
4447
|
+
const getBlocks = () => expandedBlocks.map((s) => ({
|
|
4448
|
+
id: s.id,
|
|
4449
|
+
template: s.template ?? "raw",
|
|
4450
|
+
startTime: s.startTime,
|
|
4451
|
+
duration: s.duration
|
|
4452
|
+
}));
|
|
4453
|
+
const getAudioSegments = () => doc.audio.segments.map((seg) => ({
|
|
4454
|
+
src: seg.src,
|
|
4455
|
+
name: seg.name,
|
|
4456
|
+
duration: seg.duration,
|
|
4457
|
+
startTime: seg.startTime
|
|
4458
|
+
}));
|
|
4459
|
+
const getCaptions = () => doc.captions?.phrases?.map((p) => ({
|
|
4460
|
+
text: p.text,
|
|
4461
|
+
startTime: p.startTime,
|
|
4462
|
+
endTime: p.endTime
|
|
4463
|
+
})) || [];
|
|
4464
|
+
const getChapters = () => {
|
|
4465
|
+
const titleMap = buildSegmentTitleMap(doc);
|
|
4466
|
+
return doc.audio.segments.map((seg, i) => ({
|
|
4467
|
+
title: titleMap.get(i) || seg.name,
|
|
4468
|
+
startTime: seg.startTime,
|
|
4469
|
+
duration: seg.duration
|
|
3874
4470
|
}));
|
|
3875
|
-
|
|
3876
|
-
|
|
3877
|
-
|
|
3878
|
-
|
|
3879
|
-
|
|
3880
|
-
|
|
3881
|
-
|
|
3882
|
-
|
|
3883
|
-
|
|
3884
|
-
|
|
3885
|
-
|
|
3886
|
-
|
|
3887
|
-
|
|
3888
|
-
|
|
3889
|
-
|
|
3890
|
-
|
|
3891
|
-
|
|
3892
|
-
|
|
3893
|
-
|
|
3894
|
-
|
|
3895
|
-
|
|
3896
|
-
|
|
3897
|
-
}
|
|
4471
|
+
};
|
|
4472
|
+
const showCover = () => {
|
|
4473
|
+
setCoverForced(true);
|
|
4474
|
+
return new Promise((resolve) => requestAnimationFrame(() => resolve()));
|
|
4475
|
+
};
|
|
4476
|
+
const hideCover = () => {
|
|
4477
|
+
setCoverForced(false);
|
|
4478
|
+
return new Promise((resolve) => requestAnimationFrame(() => resolve()));
|
|
4479
|
+
};
|
|
4480
|
+
const hasCoverBlock = () => !!coverBlock;
|
|
4481
|
+
const api = {
|
|
4482
|
+
seekTo: renderSeekTo,
|
|
4483
|
+
getDuration,
|
|
4484
|
+
getBlocks,
|
|
4485
|
+
getAudioSegments,
|
|
4486
|
+
getCaptions,
|
|
4487
|
+
getChapters,
|
|
4488
|
+
showCover,
|
|
4489
|
+
hideCover,
|
|
4490
|
+
hasCoverBlock
|
|
4491
|
+
};
|
|
4492
|
+
liveRenderAPIRef.current = api;
|
|
3898
4493
|
return () => {
|
|
3899
|
-
if (
|
|
3900
|
-
const w = window;
|
|
3901
|
-
delete w.seekTo;
|
|
3902
|
-
delete w.getDuration;
|
|
3903
|
-
delete w.getBlocks;
|
|
3904
|
-
delete w.getAudioSegments;
|
|
3905
|
-
delete w.getCaptions;
|
|
3906
|
-
delete w.getChapters;
|
|
3907
|
-
delete w.showCover;
|
|
3908
|
-
delete w.hideCover;
|
|
3909
|
-
delete w.hasCoverBlock;
|
|
3910
|
-
}
|
|
4494
|
+
if (liveRenderAPIRef.current === api) liveRenderAPIRef.current = null;
|
|
3911
4495
|
};
|
|
3912
|
-
}, [renderMode, isDebugMode, seekTo, totalDuration, expandedBlocks, coverBlock]);
|
|
4496
|
+
}, [renderMode, isDebugMode, seekTo, totalDuration, expandedBlocks, coverBlock, doc]);
|
|
4497
|
+
useEffect10(() => {
|
|
4498
|
+
if (!renderMode && !isDebugMode || !containerRef.current) {
|
|
4499
|
+
onRenderAPIReady?.(null);
|
|
4500
|
+
return;
|
|
4501
|
+
}
|
|
4502
|
+
onRenderAPIReady?.(stableRenderAPI);
|
|
4503
|
+
return () => onRenderAPIReady?.(null);
|
|
4504
|
+
}, [renderMode, isDebugMode, onRenderAPIReady, stableRenderAPI]);
|
|
3913
4505
|
const defaultMode = captionsEnabledProp === false ? "off" : captionStyle || "standard";
|
|
3914
|
-
const [captionMode, setCaptionMode] =
|
|
3915
|
-
|
|
4506
|
+
const [captionMode, setCaptionMode] = useState9(defaultMode);
|
|
4507
|
+
useEffect10(() => {
|
|
3916
4508
|
setCaptionMode(defaultMode);
|
|
3917
4509
|
}, [defaultMode]);
|
|
3918
4510
|
const captionsEnabled = captionMode !== "off";
|
|
3919
4511
|
const activeCaptionStyle = captionMode === "social" ? "social" : "standard";
|
|
3920
|
-
const setCaptionsEnabled =
|
|
4512
|
+
const setCaptionsEnabled = useCallback7(
|
|
3921
4513
|
(enabled) => {
|
|
3922
4514
|
setCaptionMode(enabled ? captionStyle || "standard" : "off");
|
|
3923
4515
|
onCaptionsToggle?.(enabled);
|
|
3924
4516
|
},
|
|
3925
4517
|
[onCaptionsToggle, captionStyle]
|
|
3926
4518
|
);
|
|
3927
|
-
const cycleCaptionMode =
|
|
4519
|
+
const cycleCaptionMode = useCallback7(() => {
|
|
3928
4520
|
setCaptionMode((prev) => {
|
|
3929
4521
|
const next = prev === "off" ? "standard" : prev === "standard" ? "social" : "off";
|
|
3930
4522
|
onCaptionsToggle?.(next !== "off");
|
|
@@ -3938,8 +4530,9 @@ function DocPlayerContent({
|
|
|
3938
4530
|
isPlaying,
|
|
3939
4531
|
currentTime,
|
|
3940
4532
|
totalDuration,
|
|
3941
|
-
|
|
3942
|
-
|
|
4533
|
+
isCoverVisible: showCoverBlock,
|
|
4534
|
+
currentBlockIndex: slideshowSlideIndex,
|
|
4535
|
+
totalBlocks: slideshowTotalSlides,
|
|
3943
4536
|
docProgress,
|
|
3944
4537
|
hasCaptions: !!hasCaptions,
|
|
3945
4538
|
captionsEnabled,
|
|
@@ -3947,15 +4540,19 @@ function DocPlayerContent({
|
|
|
3947
4540
|
isFullscreen,
|
|
3948
4541
|
currentSegmentIndex: currentSegment,
|
|
3949
4542
|
currentSegmentName: segmentTitleMap.get(currentSegment) ?? doc.audio.segments[currentSegment]?.name ?? null,
|
|
3950
|
-
currentBlock: currentBlock ?? null
|
|
4543
|
+
currentBlock: showSlideshowCover ? coverBlock : currentBlock ?? null,
|
|
4544
|
+
currentSlideLabel: showSlideshowCover ? "Cover" : void 0,
|
|
4545
|
+
currentSlideNumber: slideshowHasCover && !showSlideshowCover ? currentBlockIndex + 1 : void 0,
|
|
4546
|
+
totalSlideNumber: slideshowHasCover ? expandedBlocks.length : void 0
|
|
3951
4547
|
}),
|
|
3952
4548
|
// eslint-disable-next-line react-hooks/exhaustive-deps -- doc.audio.segments is stable within a given doc
|
|
3953
4549
|
[
|
|
3954
4550
|
isPlaying,
|
|
3955
4551
|
currentTime,
|
|
3956
4552
|
totalDuration,
|
|
3957
|
-
|
|
3958
|
-
|
|
4553
|
+
showCoverBlock,
|
|
4554
|
+
slideshowSlideIndex,
|
|
4555
|
+
slideshowTotalSlides,
|
|
3959
4556
|
docProgress,
|
|
3960
4557
|
hasCaptions,
|
|
3961
4558
|
captionsEnabled,
|
|
@@ -3963,7 +4560,12 @@ function DocPlayerContent({
|
|
|
3963
4560
|
isFullscreen,
|
|
3964
4561
|
currentSegment,
|
|
3965
4562
|
segmentTitleMap,
|
|
3966
|
-
currentBlock
|
|
4563
|
+
currentBlock,
|
|
4564
|
+
currentBlockIndex,
|
|
4565
|
+
showSlideshowCover,
|
|
4566
|
+
coverBlock,
|
|
4567
|
+
slideshowHasCover,
|
|
4568
|
+
expandedBlocks.length
|
|
3967
4569
|
]
|
|
3968
4570
|
);
|
|
3969
4571
|
const playbackActions = useMemo9(
|
|
@@ -3980,24 +4582,56 @@ function DocPlayerContent({
|
|
|
3980
4582
|
const slideNavActions = useMemo9(
|
|
3981
4583
|
() => ({
|
|
3982
4584
|
nextSlide: () => {
|
|
4585
|
+
if (slideshowHasCover && slideshowCoverVisible) {
|
|
4586
|
+
const target = expandedBlocks[0];
|
|
4587
|
+
if (target) {
|
|
4588
|
+
setSlideshowCoverVisible(false);
|
|
4589
|
+
seekTo(target.startTime);
|
|
4590
|
+
pause();
|
|
4591
|
+
}
|
|
4592
|
+
return;
|
|
4593
|
+
}
|
|
3983
4594
|
if (currentBlockIndex < expandedBlocks.length - 1) {
|
|
3984
4595
|
const target = expandedBlocks[currentBlockIndex + 1];
|
|
3985
4596
|
if (target) {
|
|
4597
|
+
setSlideshowCoverVisible(false);
|
|
3986
4598
|
seekTo(target.startTime);
|
|
3987
4599
|
pause();
|
|
3988
4600
|
}
|
|
3989
4601
|
}
|
|
3990
4602
|
},
|
|
3991
4603
|
prevSlide: () => {
|
|
4604
|
+
if (slideshowHasCover && !slideshowCoverVisible && currentBlockIndex <= 0) {
|
|
4605
|
+
setSlideshowCoverVisible(true);
|
|
4606
|
+
seekTo(0);
|
|
4607
|
+
pause();
|
|
4608
|
+
return;
|
|
4609
|
+
}
|
|
3992
4610
|
if (currentBlockIndex > 0) {
|
|
3993
4611
|
const target = expandedBlocks[currentBlockIndex - 1];
|
|
3994
4612
|
if (target) {
|
|
4613
|
+
setSlideshowCoverVisible(false);
|
|
3995
4614
|
seekTo(target.startTime);
|
|
3996
4615
|
pause();
|
|
3997
4616
|
}
|
|
3998
4617
|
}
|
|
3999
4618
|
},
|
|
4000
4619
|
goToSlide: (index) => {
|
|
4620
|
+
if (slideshowHasCover) {
|
|
4621
|
+
if (index === 0) {
|
|
4622
|
+
setSlideshowCoverVisible(true);
|
|
4623
|
+
seekTo(0);
|
|
4624
|
+
pause();
|
|
4625
|
+
return;
|
|
4626
|
+
}
|
|
4627
|
+
const target = expandedBlocks[index - 1];
|
|
4628
|
+
if (target) {
|
|
4629
|
+
setSlideshowCoverVisible(false);
|
|
4630
|
+
seekTo(target.startTime);
|
|
4631
|
+
pause();
|
|
4632
|
+
}
|
|
4633
|
+
return;
|
|
4634
|
+
}
|
|
4001
4635
|
if (index >= 0 && index < expandedBlocks.length) {
|
|
4002
4636
|
const target = expandedBlocks[index];
|
|
4003
4637
|
if (target) {
|
|
@@ -4007,26 +4641,42 @@ function DocPlayerContent({
|
|
|
4007
4641
|
}
|
|
4008
4642
|
}
|
|
4009
4643
|
}),
|
|
4010
|
-
[currentBlockIndex, expandedBlocks, seekTo, pause]
|
|
4644
|
+
[currentBlockIndex, expandedBlocks, seekTo, pause, slideshowHasCover, slideshowCoverVisible]
|
|
4011
4645
|
);
|
|
4012
4646
|
const swipeEnabled = isSlideshowMode && !isLinearMode && !renderMode && enableSwipe;
|
|
4647
|
+
const armContextFreeSwipeEntry = useCallback7(
|
|
4648
|
+
(destinationSlideIndex) => {
|
|
4649
|
+
const destinationBlockIndex = destinationSlideIndex - (slideshowHasCover ? 1 : 0);
|
|
4650
|
+
const destinationBlock = expandedBlocks[destinationBlockIndex];
|
|
4651
|
+
if (destinationBlock) suppressOutgoingForNextBlock(destinationBlock.id);
|
|
4652
|
+
},
|
|
4653
|
+
[expandedBlocks, slideshowHasCover, suppressOutgoingForNextBlock]
|
|
4654
|
+
);
|
|
4655
|
+
const handleSwipeNext = useCallback7(() => {
|
|
4656
|
+
armContextFreeSwipeEntry(slideshowSlideIndex + 1);
|
|
4657
|
+
slideNavActions.nextSlide();
|
|
4658
|
+
}, [armContextFreeSwipeEntry, slideshowSlideIndex, slideNavActions]);
|
|
4659
|
+
const handleSwipePrev = useCallback7(() => {
|
|
4660
|
+
armContextFreeSwipeEntry(slideshowSlideIndex - 1);
|
|
4661
|
+
slideNavActions.prevSlide();
|
|
4662
|
+
}, [armContextFreeSwipeEntry, slideshowSlideIndex, slideNavActions]);
|
|
4013
4663
|
const swipe = useSlideSwipe({
|
|
4014
4664
|
enabled: swipeEnabled,
|
|
4015
4665
|
containerRef,
|
|
4016
|
-
canGoNext:
|
|
4017
|
-
canGoPrev:
|
|
4018
|
-
onNext:
|
|
4019
|
-
onPrev:
|
|
4666
|
+
canGoNext: slideshowSlideIndex < slideshowTotalSlides - 1,
|
|
4667
|
+
canGoPrev: slideshowSlideIndex > 0,
|
|
4668
|
+
onNext: handleSwipeNext,
|
|
4669
|
+
onPrev: handleSwipePrev
|
|
4020
4670
|
});
|
|
4021
|
-
|
|
4671
|
+
useEffect10(() => {
|
|
4022
4672
|
onPlaybackStateChange?.(playbackState);
|
|
4023
4673
|
}, [playbackState, onPlaybackStateChange]);
|
|
4024
|
-
|
|
4674
|
+
useEffect10(() => {
|
|
4025
4675
|
onControlsReady?.({ play, pause, ...playbackActions });
|
|
4026
4676
|
}, [play, pause, playbackActions, onControlsReady]);
|
|
4027
|
-
const getBlockTitle =
|
|
4677
|
+
const getBlockTitle = useCallback7((block) => {
|
|
4028
4678
|
const docBlock = block;
|
|
4029
|
-
if (
|
|
4679
|
+
if (isTemplateBlock3(docBlock)) {
|
|
4030
4680
|
const props = docBlock;
|
|
4031
4681
|
if (typeof props.title === "string") return props.title;
|
|
4032
4682
|
if (typeof props.stat === "string") return props.stat;
|
|
@@ -4048,6 +4698,22 @@ function DocPlayerContent({
|
|
|
4048
4698
|
}
|
|
4049
4699
|
return block.id.replace(/-/g, " ").replace(/\b\w/g, (c) => c.toUpperCase());
|
|
4050
4700
|
}, []);
|
|
4701
|
+
const slideshowPickerItems = useMemo9(() => {
|
|
4702
|
+
const blockItems = expandedBlocks.map((block, index) => ({
|
|
4703
|
+
id: block.id,
|
|
4704
|
+
label: String(index + 1),
|
|
4705
|
+
summary: getBlockTitle(block)
|
|
4706
|
+
}));
|
|
4707
|
+
if (!slideshowHasCover || !coverBlock) return blockItems;
|
|
4708
|
+
return [
|
|
4709
|
+
{
|
|
4710
|
+
id: "__cover__",
|
|
4711
|
+
label: "Cover",
|
|
4712
|
+
summary: getBlockTitle(coverBlock)
|
|
4713
|
+
},
|
|
4714
|
+
...blockItems
|
|
4715
|
+
];
|
|
4716
|
+
}, [coverBlock, expandedBlocks, getBlockTitle, slideshowHasCover]);
|
|
4051
4717
|
const blockMarkers = useMemo9(() => {
|
|
4052
4718
|
if (!totalDuration || !expandedBlocks.length) return [];
|
|
4053
4719
|
let prevSegment = -1;
|
|
@@ -4063,16 +4729,26 @@ function DocPlayerContent({
|
|
|
4063
4729
|
};
|
|
4064
4730
|
});
|
|
4065
4731
|
}, [expandedBlocks, totalDuration, getBlockTitle]);
|
|
4066
|
-
|
|
4732
|
+
useEffect10(() => {
|
|
4067
4733
|
if (blockMarkers.length > 0) {
|
|
4068
4734
|
onBlockMarkers?.(blockMarkers);
|
|
4069
4735
|
}
|
|
4070
4736
|
}, [blockMarkers, onBlockMarkers]);
|
|
4071
|
-
expandedBlocksLenRef.current = expandedBlocks.length;
|
|
4072
|
-
const
|
|
4073
|
-
(e) => {
|
|
4074
|
-
|
|
4075
|
-
|
|
4737
|
+
expandedBlocksLenRef.current = isSlideshowMode ? slideshowTotalSlides : expandedBlocks.length;
|
|
4738
|
+
const handleKeyboardShortcut = useCallback7(
|
|
4739
|
+
(e, global) => {
|
|
4740
|
+
if (e.defaultPrevented || e.altKey || e.ctrlKey || e.metaKey || e.shiftKey) return;
|
|
4741
|
+
const target = e.target instanceof Element ? e.target : null;
|
|
4742
|
+
const isEditableTarget = !!target?.closest(
|
|
4743
|
+
'input, textarea, select, [contenteditable]:not([contenteditable="false"]), [role="textbox"], [role="combobox"], [role="listbox"], [role="slider"], [role="spinbutton"], .monaco-editor'
|
|
4744
|
+
);
|
|
4745
|
+
const isOpenInteractionTarget = !!target?.closest(
|
|
4746
|
+
'[role="menu"], [role="dialog"], [aria-modal="true"]'
|
|
4747
|
+
);
|
|
4748
|
+
const isSlideshowToolbarTarget = isSlideshowMode && !!target?.closest(".doc-controls-slideshow") && !target.closest('[role="menu"]');
|
|
4749
|
+
if (isEditableTarget || global && isOpenInteractionTarget || !global && !!target?.closest(
|
|
4750
|
+
'input, textarea, select, button, a, [contenteditable]:not([contenteditable="false"]), [role="textbox"]'
|
|
4751
|
+
) && !isSlideshowToolbarTarget) {
|
|
4076
4752
|
return;
|
|
4077
4753
|
}
|
|
4078
4754
|
if (isLinearMode) return;
|
|
@@ -4085,10 +4761,13 @@ function DocPlayerContent({
|
|
|
4085
4761
|
slideNavActions.nextSlide();
|
|
4086
4762
|
break;
|
|
4087
4763
|
case "ArrowLeft":
|
|
4088
|
-
case "ArrowUp":
|
|
4089
4764
|
e.preventDefault();
|
|
4090
4765
|
slideNavActions.prevSlide();
|
|
4091
4766
|
break;
|
|
4767
|
+
case "ArrowUp":
|
|
4768
|
+
e.preventDefault();
|
|
4769
|
+
setIsSlideshowPickerOpen(true);
|
|
4770
|
+
break;
|
|
4092
4771
|
case "Home":
|
|
4093
4772
|
e.preventDefault();
|
|
4094
4773
|
slideNavActions.goToSlide(0);
|
|
@@ -4105,9 +4784,11 @@ function DocPlayerContent({
|
|
|
4105
4784
|
toggle();
|
|
4106
4785
|
break;
|
|
4107
4786
|
case "ArrowRight":
|
|
4787
|
+
e.preventDefault();
|
|
4108
4788
|
seekTo(Math.min(currentTimeRef.current + 10, totalDurationRef.current));
|
|
4109
4789
|
break;
|
|
4110
4790
|
case "ArrowLeft":
|
|
4791
|
+
e.preventDefault();
|
|
4111
4792
|
seekTo(Math.max(currentTimeRef.current - 10, 0));
|
|
4112
4793
|
break;
|
|
4113
4794
|
}
|
|
@@ -4115,16 +4796,24 @@ function DocPlayerContent({
|
|
|
4115
4796
|
},
|
|
4116
4797
|
[isSlideshowMode, isLinearMode, toggle, seekTo, slideNavActions]
|
|
4117
4798
|
);
|
|
4118
|
-
|
|
4119
|
-
|
|
4120
|
-
|
|
4121
|
-
|
|
4122
|
-
|
|
4799
|
+
const handleKeyDown = useCallback7(
|
|
4800
|
+
(e) => handleKeyboardShortcut(e, false),
|
|
4801
|
+
[handleKeyboardShortcut]
|
|
4802
|
+
);
|
|
4803
|
+
useEffect10(() => {
|
|
4804
|
+
if (!globalKeyboardShortcuts || renderMode || isLinearMode) return;
|
|
4805
|
+
const handleDocumentKeyDown = (event) => {
|
|
4806
|
+
handleKeyboardShortcut(event, true);
|
|
4807
|
+
};
|
|
4808
|
+
document.addEventListener("keydown", handleDocumentKeyDown);
|
|
4809
|
+
return () => document.removeEventListener("keydown", handleDocumentKeyDown);
|
|
4810
|
+
}, [globalKeyboardShortcuts, handleKeyboardShortcut, isLinearMode, renderMode]);
|
|
4123
4811
|
if (isLinearMode) {
|
|
4124
|
-
return /* @__PURE__ */
|
|
4812
|
+
return /* @__PURE__ */ jsx21(
|
|
4125
4813
|
"div",
|
|
4126
4814
|
{
|
|
4127
4815
|
ref: containerRef,
|
|
4816
|
+
"data-player-id": playerId,
|
|
4128
4817
|
className: "doc-player doc-player--linear",
|
|
4129
4818
|
style: {
|
|
4130
4819
|
position: "relative",
|
|
@@ -4132,23 +4821,28 @@ function DocPlayerContent({
|
|
|
4132
4821
|
height: "100%",
|
|
4133
4822
|
overflow: "hidden"
|
|
4134
4823
|
},
|
|
4135
|
-
children: /* @__PURE__ */
|
|
4824
|
+
children: /* @__PURE__ */ jsx21(
|
|
4136
4825
|
LinearDocView,
|
|
4137
4826
|
{
|
|
4138
4827
|
doc,
|
|
4139
4828
|
basePath,
|
|
4140
4829
|
viewport: activeViewport,
|
|
4141
4830
|
theme,
|
|
4142
|
-
surface
|
|
4831
|
+
surface,
|
|
4832
|
+
animationsEnabled
|
|
4143
4833
|
}
|
|
4144
4834
|
)
|
|
4145
4835
|
}
|
|
4146
4836
|
);
|
|
4147
4837
|
}
|
|
4148
|
-
return /* @__PURE__ */
|
|
4838
|
+
return /* @__PURE__ */ jsxs14(
|
|
4149
4839
|
"div",
|
|
4150
4840
|
{
|
|
4151
4841
|
ref: containerRef,
|
|
4842
|
+
"data-player-id": playerId,
|
|
4843
|
+
tabIndex: renderMode ? -1 : 0,
|
|
4844
|
+
"aria-label": "Document player",
|
|
4845
|
+
onKeyDown: renderMode ? void 0 : handleKeyDown,
|
|
4152
4846
|
className: `doc-player${swipeEnabled ? " doc-player--swipe" : ""}${swipe.phase === "dragging" ? " doc-player--grabbing" : ""}`,
|
|
4153
4847
|
onClick: handleContainerClick,
|
|
4154
4848
|
onPointerDown: swipe.onPointerDown,
|
|
@@ -4164,33 +4858,35 @@ function DocPlayerContent({
|
|
|
4164
4858
|
touchAction: swipeEnabled ? "pan-y" : void 0
|
|
4165
4859
|
},
|
|
4166
4860
|
children: [
|
|
4167
|
-
/* @__PURE__ */
|
|
4168
|
-
/* @__PURE__ */
|
|
4861
|
+
/* @__PURE__ */ jsx21("audio", { ref: audioRef, preload: "auto", muted }),
|
|
4862
|
+
/* @__PURE__ */ jsx21(
|
|
4169
4863
|
MediaClipLayer,
|
|
4170
4864
|
{
|
|
4171
4865
|
schedule: mediaSchedule,
|
|
4172
4866
|
currentTime,
|
|
4173
4867
|
isPlaying,
|
|
4174
4868
|
basePath,
|
|
4175
|
-
renderMode
|
|
4869
|
+
renderMode,
|
|
4870
|
+
muted
|
|
4176
4871
|
}
|
|
4177
4872
|
),
|
|
4178
|
-
/* @__PURE__ */
|
|
4179
|
-
showCoverBlock && coverBlock && /* @__PURE__ */
|
|
4873
|
+
/* @__PURE__ */ jsxs14("div", { className: "doc-player__viewport", children: [
|
|
4874
|
+
showCoverBlock && coverBlock && /* @__PURE__ */ jsx21("div", { className: "doc-player__block doc-player__block--cover", children: /* @__PURE__ */ jsx21(
|
|
4180
4875
|
BlockRenderer,
|
|
4181
4876
|
{
|
|
4182
4877
|
block: coverBlock,
|
|
4183
4878
|
blockTime: 0,
|
|
4184
4879
|
basePath,
|
|
4185
4880
|
isEntering: false,
|
|
4186
|
-
viewport: activeViewport
|
|
4881
|
+
viewport: activeViewport,
|
|
4882
|
+
animationsEnabled
|
|
4187
4883
|
}
|
|
4188
4884
|
) }),
|
|
4189
|
-
!showCoverBlock && previousBlock && isExiting && // Keyed by block id so each block is its own DOM subtree: React never
|
|
4885
|
+
animationsEnabled && !showCoverBlock && previousBlock && isExiting && // Keyed by block id so each block is its own DOM subtree: React never
|
|
4190
4886
|
// reconciles one block's layers onto another's (templates reuse layer
|
|
4191
4887
|
// ids like `title`/`background`), which would otherwise reuse stale
|
|
4192
4888
|
// DOM / skip entrance animations mid-transition.
|
|
4193
|
-
/* @__PURE__ */
|
|
4889
|
+
/* @__PURE__ */ jsx21("div", { className: "doc-player__block doc-player__block--previous", children: /* @__PURE__ */ jsx21(
|
|
4194
4890
|
BlockRenderer,
|
|
4195
4891
|
{
|
|
4196
4892
|
block: previousBlock,
|
|
@@ -4198,29 +4894,31 @@ function DocPlayerContent({
|
|
|
4198
4894
|
basePath,
|
|
4199
4895
|
isExiting: true,
|
|
4200
4896
|
transition: currentBlock?.transition,
|
|
4201
|
-
viewport: activeViewport
|
|
4897
|
+
viewport: activeViewport,
|
|
4898
|
+
animationsEnabled
|
|
4202
4899
|
}
|
|
4203
4900
|
) }, previousBlock.id),
|
|
4204
|
-
!showCoverBlock && currentBlock && /* @__PURE__ */
|
|
4901
|
+
!showCoverBlock && currentBlock && /* @__PURE__ */ jsx21(
|
|
4205
4902
|
"div",
|
|
4206
4903
|
{
|
|
4207
4904
|
className: `doc-player__block doc-player__block--active${swipe.phase !== "idle" ? ` doc-player__block--${swipe.phase}` : ""}`,
|
|
4208
4905
|
style: swipe.phase !== "idle" ? { transform: `translateX(${swipe.offsetPx}px)` } : void 0,
|
|
4209
|
-
children: /* @__PURE__ */
|
|
4906
|
+
children: /* @__PURE__ */ jsx21(
|
|
4210
4907
|
BlockRenderer,
|
|
4211
4908
|
{
|
|
4212
4909
|
block: currentBlock,
|
|
4213
4910
|
blockTime,
|
|
4214
4911
|
basePath,
|
|
4215
|
-
isEntering,
|
|
4912
|
+
isEntering: animationsEnabled && isEntering,
|
|
4216
4913
|
viewport: activeViewport,
|
|
4217
|
-
isPlaying
|
|
4914
|
+
isPlaying,
|
|
4915
|
+
animationsEnabled
|
|
4218
4916
|
}
|
|
4219
4917
|
)
|
|
4220
4918
|
},
|
|
4221
4919
|
currentBlock.id
|
|
4222
4920
|
),
|
|
4223
|
-
hasCaptions && (renderMode ? captionsEnabled : true) && /* @__PURE__ */
|
|
4921
|
+
hasCaptions && (renderMode ? captionsEnabled : true) && /* @__PURE__ */ jsx21(
|
|
4224
4922
|
CaptionOverlay,
|
|
4225
4923
|
{
|
|
4226
4924
|
captions: doc.captions,
|
|
@@ -4232,7 +4930,7 @@ function DocPlayerContent({
|
|
|
4232
4930
|
viewport: activeViewport
|
|
4233
4931
|
}
|
|
4234
4932
|
),
|
|
4235
|
-
isDebugMode && /* @__PURE__ */
|
|
4933
|
+
isDebugMode && /* @__PURE__ */ jsxs14(
|
|
4236
4934
|
"div",
|
|
4237
4935
|
{
|
|
4238
4936
|
className: "doc-player__debug",
|
|
@@ -4253,27 +4951,27 @@ function DocPlayerContent({
|
|
|
4253
4951
|
textAlign: "left"
|
|
4254
4952
|
},
|
|
4255
4953
|
children: [
|
|
4256
|
-
/* @__PURE__ */
|
|
4257
|
-
/* @__PURE__ */
|
|
4258
|
-
/* @__PURE__ */
|
|
4954
|
+
/* @__PURE__ */ jsx21("div", { style: { color: "#ffcc00", fontWeight: "bold", marginBottom: "4px" }, children: "DEBUG MODE" }),
|
|
4955
|
+
/* @__PURE__ */ jsxs14("div", { children: [
|
|
4956
|
+
/* @__PURE__ */ jsx21("span", { style: { color: "#888" }, children: "template:" }),
|
|
4259
4957
|
" ",
|
|
4260
|
-
/* @__PURE__ */
|
|
4958
|
+
/* @__PURE__ */ jsx21("span", { style: { color: "#ff6b6b" }, children: currentBlock?.template ?? "raw" })
|
|
4261
4959
|
] }),
|
|
4262
|
-
/* @__PURE__ */
|
|
4263
|
-
/* @__PURE__ */
|
|
4960
|
+
/* @__PURE__ */ jsxs14("div", { children: [
|
|
4961
|
+
/* @__PURE__ */ jsx21("span", { style: { color: "#888" }, children: "block:" }),
|
|
4264
4962
|
" ",
|
|
4265
4963
|
currentBlockIndex + 1,
|
|
4266
4964
|
"/",
|
|
4267
4965
|
expandedBlocks.length,
|
|
4268
4966
|
" ",
|
|
4269
|
-
/* @__PURE__ */
|
|
4967
|
+
/* @__PURE__ */ jsxs14("span", { style: { color: "#666" }, children: [
|
|
4270
4968
|
"(",
|
|
4271
4969
|
currentBlock?.id || "none",
|
|
4272
4970
|
")"
|
|
4273
4971
|
] })
|
|
4274
4972
|
] }),
|
|
4275
|
-
/* @__PURE__ */
|
|
4276
|
-
/* @__PURE__ */
|
|
4973
|
+
/* @__PURE__ */ jsxs14("div", { children: [
|
|
4974
|
+
/* @__PURE__ */ jsx21("span", { style: { color: "#888" }, children: "time:" }),
|
|
4277
4975
|
" ",
|
|
4278
4976
|
currentTime.toFixed(2),
|
|
4279
4977
|
"s /",
|
|
@@ -4281,7 +4979,7 @@ function DocPlayerContent({
|
|
|
4281
4979
|
totalDuration.toFixed(1),
|
|
4282
4980
|
"s",
|
|
4283
4981
|
" ",
|
|
4284
|
-
/* @__PURE__ */
|
|
4982
|
+
/* @__PURE__ */ jsxs14("span", { style: { color: "#666" }, children: [
|
|
4285
4983
|
"(progress: ",
|
|
4286
4984
|
(docProgress * 100).toFixed(1),
|
|
4287
4985
|
"%, scriptDur: ",
|
|
@@ -4289,8 +4987,8 @@ function DocPlayerContent({
|
|
|
4289
4987
|
")"
|
|
4290
4988
|
] })
|
|
4291
4989
|
] }),
|
|
4292
|
-
/* @__PURE__ */
|
|
4293
|
-
/* @__PURE__ */
|
|
4990
|
+
/* @__PURE__ */ jsxs14("div", { children: [
|
|
4991
|
+
/* @__PURE__ */ jsx21("span", { style: { color: "#888" }, children: "blockTime:" }),
|
|
4294
4992
|
" ",
|
|
4295
4993
|
blockTime.toFixed(2),
|
|
4296
4994
|
"s /",
|
|
@@ -4298,58 +4996,58 @@ function DocPlayerContent({
|
|
|
4298
4996
|
(currentBlock?.duration || 0).toFixed(1),
|
|
4299
4997
|
"s"
|
|
4300
4998
|
] }),
|
|
4301
|
-
/* @__PURE__ */
|
|
4302
|
-
/* @__PURE__ */
|
|
4999
|
+
/* @__PURE__ */ jsxs14("div", { children: [
|
|
5000
|
+
/* @__PURE__ */ jsx21("span", { style: { color: "#888" }, children: "segment:" }),
|
|
4303
5001
|
" ",
|
|
4304
5002
|
currentSegment,
|
|
4305
5003
|
"/",
|
|
4306
5004
|
doc.audio.segments.length - 1,
|
|
4307
5005
|
" ",
|
|
4308
|
-
/* @__PURE__ */
|
|
5006
|
+
/* @__PURE__ */ jsxs14("span", { style: { color: "#666" }, children: [
|
|
4309
5007
|
"(",
|
|
4310
5008
|
doc.audio.segments[currentSegment]?.name || "none",
|
|
4311
5009
|
")"
|
|
4312
5010
|
] })
|
|
4313
5011
|
] }),
|
|
4314
|
-
/* @__PURE__ */
|
|
4315
|
-
/* @__PURE__ */
|
|
5012
|
+
/* @__PURE__ */ jsxs14("div", { children: [
|
|
5013
|
+
/* @__PURE__ */ jsx21("span", { style: { color: "#888" }, children: "viewport:" }),
|
|
4316
5014
|
" ",
|
|
4317
5015
|
activeViewport.name || `${activeViewport.width}x${activeViewport.height}`,
|
|
4318
5016
|
" ",
|
|
4319
|
-
/* @__PURE__ */
|
|
5017
|
+
/* @__PURE__ */ jsxs14("span", { style: { color: "#666" }, children: [
|
|
4320
5018
|
"(",
|
|
4321
5019
|
orientation,
|
|
4322
5020
|
")"
|
|
4323
5021
|
] })
|
|
4324
5022
|
] }),
|
|
4325
|
-
/* @__PURE__ */
|
|
4326
|
-
/* @__PURE__ */
|
|
5023
|
+
/* @__PURE__ */ jsxs14("div", { children: [
|
|
5024
|
+
/* @__PURE__ */ jsx21("span", { style: { color: "#888" }, children: "playing:" }),
|
|
4327
5025
|
" ",
|
|
4328
|
-
/* @__PURE__ */
|
|
4329
|
-
showCoverBlock && /* @__PURE__ */
|
|
5026
|
+
/* @__PURE__ */ jsx21("span", { style: { color: isPlaying ? "#4ade80" : "#f87171" }, children: isPlaying ? "yes" : "no" }),
|
|
5027
|
+
showCoverBlock && /* @__PURE__ */ jsx21("span", { style: { color: "#60a5fa" }, children: " (cover)" })
|
|
4330
5028
|
] }),
|
|
4331
5029
|
hasCaptions && (() => {
|
|
4332
5030
|
const debugPhrase = getCaptionAtTime2(doc.captions, currentTime);
|
|
4333
5031
|
const debugEnabled = captionsEnabled && (isPlaying || currentTime > 0);
|
|
4334
|
-
return /* @__PURE__ */
|
|
4335
|
-
/* @__PURE__ */
|
|
4336
|
-
/* @__PURE__ */
|
|
5032
|
+
return /* @__PURE__ */ jsxs14(Fragment3, { children: [
|
|
5033
|
+
/* @__PURE__ */ jsxs14("div", { children: [
|
|
5034
|
+
/* @__PURE__ */ jsx21("span", { style: { color: "#888" }, children: "captions:" }),
|
|
4337
5035
|
" ",
|
|
4338
5036
|
doc.captions?.phrases.length || 0,
|
|
4339
5037
|
" phrases",
|
|
4340
5038
|
" ",
|
|
4341
|
-
/* @__PURE__ */
|
|
5039
|
+
/* @__PURE__ */ jsxs14("span", { style: { color: captionsEnabled ? "#4ade80" : "#666" }, children: [
|
|
4342
5040
|
"(",
|
|
4343
5041
|
captionsEnabled ? "on" : "off",
|
|
4344
5042
|
")"
|
|
4345
5043
|
] })
|
|
4346
5044
|
] }),
|
|
4347
|
-
/* @__PURE__ */
|
|
4348
|
-
/* @__PURE__ */
|
|
5045
|
+
/* @__PURE__ */ jsxs14("div", { children: [
|
|
5046
|
+
/* @__PURE__ */ jsx21("span", { style: { color: "#888" }, children: "cc.enabled:" }),
|
|
4349
5047
|
" ",
|
|
4350
|
-
/* @__PURE__ */
|
|
5048
|
+
/* @__PURE__ */ jsx21("span", { style: { color: debugEnabled ? "#4ade80" : "#f87171" }, children: String(debugEnabled) }),
|
|
4351
5049
|
" ",
|
|
4352
|
-
/* @__PURE__ */
|
|
5050
|
+
/* @__PURE__ */ jsxs14("span", { style: { color: "#666" }, children: [
|
|
4353
5051
|
"(playing=",
|
|
4354
5052
|
String(isPlaying),
|
|
4355
5053
|
" t>0=",
|
|
@@ -4357,15 +5055,15 @@ function DocPlayerContent({
|
|
|
4357
5055
|
")"
|
|
4358
5056
|
] })
|
|
4359
5057
|
] }),
|
|
4360
|
-
/* @__PURE__ */
|
|
4361
|
-
/* @__PURE__ */
|
|
5058
|
+
/* @__PURE__ */ jsxs14("div", { children: [
|
|
5059
|
+
/* @__PURE__ */ jsx21("span", { style: { color: "#888" }, children: "cc.phrase:" }),
|
|
4362
5060
|
" ",
|
|
4363
|
-
/* @__PURE__ */
|
|
5061
|
+
/* @__PURE__ */ jsx21("span", { style: { color: debugPhrase ? "#4ade80" : "#f87171" }, children: debugPhrase ? `"${debugPhrase.text.slice(0, 30)}..."` : "null" })
|
|
4364
5062
|
] }),
|
|
4365
|
-
debugPhrase && /* @__PURE__ */
|
|
4366
|
-
/* @__PURE__ */
|
|
5063
|
+
debugPhrase && /* @__PURE__ */ jsxs14("div", { children: [
|
|
5064
|
+
/* @__PURE__ */ jsx21("span", { style: { color: "#888" }, children: "cc.range:" }),
|
|
4367
5065
|
" ",
|
|
4368
|
-
/* @__PURE__ */
|
|
5066
|
+
/* @__PURE__ */ jsxs14("span", { style: { color: "#60a5fa" }, children: [
|
|
4369
5067
|
debugPhrase.startTime.toFixed(2),
|
|
4370
5068
|
"-",
|
|
4371
5069
|
debugPhrase.endTime.toFixed(2)
|
|
@@ -4377,7 +5075,7 @@ function DocPlayerContent({
|
|
|
4377
5075
|
}
|
|
4378
5076
|
)
|
|
4379
5077
|
] }),
|
|
4380
|
-
!isAvailable && unavailableMessage && /* @__PURE__ */
|
|
5078
|
+
!isAvailable && unavailableMessage && /* @__PURE__ */ jsxs14(
|
|
4381
5079
|
"div",
|
|
4382
5080
|
{
|
|
4383
5081
|
className: "doc-player__unavailable",
|
|
@@ -4398,12 +5096,12 @@ function DocPlayerContent({
|
|
|
4398
5096
|
zIndex: 50
|
|
4399
5097
|
},
|
|
4400
5098
|
children: [
|
|
4401
|
-
/* @__PURE__ */
|
|
4402
|
-
/* @__PURE__ */
|
|
5099
|
+
/* @__PURE__ */ jsx21("span", { style: { fontSize: "32px" }, children: "\u{1F50A}" }),
|
|
5100
|
+
/* @__PURE__ */ jsx21("span", { children: unavailableMessage })
|
|
4403
5101
|
]
|
|
4404
5102
|
}
|
|
4405
5103
|
),
|
|
4406
|
-
!renderMode && !isSlideshowMode && showControls && /* @__PURE__ */
|
|
5104
|
+
!renderMode && !isSlideshowMode && showControls && /* @__PURE__ */ jsx21(
|
|
4407
5105
|
DocControlsOverlay,
|
|
4408
5106
|
{
|
|
4409
5107
|
state: playbackState,
|
|
@@ -4413,7 +5111,7 @@ function DocPlayerContent({
|
|
|
4413
5111
|
getBlockTitle
|
|
4414
5112
|
}
|
|
4415
5113
|
),
|
|
4416
|
-
!renderMode && !isSlideshowMode && !showControls && showScrubber && /* @__PURE__ */
|
|
5114
|
+
!renderMode && !isSlideshowMode && !showControls && showScrubber && /* @__PURE__ */ jsx21(
|
|
4417
5115
|
"div",
|
|
4418
5116
|
{
|
|
4419
5117
|
className: "doc-player__scrubber",
|
|
@@ -4428,7 +5126,7 @@ function DocPlayerContent({
|
|
|
4428
5126
|
alignItems: "center",
|
|
4429
5127
|
zIndex: 100
|
|
4430
5128
|
},
|
|
4431
|
-
children: /* @__PURE__ */
|
|
5129
|
+
children: /* @__PURE__ */ jsx21(
|
|
4432
5130
|
DocProgressBar,
|
|
4433
5131
|
{
|
|
4434
5132
|
state: playbackState,
|
|
@@ -4440,15 +5138,24 @@ function DocPlayerContent({
|
|
|
4440
5138
|
)
|
|
4441
5139
|
}
|
|
4442
5140
|
),
|
|
4443
|
-
!renderMode && isSlideshowMode && /* @__PURE__ */
|
|
4444
|
-
|
|
5141
|
+
!renderMode && isSlideshowMode && showControls && /* @__PURE__ */ jsx21(
|
|
5142
|
+
DocControlsSlideshow,
|
|
5143
|
+
{
|
|
5144
|
+
state: playbackState,
|
|
5145
|
+
slideNav: slideNavActions,
|
|
5146
|
+
slides: slideshowPickerItems,
|
|
5147
|
+
pickerOpen: isSlideshowPickerOpen,
|
|
5148
|
+
onPickerOpenChange: setIsSlideshowPickerOpen
|
|
5149
|
+
}
|
|
5150
|
+
),
|
|
5151
|
+
!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)
|
|
4445
5152
|
]
|
|
4446
5153
|
}
|
|
4447
5154
|
);
|
|
4448
5155
|
}
|
|
4449
5156
|
|
|
4450
5157
|
// src/DocControlsBottom.tsx
|
|
4451
|
-
import { jsx as
|
|
5158
|
+
import { jsx as jsx22, jsxs as jsxs15 } from "react/jsx-runtime";
|
|
4452
5159
|
function DocControlsBottom({
|
|
4453
5160
|
state,
|
|
4454
5161
|
actions,
|
|
@@ -4456,32 +5163,32 @@ function DocControlsBottom({
|
|
|
4456
5163
|
expandedBlocks,
|
|
4457
5164
|
getBlockTitle
|
|
4458
5165
|
}) {
|
|
4459
|
-
return /* @__PURE__ */
|
|
4460
|
-
/* @__PURE__ */
|
|
5166
|
+
return /* @__PURE__ */ jsxs15("div", { className: "doc-controls-bottom", children: [
|
|
5167
|
+
/* @__PURE__ */ jsx22(
|
|
4461
5168
|
"button",
|
|
4462
5169
|
{
|
|
4463
5170
|
className: "bottom-ctrl-btn",
|
|
4464
5171
|
onClick: actions.restart,
|
|
4465
5172
|
title: "Restart",
|
|
4466
5173
|
"aria-label": "Restart from beginning",
|
|
4467
|
-
children: /* @__PURE__ */
|
|
5174
|
+
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" }) })
|
|
4468
5175
|
}
|
|
4469
5176
|
),
|
|
4470
|
-
/* @__PURE__ */
|
|
5177
|
+
/* @__PURE__ */ jsx22(
|
|
4471
5178
|
"button",
|
|
4472
5179
|
{
|
|
4473
5180
|
className: "bottom-ctrl-btn bottom-play-btn",
|
|
4474
5181
|
onClick: actions.toggle,
|
|
4475
5182
|
"aria-label": state.isPlaying ? "Pause" : "Play",
|
|
4476
|
-
children: state.isPlaying ? /* @__PURE__ */
|
|
5183
|
+
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" }) })
|
|
4477
5184
|
}
|
|
4478
5185
|
),
|
|
4479
|
-
/* @__PURE__ */
|
|
5186
|
+
/* @__PURE__ */ jsxs15("span", { className: "bottom-time", children: [
|
|
4480
5187
|
formatTime(state.currentTime),
|
|
4481
5188
|
" / ",
|
|
4482
5189
|
formatTime(state.totalDuration)
|
|
4483
5190
|
] }),
|
|
4484
|
-
/* @__PURE__ */
|
|
5191
|
+
/* @__PURE__ */ jsx22(
|
|
4485
5192
|
DocProgressBar,
|
|
4486
5193
|
{
|
|
4487
5194
|
state,
|
|
@@ -4491,82 +5198,82 @@ function DocControlsBottom({
|
|
|
4491
5198
|
getBlockTitle
|
|
4492
5199
|
}
|
|
4493
5200
|
),
|
|
4494
|
-
/* @__PURE__ */
|
|
5201
|
+
/* @__PURE__ */ jsxs15("span", { className: "bottom-segment", children: [
|
|
4495
5202
|
state.currentBlockIndex + 1,
|
|
4496
5203
|
"/",
|
|
4497
5204
|
state.totalBlocks
|
|
4498
5205
|
] }),
|
|
4499
|
-
state.hasCaptions && /* @__PURE__ */
|
|
5206
|
+
state.hasCaptions && /* @__PURE__ */ jsx22(
|
|
4500
5207
|
"button",
|
|
4501
5208
|
{
|
|
4502
5209
|
className: `bottom-ctrl-btn ${state.captionMode !== "off" ? "bottom-ctrl-btn--active" : ""}`,
|
|
4503
5210
|
onClick: () => actions.cycleCaptionMode(),
|
|
4504
5211
|
title: state.captionMode === "off" ? "Captions: Off" : state.captionMode === "standard" ? "Captions: Standard" : "Captions: Social",
|
|
4505
5212
|
"aria-label": "Cycle caption style",
|
|
4506
|
-
children: /* @__PURE__ */
|
|
5213
|
+
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" }) })
|
|
4507
5214
|
}
|
|
4508
5215
|
)
|
|
4509
5216
|
] });
|
|
4510
5217
|
}
|
|
4511
5218
|
|
|
4512
5219
|
// src/DocControlsSidebar.tsx
|
|
4513
|
-
import { jsx as
|
|
5220
|
+
import { jsx as jsx23, jsxs as jsxs16 } from "react/jsx-runtime";
|
|
4514
5221
|
function DocControlsSidebar({ state, actions }) {
|
|
4515
|
-
return /* @__PURE__ */
|
|
4516
|
-
/* @__PURE__ */
|
|
5222
|
+
return /* @__PURE__ */ jsxs16("div", { className: "doc-controls-sidebar", children: [
|
|
5223
|
+
/* @__PURE__ */ jsx23(
|
|
4517
5224
|
"button",
|
|
4518
5225
|
{
|
|
4519
5226
|
className: "sidebar-ctrl-btn",
|
|
4520
5227
|
onClick: actions.restart,
|
|
4521
5228
|
title: "Restart",
|
|
4522
5229
|
"aria-label": "Restart from beginning",
|
|
4523
|
-
children: /* @__PURE__ */
|
|
5230
|
+
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" }) })
|
|
4524
5231
|
}
|
|
4525
5232
|
),
|
|
4526
|
-
/* @__PURE__ */
|
|
5233
|
+
/* @__PURE__ */ jsx23(
|
|
4527
5234
|
"button",
|
|
4528
5235
|
{
|
|
4529
5236
|
className: "sidebar-ctrl-btn sidebar-play-btn",
|
|
4530
5237
|
onClick: actions.toggle,
|
|
4531
5238
|
"aria-label": state.isPlaying ? "Pause" : "Play",
|
|
4532
|
-
children: state.isPlaying ? /* @__PURE__ */
|
|
5239
|
+
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" }) })
|
|
4533
5240
|
}
|
|
4534
5241
|
),
|
|
4535
|
-
/* @__PURE__ */
|
|
4536
|
-
/* @__PURE__ */
|
|
4537
|
-
/* @__PURE__ */
|
|
5242
|
+
/* @__PURE__ */ jsxs16("div", { className: "sidebar-time", children: [
|
|
5243
|
+
/* @__PURE__ */ jsx23("div", { children: formatTime(state.currentTime) }),
|
|
5244
|
+
/* @__PURE__ */ jsx23("div", { className: "sidebar-time-total", children: formatTime(state.totalDuration) })
|
|
4538
5245
|
] }),
|
|
4539
|
-
/* @__PURE__ */
|
|
5246
|
+
/* @__PURE__ */ jsxs16("div", { className: "sidebar-segment", children: [
|
|
4540
5247
|
state.currentBlockIndex + 1,
|
|
4541
5248
|
"/",
|
|
4542
5249
|
state.totalBlocks
|
|
4543
5250
|
] }),
|
|
4544
|
-
state.hasCaptions && /* @__PURE__ */
|
|
5251
|
+
state.hasCaptions && /* @__PURE__ */ jsx23(
|
|
4545
5252
|
"button",
|
|
4546
5253
|
{
|
|
4547
5254
|
className: `sidebar-ctrl-btn ${state.captionMode !== "off" ? "sidebar-ctrl-btn--active" : ""}`,
|
|
4548
5255
|
onClick: () => actions.cycleCaptionMode(),
|
|
4549
5256
|
title: state.captionMode === "off" ? "Captions: Off" : state.captionMode === "standard" ? "Captions: Standard" : "Captions: Social",
|
|
4550
5257
|
"aria-label": "Cycle caption style",
|
|
4551
|
-
children: /* @__PURE__ */
|
|
5258
|
+
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" }) })
|
|
4552
5259
|
}
|
|
4553
5260
|
),
|
|
4554
|
-
actions.toggleFullscreen && /* @__PURE__ */
|
|
5261
|
+
actions.toggleFullscreen && /* @__PURE__ */ jsx23(
|
|
4555
5262
|
"button",
|
|
4556
5263
|
{
|
|
4557
5264
|
className: `sidebar-ctrl-btn ${state.isFullscreen ? "sidebar-ctrl-btn--active" : ""}`,
|
|
4558
5265
|
onClick: actions.toggleFullscreen,
|
|
4559
5266
|
title: state.isFullscreen ? "Exit fullscreen (F)" : "Fullscreen (F)",
|
|
4560
5267
|
"aria-label": state.isFullscreen ? "Exit fullscreen" : "Enter fullscreen",
|
|
4561
|
-
children: state.isFullscreen ? /* @__PURE__ */
|
|
5268
|
+
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" }) })
|
|
4562
5269
|
}
|
|
4563
5270
|
)
|
|
4564
5271
|
] });
|
|
4565
5272
|
}
|
|
4566
5273
|
|
|
4567
5274
|
// src/DocPlayerWithSidebar.tsx
|
|
4568
|
-
import { useRef as
|
|
4569
|
-
import { jsx as
|
|
5275
|
+
import { useRef as useRef10, useState as useState10, useCallback as useCallback8, useEffect as useEffect11 } from "react";
|
|
5276
|
+
import { jsx as jsx24, jsxs as jsxs17 } from "react/jsx-runtime";
|
|
4570
5277
|
var DEFAULT_STATE = {
|
|
4571
5278
|
isPlaying: false,
|
|
4572
5279
|
currentTime: 0,
|
|
@@ -4588,6 +5295,7 @@ function DocPlayerWithSidebar({
|
|
|
4588
5295
|
onEnded,
|
|
4589
5296
|
onTimeUpdate,
|
|
4590
5297
|
audioController,
|
|
5298
|
+
animationsEnabled = true,
|
|
4591
5299
|
muted,
|
|
4592
5300
|
captionsEnabled,
|
|
4593
5301
|
isFullscreen,
|
|
@@ -4596,11 +5304,11 @@ function DocPlayerWithSidebar({
|
|
|
4596
5304
|
onPlayingChange,
|
|
4597
5305
|
theme
|
|
4598
5306
|
}) {
|
|
4599
|
-
const stateRef =
|
|
4600
|
-
const actionsRef =
|
|
4601
|
-
const wasPlayingRef =
|
|
4602
|
-
const [, setTick] =
|
|
4603
|
-
const handleStateChange =
|
|
5307
|
+
const stateRef = useRef10(DEFAULT_STATE);
|
|
5308
|
+
const actionsRef = useRef10(null);
|
|
5309
|
+
const wasPlayingRef = useRef10(false);
|
|
5310
|
+
const [, setTick] = useState10(0);
|
|
5311
|
+
const handleStateChange = useCallback8(
|
|
4604
5312
|
(state) => {
|
|
4605
5313
|
stateRef.current = state;
|
|
4606
5314
|
if (onPlayingChange && state.isPlaying !== wasPlayingRef.current) {
|
|
@@ -4610,7 +5318,7 @@ function DocPlayerWithSidebar({
|
|
|
4610
5318
|
},
|
|
4611
5319
|
[onPlayingChange]
|
|
4612
5320
|
);
|
|
4613
|
-
const handleControlsReady =
|
|
5321
|
+
const handleControlsReady = useCallback8(
|
|
4614
5322
|
(controls) => {
|
|
4615
5323
|
const isFirst = !actionsRef.current;
|
|
4616
5324
|
actionsRef.current = controls;
|
|
@@ -4618,14 +5326,14 @@ function DocPlayerWithSidebar({
|
|
|
4618
5326
|
},
|
|
4619
5327
|
[]
|
|
4620
5328
|
);
|
|
4621
|
-
|
|
5329
|
+
useEffect11(() => {
|
|
4622
5330
|
const interval = setInterval(() => {
|
|
4623
5331
|
setTick((t) => t + 1);
|
|
4624
5332
|
}, 250);
|
|
4625
5333
|
return () => clearInterval(interval);
|
|
4626
5334
|
}, []);
|
|
4627
|
-
return /* @__PURE__ */
|
|
4628
|
-
/* @__PURE__ */
|
|
5335
|
+
return /* @__PURE__ */ jsxs17("div", { className: "doc-player-sidebar-layout", children: [
|
|
5336
|
+
/* @__PURE__ */ jsx24("div", { className: "doc-player-sidebar-layout__video", children: /* @__PURE__ */ jsx24(
|
|
4629
5337
|
DocPlayer,
|
|
4630
5338
|
{
|
|
4631
5339
|
doc,
|
|
@@ -4635,6 +5343,7 @@ function DocPlayerWithSidebar({
|
|
|
4635
5343
|
onEnded,
|
|
4636
5344
|
onTimeUpdate,
|
|
4637
5345
|
audioController,
|
|
5346
|
+
animationsEnabled,
|
|
4638
5347
|
muted,
|
|
4639
5348
|
captionsEnabled,
|
|
4640
5349
|
showControls: isFullscreen,
|
|
@@ -4646,7 +5355,7 @@ function DocPlayerWithSidebar({
|
|
|
4646
5355
|
forceViewport
|
|
4647
5356
|
}
|
|
4648
5357
|
) }),
|
|
4649
|
-
actionsRef.current && /* @__PURE__ */
|
|
5358
|
+
actionsRef.current && /* @__PURE__ */ jsx24(DocControlsSidebar, { state: stateRef.current, actions: actionsRef.current })
|
|
4650
5359
|
] });
|
|
4651
5360
|
}
|
|
4652
5361
|
|
|
@@ -4677,18 +5386,18 @@ import {
|
|
|
4677
5386
|
arrayItemKind
|
|
4678
5387
|
} from "@bendyline/squisq/jsonForm";
|
|
4679
5388
|
import { parseMarkdown as parseMarkdown3 } from "@bendyline/squisq/markdown";
|
|
4680
|
-
import { Fragment as Fragment5, jsx as
|
|
5389
|
+
import { Fragment as Fragment5, jsx as jsx25, jsxs as jsxs18 } from "react/jsx-runtime";
|
|
4681
5390
|
function TextViewer({ value }) {
|
|
4682
5391
|
if (value === void 0 || value === null || value === "") {
|
|
4683
|
-
return /* @__PURE__ */
|
|
5392
|
+
return /* @__PURE__ */ jsx25("span", { className: "squisq-jv-empty", children: "\u2014" });
|
|
4684
5393
|
}
|
|
4685
|
-
return /* @__PURE__ */
|
|
5394
|
+
return /* @__PURE__ */ jsx25("span", { className: "squisq-jv-value", children: String(value) });
|
|
4686
5395
|
}
|
|
4687
5396
|
function MultilineViewer({ value }) {
|
|
4688
5397
|
if (value === void 0 || value === null || value === "") {
|
|
4689
|
-
return /* @__PURE__ */
|
|
5398
|
+
return /* @__PURE__ */ jsx25("span", { className: "squisq-jv-empty", children: "\u2014" });
|
|
4690
5399
|
}
|
|
4691
|
-
return /* @__PURE__ */
|
|
5400
|
+
return /* @__PURE__ */ jsx25("span", { className: "squisq-jv-value squisq-jv-value--multiline", children: String(value) });
|
|
4692
5401
|
}
|
|
4693
5402
|
function RichTextViewer({ value }) {
|
|
4694
5403
|
const nodes = useMemo11(() => {
|
|
@@ -4700,39 +5409,39 @@ function RichTextViewer({ value }) {
|
|
|
4700
5409
|
return null;
|
|
4701
5410
|
}
|
|
4702
5411
|
}, [value]);
|
|
4703
|
-
if (!nodes) return /* @__PURE__ */
|
|
4704
|
-
return /* @__PURE__ */
|
|
5412
|
+
if (!nodes) return /* @__PURE__ */ jsx25("span", { className: "squisq-jv-empty", children: "\u2014" });
|
|
5413
|
+
return /* @__PURE__ */ jsx25("div", { className: "squisq-jv-richtext", children: /* @__PURE__ */ jsx25(MarkdownRenderer, { nodes }) });
|
|
4705
5414
|
}
|
|
4706
5415
|
function NumberViewer({ value }) {
|
|
4707
5416
|
if (value === void 0 || value === null) {
|
|
4708
|
-
return /* @__PURE__ */
|
|
5417
|
+
return /* @__PURE__ */ jsx25("span", { className: "squisq-jv-empty", children: "\u2014" });
|
|
4709
5418
|
}
|
|
4710
|
-
return /* @__PURE__ */
|
|
5419
|
+
return /* @__PURE__ */ jsx25("span", { className: "squisq-jv-value", children: String(value) });
|
|
4711
5420
|
}
|
|
4712
5421
|
function BooleanViewer({ value }) {
|
|
4713
5422
|
const on = Boolean(value);
|
|
4714
|
-
return /* @__PURE__ */
|
|
5423
|
+
return /* @__PURE__ */ jsx25("span", { className: `squisq-jv-toggle squisq-jv-toggle--${on ? "on" : "off"}`, children: on ? "On" : "Off" });
|
|
4715
5424
|
}
|
|
4716
5425
|
function EnumViewer({ value, schema }) {
|
|
4717
5426
|
if (value === void 0 || value === null || value === "") {
|
|
4718
|
-
return /* @__PURE__ */
|
|
5427
|
+
return /* @__PURE__ */ jsx25("span", { className: "squisq-jv-empty", children: "\u2014" });
|
|
4719
5428
|
}
|
|
4720
5429
|
const labels = schema.squisq?.enumLabels;
|
|
4721
5430
|
const display = labels && typeof value === "string" ? labels[value] ?? value : String(value);
|
|
4722
|
-
return /* @__PURE__ */
|
|
5431
|
+
return /* @__PURE__ */ jsx25("span", { className: "squisq-jv-value", children: display });
|
|
4723
5432
|
}
|
|
4724
5433
|
function ColorViewer({ value }) {
|
|
4725
5434
|
if (typeof value !== "string" || value === "") {
|
|
4726
|
-
return /* @__PURE__ */
|
|
5435
|
+
return /* @__PURE__ */ jsx25("span", { className: "squisq-jv-empty", children: "\u2014" });
|
|
4727
5436
|
}
|
|
4728
|
-
return /* @__PURE__ */
|
|
4729
|
-
/* @__PURE__ */
|
|
4730
|
-
/* @__PURE__ */
|
|
5437
|
+
return /* @__PURE__ */ jsxs18("span", { className: "squisq-jv-color", children: [
|
|
5438
|
+
/* @__PURE__ */ jsx25("span", { className: "squisq-jv-color__swatch", style: { background: value }, "aria-hidden": true }),
|
|
5439
|
+
/* @__PURE__ */ jsx25("span", { className: "squisq-jv-color__hex", children: value })
|
|
4731
5440
|
] });
|
|
4732
5441
|
}
|
|
4733
5442
|
function DateViewer({ value, schema }) {
|
|
4734
5443
|
if (typeof value !== "string" || value === "") {
|
|
4735
|
-
return /* @__PURE__ */
|
|
5444
|
+
return /* @__PURE__ */ jsx25("span", { className: "squisq-jv-empty", children: "\u2014" });
|
|
4736
5445
|
}
|
|
4737
5446
|
const fmt = schema.format;
|
|
4738
5447
|
let display = value;
|
|
@@ -4749,31 +5458,31 @@ function DateViewer({ value, schema }) {
|
|
|
4749
5458
|
}
|
|
4750
5459
|
} catch {
|
|
4751
5460
|
}
|
|
4752
|
-
return /* @__PURE__ */
|
|
5461
|
+
return /* @__PURE__ */ jsx25("span", { className: "squisq-jv-value", children: display });
|
|
4753
5462
|
}
|
|
4754
5463
|
function ChipBinViewer({ value, schema }) {
|
|
4755
5464
|
if (!Array.isArray(value) || value.length === 0) {
|
|
4756
|
-
return /* @__PURE__ */
|
|
5465
|
+
return /* @__PURE__ */ jsx25("span", { className: "squisq-jv-empty", children: "\u2014" });
|
|
4757
5466
|
}
|
|
4758
5467
|
const itemSchema = Array.isArray(schema.items) ? schema.items[0] : schema.items;
|
|
4759
5468
|
const labels = itemSchema?.squisq?.enumLabels;
|
|
4760
|
-
return /* @__PURE__ */
|
|
5469
|
+
return /* @__PURE__ */ jsx25("div", { className: "squisq-jv-chip-bin", children: value.map((item, i) => {
|
|
4761
5470
|
const label = labels && typeof item === "string" ? labels[item] ?? String(item) : String(item);
|
|
4762
|
-
return /* @__PURE__ */
|
|
5471
|
+
return /* @__PURE__ */ jsx25("span", { className: "squisq-jv-chip", children: label }, i);
|
|
4763
5472
|
}) });
|
|
4764
5473
|
}
|
|
4765
5474
|
function CardStackViewer(props) {
|
|
4766
5475
|
const { value, schema, rootSchema, rootData, pointer, density } = props;
|
|
4767
5476
|
if (!Array.isArray(value) || value.length === 0) {
|
|
4768
|
-
return /* @__PURE__ */
|
|
5477
|
+
return /* @__PURE__ */ jsx25("span", { className: "squisq-jv-empty", children: "No items" });
|
|
4769
5478
|
}
|
|
4770
5479
|
const itemSchema = (Array.isArray(schema.items) ? schema.items[0] : schema.items) ?? {};
|
|
4771
5480
|
const itemLabel = itemSchema.squisq?.itemLabel;
|
|
4772
|
-
return /* @__PURE__ */
|
|
5481
|
+
return /* @__PURE__ */ jsx25("div", { className: "squisq-jv-card-stack", children: value.map((item, i) => {
|
|
4773
5482
|
const title = resolveItemTitle(itemLabel, item, i);
|
|
4774
|
-
return /* @__PURE__ */
|
|
4775
|
-
title ? /* @__PURE__ */
|
|
4776
|
-
/* @__PURE__ */
|
|
5483
|
+
return /* @__PURE__ */ jsxs18("div", { className: "squisq-jv-card", children: [
|
|
5484
|
+
title ? /* @__PURE__ */ jsx25("h4", { className: "squisq-jv-card__title", children: title }) : null,
|
|
5485
|
+
/* @__PURE__ */ jsx25(
|
|
4777
5486
|
RenderNode,
|
|
4778
5487
|
{
|
|
4779
5488
|
value: item,
|
|
@@ -4804,16 +5513,16 @@ function GroupViewer(props) {
|
|
|
4804
5513
|
const help = schema.squisq?.help ?? schema.description;
|
|
4805
5514
|
const obj = (value && typeof value === "object" ? value : {}) ?? {};
|
|
4806
5515
|
const propEntries = Object.entries(schema.properties ?? {});
|
|
4807
|
-
return /* @__PURE__ */
|
|
4808
|
-
title ? /* @__PURE__ */
|
|
4809
|
-
help ? /* @__PURE__ */
|
|
4810
|
-
propEntries.map(([key, propSchema]) => /* @__PURE__ */
|
|
5516
|
+
return /* @__PURE__ */ jsxs18("section", { className: "squisq-jv-group", children: [
|
|
5517
|
+
title ? /* @__PURE__ */ jsx25("h3", { className: "squisq-jv-group__title", children: title }) : null,
|
|
5518
|
+
help ? /* @__PURE__ */ jsx25("p", { className: "squisq-jv-group__help", children: help }) : null,
|
|
5519
|
+
propEntries.map(([key, propSchema]) => /* @__PURE__ */ jsx25(Fragment4, { children: /* @__PURE__ */ jsx25(
|
|
4811
5520
|
RowOrSection,
|
|
4812
5521
|
{
|
|
4813
5522
|
label: propSchema.squisq?.label ?? propSchema.title ?? key,
|
|
4814
5523
|
help: propSchema.squisq?.help ?? propSchema.description,
|
|
4815
5524
|
kindHint: propSchema,
|
|
4816
|
-
children: /* @__PURE__ */
|
|
5525
|
+
children: /* @__PURE__ */ jsx25(
|
|
4817
5526
|
RenderNode,
|
|
4818
5527
|
{
|
|
4819
5528
|
value: obj[key],
|
|
@@ -4837,11 +5546,11 @@ function RowOrSection({
|
|
|
4837
5546
|
}) {
|
|
4838
5547
|
const composite = isCompositeKind(kindHint);
|
|
4839
5548
|
if (composite) {
|
|
4840
|
-
return /* @__PURE__ */
|
|
5549
|
+
return /* @__PURE__ */ jsx25(Fragment5, { children });
|
|
4841
5550
|
}
|
|
4842
|
-
return /* @__PURE__ */
|
|
4843
|
-
/* @__PURE__ */
|
|
4844
|
-
/* @__PURE__ */
|
|
5551
|
+
return /* @__PURE__ */ jsxs18("div", { className: "squisq-jv-row", children: [
|
|
5552
|
+
/* @__PURE__ */ jsx25("div", { className: "squisq-jv-label", title: help, children: label }),
|
|
5553
|
+
/* @__PURE__ */ jsx25("div", { children })
|
|
4845
5554
|
] });
|
|
4846
5555
|
}
|
|
4847
5556
|
function isCompositeKind(schema) {
|
|
@@ -4862,11 +5571,11 @@ function TabsViewer(props) {
|
|
|
4862
5571
|
const matchedIndex = pickMatchingBranch(branches, value);
|
|
4863
5572
|
const branch = branches[matchedIndex];
|
|
4864
5573
|
if (!branch) {
|
|
4865
|
-
return /* @__PURE__ */
|
|
5574
|
+
return /* @__PURE__ */ jsx25(TextViewer, { ...props });
|
|
4866
5575
|
}
|
|
4867
|
-
return /* @__PURE__ */
|
|
4868
|
-
/* @__PURE__ */
|
|
4869
|
-
/* @__PURE__ */
|
|
5576
|
+
return /* @__PURE__ */ jsxs18("div", { className: "squisq-jv-tabs", children: [
|
|
5577
|
+
/* @__PURE__ */ jsx25("span", { className: "squisq-jv-tabs__discriminator", children: branch.squisq?.label ?? branch.title ?? `Option ${matchedIndex + 1}` }),
|
|
5578
|
+
/* @__PURE__ */ jsx25(
|
|
4870
5579
|
RenderNode,
|
|
4871
5580
|
{
|
|
4872
5581
|
value,
|
|
@@ -4930,7 +5639,7 @@ var VIEWERS = {
|
|
|
4930
5639
|
};
|
|
4931
5640
|
|
|
4932
5641
|
// src/jsonView/RenderNode.tsx
|
|
4933
|
-
import { jsx as
|
|
5642
|
+
import { jsx as jsx26 } from "react/jsx-runtime";
|
|
4934
5643
|
function RenderNode(props) {
|
|
4935
5644
|
const resolved = resolveRef(props.schema, props.rootSchema) ?? props.schema;
|
|
4936
5645
|
if (resolveFlag(resolved.squisq?.hidden, props.rootData)) return null;
|
|
@@ -4946,18 +5655,18 @@ function RenderNode(props) {
|
|
|
4946
5655
|
};
|
|
4947
5656
|
if (kind === "group" || kind === "card") {
|
|
4948
5657
|
const Group = Viewer;
|
|
4949
|
-
return /* @__PURE__ */
|
|
5658
|
+
return /* @__PURE__ */ jsx26(Group, { ...viewerProps, suppressTitle: props.suppressTopGroupTitle });
|
|
4950
5659
|
}
|
|
4951
|
-
return /* @__PURE__ */
|
|
5660
|
+
return /* @__PURE__ */ jsx26(Viewer, { ...viewerProps });
|
|
4952
5661
|
}
|
|
4953
5662
|
|
|
4954
5663
|
// src/jsonView/JsonView.tsx
|
|
4955
|
-
import { jsx as
|
|
5664
|
+
import { jsx as jsx27 } from "react/jsx-runtime";
|
|
4956
5665
|
function JsonView(props) {
|
|
4957
5666
|
const { schema, value, theme, surface, density = "comfortable", className } = props;
|
|
4958
5667
|
const { style } = useJsonViewTokens(theme, surface);
|
|
4959
5668
|
const cls = "squisq-json-view" + (density === "compact" ? " squisq-json-view--compact" : "") + (className ? ` ${className}` : "");
|
|
4960
|
-
return /* @__PURE__ */
|
|
5669
|
+
return /* @__PURE__ */ jsx27("div", { className: cls, style, children: /* @__PURE__ */ jsx27(
|
|
4961
5670
|
RenderNode,
|
|
4962
5671
|
{
|
|
4963
5672
|
value,
|
|
@@ -4993,7 +5702,7 @@ export {
|
|
|
4993
5702
|
SocialCaptionOverlay,
|
|
4994
5703
|
TableLayer,
|
|
4995
5704
|
TextLayer,
|
|
4996
|
-
|
|
5705
|
+
TreeLayer,
|
|
4997
5706
|
VideoLayer,
|
|
4998
5707
|
formatTime,
|
|
4999
5708
|
getAnimationStyle,
|