@valbuild/ui 0.13.4 → 0.17.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/dist/valbuild-ui.cjs.d.ts +13 -18
- package/dist/valbuild-ui.cjs.js +7624 -1718
- package/dist/valbuild-ui.esm.js +7625 -1719
- package/package.json +5 -2
- package/src/assets/icons/ImageIcon.tsx +15 -7
- package/src/assets/icons/Section.tsx +41 -0
- package/src/assets/icons/TextIcon.tsx +20 -0
- package/src/components/Button.tsx +18 -7
- package/src/components/DraggableList.stories.tsx +20 -0
- package/src/components/DraggableList.tsx +95 -0
- package/src/components/Dropdown.tsx +2 -0
- package/src/components/ExpandLogo.tsx +72 -0
- package/src/components/RichTextEditor/Plugins/Toolbar.tsx +1 -16
- package/src/components/RichTextEditor/RichTextEditor.tsx +2 -2
- package/src/components/User.tsx +17 -0
- package/src/components/ValMenu.tsx +40 -0
- package/src/components/ValOverlay.tsx +513 -29
- package/src/components/ValOverlayContext.tsx +63 -0
- package/src/components/ValWindow.stories.tsx +3 -3
- package/src/components/ValWindow.tsx +26 -18
- package/src/components/dashboard/DashboardButton.tsx +25 -0
- package/src/components/dashboard/DashboardDropdown.tsx +59 -0
- package/src/components/dashboard/Dropdown.stories.tsx +11 -0
- package/src/components/dashboard/Dropdown.tsx +70 -0
- package/src/components/dashboard/FormGroup.stories.tsx +37 -0
- package/src/components/dashboard/FormGroup.tsx +36 -0
- package/src/components/dashboard/Grid.stories.tsx +52 -0
- package/src/components/dashboard/Grid.tsx +126 -0
- package/src/components/dashboard/Grid2.stories.tsx +56 -0
- package/src/components/dashboard/Grid2.tsx +72 -0
- package/src/components/dashboard/Tree.stories.tsx +91 -0
- package/src/components/dashboard/Tree.tsx +72 -0
- package/src/components/dashboard/ValDashboard.tsx +148 -0
- package/src/components/dashboard/ValDashboardEditor.tsx +269 -0
- package/src/components/dashboard/ValDashboardGrid.tsx +142 -0
- package/src/components/dashboard/ValTreeNavigator.tsx +253 -0
- package/src/components/forms/Form.tsx +2 -2
- package/src/components/forms/{TextForm.tsx → TextArea.tsx} +5 -3
- package/src/dto/SerializedSchema.ts +69 -0
- package/src/dto/Session.ts +12 -0
- package/src/dto/SessionMode.ts +5 -0
- package/src/dto/Tree.ts +18 -0
- package/src/exports.ts +1 -0
- package/src/utils/Remote.ts +15 -0
- package/src/utils/resolvePath.ts +33 -0
- package/tailwind.config.js +20 -1
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from "@storybook/react";
|
|
2
|
+
import { Tree } from "./Tree";
|
|
3
|
+
|
|
4
|
+
const meta: Meta<typeof Tree> = { component: Tree };
|
|
5
|
+
|
|
6
|
+
export default meta;
|
|
7
|
+
type Story = StoryObj<typeof Tree>;
|
|
8
|
+
|
|
9
|
+
type Node = {
|
|
10
|
+
path: string;
|
|
11
|
+
type: "string" | "image" | "section";
|
|
12
|
+
children: Node[];
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
const nodes: Node[] = [
|
|
16
|
+
{
|
|
17
|
+
path: "Main nav",
|
|
18
|
+
type: "section",
|
|
19
|
+
children: [],
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
path: "H1",
|
|
23
|
+
type: "string",
|
|
24
|
+
children: [
|
|
25
|
+
{
|
|
26
|
+
path: "Section 3",
|
|
27
|
+
type: "string",
|
|
28
|
+
children: [],
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
path: "Section 4",
|
|
32
|
+
type: "section",
|
|
33
|
+
children: [
|
|
34
|
+
{
|
|
35
|
+
path: "Section 5",
|
|
36
|
+
type: "string",
|
|
37
|
+
children: [],
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
path: "Section 6",
|
|
41
|
+
type: "section",
|
|
42
|
+
children: [
|
|
43
|
+
{
|
|
44
|
+
path: "Section 7",
|
|
45
|
+
type: "string",
|
|
46
|
+
children: [],
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
path: "Image 1",
|
|
50
|
+
type: "image",
|
|
51
|
+
children: [],
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
path: "Image 2",
|
|
55
|
+
type: "image",
|
|
56
|
+
children: [],
|
|
57
|
+
},
|
|
58
|
+
],
|
|
59
|
+
},
|
|
60
|
+
],
|
|
61
|
+
},
|
|
62
|
+
],
|
|
63
|
+
},
|
|
64
|
+
];
|
|
65
|
+
|
|
66
|
+
const renderNodes = (node: Node) => (
|
|
67
|
+
<Tree.Node path={node.path} type={node.type}>
|
|
68
|
+
{node.children.map(renderNodes)}
|
|
69
|
+
</Tree.Node>
|
|
70
|
+
);
|
|
71
|
+
|
|
72
|
+
export const Default: Story = {
|
|
73
|
+
render: () => (
|
|
74
|
+
<Tree rootPath="root">
|
|
75
|
+
<Tree.Node path="Main nav" type="section" />
|
|
76
|
+
<Tree.Node path="H1" type="string">
|
|
77
|
+
<Tree.Node path="Section 3" type="string" />
|
|
78
|
+
<Tree.Node path="Section 4" type="section">
|
|
79
|
+
<Tree.Node path="Section 5" type="string" />
|
|
80
|
+
<Tree.Node path="Section 6" type="section">
|
|
81
|
+
<Tree.Node path="Section 7" type="string" />
|
|
82
|
+
</Tree.Node>
|
|
83
|
+
</Tree.Node>
|
|
84
|
+
</Tree.Node>
|
|
85
|
+
</Tree>
|
|
86
|
+
),
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
export const TestData: Story = {
|
|
90
|
+
render: () => <Tree rootPath="root">{nodes.map(renderNodes)}</Tree>,
|
|
91
|
+
};
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { Children, cloneElement } from "react";
|
|
2
|
+
import ImageIcon from "../../assets/icons/ImageIcon";
|
|
3
|
+
import Section from "../../assets/icons/Section";
|
|
4
|
+
import TextIcon from "../../assets/icons/TextIcon";
|
|
5
|
+
|
|
6
|
+
type TreeProps = {
|
|
7
|
+
children: React.ReactNode | React.ReactNode[];
|
|
8
|
+
rootPath?: string;
|
|
9
|
+
};
|
|
10
|
+
export function Tree({ children, rootPath }: TreeProps): React.ReactElement {
|
|
11
|
+
return (
|
|
12
|
+
<div className="flex flex-col bg-warm-black text-white font-sans text-xs w-full py-2">
|
|
13
|
+
{Children.map(children, (child) => {
|
|
14
|
+
return cloneElement(child as React.ReactElement, {
|
|
15
|
+
paths: [rootPath],
|
|
16
|
+
});
|
|
17
|
+
})}
|
|
18
|
+
</div>
|
|
19
|
+
);
|
|
20
|
+
}
|
|
21
|
+
type TreeNodeProps = {
|
|
22
|
+
children?: React.ReactNode | React.ReactNode[];
|
|
23
|
+
path: string;
|
|
24
|
+
paths?: string[];
|
|
25
|
+
level?: number;
|
|
26
|
+
type: "string" | "image" | "section";
|
|
27
|
+
setActivePath?: (path: string) => void;
|
|
28
|
+
};
|
|
29
|
+
Tree.Node = ({
|
|
30
|
+
children,
|
|
31
|
+
paths = [],
|
|
32
|
+
path,
|
|
33
|
+
level = 1,
|
|
34
|
+
type,
|
|
35
|
+
setActivePath,
|
|
36
|
+
}: TreeNodeProps): React.ReactElement => {
|
|
37
|
+
const paddingLeft = level * 30;
|
|
38
|
+
const logo =
|
|
39
|
+
type === "string" ? (
|
|
40
|
+
<TextIcon />
|
|
41
|
+
) : type === "image" ? (
|
|
42
|
+
<ImageIcon className="h-[9px] w-[9px]" />
|
|
43
|
+
) : (
|
|
44
|
+
<Section />
|
|
45
|
+
);
|
|
46
|
+
return (
|
|
47
|
+
<div className="w-full">
|
|
48
|
+
<button
|
|
49
|
+
className="flex justify-between w-full text-white hover:bg-dark-gray group py-2 hover:text-warm-black text-xs font-[400] shrink-0"
|
|
50
|
+
onClick={() => {
|
|
51
|
+
setActivePath && setActivePath(path);
|
|
52
|
+
}}
|
|
53
|
+
style={{ paddingLeft: paddingLeft }}
|
|
54
|
+
>
|
|
55
|
+
<div className="flex items-center justify-center gap-2">
|
|
56
|
+
{logo}
|
|
57
|
+
<p>{path}</p>
|
|
58
|
+
</div>
|
|
59
|
+
</button>
|
|
60
|
+
{children && (
|
|
61
|
+
<>
|
|
62
|
+
{Children.map(children, (child) => {
|
|
63
|
+
return cloneElement(child as React.ReactElement, {
|
|
64
|
+
level: level + 1,
|
|
65
|
+
paths: [...paths, path],
|
|
66
|
+
});
|
|
67
|
+
})}
|
|
68
|
+
</>
|
|
69
|
+
)}
|
|
70
|
+
</div>
|
|
71
|
+
);
|
|
72
|
+
};
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import { SerializedModule } from "@valbuild/core";
|
|
2
|
+
import { Json } from "@valbuild/core/src/Json";
|
|
3
|
+
import { ValApi } from "@valbuild/core";
|
|
4
|
+
import { FC, useEffect, useState } from "react";
|
|
5
|
+
import { Dropdown } from "./Dropdown";
|
|
6
|
+
import { FormGroup } from "./FormGroup";
|
|
7
|
+
import { Grid } from "./Grid";
|
|
8
|
+
import { Tree } from "./Tree";
|
|
9
|
+
|
|
10
|
+
interface ValDashboardProps {
|
|
11
|
+
showDashboard: boolean;
|
|
12
|
+
editMode: boolean;
|
|
13
|
+
valApi: ValApi;
|
|
14
|
+
}
|
|
15
|
+
export const ValDashboard: FC<ValDashboardProps> = ({
|
|
16
|
+
showDashboard,
|
|
17
|
+
editMode,
|
|
18
|
+
}) => {
|
|
19
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
20
|
+
const [modules, setModules] = useState<SerializedModule[]>([]);
|
|
21
|
+
const [selectedPath, setSelectedPath] = useState<string>("");
|
|
22
|
+
const [selectedModule, setSelectedModule] = useState<
|
|
23
|
+
| {
|
|
24
|
+
[key: string]: {
|
|
25
|
+
path: string;
|
|
26
|
+
type: string;
|
|
27
|
+
};
|
|
28
|
+
}[]
|
|
29
|
+
| undefined
|
|
30
|
+
>();
|
|
31
|
+
|
|
32
|
+
useEffect(() => {
|
|
33
|
+
// valApi.getModules({ patch: true, includeSource: true }).then((modules) => {
|
|
34
|
+
// // TODO:
|
|
35
|
+
// });
|
|
36
|
+
}, []);
|
|
37
|
+
|
|
38
|
+
useEffect(() => {
|
|
39
|
+
const newModule = modules.find((module) => module.path === selectedPath);
|
|
40
|
+
if (newModule) {
|
|
41
|
+
const children = mapChildren(newModule);
|
|
42
|
+
console.log("children", children);
|
|
43
|
+
setSelectedModule(children);
|
|
44
|
+
}
|
|
45
|
+
}, [selectedPath]);
|
|
46
|
+
|
|
47
|
+
const mapChildren = (module: SerializedModule) => {
|
|
48
|
+
if (module) {
|
|
49
|
+
if (module.schema.type === "array") {
|
|
50
|
+
return (module.source as Json[]).map((child) => {
|
|
51
|
+
const newModule: {
|
|
52
|
+
[key: string]: {
|
|
53
|
+
path: string;
|
|
54
|
+
type: string;
|
|
55
|
+
};
|
|
56
|
+
} = {};
|
|
57
|
+
if (child) {
|
|
58
|
+
for (const key of Object.keys(child)) {
|
|
59
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
60
|
+
const type = (module.schema as any).item.items[key]
|
|
61
|
+
.type as string;
|
|
62
|
+
if (key !== "rank" && type !== "richtext") {
|
|
63
|
+
newModule[key] = {
|
|
64
|
+
path: key,
|
|
65
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
66
|
+
type: (module.schema as any).item.items[key].type as string,
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
return newModule;
|
|
72
|
+
});
|
|
73
|
+
} else {
|
|
74
|
+
const child = module.source as Json;
|
|
75
|
+
const newModule: {
|
|
76
|
+
[key: string]: {
|
|
77
|
+
path: string;
|
|
78
|
+
type: string;
|
|
79
|
+
};
|
|
80
|
+
} = {};
|
|
81
|
+
if (child) {
|
|
82
|
+
for (const key of Object.keys(child)) {
|
|
83
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
84
|
+
const type = (module.schema as any).items[key].type as string;
|
|
85
|
+
if (key !== "rank" && type !== "richtext") {
|
|
86
|
+
newModule[key] = {
|
|
87
|
+
path: key,
|
|
88
|
+
type: type,
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
return [newModule];
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
return (
|
|
99
|
+
<>
|
|
100
|
+
{showDashboard && editMode && (
|
|
101
|
+
<div className="bg-base w-screen fixed z-10 top-[68.54px] text-white overflow-hidden">
|
|
102
|
+
<Grid>
|
|
103
|
+
{modules && (
|
|
104
|
+
<Dropdown
|
|
105
|
+
options={modules.map((module) => module.path)}
|
|
106
|
+
onClick={(path) => setSelectedPath(path)}
|
|
107
|
+
/>
|
|
108
|
+
)}
|
|
109
|
+
{selectedModule && (
|
|
110
|
+
<Tree>
|
|
111
|
+
{selectedModule.map((child, idx) => {
|
|
112
|
+
return (
|
|
113
|
+
<Tree.Node
|
|
114
|
+
key={idx}
|
|
115
|
+
path={`Section ${idx + 1}`}
|
|
116
|
+
type="section"
|
|
117
|
+
>
|
|
118
|
+
{Object.values(child).map((value, idx2) => (
|
|
119
|
+
<Tree.Node
|
|
120
|
+
key={idx2}
|
|
121
|
+
path={value.path}
|
|
122
|
+
type={value.type as "string" | "image" | "section"}
|
|
123
|
+
/>
|
|
124
|
+
))}
|
|
125
|
+
</Tree.Node>
|
|
126
|
+
);
|
|
127
|
+
})}
|
|
128
|
+
</Tree>
|
|
129
|
+
)}
|
|
130
|
+
<div className="flex items-center justify-between w-full h-full px-3 font-serif text-xs text-white">
|
|
131
|
+
<p>Content</p>
|
|
132
|
+
<button className="flex justify-between flex-shrink-0 gap-1">
|
|
133
|
+
<span className="w-fit">+</span>
|
|
134
|
+
<span className="w-fit">Add item</span>
|
|
135
|
+
</button>
|
|
136
|
+
</div>
|
|
137
|
+
<FormGroup>
|
|
138
|
+
<div>test</div>
|
|
139
|
+
<div>test</div>
|
|
140
|
+
<div>test</div>
|
|
141
|
+
</FormGroup>
|
|
142
|
+
<div>content</div>
|
|
143
|
+
</Grid>
|
|
144
|
+
</div>
|
|
145
|
+
)}
|
|
146
|
+
</>
|
|
147
|
+
);
|
|
148
|
+
};
|
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
import { SerializedModule } from "@valbuild/core";
|
|
2
|
+
import { ValApi } from "@valbuild/core";
|
|
3
|
+
import { LexicalEditor } from "lexical";
|
|
4
|
+
import { FC, useEffect, useState } from "react";
|
|
5
|
+
import { Inputs, RichTextEditor } from "../../exports";
|
|
6
|
+
import Button from "../Button";
|
|
7
|
+
import { ImageForm } from "../forms/ImageForm";
|
|
8
|
+
import { TextArea } from "../forms/TextArea";
|
|
9
|
+
|
|
10
|
+
interface ValDashboardEditorProps {
|
|
11
|
+
selectedPath: string;
|
|
12
|
+
valApi: ValApi;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export const ValDashboardEditor: FC<ValDashboardEditorProps> = ({
|
|
16
|
+
selectedPath,
|
|
17
|
+
}) => {
|
|
18
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
19
|
+
const [selectedModule, setSelectedModule] = useState<SerializedModule>();
|
|
20
|
+
const [inputs, setInputs] = useState<Inputs>({});
|
|
21
|
+
const [inputIsDirty, setInputIsDirty] = useState<{ [path: string]: boolean }>(
|
|
22
|
+
{}
|
|
23
|
+
);
|
|
24
|
+
const [richTextEditor, setRichTextEditor] = useState<{
|
|
25
|
+
[path: string]: LexicalEditor;
|
|
26
|
+
}>();
|
|
27
|
+
|
|
28
|
+
useEffect(() => {
|
|
29
|
+
// if (selectedPath) {
|
|
30
|
+
// valApi.getModule(selectedPath).then((module) => {
|
|
31
|
+
// setSelectedModule(module);
|
|
32
|
+
// });
|
|
33
|
+
// }
|
|
34
|
+
}, [selectedPath]);
|
|
35
|
+
|
|
36
|
+
useEffect(() => {
|
|
37
|
+
// if (selectedModule && selectedModule?.source) {
|
|
38
|
+
// setInputs({});
|
|
39
|
+
// for (const key of Object.keys(selectedModule?.source)) {
|
|
40
|
+
// if (key !== "rank") {
|
|
41
|
+
// valApi
|
|
42
|
+
// .getModule(`${selectedModule.path}.${key}`)
|
|
43
|
+
// .then((serializedModule) => {
|
|
44
|
+
// let input: Inputs[string] | undefined;
|
|
45
|
+
// if (
|
|
46
|
+
// serializedModule.schema.type === "string" &&
|
|
47
|
+
// typeof serializedModule.source === "string"
|
|
48
|
+
// ) {
|
|
49
|
+
// input = {
|
|
50
|
+
// status: "completed",
|
|
51
|
+
// type: "text",
|
|
52
|
+
// data: serializedModule.source,
|
|
53
|
+
// };
|
|
54
|
+
// } else if (
|
|
55
|
+
// serializedModule.schema.type === "richtext" &&
|
|
56
|
+
// typeof serializedModule.source === "object"
|
|
57
|
+
// ) {
|
|
58
|
+
// input = {
|
|
59
|
+
// status: "completed",
|
|
60
|
+
// type: "richtext",
|
|
61
|
+
// data: serializedModule.source as RichText, // TODO: validate
|
|
62
|
+
// };
|
|
63
|
+
// } else if (
|
|
64
|
+
// serializedModule.schema.type === "image" &&
|
|
65
|
+
// serializedModule.source &&
|
|
66
|
+
// typeof serializedModule.source === "object" &&
|
|
67
|
+
// FILE_REF_PROP in serializedModule.source &&
|
|
68
|
+
// typeof serializedModule.source[FILE_REF_PROP] === "string" &&
|
|
69
|
+
// VAL_EXTENSION in serializedModule.source &&
|
|
70
|
+
// typeof serializedModule.source[VAL_EXTENSION] === "string"
|
|
71
|
+
// ) {
|
|
72
|
+
// input = {
|
|
73
|
+
// status: "completed",
|
|
74
|
+
// type: "image",
|
|
75
|
+
// data: Internal.convertImageSource(
|
|
76
|
+
// serializedModule.source as FileSource<ImageMetadata>
|
|
77
|
+
// ),
|
|
78
|
+
// };
|
|
79
|
+
// }
|
|
80
|
+
// if (!input) {
|
|
81
|
+
// throw new Error(
|
|
82
|
+
// `Unsupported module type: ${serializedModule.schema.type}`
|
|
83
|
+
// );
|
|
84
|
+
// }
|
|
85
|
+
// setInputs((inputs) => {
|
|
86
|
+
// return {
|
|
87
|
+
// ...inputs,
|
|
88
|
+
// [serializedModule.path]: input,
|
|
89
|
+
// } as Inputs;
|
|
90
|
+
// });
|
|
91
|
+
// });
|
|
92
|
+
// }
|
|
93
|
+
// }
|
|
94
|
+
// }
|
|
95
|
+
}, [selectedModule]);
|
|
96
|
+
|
|
97
|
+
useEffect(() => {
|
|
98
|
+
for (const key of Object.keys(inputs)) {
|
|
99
|
+
if (!Object.keys(inputIsDirty).includes(key)) {
|
|
100
|
+
setInputIsDirty({
|
|
101
|
+
...inputIsDirty,
|
|
102
|
+
[key]: false,
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}, [inputs]);
|
|
107
|
+
|
|
108
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
109
|
+
const patchElement = async (path: string) => {
|
|
110
|
+
// Promise.all(
|
|
111
|
+
// Object.entries(inputs)
|
|
112
|
+
// .filter(([k]) => k === key)
|
|
113
|
+
// .map(([path, input]) => {
|
|
114
|
+
// if (input.status === "completed") {
|
|
115
|
+
// const [moduleId, modulePath] = Internal.splitModuleIdAndModulePath(
|
|
116
|
+
// path as SourcePath
|
|
117
|
+
// );
|
|
118
|
+
// if (input.type === "text") {
|
|
119
|
+
// const patch: PatchJSON = [
|
|
120
|
+
// {
|
|
121
|
+
// value: input.data,
|
|
122
|
+
// op: "replace",
|
|
123
|
+
// path: `/${modulePath
|
|
124
|
+
// .split(".")
|
|
125
|
+
// .map((p) => {
|
|
126
|
+
// return JSON.parse(p);
|
|
127
|
+
// })
|
|
128
|
+
// .join("/")}`,
|
|
129
|
+
// },
|
|
130
|
+
// ];
|
|
131
|
+
// console.log("patch", patch);
|
|
132
|
+
// return valApi.patchModuleContent(moduleId, patch);
|
|
133
|
+
// } else if (input.type === "image") {
|
|
134
|
+
// const pathParts = modulePath.split(".").map((p) => JSON.parse(p));
|
|
135
|
+
// if (!input?.data || !("src" in input.data)) {
|
|
136
|
+
// // TODO: We probably need to have an Output type that is different from the Input: we have a union of both cases in Input right now, and we believe we do not want that
|
|
137
|
+
// console.warn(
|
|
138
|
+
// "No .src on input provided - this might mean no changes was made"
|
|
139
|
+
// );
|
|
140
|
+
// return;
|
|
141
|
+
// }
|
|
142
|
+
// const patch: PatchJSON = [
|
|
143
|
+
// {
|
|
144
|
+
// value: input.data.src,
|
|
145
|
+
// op: "replace",
|
|
146
|
+
// path: `/${pathParts.slice(0, -1).join("/")}/$${
|
|
147
|
+
// pathParts[pathParts.length - 1]
|
|
148
|
+
// }`,
|
|
149
|
+
// },
|
|
150
|
+
// ];
|
|
151
|
+
// if (input.data.metadata) {
|
|
152
|
+
// if (input.data.addMetadata) {
|
|
153
|
+
// patch.push({
|
|
154
|
+
// value: input.data.metadata,
|
|
155
|
+
// op: "add",
|
|
156
|
+
// path: `/${pathParts.join("/")}/metadata`,
|
|
157
|
+
// });
|
|
158
|
+
// } else {
|
|
159
|
+
// patch.push({
|
|
160
|
+
// value: input.data.metadata,
|
|
161
|
+
// op: "replace",
|
|
162
|
+
// path: `/${pathParts.join("/")}/metadata`,
|
|
163
|
+
// });
|
|
164
|
+
// }
|
|
165
|
+
// }
|
|
166
|
+
// console.log("patch", patch);
|
|
167
|
+
// return valApi.patchModuleContent(moduleId, patch);
|
|
168
|
+
// } else if (input.type === "richtext") {
|
|
169
|
+
// const patch: PatchJSON = [
|
|
170
|
+
// {
|
|
171
|
+
// value: input.data,
|
|
172
|
+
// op: "replace",
|
|
173
|
+
// path: `/${modulePath
|
|
174
|
+
// .split(".")
|
|
175
|
+
// .map((p) => JSON.parse(p))
|
|
176
|
+
// .join("/")}`,
|
|
177
|
+
// },
|
|
178
|
+
// ];
|
|
179
|
+
// return valApi.patchModuleContent(moduleId, patch);
|
|
180
|
+
// }
|
|
181
|
+
// // eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
182
|
+
// throw new Error(`Unsupported input type: ${(input as any).type}`);
|
|
183
|
+
// } else {
|
|
184
|
+
// console.error("Submitted incomplete input, ignoring...");
|
|
185
|
+
// return Promise.resolve();
|
|
186
|
+
// }
|
|
187
|
+
// })
|
|
188
|
+
// ).then((res) => {
|
|
189
|
+
// console.log("patched", res);
|
|
190
|
+
// });
|
|
191
|
+
};
|
|
192
|
+
|
|
193
|
+
return (
|
|
194
|
+
<div className="flex flex-col items-start px-4">
|
|
195
|
+
{selectedModule ? (
|
|
196
|
+
<div className="flex flex-col items-start w-full py-3 gap-[36px] font-normal">
|
|
197
|
+
{Object.entries(inputs).map(([path, input]) => {
|
|
198
|
+
return (
|
|
199
|
+
<div key={path} className={"flex flex-col justify-start "}>
|
|
200
|
+
{input.status === "requested" && (
|
|
201
|
+
<div className="p-2 text-center text-primary">Loading...</div>
|
|
202
|
+
)}
|
|
203
|
+
<div className="flex flex-col gap-1 font-[550]">
|
|
204
|
+
<p>{path.split(".").slice(-1)[0].split('"').join("")}</p>
|
|
205
|
+
{input.status === "completed" && input.type === "image" && (
|
|
206
|
+
<ImageForm
|
|
207
|
+
name={path}
|
|
208
|
+
data={input.data}
|
|
209
|
+
onChange={(data) => {
|
|
210
|
+
if (data.value) {
|
|
211
|
+
setInputs({
|
|
212
|
+
...inputs,
|
|
213
|
+
[path]: {
|
|
214
|
+
status: "completed",
|
|
215
|
+
type: "image",
|
|
216
|
+
data: data.value,
|
|
217
|
+
},
|
|
218
|
+
});
|
|
219
|
+
setInputIsDirty({ ...inputIsDirty, [path]: true });
|
|
220
|
+
}
|
|
221
|
+
}}
|
|
222
|
+
error={null}
|
|
223
|
+
/>
|
|
224
|
+
)}
|
|
225
|
+
{input.status === "completed" && input.type === "text" && (
|
|
226
|
+
<TextArea
|
|
227
|
+
name={path}
|
|
228
|
+
text={input.data}
|
|
229
|
+
onChange={(data) => {
|
|
230
|
+
setInputs({
|
|
231
|
+
...inputs,
|
|
232
|
+
[path]: {
|
|
233
|
+
status: "completed",
|
|
234
|
+
type: "text",
|
|
235
|
+
data: data,
|
|
236
|
+
},
|
|
237
|
+
});
|
|
238
|
+
setInputIsDirty({ ...inputIsDirty, [path]: true });
|
|
239
|
+
}}
|
|
240
|
+
/>
|
|
241
|
+
)}
|
|
242
|
+
{input.status === "completed" &&
|
|
243
|
+
input.type === "richtext" && (
|
|
244
|
+
<RichTextEditor
|
|
245
|
+
richtext={input.data}
|
|
246
|
+
onEditor={(editor) => {
|
|
247
|
+
setRichTextEditor({
|
|
248
|
+
...richTextEditor,
|
|
249
|
+
[path]: editor,
|
|
250
|
+
});
|
|
251
|
+
}}
|
|
252
|
+
/>
|
|
253
|
+
)}
|
|
254
|
+
{inputIsDirty[path] && (
|
|
255
|
+
<Button onClick={() => patchElement(path)}>
|
|
256
|
+
Save changes
|
|
257
|
+
</Button>
|
|
258
|
+
)}
|
|
259
|
+
</div>
|
|
260
|
+
</div>
|
|
261
|
+
);
|
|
262
|
+
})}
|
|
263
|
+
</div>
|
|
264
|
+
) : (
|
|
265
|
+
<h1 className="px-4 py-3">No module selected</h1>
|
|
266
|
+
)}
|
|
267
|
+
</div>
|
|
268
|
+
);
|
|
269
|
+
};
|