@cntrl-site/sdk-nextjs 0.19.3 → 0.20.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.idea/codeStyles/codeStyleConfig.xml +5 -0
- package/.idea/inspectionProfiles/Project_Default.xml +15 -0
- package/cntrl-site-sdk-nextjs-0.19.4.tgz +0 -0
- package/lib/components/LinkWrapper.js +2 -2
- package/lib/components/items/ImageItem.js +28 -7
- package/lib/components/items/RectangleItem.js +2 -2
- package/lib/components/items/VideoItem.js +2 -2
- package/lib/components/items/VimeoEmbed.js +2 -2
- package/lib/components/items/YoutubeEmbed.js +2 -2
- package/lib/utils/Animator/Animator.js +15 -22
- package/lib/utils/RichTextConverter/RichTextConverter.js +3 -3
- package/lib/utils/effects/useImageFx.js +61 -0
- package/lib/utils/rangeMap.js +12 -0
- package/lib/utils/useElementRect.js +25 -0
- package/package.json +3 -2
- package/src/components/LinkWrapper.tsx +3 -2
- package/src/components/items/ImageItem.tsx +44 -11
- package/src/components/items/RectangleItem.tsx +1 -1
- package/src/components/items/VideoItem.tsx +1 -1
- package/src/components/items/VimeoEmbed.tsx +1 -1
- package/src/components/items/YoutubeEmbed.tsx +1 -1
- package/src/utils/Animator/Animator.ts +1 -14
- package/src/utils/RichTextConverter/RichTextConverter.tsx +4 -3
- package/src/utils/effects/useImageFx.ts +72 -0
- package/src/utils/rangeMap.ts +13 -0
- package/src/utils/useElementRect.ts +23 -0
- package/cntrl-site-sdk-nextjs-0.19.3.tgz +0 -0
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
<component name="InspectionProjectProfileManager">
|
|
2
|
+
<profile version="1.0">
|
|
3
|
+
<option name="myName" value="Project Default" />
|
|
4
|
+
<inspection_tool class="HtmlUnknownAttribute" enabled="true" level="WARNING" enabled_by_default="true">
|
|
5
|
+
<option name="myValues">
|
|
6
|
+
<value>
|
|
7
|
+
<list size="1">
|
|
8
|
+
<item index="0" class="java.lang.String" itemvalue="jsx" />
|
|
9
|
+
</list>
|
|
10
|
+
</value>
|
|
11
|
+
</option>
|
|
12
|
+
<option name="myCustomValuesEnabled" value="true" />
|
|
13
|
+
</inspection_tool>
|
|
14
|
+
</profile>
|
|
15
|
+
</component>
|
|
Binary file
|
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.LinkWrapper = void 0;
|
|
4
4
|
const jsx_runtime_1 = require("react/jsx-runtime");
|
|
5
|
-
const LinkWrapper = ({ url, children }) => {
|
|
5
|
+
const LinkWrapper = ({ url, children, target }) => {
|
|
6
6
|
const validUrl = url && buildValidUrl(url);
|
|
7
|
-
return url ? ((0, jsx_runtime_1.jsx)("a", Object.assign({ href: validUrl, target:
|
|
7
|
+
return url ? ((0, jsx_runtime_1.jsx)("a", Object.assign({ href: validUrl, target: target, rel: "noreferrer" }, { children: children }))) : ((0, jsx_runtime_1.jsx)(jsx_runtime_1.Fragment, { children: children }));
|
|
8
8
|
};
|
|
9
9
|
exports.LinkWrapper = LinkWrapper;
|
|
10
10
|
function buildValidUrl(url) {
|
|
@@ -15,8 +15,10 @@ const useItemAngle_1 = require("../useItemAngle");
|
|
|
15
15
|
const useCntrlContext_1 = require("../../provider/useCntrlContext");
|
|
16
16
|
const HoverStyles_1 = require("../../utils/HoverStyles/HoverStyles");
|
|
17
17
|
const useRegisterResize_1 = require("../../common/useRegisterResize");
|
|
18
|
+
const useImageFx_1 = require("../../utils/effects/useImageFx");
|
|
19
|
+
const useElementRect_1 = require("../../utils/useElementRect");
|
|
18
20
|
const ImageItem = ({ item, sectionId, onResize }) => {
|
|
19
|
-
var _a;
|
|
21
|
+
var _a, _b, _c, _d;
|
|
20
22
|
const id = (0, react_1.useId)();
|
|
21
23
|
const { layouts } = (0, useCntrlContext_1.useCntrlContext)();
|
|
22
24
|
const { radius, strokeWidth, opacity, strokeColor, blur } = (0, useFileItem_1.useFileItem)(item, sectionId);
|
|
@@ -24,15 +26,25 @@ const ImageItem = ({ item, sectionId, onResize }) => {
|
|
|
24
26
|
const borderColor = (0, react_1.useMemo)(() => color_1.CntrlColor.parse(strokeColor), [strokeColor]);
|
|
25
27
|
const [ref, setRef] = (0, react_1.useState)(null);
|
|
26
28
|
(0, useRegisterResize_1.useRegisterResize)(ref, onResize);
|
|
27
|
-
|
|
29
|
+
const { url, hasGLEffect, fragmentShader } = item.commonParams;
|
|
30
|
+
const fxCanvas = (0, react_1.useRef)(null);
|
|
31
|
+
(0, useImageFx_1.useImageFx)(fxCanvas.current, hasGLEffect !== null && hasGLEffect !== void 0 ? hasGLEffect : false, {
|
|
32
|
+
imageUrl: url,
|
|
33
|
+
fragmentShader
|
|
34
|
+
});
|
|
35
|
+
const rect = (0, useElementRect_1.useElementRect)(ref);
|
|
36
|
+
const rectWidth = Math.floor((_a = rect === null || rect === void 0 ? void 0 : rect.width) !== null && _a !== void 0 ? _a : 0);
|
|
37
|
+
const rectHeight = Math.floor((_b = rect === null || rect === void 0 ? void 0 : rect.height) !== null && _b !== void 0 ? _b : 0);
|
|
38
|
+
const inlineStyles = {
|
|
39
|
+
borderRadius: `${radius * 100}vw`,
|
|
40
|
+
borderWidth: `${strokeWidth * 100}vw`,
|
|
41
|
+
borderColor: `${borderColor.toCss()}`
|
|
42
|
+
};
|
|
43
|
+
return ((0, jsx_runtime_1.jsx)(LinkWrapper_1.LinkWrapper, Object.assign({ url: (_c = item.link) === null || _c === void 0 ? void 0 : _c.url, target: (_d = item.link) === null || _d === void 0 ? void 0 : _d.target }, { children: (0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)("div", Object.assign({ className: `image-wrapper-${item.id}`, ref: setRef, style: {
|
|
28
44
|
opacity: `${opacity}`,
|
|
29
45
|
transform: `rotate(${angle}deg)`,
|
|
30
46
|
filter: `blur(${blur * 100}vw)`
|
|
31
|
-
} }, { children: (0, jsx_runtime_1.jsx)("img", { className: `image image-${item.id}`, style: {
|
|
32
|
-
borderRadius: `${radius * 100}vw`,
|
|
33
|
-
borderWidth: `${strokeWidth * 100}vw`,
|
|
34
|
-
borderColor: `${borderColor.toCss()}`
|
|
35
|
-
}, src: item.commonParams.url }) })), (0, jsx_runtime_1.jsx)(style_1.default, Object.assign({ id: id }, { children: `
|
|
47
|
+
} }, { children: item.commonParams.hasGLEffect ? ((0, jsx_runtime_1.jsx)("canvas", { style: inlineStyles, ref: fxCanvas, className: "img-canvas", 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, Object.assign({ id: id }, { children: `
|
|
36
48
|
@supports not (color: oklch(42% 0.3 90 / 1)) {
|
|
37
49
|
.image-${item.id} {
|
|
38
50
|
border-color: ${borderColor.fmt('rgba')};
|
|
@@ -53,6 +65,15 @@ const ImageItem = ({ item, sectionId, onResize }) => {
|
|
|
53
65
|
pointer-events: none;
|
|
54
66
|
border-style: solid;
|
|
55
67
|
overflow: hidden;
|
|
68
|
+
box-sizing: border-box;
|
|
69
|
+
}
|
|
70
|
+
.img-canvas {
|
|
71
|
+
border: solid;
|
|
72
|
+
width: 100%;
|
|
73
|
+
height: 100%;
|
|
74
|
+
pointer-events: none;
|
|
75
|
+
border-width: 0;
|
|
76
|
+
box-sizing: border-box;
|
|
56
77
|
}
|
|
57
78
|
${(0, sdk_1.getLayoutStyles)(layouts, [item.state.hover], ([hoverParams]) => {
|
|
58
79
|
return (`
|
|
@@ -16,7 +16,7 @@ const useCntrlContext_1 = require("../../provider/useCntrlContext");
|
|
|
16
16
|
const HoverStyles_1 = require("../../utils/HoverStyles/HoverStyles");
|
|
17
17
|
const useRegisterResize_1 = require("../../common/useRegisterResize");
|
|
18
18
|
const RectangleItem = ({ item, sectionId, onResize }) => {
|
|
19
|
-
var _a;
|
|
19
|
+
var _a, _b;
|
|
20
20
|
const id = (0, react_1.useId)();
|
|
21
21
|
const { layouts } = (0, useCntrlContext_1.useCntrlContext)();
|
|
22
22
|
const { fillColor, radius, strokeWidth, strokeColor, blur, backdropBlur } = (0, useRectangleItem_1.useRectangleItem)(item, sectionId);
|
|
@@ -26,7 +26,7 @@ const RectangleItem = ({ item, sectionId, onResize }) => {
|
|
|
26
26
|
const [ref, setRef] = (0, react_1.useState)(null);
|
|
27
27
|
(0, useRegisterResize_1.useRegisterResize)(ref, onResize);
|
|
28
28
|
const backdropFilterValue = backdropBlur !== 0 ? `blur(${backdropBlur * 100}vw)` : 'unset';
|
|
29
|
-
return ((0, jsx_runtime_1.jsx)(LinkWrapper_1.LinkWrapper, Object.assign({ url: (_a = item.link) === null || _a === void 0 ? void 0 : _a.url }, { children: (0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)("div", { className: `rectangle-${item.id}`, ref: setRef, style: {
|
|
29
|
+
return ((0, jsx_runtime_1.jsx)(LinkWrapper_1.LinkWrapper, Object.assign({ url: (_a = item.link) === null || _a === void 0 ? void 0 : _a.url, target: (_b = item.link) === null || _b === void 0 ? void 0 : _b.target }, { children: (0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)("div", { className: `rectangle-${item.id}`, ref: setRef, style: {
|
|
30
30
|
backgroundColor: `${backgroundColor.toCss()}`,
|
|
31
31
|
borderRadius: `${radius * 100}vw`,
|
|
32
32
|
borderWidth: `${strokeWidth * 100}vw`,
|
|
@@ -16,7 +16,7 @@ const useCntrlContext_1 = require("../../provider/useCntrlContext");
|
|
|
16
16
|
const HoverStyles_1 = require("../../utils/HoverStyles/HoverStyles");
|
|
17
17
|
const useRegisterResize_1 = require("../../common/useRegisterResize");
|
|
18
18
|
const VideoItem = ({ item, sectionId, onResize }) => {
|
|
19
|
-
var _a;
|
|
19
|
+
var _a, _b;
|
|
20
20
|
const id = (0, react_1.useId)();
|
|
21
21
|
const { layouts } = (0, useCntrlContext_1.useCntrlContext)();
|
|
22
22
|
const { radius, strokeWidth, strokeColor, opacity, blur } = (0, useFileItem_1.useFileItem)(item, sectionId);
|
|
@@ -24,7 +24,7 @@ const VideoItem = ({ item, sectionId, onResize }) => {
|
|
|
24
24
|
const borderColor = (0, react_1.useMemo)(() => color_1.CntrlColor.parse(strokeColor), [strokeColor]);
|
|
25
25
|
const [ref, setRef] = (0, react_1.useState)(null);
|
|
26
26
|
(0, useRegisterResize_1.useRegisterResize)(ref, onResize);
|
|
27
|
-
return ((0, jsx_runtime_1.jsxs)(LinkWrapper_1.LinkWrapper, Object.assign({ url: (_a = item.link) === null || _a === void 0 ? void 0 : _a.url }, { children: [(0, jsx_runtime_1.jsx)("div", Object.assign({ className: `video-wrapper-${item.id}`, ref: setRef, style: {
|
|
27
|
+
return ((0, jsx_runtime_1.jsxs)(LinkWrapper_1.LinkWrapper, Object.assign({ url: (_a = item.link) === null || _a === void 0 ? void 0 : _a.url, target: (_b = item.link) === null || _b === void 0 ? void 0 : _b.target }, { children: [(0, jsx_runtime_1.jsx)("div", Object.assign({ className: `video-wrapper-${item.id}`, ref: setRef, style: {
|
|
28
28
|
opacity: `${opacity}`,
|
|
29
29
|
transform: `rotate(${angle}deg)`,
|
|
30
30
|
filter: `blur(${blur * 100}vw)`
|
|
@@ -16,7 +16,7 @@ const useCntrlContext_1 = require("../../provider/useCntrlContext");
|
|
|
16
16
|
const HoverStyles_1 = require("../../utils/HoverStyles/HoverStyles");
|
|
17
17
|
const useRegisterResize_1 = require("../../common/useRegisterResize");
|
|
18
18
|
const VimeoEmbedItem = ({ item, sectionId, onResize }) => {
|
|
19
|
-
var _a;
|
|
19
|
+
var _a, _b;
|
|
20
20
|
const id = (0, react_1.useId)();
|
|
21
21
|
const { layouts } = (0, useCntrlContext_1.useCntrlContext)();
|
|
22
22
|
const { radius, blur } = (0, useEmbedVideoItem_1.useEmbedVideoItem)(item, sectionId);
|
|
@@ -40,7 +40,7 @@ const VimeoEmbedItem = ({ item, sectionId, onResize }) => {
|
|
|
40
40
|
return validURL.href;
|
|
41
41
|
};
|
|
42
42
|
const validUrl = getValidVimeoUrl(url);
|
|
43
|
-
return ((0, jsx_runtime_1.jsxs)(LinkWrapper_1.LinkWrapper, Object.assign({ url: (_a = item.link) === null || _a === void 0 ? void 0 : _a.url }, { children: [(0, jsx_runtime_1.jsx)("div", Object.assign({ className: `embed-video-wrapper-${item.id}`, ref: setRef, style: {
|
|
43
|
+
return ((0, jsx_runtime_1.jsxs)(LinkWrapper_1.LinkWrapper, Object.assign({ url: (_a = item.link) === null || _a === void 0 ? void 0 : _a.url, target: (_b = item.link) === null || _b === void 0 ? void 0 : _b.target }, { children: [(0, jsx_runtime_1.jsx)("div", Object.assign({ className: `embed-video-wrapper-${item.id}`, ref: setRef, style: {
|
|
44
44
|
transform: `rotate(${angle}deg)`,
|
|
45
45
|
filter: `blur(${blur * 100}vw)`
|
|
46
46
|
}, onMouseEnter: () => {
|
|
@@ -17,7 +17,7 @@ const useCntrlContext_1 = require("../../provider/useCntrlContext");
|
|
|
17
17
|
const useYouTubeIframeApi_1 = require("../../utils/Youtube/useYouTubeIframeApi");
|
|
18
18
|
const useRegisterResize_1 = require("../../common/useRegisterResize");
|
|
19
19
|
const YoutubeEmbedItem = ({ item, sectionId, onResize }) => {
|
|
20
|
-
var _a;
|
|
20
|
+
var _a, _b;
|
|
21
21
|
const id = (0, react_1.useId)();
|
|
22
22
|
const { layouts } = (0, useCntrlContext_1.useCntrlContext)();
|
|
23
23
|
const { play, controls, url } = item.commonParams;
|
|
@@ -56,7 +56,7 @@ const YoutubeEmbedItem = ({ item, sectionId, onResize }) => {
|
|
|
56
56
|
(_a = placeholder.parentElement) === null || _a === void 0 ? void 0 : _a.removeChild(placeholder);
|
|
57
57
|
};
|
|
58
58
|
}, [YT, div]);
|
|
59
|
-
return ((0, jsx_runtime_1.jsxs)(LinkWrapper_1.LinkWrapper, Object.assign({ url: (_a = item.link) === null || _a === void 0 ? void 0 : _a.url }, { children: [(0, jsx_runtime_1.jsx)("div", Object.assign({ className: `embed-youtube-video-wrapper-${item.id}`, onMouseEnter: () => {
|
|
59
|
+
return ((0, jsx_runtime_1.jsxs)(LinkWrapper_1.LinkWrapper, Object.assign({ url: (_a = item.link) === null || _a === void 0 ? void 0 : _a.url, target: (_b = item.link) === null || _b === void 0 ? void 0 : _b.target }, { children: [(0, jsx_runtime_1.jsx)("div", Object.assign({ className: `embed-youtube-video-wrapper-${item.id}`, onMouseEnter: () => {
|
|
60
60
|
if (!player || play !== 'on-hover')
|
|
61
61
|
return;
|
|
62
62
|
player.playVideo();
|
|
@@ -4,6 +4,7 @@ exports.Animator = void 0;
|
|
|
4
4
|
const sdk_1 = require("@cntrl-site/sdk");
|
|
5
5
|
const color_1 = require("@cntrl-site/color");
|
|
6
6
|
const binSearchInsertAt_1 = require("../binSearchInsertAt");
|
|
7
|
+
const rangeMap_1 = require("../rangeMap");
|
|
7
8
|
const compare = (lhs, rhs) => lhs.position - rhs.position;
|
|
8
9
|
const insertBin = (0, binSearchInsertAt_1.createInsert)(binSearchInsertAt_1.binSearchInsertAt, compare);
|
|
9
10
|
class Animator {
|
|
@@ -28,8 +29,8 @@ class Animator {
|
|
|
28
29
|
}
|
|
29
30
|
const { start, end } = this.getStartEnd(pos, keyframes);
|
|
30
31
|
return {
|
|
31
|
-
width: rangeMap(pos, start.position, end.position, start.value.width, end.value.width, true),
|
|
32
|
-
height: rangeMap(pos, start.position, end.position, start.value.height, end.value.height, true)
|
|
32
|
+
width: (0, rangeMap_1.rangeMap)(pos, start.position, end.position, start.value.width, end.value.width, true),
|
|
33
|
+
height: (0, rangeMap_1.rangeMap)(pos, start.position, end.position, start.value.height, end.value.height, true)
|
|
33
34
|
};
|
|
34
35
|
}
|
|
35
36
|
getPositions(values, pos) {
|
|
@@ -45,8 +46,8 @@ class Animator {
|
|
|
45
46
|
}
|
|
46
47
|
const { start, end } = this.getStartEnd(pos, keyframes);
|
|
47
48
|
return {
|
|
48
|
-
left: rangeMap(pos, start.position, end.position, start.value.left, end.value.left, true),
|
|
49
|
-
top: rangeMap(pos, start.position, end.position, start.value.top, end.value.top, true)
|
|
49
|
+
left: (0, rangeMap_1.rangeMap)(pos, start.position, end.position, start.value.left, end.value.left, true),
|
|
50
|
+
top: (0, rangeMap_1.rangeMap)(pos, start.position, end.position, start.value.top, end.value.top, true)
|
|
50
51
|
};
|
|
51
52
|
}
|
|
52
53
|
getColor(values, pos) {
|
|
@@ -91,7 +92,7 @@ class Animator {
|
|
|
91
92
|
}
|
|
92
93
|
const { start, end } = this.getStartEnd(pos, keyframes);
|
|
93
94
|
return {
|
|
94
|
-
radius: rangeMap(pos, start.position, end.position, start.value.radius, end.value.radius, true)
|
|
95
|
+
radius: (0, rangeMap_1.rangeMap)(pos, start.position, end.position, start.value.radius, end.value.radius, true)
|
|
95
96
|
};
|
|
96
97
|
}
|
|
97
98
|
getBorderWidth(values, pos) {
|
|
@@ -106,7 +107,7 @@ class Animator {
|
|
|
106
107
|
}
|
|
107
108
|
const { start, end } = this.getStartEnd(pos, keyframes);
|
|
108
109
|
return {
|
|
109
|
-
borderWidth: rangeMap(pos, start.position, end.position, start.value.borderWidth, end.value.borderWidth, true)
|
|
110
|
+
borderWidth: (0, rangeMap_1.rangeMap)(pos, start.position, end.position, start.value.borderWidth, end.value.borderWidth, true)
|
|
110
111
|
};
|
|
111
112
|
}
|
|
112
113
|
getRotation(values, pos) {
|
|
@@ -121,7 +122,7 @@ class Animator {
|
|
|
121
122
|
}
|
|
122
123
|
const { start, end } = this.getStartEnd(pos, keyframes);
|
|
123
124
|
return {
|
|
124
|
-
angle: rangeMap(pos, start.position, end.position, start.value.angle, end.value.angle, true)
|
|
125
|
+
angle: (0, rangeMap_1.rangeMap)(pos, start.position, end.position, start.value.angle, end.value.angle, true)
|
|
125
126
|
};
|
|
126
127
|
}
|
|
127
128
|
getOpacity(values, pos) {
|
|
@@ -136,7 +137,7 @@ class Animator {
|
|
|
136
137
|
}
|
|
137
138
|
const { start, end } = this.getStartEnd(pos, keyframes);
|
|
138
139
|
return {
|
|
139
|
-
opacity: rangeMap(pos, start.position, end.position, start.value.opacity, end.value.opacity, true)
|
|
140
|
+
opacity: (0, rangeMap_1.rangeMap)(pos, start.position, end.position, start.value.opacity, end.value.opacity, true)
|
|
140
141
|
};
|
|
141
142
|
}
|
|
142
143
|
getScale(values, pos) {
|
|
@@ -151,7 +152,7 @@ class Animator {
|
|
|
151
152
|
}
|
|
152
153
|
const { start, end } = this.getStartEnd(pos, keyframes);
|
|
153
154
|
return {
|
|
154
|
-
scale: rangeMap(pos, start.position, end.position, start.value.scale, end.value.scale, true)
|
|
155
|
+
scale: (0, rangeMap_1.rangeMap)(pos, start.position, end.position, start.value.scale, end.value.scale, true)
|
|
155
156
|
};
|
|
156
157
|
}
|
|
157
158
|
getBlur(values, pos) {
|
|
@@ -166,7 +167,7 @@ class Animator {
|
|
|
166
167
|
}
|
|
167
168
|
const { start, end } = this.getStartEnd(pos, keyframes);
|
|
168
169
|
return {
|
|
169
|
-
blur: rangeMap(pos, start.position, end.position, start.value.blur, end.value.blur, true)
|
|
170
|
+
blur: (0, rangeMap_1.rangeMap)(pos, start.position, end.position, start.value.blur, end.value.blur, true)
|
|
170
171
|
};
|
|
171
172
|
}
|
|
172
173
|
getBackdropBlur(values, pos) {
|
|
@@ -181,7 +182,7 @@ class Animator {
|
|
|
181
182
|
}
|
|
182
183
|
const { start, end } = this.getStartEnd(pos, keyframes);
|
|
183
184
|
return {
|
|
184
|
-
backdropBlur: rangeMap(pos, start.position, end.position, start.value.backdropBlur, end.value.backdropBlur, true)
|
|
185
|
+
backdropBlur: (0, rangeMap_1.rangeMap)(pos, start.position, end.position, start.value.backdropBlur, end.value.backdropBlur, true)
|
|
185
186
|
};
|
|
186
187
|
}
|
|
187
188
|
getTextColor(values, pos) {
|
|
@@ -211,7 +212,7 @@ class Animator {
|
|
|
211
212
|
}
|
|
212
213
|
const { start, end } = this.getStartEnd(pos, keyframes);
|
|
213
214
|
return {
|
|
214
|
-
letterSpacing: rangeMap(pos, start.position, end.position, start.value.letterSpacing, end.value.letterSpacing, true)
|
|
215
|
+
letterSpacing: (0, rangeMap_1.rangeMap)(pos, start.position, end.position, start.value.letterSpacing, end.value.letterSpacing, true)
|
|
215
216
|
};
|
|
216
217
|
}
|
|
217
218
|
getWordSpacing(values, pos) {
|
|
@@ -226,7 +227,7 @@ class Animator {
|
|
|
226
227
|
}
|
|
227
228
|
const { start, end } = this.getStartEnd(pos, keyframes);
|
|
228
229
|
return {
|
|
229
|
-
wordSpacing: rangeMap(pos, start.position, end.position, start.value.wordSpacing, end.value.wordSpacing, true)
|
|
230
|
+
wordSpacing: (0, rangeMap_1.rangeMap)(pos, start.position, end.position, start.value.wordSpacing, end.value.wordSpacing, true)
|
|
230
231
|
};
|
|
231
232
|
}
|
|
232
233
|
getStartEnd(position, keyframes) {
|
|
@@ -242,7 +243,7 @@ class Animator {
|
|
|
242
243
|
}
|
|
243
244
|
}
|
|
244
245
|
getRangeColor(start, end, position) {
|
|
245
|
-
const rangeAmount = rangeMap(position, start.position, end.position, 0, 1, true);
|
|
246
|
+
const rangeAmount = (0, rangeMap_1.rangeMap)(position, start.position, end.position, 0, 1, true);
|
|
246
247
|
const startColor = color_1.CntrlColor.parse(start.value.color);
|
|
247
248
|
const endColor = color_1.CntrlColor.parse(end.value.color);
|
|
248
249
|
const mixedColor = startColor.mix(endColor, rangeAmount);
|
|
@@ -268,11 +269,3 @@ function createKeyframesMap() {
|
|
|
268
269
|
[sdk_1.KeyframeType.TextColor]: []
|
|
269
270
|
};
|
|
270
271
|
}
|
|
271
|
-
function rangeMap(n, start1, stop1, start2, stop2, withinBounds = false) {
|
|
272
|
-
const mapped = (n - start1) / (stop1 - start1) * (stop2 - start2) + start2;
|
|
273
|
-
if (withinBounds && n < start1)
|
|
274
|
-
return start2;
|
|
275
|
-
if (withinBounds && n > stop1)
|
|
276
|
-
return stop2;
|
|
277
|
-
return mapped;
|
|
278
|
-
}
|
|
@@ -91,7 +91,7 @@ class RichTextConverter {
|
|
|
91
91
|
offset = entity.end;
|
|
92
92
|
}
|
|
93
93
|
if (entity.link) {
|
|
94
|
-
kids.push((0, jsx_runtime_1.jsx)(LinkWrapper_1.LinkWrapper, Object.assign({ url: entity.link }, { children: entityKids }), entity.start));
|
|
94
|
+
kids.push((0, jsx_runtime_1.jsx)(LinkWrapper_1.LinkWrapper, Object.assign({ url: entity.link, target: entity.target }, { children: entityKids }), entity.start));
|
|
95
95
|
continue;
|
|
96
96
|
}
|
|
97
97
|
kids.push(...entityKids);
|
|
@@ -184,7 +184,7 @@ class RichTextConverter {
|
|
|
184
184
|
const end = dividers[i + 1];
|
|
185
185
|
const entity = entities.find(e => e.start === start);
|
|
186
186
|
entitiesGroups.push(Object.assign({ stylesGroup: [], start,
|
|
187
|
-
end }, (entity && { link: entity.data.url })));
|
|
187
|
+
end }, (entity && { link: entity.data.url, target: entity.data.target })));
|
|
188
188
|
}
|
|
189
189
|
return entitiesGroups;
|
|
190
190
|
}
|
|
@@ -209,7 +209,7 @@ class RichTextConverter {
|
|
|
209
209
|
const end = entityDividers[i + 1];
|
|
210
210
|
const entity = entities.find(e => e.start === start);
|
|
211
211
|
entitiesGroups.push(Object.assign({ stylesGroup: styleGroups.filter(s => s.start >= start && s.end <= end), start,
|
|
212
|
-
end }, (entity && { link: entity.data.url })));
|
|
212
|
+
end }, (entity && { link: entity.data.url, target: entity.data.target })));
|
|
213
213
|
}
|
|
214
214
|
return entitiesGroups;
|
|
215
215
|
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.useImageFx = void 0;
|
|
4
|
+
const react_1 = require("react");
|
|
5
|
+
const effects_1 = require("@cntrl-site/effects");
|
|
6
|
+
const rangeMap_1 = require("../rangeMap");
|
|
7
|
+
function useImageFx(canvas, enabled, { imageUrl, fragmentShader }) {
|
|
8
|
+
const cursor = (0, react_1.useRef)([0, 0]);
|
|
9
|
+
const imageFx = (0, react_1.useMemo)(() => {
|
|
10
|
+
if (!imageUrl)
|
|
11
|
+
return undefined;
|
|
12
|
+
return new effects_1.ImageEffect(imageUrl, fragmentShader, { time: 0, cursor: cursor.current });
|
|
13
|
+
}, [imageUrl, fragmentShader]);
|
|
14
|
+
(0, react_1.useEffect)(() => {
|
|
15
|
+
if (!canvas || !imageFx)
|
|
16
|
+
return;
|
|
17
|
+
const handleMouseMove = (evt) => {
|
|
18
|
+
const rect = canvas.getBoundingClientRect();
|
|
19
|
+
const x = (0, rangeMap_1.rangeMap)(evt.clientX, rect.left, rect.left + rect.width, 0, 1);
|
|
20
|
+
const y = (0, rangeMap_1.rangeMap)(evt.clientY, rect.top, rect.top + rect.height, 0, 1);
|
|
21
|
+
imageFx.setParam('cursor', [x, y]);
|
|
22
|
+
};
|
|
23
|
+
window.addEventListener('mousemove', handleMouseMove, { capture: true, passive: true });
|
|
24
|
+
return () => {
|
|
25
|
+
window.removeEventListener('mousemove', handleMouseMove);
|
|
26
|
+
};
|
|
27
|
+
}, [canvas, imageFx]);
|
|
28
|
+
(0, react_1.useEffect)(() => {
|
|
29
|
+
const gl = canvas === null || canvas === void 0 ? void 0 : canvas.getContext('webgl2');
|
|
30
|
+
if (!enabled || !canvas || !gl || !imageFx)
|
|
31
|
+
return;
|
|
32
|
+
let running = false;
|
|
33
|
+
let time = 0;
|
|
34
|
+
let frame;
|
|
35
|
+
const renderFrame = () => {
|
|
36
|
+
time += 0.1;
|
|
37
|
+
imageFx.setViewport(Math.floor(canvas.width), Math.floor(canvas.height));
|
|
38
|
+
imageFx.setParam('time', time);
|
|
39
|
+
imageFx.render(gl);
|
|
40
|
+
frame = requestAnimationFrame(renderFrame);
|
|
41
|
+
};
|
|
42
|
+
const observer = new IntersectionObserver(([entry]) => {
|
|
43
|
+
if (entry.isIntersecting && !running) {
|
|
44
|
+
frame = requestAnimationFrame(renderFrame);
|
|
45
|
+
running = true;
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
if (!entry.isIntersecting && running) {
|
|
49
|
+
window.cancelAnimationFrame(frame);
|
|
50
|
+
running = false;
|
|
51
|
+
}
|
|
52
|
+
});
|
|
53
|
+
observer.observe(canvas);
|
|
54
|
+
imageFx.prepare(gl);
|
|
55
|
+
return () => {
|
|
56
|
+
window.cancelAnimationFrame(frame);
|
|
57
|
+
observer.disconnect();
|
|
58
|
+
};
|
|
59
|
+
}, [canvas, imageFx, enabled]);
|
|
60
|
+
}
|
|
61
|
+
exports.useImageFx = useImageFx;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.rangeMap = void 0;
|
|
4
|
+
function rangeMap(n, start1, stop1, start2, stop2, withinBounds = false) {
|
|
5
|
+
const mapped = (n - start1) / (stop1 - start1) * (stop2 - start2) + start2;
|
|
6
|
+
if (withinBounds && n < start1)
|
|
7
|
+
return start2;
|
|
8
|
+
if (withinBounds && n > stop1)
|
|
9
|
+
return stop2;
|
|
10
|
+
return mapped;
|
|
11
|
+
}
|
|
12
|
+
exports.rangeMap = rangeMap;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.useElementRect = void 0;
|
|
4
|
+
const react_1 = require("react");
|
|
5
|
+
function useElementRect(element) {
|
|
6
|
+
const [rect, setRect] = (0, react_1.useState)(() => element === null || element === void 0 ? void 0 : element.getBoundingClientRect());
|
|
7
|
+
(0, react_1.useEffect)(() => {
|
|
8
|
+
if (!element)
|
|
9
|
+
return;
|
|
10
|
+
const observer = new ResizeObserver(entries => {
|
|
11
|
+
const el = entries.find(entry => entry.target === element);
|
|
12
|
+
if (!el) {
|
|
13
|
+
throw new Error('Element is not found in observed entries');
|
|
14
|
+
}
|
|
15
|
+
setRect(el.target.getBoundingClientRect());
|
|
16
|
+
});
|
|
17
|
+
observer.observe(element);
|
|
18
|
+
return () => {
|
|
19
|
+
observer.unobserve(element);
|
|
20
|
+
observer.disconnect();
|
|
21
|
+
};
|
|
22
|
+
}, [element]);
|
|
23
|
+
return rect;
|
|
24
|
+
}
|
|
25
|
+
exports.useElementRect = useElementRect;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cntrl-site/sdk-nextjs",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.20.0",
|
|
4
4
|
"description": "SDK for Next.js",
|
|
5
5
|
"main": "lib/index.js",
|
|
6
6
|
"types": "src/index.ts",
|
|
@@ -22,7 +22,8 @@
|
|
|
22
22
|
"homepage": "https://github.com/cntrl-site/sdk-nextjs#readme",
|
|
23
23
|
"dependencies": {
|
|
24
24
|
"@cntrl-site/color": "^1.0.0",
|
|
25
|
-
"@cntrl-site/
|
|
25
|
+
"@cntrl-site/effects": "^1.0.0",
|
|
26
|
+
"@cntrl-site/sdk": "^1.4.1",
|
|
26
27
|
"@types/vimeo__player": "^2.18.0",
|
|
27
28
|
"@vimeo/player": "^2.20.1",
|
|
28
29
|
"html-react-parser": "^3.0.1",
|
|
@@ -3,14 +3,15 @@ import React, { ReactElement, ReactNode } from 'react';
|
|
|
3
3
|
interface Props {
|
|
4
4
|
url?: string;
|
|
5
5
|
children: ReactElement | ReactNode[];
|
|
6
|
+
target?: string;
|
|
6
7
|
}
|
|
7
8
|
|
|
8
|
-
export const LinkWrapper: React.FC<Props> = ({ url, children }) => {
|
|
9
|
+
export const LinkWrapper: React.FC<Props> = ({ url, children, target }) => {
|
|
9
10
|
const validUrl = url && buildValidUrl(url);
|
|
10
11
|
return url ? (
|
|
11
12
|
<a
|
|
12
13
|
href={validUrl}
|
|
13
|
-
target={
|
|
14
|
+
target={target}
|
|
14
15
|
rel="noreferrer"
|
|
15
16
|
>
|
|
16
17
|
{children}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { FC, useId, useMemo, useState } from 'react';
|
|
1
|
+
import { FC, useId, useMemo, useRef, useState } from 'react';
|
|
2
2
|
import JSXStyle from 'styled-jsx/style';
|
|
3
3
|
import { CntrlColor } from '@cntrl-site/color';
|
|
4
4
|
import { ArticleItemType, getLayoutStyles, ImageItem as TImageItem } from '@cntrl-site/sdk';
|
|
@@ -9,6 +9,8 @@ import { useItemAngle } from '../useItemAngle';
|
|
|
9
9
|
import { useCntrlContext } from '../../provider/useCntrlContext';
|
|
10
10
|
import { getHoverStyles, getTransitions } from '../../utils/HoverStyles/HoverStyles';
|
|
11
11
|
import { useRegisterResize } from "../../common/useRegisterResize";
|
|
12
|
+
import { useImageFx } from '../../utils/effects/useImageFx';
|
|
13
|
+
import { useElementRect } from '../../utils/useElementRect';
|
|
12
14
|
|
|
13
15
|
export const ImageItem: FC<ItemProps<TImageItem>> = ({ item, sectionId, onResize }) => {
|
|
14
16
|
const id = useId();
|
|
@@ -18,8 +20,22 @@ export const ImageItem: FC<ItemProps<TImageItem>> = ({ item, sectionId, onResize
|
|
|
18
20
|
const borderColor = useMemo(() => CntrlColor.parse(strokeColor), [strokeColor]);
|
|
19
21
|
const [ref, setRef] = useState<HTMLDivElement | null>(null);
|
|
20
22
|
useRegisterResize(ref, onResize);
|
|
23
|
+
const { url, hasGLEffect, fragmentShader } = item.commonParams;
|
|
24
|
+
const fxCanvas = useRef<HTMLCanvasElement | null>(null);
|
|
25
|
+
useImageFx(fxCanvas.current, hasGLEffect ?? false, {
|
|
26
|
+
imageUrl: url,
|
|
27
|
+
fragmentShader
|
|
28
|
+
});
|
|
29
|
+
const rect = useElementRect(ref);
|
|
30
|
+
const rectWidth = Math.floor(rect?.width ?? 0);
|
|
31
|
+
const rectHeight = Math.floor(rect?.height ?? 0);
|
|
32
|
+
const inlineStyles = {
|
|
33
|
+
borderRadius: `${radius * 100}vw`,
|
|
34
|
+
borderWidth: `${strokeWidth * 100}vw`,
|
|
35
|
+
borderColor: `${borderColor.toCss()}`
|
|
36
|
+
};
|
|
21
37
|
return (
|
|
22
|
-
<LinkWrapper url={item.link?.url}>
|
|
38
|
+
<LinkWrapper url={item.link?.url} target={item.link?.target}>
|
|
23
39
|
<>
|
|
24
40
|
<div
|
|
25
41
|
className={`image-wrapper-${item.id}`}
|
|
@@ -30,15 +46,23 @@ export const ImageItem: FC<ItemProps<TImageItem>> = ({ item, sectionId, onResize
|
|
|
30
46
|
filter: `blur(${blur * 100}vw)`
|
|
31
47
|
}}
|
|
32
48
|
>
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
49
|
+
{item.commonParams.hasGLEffect ? (
|
|
50
|
+
<canvas
|
|
51
|
+
style={inlineStyles}
|
|
52
|
+
ref={fxCanvas}
|
|
53
|
+
className="img-canvas"
|
|
54
|
+
width={rectWidth}
|
|
55
|
+
height={rectHeight}
|
|
56
|
+
/>
|
|
57
|
+
) : (
|
|
58
|
+
<img
|
|
59
|
+
alt=""
|
|
60
|
+
className={`image image-${item.id}`}
|
|
61
|
+
style={inlineStyles}
|
|
62
|
+
src={item.commonParams.url}
|
|
63
|
+
/>
|
|
64
|
+
)}
|
|
65
|
+
|
|
42
66
|
</div>
|
|
43
67
|
<JSXStyle id={id}>{`
|
|
44
68
|
@supports not (color: oklch(42% 0.3 90 / 1)) {
|
|
@@ -61,6 +85,15 @@ export const ImageItem: FC<ItemProps<TImageItem>> = ({ item, sectionId, onResize
|
|
|
61
85
|
pointer-events: none;
|
|
62
86
|
border-style: solid;
|
|
63
87
|
overflow: hidden;
|
|
88
|
+
box-sizing: border-box;
|
|
89
|
+
}
|
|
90
|
+
.img-canvas {
|
|
91
|
+
border: solid;
|
|
92
|
+
width: 100%;
|
|
93
|
+
height: 100%;
|
|
94
|
+
pointer-events: none;
|
|
95
|
+
border-width: 0;
|
|
96
|
+
box-sizing: border-box;
|
|
64
97
|
}
|
|
65
98
|
${getLayoutStyles(layouts, [item.state.hover], ([hoverParams]) => {
|
|
66
99
|
return (`
|
|
@@ -22,7 +22,7 @@ export const RectangleItem: FC<ItemProps<TRectangleItem>> = ({ item, sectionId,
|
|
|
22
22
|
const backdropFilterValue = backdropBlur !== 0 ? `blur(${backdropBlur * 100}vw)`: 'unset';
|
|
23
23
|
|
|
24
24
|
return (
|
|
25
|
-
<LinkWrapper url={item.link?.url}>
|
|
25
|
+
<LinkWrapper url={item.link?.url} target={item.link?.target}>
|
|
26
26
|
<>
|
|
27
27
|
<div
|
|
28
28
|
className={`rectangle-${item.id}`}
|
|
@@ -19,7 +19,7 @@ export const VideoItem: FC<ItemProps<TVideoItem>> = ({ item, sectionId, onResize
|
|
|
19
19
|
const [ref, setRef] = useState<HTMLDivElement | null>(null);
|
|
20
20
|
useRegisterResize(ref, onResize);
|
|
21
21
|
return (
|
|
22
|
-
<LinkWrapper url={item.link?.url}>
|
|
22
|
+
<LinkWrapper url={item.link?.url} target={item.link?.target}>
|
|
23
23
|
<div
|
|
24
24
|
className={`video-wrapper-${item.id}`}
|
|
25
25
|
ref={setRef}
|
|
@@ -37,7 +37,7 @@ export const VimeoEmbedItem: FC<ItemProps<TVimeoEmbedItem>> = ({ item, sectionId
|
|
|
37
37
|
const validUrl = getValidVimeoUrl(url);
|
|
38
38
|
|
|
39
39
|
return (
|
|
40
|
-
<LinkWrapper url={item.link?.url}>
|
|
40
|
+
<LinkWrapper url={item.link?.url} target={item.link?.target}>
|
|
41
41
|
<div
|
|
42
42
|
className={`embed-video-wrapper-${item.id}`}
|
|
43
43
|
ref={setRef}
|
|
@@ -52,7 +52,7 @@ export const YoutubeEmbedItem: FC<ItemProps<TYoutubeEmbedItem>> = ({ item, secti
|
|
|
52
52
|
}, [YT, div]);
|
|
53
53
|
|
|
54
54
|
return (
|
|
55
|
-
<LinkWrapper url={item.link?.url}>
|
|
55
|
+
<LinkWrapper url={item.link?.url} target={item.link?.target}>
|
|
56
56
|
<div
|
|
57
57
|
className={`embed-youtube-video-wrapper-${item.id}`}
|
|
58
58
|
onMouseEnter={() => {
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { KeyframeType, KeyframeValueMap } from '@cntrl-site/sdk';
|
|
2
2
|
import { CntrlColor } from '@cntrl-site/color';
|
|
3
3
|
import { binSearchInsertAt, createInsert } from '../binSearchInsertAt';
|
|
4
|
+
import { rangeMap } from '../rangeMap';
|
|
4
5
|
|
|
5
6
|
export interface AnimationData<T extends KeyframeType> {
|
|
6
7
|
position: number;
|
|
@@ -348,17 +349,3 @@ function createKeyframesMap(): KeyframesMap {
|
|
|
348
349
|
[KeyframeType.TextColor]: []
|
|
349
350
|
};
|
|
350
351
|
}
|
|
351
|
-
|
|
352
|
-
function rangeMap(
|
|
353
|
-
n: number,
|
|
354
|
-
start1: number,
|
|
355
|
-
stop1: number,
|
|
356
|
-
start2: number,
|
|
357
|
-
stop2: number,
|
|
358
|
-
withinBounds: boolean = false
|
|
359
|
-
): number {
|
|
360
|
-
const mapped = (n - start1) / (stop1 - start1) * (stop2 - start2) + start2;
|
|
361
|
-
if (withinBounds && n < start1) return start2;
|
|
362
|
-
if (withinBounds && n > stop1) return stop2;
|
|
363
|
-
return mapped;
|
|
364
|
-
}
|
|
@@ -21,6 +21,7 @@ interface StyleGroup {
|
|
|
21
21
|
|
|
22
22
|
interface EntitiesGroup {
|
|
23
23
|
link?: string;
|
|
24
|
+
target?: string;
|
|
24
25
|
stylesGroup: StyleGroup[];
|
|
25
26
|
start: number;
|
|
26
27
|
end: number;
|
|
@@ -122,7 +123,7 @@ export class RichTextConverter {
|
|
|
122
123
|
offset = entity.end;
|
|
123
124
|
}
|
|
124
125
|
if (entity.link) {
|
|
125
|
-
kids.push(<LinkWrapper key={entity.start} url={entity.link} >{entityKids}</LinkWrapper>);
|
|
126
|
+
kids.push(<LinkWrapper key={entity.start} url={entity.link} target={entity.target}>{entityKids}</LinkWrapper>);
|
|
126
127
|
continue;
|
|
127
128
|
}
|
|
128
129
|
kids.push(...entityKids);
|
|
@@ -218,7 +219,7 @@ export class RichTextConverter {
|
|
|
218
219
|
stylesGroup: [],
|
|
219
220
|
start,
|
|
220
221
|
end,
|
|
221
|
-
...(entity && { link: entity.data.url })
|
|
222
|
+
...(entity && { link: entity.data.url, target: entity.data.target })
|
|
222
223
|
});
|
|
223
224
|
}
|
|
224
225
|
return entitiesGroups;
|
|
@@ -248,7 +249,7 @@ export class RichTextConverter {
|
|
|
248
249
|
stylesGroup: styleGroups.filter(s => s.start >= start && s.end <= end),
|
|
249
250
|
start,
|
|
250
251
|
end,
|
|
251
|
-
...(entity && { link: entity.data.url })
|
|
252
|
+
...(entity && { link: entity.data.url, target: entity.data.target })
|
|
252
253
|
});
|
|
253
254
|
}
|
|
254
255
|
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { useEffect, useMemo, useRef } from 'react';
|
|
2
|
+
import { ImageEffect } from '@cntrl-site/effects';
|
|
3
|
+
import { rangeMap } from '../rangeMap';
|
|
4
|
+
|
|
5
|
+
interface FxParams {
|
|
6
|
+
imageUrl?: string;
|
|
7
|
+
fragmentShader?: string;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function useImageFx(
|
|
11
|
+
canvas: HTMLCanvasElement | null | undefined,
|
|
12
|
+
enabled: boolean,
|
|
13
|
+
{
|
|
14
|
+
imageUrl,
|
|
15
|
+
fragmentShader
|
|
16
|
+
}: FxParams
|
|
17
|
+
): void {
|
|
18
|
+
const cursor = useRef<[number, number]>([0, 0]);
|
|
19
|
+
const imageFx = useMemo<ImageEffect | undefined>(() => {
|
|
20
|
+
if (!imageUrl) return undefined;
|
|
21
|
+
return new ImageEffect(imageUrl, fragmentShader!, { time: 0, cursor: cursor.current });
|
|
22
|
+
}, [imageUrl, fragmentShader]);
|
|
23
|
+
|
|
24
|
+
useEffect(() => {
|
|
25
|
+
if (!canvas || !imageFx) return;
|
|
26
|
+
const handleMouseMove = (evt: MouseEvent) => {
|
|
27
|
+
const rect = canvas.getBoundingClientRect();
|
|
28
|
+
const x = rangeMap(evt.clientX, rect.left, rect.left + rect.width, 0, 1);
|
|
29
|
+
const y = rangeMap(evt.clientY, rect.top, rect.top + rect.height, 0, 1);
|
|
30
|
+
imageFx.setParam('cursor', [x, y]);
|
|
31
|
+
};
|
|
32
|
+
window.addEventListener('mousemove', handleMouseMove, { capture: true, passive: true });
|
|
33
|
+
return () => {
|
|
34
|
+
window.removeEventListener('mousemove', handleMouseMove);
|
|
35
|
+
};
|
|
36
|
+
}, [canvas, imageFx]);
|
|
37
|
+
|
|
38
|
+
useEffect(() => {
|
|
39
|
+
const gl = canvas?.getContext('webgl2');
|
|
40
|
+
if (!enabled || !canvas || !gl || !imageFx) return;
|
|
41
|
+
let running = false;
|
|
42
|
+
let time = 0;
|
|
43
|
+
let frame: number;
|
|
44
|
+
|
|
45
|
+
const renderFrame = () => {
|
|
46
|
+
time += 0.1;
|
|
47
|
+
imageFx.setViewport(Math.floor(canvas.width), Math.floor(canvas.height));
|
|
48
|
+
imageFx.setParam('time', time);
|
|
49
|
+
imageFx.render(gl);
|
|
50
|
+
frame = requestAnimationFrame(renderFrame);
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
const observer = new IntersectionObserver(([entry]) => {
|
|
54
|
+
if (entry.isIntersecting && !running) {
|
|
55
|
+
frame = requestAnimationFrame(renderFrame);
|
|
56
|
+
running = true;
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
if (!entry.isIntersecting && running) {
|
|
60
|
+
window.cancelAnimationFrame(frame);
|
|
61
|
+
running = false;
|
|
62
|
+
}
|
|
63
|
+
});
|
|
64
|
+
observer.observe(canvas);
|
|
65
|
+
imageFx.prepare(gl);
|
|
66
|
+
|
|
67
|
+
return () => {
|
|
68
|
+
window.cancelAnimationFrame(frame);
|
|
69
|
+
observer.disconnect();
|
|
70
|
+
};
|
|
71
|
+
}, [canvas, imageFx, enabled]);
|
|
72
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export function rangeMap(
|
|
2
|
+
n: number,
|
|
3
|
+
start1: number,
|
|
4
|
+
stop1: number,
|
|
5
|
+
start2: number,
|
|
6
|
+
stop2: number,
|
|
7
|
+
withinBounds: boolean = false
|
|
8
|
+
): number {
|
|
9
|
+
const mapped = (n - start1) / (stop1 - start1) * (stop2 - start2) + start2;
|
|
10
|
+
if (withinBounds && n < start1) return start2;
|
|
11
|
+
if (withinBounds && n > stop1) return stop2;
|
|
12
|
+
return mapped;
|
|
13
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { useEffect, useState } from 'react';
|
|
2
|
+
|
|
3
|
+
export function useElementRect(element: HTMLElement | null): DOMRect | undefined {
|
|
4
|
+
const [rect, setRect] = useState<DOMRect | undefined>(() => element?.getBoundingClientRect());
|
|
5
|
+
|
|
6
|
+
useEffect(() => {
|
|
7
|
+
if (!element) return;
|
|
8
|
+
const observer = new ResizeObserver(entries => {
|
|
9
|
+
const el = entries.find(entry => entry.target === element);
|
|
10
|
+
if (!el) {
|
|
11
|
+
throw new Error('Element is not found in observed entries');
|
|
12
|
+
}
|
|
13
|
+
setRect(el.target.getBoundingClientRect());
|
|
14
|
+
});
|
|
15
|
+
observer.observe(element);
|
|
16
|
+
return () => {
|
|
17
|
+
observer.unobserve(element);
|
|
18
|
+
observer.disconnect();
|
|
19
|
+
};
|
|
20
|
+
}, [element]);
|
|
21
|
+
|
|
22
|
+
return rect;
|
|
23
|
+
}
|
|
Binary file
|