Skip to content

Commit 2cc5cf0

Browse files
add editor preview provider
1 parent e293e0e commit 2cc5cf0

4 files changed

Lines changed: 274 additions & 34 deletions

File tree

editor/scaffolds/editor/_providers.tsx

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,16 @@
11
import React from "react";
22
import { useHotkeys } from "react-hotkeys-hook";
3+
import { EditorImageRepositoryProvider } from "./editor-image-repository-provider";
4+
import { EditorPreviewDataProvider } from "./editor-preview-provider";
5+
36
export function EditorDefaultProviders(props: { children: React.ReactNode }) {
4-
return <ShortcutsProvider>{props.children}</ShortcutsProvider>;
7+
return (
8+
<ShortcutsProvider>
9+
<EditorImageRepositoryProvider>
10+
<EditorPreviewDataProvider>{props.children}</EditorPreviewDataProvider>
11+
</EditorImageRepositoryProvider>
12+
</ShortcutsProvider>
13+
);
514
}
615

716
function ShortcutsProvider(props: { children: React.ReactNode }) {
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
import React, { useEffect } from "react";
2+
import { RemoteImageRepositories } from "@design-sdk/figma-remote/lib/asset-repository/image-repository";
3+
import {
4+
ImageRepository,
5+
MainImageRepository,
6+
} from "@design-sdk/core/assets-repository";
7+
import { useEditorState } from "core/states";
8+
import { useFigmaAccessToken } from "hooks";
9+
10+
/**
11+
* This is a queue handler of d2c requests.
12+
* Since the d2c can share cache and is a async process, we need this middleware wrapper to handle it elegantly.
13+
* @returns
14+
*/
15+
export function EditorImageRepositoryProvider({
16+
children,
17+
}: {
18+
children: React.ReactNode;
19+
}) {
20+
const [state] = useEditorState();
21+
22+
// listen to requests
23+
24+
// handle requests, dispatch with results
25+
//
26+
27+
const fat = useFigmaAccessToken();
28+
29+
useEffect(() => {
30+
// ------------------------------------------------------------
31+
// other platforms are not supported yet
32+
// set image repo for figma platform
33+
if (state.design) {
34+
MainImageRepository.instance = new RemoteImageRepositories(
35+
state.design.key,
36+
{
37+
authentication: {
38+
personalAccessToken: fat.personalAccessToken,
39+
accessToken: fat.accessToken.token,
40+
},
41+
}
42+
);
43+
MainImageRepository.instance.register(
44+
new ImageRepository(
45+
"fill-later-assets",
46+
"grida://assets-reservation/images/"
47+
)
48+
);
49+
}
50+
// ------------------------------------------------------------
51+
}, [state.design?.key, fat.accessToken]);
52+
53+
return <>{children}</>;
54+
}
Lines changed: 209 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,209 @@
1+
import React, { useCallback, useEffect, useMemo } from "react";
2+
import { useEditorState } from "core/states";
3+
import { preview_presets } from "@grida/builder-config-preset";
4+
import { designToCode, Result } from "@designto/code";
5+
import { config } from "@designto/config";
6+
import { MainImageRepository } from "@design-sdk/core/assets-repository";
7+
import bundler from "@code-editor/esbuild-services";
8+
import assert from "assert";
9+
import { useDispatch } from "core/dispatch";
10+
import { useTargetContainer } from "hooks";
11+
12+
const esbuild_base_html_code = `<div id="root"></div>`;
13+
14+
/**
15+
* This is a queue handler of d2c requests.
16+
* Since the d2c can share cache and is a async process, we need this middleware wrapper to handle it elegantly.
17+
* @returns
18+
*/
19+
export function EditorPreviewDataProvider({
20+
children,
21+
}: {
22+
children: React.ReactNode;
23+
}) {
24+
// listen to changes
25+
// handle changes, dispatch with results
26+
27+
const [state] = useEditorState();
28+
const dispatch = useDispatch();
29+
30+
const updateBuildingState = useCallback(
31+
(isBuilding: boolean) => {
32+
dispatch({
33+
type: "preview-update-building-state",
34+
isBuilding,
35+
});
36+
},
37+
[dispatch]
38+
);
39+
40+
const onVanillaPreviewResult = useCallback(
41+
(result: Result) => {
42+
dispatch({
43+
type: "preview-set",
44+
data: {
45+
loader: "vanilla-html",
46+
viewtype: "unknown",
47+
widgetKey: result.widget.key,
48+
componentName: result.name,
49+
fallbackSource: result.scaffold.raw,
50+
source: result.scaffold.raw,
51+
initialSize: {
52+
width: result.widget?.["width"],
53+
height: result.widget?.["height"],
54+
},
55+
isBuilding: false,
56+
meta: {
57+
bundler: "vanilla",
58+
framework: result.framework.framework,
59+
},
60+
updatedAt: Date.now(),
61+
},
62+
});
63+
},
64+
[dispatch]
65+
);
66+
67+
const onEsbuildReactPreviewResult = useCallback(
68+
({
69+
bundledjs,
70+
componentName,
71+
}: {
72+
bundledjs: string;
73+
componentName: string;
74+
}) => {
75+
dispatch({
76+
type: "preview-set",
77+
data: {
78+
loader: "vanilla-esbuild-template",
79+
viewtype: "unknown",
80+
widgetKey: state.currentPreview?.widgetKey, // TODO: fixme
81+
componentName: componentName,
82+
fallbackSource: state.currentPreview?.fallbackSource,
83+
source: {
84+
html: esbuild_base_html_code,
85+
javascript: bundledjs,
86+
},
87+
initialSize: {
88+
width: undefined,
89+
height: undefined,
90+
},
91+
isBuilding: false,
92+
meta: {
93+
bundler: "esbuild-wasm",
94+
framework: "react",
95+
},
96+
updatedAt: Date.now(),
97+
},
98+
});
99+
},
100+
[dispatch]
101+
);
102+
103+
const _is_mode_requires_preview_build =
104+
state.canvasMode === "fullscreen-preview" ||
105+
state.canvasMode === "isolated-view";
106+
107+
const { target, root } = useTargetContainer();
108+
109+
useEffect(() => {
110+
if (!_is_mode_requires_preview_build) {
111+
return;
112+
}
113+
114+
if (!MainImageRepository.isReady) {
115+
return;
116+
}
117+
118+
if (!target) {
119+
return;
120+
}
121+
122+
const _input = {
123+
id: target.id,
124+
name: target.name,
125+
entry: target,
126+
};
127+
128+
const build_config = {
129+
...config.default_build_configuration,
130+
disable_components: true,
131+
};
132+
133+
designToCode({
134+
input: _input,
135+
build_config: build_config,
136+
framework: preview_presets.default,
137+
asset_config: {
138+
skip_asset_replacement: false,
139+
asset_repository: MainImageRepository.instance,
140+
custom_asset_replacement: {
141+
type: "static",
142+
resource:
143+
"https://bridged-service-static.s3.us-west-1.amazonaws.com/placeholder-images/image-placeholder-bw-tile-100.png",
144+
},
145+
},
146+
})
147+
.then(onVanillaPreviewResult)
148+
.catch(console.error);
149+
150+
if (!MainImageRepository.instance.empty) {
151+
updateBuildingState(true);
152+
designToCode({
153+
input: root,
154+
build_config: build_config,
155+
framework: preview_presets.default,
156+
asset_config: { asset_repository: MainImageRepository.instance },
157+
})
158+
.then(onVanillaPreviewResult)
159+
.catch(console.error)
160+
.finally(() => {
161+
updateBuildingState(false);
162+
});
163+
}
164+
}, [_is_mode_requires_preview_build, target?.id]);
165+
166+
// // ------------------------
167+
// // ------ for esbuild -----
168+
useEffect(() => {
169+
if (
170+
!state.editingModule ||
171+
// now only react is supported.
172+
state.editingModule.framework !== "react"
173+
) {
174+
return;
175+
}
176+
177+
const { raw, componentName } = state.editingModule;
178+
assert(componentName, "component name is required");
179+
assert(raw, "raw input code is required");
180+
updateBuildingState(true);
181+
bundler(transform(raw, componentName), "tsx")
182+
.then((d) => {
183+
if (d.err == null) {
184+
if (d.code) {
185+
onEsbuildReactPreviewResult({
186+
bundledjs: d.code,
187+
componentName: componentName,
188+
});
189+
}
190+
}
191+
})
192+
.finally(() => {
193+
updateBuildingState(false);
194+
});
195+
}, [state.editingModule?.framework, state.editingModule?.raw]);
196+
197+
return <>{children}</>;
198+
}
199+
200+
// function esbuildit(state: EditorState) {
201+
202+
// }
203+
204+
const transform = (s, n) => {
205+
return `import React from 'react'; import ReactDOM from 'react-dom';
206+
${s}
207+
const App = () => <><${n}/></>
208+
ReactDOM.render(<App />, document.querySelector('#root'));`;
209+
};

editor/scaffolds/editor/editor.tsx

Lines changed: 1 addition & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import React, { useEffect } from "react";
1+
import React from "react";
22
import { DefaultEditorWorkspaceLayout } from "layouts/default-editor-workspace-layout";
33
import {
44
WorkspaceContentPanel,
@@ -10,12 +10,6 @@ import { Canvas } from "scaffolds/canvas";
1010
import { CodeSegment } from "scaffolds/code";
1111
import { EditorSkeleton } from "./skeleton";
1212
import { colors } from "theme";
13-
import { RemoteImageRepositories } from "@design-sdk/figma-remote/lib/asset-repository/image-repository";
14-
import {
15-
ImageRepository,
16-
MainImageRepository,
17-
} from "@design-sdk/core/assets-repository";
18-
import { useFigmaAccessToken } from "hooks";
1913

2014
export function Editor({
2115
loading = false,
@@ -27,32 +21,6 @@ export function Editor({
2721
}) {
2822
const [state] = useEditorState();
2923

30-
const fat = useFigmaAccessToken();
31-
32-
useEffect(() => {
33-
// ------------------------------------------------------------
34-
// other platforms are not supported yet
35-
// set image repo for figma platform
36-
if (state.design) {
37-
MainImageRepository.instance = new RemoteImageRepositories(
38-
state.design.key,
39-
{
40-
authentication: {
41-
personalAccessToken: fat.personalAccessToken,
42-
accessToken: fat.accessToken.token,
43-
},
44-
}
45-
);
46-
MainImageRepository.instance.register(
47-
new ImageRepository(
48-
"fill-later-assets",
49-
"grida://assets-reservation/images/"
50-
)
51-
);
52-
}
53-
// ------------------------------------------------------------
54-
}, [state.design?.key, fat.accessToken]);
55-
5624
const _initially_loaded = state.design?.pages?.length > 0;
5725
const _initial_load_progress =
5826
[!!state.design?.input, state.design?.pages?.length > 0, !loading].filter(

0 commit comments

Comments
 (0)