@gxpl/sdk 0.0.36-0 → 0.0.37

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.
Files changed (30) hide show
  1. package/lib/sdk/schemas/project/Project.schema.d.ts +18 -3
  2. package/lib/sdk/schemas/project/Project.schema.js +4 -1
  3. package/lib/sdk/types/project/Project.d.ts +4 -1
  4. package/lib/sdk-nextjs/assets/AssetsCacheProvider.d.ts +13 -0
  5. package/lib/sdk-nextjs/{components/Section/SectionVideoCacheContext.js → assets/AssetsCacheProvider.js} +14 -13
  6. package/lib/sdk-nextjs/assets/getCacheAssetKey.d.ts +1 -0
  7. package/lib/sdk-nextjs/assets/getCacheAssetKey.js +6 -0
  8. package/lib/sdk-nextjs/assets/useCacheImage.d.ts +2 -0
  9. package/lib/sdk-nextjs/assets/useCacheImage.js +49 -0
  10. package/lib/sdk-nextjs/assets/useCacheVideo.d.ts +6 -0
  11. package/lib/sdk-nextjs/assets/useCacheVideo.js +36 -0
  12. package/lib/sdk-nextjs/components/Page.js +10 -10
  13. package/lib/sdk-nextjs/components/Preview/ChevronIcon.d.ts +3 -0
  14. package/lib/sdk-nextjs/components/Preview/ChevronIcon.js +8 -0
  15. package/lib/sdk-nextjs/components/Preview/IframePreviewWindowContext.d.ts +2 -0
  16. package/lib/sdk-nextjs/components/Preview/IframePreviewWindowContext.js +8 -0
  17. package/lib/sdk-nextjs/components/Preview/Preview.d.ts +8 -0
  18. package/lib/sdk-nextjs/components/Preview/Preview.js +186 -0
  19. package/lib/sdk-nextjs/components/Preview/PreviewListener.d.ts +1 -0
  20. package/lib/sdk-nextjs/components/Preview/PreviewListener.js +48 -0
  21. package/lib/sdk-nextjs/components/Preview/PreviewWrapper.d.ts +8 -0
  22. package/lib/sdk-nextjs/components/Preview/PreviewWrapper.js +29 -0
  23. package/lib/sdk-nextjs/components/Section/SectionImage.js +21 -14
  24. package/lib/sdk-nextjs/components/Section/SectionVideo.js +63 -60
  25. package/lib/sdk-nextjs/components/items/FileItem/ImageItem.js +8 -15
  26. package/lib/sdk-nextjs/components/items/FileItem/VideoItem.js +98 -43
  27. package/lib/sdk-nextjs/utils/usePrelaodAssets.d.ts +4 -1
  28. package/lib/sdk-nextjs/utils/usePrelaodAssets.js +5 -5
  29. package/package.json +1 -1
  30. package/lib/sdk-nextjs/components/Section/SectionVideoCacheContext.d.ts +0 -10
@@ -401,7 +401,16 @@ export declare const ProjectSchema: z.ZodObject<{
401
401
  google: string;
402
402
  adobe: string;
403
403
  }>;
404
- scenesAssets: z.ZodArray<z.ZodString, "many">;
404
+ scenesAssets: z.ZodArray<z.ZodObject<{
405
+ url: z.ZodString;
406
+ id: z.ZodString;
407
+ }, "strip", z.ZodTypeAny, {
408
+ url: string;
409
+ id: string;
410
+ }, {
411
+ url: string;
412
+ id: string;
413
+ }>, "many">;
405
414
  relations: z.ZodArray<z.ZodObject<{
406
415
  from: z.ZodString;
407
416
  to: z.ZodString;
@@ -1039,7 +1048,10 @@ export declare const ProjectSchema: z.ZodObject<{
1039
1048
  google: string;
1040
1049
  adobe: string;
1041
1050
  };
1042
- scenesAssets: string[];
1051
+ scenesAssets: {
1052
+ url: string;
1053
+ id: string;
1054
+ }[];
1043
1055
  relations: {
1044
1056
  type: "slide" | "fade";
1045
1057
  direction: "north" | "east" | "south" | "west";
@@ -1187,7 +1199,10 @@ export declare const ProjectSchema: z.ZodObject<{
1187
1199
  google: string;
1188
1200
  adobe: string;
1189
1201
  };
1190
- scenesAssets: string[];
1202
+ scenesAssets: {
1203
+ url: string;
1204
+ id: string;
1205
+ }[];
1191
1206
  relations: {
1192
1207
  type: "slide" | "fade";
1193
1208
  direction: "north" | "east" | "south" | "west";
@@ -59,7 +59,10 @@ exports.ProjectSchema = zod_1.z.object({
59
59
  }))
60
60
  }))
61
61
  }),
62
- scenesAssets: zod_1.z.array(zod_1.z.string()),
62
+ scenesAssets: zod_1.z.array(zod_1.z.object({
63
+ url: zod_1.z.string(),
64
+ id: zod_1.z.string()
65
+ })),
63
66
  relations: zod_1.z.array(zod_1.z.object({
64
67
  from: zod_1.z.string().min(1),
65
68
  to: zod_1.z.string().min(1),
@@ -16,7 +16,10 @@ export interface Project {
16
16
  pages: Page[];
17
17
  fonts: Fonts;
18
18
  relations: Relation[];
19
- scenesAssets: string[];
19
+ scenesAssets: {
20
+ url: string;
21
+ id: string;
22
+ }[];
20
23
  foreground: TFixedLayer;
21
24
  background: TFixedLayer;
22
25
  }
@@ -0,0 +1,13 @@
1
+ import { FC, PropsWithChildren } from 'react';
2
+ export declare const AssetsCacheContext: import("react").Context<{
3
+ videoCache: Map<string, HTMLVideoElement>;
4
+ imageCache: Map<string, HTMLImageElement>;
5
+ }>;
6
+ interface Props {
7
+ assets: {
8
+ url: string;
9
+ id: string;
10
+ }[];
11
+ }
12
+ export declare const AssetsCacheProvider: FC<PropsWithChildren<Props>>;
13
+ export {};
@@ -1,29 +1,30 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.SectionVideoCacheProvider = exports.SectionVideoCacheContext = void 0;
3
+ exports.AssetsCacheProvider = exports.AssetsCacheContext = void 0;
4
4
  const jsx_runtime_1 = require("react/jsx-runtime");
5
5
  const react_1 = require("react");
6
- exports.SectionVideoCacheContext = (0, react_1.createContext)({ videoCache: new Map(), imageCache: new Map() });
7
- const SectionVideoCacheProvider = ({ children, assets }) => {
6
+ const getCacheAssetKey_1 = require("./getCacheAssetKey");
7
+ exports.AssetsCacheContext = (0, react_1.createContext)({ videoCache: new Map(), imageCache: new Map() });
8
+ const AssetsCacheProvider = ({ children, assets }) => {
8
9
  const [videoCache, setVideoCache] = (0, react_1.useState)(new Map());
9
10
  const [imageCache, setImageCache] = (0, react_1.useState)(new Map());
10
11
  (0, react_1.useEffect)(() => {
11
- assets.forEach(asset => {
12
- if (isVideoAsset(asset)) {
13
- const video = getSectionVideo(asset);
14
- setVideoCache(prev => prev.set(asset, video));
12
+ assets.forEach(({ url, id }) => {
13
+ if (isVideoAsset(url)) {
14
+ const video = getVideo(url);
15
+ setVideoCache(prev => prev.set((0, getCacheAssetKey_1.getCacheAssetKey)(url, id), video));
15
16
  }
16
- if (isImageAsset(asset)) {
17
+ if (isImageAsset(url)) {
17
18
  const img = new Image();
18
- img.src = asset;
19
- setImageCache(prev => prev.set(asset, img));
19
+ img.src = url;
20
+ setImageCache(prev => prev.set((0, getCacheAssetKey_1.getCacheAssetKey)(url, id), img));
20
21
  }
21
22
  });
22
23
  }, [assets]);
23
- return (0, jsx_runtime_1.jsx)(exports.SectionVideoCacheContext.Provider, { value: { videoCache, imageCache }, children: children });
24
+ return (0, jsx_runtime_1.jsx)(exports.AssetsCacheContext.Provider, { value: { videoCache, imageCache }, children: children });
24
25
  };
25
- exports.SectionVideoCacheProvider = SectionVideoCacheProvider;
26
- function getSectionVideo(url) {
26
+ exports.AssetsCacheProvider = AssetsCacheProvider;
27
+ function getVideo(url) {
27
28
  const video = document.createElement('video');
28
29
  video.src = url;
29
30
  video.preload = 'auto';
@@ -0,0 +1 @@
1
+ export declare function getCacheAssetKey(url: string, id: string): string;
@@ -0,0 +1,6 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.getCacheAssetKey = getCacheAssetKey;
4
+ function getCacheAssetKey(url, id) {
5
+ return `${url}-${id}`;
6
+ }
@@ -0,0 +1,2 @@
1
+ import { CSSProperties } from 'react';
2
+ export declare const useCacheImage: (key: string | null, renderImage: boolean, style: CSSProperties, container: HTMLElement | null, className?: string, onMouseEnter?: () => void, onClick?: () => void) => void;
@@ -0,0 +1,49 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.useCacheImage = void 0;
4
+ const react_1 = require("react");
5
+ const AssetsCacheProvider_1 = require("./AssetsCacheProvider");
6
+ const useCacheImage = (key, renderImage, style, container, className = '', onMouseEnter, onClick) => {
7
+ const { imageCache } = (0, react_1.useContext)(AssetsCacheProvider_1.AssetsCacheContext);
8
+ const [image, setImage] = (0, react_1.useState)(null);
9
+ (0, react_1.useEffect)(() => {
10
+ if (!container || !key)
11
+ return;
12
+ const image = imageCache.get(key);
13
+ if (!image)
14
+ return;
15
+ if (!renderImage && container.contains(image)) {
16
+ container.removeChild(image);
17
+ return;
18
+ }
19
+ if (!renderImage)
20
+ return;
21
+ image.className = className;
22
+ if (!container.contains(image)) {
23
+ container.appendChild(image);
24
+ }
25
+ setImage(image);
26
+ }, [container, imageCache, key, renderImage]);
27
+ (0, react_1.useEffect)(() => {
28
+ if (!image)
29
+ return;
30
+ if (onClick) {
31
+ image.addEventListener('click', onClick);
32
+ }
33
+ if (onMouseEnter) {
34
+ image.addEventListener('mouseenter', onMouseEnter);
35
+ }
36
+ return () => {
37
+ if (onMouseEnter) {
38
+ image.removeEventListener('mouseenter', onMouseEnter);
39
+ }
40
+ if (onClick) {
41
+ image.removeEventListener('click', onClick);
42
+ }
43
+ };
44
+ }, [onMouseEnter, onClick, image]);
45
+ if (image) {
46
+ Object.assign(image.style, style);
47
+ }
48
+ };
49
+ exports.useCacheImage = useCacheImage;
@@ -0,0 +1,6 @@
1
+ import { CSSProperties } from 'react';
2
+ export declare const useCacheVideo: (key: string, container: HTMLElement | null, isVideoVisible: boolean, params: {
3
+ play: "on-hover" | "on-click" | "auto";
4
+ muted: boolean;
5
+ controls: boolean;
6
+ }, style: CSSProperties, video: HTMLVideoElement | null, setVideo: (video: HTMLVideoElement) => void, className?: string) => void;
@@ -0,0 +1,36 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.useCacheVideo = void 0;
4
+ const react_1 = require("react");
5
+ const AssetsCacheProvider_1 = require("./AssetsCacheProvider");
6
+ const useCacheVideo = (key, container, isVideoVisible, params, style, video, setVideo, className = '') => {
7
+ const { videoCache } = (0, react_1.useContext)(AssetsCacheProvider_1.AssetsCacheContext);
8
+ (0, react_1.useEffect)(() => {
9
+ if (!container || !isVideoVisible)
10
+ return;
11
+ const { play, muted, controls } = params;
12
+ const video = videoCache.get(key);
13
+ if (!video)
14
+ return;
15
+ video.controls = controls;
16
+ video.muted = play === "auto" || muted;
17
+ video.autoplay = play === "auto";
18
+ video.playsInline = true;
19
+ video.loop = true;
20
+ video.className = className;
21
+ if (!container.contains(video)) {
22
+ container.appendChild(video);
23
+ }
24
+ setVideo(video);
25
+ if (play === "auto") {
26
+ video.play().catch(() => { });
27
+ }
28
+ return () => {
29
+ video.pause();
30
+ };
31
+ }, [key, params, container, videoCache, className, isVideoVisible]);
32
+ if (video) {
33
+ Object.assign(video.style, style);
34
+ }
35
+ };
36
+ exports.useCacheVideo = useCacheVideo;
@@ -10,8 +10,9 @@ const Head_1 = require("./Head");
10
10
  const TransitionMachineContext_1 = require("../provider/TransitionMachineContext");
11
11
  const Scenes_1 = require("./Scenes/Scenes");
12
12
  const FixedLayer_1 = require("./fixedLayers/FixedLayer");
13
- const usePrelaodAssets_1 = require("../utils/usePrelaodAssets");
14
- const SectionVideoCacheContext_1 = require("./Section/SectionVideoCacheContext");
13
+ const PreviewWrapper_1 = require("./Preview/PreviewWrapper");
14
+ const PreviewListener_1 = require("./Preview/PreviewListener");
15
+ const AssetsCacheProvider_1 = require("../assets/AssetsCacheProvider");
15
16
  const Page = ({ project, articlesData }) => {
16
17
  var _a, _b;
17
18
  const afterBodyOpen = (0, html_react_parser_1.default)(project.html.afterBodyOpen);
@@ -19,13 +20,12 @@ const Page = ({ project, articlesData }) => {
19
20
  const startScene = (_b = (_a = project.pages.find(page => page.isStartScene)) === null || _a === void 0 ? void 0 : _a.articleId) !== null && _b !== void 0 ? _b : Object.keys(articlesData)[0];
20
21
  const scenes = Object.values(articlesData).map(({ article }) => ({ id: article.id }));
21
22
  const { relations, scenesAssets } = project;
22
- (0, usePrelaodAssets_1.usePreloadAssets)(scenesAssets);
23
- return ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)(Head_1.CNTRLHead, { project: project }), afterBodyOpen, (0, jsx_runtime_1.jsx)(TransitionMachineContext_1.TransitionMachineContext.Provider, { options: {
24
- input: {
25
- startScene,
26
- relations,
27
- scenes,
28
- }
29
- }, children: (0, jsx_runtime_1.jsxs)(SectionVideoCacheContext_1.SectionVideoCacheProvider, { assets: scenesAssets, children: [project.foreground && !project.foreground.hidden && (0, jsx_runtime_1.jsx)(FixedLayer_1.FixedLayer, { layer: project.foreground, type: "foreground" }), (0, jsx_runtime_1.jsx)(Scenes_1.Scenes, { articlesData: articlesData }), project.background && !project.background.hidden && (0, jsx_runtime_1.jsx)(FixedLayer_1.FixedLayer, { layer: project.background, type: "background" })] }) }), beforeBodyClose] }));
23
+ return ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)(Head_1.CNTRLHead, { project: project }), afterBodyOpen, (0, jsx_runtime_1.jsx)(PreviewWrapper_1.PreviewWrapper, { relations: relations, startScene: startScene, children: (0, jsx_runtime_1.jsxs)(TransitionMachineContext_1.TransitionMachineContext.Provider, { options: {
24
+ input: {
25
+ startScene,
26
+ relations,
27
+ scenes,
28
+ }
29
+ }, children: [(0, jsx_runtime_1.jsx)(PreviewListener_1.PreviewListener, {}), (0, jsx_runtime_1.jsxs)(AssetsCacheProvider_1.AssetsCacheProvider, { assets: scenesAssets, children: [project.foreground && !project.foreground.hidden && (0, jsx_runtime_1.jsx)(FixedLayer_1.FixedLayer, { layer: project.foreground, type: "foreground" }), (0, jsx_runtime_1.jsx)(Scenes_1.Scenes, { articlesData: articlesData }), project.background && !project.background.hidden && (0, jsx_runtime_1.jsx)(FixedLayer_1.FixedLayer, { layer: project.background, type: "background" })] })] }) }), beforeBodyClose] }));
30
30
  };
31
31
  exports.Page = Page;
@@ -0,0 +1,3 @@
1
+ export declare const ChevronIcon: ({ className }: {
2
+ className?: string;
3
+ }) => JSX.Element;
@@ -0,0 +1,8 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ChevronIcon = void 0;
4
+ const jsx_runtime_1 = require("react/jsx-runtime");
5
+ const ChevronIcon = ({ className }) => {
6
+ return ((0, jsx_runtime_1.jsxs)("svg", { width: "44px", height: "44px", viewBox: "0 0 44 44", version: "1.1", xmlns: "http://www.w3.org/2000/svg", xmlnsXlink: "http://www.w3.org/1999/xlink", children: [(0, jsx_runtime_1.jsx)("defs", { children: (0, jsx_runtime_1.jsx)("circle", { id: "path-1", cx: "21.5", cy: "21.5", r: "21" }) }), (0, jsx_runtime_1.jsx)("g", { id: "Page-1", stroke: "none", strokeWidth: "1", fill: "none", fillRule: "evenodd", children: (0, jsx_runtime_1.jsxs)("g", { id: "arrow", children: [(0, jsx_runtime_1.jsxs)("g", { id: "Oval", opacity: "0.2", children: [(0, jsx_runtime_1.jsx)("use", { fill: "#000000", xlinkHref: "#path-1" }), (0, jsx_runtime_1.jsx)("use", { fill: "#64686A", xlinkHref: "#path-1" })] }), (0, jsx_runtime_1.jsx)("polyline", { id: "Path", stroke: "#FFFFFF", strokeWidth: "2", points: "20 16.5 25 21.5 20 26.5" })] }) })] }));
7
+ };
8
+ exports.ChevronIcon = ChevronIcon;
@@ -0,0 +1,2 @@
1
+ export declare const IframePreviewWindowContext: import("react").Context<Window | null>;
2
+ export declare const useIframePreviewWindow: () => Window | null;
@@ -0,0 +1,8 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.useIframePreviewWindow = exports.IframePreviewWindowContext = void 0;
4
+ const react_1 = require("react");
5
+ const react_2 = require("react");
6
+ exports.IframePreviewWindowContext = (0, react_2.createContext)(null);
7
+ const useIframePreviewWindow = () => (0, react_1.useContext)(exports.IframePreviewWindowContext);
8
+ exports.useIframePreviewWindow = useIframePreviewWindow;
@@ -0,0 +1,8 @@
1
+ import { FC, PropsWithChildren } from 'react';
2
+ import { Relation } from '../../../sdk/types/project/Relation';
3
+ interface Props {
4
+ relations: Relation[];
5
+ startScene: string;
6
+ }
7
+ export declare const Preview: FC<PropsWithChildren<Props>>;
8
+ export {};
@@ -0,0 +1,186 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.Preview = void 0;
7
+ const jsx_runtime_1 = require("react/jsx-runtime");
8
+ const react_1 = require("react");
9
+ const client_1 = require("react-dom/client");
10
+ const findRelation_1 = require("../../../sdk/transitions/utils/findRelation");
11
+ const getAvailableTransitions_1 = require("../../../sdk/transitions/utils/getAvailableTransitions");
12
+ const style_1 = __importDefault(require("styled-jsx/style"));
13
+ const ChevronIcon_1 = require("./ChevronIcon");
14
+ const IframePreviewWindowContext_1 = require("./IframePreviewWindowContext");
15
+ const Preview = ({ children, relations, startScene }) => {
16
+ const id = (0, react_1.useId)();
17
+ const [iframeRef, setIframeRef] = (0, react_1.useState)(null);
18
+ const [isTransitioning, setIsTransitioning] = (0, react_1.useState)(false);
19
+ const [activeScene, setActiveScene] = (0, react_1.useState)(startScene);
20
+ const [activeSides, setActiveSides] = (0, react_1.useState)((0, getAvailableTransitions_1.getAvailableTransitions)(startScene, relations));
21
+ const handleSwipeToScene = (direction) => {
22
+ if (!iframeRef || !iframeRef.contentWindow || !iframeRef.contentDocument)
23
+ return;
24
+ setIsTransitioning(true);
25
+ const transition = (0, findRelation_1.findRelation)(relations, activeScene, direction);
26
+ const targetWindow = iframeRef.contentWindow;
27
+ const message = {
28
+ type: "TRANSITION_TRIGGER",
29
+ direction,
30
+ to: transition.to,
31
+ transitionType: transition.type,
32
+ };
33
+ targetWindow.postMessage(message, "*");
34
+ };
35
+ (0, react_1.useEffect)(() => {
36
+ if (!iframeRef)
37
+ return;
38
+ const handleMessage = (e) => {
39
+ var _a;
40
+ if (((_a = e.data) === null || _a === void 0 ? void 0 : _a.type) === "ACTIVE_SCENE_CHANGE") {
41
+ const activeScene = e.data.activeScene;
42
+ if (activeScene) {
43
+ const availableTransitions = (0, getAvailableTransitions_1.getAvailableTransitions)(activeScene, relations);
44
+ setActiveSides(availableTransitions);
45
+ setActiveScene(activeScene);
46
+ setIsTransitioning(false);
47
+ }
48
+ }
49
+ };
50
+ window.addEventListener("message", handleMessage);
51
+ return () => window.removeEventListener("message", handleMessage);
52
+ }, [iframeRef, relations]);
53
+ (0, react_1.useEffect)(() => {
54
+ if (!iframeRef)
55
+ return;
56
+ const iframeDocument = iframeRef.contentDocument;
57
+ if (!iframeDocument)
58
+ return;
59
+ const iframeWindow = iframeRef.contentWindow;
60
+ if (!iframeWindow)
61
+ return;
62
+ const iframeHead = iframeDocument.head;
63
+ if (!iframeHead)
64
+ return;
65
+ const copyStyleNode = (node) => {
66
+ if (node instanceof HTMLStyleElement) {
67
+ const cloned = node.cloneNode(true);
68
+ iframeHead.appendChild(cloned);
69
+ }
70
+ else if (node instanceof HTMLLinkElement && node.rel === 'stylesheet') {
71
+ const cloned = node.cloneNode(true);
72
+ iframeHead.appendChild(cloned);
73
+ }
74
+ };
75
+ const copyAllStyles = () => {
76
+ const style = iframeDocument.createElement("style");
77
+ style.innerHTML = `
78
+ html, body {
79
+ height: 100%;
80
+ margin: 0 !important;
81
+ padding: 0 !important;
82
+ }
83
+ `;
84
+ iframeHead.appendChild(style);
85
+ const parentHead = document.head;
86
+ if (!parentHead)
87
+ return;
88
+ Array.from(parentHead.querySelectorAll('style, link[rel="stylesheet"]')).forEach((node) => {
89
+ const href = node instanceof HTMLLinkElement ? node.href : null;
90
+ const existing = href
91
+ ? iframeHead.querySelector(`link[href="${href}"]`)
92
+ : null;
93
+ if (!existing) {
94
+ copyStyleNode(node);
95
+ }
96
+ });
97
+ };
98
+ copyAllStyles();
99
+ const observer = new MutationObserver((mutations) => {
100
+ mutations.forEach((mutation) => {
101
+ mutation.addedNodes.forEach((node) => {
102
+ if ((node instanceof HTMLStyleElement) ||
103
+ (node instanceof HTMLLinkElement && node.rel === 'stylesheet')) {
104
+ copyStyleNode(node);
105
+ }
106
+ });
107
+ });
108
+ });
109
+ observer.observe(document.head, {
110
+ childList: true,
111
+ subtree: true,
112
+ });
113
+ const mountNode = iframeDocument.createElement('div');
114
+ mountNode.id = 'react-root';
115
+ iframeDocument.body.appendChild(mountNode);
116
+ const root = (0, client_1.createRoot)(mountNode);
117
+ root.render((0, jsx_runtime_1.jsx)(IframePreviewWindowContext_1.IframePreviewWindowContext.Provider, { value: iframeWindow, children: children }));
118
+ return () => {
119
+ observer.disconnect();
120
+ };
121
+ }, [iframeRef, children]);
122
+ return ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)("div", { className: "preview-wrapper", children: (0, jsx_runtime_1.jsxs)("div", { className: "preview-inner", children: [Object.entries(activeSides).map(([direction, isActive]) => (isActive ? ((0, jsx_runtime_1.jsx)("button", { onClick: () => handleSwipeToScene(direction), disabled: isTransitioning, className: `chevron-button chevron-button-${direction}`, children: (0, jsx_runtime_1.jsx)(ChevronIcon_1.ChevronIcon, { className: "chevron-icon" }) }, direction)) : null)), (0, jsx_runtime_1.jsx)("iframe", { className: "iframe-preview", ref: setIframeRef, style: {} })] }) }), (0, jsx_runtime_1.jsx)(style_1.default, { id: id, children: `
123
+ .preview-wrapper {
124
+ width: 100%;
125
+ height: 100%;
126
+ position: relative;
127
+ padding: 66px;
128
+ box-sizing: border-box;
129
+ display: flex;
130
+ align-items: center;
131
+ justify-content: center;
132
+ background-color: #000000;
133
+ }
134
+ .preview-inner {
135
+ width: 100%;
136
+ max-height: 844px;
137
+ height: 100%;
138
+ position: relative;
139
+ display: flex;
140
+ width: 390px;
141
+ align-items: center;
142
+ justify-content: center;
143
+ }
144
+ .iframe-preview {
145
+ width: 390px;
146
+ max-height: 844px;
147
+ height: 100%;
148
+ border: none;
149
+ z-index: 1000;
150
+ background-color: #FFFFFF;
151
+ }
152
+ .chevron-icon {
153
+ width: 44px;
154
+ height: 44px;
155
+ }
156
+ .chevron-button {
157
+ border: none;
158
+ background: none;
159
+ padding: 0;
160
+ margin: 0;
161
+ cursor: pointer;
162
+ position: absolute;
163
+ }
164
+ .chevron-button-north {
165
+ top: 0;
166
+ left: 50%;
167
+ transform: translate(-50%, -125%) rotate(-90deg);
168
+ }
169
+ .chevron-button-south {
170
+ bottom: 0;
171
+ left: 50%;
172
+ transform: translate(-50%, 125%) rotate(90deg);
173
+ }
174
+ .chevron-button-east {
175
+ right: 0;
176
+ top: 50%;
177
+ transform: translate(125%, -50%);
178
+ }
179
+ .chevron-button-west {
180
+ left: 0;
181
+ top: 50%;
182
+ transform: translate(-125%, -50%) rotate(180deg);
183
+ }
184
+ ` })] }));
185
+ };
186
+ exports.Preview = Preview;
@@ -0,0 +1 @@
1
+ export declare const PreviewListener: () => null;
@@ -0,0 +1,48 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.PreviewListener = void 0;
4
+ const react_1 = require("react");
5
+ const TransitionMachineContext_1 = require("../../provider/TransitionMachineContext");
6
+ const IframePreviewWindowContext_1 = require("./IframePreviewWindowContext");
7
+ const PreviewListener = () => {
8
+ const actorRef = TransitionMachineContext_1.TransitionMachineContext.useActorRef();
9
+ const { isActive } = TransitionMachineContext_1.TransitionMachineContext.useSelector((state) => ({
10
+ isActive: state.matches('active'),
11
+ }));
12
+ const iframePreviewWindow = (0, IframePreviewWindowContext_1.useIframePreviewWindow)();
13
+ (0, react_1.useEffect)(() => {
14
+ if (!iframePreviewWindow)
15
+ return;
16
+ const handleMessage = (e) => {
17
+ var _a;
18
+ if (((_a = e.data) === null || _a === void 0 ? void 0 : _a.type) === "TRANSITION_TRIGGER") {
19
+ const { direction, to, transitionType } = e.data;
20
+ actorRef.send({
21
+ type: 'TRANSITION_TRIGGER',
22
+ transition: transitionType,
23
+ to,
24
+ direction
25
+ });
26
+ }
27
+ };
28
+ iframePreviewWindow.addEventListener("message", handleMessage);
29
+ return () => {
30
+ if (iframePreviewWindow) {
31
+ iframePreviewWindow.removeEventListener("message", handleMessage);
32
+ }
33
+ };
34
+ }, [iframePreviewWindow]);
35
+ (0, react_1.useEffect)(() => {
36
+ if (isActive && actorRef && iframePreviewWindow) {
37
+ const { context } = actorRef.getSnapshot();
38
+ const { scenes } = context;
39
+ const [activeScene] = scenes;
40
+ iframePreviewWindow.parent.postMessage({
41
+ type: "ACTIVE_SCENE_CHANGE",
42
+ activeScene: activeScene.id,
43
+ }, "*");
44
+ }
45
+ }, [isActive, actorRef, iframePreviewWindow]);
46
+ return null;
47
+ };
48
+ exports.PreviewListener = PreviewListener;
@@ -0,0 +1,8 @@
1
+ import { FC, PropsWithChildren } from 'react';
2
+ import { Relation } from '../../../sdk/types/project/Relation';
3
+ interface Props {
4
+ relations: Relation[];
5
+ startScene: string;
6
+ }
7
+ export declare const PreviewWrapper: FC<PropsWithChildren<Props>>;
8
+ export {};
@@ -0,0 +1,29 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.PreviewWrapper = void 0;
4
+ const jsx_runtime_1 = require("react/jsx-runtime");
5
+ const react_1 = require("react");
6
+ const Preview_1 = require("./Preview");
7
+ const PreviewWrapper = ({ children, relations, startScene }) => {
8
+ const [isDesktop, setIsDesktop] = (0, react_1.useState)(false);
9
+ const handleResize = () => {
10
+ if (window.innerWidth < 768) {
11
+ setIsDesktop(false);
12
+ }
13
+ else {
14
+ setIsDesktop(true);
15
+ }
16
+ };
17
+ (0, react_1.useEffect)(() => {
18
+ handleResize();
19
+ window.addEventListener('resize', handleResize);
20
+ return () => {
21
+ window.removeEventListener('resize', handleResize);
22
+ };
23
+ }, []);
24
+ if (isDesktop) {
25
+ return (0, jsx_runtime_1.jsx)(Preview_1.Preview, { relations: relations, startScene: startScene, children: children });
26
+ }
27
+ return (0, jsx_runtime_1.jsx)(jsx_runtime_1.Fragment, { children: children });
28
+ };
29
+ exports.PreviewWrapper = PreviewWrapper;
@@ -2,23 +2,30 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.SectionImage = void 0;
4
4
  const jsx_runtime_1 = require("react/jsx-runtime");
5
+ const react_1 = require("react");
6
+ const useCacheImage_1 = require("../../assets/useCacheImage");
7
+ const getCacheAssetKey_1 = require("../../assets/getCacheAssetKey");
5
8
  const SectionImage = ({ media, sectionId }) => {
6
9
  const { url, size, position, offsetX } = media;
10
+ const key = (0, getCacheAssetKey_1.getCacheAssetKey)(url, sectionId);
11
+ const [container, setContainer] = (0, react_1.useState)(null);
7
12
  const isContainHeight = size === 'contain-height';
8
13
  const hasOffsetX = offsetX !== null;
9
- return ((0, jsx_runtime_1.jsx)(jsx_runtime_1.Fragment, { children: (0, jsx_runtime_1.jsx)("div", { className: `section-image-wrapper-${sectionId}`, style: {
10
- position: position === 'fixed' ? 'sticky' : 'relative',
11
- height: position === 'fixed' ? '100vh' : '100%',
12
- top: position === 'fixed' ? '100vh' : '0',
13
- width: '100%',
14
- overflow: 'hidden'
15
- }, children: (0, jsx_runtime_1.jsx)("img", { src: url, className: `image-background-${sectionId}`, style: {
16
- objectFit: isContainHeight ? 'unset' : (size !== null && size !== void 0 ? size : 'cover'),
17
- width: isContainHeight ? 'auto' : '100%',
18
- transform: isContainHeight ? 'translateX(-50%)' : 'none',
19
- position: 'relative',
20
- left: isContainHeight ? '50%' : (hasOffsetX ? `${offsetX * 100}vw` : '0'),
21
- height: '100%'
22
- } }) }) }));
14
+ const styles = {
15
+ objectFit: isContainHeight ? 'unset' : (size !== null && size !== void 0 ? size : 'cover'),
16
+ width: isContainHeight ? 'auto' : '100%',
17
+ transform: isContainHeight ? 'translateX(-50%)' : 'none',
18
+ position: 'relative',
19
+ left: isContainHeight ? '50%' : (hasOffsetX ? `${offsetX * 100}vw` : '0'),
20
+ height: '100%'
21
+ };
22
+ (0, useCacheImage_1.useCacheImage)(key, true, styles, container, `image-background-${sectionId}`);
23
+ return ((0, jsx_runtime_1.jsx)("div", { ref: setContainer, className: `section-image-wrapper-${sectionId}`, style: {
24
+ position: position === 'fixed' ? 'sticky' : 'relative',
25
+ height: position === 'fixed' ? '100vh' : '100%',
26
+ top: position === 'fixed' ? '100vh' : '0',
27
+ width: '100%',
28
+ overflow: 'hidden'
29
+ } }));
23
30
  };
24
31
  exports.SectionImage = SectionImage;
@@ -3,13 +3,17 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.SectionVideo = void 0;
4
4
  const jsx_runtime_1 = require("react/jsx-runtime");
5
5
  const react_1 = require("react");
6
- const SectionVideoCacheContext_1 = require("./SectionVideoCacheContext");
6
+ const useCacheVideo_1 = require("../../assets/useCacheVideo");
7
+ const useCacheImage_1 = require("../../assets/useCacheImage");
8
+ const getCacheAssetKey_1 = require("../../assets/getCacheAssetKey");
7
9
  const SectionVideo = ({ container, sectionId, media }) => {
8
10
  const [video, setVideo] = (0, react_1.useState)(null);
9
- const { videoCache } = (0, react_1.useContext)(SectionVideoCacheContext_1.SectionVideoCacheContext);
10
11
  const [videoWrapper, setVideoWrapper] = (0, react_1.useState)(null);
12
+ const [coverImageWrapper, setCoverImageWrapper] = (0, react_1.useState)(null);
11
13
  const [isVideoWidthOverflow, setIsVideoWidthOverflow] = (0, react_1.useState)(false);
12
14
  const { url, size, position, offsetX, coverUrl, play } = media;
15
+ const key = (0, getCacheAssetKey_1.getCacheAssetKey)(url, sectionId);
16
+ const coverKey = coverUrl ? (0, getCacheAssetKey_1.getCacheAssetKey)(coverUrl, sectionId) : null;
13
17
  const [isPlaying, setIsPlaying] = (0, react_1.useState)(false);
14
18
  const [userPaused, setUserPaused] = (0, react_1.useState)(false);
15
19
  const [isClickedOnCover, setIsClickedOnCover] = (0, react_1.useState)(false);
@@ -26,42 +30,54 @@ const SectionVideo = ({ container, sectionId, media }) => {
26
30
  setUserPaused(false);
27
31
  }
28
32
  };
33
+ const isContainHeight = size === 'contain-height';
34
+ const hasOffsetX = offsetX !== null && size === 'contain';
35
+ const styles = {
36
+ objectFit: isContainHeight ? 'cover' : (size !== null && size !== void 0 ? size : 'cover'),
37
+ width: isContainHeight && !isVideoWidthOverflow ? 'auto' : '100%',
38
+ transform: isContainHeight ? 'translateX(-50%)' : 'none',
39
+ left: isContainHeight ? '50%' : (hasOffsetX ? `${offsetX * 100}vw` : '0'),
40
+ opacity: !isClickedOnCover && play === 'on-click' && coverUrl ? 0 : 1,
41
+ height: '100%',
42
+ position: 'relative'
43
+ };
44
+ const videoParams = (0, react_1.useMemo)(() => ({
45
+ play,
46
+ muted: play === 'auto',
47
+ controls: play === 'on-click'
48
+ }), [play]);
49
+ (0, useCacheVideo_1.useCacheVideo)(key, videoWrapper, true, videoParams, styles, video, setVideo, `section-video-${sectionId}`);
29
50
  (0, react_1.useEffect)(() => {
30
- if (!videoWrapper)
31
- return;
32
- const video = videoCache.get(url);
33
51
  if (!video)
34
52
  return;
35
- video.controls = play === "on-click";
36
- video.muted = play === "auto";
37
- video.autoplay = play === "auto";
38
- video.loop = true;
39
- const style = {
40
- objectFit: isContainHeight ? 'cover' : (size !== null && size !== void 0 ? size : 'cover'),
41
- width: isContainHeight && !isVideoWidthOverflow ? 'auto' : '100%',
42
- transform: isContainHeight ? 'translateX(-50%)' : 'none',
43
- left: isContainHeight ? '50%' : (hasOffsetX ? `${offsetX * 100}vw` : '0'),
44
- height: '100%',
45
- position: 'relative'
46
- };
47
- Object.assign(video.style, style);
48
- if (!videoWrapper.contains(video)) {
49
- videoWrapper.appendChild(video);
50
- }
51
- setVideo(video);
52
- video.addEventListener('play', () => {
53
+ const onPlay = () => {
53
54
  setIsPlaying(true);
54
- });
55
- video.addEventListener('pause', () => {
55
+ };
56
+ const onPause = () => {
56
57
  setIsPlaying(false);
57
- });
58
- if (play === "auto") {
59
- video.play().catch(() => { });
60
- }
58
+ };
59
+ video.addEventListener('play', onPlay);
60
+ video.addEventListener('pause', onPause);
61
61
  return () => {
62
- video.pause();
62
+ video.removeEventListener('play', onPlay);
63
+ video.removeEventListener('pause', onPause);
63
64
  };
64
- }, [url, play, videoWrapper, videoCache, sectionId, video, isVideoWidthOverflow]);
65
+ }, [video]);
66
+ (0, react_1.useEffect)(() => {
67
+ if (!video || !videoWrapper)
68
+ return;
69
+ video.addEventListener('loadedmetadata', () => {
70
+ const h = video.videoHeight;
71
+ const w = video.videoWidth;
72
+ const width = (videoWrapper.clientHeight / h) * w;
73
+ if (width > videoWrapper.clientWidth) {
74
+ setIsVideoWidthOverflow(true);
75
+ }
76
+ else {
77
+ setIsVideoWidthOverflow(false);
78
+ }
79
+ });
80
+ }, [video, videoWrapper]);
65
81
  (0, react_1.useEffect)(() => {
66
82
  if (!video || play !== 'on-click')
67
83
  return;
@@ -78,46 +94,33 @@ const SectionVideo = ({ container, sectionId, media }) => {
78
94
  observer.observe(container);
79
95
  return () => observer.disconnect();
80
96
  }, [container, play, userPaused, isClickedOnCover]);
81
- (0, react_1.useEffect)(() => {
82
- if (!video || !videoWrapper)
83
- return;
84
- video.addEventListener('loadedmetadata', () => {
85
- console.log('loadedmetadata', video.videoHeight, video.videoWidth);
86
- const h = video.videoHeight;
87
- const w = video.videoWidth;
88
- const width = (videoWrapper.clientHeight / h) * w;
89
- if (width > videoWrapper.clientWidth) {
90
- setIsVideoWidthOverflow(true);
91
- }
92
- else {
93
- setIsVideoWidthOverflow(false);
94
- }
95
- });
96
- }, [video, videoWrapper]);
97
- const isContainHeight = size === 'contain-height';
98
- const hasOffsetX = offsetX !== null && size === 'contain';
97
+ const coverStyles = {
98
+ opacity: isPlaying ? 0 : 1,
99
+ left: isContainHeight ? '50%' : (hasOffsetX ? `${offsetX * 100}vw` : '0'),
100
+ width: isContainHeight ? 'auto' : '100%',
101
+ objectFit: isContainHeight ? 'unset' : (size !== null && size !== void 0 ? size : 'cover'),
102
+ transform: isContainHeight ? 'translateX(-50%)' : 'none',
103
+ height: '100%',
104
+ position: 'relative',
105
+ pointerEvents: 'none',
106
+ transition: 'opacity 0.1s ease-in-out'
107
+ };
108
+ const renderCover = coverUrl && play === 'on-click' && !isClickedOnCover;
109
+ (0, useCacheImage_1.useCacheImage)(coverKey, !!renderCover, coverStyles, coverImageWrapper, `video-background-${sectionId}-cover`);
99
110
  return ((0, jsx_runtime_1.jsx)(jsx_runtime_1.Fragment, { children: (0, jsx_runtime_1.jsx)("div", { ref: setVideoWrapper, className: `section-video-wrapper-${sectionId}`, style: {
100
111
  position: position === 'fixed' ? 'sticky' : 'relative',
101
112
  height: position === 'fixed' ? '100vh' : '100%',
102
113
  top: position === 'fixed' ? '100vh' : '0',
103
114
  overflow: 'hidden',
104
- opacity: !isClickedOnCover && play === 'on-click' && coverUrl ? 0 : 1,
105
115
  width: '100%'
106
116
  }, children: play === 'on-click' && !isClickedOnCover && ((0, jsx_runtime_1.jsx)("div", { className: `video-background-${sectionId}-cover-container`, style: {
107
117
  position: 'absolute',
118
+ pointerEvents: 'all',
108
119
  left: 0,
109
120
  width: '100%',
121
+ zIndex: 1,
110
122
  height: '100%',
111
123
  top: 0
112
- }, onClick: handleCoverClick, children: coverUrl && play === 'on-click' && ((0, jsx_runtime_1.jsx)("img", { src: coverUrl, alt: "Video cover", className: `video-background-${sectionId}-cover`, style: {
113
- opacity: isPlaying ? 0 : 1,
114
- left: isContainHeight ? '50%' : (hasOffsetX ? `${offsetX * 100}vw` : '0'),
115
- width: isContainHeight ? 'auto' : '100%',
116
- objectFit: isContainHeight ? 'unset' : (size !== null && size !== void 0 ? size : 'cover'),
117
- transform: isContainHeight ? 'translateX(-50%)' : 'none',
118
- position: 'relative',
119
- height: '100%',
120
- transition: 'opacity 0.1s ease-in-out'
121
- } })) })) }) }));
124
+ }, onClick: handleCoverClick, ref: setCoverImageWrapper })) }) }));
122
125
  };
123
126
  exports.SectionVideo = SectionVideo;
@@ -17,16 +17,19 @@ const getStyleFromItemStateAndParams_1 = require("../../../utils/getStyleFromIte
17
17
  const useItemFXData_1 = require("../../../common/useItemFXData");
18
18
  const getFill_1 = require("../../../utils/getFill");
19
19
  const useExemplary_1 = require("../../../common/useExemplary");
20
- const SectionVideoCacheContext_1 = require("../../Section/SectionVideoCacheContext");
20
+ const AssetsCacheProvider_1 = require("../../../assets/AssetsCacheProvider");
21
+ const useCacheImage_1 = require("../../../assets/useCacheImage");
22
+ const getCacheAssetKey_1 = require("../../../assets/getCacheAssetKey");
21
23
  const ImageItem = ({ item, sectionId, onResize, interactionCtrl, onVisibilityChange }) => {
22
24
  var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o;
23
25
  const id = (0, react_1.useId)();
24
26
  const { radius: itemRadius, strokeWidth: itemStrokeWidth, opacity: itemOpacity, strokeFill: itemStrokeFill, blur: itemBlur } = (0, useFileItem_1.useFileItem)(item, sectionId);
25
- const { imageCache } = (0, react_1.useContext)(SectionVideoCacheContext_1.SectionVideoCacheContext);
27
+ const { imageCache } = (0, react_1.useContext)(AssetsCacheProvider_1.AssetsCacheContext);
26
28
  const itemAngle = (0, useItemAngle_1.useItemAngle)(item, sectionId);
27
29
  const [wrapperRef, setWrapperRef] = (0, react_1.useState)(null);
28
30
  (0, useRegisterResize_1.useRegisterResize)(wrapperRef, onResize);
29
31
  const { url, hasGLEffect } = item.params;
32
+ const cacheKey = (0, getCacheAssetKey_1.getCacheAssetKey)(url, item.id);
30
33
  const fxCanvas = (0, react_1.useRef)(null);
31
34
  const isInitialRef = (0, react_1.useRef)(true);
32
35
  const { controlsValues, fragmentShader } = (0, useItemFXData_1.useItemFXData)(item, sectionId);
@@ -64,22 +67,12 @@ const ImageItem = ({ item, sectionId, onResize, interactionCtrl, onVisibilityCha
64
67
  borderStyle: 'solid',
65
68
  } : {})), { transition: (_m = imgStateParams === null || imgStateParams === void 0 ? void 0 : imgStateParams.transition) !== null && _m !== void 0 ? _m : 'none' });
66
69
  const isInteractive = opacity !== 0;
70
+ const renderImage = !(hasGLEffect && isFXAllowed);
71
+ (0, useCacheImage_1.useCacheImage)(cacheKey, renderImage, inlineStyles, wrapperRef, `image image-${item.id}`);
67
72
  (0, react_1.useEffect)(() => {
68
73
  onVisibilityChange === null || onVisibilityChange === void 0 ? void 0 : onVisibilityChange(isInteractive);
69
74
  }, [isInteractive, onVisibilityChange]);
70
- (0, react_1.useEffect)(() => {
71
- if (!wrapperRef)
72
- return;
73
- const image = imageCache.get(url);
74
- if (!image)
75
- return;
76
- image.className = `image image-${item.id}`;
77
- Object.assign(image.style, inlineStyles);
78
- if (!wrapperRef.contains(image)) {
79
- wrapperRef.appendChild(image);
80
- }
81
- }, [wrapperRef, imageCache, url, angle, blur, inlineStyles]);
82
- return ((0, jsx_runtime_1.jsx)(LinkWrapper_1.LinkWrapper, { link: item.link, children: (0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsxs)("div", { className: `image-wrapper-${item.id}`, ref: setWrapperRef, style: Object.assign(Object.assign({ opacity, transform: `rotate(${angle}deg)` }, (blur !== undefined ? { filter: `blur(${blur * 100}vw)` } : {})), { willChange: blur !== 0 && blur !== undefined ? 'transform' : 'unset', transition: (_o = wrapperStateParams === null || wrapperStateParams === void 0 ? void 0 : wrapperStateParams.transition) !== null && _o !== void 0 ? _o : 'none' }), children: [hasGLEffect && isFXAllowed && ((0, jsx_runtime_1.jsx)("canvas", { style: inlineStyles, ref: fxCanvas, className: `img-canvas image-${item.id}`, width: rectWidth, height: rectHeight })), !(hasGLEffect && isFXAllowed) && !imageCache.has(url) && ((0, jsx_runtime_1.jsx)("img", { alt: "", className: `image image-${item.id}`, style: inlineStyles, src: item.params.url }))] }), (0, jsx_runtime_1.jsx)(style_1.default, { id: id, children: `
75
+ return ((0, jsx_runtime_1.jsx)(LinkWrapper_1.LinkWrapper, { link: item.link, children: (0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsxs)("div", { className: `image-wrapper-${item.id}`, ref: setWrapperRef, style: Object.assign(Object.assign({ opacity, transform: `rotate(${angle}deg)` }, (blur !== undefined ? { filter: `blur(${blur * 100}vw)` } : {})), { willChange: blur !== 0 && blur !== undefined ? 'transform' : 'unset', transition: (_o = wrapperStateParams === null || wrapperStateParams === void 0 ? void 0 : wrapperStateParams.transition) !== null && _o !== void 0 ? _o : 'none' }), children: [hasGLEffect && isFXAllowed && ((0, jsx_runtime_1.jsx)("canvas", { style: inlineStyles, ref: fxCanvas, className: `img-canvas image-${item.id}`, width: rectWidth, height: rectHeight })), renderImage && !imageCache.has(cacheKey) && ((0, jsx_runtime_1.jsx)("img", { alt: "", className: `image image-${item.id}`, style: inlineStyles, src: item.params.url }))] }), (0, jsx_runtime_1.jsx)(style_1.default, { id: id, children: `
83
76
  .image-wrapper-${item.id} {
84
77
  position: absolute;
85
78
  width: 100%;
@@ -18,19 +18,24 @@ const useElementRect_1 = require("../../../utils/useElementRect");
18
18
  const useItemFXData_1 = require("../../../common/useItemFXData");
19
19
  const getFill_1 = require("../../../utils/getFill");
20
20
  const useExemplary_1 = require("../../../common/useExemplary");
21
- const SectionVideoCacheContext_1 = require("../../Section/SectionVideoCacheContext");
21
+ const AssetsCacheProvider_1 = require("../../../assets/AssetsCacheProvider");
22
+ const useCacheVideo_1 = require("../../../assets/useCacheVideo");
23
+ const useCacheImage_1 = require("../../../assets/useCacheImage");
24
+ const getCacheAssetKey_1 = require("../../../assets/getCacheAssetKey");
22
25
  const VideoItem = ({ item, sectionId, onResize, interactionCtrl, onVisibilityChange }) => {
23
- var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p;
26
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q;
24
27
  const id = (0, react_1.useId)();
25
28
  const { radius: itemRadius, strokeWidth: itemStrokeWidth, strokeFill: itemStrokeFill, opacity: itemOpacity, blur: itemBlur } = (0, useFileItem_1.useFileItem)(item, sectionId);
26
- const { videoCache } = (0, react_1.useContext)(SectionVideoCacheContext_1.SectionVideoCacheContext);
29
+ const { videoCache, imageCache } = (0, react_1.useContext)(AssetsCacheProvider_1.AssetsCacheContext);
27
30
  const [isVideoPlaying, setIsVideoPlaying] = (0, react_1.useState)(false);
31
+ const videoCacheKey = (0, getCacheAssetKey_1.getCacheAssetKey)(item.params.url, item.id);
32
+ const coverCacheKey = item.params.coverUrl ? (0, getCacheAssetKey_1.getCacheAssetKey)(item.params.coverUrl, item.id) : null;
28
33
  const isScrollPausedRef = (0, react_1.useRef)(false);
29
34
  const [userPaused, setUserPaused] = (0, react_1.useState)(false);
30
35
  const [isVideoInteracted, setIsVideoInteracted] = (0, react_1.useState)(false);
31
36
  const itemAngle = (0, useItemAngle_1.useItemAngle)(item, sectionId);
32
37
  const [videoWrapper, setVideoWrapper] = (0, react_1.useState)(null);
33
- const [videoRef, setVideoRef] = (0, react_1.useState)(null);
38
+ const [video, setVideo] = (0, react_1.useState)(null);
34
39
  const fxCanvas = (0, react_1.useRef)(null);
35
40
  const { url, hasGLEffect } = item.params;
36
41
  const isInitialRef = (0, react_1.useRef)(true);
@@ -65,77 +70,127 @@ const VideoItem = ({ item, sectionId, onResize, interactionCtrl, onVisibilityCha
65
70
  controls: controlsValues
66
71
  }, width, height);
67
72
  (0, useRegisterResize_1.useRegisterResize)(videoWrapper, onResize);
68
- const inlineStyles = Object.assign(Object.assign({ borderRadius: `${radius * 100}vw` }, (strokeWidth !== undefined ? {
73
+ const inlineStyles = Object.assign(Object.assign({ transform: `translateZ(0)`, borderRadius: `${radius * 100}vw` }, (strokeWidth !== undefined ? {
69
74
  borderWidth: `${strokeWidth * 100}vw`,
70
75
  borderColor: stroke,
71
76
  borderRadius: radius !== undefined ? `${radius * 100}vw` : 'inherit',
72
- borderStyle: 'solid'
77
+ borderStyle: 'solid',
73
78
  } : {})), { transition: (_m = videoStateParams === null || videoStateParams === void 0 ? void 0 : videoStateParams.transition) !== null && _m !== void 0 ? _m : 'none' });
74
79
  const isInteractive = opacity !== 0;
75
- (0, react_1.useEffect)(() => {
76
- onVisibilityChange === null || onVisibilityChange === void 0 ? void 0 : onVisibilityChange(isInteractive);
77
- }, [isInteractive, onVisibilityChange]);
78
- (0, react_1.useEffect)(() => {
79
- var _a;
80
- if (!videoWrapper || hasScrollPlayback || hasGLEffect)
80
+ const isVideoVisible = !hasScrollPlayback && !hasGLEffect;
81
+ (0, useCacheVideo_1.useCacheVideo)(videoCacheKey, videoWrapper, isVideoVisible, params, inlineStyles, video, setVideo, `video video-${item.id}`);
82
+ const onCoverMouseEnter = (0, react_1.useCallback)(() => {
83
+ if (!video || params.play !== 'on-hover')
81
84
  return;
82
- const { play } = params;
83
- const video = videoCache.get(url);
85
+ setIsVideoInteracted(true);
86
+ video.play();
87
+ }, [video, params]);
88
+ const onCoverClick = (0, react_1.useCallback)(() => {
84
89
  if (!video)
85
90
  return;
86
- video.controls = play === "on-click";
87
- video.muted = play === "auto";
88
- video.autoplay = play === "auto";
89
- video.loop = true;
90
- video.poster = (_a = item.params.coverUrl) !== null && _a !== void 0 ? _a : '';
91
- video.style.transform = `translateZ(0)`;
92
- video.className = `video video-${item.id}`;
93
- if (!videoWrapper.contains(video)) {
94
- videoWrapper.appendChild(video);
95
- }
96
- setVideoRef(video);
97
- video.addEventListener('play', () => {
91
+ setIsVideoInteracted(true);
92
+ video.play();
93
+ }, [video, params]);
94
+ const renderCover = isVideoVisible && ((params.play === 'on-click' || params.play === 'on-hover') && item.params.coverUrl && !isVideoInteracted);
95
+ (0, useCacheImage_1.useCacheImage)(coverCacheKey, !!renderCover, {}, videoWrapper, `video-cover-${item.id}`, onCoverMouseEnter, onCoverClick);
96
+ (0, react_1.useEffect)(() => {
97
+ if (!video || !videoCache.has(url))
98
+ return;
99
+ const onPlay = () => {
98
100
  setIsVideoPlaying(true);
99
101
  setUserPaused(false);
100
- });
101
- video.addEventListener('pause', () => {
102
+ };
103
+ const onPause = () => {
102
104
  if (!isScrollPausedRef.current) {
103
105
  setUserPaused(true);
104
106
  }
105
107
  setIsVideoPlaying(false);
106
- });
107
- if (play === "auto") {
108
- video.play().catch(() => { });
109
- }
110
- return () => {
108
+ };
109
+ const onMouseEnter = () => {
110
+ if (!video || params.play !== 'on-hover')
111
+ return;
112
+ video.play();
113
+ };
114
+ const onMouseLeave = () => {
115
+ if (!video || params.play !== 'on-hover')
116
+ return;
111
117
  video.pause();
112
118
  };
113
- }, [params, videoWrapper, videoCache]);
119
+ video.addEventListener('play', onPlay);
120
+ video.addEventListener('pause', onPause);
121
+ video.addEventListener('mouseenter', onMouseEnter);
122
+ video.addEventListener('mouseleave', onMouseLeave);
123
+ return () => {
124
+ video.removeEventListener('play', onPlay);
125
+ video.removeEventListener('pause', onPause);
126
+ video.removeEventListener('mouseenter', onMouseEnter);
127
+ video.removeEventListener('mouseleave', onMouseLeave);
128
+ };
129
+ }, [video, videoCache, params]);
130
+ (0, react_1.useEffect)(() => {
131
+ if (!params || !video || params.play !== 'on-click' || !videoWrapper)
132
+ return;
133
+ const observer = new IntersectionObserver(([entry]) => {
134
+ if (userPaused || !isVideoInteracted)
135
+ return;
136
+ if (entry.isIntersecting) {
137
+ isScrollPausedRef.current = false;
138
+ video.play();
139
+ }
140
+ else {
141
+ isScrollPausedRef.current = true;
142
+ video.pause();
143
+ }
144
+ });
145
+ observer.observe(videoWrapper);
146
+ return () => observer.disconnect();
147
+ }, [params, video, videoWrapper, userPaused, isVideoInteracted]);
148
+ (0, react_1.useEffect)(() => {
149
+ onVisibilityChange === null || onVisibilityChange === void 0 ? void 0 : onVisibilityChange(isInteractive);
150
+ }, [isInteractive, onVisibilityChange]);
114
151
  return ((0, jsx_runtime_1.jsxs)(LinkWrapper_1.LinkWrapper, { link: item.link, children: [(0, jsx_runtime_1.jsxs)("div", { className: `video-wrapper-${item.id}`, ref: setVideoWrapper, style: {
115
152
  opacity,
116
153
  transform: `rotate(${angle}deg) translateZ(0)`,
117
154
  filter: `blur(${blur * 100}vw)`,
118
155
  willChange: blur !== 0 && blur !== undefined ? 'transform' : 'unset',
119
156
  transition: (_o = wrapperStateParams === null || wrapperStateParams === void 0 ? void 0 : wrapperStateParams.transition) !== null && _o !== void 0 ? _o : 'none'
120
- }, children: [hasScrollPlayback && ((0, jsx_runtime_1.jsx)(ScrollPlaybackVideo_1.ScrollPlaybackVideo, { sectionId: sectionId, src: item.params.url, playbackParams: scrollPlayback, style: inlineStyles, className: `video video-playback-wrapper video-${item.id}` })), hasGLEffect && isFXAllowed && ((0, jsx_runtime_1.jsx)("canvas", { style: inlineStyles, ref: fxCanvas, className: `video-canvas video-${item.id}`, width: rectWidth, height: rectHeight })), !hasScrollPlayback && !hasGLEffect && ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(params.play === 'on-click' || params.play === 'on-hover') && item.params.coverUrl && !isVideoInteracted && ((0, jsx_runtime_1.jsx)("img", { onMouseEnter: () => {
121
- if (!videoRef || params.play !== 'on-hover')
157
+ }, children: [hasScrollPlayback && ((0, jsx_runtime_1.jsx)(ScrollPlaybackVideo_1.ScrollPlaybackVideo, { sectionId: sectionId, src: item.params.url, playbackParams: scrollPlayback, style: inlineStyles, className: `video video-playback-wrapper video-${item.id}` })), hasGLEffect && isFXAllowed && ((0, jsx_runtime_1.jsx)("canvas", { style: inlineStyles, ref: fxCanvas, className: `video-canvas video-${item.id}`, width: rectWidth, height: rectHeight })), !hasScrollPlayback && !hasGLEffect && ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [isVideoVisible && !videoCache.has(videoCacheKey) && ((0, jsx_runtime_1.jsx)("video", { poster: (_p = item.params.coverUrl) !== null && _p !== void 0 ? _p : '', ref: setVideo, autoPlay: params.play === 'auto', preload: "auto", onClick: () => {
158
+ setIsVideoInteracted(true);
159
+ }, muted: params.muted, onPlay: () => {
160
+ setIsVideoPlaying(true);
161
+ setUserPaused(false);
162
+ }, onPause: () => {
163
+ if (!isScrollPausedRef.current) {
164
+ setUserPaused(true);
165
+ }
166
+ setIsVideoPlaying(false);
167
+ }, onMouseEnter: () => {
168
+ if (!video || params.play !== 'on-hover')
169
+ return;
170
+ video.play();
171
+ }, onMouseLeave: () => {
172
+ if (!video || params.play !== 'on-hover')
173
+ return;
174
+ video.pause();
175
+ }, loop: true, controls: params.controls, playsInline: true, className: `video video-${item.id}`, style: inlineStyles, children: (0, jsx_runtime_1.jsx)("source", { src: item.params.url }) })), renderCover && coverCacheKey && !imageCache.has(coverCacheKey) && ((0, jsx_runtime_1.jsx)("img", { onMouseEnter: () => {
176
+ if (!video || params.play !== 'on-hover')
122
177
  return;
123
178
  setIsVideoInteracted(true);
124
- videoRef.play();
125
- }, src: (_p = item.params.coverUrl) !== null && _p !== void 0 ? _p : '', className: `video-cover-${item.id}`, onClick: () => {
126
- if (!videoRef)
179
+ video.play();
180
+ }, src: (_q = item.params.coverUrl) !== null && _q !== void 0 ? _q : '', className: `video-cover-${item.id}`, onClick: () => {
181
+ if (!video)
127
182
  return;
128
183
  setIsVideoInteracted(true);
129
- videoRef.play();
184
+ video.play();
130
185
  } })), (params.play === 'on-click' && !params.controls && ((0, jsx_runtime_1.jsx)("div", { className: `video-overlay-${item.id}`, onClick: () => {
131
- if (!videoRef)
186
+ if (!video)
132
187
  return;
133
188
  setIsVideoInteracted(true);
134
189
  if (isVideoPlaying) {
135
- videoRef.pause();
190
+ video.pause();
136
191
  }
137
192
  else {
138
- videoRef.play();
193
+ video.play();
139
194
  }
140
195
  } })))] }))] }), (0, jsx_runtime_1.jsx)(style_1.default, { id: id, children: `
141
196
  .video-wrapper-${item.id} {
@@ -1 +1,4 @@
1
- export declare function usePreloadAssets(assets: string[]): void;
1
+ export declare function usePreloadAssets(assets: {
2
+ url: string;
3
+ id: string;
4
+ }[]): void;
@@ -14,10 +14,10 @@ function isImageAsset(url) {
14
14
  }
15
15
  function usePreloadAssets(assets) {
16
16
  (0, react_1.useEffect)(() => {
17
- assets.forEach(asset => {
18
- if (isVideoAsset(asset)) {
17
+ assets.forEach(({ url, id }) => {
18
+ if (isVideoAsset(url)) {
19
19
  const video = document.createElement('video');
20
- video.src = asset;
20
+ video.src = url;
21
21
  video.preload = 'auto';
22
22
  video.style.display = 'none';
23
23
  document.body.appendChild(video);
@@ -29,9 +29,9 @@ function usePreloadAssets(assets) {
29
29
  }, 1000);
30
30
  });
31
31
  }
32
- if (isImageAsset(asset)) {
32
+ if (isImageAsset(url)) {
33
33
  const img = new Image();
34
- img.src = asset;
34
+ img.src = url;
35
35
  }
36
36
  });
37
37
  }, [assets]);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gxpl/sdk",
3
- "version": "0.0.36-0",
3
+ "version": "0.0.37",
4
4
  "description": "Generic SDK for use in public websites.",
5
5
  "main": "lib/index.js",
6
6
  "types": "lib/index.d.ts",
@@ -1,10 +0,0 @@
1
- import { FC, PropsWithChildren } from 'react';
2
- export declare const SectionVideoCacheContext: import("react").Context<{
3
- videoCache: Map<string, HTMLVideoElement>;
4
- imageCache: Map<string, HTMLImageElement>;
5
- }>;
6
- interface Props {
7
- assets: string[];
8
- }
9
- export declare const SectionVideoCacheProvider: FC<PropsWithChildren<Props>>;
10
- export {};