@cntrl-site/sdk-nextjs 1.9.78 → 1.9.80-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/LICENSE +21 -21
- package/README.md +4 -4
- package/TODO.md +1 -1
- package/eslint.config.mjs +35 -35
- package/jest.config.js +6 -6
- package/lib/common/useCurrentLayout.js +5 -3
- package/lib/common/useItemFXData.js +5 -5
- package/lib/components/Article.js +5 -5
- package/lib/components/LayoutStyle.js +4 -4
- package/lib/components/Section/Section.js +27 -27
- package/lib/components/StructuredBlockItem/StructuredBlockItem.js +13 -13
- package/lib/components/StructuredBlockItem/StructuredComponent/StructuredComponent.js +15 -13
- package/lib/components/StructuredContent/StructuredContent.js +11 -11
- package/lib/components/items/CodeEmbedItem/CodeEmbedItem.js +36 -36
- package/lib/components/items/ComponentItem/ComponentItem.js +13 -13
- package/lib/components/items/CompoundItem/CompoundChild.js +20 -20
- package/lib/components/items/CompoundItem/CompoundItem.js +14 -14
- package/lib/components/items/CustomItem/CustomItem.js +9 -9
- package/lib/components/items/EmbedVideoItem/VimeoEmbed.js +24 -24
- package/lib/components/items/EmbedVideoItem/YoutubeEmbed.js +27 -27
- package/lib/components/items/FileItem/ImageItem.js +40 -40
- package/lib/components/items/FileItem/VideoItem.js +64 -64
- package/lib/components/items/GroupItem/GroupItem.js +14 -14
- package/lib/components/items/Item.js +30 -30
- package/lib/components/items/RectangleItem/RectangleItem.js +27 -27
- package/lib/components/items/RichTextItem/RichTextItem.js +22 -22
- package/lib/utils/RichTextConverter/RichTextConverter.js +21 -21
- package/package.json +62 -62
- package/src/ItemGeometry/CSSPropertyNameMap.ts +37 -37
- package/src/ItemGeometry/GenericGeometryController.ts +59 -59
- package/src/ItemGeometry/ItemGeometryContext.ts +4 -4
- package/src/ItemGeometry/ItemGeometryController.ts +22 -22
- package/src/ItemGeometry/ItemGeometryService.ts +34 -34
- package/src/ItemGeometry/RichTextGeometryController.ts +166 -166
- package/src/ItemGeometry/useItemGeometry.ts +25 -25
- package/src/common/useCurrentLayout.ts +53 -52
- package/src/common/useExemplary.ts +9 -9
- package/src/common/useItemFXData.ts +63 -63
- package/src/common/useKeyframeValue.ts +85 -85
- package/src/common/useRegisterResize.ts +16 -16
- package/src/components/Article.tsx +69 -69
- package/src/components/ArticleWrapper.tsx +21 -21
- package/src/components/Head.tsx +95 -95
- package/src/components/LayoutStyle.tsx +21 -21
- package/src/components/Page.tsx +47 -47
- package/src/components/ScrollPlaybackVideo.tsx +75 -75
- package/src/components/Section/Section.tsx +162 -162
- package/src/components/Section/SectionImage.tsx +48 -48
- package/src/components/Section/SectionVideo.tsx +144 -144
- package/src/components/Section/useSectionColor.ts +19 -19
- package/src/components/Section/useSectionHeightMap.ts +19 -19
- package/src/components/StructuredBlockItem/StructuredBlockItem.tsx +69 -69
- package/src/components/StructuredBlockItem/StructuredComponent/StructuredComponent.tsx +68 -66
- package/src/components/StructuredContent/StructuredContent.tsx +42 -42
- package/src/components/items/CodeEmbedItem/CodeEmbedItem.tsx +148 -148
- package/src/components/items/CodeEmbedItem/useCodeEmbedItem.ts +36 -36
- package/src/components/items/ComponentItem/ComponentItem.tsx +85 -85
- package/src/components/items/ComponentItem/useComponentItem.ts +35 -35
- package/src/components/items/CompoundItem/CompoundChild.tsx +151 -151
- package/src/components/items/CompoundItem/CompoundItem.tsx +88 -88
- package/src/components/items/CompoundItem/useCompoundItem.ts +21 -21
- package/src/components/items/CustomItem/CustomItem.tsx +50 -50
- package/src/components/items/EmbedVideoItem/VimeoEmbed.tsx +217 -217
- package/src/components/items/EmbedVideoItem/YoutubeEmbed.tsx +201 -201
- package/src/components/items/EmbedVideoItem/useEmbedVideoItem.ts +46 -46
- package/src/components/items/FileItem/ImageItem.tsx +166 -166
- package/src/components/items/FileItem/VideoItem.tsx +287 -287
- package/src/components/items/FileItem/useFileItem.ts +81 -81
- package/src/components/items/GroupItem/GroupItem.tsx +99 -99
- package/src/components/items/GroupItem/useGroupItem.ts +34 -34
- package/src/components/items/Item.tsx +268 -268
- package/src/components/items/LinkWrapper.tsx +39 -39
- package/src/components/items/RectangleItem/RectangleItem.tsx +177 -177
- package/src/components/items/RectangleItem/useRectangleItem.ts +89 -89
- package/src/components/items/RichTextItem/RichTextItem.tsx +113 -113
- package/src/components/items/RichTextItem/useRichTextItem.ts +12 -12
- package/src/components/items/RichTextWrapper.tsx +15 -15
- package/src/components/items/itemsMap.ts +28 -28
- package/src/components/items/useDraggable.ts +252 -252
- package/src/components/items/useItemAngle.ts +13 -13
- package/src/components/items/useItemArea.ts +35 -35
- package/src/components/items/useItemPointerEvents.ts +23 -23
- package/src/components/items/useItemScale.ts +17 -17
- package/src/components/items/useItemTriggers.ts +15 -15
- package/src/components/items/useRichTextItemValues.ts +66 -66
- package/src/components/items/useSizing.ts +25 -25
- package/src/components/items/useStickyItemTop.ts +21 -21
- package/src/components/useLayoutContext.ts +7 -7
- package/src/index.ts +25 -25
- package/src/interactions/InteractionsRegistry.ts +513 -513
- package/src/interactions/ItemInteractionCtrl.ts +78 -78
- package/src/interactions/getTransition.ts +27 -27
- package/src/interactions/types.ts +36 -36
- package/src/interactions/useItemInteractionCtrl.ts +18 -18
- package/src/provider/ArticleRectContext.ts +4 -4
- package/src/provider/CntrlContext.ts +5 -5
- package/src/provider/CntrlProvider.tsx +11 -11
- package/src/provider/CntrlSdkContext.ts +113 -113
- package/src/provider/CustomItemRegistry.ts +14 -14
- package/src/provider/CustomItemTypes.ts +3 -3
- package/src/provider/CustomSectionRegistry.ts +25 -25
- package/src/provider/InteractionsContext.tsx +73 -73
- package/src/provider/Keyframes.ts +11 -11
- package/src/provider/KeyframesContext.ts +5 -5
- package/src/provider/LayoutContext.ts +3 -3
- package/src/provider/WebGLContextManagerContext.tsx +4 -4
- package/src/provider/defaultContext.ts +8 -8
- package/src/provider/useCntrlContext.ts +8 -8
- package/src/styled-jsx.d.ts +1 -1
- package/src/types/components-utils.d.ts +7 -7
- package/src/utils/Animator/Animator.ts +570 -570
- package/src/utils/ArticleRectManager/ArticleRectObserver.ts +93 -93
- package/src/utils/ArticleRectManager/useArticleRectObserver.ts +17 -17
- package/src/utils/ArticleRectManager/useSectionRegistry.ts +11 -11
- package/src/utils/CSSNumeric.ts +19 -19
- package/src/utils/EventEmitter.ts +41 -41
- package/src/utils/RichTextConverter/RichTextConverter.tsx +342 -342
- package/src/utils/ScaleAnchorMap.ts +13 -13
- package/src/utils/StickyManager/StickyManager.ts +23 -23
- package/src/utils/Youtube/YouTubeIframeApiLoader.ts +58 -58
- package/src/utils/Youtube/YoutubeIframeApi.ts +101 -101
- package/src/utils/Youtube/useYouTubeIframeApi.ts +12 -12
- package/src/utils/areFillsVisible/areFillsVisible.test.ts +63 -63
- package/src/utils/areFillsVisible/areFillsVisible.ts +37 -37
- package/src/utils/binSearchInsertAt.ts +33 -33
- package/src/utils/castObject.ts +10 -10
- package/src/utils/checkOverflowClipSupport.ts +13 -13
- package/src/utils/domDfs.ts +11 -11
- package/src/utils/effects/useImageFx.ts +154 -154
- package/src/utils/effects/useVideoFx.ts +156 -156
- package/src/utils/evaluateComponentBundle.ts +83 -83
- package/src/utils/getAnchoredItemTop.ts +12 -12
- package/src/utils/getCompoundBoundaryStyles.ts +60 -60
- package/src/utils/getFill.ts +46 -46
- package/src/utils/getFontFamilyValue.ts +3 -3
- package/src/utils/getInvertedRanges.ts +43 -43
- package/src/utils/getItemTopStyle.ts +39 -39
- package/src/utils/getStyleFromItemStateAndParams.ts +8 -8
- package/src/utils/getValidYoutubeUrl.ts +29 -29
- package/src/utils/isItemType.ts +5 -5
- package/src/utils/rangeMap.ts +13 -13
- package/src/utils/useElementRect.ts +23 -23
|
@@ -1,85 +1,85 @@
|
|
|
1
|
-
import { KeyframeType, ItemAny } from '@cntrl-site/sdk';
|
|
2
|
-
import isEqual from 'lodash.isequal';
|
|
3
|
-
import { DependencyList, useCallback, useContext, useEffect, useMemo, useRef, useState } from 'react';
|
|
4
|
-
import { ArticleRectContext } from '../provider/ArticleRectContext';
|
|
5
|
-
import { KeyframesContext } from '../provider/KeyframesContext';
|
|
6
|
-
import { AnimationData, Animator } from '../utils/Animator/Animator';
|
|
7
|
-
import { useLayoutContext } from '../components/useLayoutContext';
|
|
8
|
-
|
|
9
|
-
export type AnimatorGetter<T> = (animator: Animator, scroll: number, value: T) => T;
|
|
10
|
-
type ItemParamGetter<T> = (item: ItemAny, layoutId: string | undefined) => T;
|
|
11
|
-
const emptyDeps: DependencyList = [];
|
|
12
|
-
|
|
13
|
-
export function useKeyframeValue<T>(
|
|
14
|
-
item: ItemAny,
|
|
15
|
-
type: KeyframeType,
|
|
16
|
-
itemParamsGetter: ItemParamGetter<T>,
|
|
17
|
-
animatorGetter: AnimatorGetter<T>,
|
|
18
|
-
sectionId: string,
|
|
19
|
-
deps: DependencyList = emptyDeps
|
|
20
|
-
): T {
|
|
21
|
-
const animatorGetterRef = useRef(animatorGetter);
|
|
22
|
-
const itemParamsGetterRef = useRef(itemParamsGetter);
|
|
23
|
-
|
|
24
|
-
animatorGetterRef.current = animatorGetter;
|
|
25
|
-
itemParamsGetterRef.current = itemParamsGetter;
|
|
26
|
-
|
|
27
|
-
const articleRectObserver = useContext(ArticleRectContext);
|
|
28
|
-
const layoutId = useLayoutContext();
|
|
29
|
-
const keyframesRepo = useContext(KeyframesContext);
|
|
30
|
-
const keyframes = useMemo(() => keyframesRepo.getItemKeyframes(item.id).filter(kf => kf.type === type), [item.id, keyframesRepo, type]);
|
|
31
|
-
const paramValue = useMemo<T>(() => {
|
|
32
|
-
return itemParamsGetterRef.current(item, layoutId);
|
|
33
|
-
}, [item, layoutId, ...deps]);
|
|
34
|
-
|
|
35
|
-
const [adjustedValue, setAdjustedValue] = useState<T>(paramValue);
|
|
36
|
-
const adjustedValueRef = useRef<T>(adjustedValue);
|
|
37
|
-
adjustedValueRef.current = adjustedValue;
|
|
38
|
-
|
|
39
|
-
const animator = useMemo(() => {
|
|
40
|
-
if (!layoutId || !keyframes.length) return;
|
|
41
|
-
const animationData = keyframes
|
|
42
|
-
.filter(k => k.layoutId === layoutId)
|
|
43
|
-
.map<AnimationData<KeyframeType>>(({ position, type, value }) => ({
|
|
44
|
-
position,
|
|
45
|
-
type,
|
|
46
|
-
value
|
|
47
|
-
}));
|
|
48
|
-
return new Animator(animationData);
|
|
49
|
-
}, [keyframes, layoutId]);
|
|
50
|
-
|
|
51
|
-
const handleKeyframeValue = useCallback((scroll: number) => {
|
|
52
|
-
if (!animator) return;
|
|
53
|
-
const newValue = animatorGetterRef.current(animator, scroll, paramValue);
|
|
54
|
-
if (!isEqual(newValue, adjustedValueRef.current)) {
|
|
55
|
-
setAdjustedValue(newValue);
|
|
56
|
-
}
|
|
57
|
-
}, [animator, paramValue]);
|
|
58
|
-
|
|
59
|
-
useEffect(() => {
|
|
60
|
-
setAdjustedValue(paramValue);
|
|
61
|
-
}, [paramValue]);
|
|
62
|
-
|
|
63
|
-
useEffect(() => {
|
|
64
|
-
if (!articleRectObserver || !animator) return;
|
|
65
|
-
const scroll = articleRectObserver.getSectionScroll(sectionId);
|
|
66
|
-
handleKeyframeValue(scroll);
|
|
67
|
-
}, [articleRectObserver, handleKeyframeValue, animator]);
|
|
68
|
-
|
|
69
|
-
useEffect(() => {
|
|
70
|
-
if (!articleRectObserver || !animator) return;
|
|
71
|
-
return articleRectObserver.on('resize', () => {
|
|
72
|
-
const scroll = articleRectObserver.getSectionScroll(sectionId);
|
|
73
|
-
handleKeyframeValue(scroll);
|
|
74
|
-
});
|
|
75
|
-
}, [handleKeyframeValue, articleRectObserver, animator]);
|
|
76
|
-
|
|
77
|
-
useEffect(() => {
|
|
78
|
-
if (!articleRectObserver || !animator) return;
|
|
79
|
-
return articleRectObserver.on('scroll', () => {
|
|
80
|
-
const scroll = articleRectObserver.getSectionScroll(sectionId);
|
|
81
|
-
handleKeyframeValue(scroll);
|
|
82
|
-
});
|
|
83
|
-
}, [handleKeyframeValue, articleRectObserver, animator]);
|
|
84
|
-
return keyframes.length ? adjustedValue : paramValue;
|
|
85
|
-
}
|
|
1
|
+
import { KeyframeType, ItemAny } from '@cntrl-site/sdk';
|
|
2
|
+
import isEqual from 'lodash.isequal';
|
|
3
|
+
import { DependencyList, useCallback, useContext, useEffect, useMemo, useRef, useState } from 'react';
|
|
4
|
+
import { ArticleRectContext } from '../provider/ArticleRectContext';
|
|
5
|
+
import { KeyframesContext } from '../provider/KeyframesContext';
|
|
6
|
+
import { AnimationData, Animator } from '../utils/Animator/Animator';
|
|
7
|
+
import { useLayoutContext } from '../components/useLayoutContext';
|
|
8
|
+
|
|
9
|
+
export type AnimatorGetter<T> = (animator: Animator, scroll: number, value: T) => T;
|
|
10
|
+
type ItemParamGetter<T> = (item: ItemAny, layoutId: string | undefined) => T;
|
|
11
|
+
const emptyDeps: DependencyList = [];
|
|
12
|
+
|
|
13
|
+
export function useKeyframeValue<T>(
|
|
14
|
+
item: ItemAny,
|
|
15
|
+
type: KeyframeType,
|
|
16
|
+
itemParamsGetter: ItemParamGetter<T>,
|
|
17
|
+
animatorGetter: AnimatorGetter<T>,
|
|
18
|
+
sectionId: string,
|
|
19
|
+
deps: DependencyList = emptyDeps
|
|
20
|
+
): T {
|
|
21
|
+
const animatorGetterRef = useRef(animatorGetter);
|
|
22
|
+
const itemParamsGetterRef = useRef(itemParamsGetter);
|
|
23
|
+
|
|
24
|
+
animatorGetterRef.current = animatorGetter;
|
|
25
|
+
itemParamsGetterRef.current = itemParamsGetter;
|
|
26
|
+
|
|
27
|
+
const articleRectObserver = useContext(ArticleRectContext);
|
|
28
|
+
const layoutId = useLayoutContext();
|
|
29
|
+
const keyframesRepo = useContext(KeyframesContext);
|
|
30
|
+
const keyframes = useMemo(() => keyframesRepo.getItemKeyframes(item.id).filter(kf => kf.type === type), [item.id, keyframesRepo, type]);
|
|
31
|
+
const paramValue = useMemo<T>(() => {
|
|
32
|
+
return itemParamsGetterRef.current(item, layoutId);
|
|
33
|
+
}, [item, layoutId, ...deps]);
|
|
34
|
+
|
|
35
|
+
const [adjustedValue, setAdjustedValue] = useState<T>(paramValue);
|
|
36
|
+
const adjustedValueRef = useRef<T>(adjustedValue);
|
|
37
|
+
adjustedValueRef.current = adjustedValue;
|
|
38
|
+
|
|
39
|
+
const animator = useMemo(() => {
|
|
40
|
+
if (!layoutId || !keyframes.length) return;
|
|
41
|
+
const animationData = keyframes
|
|
42
|
+
.filter(k => k.layoutId === layoutId)
|
|
43
|
+
.map<AnimationData<KeyframeType>>(({ position, type, value }) => ({
|
|
44
|
+
position,
|
|
45
|
+
type,
|
|
46
|
+
value
|
|
47
|
+
}));
|
|
48
|
+
return new Animator(animationData);
|
|
49
|
+
}, [keyframes, layoutId]);
|
|
50
|
+
|
|
51
|
+
const handleKeyframeValue = useCallback((scroll: number) => {
|
|
52
|
+
if (!animator) return;
|
|
53
|
+
const newValue = animatorGetterRef.current(animator, scroll, paramValue);
|
|
54
|
+
if (!isEqual(newValue, adjustedValueRef.current)) {
|
|
55
|
+
setAdjustedValue(newValue);
|
|
56
|
+
}
|
|
57
|
+
}, [animator, paramValue]);
|
|
58
|
+
|
|
59
|
+
useEffect(() => {
|
|
60
|
+
setAdjustedValue(paramValue);
|
|
61
|
+
}, [paramValue]);
|
|
62
|
+
|
|
63
|
+
useEffect(() => {
|
|
64
|
+
if (!articleRectObserver || !animator) return;
|
|
65
|
+
const scroll = articleRectObserver.getSectionScroll(sectionId);
|
|
66
|
+
handleKeyframeValue(scroll);
|
|
67
|
+
}, [articleRectObserver, handleKeyframeValue, animator]);
|
|
68
|
+
|
|
69
|
+
useEffect(() => {
|
|
70
|
+
if (!articleRectObserver || !animator) return;
|
|
71
|
+
return articleRectObserver.on('resize', () => {
|
|
72
|
+
const scroll = articleRectObserver.getSectionScroll(sectionId);
|
|
73
|
+
handleKeyframeValue(scroll);
|
|
74
|
+
});
|
|
75
|
+
}, [handleKeyframeValue, articleRectObserver, animator]);
|
|
76
|
+
|
|
77
|
+
useEffect(() => {
|
|
78
|
+
if (!articleRectObserver || !animator) return;
|
|
79
|
+
return articleRectObserver.on('scroll', () => {
|
|
80
|
+
const scroll = articleRectObserver.getSectionScroll(sectionId);
|
|
81
|
+
handleKeyframeValue(scroll);
|
|
82
|
+
});
|
|
83
|
+
}, [handleKeyframeValue, articleRectObserver, animator]);
|
|
84
|
+
return keyframes.length ? adjustedValue : paramValue;
|
|
85
|
+
}
|
|
@@ -1,16 +1,16 @@
|
|
|
1
|
-
import { useEffect } from 'react';
|
|
2
|
-
import ResizeObserver from 'resize-observer-polyfill';
|
|
3
|
-
|
|
4
|
-
export function useRegisterResize(ref: HTMLElement | null, onResize?: (height: number) => void) {
|
|
5
|
-
useEffect(() => {
|
|
6
|
-
if (!ref || !onResize) return;
|
|
7
|
-
const observer = new ResizeObserver((entries) => {
|
|
8
|
-
const [entry] = entries;
|
|
9
|
-
onResize(entry.target.clientHeight / window.innerWidth);
|
|
10
|
-
});
|
|
11
|
-
observer.observe(ref);
|
|
12
|
-
return () => {
|
|
13
|
-
observer.unobserve(ref);
|
|
14
|
-
};
|
|
15
|
-
}, [ref, onResize]);
|
|
16
|
-
}
|
|
1
|
+
import { useEffect } from 'react';
|
|
2
|
+
import ResizeObserver from 'resize-observer-polyfill';
|
|
3
|
+
|
|
4
|
+
export function useRegisterResize(ref: HTMLElement | null, onResize?: (height: number) => void) {
|
|
5
|
+
useEffect(() => {
|
|
6
|
+
if (!ref || !onResize) return;
|
|
7
|
+
const observer = new ResizeObserver((entries) => {
|
|
8
|
+
const [entry] = entries;
|
|
9
|
+
onResize(entry.target.clientHeight / window.innerWidth);
|
|
10
|
+
});
|
|
11
|
+
observer.observe(ref);
|
|
12
|
+
return () => {
|
|
13
|
+
observer.unobserve(ref);
|
|
14
|
+
};
|
|
15
|
+
}, [ref, onResize]);
|
|
16
|
+
}
|
|
@@ -1,69 +1,69 @@
|
|
|
1
|
-
import { FC, useEffect, useId, useMemo, useRef, useState } from 'react';
|
|
2
|
-
import JSXStyle from 'styled-jsx/style';
|
|
3
|
-
import { Article as TArticle } from '@cntrl-site/sdk';
|
|
4
|
-
import { Section } from './Section/Section';
|
|
5
|
-
import { Item } from './items/Item';
|
|
6
|
-
import { useArticleRectObserver } from '../utils/ArticleRectManager/useArticleRectObserver';
|
|
7
|
-
import { ArticleRectContext } from '../provider/ArticleRectContext';
|
|
8
|
-
import { ArticleWrapper } from './ArticleWrapper';
|
|
9
|
-
import { InteractionsProvider } from '../provider/InteractionsContext';
|
|
10
|
-
import { WebglContextManagerContext } from '../provider/WebGLContextManagerContext';
|
|
11
|
-
import { WebGLContextManager } from '@cntrl-site/effects';
|
|
12
|
-
import { StructuredContent } from './StructuredContent/StructuredContent';
|
|
13
|
-
|
|
14
|
-
interface Props {
|
|
15
|
-
article: TArticle;
|
|
16
|
-
sectionData: Record<SectionName, any>;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
export const Article: FC<Props> = ({ article, sectionData }) => {
|
|
20
|
-
const articleRef = useRef<HTMLDivElement | null>(null);
|
|
21
|
-
const articleRectObserver = useArticleRectObserver(articleRef.current);
|
|
22
|
-
const id = useId();
|
|
23
|
-
const [articleHeight, setArticleHeight] = useState(1);
|
|
24
|
-
|
|
25
|
-
useEffect(() => {
|
|
26
|
-
if (!articleRectObserver) return;
|
|
27
|
-
return articleRectObserver.on('resize', (rect) => {
|
|
28
|
-
setArticleHeight(rect.height / rect.width);
|
|
29
|
-
});
|
|
30
|
-
}, [articleRectObserver]);
|
|
31
|
-
|
|
32
|
-
const webglContextManager = useMemo(() => new WebGLContextManager(), []);
|
|
33
|
-
const sectionsLength = article.sections.length;
|
|
34
|
-
|
|
35
|
-
return (
|
|
36
|
-
<ArticleRectContext.Provider value={articleRectObserver}>
|
|
37
|
-
<InteractionsProvider article={article}>
|
|
38
|
-
<ArticleWrapper>
|
|
39
|
-
<div className="article" ref={articleRef}>
|
|
40
|
-
<WebglContextManagerContext.Provider value={webglContextManager}>
|
|
41
|
-
{article.sections.map((section, i) => {
|
|
42
|
-
const data = section.name ? sectionData[section.name] : {};
|
|
43
|
-
return (
|
|
44
|
-
<Section
|
|
45
|
-
articleHeight={articleHeight}
|
|
46
|
-
section={section}
|
|
47
|
-
key={section.id}
|
|
48
|
-
data={data}
|
|
49
|
-
zIndex={sectionsLength - i}
|
|
50
|
-
/>
|
|
51
|
-
);
|
|
52
|
-
})}
|
|
53
|
-
</WebglContextManagerContext.Provider>
|
|
54
|
-
<div id="component-portal" />
|
|
55
|
-
<div id="grid-component-lightbox-portal" />
|
|
56
|
-
</div>
|
|
57
|
-
</ArticleWrapper>
|
|
58
|
-
<JSXStyle id={id}>{`
|
|
59
|
-
.article {
|
|
60
|
-
position: relative;
|
|
61
|
-
overflow: clip;
|
|
62
|
-
}
|
|
63
|
-
`}</JSXStyle>
|
|
64
|
-
</InteractionsProvider>
|
|
65
|
-
</ArticleRectContext.Provider>
|
|
66
|
-
);
|
|
67
|
-
};
|
|
68
|
-
|
|
69
|
-
type SectionName = string;
|
|
1
|
+
import { FC, useEffect, useId, useMemo, useRef, useState } from 'react';
|
|
2
|
+
import JSXStyle from 'styled-jsx/style';
|
|
3
|
+
import { Article as TArticle } from '@cntrl-site/sdk';
|
|
4
|
+
import { Section } from './Section/Section';
|
|
5
|
+
import { Item } from './items/Item';
|
|
6
|
+
import { useArticleRectObserver } from '../utils/ArticleRectManager/useArticleRectObserver';
|
|
7
|
+
import { ArticleRectContext } from '../provider/ArticleRectContext';
|
|
8
|
+
import { ArticleWrapper } from './ArticleWrapper';
|
|
9
|
+
import { InteractionsProvider } from '../provider/InteractionsContext';
|
|
10
|
+
import { WebglContextManagerContext } from '../provider/WebGLContextManagerContext';
|
|
11
|
+
import { WebGLContextManager } from '@cntrl-site/effects';
|
|
12
|
+
import { StructuredContent } from './StructuredContent/StructuredContent';
|
|
13
|
+
|
|
14
|
+
interface Props {
|
|
15
|
+
article: TArticle;
|
|
16
|
+
sectionData: Record<SectionName, any>;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export const Article: FC<Props> = ({ article, sectionData }) => {
|
|
20
|
+
const articleRef = useRef<HTMLDivElement | null>(null);
|
|
21
|
+
const articleRectObserver = useArticleRectObserver(articleRef.current);
|
|
22
|
+
const id = useId();
|
|
23
|
+
const [articleHeight, setArticleHeight] = useState(1);
|
|
24
|
+
|
|
25
|
+
useEffect(() => {
|
|
26
|
+
if (!articleRectObserver) return;
|
|
27
|
+
return articleRectObserver.on('resize', (rect) => {
|
|
28
|
+
setArticleHeight(rect.height / rect.width);
|
|
29
|
+
});
|
|
30
|
+
}, [articleRectObserver]);
|
|
31
|
+
|
|
32
|
+
const webglContextManager = useMemo(() => new WebGLContextManager(), []);
|
|
33
|
+
const sectionsLength = article.sections.length;
|
|
34
|
+
|
|
35
|
+
return (
|
|
36
|
+
<ArticleRectContext.Provider value={articleRectObserver}>
|
|
37
|
+
<InteractionsProvider article={article}>
|
|
38
|
+
<ArticleWrapper>
|
|
39
|
+
<div className="article" ref={articleRef}>
|
|
40
|
+
<WebglContextManagerContext.Provider value={webglContextManager}>
|
|
41
|
+
{article.sections.map((section, i) => {
|
|
42
|
+
const data = section.name ? sectionData[section.name] : {};
|
|
43
|
+
return (
|
|
44
|
+
<Section
|
|
45
|
+
articleHeight={articleHeight}
|
|
46
|
+
section={section}
|
|
47
|
+
key={section.id}
|
|
48
|
+
data={data}
|
|
49
|
+
zIndex={sectionsLength - i}
|
|
50
|
+
/>
|
|
51
|
+
);
|
|
52
|
+
})}
|
|
53
|
+
</WebglContextManagerContext.Provider>
|
|
54
|
+
<div id="component-portal" />
|
|
55
|
+
<div id="grid-component-lightbox-portal" />
|
|
56
|
+
</div>
|
|
57
|
+
</ArticleWrapper>
|
|
58
|
+
<JSXStyle id={id}>{`
|
|
59
|
+
.article {
|
|
60
|
+
position: relative;
|
|
61
|
+
overflow: clip;
|
|
62
|
+
}
|
|
63
|
+
`}</JSXStyle>
|
|
64
|
+
</InteractionsProvider>
|
|
65
|
+
</ArticleRectContext.Provider>
|
|
66
|
+
);
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
type SectionName = string;
|
|
@@ -1,21 +1,21 @@
|
|
|
1
|
-
import React, { CSSProperties, FC, PropsWithChildren } from 'react';
|
|
2
|
-
import { useCurrentLayout } from '../common/useCurrentLayout';
|
|
3
|
-
import { LayoutContext } from '../provider/LayoutContext';
|
|
4
|
-
|
|
5
|
-
export const ArticleWrapper: FC<PropsWithChildren<{}>> = ({ children }) => {
|
|
6
|
-
const { layoutId, layoutDeviation } = useCurrentLayout();
|
|
7
|
-
const layoutDeviationStyle = { '--layout-deviation': layoutDeviation } as CSSProperties;
|
|
8
|
-
|
|
9
|
-
return (
|
|
10
|
-
<LayoutContext.Provider value={layoutId}>
|
|
11
|
-
<div
|
|
12
|
-
className="article-wrapper"
|
|
13
|
-
style={{
|
|
14
|
-
...layoutDeviationStyle
|
|
15
|
-
}}
|
|
16
|
-
>
|
|
17
|
-
{children}
|
|
18
|
-
</div>
|
|
19
|
-
</LayoutContext.Provider>
|
|
20
|
-
);
|
|
21
|
-
};
|
|
1
|
+
import React, { CSSProperties, FC, PropsWithChildren } from 'react';
|
|
2
|
+
import { useCurrentLayout } from '../common/useCurrentLayout';
|
|
3
|
+
import { LayoutContext } from '../provider/LayoutContext';
|
|
4
|
+
|
|
5
|
+
export const ArticleWrapper: FC<PropsWithChildren<{}>> = ({ children }) => {
|
|
6
|
+
const { layoutId, layoutDeviation } = useCurrentLayout();
|
|
7
|
+
const layoutDeviationStyle = { '--layout-deviation': layoutDeviation } as CSSProperties;
|
|
8
|
+
|
|
9
|
+
return (
|
|
10
|
+
<LayoutContext.Provider value={layoutId}>
|
|
11
|
+
<div
|
|
12
|
+
className="article-wrapper"
|
|
13
|
+
style={{
|
|
14
|
+
...layoutDeviationStyle
|
|
15
|
+
}}
|
|
16
|
+
>
|
|
17
|
+
{children}
|
|
18
|
+
</div>
|
|
19
|
+
</LayoutContext.Provider>
|
|
20
|
+
);
|
|
21
|
+
};
|
package/src/components/Head.tsx
CHANGED
|
@@ -1,95 +1,95 @@
|
|
|
1
|
-
import React, { FC } from 'react';
|
|
2
|
-
import HTMLReactParser, { domToReact } from 'html-react-parser';
|
|
3
|
-
import Head from 'next/head';
|
|
4
|
-
import { FontFaceGenerator, Meta, Project } from '@cntrl-site/sdk';
|
|
5
|
-
|
|
6
|
-
interface Props {
|
|
7
|
-
project: Project;
|
|
8
|
-
meta: Meta;
|
|
9
|
-
slug?: string;
|
|
10
|
-
siteUrl?: string;
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
export const CNTRLHead: FC<Props> = ({ meta, project, slug, siteUrl }) => {
|
|
14
|
-
const canonicalUrl = buildCanonicalUrl(siteUrl ?? project.primaryDomain, slug);
|
|
15
|
-
const googleFonts: ReturnType<typeof domToReact> = HTMLReactParser(project.fonts.google);
|
|
16
|
-
const adobeFonts: ReturnType<typeof domToReact> = HTMLReactParser(project.fonts.adobe);
|
|
17
|
-
const parsedFonts = {
|
|
18
|
-
...(typeof googleFonts === 'object' ? googleFonts : {}),
|
|
19
|
-
...(typeof adobeFonts === 'object' ? adobeFonts : {})
|
|
20
|
-
};
|
|
21
|
-
const customFonts = project.fonts.custom;
|
|
22
|
-
const htmlHead = HTMLReactParser(project.html.head);
|
|
23
|
-
const ffGenerator = new FontFaceGenerator(customFonts);
|
|
24
|
-
const links = Object.values(parsedFonts as ReturnType<typeof domToReact>).map((value) => {
|
|
25
|
-
if (!value) return null;
|
|
26
|
-
const rel = value?.rel || value.props?.rel;
|
|
27
|
-
const href = value?.href || value.props?.href;
|
|
28
|
-
if (!rel || !href) return null;
|
|
29
|
-
return (
|
|
30
|
-
<link key={`link-${rel}-${href}`} rel={rel} href={href} />
|
|
31
|
-
);
|
|
32
|
-
});
|
|
33
|
-
return (
|
|
34
|
-
<Head>
|
|
35
|
-
<title>{meta.title}</title>
|
|
36
|
-
<meta name="description" content={meta.description} />
|
|
37
|
-
<meta name="keywords" content={meta.keywords} />
|
|
38
|
-
<meta property="og:title" content={meta.title} />
|
|
39
|
-
<meta property="og:image" content={meta.opengraphThumbnail} />
|
|
40
|
-
<meta property="og:description" content={meta.description} />
|
|
41
|
-
<meta property="og:type" content="website" />
|
|
42
|
-
{canonicalUrl && <link rel="canonical" href={canonicalUrl} />}
|
|
43
|
-
{canonicalUrl && <meta property="og:url" content={canonicalUrl} />}
|
|
44
|
-
<meta name="twitter:card" content="summary_large_image" />
|
|
45
|
-
<meta name="twitter:title" content={meta.title} />
|
|
46
|
-
<meta name="twitter:image" content={meta.opengraphThumbnail} />
|
|
47
|
-
<meta name="twitter:description" content={meta.description} />
|
|
48
|
-
<meta name="generator" content="https://cntrl.site" />
|
|
49
|
-
<link rel="icon" href={meta.favicon} />
|
|
50
|
-
{customFonts.length > 0 && (
|
|
51
|
-
<style
|
|
52
|
-
dangerouslySetInnerHTML={{
|
|
53
|
-
__html: ffGenerator.generate()
|
|
54
|
-
}}
|
|
55
|
-
/>
|
|
56
|
-
)}
|
|
57
|
-
{links}
|
|
58
|
-
{htmlHead}
|
|
59
|
-
|
|
60
|
-
</Head>
|
|
61
|
-
);
|
|
62
|
-
};
|
|
63
|
-
|
|
64
|
-
function buildCanonicalUrl(host: string | null | undefined, slug: string | undefined): string | null {
|
|
65
|
-
if (!host) return null;
|
|
66
|
-
if (slug === undefined) return null;
|
|
67
|
-
const base = parseAsUrl(host);
|
|
68
|
-
if (!base) return null;
|
|
69
|
-
if (!base.pathname.endsWith('/')) base.pathname += '/';
|
|
70
|
-
const relative = stripSlashes(slug);
|
|
71
|
-
const target = relative ? new URL(`${relative}/`, base) : base;
|
|
72
|
-
target.search = '';
|
|
73
|
-
target.hash = '';
|
|
74
|
-
return target.toString();
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
function parseAsUrl(input: string): URL | null {
|
|
78
|
-
return tryParseUrl(input) ?? tryParseUrl(`https://${input}`);
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
function tryParseUrl(input: string): URL | null {
|
|
82
|
-
try {
|
|
83
|
-
return new URL(input);
|
|
84
|
-
} catch {
|
|
85
|
-
return null;
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
function stripSlashes(value: string): string {
|
|
90
|
-
let start = 0;
|
|
91
|
-
let end = value.length;
|
|
92
|
-
while (start < end && value[start] === '/') start += 1;
|
|
93
|
-
while (end > start && value[end - 1] === '/') end -= 1;
|
|
94
|
-
return value.slice(start, end);
|
|
95
|
-
}
|
|
1
|
+
import React, { FC } from 'react';
|
|
2
|
+
import HTMLReactParser, { domToReact } from 'html-react-parser';
|
|
3
|
+
import Head from 'next/head';
|
|
4
|
+
import { FontFaceGenerator, Meta, Project } from '@cntrl-site/sdk';
|
|
5
|
+
|
|
6
|
+
interface Props {
|
|
7
|
+
project: Project;
|
|
8
|
+
meta: Meta;
|
|
9
|
+
slug?: string;
|
|
10
|
+
siteUrl?: string;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export const CNTRLHead: FC<Props> = ({ meta, project, slug, siteUrl }) => {
|
|
14
|
+
const canonicalUrl = buildCanonicalUrl(siteUrl ?? project.primaryDomain, slug);
|
|
15
|
+
const googleFonts: ReturnType<typeof domToReact> = HTMLReactParser(project.fonts.google);
|
|
16
|
+
const adobeFonts: ReturnType<typeof domToReact> = HTMLReactParser(project.fonts.adobe);
|
|
17
|
+
const parsedFonts = {
|
|
18
|
+
...(typeof googleFonts === 'object' ? googleFonts : {}),
|
|
19
|
+
...(typeof adobeFonts === 'object' ? adobeFonts : {})
|
|
20
|
+
};
|
|
21
|
+
const customFonts = project.fonts.custom;
|
|
22
|
+
const htmlHead = HTMLReactParser(project.html.head);
|
|
23
|
+
const ffGenerator = new FontFaceGenerator(customFonts);
|
|
24
|
+
const links = Object.values(parsedFonts as ReturnType<typeof domToReact>).map((value) => {
|
|
25
|
+
if (!value) return null;
|
|
26
|
+
const rel = value?.rel || value.props?.rel;
|
|
27
|
+
const href = value?.href || value.props?.href;
|
|
28
|
+
if (!rel || !href) return null;
|
|
29
|
+
return (
|
|
30
|
+
<link key={`link-${rel}-${href}`} rel={rel} href={href} />
|
|
31
|
+
);
|
|
32
|
+
});
|
|
33
|
+
return (
|
|
34
|
+
<Head>
|
|
35
|
+
<title>{meta.title}</title>
|
|
36
|
+
<meta name="description" content={meta.description} />
|
|
37
|
+
<meta name="keywords" content={meta.keywords} />
|
|
38
|
+
<meta property="og:title" content={meta.title} />
|
|
39
|
+
<meta property="og:image" content={meta.opengraphThumbnail} />
|
|
40
|
+
<meta property="og:description" content={meta.description} />
|
|
41
|
+
<meta property="og:type" content="website" />
|
|
42
|
+
{canonicalUrl && <link rel="canonical" href={canonicalUrl} />}
|
|
43
|
+
{canonicalUrl && <meta property="og:url" content={canonicalUrl} />}
|
|
44
|
+
<meta name="twitter:card" content="summary_large_image" />
|
|
45
|
+
<meta name="twitter:title" content={meta.title} />
|
|
46
|
+
<meta name="twitter:image" content={meta.opengraphThumbnail} />
|
|
47
|
+
<meta name="twitter:description" content={meta.description} />
|
|
48
|
+
<meta name="generator" content="https://cntrl.site" />
|
|
49
|
+
<link rel="icon" href={meta.favicon} />
|
|
50
|
+
{customFonts.length > 0 && (
|
|
51
|
+
<style
|
|
52
|
+
dangerouslySetInnerHTML={{
|
|
53
|
+
__html: ffGenerator.generate()
|
|
54
|
+
}}
|
|
55
|
+
/>
|
|
56
|
+
)}
|
|
57
|
+
{links}
|
|
58
|
+
{htmlHead}
|
|
59
|
+
|
|
60
|
+
</Head>
|
|
61
|
+
);
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
function buildCanonicalUrl(host: string | null | undefined, slug: string | undefined): string | null {
|
|
65
|
+
if (!host) return null;
|
|
66
|
+
if (slug === undefined) return null;
|
|
67
|
+
const base = parseAsUrl(host);
|
|
68
|
+
if (!base) return null;
|
|
69
|
+
if (!base.pathname.endsWith('/')) base.pathname += '/';
|
|
70
|
+
const relative = stripSlashes(slug);
|
|
71
|
+
const target = relative ? new URL(`${relative}/`, base) : base;
|
|
72
|
+
target.search = '';
|
|
73
|
+
target.hash = '';
|
|
74
|
+
return target.toString();
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function parseAsUrl(input: string): URL | null {
|
|
78
|
+
return tryParseUrl(input) ?? tryParseUrl(`https://${input}`);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function tryParseUrl(input: string): URL | null {
|
|
82
|
+
try {
|
|
83
|
+
return new URL(input);
|
|
84
|
+
} catch {
|
|
85
|
+
return null;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function stripSlashes(value: string): string {
|
|
90
|
+
let start = 0;
|
|
91
|
+
let end = value.length;
|
|
92
|
+
while (start < end && value[start] === '/') start += 1;
|
|
93
|
+
while (end > start && value[end - 1] === '/') end -= 1;
|
|
94
|
+
return value.slice(start, end);
|
|
95
|
+
}
|
|
@@ -1,21 +1,21 @@
|
|
|
1
|
-
import React, { FC } from 'react';
|
|
2
|
-
import { getLayoutMediaQuery, Layout } from '@cntrl-site/sdk';
|
|
3
|
-
import JSXStyle from 'styled-jsx/style';
|
|
4
|
-
|
|
5
|
-
export interface LayoutStyleProps {
|
|
6
|
-
id: string;
|
|
7
|
-
layouts: Layout[];
|
|
8
|
-
layoutId: string;
|
|
9
|
-
children?: (layout: Layout) => string;
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
export const LayoutStyle: FC<LayoutStyleProps> = ({ id, layouts, layoutId, children }) => {
|
|
13
|
-
const layout = layouts.find(l => l.id === layoutId)!;
|
|
14
|
-
return (
|
|
15
|
-
<JSXStyle id={id}>{`
|
|
16
|
-
${getLayoutMediaQuery(layoutId, layouts)} {
|
|
17
|
-
${children?.(layout)}
|
|
18
|
-
}
|
|
19
|
-
`}</JSXStyle>
|
|
20
|
-
);
|
|
21
|
-
};
|
|
1
|
+
import React, { FC } from 'react';
|
|
2
|
+
import { getLayoutMediaQuery, Layout } from '@cntrl-site/sdk';
|
|
3
|
+
import JSXStyle from 'styled-jsx/style';
|
|
4
|
+
|
|
5
|
+
export interface LayoutStyleProps {
|
|
6
|
+
id: string;
|
|
7
|
+
layouts: Layout[];
|
|
8
|
+
layoutId: string;
|
|
9
|
+
children?: (layout: Layout) => string;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export const LayoutStyle: FC<LayoutStyleProps> = ({ id, layouts, layoutId, children }) => {
|
|
13
|
+
const layout = layouts.find(l => l.id === layoutId)!;
|
|
14
|
+
return (
|
|
15
|
+
<JSXStyle id={id}>{`
|
|
16
|
+
${getLayoutMediaQuery(layoutId, layouts)} {
|
|
17
|
+
${children?.(layout)}
|
|
18
|
+
}
|
|
19
|
+
`}</JSXStyle>
|
|
20
|
+
);
|
|
21
|
+
};
|