@effing/effie-preview 0.7.2 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +12 -42
- package/dist/react/index.d.ts +1 -0
- package/dist/react/index.js +18 -36
- package/dist/react/index.js.map +1 -1
- package/package.json +5 -4
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { EffieSources } from '@effing/effie';
|
|
2
|
+
import { WarmupEventMap } from '@effing/ffs/sse';
|
|
2
3
|
|
|
3
4
|
/**
|
|
4
5
|
* Create a source resolver function that handles #reference lookups
|
|
@@ -41,55 +42,24 @@ type EffieValidationIssue = {
|
|
|
41
42
|
*/
|
|
42
43
|
declare function parseEffieValidationIssues(issues: unknown): EffieValidationIssue[] | undefined;
|
|
43
44
|
|
|
44
|
-
/**
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
failed: number;
|
|
50
|
-
total: number;
|
|
51
|
-
ms?: number;
|
|
52
|
-
error?: string;
|
|
53
|
-
};
|
|
54
|
-
/** Downloading event during source fetch */
|
|
55
|
-
type EffieWarmupDownloadingEvent = {
|
|
56
|
-
url: string;
|
|
57
|
-
status: "started" | "downloading";
|
|
58
|
-
bytesReceived: number;
|
|
59
|
-
};
|
|
60
|
-
/** Union of all SSE event types */
|
|
45
|
+
/**
|
|
46
|
+
* Union of all warmup SSE event types, derived from the server-side
|
|
47
|
+
* `WarmupEventMap` so the client stays in sync automatically.
|
|
48
|
+
* Each variant is `{ type: K; data: WarmupEventMap[K] }`.
|
|
49
|
+
*/
|
|
61
50
|
type EffieWarmupEvent = {
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
} | {
|
|
68
|
-
type: "downloading";
|
|
69
|
-
data: EffieWarmupDownloadingEvent;
|
|
70
|
-
} | {
|
|
71
|
-
type: "keepalive";
|
|
72
|
-
cached: number;
|
|
73
|
-
failed: number;
|
|
74
|
-
total: number;
|
|
75
|
-
} | {
|
|
76
|
-
type: "summary";
|
|
77
|
-
cached: number;
|
|
78
|
-
failed: number;
|
|
79
|
-
total: number;
|
|
80
|
-
} | {
|
|
81
|
-
type: "complete";
|
|
82
|
-
status: "ready";
|
|
83
|
-
} | {
|
|
84
|
-
type: "error";
|
|
85
|
-
message: string;
|
|
86
|
-
};
|
|
51
|
+
[K in keyof WarmupEventMap & string]: {
|
|
52
|
+
type: K;
|
|
53
|
+
data: WarmupEventMap[K];
|
|
54
|
+
};
|
|
55
|
+
}[keyof WarmupEventMap & string];
|
|
87
56
|
/** Current warmup state */
|
|
88
57
|
type EffieWarmupState = {
|
|
89
58
|
status: "idle" | "connecting" | "warming" | "ready" | "error";
|
|
90
59
|
total: number;
|
|
91
60
|
cached: number;
|
|
92
61
|
failed: number;
|
|
62
|
+
skipped: number;
|
|
93
63
|
downloading: Map<string, {
|
|
94
64
|
url: string;
|
|
95
65
|
bytesReceived: number;
|
package/dist/react/index.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
2
|
import { EffieWebUrl, EffieBackground, EffieSources, EffieLayer, EffieSegment } from '@effing/effie';
|
|
3
3
|
import { EffieSourceResolver, EffieValidationIssue, EffieWarmupState } from '../index.js';
|
|
4
|
+
import '@effing/ffs/sse';
|
|
4
5
|
|
|
5
6
|
type EffieCoverPreviewProps = {
|
|
6
7
|
/** Cover image URL */
|
package/dist/react/index.js
CHANGED
|
@@ -6,46 +6,29 @@ import { AnniePlayer } from "@effing/annie-player/react";
|
|
|
6
6
|
function connectEffieWarmupStream(streamUrl, onEvent) {
|
|
7
7
|
const eventSource = new EventSource(streamUrl);
|
|
8
8
|
eventSource.addEventListener("start", (e) => {
|
|
9
|
-
|
|
10
|
-
onEvent({ type: "start", total: data.total });
|
|
9
|
+
onEvent({ type: "start", data: JSON.parse(e.data) });
|
|
11
10
|
});
|
|
12
11
|
eventSource.addEventListener("progress", (e) => {
|
|
13
|
-
|
|
14
|
-
e.data
|
|
15
|
-
);
|
|
16
|
-
onEvent({ type: "progress", data });
|
|
12
|
+
onEvent({ type: "progress", data: JSON.parse(e.data) });
|
|
17
13
|
});
|
|
18
14
|
eventSource.addEventListener("downloading", (e) => {
|
|
19
|
-
const data = JSON.parse(
|
|
20
|
-
e.data
|
|
21
|
-
);
|
|
22
|
-
onEvent({ type: "downloading", data });
|
|
23
|
-
});
|
|
24
|
-
eventSource.addEventListener("keepalive", (e) => {
|
|
25
|
-
const data = JSON.parse(e.data);
|
|
26
15
|
onEvent({
|
|
27
|
-
type: "
|
|
28
|
-
|
|
29
|
-
failed: data.failed,
|
|
30
|
-
total: data.total
|
|
16
|
+
type: "downloading",
|
|
17
|
+
data: JSON.parse(e.data)
|
|
31
18
|
});
|
|
32
19
|
});
|
|
20
|
+
eventSource.addEventListener("keepalive", (e) => {
|
|
21
|
+
onEvent({ type: "keepalive", data: JSON.parse(e.data) });
|
|
22
|
+
});
|
|
33
23
|
eventSource.addEventListener("summary", (e) => {
|
|
34
|
-
|
|
35
|
-
onEvent({
|
|
36
|
-
type: "summary",
|
|
37
|
-
cached: data.cached,
|
|
38
|
-
failed: data.failed,
|
|
39
|
-
total: data.total
|
|
40
|
-
});
|
|
24
|
+
onEvent({ type: "summary", data: JSON.parse(e.data) });
|
|
41
25
|
});
|
|
42
26
|
eventSource.addEventListener("complete", (e) => {
|
|
43
|
-
|
|
44
|
-
onEvent({ type: "complete", status: data.status });
|
|
27
|
+
onEvent({ type: "complete", data: JSON.parse(e.data) });
|
|
45
28
|
eventSource.close();
|
|
46
29
|
});
|
|
47
30
|
eventSource.addEventListener("error", () => {
|
|
48
|
-
onEvent({ type: "error", message: "Connection lost" });
|
|
31
|
+
onEvent({ type: "error", data: { message: "Connection lost" } });
|
|
49
32
|
eventSource.close();
|
|
50
33
|
});
|
|
51
34
|
return () => eventSource.close();
|
|
@@ -541,6 +524,7 @@ function useEffieWarmup(streamUrl) {
|
|
|
541
524
|
total: 0,
|
|
542
525
|
cached: 0,
|
|
543
526
|
failed: 0,
|
|
527
|
+
skipped: 0,
|
|
544
528
|
downloading: /* @__PURE__ */ new Map()
|
|
545
529
|
});
|
|
546
530
|
const cleanupRef = useRef(null);
|
|
@@ -553,6 +537,7 @@ function useEffieWarmup(streamUrl) {
|
|
|
553
537
|
total: 0,
|
|
554
538
|
cached: 0,
|
|
555
539
|
failed: 0,
|
|
540
|
+
skipped: 0,
|
|
556
541
|
downloading: /* @__PURE__ */ new Map(),
|
|
557
542
|
startTime: Date.now()
|
|
558
543
|
});
|
|
@@ -560,7 +545,7 @@ function useEffieWarmup(streamUrl) {
|
|
|
560
545
|
setState((prev) => {
|
|
561
546
|
switch (event.type) {
|
|
562
547
|
case "start":
|
|
563
|
-
return { ...prev, status: "warming", total: event.total };
|
|
548
|
+
return { ...prev, status: "warming", total: event.data.total };
|
|
564
549
|
case "progress": {
|
|
565
550
|
const newDownloading = new Map(prev.downloading);
|
|
566
551
|
newDownloading.delete(hashUrlToId(event.data.url));
|
|
@@ -568,6 +553,7 @@ function useEffieWarmup(streamUrl) {
|
|
|
568
553
|
...prev,
|
|
569
554
|
cached: event.data.cached,
|
|
570
555
|
failed: event.data.failed,
|
|
556
|
+
skipped: event.data.skipped,
|
|
571
557
|
downloading: newDownloading
|
|
572
558
|
};
|
|
573
559
|
}
|
|
@@ -580,21 +566,17 @@ function useEffieWarmup(streamUrl) {
|
|
|
580
566
|
return { ...prev, downloading: newDownloading };
|
|
581
567
|
}
|
|
582
568
|
case "keepalive":
|
|
583
|
-
return {
|
|
584
|
-
...prev,
|
|
585
|
-
cached: event.cached,
|
|
586
|
-
failed: event.failed
|
|
587
|
-
};
|
|
588
569
|
case "summary":
|
|
589
570
|
return {
|
|
590
571
|
...prev,
|
|
591
|
-
cached: event.cached,
|
|
592
|
-
failed: event.failed
|
|
572
|
+
cached: event.data.cached,
|
|
573
|
+
failed: event.data.failed,
|
|
574
|
+
skipped: event.data.skipped
|
|
593
575
|
};
|
|
594
576
|
case "complete":
|
|
595
577
|
return { ...prev, status: "ready", endTime: Date.now() };
|
|
596
578
|
case "error":
|
|
597
|
-
return { ...prev, status: "error", error: event.message };
|
|
579
|
+
return { ...prev, status: "error", error: event.data.message };
|
|
598
580
|
default:
|
|
599
581
|
return prev;
|
|
600
582
|
}
|
package/dist/react/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/react/index.tsx","../../src/warmup.ts","../../src/utils.ts"],"sourcesContent":["import { useEffect, useRef, useState } from \"react\";\nimport type {\n EffieBackground,\n EffieSegment,\n EffieLayer,\n EffieSources,\n EffieWebUrl,\n} from \"@effing/effie\";\nimport { AnniePlayer } from \"@effing/annie-player/react\";\nimport type { EffieSourceResolver, EffieValidationIssue } from \"../core\";\nimport { connectEffieWarmupStream, type EffieWarmupState } from \"../warmup\";\nimport { hashUrlToId } from \"../utils\";\n\n// ============ Cover Preview ============\n\nexport type EffieCoverPreviewProps = {\n /** Cover image URL */\n cover: EffieWebUrl;\n /** Resolution for preview */\n resolution: { width: number; height: number };\n /** Optional video URL to show instead of cover image (e.g., after rendering) */\n video?: string | null;\n /** Callback when video starts playing */\n onPlay?: () => void;\n /** Callback when video is fully buffered (entire video downloaded) */\n onFullyBuffered?: () => void;\n /** Class name for the img/video element */\n className?: string;\n /** Style for the img/video element */\n style?: React.CSSProperties;\n};\n\n/**\n * Displays the effie cover image, or a video if provided.\n * Simple component with built-in inline styles.\n */\nexport function EffieCoverPreview({\n cover,\n resolution,\n video,\n onPlay,\n onFullyBuffered,\n className,\n style,\n}: EffieCoverPreviewProps) {\n const fullyBufferedRef = useRef(false);\n const lastVideoRef = useRef<string | null>(null);\n\n // Reset the fully buffered flag when video URL changes\n if (video !== lastVideoRef.current) {\n lastVideoRef.current = video ?? null;\n fullyBufferedRef.current = false;\n }\n\n const handleProgress = (e: React.SyntheticEvent<HTMLVideoElement>) => {\n if (fullyBufferedRef.current || !onFullyBuffered) return;\n\n const vid = e.currentTarget;\n if (vid.buffered.length > 0 && vid.duration > 0) {\n const bufferedEnd = vid.buffered.end(vid.buffered.length - 1);\n if (bufferedEnd >= vid.duration) {\n fullyBufferedRef.current = true;\n onFullyBuffered();\n }\n }\n };\n\n if (video) {\n return (\n <video\n src={video}\n poster={cover}\n crossOrigin=\"anonymous\"\n className={className}\n style={{ ...style, height: resolution.height }}\n controls\n autoPlay\n onPlay={onPlay}\n onProgress={handleProgress}\n />\n );\n }\n\n return (\n <img\n src={cover}\n alt=\"Cover\"\n className={className}\n style={{ ...style, height: resolution.height }}\n />\n );\n}\n\n// ============ Background Preview - Compound Components ============\n\ntype EffieBackgroundPreviewRootProps = {\n className?: string;\n style?: React.CSSProperties;\n children: React.ReactNode;\n};\n\nfunction EffieBackgroundPreviewRoot({\n className,\n style,\n children,\n}: EffieBackgroundPreviewRootProps) {\n return (\n <div className={className} style={style}>\n {children}\n </div>\n );\n}\n\ntype EffieBackgroundPreviewMediaProps = {\n /** Background configuration from effie JSON */\n background: EffieBackground<EffieSources>;\n /** Function to resolve source references */\n resolveSource: EffieSourceResolver;\n /** Class name for the media element */\n className?: string;\n /** Style for the media element */\n style?: React.CSSProperties;\n};\n\nfunction EffieBackgroundPreviewMedia({\n background,\n resolveSource,\n className,\n style,\n}: EffieBackgroundPreviewMediaProps) {\n if (background.type === \"color\") {\n return (\n <div\n className={className}\n style={{ ...style, backgroundColor: background.color }}\n />\n );\n }\n\n if (background.type === \"image\") {\n return (\n <img\n src={resolveSource(background.source)}\n alt=\"Background\"\n className={className}\n style={{ objectFit: \"cover\", ...style }}\n />\n );\n }\n\n if (background.type === \"video\") {\n return (\n <video\n src={resolveSource(background.source)}\n className={className}\n style={{ objectFit: \"cover\", ...style }}\n autoPlay\n loop\n muted\n />\n );\n }\n\n return null;\n}\n\ntype EffieBackgroundPreviewInfoProps = {\n /** Background configuration from effie JSON */\n background: EffieBackground<EffieSources>;\n /** Class name for the info section */\n className?: string;\n /** Style for the info section */\n style?: React.CSSProperties;\n};\n\nfunction EffieBackgroundPreviewInfo({\n background,\n className,\n style,\n}: EffieBackgroundPreviewInfoProps) {\n return (\n <div className={className} style={style}>\n <strong>Type:</strong> {background.type}\n {background.type === \"color\" && (\n <div>\n <strong>Color:</strong> {background.color}\n </div>\n )}\n </div>\n );\n}\n\n// Simple pre-composed Background Preview\n\nexport type EffieBackgroundPreviewProps = {\n /** Background configuration from effie JSON */\n background: EffieBackground<EffieSources>;\n /** Function to resolve source references */\n resolveSource: EffieSourceResolver;\n /** Resolution for preview */\n resolution: { width: number; height: number };\n /** Class name for the container */\n className?: string;\n /** Style for the container */\n style?: React.CSSProperties;\n};\n\nfunction EffieBackgroundPreviewSimple({\n background,\n resolveSource,\n resolution,\n className,\n style,\n}: EffieBackgroundPreviewProps) {\n return (\n <EffieBackgroundPreviewRoot\n className={className}\n style={{\n display: \"flex\",\n gap: \"1rem\",\n ...style,\n }}\n >\n <EffieBackgroundPreviewMedia\n background={background}\n resolveSource={resolveSource}\n style={{\n border: \"1px solid #ddd\",\n width: resolution.width,\n height: resolution.height,\n }}\n />\n <EffieBackgroundPreviewInfo\n background={background}\n style={{\n fontSize: \"0.85rem\",\n color: \"#666\",\n }}\n />\n </EffieBackgroundPreviewRoot>\n );\n}\n\n/**\n * Displays a preview of the effie background (color, image, or video).\n *\n * Usage:\n * - Simple: `<EffieBackgroundPreview background={...} resolveSource={...} />`\n * - Compound: `<EffieBackgroundPreview.Root>...</EffieBackgroundPreview.Root>`\n */\nexport const EffieBackgroundPreview = Object.assign(\n EffieBackgroundPreviewSimple,\n {\n Root: EffieBackgroundPreviewRoot,\n Media: EffieBackgroundPreviewMedia,\n Info: EffieBackgroundPreviewInfo,\n },\n);\n\n// ============ Layer Preview - Compound Components ============\n\ntype EffieLayerPreviewRootProps = {\n className?: string;\n style?: React.CSSProperties;\n children: React.ReactNode;\n};\n\nfunction EffieLayerPreviewRoot({\n className,\n style,\n children,\n}: EffieLayerPreviewRootProps) {\n return (\n <div className={className} style={style}>\n {children}\n </div>\n );\n}\n\ntype EffieLayerPreviewMediaProps = {\n /** Layer configuration from effie JSON */\n layer: EffieLayer<EffieSources>;\n /** Layer index (0-based) for alt text */\n index: number;\n /** Function to resolve source references */\n resolveSource: EffieSourceResolver;\n /** Resolution for preview */\n resolution: { width: number; height: number };\n /** Class name for the media element */\n className?: string;\n /** Style for the media element */\n style?: React.CSSProperties;\n};\n\nfunction EffieLayerPreviewMedia({\n layer,\n index,\n resolveSource,\n resolution,\n className,\n style,\n}: EffieLayerPreviewMediaProps) {\n if (layer.type === \"animation\") {\n return (\n <AnniePlayer\n src={resolveSource(layer.source)}\n height={resolution.height}\n defaultWidth={resolution.width}\n autoLoad={false}\n autoPlay={true}\n className={className}\n style={style}\n />\n );\n }\n\n return (\n <img\n src={resolveSource(layer.source)}\n alt={`Layer ${index + 1}`}\n className={className}\n style={{ ...style, height: resolution.height }}\n />\n );\n}\n\ntype EffieLayerPreviewInfoProps = {\n /** Layer configuration from effie JSON */\n layer: EffieLayer<EffieSources>;\n /** Layer index (0-based) */\n index: number;\n /** Class name for the info section */\n className?: string;\n /** Style for the info section */\n style?: React.CSSProperties;\n};\n\nfunction EffieLayerPreviewInfo({\n layer,\n index,\n className,\n style,\n}: EffieLayerPreviewInfoProps) {\n return (\n <div className={className} style={style}>\n <strong>Layer {index + 1}</strong> ({layer.type})\n {layer.delay !== undefined && layer.delay > 0 && (\n <div>Delay: {layer.delay}s</div>\n )}\n {layer.from !== undefined && <div>From: {layer.from}s</div>}\n {layer.until !== undefined && <div>Until: {layer.until}s</div>}\n {layer.effects && layer.effects.length > 0 && (\n <EffieJsonBlock label=\"Effects\" data={layer.effects} />\n )}\n {layer.motion && <EffieJsonBlock label=\"Motion\" data={layer.motion} />}\n </div>\n );\n}\n\n// Simple pre-composed Layer Preview\n\nexport type EffieLayerPreviewProps = {\n /** Layer configuration from effie JSON */\n layer: EffieLayer<EffieSources>;\n /** Layer index (0-based) */\n index: number;\n /** Function to resolve source references */\n resolveSource: EffieSourceResolver;\n /** Resolution for preview */\n resolution: { width: number; height: number };\n /** How to stack info relative to media: \"horizontal\" (default, info beside) or \"vertical\" (info below) */\n stacking?: \"vertical\" | \"horizontal\";\n /** Class name for the container */\n className?: string;\n /** Style for the container */\n style?: React.CSSProperties;\n};\n\nfunction EffieLayerPreviewSimple({\n layer,\n index,\n resolveSource,\n resolution,\n stacking = \"horizontal\",\n className,\n style,\n}: EffieLayerPreviewProps) {\n return (\n <EffieLayerPreviewRoot\n className={className}\n style={{\n display: \"flex\",\n flexDirection: stacking === \"vertical\" ? \"column\" : \"row\",\n gap: \"1rem\",\n ...style,\n }}\n >\n <EffieLayerPreviewMedia\n layer={layer}\n index={index}\n resolveSource={resolveSource}\n resolution={resolution}\n style={{\n border: \"1px solid #ddd\",\n }}\n />\n <EffieLayerPreviewInfo\n layer={layer}\n index={index}\n style={{\n fontSize: \"0.85rem\",\n color: \"#666\",\n }}\n />\n </EffieLayerPreviewRoot>\n );\n}\n\n/**\n * Displays a preview of a single layer, including its metadata.\n * Uses AnniePlayer for animation layers.\n *\n * Usage:\n * - Simple: `<EffieLayerPreview layer={...} index={...} resolveSource={...} />`\n * - Compound: `<EffieLayerPreview.Root>...</EffieLayerPreview.Root>`\n */\nexport const EffieLayerPreview = Object.assign(EffieLayerPreviewSimple, {\n Root: EffieLayerPreviewRoot,\n Media: EffieLayerPreviewMedia,\n Info: EffieLayerPreviewInfo,\n});\n\n// ============ Segment Preview - Compound Components ============\n\ntype EffieSegmentPreviewRootProps = {\n className?: string;\n style?: React.CSSProperties;\n children: React.ReactNode;\n};\n\nfunction EffieSegmentPreviewRoot({\n className,\n style,\n children,\n}: EffieSegmentPreviewRootProps) {\n return (\n <div className={className} style={style}>\n {children}\n </div>\n );\n}\n\ntype EffieSegmentPreviewHeaderProps = {\n className?: string;\n style?: React.CSSProperties;\n children: React.ReactNode;\n};\n\nfunction EffieSegmentPreviewHeader({\n className,\n style,\n children,\n}: EffieSegmentPreviewHeaderProps) {\n return (\n <div className={className} style={style}>\n {children}\n </div>\n );\n}\n\ntype EffieSegmentPreviewLayersProps = {\n className?: string;\n style?: React.CSSProperties;\n children: React.ReactNode;\n};\n\nfunction EffieSegmentPreviewLayers({\n className,\n style,\n children,\n}: EffieSegmentPreviewLayersProps) {\n return (\n <div className={className} style={style}>\n {children}\n </div>\n );\n}\n\n// Simple pre-composed Segment Preview\n\nexport type EffieSegmentPreviewProps = {\n /** Segment configuration from effie JSON */\n segment: EffieSegment<EffieSources>;\n /** Segment index (0-based) */\n index: number;\n /** Function to resolve source references */\n resolveSource: EffieSourceResolver;\n /** Resolution for preview */\n resolution: { width: number; height: number };\n /** How to stack layers: \"vertical\" (default) or \"horizontal\" */\n stacking?: \"vertical\" | \"horizontal\";\n /** Class name for the container */\n className?: string;\n /** Style for the container */\n style?: React.CSSProperties;\n};\n\nfunction EffieSegmentPreviewSimple({\n segment,\n index,\n resolveSource,\n resolution,\n stacking = \"vertical\",\n className,\n style,\n}: EffieSegmentPreviewProps) {\n const layerStacking = stacking === \"horizontal\" ? \"vertical\" : \"horizontal\";\n\n return (\n <EffieSegmentPreviewRoot\n className={className}\n style={{\n padding: \"1rem\",\n border: \"1px solid #ddd\",\n borderRadius: 8,\n backgroundColor: \"#fafafa\",\n ...style,\n }}\n >\n <EffieSegmentPreviewHeader\n style={{\n fontWeight: 600,\n marginBottom: \"1rem\",\n }}\n >\n <strong>Segment {index + 1}</strong>\n {\" — \"}\n {segment.duration}s\n {segment.transition && (\n <span>\n {\" \"}\n (transition: {segment.transition.type},{\" \"}\n {segment.transition.duration}\n s)\n </span>\n )}\n </EffieSegmentPreviewHeader>\n\n <EffieSegmentPreviewLayers\n style={{\n display: \"flex\",\n flexDirection: stacking === \"horizontal\" ? \"row\" : \"column\",\n flexWrap: stacking === \"horizontal\" ? \"wrap\" : undefined,\n gap: \"1rem\",\n }}\n >\n {segment.layers.map((layer, j) => (\n <EffieLayerPreviewSimple\n key={j}\n layer={layer}\n index={j}\n resolveSource={resolveSource}\n resolution={resolution}\n stacking={layerStacking}\n />\n ))}\n </EffieSegmentPreviewLayers>\n </EffieSegmentPreviewRoot>\n );\n}\n\n/**\n * Displays a preview of a segment with all its layers.\n *\n * Usage:\n * - Simple: `<EffieSegmentPreview segment={...} index={...} resolveSource={...} />`\n * - Compound: `<EffieSegmentPreview.Root>...</EffieSegmentPreview.Root>`\n */\nexport const EffieSegmentPreview = Object.assign(EffieSegmentPreviewSimple, {\n Root: EffieSegmentPreviewRoot,\n Header: EffieSegmentPreviewHeader,\n Layers: EffieSegmentPreviewLayers,\n});\n\n// ============ Validation Errors ============\n\nexport type EffieValidationErrorsProps = {\n /** Error message (e.g., \"Invalid effie data\") */\n error: string;\n /** List of validation issues */\n issues?: EffieValidationIssue[];\n /** Class name for the container */\n className?: string;\n /** Style for the container */\n style?: React.CSSProperties;\n};\n\n/**\n * Displays validation errors with detailed issue breakdown.\n * Simple component with built-in inline styles.\n */\nexport function EffieValidationErrors({\n error,\n issues,\n className,\n style,\n}: EffieValidationErrorsProps) {\n return (\n <div\n className={className}\n style={{\n padding: \"1rem\",\n backgroundColor: \"#fff5f5\",\n border: \"1px solid #ffcccc\",\n borderRadius: 8,\n ...style,\n }}\n >\n <div\n style={{\n color: \"red\",\n fontWeight: 600,\n }}\n >\n Error: {error}\n </div>\n {issues && issues.length > 0 && (\n <ul\n style={{\n margin: \"0.5rem 0\",\n paddingLeft: \"1.5rem\",\n }}\n >\n {issues.map((issue, i) => (\n <li\n key={i}\n style={{\n marginBottom: \"0.25rem\",\n }}\n >\n <code>{issue.path || \"(root)\"}</code>: {issue.message}\n </li>\n ))}\n </ul>\n )}\n </div>\n );\n}\n\n// ============ Internal Components ============\n\ntype EffieJsonBlockProps = {\n label: string;\n data: unknown;\n};\n\nfunction EffieJsonBlock({ label, data }: EffieJsonBlockProps) {\n return (\n <div>\n <div>{label}:</div>\n <pre\n style={{\n margin: \"4px 0\",\n fontSize: \"0.75rem\",\n backgroundColor: \"#f0f0f0\",\n padding: \"4px 8px\",\n borderRadius: 4,\n overflow: \"auto\",\n }}\n >\n {JSON.stringify(data, null, 2)}\n </pre>\n </div>\n );\n}\n\n// ============ Warmup Hook ============\n\nexport type UseEffieWarmupResult = {\n state: EffieWarmupState;\n isReady: boolean;\n isWarming: boolean;\n hasError: boolean;\n};\n\n/**\n * Hook to connect to a warmup SSE stream and track progress.\n *\n * @param streamUrl - The full SSE stream URL returned by the FFS server (null if FFS not configured)\n */\nexport function useEffieWarmup(streamUrl: string | null): UseEffieWarmupResult {\n const [state, setState] = useState<EffieWarmupState>({\n status: \"idle\",\n total: 0,\n cached: 0,\n failed: 0,\n downloading: new Map(),\n });\n\n const cleanupRef = useRef<(() => void) | null>(null);\n\n useEffect(() => {\n if (!streamUrl) {\n return;\n }\n\n setState({\n status: \"connecting\",\n total: 0,\n cached: 0,\n failed: 0,\n downloading: new Map(),\n startTime: Date.now(),\n });\n\n // streamUrl is now a full URL returned by the FFS server\n cleanupRef.current = connectEffieWarmupStream(streamUrl, (event) => {\n setState((prev) => {\n switch (event.type) {\n case \"start\":\n return { ...prev, status: \"warming\", total: event.total };\n\n case \"progress\": {\n const newDownloading = new Map(prev.downloading);\n newDownloading.delete(hashUrlToId(event.data.url));\n return {\n ...prev,\n cached: event.data.cached,\n failed: event.data.failed,\n downloading: newDownloading,\n };\n }\n\n case \"downloading\": {\n const newDownloading = new Map(prev.downloading);\n newDownloading.set(hashUrlToId(event.data.url), {\n url: event.data.url,\n bytesReceived: event.data.bytesReceived,\n });\n return { ...prev, downloading: newDownloading };\n }\n\n case \"keepalive\":\n return {\n ...prev,\n cached: event.cached,\n failed: event.failed,\n };\n\n case \"summary\":\n return {\n ...prev,\n cached: event.cached,\n failed: event.failed,\n };\n\n case \"complete\":\n return { ...prev, status: \"ready\", endTime: Date.now() };\n\n case \"error\":\n return { ...prev, status: \"error\", error: event.message };\n\n default:\n return prev;\n }\n });\n });\n\n return () => {\n cleanupRef.current?.();\n cleanupRef.current = null;\n };\n }, [streamUrl]);\n\n return {\n state,\n isReady: state.status === \"ready\",\n isWarming: state.status === \"warming\" || state.status === \"connecting\",\n hasError: state.status === \"error\",\n };\n}\n","// ============ Types ============\n\n/** Progress event when a source is processed */\nexport type EffieWarmupProgressEvent = {\n url: string;\n status: \"hit\" | \"cached\" | \"error\";\n cached: number;\n failed: number;\n total: number;\n ms?: number;\n error?: string;\n};\n\n/** Downloading event during source fetch */\nexport type EffieWarmupDownloadingEvent = {\n url: string;\n status: \"started\" | \"downloading\";\n bytesReceived: number;\n};\n\n/** Union of all SSE event types */\nexport type EffieWarmupEvent =\n | { type: \"start\"; total: number }\n | { type: \"progress\"; data: EffieWarmupProgressEvent }\n | { type: \"downloading\"; data: EffieWarmupDownloadingEvent }\n | { type: \"keepalive\"; cached: number; failed: number; total: number }\n | { type: \"summary\"; cached: number; failed: number; total: number }\n | { type: \"complete\"; status: \"ready\" }\n | { type: \"error\"; message: string };\n\n/** Current warmup state */\nexport type EffieWarmupState = {\n status: \"idle\" | \"connecting\" | \"warming\" | \"ready\" | \"error\";\n total: number;\n cached: number;\n failed: number;\n downloading: Map<string, { url: string; bytesReceived: number }>;\n error?: string;\n startTime?: number;\n endTime?: number;\n};\n\n// ============ SSE Connection ============\n\n/**\n * Connects to an SSE warmup stream and calls onEvent for each event.\n * Returns a cleanup function to close the connection.\n *\n * @param streamUrl - Full URL to the warmup SSE endpoint\n * @param onEvent - Callback for each SSE event\n */\nexport function connectEffieWarmupStream(\n streamUrl: string,\n onEvent: (event: EffieWarmupEvent) => void,\n): () => void {\n const eventSource = new EventSource(streamUrl);\n\n eventSource.addEventListener(\"start\", (e) => {\n const data = JSON.parse((e as MessageEvent).data);\n onEvent({ type: \"start\", total: data.total });\n });\n\n eventSource.addEventListener(\"progress\", (e) => {\n const data = JSON.parse(\n (e as MessageEvent).data,\n ) as EffieWarmupProgressEvent;\n onEvent({ type: \"progress\", data });\n });\n\n eventSource.addEventListener(\"downloading\", (e) => {\n const data = JSON.parse(\n (e as MessageEvent).data,\n ) as EffieWarmupDownloadingEvent;\n onEvent({ type: \"downloading\", data });\n });\n\n eventSource.addEventListener(\"keepalive\", (e) => {\n const data = JSON.parse((e as MessageEvent).data);\n onEvent({\n type: \"keepalive\",\n cached: data.cached,\n failed: data.failed,\n total: data.total,\n });\n });\n\n eventSource.addEventListener(\"summary\", (e) => {\n const data = JSON.parse((e as MessageEvent).data);\n onEvent({\n type: \"summary\",\n cached: data.cached,\n failed: data.failed,\n total: data.total,\n });\n });\n\n eventSource.addEventListener(\"complete\", (e) => {\n const data = JSON.parse((e as MessageEvent).data);\n onEvent({ type: \"complete\", status: data.status });\n eventSource.close();\n });\n\n eventSource.addEventListener(\"error\", () => {\n onEvent({ type: \"error\", message: \"Connection lost\" });\n eventSource.close();\n });\n\n return () => eventSource.close();\n}\n","/**\n * Stable, sync ID for a URL for use as a Map key.\n * (We don't want to use the full URL as a Map key because it can be huge.)\n */\nexport function hashUrlToId(url: string): string {\n // FNV-1a 32-bit\n let hash = 0x811c9dc5;\n for (let i = 0; i < url.length; i++) {\n hash ^= url.charCodeAt(i);\n hash = Math.imul(hash, 0x01000193);\n }\n // Convert to unsigned + compact string\n return (hash >>> 0).toString(36);\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../src/react/index.tsx","../../src/warmup.ts","../../src/utils.ts"],"sourcesContent":["import { useEffect, useRef, useState } from \"react\";\nimport type {\n EffieBackground,\n EffieSegment,\n EffieLayer,\n EffieSources,\n EffieWebUrl,\n} from \"@effing/effie\";\nimport { AnniePlayer } from \"@effing/annie-player/react\";\nimport type { EffieSourceResolver, EffieValidationIssue } from \"../core\";\nimport { connectEffieWarmupStream, type EffieWarmupState } from \"../warmup\";\nimport { hashUrlToId } from \"../utils\";\n\n// ============ Cover Preview ============\n\nexport type EffieCoverPreviewProps = {\n /** Cover image URL */\n cover: EffieWebUrl;\n /** Resolution for preview */\n resolution: { width: number; height: number };\n /** Optional video URL to show instead of cover image (e.g., after rendering) */\n video?: string | null;\n /** Callback when video starts playing */\n onPlay?: () => void;\n /** Callback when video is fully buffered (entire video downloaded) */\n onFullyBuffered?: () => void;\n /** Class name for the img/video element */\n className?: string;\n /** Style for the img/video element */\n style?: React.CSSProperties;\n};\n\n/**\n * Displays the effie cover image, or a video if provided.\n * Simple component with built-in inline styles.\n */\nexport function EffieCoverPreview({\n cover,\n resolution,\n video,\n onPlay,\n onFullyBuffered,\n className,\n style,\n}: EffieCoverPreviewProps) {\n const fullyBufferedRef = useRef(false);\n const lastVideoRef = useRef<string | null>(null);\n\n // Reset the fully buffered flag when video URL changes\n if (video !== lastVideoRef.current) {\n lastVideoRef.current = video ?? null;\n fullyBufferedRef.current = false;\n }\n\n const handleProgress = (e: React.SyntheticEvent<HTMLVideoElement>) => {\n if (fullyBufferedRef.current || !onFullyBuffered) return;\n\n const vid = e.currentTarget;\n if (vid.buffered.length > 0 && vid.duration > 0) {\n const bufferedEnd = vid.buffered.end(vid.buffered.length - 1);\n if (bufferedEnd >= vid.duration) {\n fullyBufferedRef.current = true;\n onFullyBuffered();\n }\n }\n };\n\n if (video) {\n return (\n <video\n src={video}\n poster={cover}\n crossOrigin=\"anonymous\"\n className={className}\n style={{ ...style, height: resolution.height }}\n controls\n autoPlay\n onPlay={onPlay}\n onProgress={handleProgress}\n />\n );\n }\n\n return (\n <img\n src={cover}\n alt=\"Cover\"\n className={className}\n style={{ ...style, height: resolution.height }}\n />\n );\n}\n\n// ============ Background Preview - Compound Components ============\n\ntype EffieBackgroundPreviewRootProps = {\n className?: string;\n style?: React.CSSProperties;\n children: React.ReactNode;\n};\n\nfunction EffieBackgroundPreviewRoot({\n className,\n style,\n children,\n}: EffieBackgroundPreviewRootProps) {\n return (\n <div className={className} style={style}>\n {children}\n </div>\n );\n}\n\ntype EffieBackgroundPreviewMediaProps = {\n /** Background configuration from effie JSON */\n background: EffieBackground<EffieSources>;\n /** Function to resolve source references */\n resolveSource: EffieSourceResolver;\n /** Class name for the media element */\n className?: string;\n /** Style for the media element */\n style?: React.CSSProperties;\n};\n\nfunction EffieBackgroundPreviewMedia({\n background,\n resolveSource,\n className,\n style,\n}: EffieBackgroundPreviewMediaProps) {\n if (background.type === \"color\") {\n return (\n <div\n className={className}\n style={{ ...style, backgroundColor: background.color }}\n />\n );\n }\n\n if (background.type === \"image\") {\n return (\n <img\n src={resolveSource(background.source)}\n alt=\"Background\"\n className={className}\n style={{ objectFit: \"cover\", ...style }}\n />\n );\n }\n\n if (background.type === \"video\") {\n return (\n <video\n src={resolveSource(background.source)}\n className={className}\n style={{ objectFit: \"cover\", ...style }}\n autoPlay\n loop\n muted\n />\n );\n }\n\n return null;\n}\n\ntype EffieBackgroundPreviewInfoProps = {\n /** Background configuration from effie JSON */\n background: EffieBackground<EffieSources>;\n /** Class name for the info section */\n className?: string;\n /** Style for the info section */\n style?: React.CSSProperties;\n};\n\nfunction EffieBackgroundPreviewInfo({\n background,\n className,\n style,\n}: EffieBackgroundPreviewInfoProps) {\n return (\n <div className={className} style={style}>\n <strong>Type:</strong> {background.type}\n {background.type === \"color\" && (\n <div>\n <strong>Color:</strong> {background.color}\n </div>\n )}\n </div>\n );\n}\n\n// Simple pre-composed Background Preview\n\nexport type EffieBackgroundPreviewProps = {\n /** Background configuration from effie JSON */\n background: EffieBackground<EffieSources>;\n /** Function to resolve source references */\n resolveSource: EffieSourceResolver;\n /** Resolution for preview */\n resolution: { width: number; height: number };\n /** Class name for the container */\n className?: string;\n /** Style for the container */\n style?: React.CSSProperties;\n};\n\nfunction EffieBackgroundPreviewSimple({\n background,\n resolveSource,\n resolution,\n className,\n style,\n}: EffieBackgroundPreviewProps) {\n return (\n <EffieBackgroundPreviewRoot\n className={className}\n style={{\n display: \"flex\",\n gap: \"1rem\",\n ...style,\n }}\n >\n <EffieBackgroundPreviewMedia\n background={background}\n resolveSource={resolveSource}\n style={{\n border: \"1px solid #ddd\",\n width: resolution.width,\n height: resolution.height,\n }}\n />\n <EffieBackgroundPreviewInfo\n background={background}\n style={{\n fontSize: \"0.85rem\",\n color: \"#666\",\n }}\n />\n </EffieBackgroundPreviewRoot>\n );\n}\n\n/**\n * Displays a preview of the effie background (color, image, or video).\n *\n * Usage:\n * - Simple: `<EffieBackgroundPreview background={...} resolveSource={...} />`\n * - Compound: `<EffieBackgroundPreview.Root>...</EffieBackgroundPreview.Root>`\n */\nexport const EffieBackgroundPreview = Object.assign(\n EffieBackgroundPreviewSimple,\n {\n Root: EffieBackgroundPreviewRoot,\n Media: EffieBackgroundPreviewMedia,\n Info: EffieBackgroundPreviewInfo,\n },\n);\n\n// ============ Layer Preview - Compound Components ============\n\ntype EffieLayerPreviewRootProps = {\n className?: string;\n style?: React.CSSProperties;\n children: React.ReactNode;\n};\n\nfunction EffieLayerPreviewRoot({\n className,\n style,\n children,\n}: EffieLayerPreviewRootProps) {\n return (\n <div className={className} style={style}>\n {children}\n </div>\n );\n}\n\ntype EffieLayerPreviewMediaProps = {\n /** Layer configuration from effie JSON */\n layer: EffieLayer<EffieSources>;\n /** Layer index (0-based) for alt text */\n index: number;\n /** Function to resolve source references */\n resolveSource: EffieSourceResolver;\n /** Resolution for preview */\n resolution: { width: number; height: number };\n /** Class name for the media element */\n className?: string;\n /** Style for the media element */\n style?: React.CSSProperties;\n};\n\nfunction EffieLayerPreviewMedia({\n layer,\n index,\n resolveSource,\n resolution,\n className,\n style,\n}: EffieLayerPreviewMediaProps) {\n if (layer.type === \"animation\") {\n return (\n <AnniePlayer\n src={resolveSource(layer.source)}\n height={resolution.height}\n defaultWidth={resolution.width}\n autoLoad={false}\n autoPlay={true}\n className={className}\n style={style}\n />\n );\n }\n\n return (\n <img\n src={resolveSource(layer.source)}\n alt={`Layer ${index + 1}`}\n className={className}\n style={{ ...style, height: resolution.height }}\n />\n );\n}\n\ntype EffieLayerPreviewInfoProps = {\n /** Layer configuration from effie JSON */\n layer: EffieLayer<EffieSources>;\n /** Layer index (0-based) */\n index: number;\n /** Class name for the info section */\n className?: string;\n /** Style for the info section */\n style?: React.CSSProperties;\n};\n\nfunction EffieLayerPreviewInfo({\n layer,\n index,\n className,\n style,\n}: EffieLayerPreviewInfoProps) {\n return (\n <div className={className} style={style}>\n <strong>Layer {index + 1}</strong> ({layer.type})\n {layer.delay !== undefined && layer.delay > 0 && (\n <div>Delay: {layer.delay}s</div>\n )}\n {layer.from !== undefined && <div>From: {layer.from}s</div>}\n {layer.until !== undefined && <div>Until: {layer.until}s</div>}\n {layer.effects && layer.effects.length > 0 && (\n <EffieJsonBlock label=\"Effects\" data={layer.effects} />\n )}\n {layer.motion && <EffieJsonBlock label=\"Motion\" data={layer.motion} />}\n </div>\n );\n}\n\n// Simple pre-composed Layer Preview\n\nexport type EffieLayerPreviewProps = {\n /** Layer configuration from effie JSON */\n layer: EffieLayer<EffieSources>;\n /** Layer index (0-based) */\n index: number;\n /** Function to resolve source references */\n resolveSource: EffieSourceResolver;\n /** Resolution for preview */\n resolution: { width: number; height: number };\n /** How to stack info relative to media: \"horizontal\" (default, info beside) or \"vertical\" (info below) */\n stacking?: \"vertical\" | \"horizontal\";\n /** Class name for the container */\n className?: string;\n /** Style for the container */\n style?: React.CSSProperties;\n};\n\nfunction EffieLayerPreviewSimple({\n layer,\n index,\n resolveSource,\n resolution,\n stacking = \"horizontal\",\n className,\n style,\n}: EffieLayerPreviewProps) {\n return (\n <EffieLayerPreviewRoot\n className={className}\n style={{\n display: \"flex\",\n flexDirection: stacking === \"vertical\" ? \"column\" : \"row\",\n gap: \"1rem\",\n ...style,\n }}\n >\n <EffieLayerPreviewMedia\n layer={layer}\n index={index}\n resolveSource={resolveSource}\n resolution={resolution}\n style={{\n border: \"1px solid #ddd\",\n }}\n />\n <EffieLayerPreviewInfo\n layer={layer}\n index={index}\n style={{\n fontSize: \"0.85rem\",\n color: \"#666\",\n }}\n />\n </EffieLayerPreviewRoot>\n );\n}\n\n/**\n * Displays a preview of a single layer, including its metadata.\n * Uses AnniePlayer for animation layers.\n *\n * Usage:\n * - Simple: `<EffieLayerPreview layer={...} index={...} resolveSource={...} />`\n * - Compound: `<EffieLayerPreview.Root>...</EffieLayerPreview.Root>`\n */\nexport const EffieLayerPreview = Object.assign(EffieLayerPreviewSimple, {\n Root: EffieLayerPreviewRoot,\n Media: EffieLayerPreviewMedia,\n Info: EffieLayerPreviewInfo,\n});\n\n// ============ Segment Preview - Compound Components ============\n\ntype EffieSegmentPreviewRootProps = {\n className?: string;\n style?: React.CSSProperties;\n children: React.ReactNode;\n};\n\nfunction EffieSegmentPreviewRoot({\n className,\n style,\n children,\n}: EffieSegmentPreviewRootProps) {\n return (\n <div className={className} style={style}>\n {children}\n </div>\n );\n}\n\ntype EffieSegmentPreviewHeaderProps = {\n className?: string;\n style?: React.CSSProperties;\n children: React.ReactNode;\n};\n\nfunction EffieSegmentPreviewHeader({\n className,\n style,\n children,\n}: EffieSegmentPreviewHeaderProps) {\n return (\n <div className={className} style={style}>\n {children}\n </div>\n );\n}\n\ntype EffieSegmentPreviewLayersProps = {\n className?: string;\n style?: React.CSSProperties;\n children: React.ReactNode;\n};\n\nfunction EffieSegmentPreviewLayers({\n className,\n style,\n children,\n}: EffieSegmentPreviewLayersProps) {\n return (\n <div className={className} style={style}>\n {children}\n </div>\n );\n}\n\n// Simple pre-composed Segment Preview\n\nexport type EffieSegmentPreviewProps = {\n /** Segment configuration from effie JSON */\n segment: EffieSegment<EffieSources>;\n /** Segment index (0-based) */\n index: number;\n /** Function to resolve source references */\n resolveSource: EffieSourceResolver;\n /** Resolution for preview */\n resolution: { width: number; height: number };\n /** How to stack layers: \"vertical\" (default) or \"horizontal\" */\n stacking?: \"vertical\" | \"horizontal\";\n /** Class name for the container */\n className?: string;\n /** Style for the container */\n style?: React.CSSProperties;\n};\n\nfunction EffieSegmentPreviewSimple({\n segment,\n index,\n resolveSource,\n resolution,\n stacking = \"vertical\",\n className,\n style,\n}: EffieSegmentPreviewProps) {\n const layerStacking = stacking === \"horizontal\" ? \"vertical\" : \"horizontal\";\n\n return (\n <EffieSegmentPreviewRoot\n className={className}\n style={{\n padding: \"1rem\",\n border: \"1px solid #ddd\",\n borderRadius: 8,\n backgroundColor: \"#fafafa\",\n ...style,\n }}\n >\n <EffieSegmentPreviewHeader\n style={{\n fontWeight: 600,\n marginBottom: \"1rem\",\n }}\n >\n <strong>Segment {index + 1}</strong>\n {\" — \"}\n {segment.duration}s\n {segment.transition && (\n <span>\n {\" \"}\n (transition: {segment.transition.type},{\" \"}\n {segment.transition.duration}\n s)\n </span>\n )}\n </EffieSegmentPreviewHeader>\n\n <EffieSegmentPreviewLayers\n style={{\n display: \"flex\",\n flexDirection: stacking === \"horizontal\" ? \"row\" : \"column\",\n flexWrap: stacking === \"horizontal\" ? \"wrap\" : undefined,\n gap: \"1rem\",\n }}\n >\n {segment.layers.map((layer, j) => (\n <EffieLayerPreviewSimple\n key={j}\n layer={layer}\n index={j}\n resolveSource={resolveSource}\n resolution={resolution}\n stacking={layerStacking}\n />\n ))}\n </EffieSegmentPreviewLayers>\n </EffieSegmentPreviewRoot>\n );\n}\n\n/**\n * Displays a preview of a segment with all its layers.\n *\n * Usage:\n * - Simple: `<EffieSegmentPreview segment={...} index={...} resolveSource={...} />`\n * - Compound: `<EffieSegmentPreview.Root>...</EffieSegmentPreview.Root>`\n */\nexport const EffieSegmentPreview = Object.assign(EffieSegmentPreviewSimple, {\n Root: EffieSegmentPreviewRoot,\n Header: EffieSegmentPreviewHeader,\n Layers: EffieSegmentPreviewLayers,\n});\n\n// ============ Validation Errors ============\n\nexport type EffieValidationErrorsProps = {\n /** Error message (e.g., \"Invalid effie data\") */\n error: string;\n /** List of validation issues */\n issues?: EffieValidationIssue[];\n /** Class name for the container */\n className?: string;\n /** Style for the container */\n style?: React.CSSProperties;\n};\n\n/**\n * Displays validation errors with detailed issue breakdown.\n * Simple component with built-in inline styles.\n */\nexport function EffieValidationErrors({\n error,\n issues,\n className,\n style,\n}: EffieValidationErrorsProps) {\n return (\n <div\n className={className}\n style={{\n padding: \"1rem\",\n backgroundColor: \"#fff5f5\",\n border: \"1px solid #ffcccc\",\n borderRadius: 8,\n ...style,\n }}\n >\n <div\n style={{\n color: \"red\",\n fontWeight: 600,\n }}\n >\n Error: {error}\n </div>\n {issues && issues.length > 0 && (\n <ul\n style={{\n margin: \"0.5rem 0\",\n paddingLeft: \"1.5rem\",\n }}\n >\n {issues.map((issue, i) => (\n <li\n key={i}\n style={{\n marginBottom: \"0.25rem\",\n }}\n >\n <code>{issue.path || \"(root)\"}</code>: {issue.message}\n </li>\n ))}\n </ul>\n )}\n </div>\n );\n}\n\n// ============ Internal Components ============\n\ntype EffieJsonBlockProps = {\n label: string;\n data: unknown;\n};\n\nfunction EffieJsonBlock({ label, data }: EffieJsonBlockProps) {\n return (\n <div>\n <div>{label}:</div>\n <pre\n style={{\n margin: \"4px 0\",\n fontSize: \"0.75rem\",\n backgroundColor: \"#f0f0f0\",\n padding: \"4px 8px\",\n borderRadius: 4,\n overflow: \"auto\",\n }}\n >\n {JSON.stringify(data, null, 2)}\n </pre>\n </div>\n );\n}\n\n// ============ Warmup Hook ============\n\nexport type UseEffieWarmupResult = {\n state: EffieWarmupState;\n isReady: boolean;\n isWarming: boolean;\n hasError: boolean;\n};\n\n/**\n * Hook to connect to a warmup SSE stream and track progress.\n *\n * @param streamUrl - The full SSE stream URL returned by the FFS server (null if FFS not configured)\n */\nexport function useEffieWarmup(streamUrl: string | null): UseEffieWarmupResult {\n const [state, setState] = useState<EffieWarmupState>({\n status: \"idle\",\n total: 0,\n cached: 0,\n failed: 0,\n skipped: 0,\n downloading: new Map(),\n });\n\n const cleanupRef = useRef<(() => void) | null>(null);\n\n useEffect(() => {\n if (!streamUrl) {\n return;\n }\n\n setState({\n status: \"connecting\",\n total: 0,\n cached: 0,\n failed: 0,\n skipped: 0,\n downloading: new Map(),\n startTime: Date.now(),\n });\n\n // streamUrl is now a full URL returned by the FFS server\n cleanupRef.current = connectEffieWarmupStream(streamUrl, (event) => {\n setState((prev) => {\n switch (event.type) {\n case \"start\":\n return { ...prev, status: \"warming\", total: event.data.total };\n\n case \"progress\": {\n const newDownloading = new Map(prev.downloading);\n newDownloading.delete(hashUrlToId(event.data.url));\n return {\n ...prev,\n cached: event.data.cached,\n failed: event.data.failed,\n skipped: event.data.skipped,\n downloading: newDownloading,\n };\n }\n\n case \"downloading\": {\n const newDownloading = new Map(prev.downloading);\n newDownloading.set(hashUrlToId(event.data.url), {\n url: event.data.url,\n bytesReceived: event.data.bytesReceived,\n });\n return { ...prev, downloading: newDownloading };\n }\n\n case \"keepalive\":\n case \"summary\":\n return {\n ...prev,\n cached: event.data.cached,\n failed: event.data.failed,\n skipped: event.data.skipped,\n };\n\n case \"complete\":\n return { ...prev, status: \"ready\", endTime: Date.now() };\n\n case \"error\":\n return { ...prev, status: \"error\", error: event.data.message };\n\n default:\n return prev;\n }\n });\n });\n\n return () => {\n cleanupRef.current?.();\n cleanupRef.current = null;\n };\n }, [streamUrl]);\n\n return {\n state,\n isReady: state.status === \"ready\",\n isWarming: state.status === \"warming\" || state.status === \"connecting\",\n hasError: state.status === \"error\",\n };\n}\n","import type { WarmupEventMap } from \"@effing/ffs/sse\";\n\n// ============ Types ============\n\n/**\n * Union of all warmup SSE event types, derived from the server-side\n * `WarmupEventMap` so the client stays in sync automatically.\n * Each variant is `{ type: K; data: WarmupEventMap[K] }`.\n */\nexport type EffieWarmupEvent = {\n [K in keyof WarmupEventMap & string]: { type: K; data: WarmupEventMap[K] };\n}[keyof WarmupEventMap & string];\n\n/** Current warmup state */\nexport type EffieWarmupState = {\n status: \"idle\" | \"connecting\" | \"warming\" | \"ready\" | \"error\";\n total: number;\n cached: number;\n failed: number;\n skipped: number;\n downloading: Map<string, { url: string; bytesReceived: number }>;\n error?: string;\n startTime?: number;\n endTime?: number;\n};\n\n// ============ SSE Connection ============\n\n/**\n * Connects to an SSE warmup stream and calls onEvent for each event.\n * Returns a cleanup function to close the connection.\n *\n * @param streamUrl - Full URL to the warmup SSE endpoint\n * @param onEvent - Callback for each SSE event\n */\nexport function connectEffieWarmupStream(\n streamUrl: string,\n onEvent: (event: EffieWarmupEvent) => void,\n): () => void {\n const eventSource = new EventSource(streamUrl);\n\n eventSource.addEventListener(\"start\", (e) => {\n onEvent({ type: \"start\", data: JSON.parse((e as MessageEvent).data) });\n });\n\n eventSource.addEventListener(\"progress\", (e) => {\n onEvent({ type: \"progress\", data: JSON.parse((e as MessageEvent).data) });\n });\n\n eventSource.addEventListener(\"downloading\", (e) => {\n onEvent({\n type: \"downloading\",\n data: JSON.parse((e as MessageEvent).data),\n });\n });\n\n eventSource.addEventListener(\"keepalive\", (e) => {\n onEvent({ type: \"keepalive\", data: JSON.parse((e as MessageEvent).data) });\n });\n\n eventSource.addEventListener(\"summary\", (e) => {\n onEvent({ type: \"summary\", data: JSON.parse((e as MessageEvent).data) });\n });\n\n eventSource.addEventListener(\"complete\", (e) => {\n onEvent({ type: \"complete\", data: JSON.parse((e as MessageEvent).data) });\n eventSource.close();\n });\n\n eventSource.addEventListener(\"error\", () => {\n onEvent({ type: \"error\", data: { message: \"Connection lost\" } });\n eventSource.close();\n });\n\n return () => eventSource.close();\n}\n","/**\n * Stable, sync ID for a URL for use as a Map key.\n * (We don't want to use the full URL as a Map key because it can be huge.)\n */\nexport function hashUrlToId(url: string): string {\n // FNV-1a 32-bit\n let hash = 0x811c9dc5;\n for (let i = 0; i < url.length; i++) {\n hash ^= url.charCodeAt(i);\n hash = Math.imul(hash, 0x01000193);\n }\n // Convert to unsigned + compact string\n return (hash >>> 0).toString(36);\n}\n"],"mappings":"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","names":[]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@effing/effie-preview",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.0",
|
|
4
4
|
"description": "Preview components for Effie video compositions",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -17,14 +17,15 @@
|
|
|
17
17
|
"dist"
|
|
18
18
|
],
|
|
19
19
|
"dependencies": {
|
|
20
|
-
"@effing/annie-player": "0.
|
|
21
|
-
"@effing/effie": "0.
|
|
20
|
+
"@effing/annie-player": "0.8.0",
|
|
21
|
+
"@effing/effie": "0.8.0"
|
|
22
22
|
},
|
|
23
23
|
"devDependencies": {
|
|
24
24
|
"@types/react": "^19.0.0",
|
|
25
25
|
"tsup": "^8.0.0",
|
|
26
26
|
"typescript": "^5.9.3",
|
|
27
|
-
"vitest": "^3.2.4"
|
|
27
|
+
"vitest": "^3.2.4",
|
|
28
|
+
"@effing/ffs": "0.8.0"
|
|
28
29
|
},
|
|
29
30
|
"peerDependencies": {
|
|
30
31
|
"react": "^18.0.0 || ^19.0.0"
|