Skip to content

Commit 466f7fc

Browse files
committed
feat: update playground editor
1 parent e79bd67 commit 466f7fc

9 files changed

Lines changed: 149 additions & 86 deletions

File tree

Lines changed: 7 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1,52 +1,19 @@
1-
import { json, jsonParseLinter } from "@codemirror/lang-json";
2-
import { linter, lintGutter } from "@codemirror/lint";
3-
import { githubLight } from "@uiw/codemirror-theme-github";
4-
import CodeMirror, { type Extension } from "@uiw/react-codemirror";
5-
import { ReactElement, useEffect, useMemo, useRef, useState } from "react";
6-
7-
export type EditorChangeHandler = (value: string) => void;
1+
import { ReactElement } from "react";
82

93
export interface CodeEditorProps {
10-
defaultValue: string;
11-
onChange?: EditorChangeHandler;
4+
value: string;
5+
onChange?: (value: string) => void;
126
readOnly?: boolean;
137
height?: string;
148
}
159

1610
export function CodeEditor(props: CodeEditorProps): ReactElement {
17-
const [value, onChange] = useEditorState({ initState: props.defaultValue, onChange: props.onChange });
18-
const extensions = useMemo<Extension[]>(
19-
() => [
20-
json(),
21-
linter(jsonParseLinter(), {
22-
// default is 750ms
23-
delay: 300
24-
}),
25-
lintGutter()
26-
],
27-
[]
28-
);
2911
return (
30-
<CodeMirror
31-
height={props.height}
32-
value={value}
33-
onChange={onChange}
34-
theme={githubLight}
12+
<textarea
13+
value={props.value}
14+
onChange={e => props.onChange?.(e.target.value)}
15+
style={{ height: props.height ?? "200px", width: "100%", fontFamily: "monospace" }}
3516
readOnly={props.readOnly}
36-
extensions={extensions}
3717
/>
3818
);
3919
}
40-
41-
function useEditorState(params: { initState: string; onChange?: EditorChangeHandler }): [string, EditorChangeHandler] {
42-
const listener = useRef(params.onChange);
43-
const [value, setValue] = useState(params.initState);
44-
const { current: onValueChange } = useRef<EditorChangeHandler>(value => {
45-
setValue(value);
46-
listener.current?.(value);
47-
});
48-
useEffect(() => {
49-
listener.current = params.onChange;
50-
});
51-
return [value, onValueChange];
52-
}

packages/pluggableWidgets/chart-playground-web/src/components/ComposedEditor.tsx

Lines changed: 7 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
1-
import { Alert } from "@mendix/widget-plugin-component-kit/Alert";
2-
import { useOnClickOutside } from "@mendix/widget-plugin-hooks/useOnClickOutside";
31
import classNames from "classnames";
42
import { Fragment, ReactElement, ReactNode, RefObject, useCallback, useRef, useState } from "react";
3+
import { Alert } from "@mendix/widget-plugin-component-kit/Alert";
4+
import { useOnClickOutside } from "@mendix/widget-plugin-hooks/useOnClickOutside";
55
import "../ui/Playground.scss";
6+
import { CodeEditor } from "./CodeEditor";
67
import { Select, SelectOption, Sidebar, SidebarHeader, SidebarHeaderTools, SidebarPanel } from "./Sidebar";
7-
import { CodeEditor, EditorChangeHandler } from "./CodeEditor";
88

99
interface WrapperProps {
1010
renderPanels: ReactNode;
@@ -71,8 +71,8 @@ const SidebarContentTooltip = (): ReactElement => {
7171
};
7272

7373
export interface ComposedEditorProps {
74-
defaultEditorValue: string;
75-
onEditorChange: EditorChangeHandler;
74+
value: string;
75+
onEditorChange: (value: string) => void;
7676
modelerCode: string;
7777
onViewSelectChange: (value: string) => void;
7878
viewSelectValue: string;
@@ -138,11 +138,7 @@ export function ComposedEditor(props: ComposedEditorProps): ReactElement {
138138
heading={topPanelHeader}
139139
>
140140
<TabGuard>
141-
<CodeEditor
142-
defaultValue={props.defaultEditorValue}
143-
onChange={props.onEditorChange}
144-
height="var(--editor-h)"
145-
/>
141+
<CodeEditor value={props.value} onChange={props.onEditorChange} height="var(--editor-h)" />
146142
</TabGuard>
147143
</SidebarPanel>
148144
<SidebarPanel
@@ -154,7 +150,7 @@ export function ComposedEditor(props: ComposedEditorProps): ReactElement {
154150
<CodeEditor
155151
key={props.modelerCode}
156152
readOnly
157-
defaultValue={props.modelerCode}
153+
value={props.modelerCode}
158154
height="var(--static-settings-h)"
159155
/>
160156
</SidebarPanel>
Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,35 @@
1-
import { PlaygroundData, usePlaygroundContext } from "@mendix/shared-charts/main";
2-
import { Alert } from "@mendix/widget-plugin-component-kit/Alert";
31
import { ReactElement } from "react";
4-
import { useComposedEditorController } from "../helpers/useComposedEditorController";
2+
import { PlaygroundDataV1, PlaygroundDataV2, usePlaygroundContext } from "@mendix/shared-charts/main";
3+
import { Alert } from "@mendix/widget-plugin-component-kit/Alert";
54
import { ComposedEditor } from "./ComposedEditor";
5+
import { useComposedEditorController } from "../helpers/useComposedEditorController";
66
import "../ui/Playground.scss";
7+
import { useV2EditorController } from "../helpers/useV2EdtiorController";
78

8-
function Editor({ data }: { data: PlaygroundData }): ReactElement {
9+
function EditorGen1({ data }: { data: PlaygroundDataV1 }): ReactElement {
910
const props = useComposedEditorController(data);
1011

1112
return <ComposedEditor {...props} />;
1213
}
1314

15+
function EditorGen2({ data }: { data: PlaygroundDataV2 }): ReactElement {
16+
const props = useV2EditorController(data);
17+
18+
return <ComposedEditor {...props} />;
19+
}
20+
1421
export function Playground(): ReactElement {
1522
const ctx = usePlaygroundContext();
1623

1724
if ("error" in ctx) {
1825
return <Alert bootstrapStyle="danger">{ctx.error.message}</Alert>;
1926
}
2027

21-
return <Editor data={ctx.data} />;
28+
const { data } = ctx;
29+
30+
if (Object.hasOwn(data, "type") && (data as PlaygroundDataV2).type === "editor.data.v2") {
31+
return <EditorGen2 data={data as PlaygroundDataV2} />;
32+
}
33+
34+
return <EditorGen1 data={data as PlaygroundDataV1} />;
2235
}

packages/pluggableWidgets/chart-playground-web/src/helpers/useComposedEditorController.ts

Lines changed: 18 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
1-
import { fallback, PlaygroundData } from "@mendix/shared-charts/main";
2-
import { EditorChangeHandler } from "../components/CodeEditor";
3-
import { useMemo, useState } from "react";
1+
import { useCallback, useMemo, useState } from "react";
2+
import { fallback, PlaygroundDataV1 } from "@mendix/shared-charts/main";
43
import { ComposedEditorProps } from "../components/ComposedEditor";
54
import { SelectOption } from "../components/Sidebar";
65

@@ -14,13 +13,13 @@ const irrelevantSeriesKeys = ["x", "y", "z", "customSeriesOptions", "dataSourceI
1413

1514
type ConfigKey = "layout" | "config" | number;
1615

17-
function getEditorCode({ store }: PlaygroundData, key: ConfigKey): string {
16+
function getEditorCode({ store }: PlaygroundDataV1, key: ConfigKey): string {
1817
let value = typeof key === "number" ? store.state.data.at(key) : store.state[key];
1918
value = value ?? '{ "error": "value is unavailable" }';
2019
return value;
2120
}
2221

23-
function getModelerCode(data: PlaygroundData, key: ConfigKey): Partial<Data> | Partial<Layout> | Partial<Config> {
22+
function getModelerCode(data: PlaygroundDataV1, key: ConfigKey): Partial<Data> | Partial<Layout> | Partial<Config> {
2423
if (key === "layout") {
2524
return data.layoutOptions;
2625
}
@@ -32,7 +31,7 @@ function getModelerCode(data: PlaygroundData, key: ConfigKey): Partial<Data> | P
3231
return Object.fromEntries(entries) as Partial<Data>;
3332
}
3433

35-
export function useComposedEditorController(data: PlaygroundData): ComposedEditorProps {
34+
export function useComposedEditorController(data: PlaygroundDataV1): ComposedEditorProps {
3635
const [key, setKey] = useState<ConfigKey>("layout");
3736

3837
const onViewSelectChange = (value: string): void => {
@@ -56,20 +55,24 @@ export function useComposedEditorController(data: PlaygroundData): ComposedEdito
5655
];
5756
}, [data.plotData]);
5857

59-
const onEditorChange: EditorChangeHandler = (json): void => {
60-
json = fallback(json);
61-
try {
62-
JSON.parse(json);
63-
data.store.set(key, json);
64-
// eslint-disable-next-line no-empty
65-
} catch {}
66-
};
58+
const store = data.store;
59+
const onEditorChange = useCallback(
60+
(value: string): void => {
61+
try {
62+
const json = fallback(value);
63+
JSON.parse(value);
64+
store.set(key, json);
65+
// eslint-disable-next-line no-empty
66+
} catch {}
67+
},
68+
[store, key]
69+
);
6770

6871
return {
6972
viewSelectValue: key.toString(),
7073
viewSelectOptions: options,
7174
onViewSelectChange,
72-
defaultEditorValue: getEditorCode(data, key),
75+
value: getEditorCode(data, key),
7376
modelerCode: useMemo(() => JSON.stringify(getModelerCode(data, key), null, 2), [data, key]),
7477
onEditorChange
7578
};
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
import { useCallback, useMemo, useState } from "react";
2+
import { PlaygroundDataV2 } from "@mendix/shared-charts/main";
3+
import { ComposedEditorProps } from "../components/ComposedEditor";
4+
import { SelectOption } from "../components/Sidebar";
5+
6+
type ConfigKey = "layout" | "config" | number;
7+
8+
const irrelevantSeriesKeys = ["x", "y", "z", "customSeriesOptions", "dataSourceItems"];
9+
10+
function getEditorCode({ store }: PlaygroundDataV2, key: ConfigKey): string {
11+
if (key === "layout") {
12+
return store.layoutJson ?? '{ "error": "value is unavailable" }';
13+
}
14+
if (key === "config") {
15+
return store.configJson ?? '{ "error": "value is unavailable" }';
16+
}
17+
return store.dataJson.at(key) ?? '{ "error": "value is unavailable" }';
18+
}
19+
20+
function getModelerCode(data: PlaygroundDataV2, key: ConfigKey): object {
21+
if (key === "layout") {
22+
return data.layoutOptions;
23+
}
24+
if (key === "config") {
25+
return data.configOptions;
26+
}
27+
const entries = Object.entries(data.plotData.at(key) ?? {}).filter(([k]) => !irrelevantSeriesKeys.includes(k));
28+
return Object.fromEntries(entries);
29+
}
30+
31+
export function useV2EditorController(data: PlaygroundDataV2): ComposedEditorProps {
32+
const [key, setKey] = useState<ConfigKey>("layout");
33+
34+
const onViewSelectChange = (value: string): void => {
35+
if (value === "layout" || value === "config") {
36+
setKey(value);
37+
} else {
38+
const n = parseInt(value, 10);
39+
setKey(isNaN(n) ? "layout" : n);
40+
}
41+
};
42+
43+
const options: SelectOption[] = useMemo(() => {
44+
return [
45+
{ name: "Layout", value: "layout", isDefaultSelected: true },
46+
...data.plotData.map((trace, index) => ({
47+
name: trace.name || `trace ${index}`,
48+
value: index,
49+
isDefaultSelected: false
50+
})),
51+
{ name: "Configuration", value: "config", isDefaultSelected: false }
52+
];
53+
}, [data.plotData]);
54+
55+
const store = data.store;
56+
const onEditorChange = useCallback(
57+
(value: string): void => {
58+
try {
59+
if (key === "layout") {
60+
store.setLayout(JSON.parse(value));
61+
} else if (key === "config") {
62+
store.setConfig(JSON.parse(value));
63+
} else {
64+
store.setDataAt(key, value);
65+
}
66+
// eslint-disable-next-line no-empty
67+
} catch {}
68+
},
69+
[store, key]
70+
);
71+
72+
return {
73+
viewSelectValue: key.toString(),
74+
viewSelectOptions: options,
75+
onViewSelectChange,
76+
value: getEditorCode(data, key),
77+
modelerCode: useMemo(() => JSON.stringify(getModelerCode(data, key), null, 2), [data, key]),
78+
onEditorChange
79+
};
80+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
declare module "*.scss";

packages/shared/charts/src/helpers/playground-context.ts

Lines changed: 16 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -4,20 +4,22 @@ import { EditorStore } from "./EditorStore";
44
import { ChartProps } from "../components/types";
55
import { EditableChartStore } from "../main";
66

7-
export type PlaygroundData =
8-
| {
9-
plotData: Array<Partial<Data>>;
10-
store: EditorStore;
11-
configOptions: Partial<Config>;
12-
layoutOptions: Partial<Layout>;
13-
}
14-
| {
15-
type: "editor.data.v2";
16-
store: EditableChartStore;
17-
plotData: Array<Partial<Data>>;
18-
configOptions: Partial<Config>;
19-
layoutOptions: Partial<Layout>;
20-
};
7+
export type PlaygroundDataV1 = {
8+
plotData: Array<Partial<Data>>;
9+
store: EditorStore;
10+
configOptions: Partial<Config>;
11+
layoutOptions: Partial<Layout>;
12+
};
13+
14+
export type PlaygroundDataV2 = {
15+
type: "editor.data.v2";
16+
store: EditableChartStore;
17+
plotData: Array<Partial<Data>>;
18+
configOptions: Partial<Config>;
19+
layoutOptions: Partial<Layout>;
20+
};
21+
22+
export type PlaygroundData = PlaygroundDataV1 | PlaygroundDataV2;
2123

2224
// We use Symbol.for to make this symbol accessible through whole runtime.
2325
const contextSymbol = Symbol.for("ChartsPlaygroundContext");
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
{"root":["./src/main.ts","./src/preview.ts","./src/components/Chart.tsx","./src/components/ChartPreview.tsx","./src/components/ChartView.tsx","./src/components/ChartWidget.tsx","./src/components/types.ts","./src/helpers/ChartDataStore.ts","./src/helpers/EditorStore.ts","./src/helpers/playground-context.ts","./src/helpers/useChartController.ts","./src/helpers/useEditorStore.ts","./src/hooks/usePlotChartDataSeries.ts","./src/typings/declare-svg.d.ts","./src/typings/global.d.ts","./src/typings/json-source-map.d.ts","./src/utils/aggregations.ts","./src/utils/chartStyles.ts","./src/utils/compareAttrValuesAsc.ts","./src/utils/configs.ts","./src/utils/equality.ts","./src/utils/json.ts","./src/utils/preview-utils.ts","./src/utils/setupBasicSeries.ts","./src/utils/themeFolderConfig.ts"],"version":"5.9.3"}
1+
{"root":["./src/main.ts","./src/preview.ts","./src/components/Chart.tsx","./src/components/ChartPreview.tsx","./src/components/ChartView.tsx","./src/components/ChartWidget.tsx","./src/components/types.ts","./src/helpers/EditorStore.ts","./src/helpers/playground-context.ts","./src/helpers/useChartController.ts","./src/helpers/useEditorStore.ts","./src/hooks/usePlotChartDataSeries.ts","./src/model/stores/EditableChart.store.ts","./src/typings/declare-svg.d.ts","./src/typings/global.d.ts","./src/typings/json-source-map.d.ts","./src/utils/aggregations.ts","./src/utils/chartStyles.ts","./src/utils/compareAttrValuesAsc.ts","./src/utils/configs.ts","./src/utils/equality.ts","./src/utils/json.ts","./src/utils/preview-utils.ts","./src/utils/setupBasicSeries.ts","./src/utils/themeFolderConfig.ts"],"version":"5.9.3"}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
{"root":["./src/main.ts","./src/preview.ts","./src/components/Chart.tsx","./src/components/ChartPreview.tsx","./src/components/ChartView.tsx","./src/components/ChartWidget.tsx","./src/components/types.ts","./src/components/__tests__/configs.spec.ts","./src/helpers/EditorStore.ts","./src/helpers/playground-context.ts","./src/helpers/useChartController.ts","./src/helpers/useEditorStore.ts","./src/hooks/usePlotChartDataSeries.ts","./src/hooks/__tests__/usePlotChartDataSeries.spec.ts","./src/model/stores/EditableChart.store.ts","./src/typings/declare-svg.d.ts","./src/typings/global.d.ts","./src/typings/json-source-map.d.ts","./src/utils/aggregations.ts","./src/utils/chartStyles.ts","./src/utils/compareAttrValuesAsc.ts","./src/utils/configs.ts","./src/utils/equality.ts","./src/utils/json.ts","./src/utils/preview-utils.ts","./src/utils/setupBasicSeries.ts","./src/utils/themeFolderConfig.ts","./src/utils/__tests__/aggregations.spec.ts"],"version":"5.9.3"}

0 commit comments

Comments
 (0)