@cntrl-site/sdk-nextjs 1.2.3-alpha.1 → 1.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/eslint.config.mjs CHANGED
@@ -13,6 +13,22 @@ export default antfu(
13
13
  'style/member-delimiter-style': 'off',
14
14
  'semi': ['error', 'always'],
15
15
  'no-alert': 'off',
16
+ 'ts/consistent-type-definitions': 'off',
17
+ 'perfectionist/sort-imports': 'off',
18
+ 'perfectionist/sort-named-imports': 'off',
19
+ 'ts/consistent-type-imports': 'off',
20
+ 'jsonc/sort-keys': 'off',
21
+ 'node/prefer-global/process': 'off',
22
+ 'style/arrow-parens': 'off',
23
+ 'ts/method-signature-style': 'off',
24
+ 'style/brace-style': 'off',
25
+ 'style/jsx-one-expression-per-line': 'off',
26
+ 'test/prefer-lowercase-title': 'off',
27
+ 'perfectionist/sort-exports': 'off',
28
+ 'no-extra-boolean-cast': 'off',
29
+ 'prefer-promise-reject-errors': 'off',
30
+ 'new-cap': 'off',
31
+ 'perfectionist/sort-named-exports': 'off'
16
32
  },
17
33
  },
18
34
  );
@@ -18,14 +18,7 @@ const useImageFx_1 = require("../../../utils/effects/useImageFx");
18
18
  const useElementRect_1 = require("../../../utils/useElementRect");
19
19
  const useLayoutContext_1 = require("../../useLayoutContext");
20
20
  const getStyleFromItemStateAndParams_1 = require("../../../utils/getStyleFromItemStateAndParams");
21
- const baseVariables = `precision mediump float;
22
- uniform sampler2D u_image;
23
- uniform sampler2D u_pattern;
24
- uniform vec2 u_imgDimensions;
25
- uniform vec2 u_patternDimensions;
26
- uniform float u_time;
27
- uniform vec2 u_cursor;
28
- varying vec2 v_texCoord;`;
21
+ const shaderBaseVars_1 = require("./shaderBaseVars");
29
22
  const ImageItem = ({ item, sectionId, onResize, interactionCtrl, onVisibilityChange }) => {
30
23
  var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o;
31
24
  const id = (0, react_1.useId)();
@@ -46,7 +39,7 @@ const ImageItem = ({ item, sectionId, onResize, interactionCtrl, onVisibilityCha
46
39
  return acc;
47
40
  }, {});
48
41
  const layoutValues = [item.area, item.layoutParams];
49
- const fullShaderCode = `${baseVariables}\n${controlsVariables}\n${fragmentShader}`;
42
+ const fullShaderCode = `${shaderBaseVars_1.baseVariables}\n${controlsVariables}\n${fragmentShader}`;
50
43
  const area = layoutId ? item.area[layoutId] : null;
51
44
  const exemplary = (_a = layouts === null || layouts === void 0 ? void 0 : layouts.find(l => l.id === layoutId)) === null || _a === void 0 ? void 0 : _a.exemplary;
52
45
  const width = area && exemplary ? area.width * exemplary : 0;
@@ -80,7 +73,9 @@ const ImageItem = ({ item, sectionId, onResize, interactionCtrl, onVisibilityCha
80
73
  (0, react_1.useEffect)(() => {
81
74
  onVisibilityChange === null || onVisibilityChange === void 0 ? void 0 : onVisibilityChange(isInteractive);
82
75
  }, [isInteractive, onVisibilityChange]);
83
- return ((0, jsx_runtime_1.jsx)(LinkWrapper_1.LinkWrapper, { url: (_l = item.link) === null || _l === void 0 ? void 0 : _l.url, target: (_m = item.link) === null || _m === void 0 ? void 0 : _m.target, 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(Object.assign(Object.assign({}, (opacity !== undefined ? { opacity } : {})), (angle !== undefined ? { transform: `rotate(${angle}deg)` } : {})), (blur !== undefined ? { filter: `blur(${blur * 100}vw)` } : {})), { 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 })) : ((0, jsx_runtime_1.jsx)("img", { alt: "", className: `image image-${item.id}`, style: inlineStyles, src: item.commonParams.url })) }), (0, jsx_runtime_1.jsx)(style_1.default, { id: id, children: `
76
+ return ((0, jsx_runtime_1.jsx)(LinkWrapper_1.LinkWrapper, { url: (_l = item.link) === null || _l === void 0 ? void 0 : _l.url, target: (_m = item.link) === null || _m === void 0 ? void 0 : _m.target, 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(Object.assign(Object.assign({}, (opacity !== undefined ? { opacity } : {})), (angle !== undefined ? { transform: `rotate(${angle}deg)` } : {})), (blur !== undefined ? { filter: `blur(${blur * 100}vw)` } : {})), { transition: (_o = wrapperStateParams === null || wrapperStateParams === void 0 ? void 0 : wrapperStateParams.transition) !== null && _o !== void 0 ? _o : 'none' }), children: hasGLEffect && isFXAllowed
77
+ ? ((0, jsx_runtime_1.jsx)("canvas", { style: inlineStyles, ref: fxCanvas, className: `img-canvas image-${item.id}`, width: rectWidth, height: rectHeight }))
78
+ : ((0, jsx_runtime_1.jsx)("img", { alt: "", className: `image image-${item.id}`, style: inlineStyles, src: item.commonParams.url })) }), (0, jsx_runtime_1.jsx)(style_1.default, { id: id, children: `
84
79
  .image-wrapper-${item.id} {
85
80
  position: absolute;
86
81
  width: 100%;
@@ -17,15 +17,36 @@ const useRegisterResize_1 = require("../../../common/useRegisterResize");
17
17
  const useLayoutContext_1 = require("../../useLayoutContext");
18
18
  const ScrollPlaybackVideo_1 = require("../../ScrollPlaybackVideo");
19
19
  const getStyleFromItemStateAndParams_1 = require("../../../utils/getStyleFromItemStateAndParams");
20
+ const shaderBaseVars_1 = require("./shaderBaseVars");
21
+ const useVideoFx_1 = require("../../../utils/effects/useVideoFx");
22
+ const useElementRect_1 = require("../../../utils/useElementRect");
20
23
  const VideoItem = ({ item, sectionId, onResize, interactionCtrl, onVisibilityChange }) => {
21
- var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l;
24
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p;
22
25
  const id = (0, react_1.useId)();
23
26
  const { layouts } = (0, useCntrlContext_1.useCntrlContext)();
27
+ const layoutId = (0, useLayoutContext_1.useLayoutContext)();
24
28
  const { radius: itemRadius, strokeWidth: itemStrokeWidth, strokeColor: itemStrokeColor, opacity: itemOpacity, blur: itemBlur } = (0, useFileItem_1.useFileItem)(item, sectionId);
25
29
  const itemAngle = (0, useItemAngle_1.useItemAngle)(item, sectionId);
26
30
  const [ref, setRef] = (0, react_1.useState)(null);
27
31
  const videoRef = (0, react_1.useRef)(null);
28
- const layoutId = (0, useLayoutContext_1.useLayoutContext)();
32
+ const fxCanvas = (0, react_1.useRef)(null);
33
+ const { url, hasGLEffect, fragmentShader, FXControls, FXCursor } = item.commonParams;
34
+ const isInitialRef = (0, react_1.useRef)(true);
35
+ const area = layoutId ? item.area[layoutId] : null;
36
+ const exemplary = (_a = layouts === null || layouts === void 0 ? void 0 : layouts.find(l => l.id === layoutId)) === null || _a === void 0 ? void 0 : _a.exemplary;
37
+ const width = area && exemplary ? area.width * exemplary : 0;
38
+ const height = area && exemplary ? area.height * exemplary : 0;
39
+ const controls = FXControls !== null && FXControls !== void 0 ? FXControls : [];
40
+ const controlsVariables = controls.map((c) => `uniform ${c.type} ${c.shaderParam};`)
41
+ .join('\n');
42
+ const controlValues = controls.reduce((acc, control) => {
43
+ acc[control.shaderParam] = control.value;
44
+ return acc;
45
+ }, {});
46
+ const fullShaderCode = `${shaderBaseVars_1.baseVariables}\n${controlsVariables}\n${fragmentShader}`;
47
+ const rect = (0, useElementRect_1.useElementRect)(ref);
48
+ const rectWidth = Math.floor((_b = rect === null || rect === void 0 ? void 0 : rect.width) !== null && _b !== void 0 ? _b : 0);
49
+ const rectHeight = Math.floor((_c = rect === null || rect === void 0 ? void 0 : rect.height) !== null && _c !== void 0 ? _c : 0);
29
50
  const scrollPlayback = layoutId ? item.layoutParams[layoutId].scrollPlayback : null;
30
51
  const layoutValues = [item.area, item.layoutParams];
31
52
  const hasScrollPlayback = scrollPlayback !== null;
@@ -35,19 +56,32 @@ const VideoItem = ({ item, sectionId, onResize, interactionCtrl, onVisibilityCha
35
56
  var _a;
36
57
  const strokeColor = (0, getStyleFromItemStateAndParams_1.getStyleFromItemStateAndParams)((_a = videoStateParams === null || videoStateParams === void 0 ? void 0 : videoStateParams.styles) === null || _a === void 0 ? void 0 : _a.color, itemStrokeColor);
37
58
  return strokeColor ? color_1.CntrlColor.parse(strokeColor) : undefined;
38
- }, [(_a = videoStateParams === null || videoStateParams === void 0 ? void 0 : videoStateParams.styles) === null || _a === void 0 ? void 0 : _a.color, itemStrokeColor]);
39
- const angle = (0, getStyleFromItemStateAndParams_1.getStyleFromItemStateAndParams)((_b = wrapperStateParams === null || wrapperStateParams === void 0 ? void 0 : wrapperStateParams.styles) === null || _b === void 0 ? void 0 : _b.angle, itemAngle);
40
- const opacity = (0, getStyleFromItemStateAndParams_1.getStyleFromItemStateAndParams)((_c = wrapperStateParams === null || wrapperStateParams === void 0 ? void 0 : wrapperStateParams.styles) === null || _c === void 0 ? void 0 : _c.opacity, itemOpacity);
41
- const blur = (0, getStyleFromItemStateAndParams_1.getStyleFromItemStateAndParams)((_d = wrapperStateParams === null || wrapperStateParams === void 0 ? void 0 : wrapperStateParams.styles) === null || _d === void 0 ? void 0 : _d.blur, itemBlur);
42
- const strokeWidth = (0, getStyleFromItemStateAndParams_1.getStyleFromItemStateAndParams)((_e = wrapperStateParams === null || wrapperStateParams === void 0 ? void 0 : wrapperStateParams.styles) === null || _e === void 0 ? void 0 : _e.strokeWidth, itemStrokeWidth);
43
- const radius = (0, getStyleFromItemStateAndParams_1.getStyleFromItemStateAndParams)((_f = wrapperStateParams === null || wrapperStateParams === void 0 ? void 0 : wrapperStateParams.styles) === null || _f === void 0 ? void 0 : _f.radius, itemRadius);
59
+ }, [(_d = videoStateParams === null || videoStateParams === void 0 ? void 0 : videoStateParams.styles) === null || _d === void 0 ? void 0 : _d.color, itemStrokeColor]);
60
+ const angle = (0, getStyleFromItemStateAndParams_1.getStyleFromItemStateAndParams)((_e = wrapperStateParams === null || wrapperStateParams === void 0 ? void 0 : wrapperStateParams.styles) === null || _e === void 0 ? void 0 : _e.angle, itemAngle);
61
+ const opacity = (0, getStyleFromItemStateAndParams_1.getStyleFromItemStateAndParams)((_f = wrapperStateParams === null || wrapperStateParams === void 0 ? void 0 : wrapperStateParams.styles) === null || _f === void 0 ? void 0 : _f.opacity, itemOpacity);
62
+ const blur = (0, getStyleFromItemStateAndParams_1.getStyleFromItemStateAndParams)((_g = wrapperStateParams === null || wrapperStateParams === void 0 ? void 0 : wrapperStateParams.styles) === null || _g === void 0 ? void 0 : _g.blur, itemBlur);
63
+ const strokeWidth = (0, getStyleFromItemStateAndParams_1.getStyleFromItemStateAndParams)((_h = wrapperStateParams === null || wrapperStateParams === void 0 ? void 0 : wrapperStateParams.styles) === null || _h === void 0 ? void 0 : _h.strokeWidth, itemStrokeWidth);
64
+ const radius = (0, getStyleFromItemStateAndParams_1.getStyleFromItemStateAndParams)((_j = wrapperStateParams === null || wrapperStateParams === void 0 ? void 0 : wrapperStateParams.styles) === null || _j === void 0 ? void 0 : _j.radius, itemRadius);
65
+ (0, react_1.useEffect)(() => {
66
+ isInitialRef.current = false;
67
+ }, []);
68
+ const isFXAllowed = (0, useVideoFx_1.useVideoFx)(fxCanvas.current, !!(hasGLEffect && !isInitialRef.current), {
69
+ videoUrl: url,
70
+ fragmentShader: fullShaderCode,
71
+ cursor: FXCursor,
72
+ controls: controlValues
73
+ }, width, height);
44
74
  (0, useRegisterResize_1.useRegisterResize)(ref, onResize);
45
- const inlineStyles = Object.assign(Object.assign(Object.assign(Object.assign({}, (radius !== undefined ? { borderRadius: `${radius * 100}vw` } : {})), (strokeWidth !== undefined ? { borderWidth: `${strokeWidth * 100}vw` } : {})), (borderColor ? { borderColor: `${borderColor.toCss()}` } : {})), { transition: (_g = videoStateParams === null || videoStateParams === void 0 ? void 0 : videoStateParams.transition) !== null && _g !== void 0 ? _g : 'none' });
75
+ const inlineStyles = Object.assign(Object.assign(Object.assign(Object.assign({}, (radius !== undefined ? { borderRadius: `${radius * 100}vw` } : {})), (strokeWidth !== undefined ? { borderWidth: `${strokeWidth * 100}vw` } : {})), (borderColor ? { borderColor: `${borderColor.toCss()}` } : {})), { transition: (_k = videoStateParams === null || videoStateParams === void 0 ? void 0 : videoStateParams.transition) !== null && _k !== void 0 ? _k : 'none' });
46
76
  const isInteractive = opacity !== 0;
47
77
  (0, react_1.useEffect)(() => {
48
78
  onVisibilityChange === null || onVisibilityChange === void 0 ? void 0 : onVisibilityChange(isInteractive);
49
79
  }, [isInteractive, onVisibilityChange]);
50
- return ((0, jsx_runtime_1.jsxs)(LinkWrapper_1.LinkWrapper, { url: (_h = item.link) === null || _h === void 0 ? void 0 : _h.url, target: (_j = item.link) === null || _j === void 0 ? void 0 : _j.target, children: [(0, jsx_runtime_1.jsx)("div", { className: `video-wrapper-${item.id}`, ref: setRef, style: Object.assign(Object.assign(Object.assign(Object.assign({}, (opacity !== undefined ? { opacity } : {})), (angle !== undefined ? { transform: `rotate(${angle}deg)` } : {})), (blur !== undefined ? { filter: `blur(${blur * 100}vw)` } : {})), { transition: (_k = wrapperStateParams === null || wrapperStateParams === void 0 ? void 0 : wrapperStateParams.transition) !== null && _k !== void 0 ? _k : 'none' }), children: hasScrollPlayback ? ((0, jsx_runtime_1.jsx)(ScrollPlaybackVideo_1.ScrollPlaybackVideo, { sectionId: sectionId, src: item.commonParams.url, playbackParams: scrollPlayback, style: inlineStyles, className: `video video-playback-wrapper video-${item.id}` })) : ((0, jsx_runtime_1.jsx)("video", { poster: (_l = item.commonParams.coverUrl) !== null && _l !== void 0 ? _l : '', ref: videoRef, autoPlay: true, muted: true, loop: true, playsInline: true, className: `video video-${item.id}`, style: inlineStyles, children: (0, jsx_runtime_1.jsx)("source", { src: item.commonParams.url }) })) }), (0, jsx_runtime_1.jsx)(style_1.default, { id: id, children: `
80
+ return ((0, jsx_runtime_1.jsxs)(LinkWrapper_1.LinkWrapper, { url: (_l = item.link) === null || _l === void 0 ? void 0 : _l.url, target: (_m = item.link) === null || _m === void 0 ? void 0 : _m.target, children: [(0, jsx_runtime_1.jsx)("div", { className: `video-wrapper-${item.id}`, ref: setRef, style: Object.assign(Object.assign(Object.assign(Object.assign({}, (opacity !== undefined ? { opacity } : {})), (angle !== undefined ? { transform: `rotate(${angle}deg)` } : {})), (blur !== undefined ? { filter: `blur(${blur * 100}vw)` } : {})), { transition: (_o = wrapperStateParams === null || wrapperStateParams === void 0 ? void 0 : wrapperStateParams.transition) !== null && _o !== void 0 ? _o : 'none' }), children: hasScrollPlayback
81
+ ? ((0, jsx_runtime_1.jsx)(ScrollPlaybackVideo_1.ScrollPlaybackVideo, { sectionId: sectionId, src: item.commonParams.url, playbackParams: scrollPlayback, style: inlineStyles, className: `video video-playback-wrapper video-${item.id}` }))
82
+ : ((0, jsx_runtime_1.jsx)(jsx_runtime_1.Fragment, { children: hasGLEffect && isFXAllowed
83
+ ? ((0, jsx_runtime_1.jsx)("canvas", { style: inlineStyles, ref: fxCanvas, className: `video-canvas video-${item.id}`, width: rectWidth, height: rectHeight }))
84
+ : ((0, jsx_runtime_1.jsx)("video", { poster: (_p = item.commonParams.coverUrl) !== null && _p !== void 0 ? _p : '', ref: videoRef, autoPlay: true, muted: true, loop: true, playsInline: true, className: `video video-${item.id}`, style: inlineStyles, children: (0, jsx_runtime_1.jsx)("source", { src: item.commonParams.url }) })) })) }), (0, jsx_runtime_1.jsx)(style_1.default, { id: id, children: `
51
85
  .video-wrapper-${item.id} {
52
86
  position: absolute;
53
87
  overflow: hidden;
@@ -74,6 +108,14 @@ const VideoItem = ({ item, sectionId, onResize, interactionCtrl, onVisibilityCha
74
108
  display: flex;
75
109
  justify-content: center;
76
110
  }
111
+ .video-canvas {
112
+ border: solid;
113
+ width: 100%;
114
+ height: 100%;
115
+ pointer-events: none;
116
+ border-width: 0;
117
+ box-sizing: border-box;
118
+ }
77
119
  ${(0, sdk_1.getLayoutStyles)(layouts, layoutValues, ([area, layoutParams]) => {
78
120
  return (`
79
121
  .video-wrapper-${item.id} {
@@ -0,0 +1,11 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.baseVariables = void 0;
4
+ exports.baseVariables = `precision mediump float;
5
+ uniform sampler2D u_image;
6
+ uniform sampler2D u_pattern;
7
+ uniform vec2 u_imgDimensions;
8
+ uniform vec2 u_patternDimensions;
9
+ uniform float u_time;
10
+ uniform vec2 u_cursor;
11
+ varying vec2 v_texCoord;`;
@@ -9,12 +9,17 @@ const PATTERN_2_URL = 'https://cdn.cntrl.site/client-app-files/bayer16.png';
9
9
  function useImageFx(canvas, enabled, { imageUrl, fragmentShader, cursor, controls }, width, height) {
10
10
  const [isFXAllowed, setIsFXAllowed] = (0, react_1.useState)(true);
11
11
  const mousePos = (0, react_1.useRef)([0.0, 0.0]);
12
+ const imageTextureManager = (0, react_1.useMemo)(() => {
13
+ if (!imageUrl || !enabled)
14
+ return;
15
+ return new effects_1.ImageTextureManager(imageUrl);
16
+ }, [imageUrl, enabled]);
12
17
  const imageFx = (0, react_1.useMemo)(() => {
13
- if (!imageUrl || !cursor || !enabled)
14
- return undefined;
18
+ if (!cursor || !imageTextureManager)
19
+ return;
15
20
  const { type, x, y } = cursor;
16
- return new effects_1.ImageEffect(imageUrl, PATTERN_URL, PATTERN_2_URL, fragmentShader, Object.assign({ time: 0, cursor: type === 'mouse' ? mousePos.current : [x, y] }, controls), width, height);
17
- }, [imageUrl, fragmentShader, width, height, enabled]);
21
+ return new effects_1.MediaEffect(imageTextureManager, PATTERN_URL, PATTERN_2_URL, fragmentShader, Object.assign({ time: 0, cursor: type === 'mouse' ? mousePos.current : [x, y] }, controls), width, height);
22
+ }, [imageTextureManager, fragmentShader, width, height]);
18
23
  (0, react_1.useEffect)(() => {
19
24
  if (!cursor || cursor.type !== 'mouse' || !canvas || !imageFx)
20
25
  return;
@@ -45,7 +50,7 @@ function useImageFx(canvas, enabled, { imageUrl, fragmentShader, cursor, control
45
50
  try {
46
51
  imageFx.render(gl);
47
52
  }
48
- catch (e) {
53
+ catch (_a) {
49
54
  setIsFXAllowed(false);
50
55
  }
51
56
  frame = requestAnimationFrame(renderFrame);
@@ -65,7 +70,7 @@ function useImageFx(canvas, enabled, { imageUrl, fragmentShader, cursor, control
65
70
  try {
66
71
  imageFx.prepare(gl);
67
72
  }
68
- catch (e) {
73
+ catch (_a) {
69
74
  setIsFXAllowed(false);
70
75
  }
71
76
  return () => {
@@ -0,0 +1,93 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.useVideoFx = void 0;
4
+ const react_1 = require("react");
5
+ const effects_1 = require("@cntrl-site/effects");
6
+ const rangeMap_1 = require("../rangeMap");
7
+ const PATTERN_URL = 'https://cdn.cntrl.site/client-app-files/texture2.png';
8
+ const PATTERN_2_URL = 'https://cdn.cntrl.site/client-app-files/bayer16.png';
9
+ function useVideoFx(canvas, enabled, { videoUrl, fragmentShader, cursor, controls }, width, height) {
10
+ const [isFXAllowed, setIsFXAllowed] = (0, react_1.useState)(true);
11
+ const [isReady, setIsReady] = (0, react_1.useState)(false);
12
+ const active = enabled && isReady;
13
+ const mousePos = (0, react_1.useRef)([0.0, 0.0]);
14
+ const videoTextureManager = (0, react_1.useMemo)(() => {
15
+ if (!videoUrl || !enabled)
16
+ return;
17
+ const handlePlayError = () => {
18
+ setIsFXAllowed(false);
19
+ };
20
+ return new effects_1.VideoTextureManager(videoUrl, handlePlayError);
21
+ }, [videoUrl, enabled]);
22
+ const videoFx = (0, react_1.useMemo)(() => {
23
+ if (!cursor || !videoTextureManager)
24
+ return;
25
+ const { type, x, y } = cursor;
26
+ return new effects_1.MediaEffect(videoTextureManager, PATTERN_URL, PATTERN_2_URL, fragmentShader, Object.assign({ time: 0, cursor: type === 'mouse' ? mousePos.current : [x, y] }, controls), width, height);
27
+ }, [videoTextureManager, fragmentShader, width, height]);
28
+ (0, react_1.useEffect)(() => {
29
+ if (!videoTextureManager)
30
+ return;
31
+ videoTextureManager.onReadyStatusChange(setIsReady);
32
+ }, [videoTextureManager]);
33
+ (0, react_1.useEffect)(() => {
34
+ if (!cursor || cursor.type !== 'mouse' || !canvas || !videoFx)
35
+ return;
36
+ const handleMouseMove = (evt) => {
37
+ const rect = canvas.getBoundingClientRect();
38
+ const x = (0, rangeMap_1.rangeMap)(evt.clientX, rect.left, rect.left + rect.width, 0, 1, true);
39
+ const y = (0, rangeMap_1.rangeMap)(evt.clientY, rect.top, rect.top + rect.height, 0, 1, true);
40
+ videoFx.setParam('cursor', [x, y]);
41
+ };
42
+ window.addEventListener('mousemove', handleMouseMove, { capture: true, passive: true });
43
+ return () => {
44
+ window.removeEventListener('mousemove', handleMouseMove);
45
+ };
46
+ }, [canvas, videoFx, cursor === null || cursor === void 0 ? void 0 : cursor.type]);
47
+ (0, react_1.useEffect)(() => {
48
+ const gl = canvas === null || canvas === void 0 ? void 0 : canvas.getContext('webgl2');
49
+ if (!active || !canvas || !gl || !videoFx)
50
+ return;
51
+ gl.enable(gl.BLEND);
52
+ gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
53
+ let running = false;
54
+ let time = 0;
55
+ let frame;
56
+ const renderFrame = () => {
57
+ time += 0.1;
58
+ videoFx.setViewport(Math.floor(canvas.width), Math.floor(canvas.height));
59
+ videoFx.setParam('time', time);
60
+ try {
61
+ videoFx.render(gl);
62
+ }
63
+ catch (_a) {
64
+ setIsFXAllowed(false);
65
+ }
66
+ frame = requestAnimationFrame(renderFrame);
67
+ };
68
+ const observer = new IntersectionObserver(([entry]) => {
69
+ if (entry.isIntersecting && !running) {
70
+ frame = requestAnimationFrame(renderFrame);
71
+ running = true;
72
+ return;
73
+ }
74
+ if (!entry.isIntersecting && running) {
75
+ window.cancelAnimationFrame(frame);
76
+ running = false;
77
+ }
78
+ });
79
+ observer.observe(canvas);
80
+ try {
81
+ videoFx.prepare(gl);
82
+ }
83
+ catch (_a) {
84
+ setIsFXAllowed(false);
85
+ }
86
+ return () => {
87
+ window.cancelAnimationFrame(frame);
88
+ observer.disconnect();
89
+ };
90
+ }, [canvas, videoFx, active]);
91
+ return isFXAllowed;
92
+ }
93
+ exports.useVideoFx = useVideoFx;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cntrl-site/sdk-nextjs",
3
- "version": "1.2.3-alpha.1",
3
+ "version": "1.3.1",
4
4
  "description": "SDK for Next.js",
5
5
  "author": "arsen@momdesign.nyc",
6
6
  "license": "MIT",
@@ -30,8 +30,8 @@
30
30
  "dependencies": {
31
31
  "@antfu/eslint-config": "^3.8.0",
32
32
  "@cntrl-site/color": "^1.0.0",
33
- "@cntrl-site/effects": "^1.2.0",
34
- "@cntrl-site/sdk": "^1.14.0",
33
+ "@cntrl-site/effects": "^1.3.1",
34
+ "@cntrl-site/sdk": "^1.15.0",
35
35
  "@types/vimeo__player": "^2.18.0",
36
36
  "@vimeo/player": "^2.25.0",
37
37
  "html-react-parser": "^3.0.1",
@@ -7,20 +7,12 @@ import { LinkWrapper } from '../LinkWrapper';
7
7
  import { useFileItem } from './useFileItem';
8
8
  import { useItemAngle } from '../useItemAngle';
9
9
  import { useCntrlContext } from '../../../provider/useCntrlContext';
10
- import { useRegisterResize } from "../../../common/useRegisterResize";
10
+ import { useRegisterResize } from '../../../common/useRegisterResize';
11
11
  import { useImageFx } from '../../../utils/effects/useImageFx';
12
12
  import { useElementRect } from '../../../utils/useElementRect';
13
13
  import { useLayoutContext } from '../../useLayoutContext';
14
14
  import { getStyleFromItemStateAndParams } from '../../../utils/getStyleFromItemStateAndParams';
15
-
16
- const baseVariables = `precision mediump float;
17
- uniform sampler2D u_image;
18
- uniform sampler2D u_pattern;
19
- uniform vec2 u_imgDimensions;
20
- uniform vec2 u_patternDimensions;
21
- uniform float u_time;
22
- uniform vec2 u_cursor;
23
- varying vec2 v_texCoord;`;
15
+ import { baseVariables } from './shaderBaseVars';
24
16
 
25
17
  export const ImageItem: FC<ItemProps<TImageItem>> = ({ item, sectionId, onResize, interactionCtrl, onVisibilityChange }) => {
26
18
  const id = useId();
@@ -41,7 +33,7 @@ export const ImageItem: FC<ItemProps<TImageItem>> = ({ item, sectionId, onResize
41
33
  const isInitialRef = useRef(true);
42
34
  const controls = FXControls ?? [];
43
35
  const controlsVariables = controls.map((c) => `uniform ${c.type} ${c.shaderParam};`)
44
- .join('\n');
36
+ .join('\n');
45
37
  const controlValues = controls.reduce<Record<string, ControlValue>>((acc, control) => {
46
38
  acc[control.shaderParam] = control.value;
47
39
  return acc;
@@ -74,7 +66,7 @@ export const ImageItem: FC<ItemProps<TImageItem>> = ({ item, sectionId, onResize
74
66
  const rectWidth = Math.floor(rect?.width ?? 0);
75
67
  const rectHeight = Math.floor(rect?.height ?? 0);
76
68
  const borderColor = useMemo(() => {
77
- const borderColor = getStyleFromItemStateAndParams(imgStateParams?.styles?.strokeColor, itemStrokeColor)
69
+ const borderColor = getStyleFromItemStateAndParams(imgStateParams?.styles?.strokeColor, itemStrokeColor);
78
70
  return borderColor ? CntrlColor.parse(borderColor) : undefined;
79
71
  }, [itemStrokeColor, imgStateParams?.styles?.strokeColor]);
80
72
  const radius = getStyleFromItemStateAndParams(imgStateParams?.styles?.radius, itemRadius);
@@ -105,22 +97,24 @@ export const ImageItem: FC<ItemProps<TImageItem>> = ({ item, sectionId, onResize
105
97
  transition: wrapperStateParams?.transition ?? 'none'
106
98
  }}
107
99
  >
108
- {hasGLEffect && isFXAllowed ? (
109
- <canvas
110
- style={inlineStyles}
111
- ref={fxCanvas}
112
- className={`img-canvas image-${item.id}`}
113
- width={rectWidth}
114
- height={rectHeight}
115
- />
116
- ) : (
117
- <img
118
- alt=""
119
- className={`image image-${item.id}`}
120
- style={inlineStyles}
121
- src={item.commonParams.url}
122
- />
123
- )}
100
+ {hasGLEffect && isFXAllowed
101
+ ? (
102
+ <canvas
103
+ style={inlineStyles}
104
+ ref={fxCanvas}
105
+ className={`img-canvas image-${item.id}`}
106
+ width={rectWidth}
107
+ height={rectHeight}
108
+ />
109
+ )
110
+ : (
111
+ <img
112
+ alt=""
113
+ className={`image image-${item.id}`}
114
+ style={inlineStyles}
115
+ src={item.commonParams.url}
116
+ />
117
+ )}
124
118
  </div>
125
119
  <JSXStyle id={id}>{`
126
120
  .image-wrapper-${item.id} {
@@ -7,14 +7,18 @@ import { LinkWrapper } from '../LinkWrapper';
7
7
  import { useFileItem } from './useFileItem';
8
8
  import { useItemAngle } from '../useItemAngle';
9
9
  import { useCntrlContext } from '../../../provider/useCntrlContext';
10
- import { useRegisterResize } from "../../../common/useRegisterResize";
10
+ import { useRegisterResize } from '../../../common/useRegisterResize';
11
11
  import { useLayoutContext } from '../../useLayoutContext';
12
12
  import { ScrollPlaybackVideo } from '../../ScrollPlaybackVideo';
13
13
  import { getStyleFromItemStateAndParams } from '../../../utils/getStyleFromItemStateAndParams';
14
+ import { baseVariables } from './shaderBaseVars';
15
+ import { useVideoFx } from '../../../utils/effects/useVideoFx';
16
+ import { useElementRect } from '../../../utils/useElementRect';
14
17
 
15
18
  export const VideoItem: FC<ItemProps<TVideoItem>> = ({ item, sectionId, onResize, interactionCtrl, onVisibilityChange }) => {
16
19
  const id = useId();
17
20
  const { layouts } = useCntrlContext();
21
+ const layoutId = useLayoutContext();
18
22
  const {
19
23
  radius: itemRadius,
20
24
  strokeWidth: itemStrokeWidth,
@@ -25,7 +29,24 @@ export const VideoItem: FC<ItemProps<TVideoItem>> = ({ item, sectionId, onResize
25
29
  const itemAngle = useItemAngle(item, sectionId);
26
30
  const [ref, setRef] = useState<HTMLDivElement | null>(null);
27
31
  const videoRef = useRef<HTMLVideoElement | null>(null);
28
- const layoutId = useLayoutContext();
32
+ const fxCanvas = useRef<HTMLCanvasElement | null>(null);
33
+ const { url, hasGLEffect, fragmentShader, FXControls, FXCursor } = item.commonParams;
34
+ const isInitialRef = useRef(true);
35
+ const area = layoutId ? item.area[layoutId] : null;
36
+ const exemplary = layouts?.find(l => l.id === layoutId)?.exemplary;
37
+ const width = area && exemplary ? area.width * exemplary : 0;
38
+ const height = area && exemplary ? area.height * exemplary : 0;
39
+ const controls = FXControls ?? [];
40
+ const controlsVariables = controls.map((c) => `uniform ${c.type} ${c.shaderParam};`)
41
+ .join('\n');
42
+ const controlValues = controls.reduce<Record<string, ControlValue>>((acc, control) => {
43
+ acc[control.shaderParam] = control.value;
44
+ return acc;
45
+ }, {});
46
+ const fullShaderCode = `${baseVariables}\n${controlsVariables}\n${fragmentShader}`;
47
+ const rect = useElementRect(ref);
48
+ const rectWidth = Math.floor(rect?.width ?? 0);
49
+ const rectHeight = Math.floor(rect?.height ?? 0);
29
50
  const scrollPlayback = layoutId ? item.layoutParams[layoutId].scrollPlayback : null;
30
51
  const layoutValues: Record<string, any>[] = [item.area, item.layoutParams];
31
52
  const hasScrollPlayback = scrollPlayback !== null;
@@ -40,10 +61,25 @@ export const VideoItem: FC<ItemProps<TVideoItem>> = ({ item, sectionId, onResize
40
61
  const blur = getStyleFromItemStateAndParams(wrapperStateParams?.styles?.blur, itemBlur);
41
62
  const strokeWidth = getStyleFromItemStateAndParams(wrapperStateParams?.styles?.strokeWidth, itemStrokeWidth);
42
63
  const radius = getStyleFromItemStateAndParams(wrapperStateParams?.styles?.radius, itemRadius);
64
+ useEffect(() => {
65
+ isInitialRef.current = false;
66
+ }, []);
67
+ const isFXAllowed = useVideoFx(
68
+ fxCanvas.current,
69
+ !!(hasGLEffect && !isInitialRef.current),
70
+ {
71
+ videoUrl: url,
72
+ fragmentShader: fullShaderCode,
73
+ cursor: FXCursor,
74
+ controls: controlValues
75
+ },
76
+ width,
77
+ height
78
+ );
43
79
  useRegisterResize(ref, onResize);
44
80
  const inlineStyles = {
45
81
  ...(radius !== undefined ? { borderRadius: `${radius * 100}vw` } : {}),
46
- ...(strokeWidth !== undefined ? { borderWidth: `${strokeWidth * 100}vw` } : {}),
82
+ ...(strokeWidth !== undefined ? { borderWidth: `${strokeWidth * 100}vw` } : {}),
47
83
  ...(borderColor ? { borderColor: `${borderColor.toCss()}` } : {}),
48
84
  transition: videoStateParams?.transition ?? 'none'
49
85
  };
@@ -64,28 +100,45 @@ export const VideoItem: FC<ItemProps<TVideoItem>> = ({ item, sectionId, onResize
64
100
  transition: wrapperStateParams?.transition ?? 'none'
65
101
  }}
66
102
  >
67
- {hasScrollPlayback ? (
68
- <ScrollPlaybackVideo
69
- sectionId={sectionId}
70
- src={item.commonParams.url}
71
- playbackParams={scrollPlayback}
72
- style={inlineStyles}
73
- className={`video video-playback-wrapper video-${item.id}`}
74
- />
75
- ) : (
76
- <video
77
- poster={item.commonParams.coverUrl ?? ''}
78
- ref={videoRef}
79
- autoPlay
80
- muted
81
- loop
82
- playsInline
83
- className={`video video-${item.id}`}
84
- style={inlineStyles}
85
- >
86
- <source src={item.commonParams.url} />
87
- </video>
88
- )}
103
+ {hasScrollPlayback
104
+ ? (
105
+ <ScrollPlaybackVideo
106
+ sectionId={sectionId}
107
+ src={item.commonParams.url}
108
+ playbackParams={scrollPlayback}
109
+ style={inlineStyles}
110
+ className={`video video-playback-wrapper video-${item.id}`}
111
+ />
112
+ )
113
+ : (
114
+ <>
115
+ {hasGLEffect && isFXAllowed
116
+ ? (
117
+ <canvas
118
+ style={inlineStyles}
119
+ ref={fxCanvas}
120
+ className={`video-canvas video-${item.id}`}
121
+ width={rectWidth}
122
+ height={rectHeight}
123
+ />
124
+ )
125
+ : (
126
+ <video
127
+ poster={item.commonParams.coverUrl ?? ''}
128
+ ref={videoRef}
129
+ autoPlay
130
+ muted
131
+ loop
132
+ playsInline
133
+ className={`video video-${item.id}`}
134
+ style={inlineStyles}
135
+ >
136
+ <source src={item.commonParams.url} />
137
+ </video>
138
+ )}
139
+
140
+ </>
141
+ )}
89
142
  </div>
90
143
  <JSXStyle id={id}>{`
91
144
  .video-wrapper-${item.id} {
@@ -114,8 +167,16 @@ export const VideoItem: FC<ItemProps<TVideoItem>> = ({ item, sectionId, onResize
114
167
  display: flex;
115
168
  justify-content: center;
116
169
  }
170
+ .video-canvas {
171
+ border: solid;
172
+ width: 100%;
173
+ height: 100%;
174
+ pointer-events: none;
175
+ border-width: 0;
176
+ box-sizing: border-box;
177
+ }
117
178
  ${getLayoutStyles(layouts, layoutValues, ([area, layoutParams]) => {
118
- return (`
179
+ return (`
119
180
  .video-wrapper-${item.id} {
120
181
  opacity: ${layoutParams.opacity};
121
182
  transform: rotate(${area.angle}deg);
@@ -127,8 +188,10 @@ export const VideoItem: FC<ItemProps<TVideoItem>> = ({ item, sectionId, onResize
127
188
  border-width: ${layoutParams.strokeWidth * 100}vw;
128
189
  }
129
190
  `);
130
- })}
191
+ })}
131
192
  `}</JSXStyle>
132
193
  </LinkWrapper>
133
194
  );
134
195
  };
196
+
197
+ type ControlValue = number | [number, number];
@@ -0,0 +1,8 @@
1
+ export const baseVariables = `precision mediump float;
2
+ uniform sampler2D u_image;
3
+ uniform sampler2D u_pattern;
4
+ uniform vec2 u_imgDimensions;
5
+ uniform vec2 u_patternDimensions;
6
+ uniform float u_time;
7
+ uniform vec2 u_cursor;
8
+ varying vec2 v_texCoord;`;
@@ -1,5 +1,5 @@
1
1
  import { useEffect, useMemo, useRef, useState } from 'react';
2
- import { ImageEffect } from '@cntrl-site/effects';
2
+ import { MediaEffect, ImageTextureManager } from '@cntrl-site/effects';
3
3
  import { rangeMap } from '../rangeMap';
4
4
 
5
5
  export interface FXCursor {
@@ -12,7 +12,6 @@ interface FxParams {
12
12
  imageUrl?: string;
13
13
  fragmentShader: string | null;
14
14
  cursor: FXCursor | null;
15
- // TODO use types from SDk
16
15
  controls?: Record<string, number | [number, number]>;
17
16
  }
18
17
 
@@ -33,11 +32,15 @@ export function useImageFx(
33
32
  ): boolean {
34
33
  const [isFXAllowed, setIsFXAllowed] = useState(true);
35
34
  const mousePos = useRef<[number, number]>([0.0, 0.0]);
36
- const imageFx = useMemo<ImageEffect | undefined>(() => {
37
- if (!imageUrl || !cursor || !enabled) return undefined;
35
+ const imageTextureManager = useMemo(() => {
36
+ if (!imageUrl || !enabled) return;
37
+ return new ImageTextureManager(imageUrl);
38
+ }, [imageUrl, enabled]);
39
+ const imageFx = useMemo<MediaEffect | undefined>(() => {
40
+ if (!cursor || !imageTextureManager) return;
38
41
  const { type, x, y } = cursor;
39
- return new ImageEffect(
40
- imageUrl,
42
+ return new MediaEffect(
43
+ imageTextureManager,
41
44
  PATTERN_URL,
42
45
  PATTERN_2_URL,
43
46
  fragmentShader!,
@@ -49,7 +52,7 @@ export function useImageFx(
49
52
  width,
50
53
  height
51
54
  );
52
- }, [imageUrl, fragmentShader, width, height, enabled]);
55
+ }, [imageTextureManager, fragmentShader, width, height]);
53
56
 
54
57
  useEffect(() => {
55
58
  if (!cursor || cursor.type !== 'mouse' || !canvas || !imageFx) return;
@@ -80,7 +83,7 @@ export function useImageFx(
80
83
  imageFx.setParam('time', time);
81
84
  try {
82
85
  imageFx.render(gl);
83
- } catch (e) {
86
+ } catch {
84
87
  setIsFXAllowed(false);
85
88
  }
86
89
  frame = requestAnimationFrame(renderFrame);
@@ -100,7 +103,7 @@ export function useImageFx(
100
103
  observer.observe(canvas);
101
104
  try {
102
105
  imageFx.prepare(gl);
103
- } catch (e) {
106
+ } catch {
104
107
  setIsFXAllowed(false);
105
108
  }
106
109
 
@@ -0,0 +1,126 @@
1
+ import { useEffect, useMemo, useRef, useState } from 'react';
2
+ import { MediaEffect, VideoTextureManager } from '@cntrl-site/effects';
3
+ import { rangeMap } from '../rangeMap';
4
+
5
+ export interface FXCursor {
6
+ type: 'mouse' | 'manual';
7
+ x: number;
8
+ y: number;
9
+ }
10
+
11
+ interface FxParams {
12
+ videoUrl?: string;
13
+ fragmentShader: string | null;
14
+ cursor: FXCursor | null;
15
+ controls?: Record<string, number | [number, number]>;
16
+ }
17
+
18
+ const PATTERN_URL = 'https://cdn.cntrl.site/client-app-files/texture2.png';
19
+ const PATTERN_2_URL = 'https://cdn.cntrl.site/client-app-files/bayer16.png';
20
+
21
+ export function useVideoFx(
22
+ canvas: HTMLCanvasElement | null | undefined,
23
+ enabled: boolean,
24
+ {
25
+ videoUrl,
26
+ fragmentShader,
27
+ cursor,
28
+ controls
29
+ }: FxParams,
30
+ width: number,
31
+ height: number
32
+ ): boolean {
33
+ const [isFXAllowed, setIsFXAllowed] = useState(true);
34
+ const [isReady, setIsReady] = useState(false);
35
+ const active = enabled && isReady;
36
+ const mousePos = useRef<[number, number]>([0.0, 0.0]);
37
+ const videoTextureManager = useMemo(() => {
38
+ if (!videoUrl || !enabled) return;
39
+ const handlePlayError = () => {
40
+ setIsFXAllowed(false);
41
+ };
42
+ return new VideoTextureManager(videoUrl, handlePlayError);
43
+ }, [videoUrl, enabled]);
44
+ const videoFx = useMemo<MediaEffect | undefined>(() => {
45
+ if (!cursor || !videoTextureManager) return;
46
+ const { type, x, y } = cursor;
47
+ return new MediaEffect(
48
+ videoTextureManager,
49
+ PATTERN_URL,
50
+ PATTERN_2_URL,
51
+ fragmentShader!,
52
+ {
53
+ time: 0,
54
+ cursor: type === 'mouse' ? mousePos.current : [x, y],
55
+ ...controls
56
+ },
57
+ width,
58
+ height
59
+ );
60
+ }, [videoTextureManager, fragmentShader, width, height]);
61
+
62
+ useEffect(() => {
63
+ if (!videoTextureManager) return;
64
+ videoTextureManager.onReadyStatusChange(setIsReady);
65
+ }, [videoTextureManager]);
66
+
67
+ useEffect(() => {
68
+ if (!cursor || cursor.type !== 'mouse' || !canvas || !videoFx) return;
69
+ const handleMouseMove = (evt: MouseEvent) => {
70
+ const rect = canvas.getBoundingClientRect();
71
+ const x = rangeMap(evt.clientX, rect.left, rect.left + rect.width, 0, 1, true);
72
+ const y = rangeMap(evt.clientY, rect.top, rect.top + rect.height, 0, 1, true);
73
+ videoFx.setParam('cursor', [x, y]);
74
+ };
75
+ window.addEventListener('mousemove', handleMouseMove, { capture: true, passive: true });
76
+ return () => {
77
+ window.removeEventListener('mousemove', handleMouseMove);
78
+ };
79
+ }, [canvas, videoFx, cursor?.type]);
80
+
81
+ useEffect(() => {
82
+ const gl = canvas?.getContext('webgl2');
83
+ if (!active || !canvas || !gl || !videoFx) return;
84
+ gl.enable(gl.BLEND);
85
+ gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
86
+ let running = false;
87
+ let time = 0;
88
+ let frame: number;
89
+
90
+ const renderFrame = () => {
91
+ time += 0.1;
92
+ videoFx.setViewport(Math.floor(canvas.width), Math.floor(canvas.height));
93
+ videoFx.setParam('time', time);
94
+ try {
95
+ videoFx.render(gl);
96
+ } catch {
97
+ setIsFXAllowed(false);
98
+ }
99
+ frame = requestAnimationFrame(renderFrame);
100
+ };
101
+
102
+ const observer = new IntersectionObserver(([entry]) => {
103
+ if (entry.isIntersecting && !running) {
104
+ frame = requestAnimationFrame(renderFrame);
105
+ running = true;
106
+ return;
107
+ }
108
+ if (!entry.isIntersecting && running) {
109
+ window.cancelAnimationFrame(frame);
110
+ running = false;
111
+ }
112
+ });
113
+ observer.observe(canvas);
114
+ try {
115
+ videoFx.prepare(gl);
116
+ } catch {
117
+ setIsFXAllowed(false);
118
+ }
119
+
120
+ return () => {
121
+ window.cancelAnimationFrame(frame);
122
+ observer.disconnect();
123
+ };
124
+ }, [canvas, videoFx, active]);
125
+ return isFXAllowed;
126
+ }
Binary file