@gxpl/sdk 0.0.36 → 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.
@@ -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 {};
@@ -0,0 +1,44 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.AssetsCacheProvider = exports.AssetsCacheContext = void 0;
4
+ const jsx_runtime_1 = require("react/jsx-runtime");
5
+ const react_1 = require("react");
6
+ const getCacheAssetKey_1 = require("./getCacheAssetKey");
7
+ exports.AssetsCacheContext = (0, react_1.createContext)({ videoCache: new Map(), imageCache: new Map() });
8
+ const AssetsCacheProvider = ({ children, assets }) => {
9
+ const [videoCache, setVideoCache] = (0, react_1.useState)(new Map());
10
+ const [imageCache, setImageCache] = (0, react_1.useState)(new Map());
11
+ (0, react_1.useEffect)(() => {
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));
16
+ }
17
+ if (isImageAsset(url)) {
18
+ const img = new Image();
19
+ img.src = url;
20
+ setImageCache(prev => prev.set((0, getCacheAssetKey_1.getCacheAssetKey)(url, id), img));
21
+ }
22
+ });
23
+ }, [assets]);
24
+ return (0, jsx_runtime_1.jsx)(exports.AssetsCacheContext.Provider, { value: { videoCache, imageCache }, children: children });
25
+ };
26
+ exports.AssetsCacheProvider = AssetsCacheProvider;
27
+ function getVideo(url) {
28
+ const video = document.createElement('video');
29
+ video.src = url;
30
+ video.preload = 'auto';
31
+ video.playsInline = true;
32
+ video.load();
33
+ return video;
34
+ }
35
+ function isVideoAsset(url) {
36
+ const videoExtensions = ['.mp4', '.mov', '.webm'];
37
+ const lowerUrl = url.toLowerCase();
38
+ return videoExtensions.some(ext => lowerUrl.endsWith(ext));
39
+ }
40
+ function isImageAsset(url) {
41
+ const imageExtensions = ['.gif', '.png', '.jpg', '.jpeg', '.webp', '.avif', '.svg'];
42
+ const lowerUrl = url.toLowerCase();
43
+ return imageExtensions.some(ext => lowerUrl.endsWith(ext));
44
+ }
@@ -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,9 +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
13
  const PreviewWrapper_1 = require("./Preview/PreviewWrapper");
15
14
  const PreviewListener_1 = require("./Preview/PreviewListener");
15
+ const AssetsCacheProvider_1 = require("../assets/AssetsCacheProvider");
16
16
  const Page = ({ project, articlesData }) => {
17
17
  var _a, _b;
18
18
  const afterBodyOpen = (0, html_react_parser_1.default)(project.html.afterBodyOpen);
@@ -20,13 +20,12 @@ const Page = ({ project, articlesData }) => {
20
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];
21
21
  const scenes = Object.values(articlesData).map(({ article }) => ({ id: article.id }));
22
22
  const { relations, scenesAssets } = project;
23
- (0, usePrelaodAssets_1.usePreloadAssets)(scenesAssets);
24
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: {
25
24
  input: {
26
25
  startScene,
27
26
  relations,
28
27
  scenes,
29
28
  }
30
- }, children: [(0, jsx_runtime_1.jsx)(PreviewListener_1.PreviewListener, {}), 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] }));
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] }));
31
30
  };
32
31
  exports.Page = Page;
@@ -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,11 +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 useCacheVideo_1 = require("../../assets/useCacheVideo");
7
+ const useCacheImage_1 = require("../../assets/useCacheImage");
8
+ const getCacheAssetKey_1 = require("../../assets/getCacheAssetKey");
6
9
  const SectionVideo = ({ container, sectionId, media }) => {
7
10
  const [video, setVideo] = (0, react_1.useState)(null);
8
11
  const [videoWrapper, setVideoWrapper] = (0, react_1.useState)(null);
12
+ const [coverImageWrapper, setCoverImageWrapper] = (0, react_1.useState)(null);
9
13
  const [isVideoWidthOverflow, setIsVideoWidthOverflow] = (0, react_1.useState)(false);
10
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;
11
17
  const [isPlaying, setIsPlaying] = (0, react_1.useState)(false);
12
18
  const [userPaused, setUserPaused] = (0, react_1.useState)(false);
13
19
  const [isClickedOnCover, setIsClickedOnCover] = (0, react_1.useState)(false);
@@ -24,22 +30,39 @@ const SectionVideo = ({ container, sectionId, media }) => {
24
30
  setUserPaused(false);
25
31
  }
26
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}`);
27
50
  (0, react_1.useEffect)(() => {
28
- if (!video || play !== 'on-click')
51
+ if (!video)
29
52
  return;
30
- const observer = new IntersectionObserver(([entry]) => {
31
- if (userPaused || !isClickedOnCover)
32
- return;
33
- if (entry.isIntersecting) {
34
- video.play();
35
- }
36
- else {
37
- video.pause();
38
- }
39
- });
40
- observer.observe(container);
41
- return () => observer.disconnect();
42
- }, [container, play, userPaused, isClickedOnCover]);
53
+ const onPlay = () => {
54
+ setIsPlaying(true);
55
+ };
56
+ const onPause = () => {
57
+ setIsPlaying(false);
58
+ };
59
+ video.addEventListener('play', onPlay);
60
+ video.addEventListener('pause', onPause);
61
+ return () => {
62
+ video.removeEventListener('play', onPlay);
63
+ video.removeEventListener('pause', onPause);
64
+ };
65
+ }, [video]);
43
66
  (0, react_1.useEffect)(() => {
44
67
  if (!video || !videoWrapper)
45
68
  return;
@@ -55,37 +78,49 @@ const SectionVideo = ({ container, sectionId, media }) => {
55
78
  }
56
79
  });
57
80
  }, [video, videoWrapper]);
58
- const isContainHeight = size === 'contain-height';
59
- const hasOffsetX = offsetX !== null && size === 'contain';
60
- return ((0, jsx_runtime_1.jsx)(jsx_runtime_1.Fragment, { children: (0, jsx_runtime_1.jsxs)("div", { ref: setVideoWrapper, className: `section-video-wrapper-${sectionId}`, style: {
81
+ (0, react_1.useEffect)(() => {
82
+ if (!video || play !== 'on-click')
83
+ return;
84
+ const observer = new IntersectionObserver(([entry]) => {
85
+ if (userPaused || !isClickedOnCover)
86
+ return;
87
+ if (entry.isIntersecting) {
88
+ video.play();
89
+ }
90
+ else {
91
+ video.pause();
92
+ }
93
+ });
94
+ observer.observe(container);
95
+ return () => observer.disconnect();
96
+ }, [container, play, userPaused, isClickedOnCover]);
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`);
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: {
61
111
  position: position === 'fixed' ? 'sticky' : 'relative',
62
112
  height: position === 'fixed' ? '100vh' : '100%',
63
113
  top: position === 'fixed' ? '100vh' : '0',
64
114
  overflow: 'hidden',
65
115
  width: '100%'
66
- }, children: [(0, jsx_runtime_1.jsx)("video", { ref: setVideo, autoPlay: play === 'auto', loop: true, style: {
67
- opacity: !isClickedOnCover && play === 'on-click' && coverUrl ? 0 : 1,
68
- objectFit: isContainHeight ? 'cover' : (size !== null && size !== void 0 ? size : 'cover'),
69
- width: isContainHeight && !isVideoWidthOverflow ? 'auto' : '100%',
70
- transform: isContainHeight ? 'translateX(-50%)' : 'none',
71
- left: isContainHeight ? '50%' : (hasOffsetX ? `${offsetX * 100}vw` : '0'),
72
- height: '100%',
73
- position: 'relative'
74
- }, controls: play === 'on-click', muted: play === 'auto', playsInline: true, preload: "auto", className: `video-background-${sectionId}`, onPlay: () => setIsPlaying(true), onPause: () => setIsPlaying(false), children: (0, jsx_runtime_1.jsx)("source", { src: `${url}` }) }), play === 'on-click' && !isClickedOnCover && ((0, jsx_runtime_1.jsx)("div", { className: `video-background-${sectionId}-cover-container`, style: {
75
- position: 'absolute',
76
- left: 0,
77
- width: '100%',
78
- height: '100%',
79
- top: 0
80
- }, onClick: handleCoverClick, children: coverUrl && play === 'on-click' && ((0, jsx_runtime_1.jsx)("img", { src: coverUrl, alt: "Video cover", className: `video-background-${sectionId}-cover`, style: {
81
- opacity: isPlaying ? 0 : 1,
82
- left: isContainHeight ? '50%' : (hasOffsetX ? `${offsetX * 100}vw` : '0'),
83
- width: isContainHeight ? 'auto' : '100%',
84
- objectFit: isContainHeight ? 'unset' : (size !== null && size !== void 0 ? size : 'cover'),
85
- transform: isContainHeight ? 'translateX(-50%)' : 'none',
86
- position: 'relative',
87
- height: '100%',
88
- transition: 'opacity 0.1s ease-in-out'
89
- } })) }))] }) }));
116
+ }, children: play === 'on-click' && !isClickedOnCover && ((0, jsx_runtime_1.jsx)("div", { className: `video-background-${sectionId}-cover-container`, style: {
117
+ position: 'absolute',
118
+ pointerEvents: 'all',
119
+ left: 0,
120
+ width: '100%',
121
+ zIndex: 1,
122
+ height: '100%',
123
+ top: 0
124
+ }, onClick: handleCoverClick, ref: setCoverImageWrapper })) }) }));
90
125
  };
91
126
  exports.SectionVideo = SectionVideo;
@@ -17,14 +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 AssetsCacheProvider_1 = require("../../../assets/AssetsCacheProvider");
21
+ const useCacheImage_1 = require("../../../assets/useCacheImage");
22
+ const getCacheAssetKey_1 = require("../../../assets/getCacheAssetKey");
20
23
  const ImageItem = ({ item, sectionId, onResize, interactionCtrl, onVisibilityChange }) => {
21
24
  var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o;
22
25
  const id = (0, react_1.useId)();
23
26
  const { radius: itemRadius, strokeWidth: itemStrokeWidth, opacity: itemOpacity, strokeFill: itemStrokeFill, blur: itemBlur } = (0, useFileItem_1.useFileItem)(item, sectionId);
27
+ const { imageCache } = (0, react_1.useContext)(AssetsCacheProvider_1.AssetsCacheContext);
24
28
  const itemAngle = (0, useItemAngle_1.useItemAngle)(item, sectionId);
25
29
  const [wrapperRef, setWrapperRef] = (0, react_1.useState)(null);
26
30
  (0, useRegisterResize_1.useRegisterResize)(wrapperRef, onResize);
27
31
  const { url, hasGLEffect } = item.params;
32
+ const cacheKey = (0, getCacheAssetKey_1.getCacheAssetKey)(url, item.id);
28
33
  const fxCanvas = (0, react_1.useRef)(null);
29
34
  const isInitialRef = (0, react_1.useRef)(true);
30
35
  const { controlsValues, fragmentShader } = (0, useItemFXData_1.useItemFXData)(item, sectionId);
@@ -62,12 +67,12 @@ const ImageItem = ({ item, sectionId, onResize, interactionCtrl, onVisibilityCha
62
67
  borderStyle: 'solid',
63
68
  } : {})), { transition: (_m = imgStateParams === null || imgStateParams === void 0 ? void 0 : imgStateParams.transition) !== null && _m !== void 0 ? _m : 'none' });
64
69
  const isInteractive = opacity !== 0;
70
+ const renderImage = !(hasGLEffect && isFXAllowed);
71
+ (0, useCacheImage_1.useCacheImage)(cacheKey, renderImage, inlineStyles, wrapperRef, `image image-${item.id}`);
65
72
  (0, react_1.useEffect)(() => {
66
73
  onVisibilityChange === null || onVisibilityChange === void 0 ? void 0 : onVisibilityChange(isInteractive);
67
74
  }, [isInteractive, onVisibilityChange]);
68
- 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.jsx)("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
69
- ? ((0, jsx_runtime_1.jsx)("canvas", { style: inlineStyles, ref: fxCanvas, className: `img-canvas image-${item.id}`, width: rectWidth, height: rectHeight }))
70
- : ((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: `
71
76
  .image-wrapper-${item.id} {
72
77
  position: absolute;
73
78
  width: 100%;
@@ -18,17 +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 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");
21
25
  const VideoItem = ({ item, sectionId, onResize, interactionCtrl, onVisibilityChange }) => {
22
26
  var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q;
23
27
  const id = (0, react_1.useId)();
24
28
  const { radius: itemRadius, strokeWidth: itemStrokeWidth, strokeFill: itemStrokeFill, opacity: itemOpacity, blur: itemBlur } = (0, useFileItem_1.useFileItem)(item, sectionId);
29
+ const { videoCache, imageCache } = (0, react_1.useContext)(AssetsCacheProvider_1.AssetsCacheContext);
25
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;
26
33
  const isScrollPausedRef = (0, react_1.useRef)(false);
27
34
  const [userPaused, setUserPaused] = (0, react_1.useState)(false);
28
35
  const [isVideoInteracted, setIsVideoInteracted] = (0, react_1.useState)(false);
29
36
  const itemAngle = (0, useItemAngle_1.useItemAngle)(item, sectionId);
30
- const [ref, setRef] = (0, react_1.useState)(null);
31
- const [videoRef, setVideoRef] = (0, react_1.useState)(null);
37
+ const [videoWrapper, setVideoWrapper] = (0, react_1.useState)(null);
38
+ const [video, setVideo] = (0, react_1.useState)(null);
32
39
  const fxCanvas = (0, react_1.useRef)(null);
33
40
  const { url, hasGLEffect } = item.params;
34
41
  const isInitialRef = (0, react_1.useRef)(true);
@@ -38,7 +45,7 @@ const VideoItem = ({ item, sectionId, onResize, interactionCtrl, onVisibilityCha
38
45
  const width = area && exemplary ? area.width * exemplary : 0;
39
46
  const height = area && exemplary ? area.height * exemplary : 0;
40
47
  const { controlsValues, fragmentShader } = (0, useItemFXData_1.useItemFXData)(item, sectionId);
41
- const rect = (0, useElementRect_1.useElementRect)(ref);
48
+ const rect = (0, useElementRect_1.useElementRect)(videoWrapper);
42
49
  const rectWidth = Math.floor((_a = rect === null || rect === void 0 ? void 0 : rect.width) !== null && _a !== void 0 ? _a : 0);
43
50
  const rectHeight = Math.floor((_b = rect === null || rect === void 0 ? void 0 : rect.height) !== null && _b !== void 0 ? _b : 0);
44
51
  const scrollPlayback = params.scrollPlayback;
@@ -62,42 +69,92 @@ const VideoItem = ({ item, sectionId, onResize, interactionCtrl, onVisibilityCha
62
69
  fragmentShader,
63
70
  controls: controlsValues
64
71
  }, width, height);
65
- (0, useRegisterResize_1.useRegisterResize)(ref, onResize);
66
- const inlineStyles = Object.assign(Object.assign({ borderRadius: `${radius * 100}vw` }, (strokeWidth !== undefined ? {
72
+ (0, useRegisterResize_1.useRegisterResize)(videoWrapper, onResize);
73
+ const inlineStyles = Object.assign(Object.assign({ transform: `translateZ(0)`, borderRadius: `${radius * 100}vw` }, (strokeWidth !== undefined ? {
67
74
  borderWidth: `${strokeWidth * 100}vw`,
68
75
  borderColor: stroke,
69
76
  borderRadius: radius !== undefined ? `${radius * 100}vw` : 'inherit',
70
- borderStyle: 'solid'
77
+ borderStyle: 'solid',
71
78
  } : {})), { transition: (_m = videoStateParams === null || videoStateParams === void 0 ? void 0 : videoStateParams.transition) !== null && _m !== void 0 ? _m : 'none' });
72
79
  const isInteractive = opacity !== 0;
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')
84
+ return;
85
+ setIsVideoInteracted(true);
86
+ video.play();
87
+ }, [video, params]);
88
+ const onCoverClick = (0, react_1.useCallback)(() => {
89
+ if (!video)
90
+ return;
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);
73
96
  (0, react_1.useEffect)(() => {
74
- onVisibilityChange === null || onVisibilityChange === void 0 ? void 0 : onVisibilityChange(isInteractive);
75
- }, [isInteractive, onVisibilityChange]);
97
+ if (!video || !videoCache.has(url))
98
+ return;
99
+ const onPlay = () => {
100
+ setIsVideoPlaying(true);
101
+ setUserPaused(false);
102
+ };
103
+ const onPause = () => {
104
+ if (!isScrollPausedRef.current) {
105
+ setUserPaused(true);
106
+ }
107
+ setIsVideoPlaying(false);
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;
117
+ video.pause();
118
+ };
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]);
76
130
  (0, react_1.useEffect)(() => {
77
- if (!videoRef || params.play !== 'on-click' || !ref)
131
+ if (!params || !video || params.play !== 'on-click' || !videoWrapper)
78
132
  return;
79
133
  const observer = new IntersectionObserver(([entry]) => {
80
134
  if (userPaused || !isVideoInteracted)
81
135
  return;
82
136
  if (entry.isIntersecting) {
83
137
  isScrollPausedRef.current = false;
84
- videoRef.play();
138
+ video.play();
85
139
  }
86
140
  else {
87
141
  isScrollPausedRef.current = true;
88
- videoRef.pause();
142
+ video.pause();
89
143
  }
90
144
  });
91
- observer.observe(ref);
145
+ observer.observe(videoWrapper);
92
146
  return () => observer.disconnect();
93
- }, [videoRef, ref, userPaused, isVideoInteracted]);
94
- 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: setRef, style: {
147
+ }, [params, video, videoWrapper, userPaused, isVideoInteracted]);
148
+ (0, react_1.useEffect)(() => {
149
+ onVisibilityChange === null || onVisibilityChange === void 0 ? void 0 : onVisibilityChange(isInteractive);
150
+ }, [isInteractive, onVisibilityChange]);
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: {
95
152
  opacity,
96
- transform: `rotate(${angle}deg)`,
153
+ transform: `rotate(${angle}deg) translateZ(0)`,
97
154
  filter: `blur(${blur * 100}vw)`,
98
155
  willChange: blur !== 0 && blur !== undefined ? 'transform' : 'unset',
99
156
  transition: (_o = wrapperStateParams === null || wrapperStateParams === void 0 ? void 0 : wrapperStateParams.transition) !== null && _o !== void 0 ? _o : 'none'
100
- }, 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: [(0, jsx_runtime_1.jsx)("video", { poster: (_p = item.params.coverUrl) !== null && _p !== void 0 ? _p : '', ref: setVideoRef, autoPlay: params.play === 'auto', preload: "auto", onClick: () => {
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: () => {
101
158
  setIsVideoInteracted(true);
102
159
  }, muted: params.muted, onPlay: () => {
103
160
  setIsVideoPlaying(true);
@@ -108,32 +165,32 @@ const VideoItem = ({ item, sectionId, onResize, interactionCtrl, onVisibilityCha
108
165
  }
109
166
  setIsVideoPlaying(false);
110
167
  }, onMouseEnter: () => {
111
- if (!videoRef || params.play !== 'on-hover')
168
+ if (!video || params.play !== 'on-hover')
112
169
  return;
113
- videoRef.play();
170
+ video.play();
114
171
  }, onMouseLeave: () => {
115
- if (!videoRef || params.play !== 'on-hover')
172
+ if (!video || params.play !== 'on-hover')
116
173
  return;
117
- videoRef.pause();
118
- }, 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 }) }), (params.play === 'on-click' || params.play === 'on-hover') && item.params.coverUrl && !isVideoInteracted && ((0, jsx_runtime_1.jsx)("img", { onMouseEnter: () => {
119
- if (!videoRef || params.play !== 'on-hover')
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')
120
177
  return;
121
178
  setIsVideoInteracted(true);
122
- videoRef.play();
179
+ video.play();
123
180
  }, src: (_q = item.params.coverUrl) !== null && _q !== void 0 ? _q : '', className: `video-cover-${item.id}`, onClick: () => {
124
- if (!videoRef)
181
+ if (!video)
125
182
  return;
126
183
  setIsVideoInteracted(true);
127
- videoRef.play();
184
+ video.play();
128
185
  } })), (params.play === 'on-click' && !params.controls && ((0, jsx_runtime_1.jsx)("div", { className: `video-overlay-${item.id}`, onClick: () => {
129
- if (!videoRef)
186
+ if (!video)
130
187
  return;
131
188
  setIsVideoInteracted(true);
132
189
  if (isVideoPlaying) {
133
- videoRef.pause();
190
+ video.pause();
134
191
  }
135
192
  else {
136
- videoRef.play();
193
+ video.play();
137
194
  }
138
195
  } })))] }))] }), (0, jsx_runtime_1.jsx)(style_1.default, { id: id, children: `
139
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",
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",