@developer_tribe/react-builder 1.2.23 → 1.2.25
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/dist/attribute-analyser/style/native/useExtractImageStyle.d.ts +5 -5
- package/dist/attribute-analyser/style/native/useExtractTextStyle.d.ts +6 -4
- package/dist/attribute-analyser/style/native/useExtractViewStyle.d.ts +5 -3
- package/dist/attributes-editor/SpecialCategorySection.d.ts +2 -1
- package/dist/attributes-editor/attributesEditorModelTypes.d.ts +2 -0
- package/dist/build-components/BIcon/BIconProps.generated.d.ts +0 -2
- package/dist/build-components/BackgroundImage/BackgroundImageProps.generated.d.ts +0 -2
- package/dist/build-components/Button/ButtonProps.generated.d.ts +0 -2
- package/dist/build-components/Carousel/CarouselProps.generated.d.ts +0 -2
- package/dist/build-components/CarouselButtons/CarouselButtonsProps.generated.d.ts +0 -2
- package/dist/build-components/CarouselDots/CarouselDotsProps.generated.d.ts +0 -2
- package/dist/build-components/CarouselItem/CarouselItemProps.generated.d.ts +0 -2
- package/dist/build-components/CarouselProvider/CarouselProviderProps.generated.d.ts +0 -2
- package/dist/build-components/CountDown/CountDownProps.generated.d.ts +0 -2
- package/dist/build-components/Counter/CounterProps.generated.d.ts +0 -2
- package/dist/build-components/Image/ImageProps.generated.d.ts +0 -2
- package/dist/build-components/Main/MainProps.generated.d.ts +0 -2
- package/dist/build-components/NavigationBarColor/NavigationBarColorProps.generated.d.ts +0 -2
- package/dist/build-components/Onboard/OnboardProps.generated.d.ts +0 -2
- package/dist/build-components/OnboardButton/OnboardButtonProps.generated.d.ts +0 -2
- package/dist/build-components/OnboardButtons/OnboardButtonsProps.generated.d.ts +0 -2
- package/dist/build-components/OnboardDot/OnboardDotProps.generated.d.ts +0 -2
- package/dist/build-components/OnboardFooter/OnboardFooterProps.generated.d.ts +0 -2
- package/dist/build-components/OnboardImage/OnboardImageProps.generated.d.ts +0 -2
- package/dist/build-components/OnboardItem/OnboardItemProps.generated.d.ts +0 -2
- package/dist/build-components/OnboardProvider/OnboardProviderProps.generated.d.ts +0 -2
- package/dist/build-components/OnboardSubtitle/OnboardSubtitleProps.generated.d.ts +0 -2
- package/dist/build-components/OnboardTitle/OnboardTitleProps.generated.d.ts +0 -2
- package/dist/build-components/PaywallBackground/PaywallBackgroundProps.generated.d.ts +0 -2
- package/dist/build-components/PaywallCloseButton/PaywallCloseButtonProps.generated.d.ts +0 -2
- package/dist/build-components/PaywallCounter/PaywallCounterProps.generated.d.ts +0 -2
- package/dist/build-components/PaywallOptions/PaywallOptionsProps.generated.d.ts +0 -2
- package/dist/build-components/PaywallProvider/PaywallProviderProps.generated.d.ts +0 -2
- package/dist/build-components/PaywallSubscribeButton/PaywallSubscribeButtonProps.generated.d.ts +0 -2
- package/dist/build-components/RadioButton/RadioButtonProps.generated.d.ts +0 -2
- package/dist/build-components/Separator/SeparatorProps.generated.d.ts +0 -2
- package/dist/build-components/StatusBarColor/StatusBarColorProps.generated.d.ts +0 -2
- package/dist/build-components/Text/TextProps.generated.d.ts +0 -2
- package/dist/build-components/patterns.generated.d.ts +80 -66
- package/dist/index.cjs.js +2 -2
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.d.ts +3 -1
- package/dist/index.esm.js +2 -2
- package/dist/index.esm.js.map +1 -1
- package/dist/index.web.cjs.js +3 -3
- package/dist/index.web.cjs.js.map +1 -1
- package/dist/index.web.esm.js +3 -3
- package/dist/index.web.esm.js.map +1 -1
- package/dist/pages/ProjectPage.d.ts +2 -2
- package/dist/pages/projectPageUtils.d.ts +7 -1
- package/dist/types/Project.d.ts +6 -0
- package/dist/utils/attributeStyle.d.ts +12 -0
- package/dist/utils/patterns.d.ts +2 -0
- package/package.json +6 -1
- package/scripts/prebuild/utils/validateAllComponentsOrThrow.js +11 -2
- package/src/AttributesEditor.tsx +15 -4
- package/src/assets/meta.json +1 -1
- package/src/assets/samples/paywall-1.json +5 -5
- package/src/assets/samples/paywall-2.json +5 -5
- package/src/assets/samples/paywall-app-delete-offer.json +0 -1
- package/src/assets/samples/paywall-app-open-offer.json +0 -1
- package/src/assets/samples/paywall-back-offer.json +0 -1
- package/src/assets/samples/paywall-notification-offer.json +0 -1
- package/src/assets/samples/simple-2.json +0 -1
- package/src/attribute-analyser/style/native/useExtractImageStyle.ts +19 -15
- package/src/attribute-analyser/style/native/useExtractTextStyle.ts +25 -15
- package/src/attribute-analyser/style/native/useExtractViewStyle.ts +19 -21
- package/src/attributes-editor/AttributesEditorView.tsx +43 -36
- package/src/attributes-editor/SpecialCategorySection.tsx +5 -3
- package/src/attributes-editor/attributesEditorModelTypes.ts +2 -0
- package/src/attributes-editor/useAttributesEditorModel.ts +6 -0
- package/src/build-components/BIcon/BIconProps.generated.ts +0 -2
- package/src/build-components/BIcon/pattern.json +5 -3
- package/src/build-components/BackgroundImage/BackgroundImageProps.generated.ts +0 -2
- package/src/build-components/BackgroundImage/pattern.json +12 -4
- package/src/build-components/Button/ButtonProps.generated.ts +0 -2
- package/src/build-components/Button/pattern.json +5 -3
- package/src/build-components/Carousel/CarouselProps.generated.ts +0 -2
- package/src/build-components/Carousel/pattern.json +11 -5
- package/src/build-components/CarouselButtons/CarouselButtonsProps.generated.ts +0 -2
- package/src/build-components/CarouselButtons/pattern.json +11 -4
- package/src/build-components/CarouselDots/CarouselDotsProps.generated.ts +0 -2
- package/src/build-components/CarouselDots/pattern.json +5 -3
- package/src/build-components/CarouselItem/CarouselItemProps.generated.ts +0 -2
- package/src/build-components/CarouselItem/pattern.json +6 -6
- package/src/build-components/CarouselProvider/CarouselProviderProps.generated.ts +0 -2
- package/src/build-components/CarouselProvider/pattern.json +6 -5
- package/src/build-components/CountDown/CountDownProps.generated.ts +0 -2
- package/src/build-components/CountDown/pattern.json +6 -3
- package/src/build-components/Counter/CounterProps.generated.ts +0 -2
- package/src/build-components/Counter/pattern.json +5 -1
- package/src/build-components/Image/ImageProps.generated.ts +0 -2
- package/src/build-components/Image/pattern.json +7 -2
- package/src/build-components/Main/MainProps.generated.ts +0 -2
- package/src/build-components/Main/pattern.json +5 -3
- package/src/build-components/NavigationBarColor/NavigationBarColorProps.generated.ts +0 -2
- package/src/build-components/NavigationBarColor/pattern.json +5 -3
- package/src/build-components/Onboard/OnboardProps.generated.ts +0 -2
- package/src/build-components/Onboard/pattern.json +9 -7
- package/src/build-components/OnboardButton/OnboardButton.tsx +19 -5
- package/src/build-components/OnboardButton/OnboardButtonProps.generated.ts +0 -2
- package/src/build-components/OnboardButton/pattern.json +16 -5
- package/src/build-components/OnboardButtons/OnboardButtonsProps.generated.ts +0 -2
- package/src/build-components/OnboardButtons/pattern.json +17 -6
- package/src/build-components/OnboardDot/OnboardDotProps.generated.ts +0 -2
- package/src/build-components/OnboardDot/pattern.json +5 -3
- package/src/build-components/OnboardFooter/OnboardFooter.tsx +15 -4
- package/src/build-components/OnboardFooter/OnboardFooterProps.generated.ts +0 -2
- package/src/build-components/OnboardFooter/pattern.json +5 -3
- package/src/build-components/OnboardImage/OnboardImageProps.generated.ts +0 -2
- package/src/build-components/OnboardImage/pattern.json +7 -3
- package/src/build-components/OnboardItem/OnboardItemProps.generated.ts +0 -2
- package/src/build-components/OnboardItem/pattern.json +13 -5
- package/src/build-components/OnboardProvider/OnboardProviderProps.generated.ts +0 -2
- package/src/build-components/OnboardProvider/pattern.json +10 -4
- package/src/build-components/OnboardSubtitle/OnboardSubtitleProps.generated.ts +0 -2
- package/src/build-components/OnboardSubtitle/pattern.json +7 -6
- package/src/build-components/OnboardTitle/OnboardTitleProps.generated.ts +0 -2
- package/src/build-components/OnboardTitle/pattern.json +7 -6
- package/src/build-components/PaywallBackground/PaywallBackgroundProps.generated.ts +0 -2
- package/src/build-components/PaywallBackground/pattern.json +5 -5
- package/src/build-components/PaywallCloseButton/PaywallCloseButtonProps.generated.ts +0 -2
- package/src/build-components/PaywallCloseButton/pattern.json +6 -6
- package/src/build-components/PaywallCounter/PaywallCounterProps.generated.ts +0 -2
- package/src/build-components/PaywallCounter/pattern.json +6 -3
- package/src/build-components/PaywallOptions/PaywallOptionsProps.generated.ts +0 -2
- package/src/build-components/PaywallOptions/pattern.json +6 -6
- package/src/build-components/PaywallProvider/PaywallProviderProps.generated.ts +0 -2
- package/src/build-components/PaywallProvider/pattern.json +5 -3
- package/src/build-components/PaywallSubscribeButton/PaywallSubscribeButtonProps.generated.ts +0 -2
- package/src/build-components/PaywallSubscribeButton/pattern.json +6 -6
- package/src/build-components/RadioButton/RadioButtonProps.generated.ts +0 -2
- package/src/build-components/RadioButton/pattern.json +5 -3
- package/src/build-components/Separator/SeparatorProps.generated.ts +0 -2
- package/src/build-components/Separator/pattern.json +5 -3
- package/src/build-components/StatusBarColor/StatusBarColorProps.generated.ts +0 -2
- package/src/build-components/StatusBarColor/pattern.json +5 -3
- package/src/build-components/Text/TextProps.generated.ts +0 -2
- package/src/build-components/Text/pattern.json +11 -5
- package/src/build-components/View/pattern.json +18 -4
- package/src/build-components/patterns.generated.ts +72 -66
- package/src/components/AttributesEditorPanel.tsx +48 -32
- package/src/components/Builder.tsx +4 -1
- package/src/components/BuilderProvider.tsx +6 -6
- package/src/index.ts +4 -1
- package/src/pages/ProjectPage.tsx +45 -22
- package/src/pages/projectPageUtils.ts +15 -1
- package/src/types/Project.ts +7 -0
- package/src/utils/attributeStyle.ts +78 -0
- package/src/utils/patterns.ts +2 -0
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { useCallback, useRef } from 'react';
|
|
1
2
|
import { AttributesEditor } from '../AttributesEditor';
|
|
2
3
|
import type { Node, NodeData } from '../types/Node';
|
|
3
4
|
import type { ProjectColors } from '../types/Project';
|
|
@@ -11,6 +12,32 @@ interface AttributesEditorPanelProps {
|
|
|
11
12
|
projectColors?: ProjectColors;
|
|
12
13
|
}
|
|
13
14
|
|
|
15
|
+
function replaceNode(root: Node, target: Node, next: Node): Node {
|
|
16
|
+
if (root === target) return next;
|
|
17
|
+
if (root === null || root === undefined) return root;
|
|
18
|
+
if (typeof root === 'string') return root;
|
|
19
|
+
if (Array.isArray(root)) {
|
|
20
|
+
let changed = false;
|
|
21
|
+
const arr = root.map((item) => {
|
|
22
|
+
const r = replaceNode(item, target, next);
|
|
23
|
+
if (r !== item) changed = true;
|
|
24
|
+
return r;
|
|
25
|
+
});
|
|
26
|
+
return changed ? arr : root;
|
|
27
|
+
}
|
|
28
|
+
const data = root as NodeData;
|
|
29
|
+
if ('children' in data) {
|
|
30
|
+
const prev = data.children;
|
|
31
|
+
const replaced = Array.isArray(prev)
|
|
32
|
+
? prev.map((c: Node) => replaceNode(c, target, next))
|
|
33
|
+
: replaceNode(prev as Node, target, next);
|
|
34
|
+
if (replaced !== prev) {
|
|
35
|
+
return { ...data, children: replaced } as Node;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
return root;
|
|
39
|
+
}
|
|
40
|
+
|
|
14
41
|
export function AttributesEditorPanel({
|
|
15
42
|
attributes,
|
|
16
43
|
onChange,
|
|
@@ -21,6 +48,27 @@ export function AttributesEditorPanel({
|
|
|
21
48
|
current: s.current,
|
|
22
49
|
setCurrent: s.setCurrent,
|
|
23
50
|
}));
|
|
51
|
+
|
|
52
|
+
// Stable refs so the onChange callback doesn't change identity every render.
|
|
53
|
+
const attributesRef = useRef(attributes);
|
|
54
|
+
attributesRef.current = attributes;
|
|
55
|
+
const currentRef = useRef(current);
|
|
56
|
+
currentRef.current = current;
|
|
57
|
+
const onChangeRef = useRef(onChange);
|
|
58
|
+
onChangeRef.current = onChange;
|
|
59
|
+
|
|
60
|
+
const handleAttributesChange = useCallback(
|
|
61
|
+
(next: Node) => {
|
|
62
|
+
const root = attributesRef.current as Node;
|
|
63
|
+
const target = currentRef.current;
|
|
64
|
+
if (!target) return;
|
|
65
|
+
const updated = replaceNode(root, target, next);
|
|
66
|
+
onChangeRef.current(updated);
|
|
67
|
+
setCurrent(next);
|
|
68
|
+
},
|
|
69
|
+
[setCurrent],
|
|
70
|
+
);
|
|
71
|
+
|
|
24
72
|
if (!current) return null;
|
|
25
73
|
|
|
26
74
|
const currentKey =
|
|
@@ -33,38 +81,6 @@ export function AttributesEditorPanel({
|
|
|
33
81
|
: null;
|
|
34
82
|
const nodeForEditor = resolvedCurrent ?? current;
|
|
35
83
|
|
|
36
|
-
function replaceNode(root: Node, target: Node, next: Node): Node {
|
|
37
|
-
if (root === target) return next;
|
|
38
|
-
if (root === null || root === undefined) return root;
|
|
39
|
-
if (typeof root === 'string') return root;
|
|
40
|
-
if (Array.isArray(root)) {
|
|
41
|
-
let changed = false;
|
|
42
|
-
const arr = root.map((item) => {
|
|
43
|
-
const r = replaceNode(item, target, next);
|
|
44
|
-
if (r !== item) changed = true;
|
|
45
|
-
return r;
|
|
46
|
-
});
|
|
47
|
-
return changed ? arr : root;
|
|
48
|
-
}
|
|
49
|
-
const data = root as NodeData;
|
|
50
|
-
if ('children' in data) {
|
|
51
|
-
const prev = data.children;
|
|
52
|
-
const replaced = Array.isArray(prev)
|
|
53
|
-
? prev.map((c: Node) => replaceNode(c, target, next))
|
|
54
|
-
: replaceNode(prev as Node, target, next);
|
|
55
|
-
if (replaced !== prev) {
|
|
56
|
-
return { ...data, children: replaced } as Node;
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
return root;
|
|
60
|
-
}
|
|
61
|
-
const handleAttributesChange = (next: Node) => {
|
|
62
|
-
const root = attributes as Node;
|
|
63
|
-
const updated = replaceNode(root, current, next);
|
|
64
|
-
onChange(updated);
|
|
65
|
-
setCurrent(next);
|
|
66
|
-
};
|
|
67
|
-
|
|
68
84
|
return (
|
|
69
85
|
<div className="attributes-editor-panel">
|
|
70
86
|
<AttributesEditor
|
|
@@ -603,7 +603,10 @@ function getNodeLabel(node: Node): string {
|
|
|
603
603
|
if (isNodeNullOrUndefined(node)) return 'Empty';
|
|
604
604
|
if (isNodeString(node)) return node as string;
|
|
605
605
|
if (isNodeArray(node)) return 'Collection';
|
|
606
|
-
|
|
606
|
+
const nodeData = node as NodeData<NodeDefaultAttribute>;
|
|
607
|
+
const title = (nodeData.attributes as Record<string, unknown>)?.title;
|
|
608
|
+
if (typeof title === 'string' && title.trim().length > 0) return title;
|
|
609
|
+
return nodeData.type ?? 'Node';
|
|
607
610
|
}
|
|
608
611
|
|
|
609
612
|
function findNodePath(root: Node, target: Node): Node[] {
|
|
@@ -43,7 +43,7 @@ type BuilderProviderProps = {
|
|
|
43
43
|
children: React.ReactNode;
|
|
44
44
|
};
|
|
45
45
|
|
|
46
|
-
const
|
|
46
|
+
const builderContext = createContext<BuilderProviderParams | undefined>(
|
|
47
47
|
undefined,
|
|
48
48
|
);
|
|
49
49
|
|
|
@@ -91,7 +91,7 @@ export function BuilderProvider({ params, children }: BuilderProviderProps) {
|
|
|
91
91
|
);
|
|
92
92
|
|
|
93
93
|
return (
|
|
94
|
-
<
|
|
94
|
+
<builderContext.Provider value={value}>{children}</builderContext.Provider>
|
|
95
95
|
);
|
|
96
96
|
}
|
|
97
97
|
|
|
@@ -119,10 +119,10 @@ const defaultProjectColors: Readonly<ProjectColors> = {
|
|
|
119
119
|
FOOTER_TEXT: '#81838F',
|
|
120
120
|
},
|
|
121
121
|
dark: {
|
|
122
|
-
TEXT: '#
|
|
123
|
-
BACKGROUND: '#
|
|
122
|
+
TEXT: '#E9EBF9',
|
|
123
|
+
BACKGROUND: '#080A17',
|
|
124
124
|
ICON: '#0450E2',
|
|
125
|
-
LINE: '#
|
|
125
|
+
LINE: '#161827',
|
|
126
126
|
ONBOARD_TITLE: '#FDFDFD',
|
|
127
127
|
ONBOARD_SUBTITLE: '#C7C7C7',
|
|
128
128
|
BUTTON_SECONDARY_TEXT: '#A9AAAC',
|
|
@@ -133,7 +133,7 @@ const defaultProjectColors: Readonly<ProjectColors> = {
|
|
|
133
133
|
|
|
134
134
|
export function useBuilderParams(): Readonly<BuilderProviderParams> {
|
|
135
135
|
return (
|
|
136
|
-
useContext(
|
|
136
|
+
useContext(builderContext) ?? {
|
|
137
137
|
products: [],
|
|
138
138
|
benefits: {},
|
|
139
139
|
platform: 'web',
|
package/src/index.ts
CHANGED
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
// Types
|
|
14
14
|
export type { TargetedScreenSize } from './types/TargetedScreenSize';
|
|
15
15
|
export type { Node, NodeData, NodeDefaultAttribute } from './types/Node';
|
|
16
|
-
export type { Project, ProjectColors } from './types/Project';
|
|
16
|
+
export type { Project, ProjectColors, ProjectMeta } from './types/Project';
|
|
17
17
|
export type { Device } from './types/Device';
|
|
18
18
|
export type { AppConfig, Localication } from './types/PreviewConfig';
|
|
19
19
|
export { defaultAppConfig } from './types/PreviewConfig';
|
|
@@ -91,3 +91,6 @@ export {
|
|
|
91
91
|
} from './assets/samples/getSamples';
|
|
92
92
|
export { getDefaultProject } from './utils/getDefaultProject';
|
|
93
93
|
export type { EventObjectGenerated } from './build-components/OnboardButton/OnboardButtonProps.generated';
|
|
94
|
+
|
|
95
|
+
export { parseColor } from './utils/parseColor';
|
|
96
|
+
export type { ParseColorOptions } from './utils/parseColor';
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { useCallback, useEffect, useState } from 'react';
|
|
1
|
+
import { useCallback, useEffect, useRef, useState } from 'react';
|
|
2
2
|
import type { Node, NodeData } from '../types/Node';
|
|
3
|
-
import type { Project, ProjectColors } from '../types/Project';
|
|
3
|
+
import type { Project, ProjectColors, ProjectMeta } from '../types/Project';
|
|
4
4
|
import { ToastContainer, toast } from 'react-toastify';
|
|
5
5
|
import { RenderPage } from '../RenderPage';
|
|
6
6
|
import { EditorHeader } from '../components/EditorHeader';
|
|
@@ -31,16 +31,16 @@ import {
|
|
|
31
31
|
deleteNodeFromTree,
|
|
32
32
|
findNodeByKey,
|
|
33
33
|
isNodeRecord,
|
|
34
|
-
nodeHasChild,
|
|
35
34
|
} from '../utils/nodeTree';
|
|
36
35
|
import type { Fonts } from '../types/Fonts';
|
|
37
36
|
import { useProjectFonts } from '../hooks/useProjectFonts';
|
|
38
|
-
import { resolveProjectForSave } from './projectPageUtils';
|
|
37
|
+
import { resolveProjectForSave, toProjectMeta } from './projectPageUtils';
|
|
39
38
|
import { getDefaultProject } from '../utils/getDefaultProject';
|
|
40
39
|
import { CURRENT_PROJECT_VERSION } from '../migrations/migratePipe';
|
|
41
40
|
export type ProjectPageProps = {
|
|
42
41
|
project: Project;
|
|
43
|
-
|
|
42
|
+
// TODO: Tüm onSaveProject call-site'ları toProjectMeta kullanacak şekilde migrate et
|
|
43
|
+
onSaveProject: (project: ProjectMeta) => void;
|
|
44
44
|
appConfig?: AppConfig;
|
|
45
45
|
logLevel?: LogLevel;
|
|
46
46
|
projectColors?: ProjectColors;
|
|
@@ -151,14 +151,15 @@ export function ProjectPage({
|
|
|
151
151
|
return;
|
|
152
152
|
}
|
|
153
153
|
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
154
|
+
// Refresh current's reference so the Builder re-renders the updated subtree.
|
|
155
|
+
// deleteNodeFromTree creates new objects for ancestors of the removed node,
|
|
156
|
+
// so the old `current` reference becomes stale when the deleted node was a
|
|
157
|
+
// descendant of `current`.
|
|
158
|
+
if (isNodeRecord(current) && current.key) {
|
|
159
|
+
const nextCurrent = findNodeByKey(updated, current.key);
|
|
160
|
+
setCurrent(nextCurrent ?? updated);
|
|
161
|
+
} else {
|
|
162
|
+
setCurrent(updated);
|
|
162
163
|
}
|
|
163
164
|
},
|
|
164
165
|
[editorData, current],
|
|
@@ -226,8 +227,26 @@ export function ProjectPage({
|
|
|
226
227
|
return () => clearTimeout(timer);
|
|
227
228
|
}, [activeProject.data]);
|
|
228
229
|
|
|
230
|
+
// Ref for the full project (used inside effect for migration check etc.)
|
|
231
|
+
const activeProjectRef = useRef(activeProject);
|
|
232
|
+
activeProjectRef.current = activeProject;
|
|
233
|
+
// Ref for current editorData so the effect can compare without depending on it.
|
|
234
|
+
const editorDataRef = useRef(editorData);
|
|
235
|
+
editorDataRef.current = editorData;
|
|
236
|
+
|
|
229
237
|
useEffect(() => {
|
|
230
238
|
try {
|
|
239
|
+
const currentProject = activeProjectRef.current;
|
|
240
|
+
const projectData = currentProject.data;
|
|
241
|
+
|
|
242
|
+
// Guard: skip reinit when the incoming project data is the same reference
|
|
243
|
+
// we already hold in editorData. After a save round-trip the parent pushes
|
|
244
|
+
// a new project object whose .data is the very same reference as editorData.
|
|
245
|
+
// Re-processing it would flash a loading state and discard in-flight changes.
|
|
246
|
+
if (projectData != null && projectData === editorDataRef.current) {
|
|
247
|
+
return;
|
|
248
|
+
}
|
|
249
|
+
|
|
231
250
|
// Reset to "loading" immediately on project change so the loader is shown
|
|
232
251
|
// until a valid node is available (and for at least 2 seconds).
|
|
233
252
|
if (!isEmptyProjectData) {
|
|
@@ -237,7 +256,7 @@ export function ProjectPage({
|
|
|
237
256
|
setValidationError(null);
|
|
238
257
|
setValidationErrorStack(null);
|
|
239
258
|
// Version gate: if project is older than the current schema, show migration UI.
|
|
240
|
-
const pipe = getMigrationPipe(
|
|
259
|
+
const pipe = getMigrationPipe(currentProject);
|
|
241
260
|
if (!bypassValidation && pipe.required) {
|
|
242
261
|
setMigrationGate(pipe);
|
|
243
262
|
setEditorData(null);
|
|
@@ -248,8 +267,8 @@ export function ProjectPage({
|
|
|
248
267
|
if (bypassValidation) {
|
|
249
268
|
// Best-effort: let the user continue with the raw data even if invalid.
|
|
250
269
|
// This may still crash the preview, but it unblocks users for debugging.
|
|
251
|
-
setEditorData(
|
|
252
|
-
setCurrent(
|
|
270
|
+
setEditorData(currentProject.data as Node);
|
|
271
|
+
setCurrent(currentProject.data as Node);
|
|
253
272
|
return;
|
|
254
273
|
}
|
|
255
274
|
if (isEmptyProjectData) {
|
|
@@ -258,7 +277,7 @@ export function ProjectPage({
|
|
|
258
277
|
return;
|
|
259
278
|
}
|
|
260
279
|
|
|
261
|
-
const inputNode: Node =
|
|
280
|
+
const inputNode: Node = currentProject.data as Node;
|
|
262
281
|
|
|
263
282
|
const processed = analyseAndProccess(inputNode);
|
|
264
283
|
if (!processed) return;
|
|
@@ -275,7 +294,9 @@ export function ProjectPage({
|
|
|
275
294
|
setEditorData(null);
|
|
276
295
|
setCurrent(null);
|
|
277
296
|
}
|
|
278
|
-
|
|
297
|
+
// Note: depend on activeProject.data (not activeProject object) to avoid
|
|
298
|
+
// reinit when the project wrapper changes but data is the same reference.
|
|
299
|
+
}, [activeProject.data, isEmptyProjectData, bypassValidation, setCurrent]);
|
|
279
300
|
|
|
280
301
|
const showLoading =
|
|
281
302
|
!isEmptyProjectData && (editorData === null || !minLoadingDelayDone);
|
|
@@ -300,13 +321,15 @@ export function ProjectPage({
|
|
|
300
321
|
if (onSaveProjectColors && resolvedProjectColors) {
|
|
301
322
|
onSaveProjectColors(resolvedProjectColors);
|
|
302
323
|
}
|
|
303
|
-
|
|
324
|
+
const projectMeta = toProjectMeta(
|
|
304
325
|
resolveProjectForSave({
|
|
305
326
|
project,
|
|
306
327
|
overrideProject,
|
|
307
328
|
data: editorData,
|
|
308
329
|
}),
|
|
309
330
|
);
|
|
331
|
+
logger.info('ProjectPage', 'saving project meta', projectMeta);
|
|
332
|
+
onSaveProject(projectMeta);
|
|
310
333
|
toast.success('Saved');
|
|
311
334
|
} catch (e) {
|
|
312
335
|
logger.error('ProjectPage', 'save project failed', e);
|
|
@@ -377,7 +400,7 @@ export function ProjectPage({
|
|
|
377
400
|
|
|
378
401
|
const { project: migratedProject } =
|
|
379
402
|
runProjectMigrations(projectForMigration);
|
|
380
|
-
onSaveProject(migratedProject);
|
|
403
|
+
onSaveProject(toProjectMeta(migratedProject));
|
|
381
404
|
setOverrideProject(migratedProject);
|
|
382
405
|
setBypassValidation(true);
|
|
383
406
|
setMigrationGate(null);
|
|
@@ -430,7 +453,7 @@ export function ProjectPage({
|
|
|
430
453
|
|
|
431
454
|
// This action only fixes project metadata. It intentionally does NOT
|
|
432
455
|
// validate/normalize node data (it might still be invalid).
|
|
433
|
-
onSaveProject(fixedProject);
|
|
456
|
+
onSaveProject(toProjectMeta(fixedProject));
|
|
434
457
|
setOverrideProject(fixedProject);
|
|
435
458
|
setBypassValidation(false);
|
|
436
459
|
setMigrationGate(null);
|
|
@@ -488,7 +511,7 @@ export function ProjectPage({
|
|
|
488
511
|
data: nodeCandidate as Project['data'],
|
|
489
512
|
};
|
|
490
513
|
|
|
491
|
-
onSaveProject(nextProject);
|
|
514
|
+
onSaveProject(toProjectMeta(nextProject));
|
|
492
515
|
setOverrideProject(nextProject);
|
|
493
516
|
setBypassValidation(false);
|
|
494
517
|
setValidationError(null);
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { Project } from '../types/Project';
|
|
1
|
+
import type { Project, ProjectMeta } from '../types/Project';
|
|
2
2
|
import type { Node } from '../types/Node';
|
|
3
3
|
import { getDefaultProject } from '../utils/getDefaultProject';
|
|
4
4
|
|
|
@@ -13,3 +13,17 @@ export function resolveProjectForSave(args: {
|
|
|
13
13
|
data: args.data,
|
|
14
14
|
});
|
|
15
15
|
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Strips a full Project down to its essential persistence fields.
|
|
19
|
+
* Use before handing the project to onSaveProject so consumers only
|
|
20
|
+
* receive the canonical metadata (name, version, type, data).
|
|
21
|
+
*/
|
|
22
|
+
export function toProjectMeta(project: Project): ProjectMeta {
|
|
23
|
+
return {
|
|
24
|
+
name: project.name,
|
|
25
|
+
version: project.version,
|
|
26
|
+
...(project.type ? { type: project.type } : {}),
|
|
27
|
+
data: project.data,
|
|
28
|
+
};
|
|
29
|
+
}
|
package/src/types/Project.ts
CHANGED
|
@@ -29,6 +29,13 @@ export interface ProjectBase<T> {
|
|
|
29
29
|
|
|
30
30
|
export interface Project extends ProjectBase<Node> {}
|
|
31
31
|
|
|
32
|
+
/**
|
|
33
|
+
* Lightweight subset of Project containing only the essential metadata
|
|
34
|
+
* needed for persistence (name, version, type, data).
|
|
35
|
+
* Excludes runtime/editor-only fields like appConfig and projectColors.
|
|
36
|
+
*/
|
|
37
|
+
export type ProjectMeta = Pick<Project, 'name' | 'version' | 'type' | 'data'>;
|
|
38
|
+
|
|
32
39
|
export type LogLevel = 'NONE' | 'ERROR' | 'WARN' | 'INFO' | 'VERBOSE';
|
|
33
40
|
|
|
34
41
|
export type LogSource = string;
|
|
@@ -24,3 +24,81 @@ export function toAttributeRecord(
|
|
|
24
24
|
function isPlainObject(value: unknown): value is Record<string, unknown> {
|
|
25
25
|
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
26
26
|
}
|
|
27
|
+
|
|
28
|
+
// ---------------------------------------------------------------------------
|
|
29
|
+
// Style-key filtering – separates visual style keys from non-style attributes
|
|
30
|
+
// ---------------------------------------------------------------------------
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* All attribute keys that represent visual style properties.
|
|
34
|
+
* Used to separate style keys from non-style (behavioral/content) keys.
|
|
35
|
+
*
|
|
36
|
+
* Keep in sync with ViewStyleGenerated, TextStyleGenerated, and ImageStyleGenerated.
|
|
37
|
+
*/
|
|
38
|
+
const STYLE_ATTR_KEYS_LIST = [
|
|
39
|
+
// Style bag containers
|
|
40
|
+
'style',
|
|
41
|
+
'styles',
|
|
42
|
+
// Layout
|
|
43
|
+
'flexDirection',
|
|
44
|
+
'flexWrap',
|
|
45
|
+
'alignItems',
|
|
46
|
+
'justifyContent',
|
|
47
|
+
'gap',
|
|
48
|
+
// Padding
|
|
49
|
+
'padding',
|
|
50
|
+
'paddingHorizontal',
|
|
51
|
+
'paddingVertical',
|
|
52
|
+
'paddingTop',
|
|
53
|
+
'paddingBottom',
|
|
54
|
+
'paddingLeft',
|
|
55
|
+
'paddingRight',
|
|
56
|
+
// Margin
|
|
57
|
+
'margin',
|
|
58
|
+
'marginHorizontal',
|
|
59
|
+
'marginVertical',
|
|
60
|
+
'marginTop',
|
|
61
|
+
'marginBottom',
|
|
62
|
+
'marginLeft',
|
|
63
|
+
'marginRight',
|
|
64
|
+
// Background & border
|
|
65
|
+
'backgroundColor',
|
|
66
|
+
'borderRadius',
|
|
67
|
+
// Sizing
|
|
68
|
+
'width',
|
|
69
|
+
'minWidth',
|
|
70
|
+
'maxWidth',
|
|
71
|
+
'height',
|
|
72
|
+
'minHeight',
|
|
73
|
+
'maxHeight',
|
|
74
|
+
// Flex & position
|
|
75
|
+
'flex',
|
|
76
|
+
'position',
|
|
77
|
+
'top',
|
|
78
|
+
'bottom',
|
|
79
|
+
'left',
|
|
80
|
+
'right',
|
|
81
|
+
'zIndex',
|
|
82
|
+
// Text
|
|
83
|
+
'color',
|
|
84
|
+
'fontSize',
|
|
85
|
+
'fontFamily',
|
|
86
|
+
'fontWeight',
|
|
87
|
+
'textAlign',
|
|
88
|
+
// Image
|
|
89
|
+
'resizeMode',
|
|
90
|
+
] as const;
|
|
91
|
+
|
|
92
|
+
/** Type-level union of all style attribute keys. Use with `Omit<T, StyleAttrKey>`. */
|
|
93
|
+
export type StyleAttrKey = (typeof STYLE_ATTR_KEYS_LIST)[number];
|
|
94
|
+
|
|
95
|
+
const STYLE_ATTR_KEYS: ReadonlySet<string> = new Set(STYLE_ATTR_KEYS_LIST);
|
|
96
|
+
|
|
97
|
+
/** Strips all visual-style keys from an attributes record, returning only non-style keys. */
|
|
98
|
+
export function stripStyleKeys(
|
|
99
|
+
attrs: Record<string, unknown>,
|
|
100
|
+
): Record<string, unknown> {
|
|
101
|
+
return Object.fromEntries(
|
|
102
|
+
Object.entries(attrs).filter(([key]) => !STYLE_ATTR_KEYS.has(key)),
|
|
103
|
+
);
|
|
104
|
+
}
|