@cntrl-site/sdk-nextjs 1.8.40 → 1.9.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.
Files changed (134) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +4 -4
  3. package/TODO.md +1 -1
  4. package/cntrl-site-sdk-nextjs-1.8.40.tgz +0 -0
  5. package/eslint.config.mjs +35 -35
  6. package/jest.config.js +6 -6
  7. package/lib/common/useItemFXData.js +7 -7
  8. package/lib/components/Article.js +5 -5
  9. package/lib/components/LayoutStyle.js +4 -4
  10. package/lib/components/Section/Section.js +27 -27
  11. package/lib/components/items/CodeEmbedItem/CodeEmbedItem.js +36 -36
  12. package/lib/components/items/ComponentItem/ComponentItem.js +13 -13
  13. package/lib/components/items/CompoundItem/CompoundChild.js +20 -20
  14. package/lib/components/items/CompoundItem/CompoundItem.js +14 -14
  15. package/lib/components/items/CustomItem/CustomItem.js +9 -9
  16. package/lib/components/items/EmbedVideoItem/VimeoEmbed.js +24 -24
  17. package/lib/components/items/EmbedVideoItem/YoutubeEmbed.js +27 -27
  18. package/lib/components/items/FileItem/ImageItem.js +85 -58
  19. package/lib/components/items/FileItem/VideoItem.js +107 -80
  20. package/lib/components/items/FileItem/useFileItem.js +12 -5
  21. package/lib/components/items/GroupItem/GroupItem.js +14 -14
  22. package/lib/components/items/Item.js +30 -30
  23. package/lib/components/items/RectangleItem/RectangleItem.js +92 -36
  24. package/lib/components/items/RectangleItem/useRectangleItem.js +15 -8
  25. package/lib/components/items/RichTextItem/RichTextItem.js +22 -22
  26. package/lib/interactions/CSSPropertyNameMap.js +18 -18
  27. package/lib/utils/Animator/Animator.js +171 -19
  28. package/lib/utils/RichTextConverter/RichTextConverter.js +21 -21
  29. package/lib/utils/areFillsVisible/areFillsVisible.js +23 -0
  30. package/lib/utils/getFill.js +43 -0
  31. package/package.json +58 -57
  32. package/src/common/useCurrentLayout.ts +52 -52
  33. package/src/common/useExemplary.ts +9 -9
  34. package/src/common/useItemFXData.ts +47 -47
  35. package/src/common/useKeyframeValue.ts +85 -85
  36. package/src/common/useRegisterResize.ts +16 -16
  37. package/src/components/Article.tsx +73 -73
  38. package/src/components/ArticleWrapper.tsx +21 -21
  39. package/src/components/Head.tsx +56 -56
  40. package/src/components/LayoutStyle.tsx +21 -21
  41. package/src/components/Page.tsx +33 -33
  42. package/src/components/ScrollPlaybackVideo.tsx +56 -56
  43. package/src/components/Section/Section.tsx +127 -127
  44. package/src/components/Section/SectionImage.tsx +48 -48
  45. package/src/components/Section/SectionVideo.tsx +144 -144
  46. package/src/components/Section/useSectionColor.ts +19 -19
  47. package/src/components/Section/useSectionHeightMap.ts +19 -19
  48. package/src/components/items/CodeEmbedItem/CodeEmbedItem.tsx +145 -145
  49. package/src/components/items/CodeEmbedItem/useCodeEmbedItem.ts +36 -36
  50. package/src/components/items/ComponentItem/ComponentItem.tsx +67 -67
  51. package/src/components/items/ComponentItem/useComponentItem.ts +35 -35
  52. package/src/components/items/CompoundItem/CompoundChild.tsx +150 -150
  53. package/src/components/items/CompoundItem/CompoundItem.tsx +70 -70
  54. package/src/components/items/CompoundItem/useCompoundItem.ts +21 -21
  55. package/src/components/items/CustomItem/CustomItem.tsx +48 -48
  56. package/src/components/items/EmbedVideoItem/VimeoEmbed.tsx +206 -206
  57. package/src/components/items/EmbedVideoItem/YoutubeEmbed.tsx +193 -193
  58. package/src/components/items/EmbedVideoItem/useEmbedVideoItem.ts +46 -46
  59. package/src/components/items/FileItem/ImageItem.tsx +211 -158
  60. package/src/components/items/FileItem/VideoItem.tsx +328 -275
  61. package/src/components/items/FileItem/useFileItem.ts +81 -73
  62. package/src/components/items/GroupItem/GroupItem.tsx +81 -81
  63. package/src/components/items/GroupItem/useGroupItem.ts +34 -34
  64. package/src/components/items/Item.tsx +251 -251
  65. package/src/components/items/LinkWrapper.tsx +39 -39
  66. package/src/components/items/RectangleItem/RectangleItem.tsx +199 -97
  67. package/src/components/items/RectangleItem/useRectangleItem.ts +89 -82
  68. package/src/components/items/RichTextItem/RichTextItem.tsx +107 -107
  69. package/src/components/items/RichTextItem/useRichTextItem.ts +12 -12
  70. package/src/components/items/RichTextWrapper.tsx +15 -15
  71. package/src/components/items/itemsMap.ts +28 -28
  72. package/src/components/items/useDraggable.ts +252 -252
  73. package/src/components/items/useItemAngle.ts +13 -13
  74. package/src/components/items/useItemArea.ts +35 -35
  75. package/src/components/items/useItemPointerEvents.ts +23 -23
  76. package/src/components/items/useItemScale.ts +17 -17
  77. package/src/components/items/useItemTriggers.ts +15 -15
  78. package/src/components/items/useRichTextItemValues.ts +65 -65
  79. package/src/components/items/useSizing.ts +25 -25
  80. package/src/components/items/useStickyItemTop.ts +21 -21
  81. package/src/components/useLayoutContext.ts +7 -7
  82. package/src/index.ts +24 -24
  83. package/src/interactions/CSSPropertyNameMap.ts +37 -38
  84. package/src/interactions/InteractionsRegistry.ts +380 -380
  85. package/src/interactions/ItemInteractionCtrl.ts +78 -78
  86. package/src/interactions/getTransition.ts +27 -27
  87. package/src/interactions/types.ts +36 -36
  88. package/src/interactions/useItemInteractionCtrl.ts +18 -18
  89. package/src/provider/ArticleRectContext.ts +4 -4
  90. package/src/provider/CntrlContext.ts +5 -5
  91. package/src/provider/CntrlProvider.tsx +11 -11
  92. package/src/provider/CntrlSdkContext.ts +83 -83
  93. package/src/provider/CustomItemRegistry.ts +14 -14
  94. package/src/provider/CustomItemTypes.ts +3 -3
  95. package/src/provider/CustomSectionRegistry.ts +25 -25
  96. package/src/provider/InteractionsContext.tsx +59 -59
  97. package/src/provider/Keyframes.ts +11 -11
  98. package/src/provider/KeyframesContext.ts +5 -5
  99. package/src/provider/LayoutContext.ts +3 -3
  100. package/src/provider/WebGLContextManagerContext.tsx +4 -4
  101. package/src/provider/defaultContext.ts +8 -8
  102. package/src/provider/useCntrlContext.ts +8 -8
  103. package/src/styled-jsx.d.ts +1 -1
  104. package/src/utils/Animator/Animator.ts +571 -371
  105. package/src/utils/ArticleRectManager/ArticleRectObserver.ts +93 -93
  106. package/src/utils/ArticleRectManager/useArticleRectObserver.ts +17 -17
  107. package/src/utils/ArticleRectManager/useSectionRegistry.ts +11 -11
  108. package/src/utils/EventEmitter.ts +41 -41
  109. package/src/utils/RichTextConverter/RichTextConverter.tsx +326 -326
  110. package/src/utils/ScaleAnchorMap.ts +13 -13
  111. package/src/utils/StickyManager/StickyManager.ts +23 -23
  112. package/src/utils/Youtube/YouTubeIframeApiLoader.ts +58 -58
  113. package/src/utils/Youtube/YoutubeIframeApi.ts +101 -101
  114. package/src/utils/Youtube/useYouTubeIframeApi.ts +12 -12
  115. package/src/utils/areFillsVisible/areFillsVisible.test.ts +63 -0
  116. package/src/utils/areFillsVisible/areFillsVisible.ts +37 -0
  117. package/src/utils/binSearchInsertAt.ts +33 -33
  118. package/src/utils/castObject.ts +10 -10
  119. package/src/utils/checkOverflowClipSupport.ts +13 -13
  120. package/src/utils/effects/useImageFx.ts +156 -156
  121. package/src/utils/effects/useVideoFx.ts +158 -158
  122. package/src/utils/getAnchoredItemTop.ts +12 -12
  123. package/src/utils/getCompoundBoundaryStyles.ts +60 -60
  124. package/src/utils/getFill.ts +46 -0
  125. package/src/utils/getFontFamilyValue.ts +3 -3
  126. package/src/utils/getInvertedRanges.ts +43 -43
  127. package/src/utils/getItemTopStyle.ts +14 -14
  128. package/src/utils/getStyleFromItemStateAndParams.ts +8 -8
  129. package/src/utils/getValidYoutubeUrl.ts +29 -29
  130. package/src/utils/isItemType.ts +5 -5
  131. package/src/utils/rangeMap.ts +13 -13
  132. package/src/utils/useElementRect.ts +23 -23
  133. package/src/provider/InteractionsContext.old.tsx +0 -66
  134. package/src/provider/InteractionsContext.test.tsx +0 -97
@@ -1,156 +1,156 @@
1
- import { useContext, useEffect, useMemo, useRef, useState } from 'react';
2
- import { MediaEffect, ImageTextureManager } from '@cntrl-site/effects';
3
- import { rangeMap } from '../rangeMap';
4
- import { WebglContextManagerContext } from '../../provider/WebGLContextManagerContext';
5
-
6
- interface FxParams {
7
- imageUrl?: string;
8
- fragmentShader: string | null;
9
- controlsValues?: Record<string, number | [number, number]>;
10
- }
11
-
12
- const PATTERN_URL = 'https://cdn.cntrl.site/client-app-files/texture2.png';
13
- const PATTERN_2_URL = 'https://cdn.cntrl.site/client-app-files/bayer16.png';
14
-
15
- export function useImageFx(
16
- canvas: HTMLCanvasElement | null | undefined,
17
- enabled: boolean,
18
- {
19
- imageUrl,
20
- fragmentShader,
21
- controlsValues
22
- }: FxParams,
23
- width: number,
24
- height: number
25
- ): boolean {
26
- const webGLContextManager = useContext(WebglContextManagerContext);
27
- const [isFXAllowed, setIsFXAllowed] = useState(true);
28
- const glRef = useRef<WebGL2RenderingContext | null>(null);
29
- const isRenderingRef = useRef(false);
30
- const frameRef = useRef<number>();
31
- const imageTextureManager = useMemo(() => {
32
- if (!imageUrl || !enabled) return;
33
- return new ImageTextureManager(imageUrl);
34
- }, [imageUrl, enabled]);
35
- const imageFx = useMemo<MediaEffect | undefined>(() => {
36
- if (!imageTextureManager) return;
37
- return new MediaEffect(
38
- imageTextureManager,
39
- PATTERN_URL,
40
- PATTERN_2_URL,
41
- fragmentShader!,
42
- {
43
- time: 0,
44
- cursor: [0, 0],
45
- ...controlsValues
46
- },
47
- width,
48
- height
49
- );
50
- }, [imageTextureManager, fragmentShader, width, height]);
51
-
52
- useEffect(() => {
53
- if (!imageFx || !controlsValues) return;
54
- for (const [key, value] of Object.entries(controlsValues)) {
55
- imageFx.setParam(key, value);
56
- }
57
- }, [imageFx, controlsValues]);
58
-
59
- useEffect(() => {
60
- if (!canvas || !imageFx) return;
61
- const handleMouseMove = (evt: MouseEvent) => {
62
- const rect = canvas.getBoundingClientRect();
63
- const x = rangeMap(evt.clientX, rect.left, rect.left + rect.width, 0, 1, true);
64
- const y = rangeMap(evt.clientY, rect.top, rect.top + rect.height, 0, 1, true);
65
- imageFx.setParam('cursor', [x, y]);
66
- };
67
- window.addEventListener('mousemove', handleMouseMove, { capture: true, passive: true });
68
- return () => {
69
- window.removeEventListener('mousemove', handleMouseMove);
70
- };
71
- }, [canvas, imageFx]);
72
-
73
- useEffect(() => {
74
- if (!enabled || !canvas || !imageFx) return;
75
-
76
- let time = 0;
77
-
78
- const startRendering = () => {
79
- if (isRenderingRef.current) return;
80
-
81
- const gl = webGLContextManager.getContext(canvas);
82
- if (!gl) {
83
- return;
84
- }
85
-
86
- glRef.current = gl;
87
- isRenderingRef.current = true;
88
-
89
- imageFx.prepare(gl);
90
-
91
- const renderFrame = () => {
92
- if (!isRenderingRef.current || !glRef.current) return;
93
-
94
- time += 0.1;
95
-
96
- webGLContextManager.updateContextSize(glRef.current, canvas.width, canvas.height);
97
-
98
- imageFx.setViewport(Math.floor(canvas.width), Math.floor(canvas.height));
99
- imageFx.setParam('time', time);
100
- try {
101
- imageFx.render(glRef.current);
102
- } catch {
103
- setIsFXAllowed(false);
104
- }
105
- webGLContextManager.renderToCanvas(glRef.current, canvas);
106
-
107
- frameRef.current = requestAnimationFrame(renderFrame);
108
- };
109
-
110
- frameRef.current = requestAnimationFrame(renderFrame);
111
- };
112
-
113
- const stopRendering = () => {
114
- if (!isRenderingRef.current) return;
115
-
116
- if (frameRef.current) {
117
- cancelAnimationFrame(frameRef.current);
118
- frameRef.current = undefined;
119
- }
120
-
121
- if (glRef.current) {
122
- webGLContextManager.releaseContext(canvas);
123
- glRef.current = null;
124
- }
125
-
126
- const ctx = canvas.getContext('2d');
127
- if (ctx) {
128
- ctx.clearRect(0, 0, canvas.width, canvas.height);
129
- }
130
-
131
- isRenderingRef.current = false;
132
- };
133
-
134
- const observer = new IntersectionObserver(
135
- ([entry]) => {
136
- if (entry.isIntersecting) {
137
- startRendering();
138
- } else {
139
- stopRendering();
140
- }
141
- },
142
- {
143
- threshold: 0,
144
- rootMargin: '75px'
145
- }
146
- );
147
-
148
- observer.observe(canvas);
149
-
150
- return () => {
151
- stopRendering();
152
- observer.disconnect();
153
- };
154
- }, [canvas, imageFx, enabled]);
155
- return isFXAllowed;
156
- }
1
+ import { useContext, useEffect, useMemo, useRef, useState } from 'react';
2
+ import { MediaEffect, ImageTextureManager } from '@cntrl-site/effects';
3
+ import { rangeMap } from '../rangeMap';
4
+ import { WebglContextManagerContext } from '../../provider/WebGLContextManagerContext';
5
+
6
+ interface FxParams {
7
+ imageUrl?: string;
8
+ fragmentShader: string | null;
9
+ controlsValues?: Record<string, number | [number, number]>;
10
+ }
11
+
12
+ const PATTERN_URL = 'https://cdn.cntrl.site/client-app-files/texture2.png';
13
+ const PATTERN_2_URL = 'https://cdn.cntrl.site/client-app-files/bayer16.png';
14
+
15
+ export function useImageFx(
16
+ canvas: HTMLCanvasElement | null | undefined,
17
+ enabled: boolean,
18
+ {
19
+ imageUrl,
20
+ fragmentShader,
21
+ controlsValues
22
+ }: FxParams,
23
+ width: number,
24
+ height: number
25
+ ): boolean {
26
+ const webGLContextManager = useContext(WebglContextManagerContext);
27
+ const [isFXAllowed, setIsFXAllowed] = useState(true);
28
+ const glRef = useRef<WebGL2RenderingContext | null>(null);
29
+ const isRenderingRef = useRef(false);
30
+ const frameRef = useRef<number>();
31
+ const imageTextureManager = useMemo(() => {
32
+ if (!imageUrl || !enabled) return;
33
+ return new ImageTextureManager(imageUrl);
34
+ }, [imageUrl, enabled]);
35
+ const imageFx = useMemo<MediaEffect | undefined>(() => {
36
+ if (!imageTextureManager) return;
37
+ return new MediaEffect(
38
+ imageTextureManager,
39
+ PATTERN_URL,
40
+ PATTERN_2_URL,
41
+ fragmentShader!,
42
+ {
43
+ time: 0,
44
+ cursor: [0, 0],
45
+ ...controlsValues
46
+ },
47
+ width,
48
+ height
49
+ );
50
+ }, [imageTextureManager, fragmentShader, width, height]);
51
+
52
+ useEffect(() => {
53
+ if (!imageFx || !controlsValues) return;
54
+ for (const [key, value] of Object.entries(controlsValues)) {
55
+ imageFx.setParam(key, value);
56
+ }
57
+ }, [imageFx, controlsValues]);
58
+
59
+ useEffect(() => {
60
+ if (!canvas || !imageFx) return;
61
+ const handleMouseMove = (evt: MouseEvent) => {
62
+ const rect = canvas.getBoundingClientRect();
63
+ const x = rangeMap(evt.clientX, rect.left, rect.left + rect.width, 0, 1, true);
64
+ const y = rangeMap(evt.clientY, rect.top, rect.top + rect.height, 0, 1, true);
65
+ imageFx.setParam('cursor', [x, y]);
66
+ };
67
+ window.addEventListener('mousemove', handleMouseMove, { capture: true, passive: true });
68
+ return () => {
69
+ window.removeEventListener('mousemove', handleMouseMove);
70
+ };
71
+ }, [canvas, imageFx]);
72
+
73
+ useEffect(() => {
74
+ if (!enabled || !canvas || !imageFx) return;
75
+
76
+ let time = 0;
77
+
78
+ const startRendering = () => {
79
+ if (isRenderingRef.current) return;
80
+
81
+ const gl = webGLContextManager.getContext(canvas);
82
+ if (!gl) {
83
+ return;
84
+ }
85
+
86
+ glRef.current = gl;
87
+ isRenderingRef.current = true;
88
+
89
+ imageFx.prepare(gl);
90
+
91
+ const renderFrame = () => {
92
+ if (!isRenderingRef.current || !glRef.current) return;
93
+
94
+ time += 0.1;
95
+
96
+ webGLContextManager.updateContextSize(glRef.current, canvas.width, canvas.height);
97
+
98
+ imageFx.setViewport(Math.floor(canvas.width), Math.floor(canvas.height));
99
+ imageFx.setParam('time', time);
100
+ try {
101
+ imageFx.render(glRef.current);
102
+ } catch {
103
+ setIsFXAllowed(false);
104
+ }
105
+ webGLContextManager.renderToCanvas(glRef.current, canvas);
106
+
107
+ frameRef.current = requestAnimationFrame(renderFrame);
108
+ };
109
+
110
+ frameRef.current = requestAnimationFrame(renderFrame);
111
+ };
112
+
113
+ const stopRendering = () => {
114
+ if (!isRenderingRef.current) return;
115
+
116
+ if (frameRef.current) {
117
+ cancelAnimationFrame(frameRef.current);
118
+ frameRef.current = undefined;
119
+ }
120
+
121
+ if (glRef.current) {
122
+ webGLContextManager.releaseContext(canvas);
123
+ glRef.current = null;
124
+ }
125
+
126
+ const ctx = canvas.getContext('2d');
127
+ if (ctx) {
128
+ ctx.clearRect(0, 0, canvas.width, canvas.height);
129
+ }
130
+
131
+ isRenderingRef.current = false;
132
+ };
133
+
134
+ const observer = new IntersectionObserver(
135
+ ([entry]) => {
136
+ if (entry.isIntersecting) {
137
+ startRendering();
138
+ } else {
139
+ stopRendering();
140
+ }
141
+ },
142
+ {
143
+ threshold: 0,
144
+ rootMargin: '75px'
145
+ }
146
+ );
147
+
148
+ observer.observe(canvas);
149
+
150
+ return () => {
151
+ stopRendering();
152
+ observer.disconnect();
153
+ };
154
+ }, [canvas, imageFx, enabled]);
155
+ return isFXAllowed;
156
+ }
@@ -1,158 +1,158 @@
1
- import { useContext, useEffect, useMemo, useRef, useState } from 'react';
2
- import { MediaEffect, VideoTextureManager } from '@cntrl-site/effects';
3
- import { rangeMap } from '../rangeMap';
4
- import { WebglContextManagerContext } from '../../provider/WebGLContextManagerContext';
5
-
6
- interface FxParams {
7
- videoUrl?: string;
8
- fragmentShader: string | null;
9
- controls?: Record<string, number | [number, number]>;
10
- }
11
-
12
- const PATTERN_URL = 'https://cdn.cntrl.site/client-app-files/texture2.png';
13
- const PATTERN_2_URL = 'https://cdn.cntrl.site/client-app-files/bayer16.png';
14
-
15
- export function useVideoFx(
16
- canvas: HTMLCanvasElement | null | undefined,
17
- enabled: boolean,
18
- {
19
- videoUrl,
20
- fragmentShader,
21
- controls
22
- }: FxParams,
23
- width: number,
24
- height: number
25
- ): boolean {
26
- const [isFXAllowed, setIsFXAllowed] = useState(true);
27
- const [isReady, setIsReady] = useState(false);
28
- const active = enabled && isReady;
29
- const webGLContextManager = useContext(WebglContextManagerContext);
30
- const glRef = useRef<WebGL2RenderingContext | null>(null);
31
- const isRenderingRef = useRef(false);
32
- const frameRef = useRef<number>();
33
- const videoTextureManager = useMemo(() => {
34
- if (!videoUrl || !enabled) return;
35
- const handlePlayError = () => {
36
- setIsFXAllowed(false);
37
- };
38
- return new VideoTextureManager(videoUrl, handlePlayError);
39
- }, [videoUrl, enabled]);
40
- const videoFx = useMemo<MediaEffect | undefined>(() => {
41
- if (!videoTextureManager) return;
42
- return new MediaEffect(
43
- videoTextureManager,
44
- PATTERN_URL,
45
- PATTERN_2_URL,
46
- fragmentShader!,
47
- {
48
- time: 0,
49
- cursor: [0, 0],
50
- ...controls
51
- },
52
- width,
53
- height
54
- );
55
- }, [videoTextureManager, fragmentShader, width, height]);
56
-
57
- useEffect(() => {
58
- if (!videoTextureManager) return;
59
- videoTextureManager.onReadyStatusChange(setIsReady);
60
- }, [videoTextureManager]);
61
-
62
- useEffect(() => {
63
- if (!canvas || !videoFx) return;
64
- const handleMouseMove = (evt: MouseEvent) => {
65
- const rect = canvas.getBoundingClientRect();
66
- const x = rangeMap(evt.clientX, rect.left, rect.left + rect.width, 0, 1, true);
67
- const y = rangeMap(evt.clientY, rect.top, rect.top + rect.height, 0, 1, true);
68
- videoFx.setParam('cursor', [x, y]);
69
- };
70
- window.addEventListener('mousemove', handleMouseMove, { capture: true, passive: true });
71
- return () => {
72
- window.removeEventListener('mousemove', handleMouseMove);
73
- };
74
- }, [canvas, videoFx]);
75
-
76
- useEffect(() => {
77
- if (!active || !canvas || !videoFx) return;
78
- let time = 0;
79
-
80
- const startRendering = () => {
81
- if (isRenderingRef.current) return;
82
-
83
- const gl = webGLContextManager.getContext(canvas);
84
- if (!gl) {
85
- return;
86
- }
87
-
88
- glRef.current = gl;
89
- isRenderingRef.current = true;
90
-
91
- videoFx.prepare(gl);
92
-
93
- const renderFrame = () => {
94
- if (!isRenderingRef.current || !glRef.current) return;
95
-
96
- time += 0.1;
97
-
98
- webGLContextManager.updateContextSize(glRef.current, canvas.width, canvas.height);
99
-
100
- videoFx.setViewport(Math.floor(canvas.width), Math.floor(canvas.height));
101
- videoFx.setParam('time', time);
102
- try {
103
- videoFx.render(glRef.current);
104
- } catch {
105
- setIsFXAllowed(false);
106
- }
107
- webGLContextManager.renderToCanvas(glRef.current, canvas);
108
-
109
- frameRef.current = requestAnimationFrame(renderFrame);
110
- };
111
-
112
- frameRef.current = requestAnimationFrame(renderFrame);
113
- };
114
-
115
- const stopRendering = () => {
116
- if (!isRenderingRef.current) return;
117
-
118
- if (frameRef.current) {
119
- cancelAnimationFrame(frameRef.current);
120
- frameRef.current = undefined;
121
- }
122
-
123
- if (glRef.current) {
124
- webGLContextManager.releaseContext(canvas);
125
- glRef.current = null;
126
- }
127
-
128
- const ctx = canvas.getContext('2d');
129
- if (ctx) {
130
- ctx.clearRect(0, 0, canvas.width, canvas.height);
131
- }
132
-
133
- isRenderingRef.current = false;
134
- };
135
-
136
- const observer = new IntersectionObserver(
137
- ([entry]) => {
138
- if (entry.isIntersecting) {
139
- startRendering();
140
- } else {
141
- stopRendering();
142
- }
143
- },
144
- {
145
- threshold: 0,
146
- rootMargin: '75px'
147
- }
148
- );
149
-
150
- observer.observe(canvas);
151
-
152
- return () => {
153
- stopRendering();
154
- observer.disconnect();
155
- };
156
- }, [canvas, videoFx, active]);
157
- return isFXAllowed;
158
- }
1
+ import { useContext, useEffect, useMemo, useRef, useState } from 'react';
2
+ import { MediaEffect, VideoTextureManager } from '@cntrl-site/effects';
3
+ import { rangeMap } from '../rangeMap';
4
+ import { WebglContextManagerContext } from '../../provider/WebGLContextManagerContext';
5
+
6
+ interface FxParams {
7
+ videoUrl?: string;
8
+ fragmentShader: string | null;
9
+ controls?: Record<string, number | [number, number]>;
10
+ }
11
+
12
+ const PATTERN_URL = 'https://cdn.cntrl.site/client-app-files/texture2.png';
13
+ const PATTERN_2_URL = 'https://cdn.cntrl.site/client-app-files/bayer16.png';
14
+
15
+ export function useVideoFx(
16
+ canvas: HTMLCanvasElement | null | undefined,
17
+ enabled: boolean,
18
+ {
19
+ videoUrl,
20
+ fragmentShader,
21
+ controls
22
+ }: FxParams,
23
+ width: number,
24
+ height: number
25
+ ): boolean {
26
+ const [isFXAllowed, setIsFXAllowed] = useState(true);
27
+ const [isReady, setIsReady] = useState(false);
28
+ const active = enabled && isReady;
29
+ const webGLContextManager = useContext(WebglContextManagerContext);
30
+ const glRef = useRef<WebGL2RenderingContext | null>(null);
31
+ const isRenderingRef = useRef(false);
32
+ const frameRef = useRef<number>();
33
+ const videoTextureManager = useMemo(() => {
34
+ if (!videoUrl || !enabled) return;
35
+ const handlePlayError = () => {
36
+ setIsFXAllowed(false);
37
+ };
38
+ return new VideoTextureManager(videoUrl, handlePlayError);
39
+ }, [videoUrl, enabled]);
40
+ const videoFx = useMemo<MediaEffect | undefined>(() => {
41
+ if (!videoTextureManager) return;
42
+ return new MediaEffect(
43
+ videoTextureManager,
44
+ PATTERN_URL,
45
+ PATTERN_2_URL,
46
+ fragmentShader!,
47
+ {
48
+ time: 0,
49
+ cursor: [0, 0],
50
+ ...controls
51
+ },
52
+ width,
53
+ height
54
+ );
55
+ }, [videoTextureManager, fragmentShader, width, height]);
56
+
57
+ useEffect(() => {
58
+ if (!videoTextureManager) return;
59
+ videoTextureManager.onReadyStatusChange(setIsReady);
60
+ }, [videoTextureManager]);
61
+
62
+ useEffect(() => {
63
+ if (!canvas || !videoFx) return;
64
+ const handleMouseMove = (evt: MouseEvent) => {
65
+ const rect = canvas.getBoundingClientRect();
66
+ const x = rangeMap(evt.clientX, rect.left, rect.left + rect.width, 0, 1, true);
67
+ const y = rangeMap(evt.clientY, rect.top, rect.top + rect.height, 0, 1, true);
68
+ videoFx.setParam('cursor', [x, y]);
69
+ };
70
+ window.addEventListener('mousemove', handleMouseMove, { capture: true, passive: true });
71
+ return () => {
72
+ window.removeEventListener('mousemove', handleMouseMove);
73
+ };
74
+ }, [canvas, videoFx]);
75
+
76
+ useEffect(() => {
77
+ if (!active || !canvas || !videoFx) return;
78
+ let time = 0;
79
+
80
+ const startRendering = () => {
81
+ if (isRenderingRef.current) return;
82
+
83
+ const gl = webGLContextManager.getContext(canvas);
84
+ if (!gl) {
85
+ return;
86
+ }
87
+
88
+ glRef.current = gl;
89
+ isRenderingRef.current = true;
90
+
91
+ videoFx.prepare(gl);
92
+
93
+ const renderFrame = () => {
94
+ if (!isRenderingRef.current || !glRef.current) return;
95
+
96
+ time += 0.1;
97
+
98
+ webGLContextManager.updateContextSize(glRef.current, canvas.width, canvas.height);
99
+
100
+ videoFx.setViewport(Math.floor(canvas.width), Math.floor(canvas.height));
101
+ videoFx.setParam('time', time);
102
+ try {
103
+ videoFx.render(glRef.current);
104
+ } catch {
105
+ setIsFXAllowed(false);
106
+ }
107
+ webGLContextManager.renderToCanvas(glRef.current, canvas);
108
+
109
+ frameRef.current = requestAnimationFrame(renderFrame);
110
+ };
111
+
112
+ frameRef.current = requestAnimationFrame(renderFrame);
113
+ };
114
+
115
+ const stopRendering = () => {
116
+ if (!isRenderingRef.current) return;
117
+
118
+ if (frameRef.current) {
119
+ cancelAnimationFrame(frameRef.current);
120
+ frameRef.current = undefined;
121
+ }
122
+
123
+ if (glRef.current) {
124
+ webGLContextManager.releaseContext(canvas);
125
+ glRef.current = null;
126
+ }
127
+
128
+ const ctx = canvas.getContext('2d');
129
+ if (ctx) {
130
+ ctx.clearRect(0, 0, canvas.width, canvas.height);
131
+ }
132
+
133
+ isRenderingRef.current = false;
134
+ };
135
+
136
+ const observer = new IntersectionObserver(
137
+ ([entry]) => {
138
+ if (entry.isIntersecting) {
139
+ startRendering();
140
+ } else {
141
+ stopRendering();
142
+ }
143
+ },
144
+ {
145
+ threshold: 0,
146
+ rootMargin: '75px'
147
+ }
148
+ );
149
+
150
+ observer.observe(canvas);
151
+
152
+ return () => {
153
+ stopRendering();
154
+ observer.disconnect();
155
+ };
156
+ }, [canvas, videoFx, active]);
157
+ return isFXAllowed;
158
+ }
@@ -1,12 +1,12 @@
1
- import { AnchorSide } from '@cntrl-site/sdk';
2
-
3
- export function getAnchoredItemTop(top: number, sectionHeight: string, anchorSide?: AnchorSide) {
4
- const styleTop = `${top * 100}vw`;
5
- switch (anchorSide) {
6
- case AnchorSide.Center: return `calc(${styleTop} + ${sectionHeight} / 2)`;
7
- case AnchorSide.Bottom: return `calc(${styleTop} + ${sectionHeight})`;
8
- case AnchorSide.Top:
9
- default:
10
- return styleTop;
11
- }
12
- }
1
+ import { AnchorSide } from '@cntrl-site/sdk';
2
+
3
+ export function getAnchoredItemTop(top: number, sectionHeight: string, anchorSide?: AnchorSide) {
4
+ const styleTop = `${top * 100}vw`;
5
+ switch (anchorSide) {
6
+ case AnchorSide.Center: return `calc(${styleTop} + ${sectionHeight} / 2)`;
7
+ case AnchorSide.Bottom: return `calc(${styleTop} + ${sectionHeight})`;
8
+ case AnchorSide.Top:
9
+ default:
10
+ return styleTop;
11
+ }
12
+ }