Skip to content

Commit 5cb998e

Browse files
committed
Add @codesandbox/react-embed
1 parent d707151 commit 5cb998e

File tree

11 files changed

+709
-3
lines changed

11 files changed

+709
-3
lines changed

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,8 @@
3939
"packages/homepage",
4040
"packages/node-services",
4141
"packages/sandbox-hooks",
42-
"packages/sse-hooks"
42+
"packages/sse-hooks",
43+
"packages/react-embed"
4344
],
4445
"nohoist": [
4546
"**/gatsby",

packages/react-embed/.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
dist
2+
dist-es

packages/react-embed/package.json

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
{
2+
"name": "@codesandbox/react-embed",
3+
"version": "0.0.14",
4+
"license": "MIT",
5+
"private": false,
6+
"sideEffects": false,
7+
"main": "dist/index",
8+
"module": "dist-es/index",
9+
"typings": "dist/index.d.ts",
10+
"peerDependencies": {
11+
"react": "^15.0.0 | ^16.0.0"
12+
},
13+
"dependencies": {
14+
"react-codesandboxer": "^3.0.1"
15+
},
16+
"devDependencies": {
17+
"rimraf": "^2.6.2"
18+
},
19+
"scripts": {
20+
"watch": "tsc --watch",
21+
"clean": "rimraf dist dist-es",
22+
"build": "yarn clean && tsc || tsc -m es6 --outDir dist-es"
23+
},
24+
"files": [
25+
"dist",
26+
"dist-es"
27+
]
28+
}
Lines changed: 278 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,278 @@
1+
import * as React from 'react';
2+
// Replace this with the library later
3+
import CodeSandboxer from '../react-codesandboxer/CodeSandboxer';
4+
import {
5+
GitInfo,
6+
Package,
7+
ImportReplacement,
8+
Files,
9+
} from './codesandboxer-types';
10+
11+
export interface CodeSandboxerProps {
12+
/**
13+
* The absolute path to the example within the git file structure
14+
*
15+
* Example: 'examples/Basic.js'
16+
**/
17+
examplePath: string;
18+
/** Name for the codesandbox instance **/
19+
name?: string;
20+
/** This is all the information we need to fetch information from github or bitbucket **/
21+
gitInfo?: GitInfo;
22+
/** Pass in the example as code to prevent it being fetched **/
23+
example?: string | Promise<string>;
24+
/** Either take in a package.json object, or a string as the path of the package.json **/
25+
pkgJSON?: Package | string | Promise<Package | string>;
26+
/** paths in the example that we do not want to be pulled from their relativeLocation **/
27+
importReplacements?: Array<ImportReplacement>;
28+
/** Dependencies we always include. Most likely react and react-dom **/
29+
dependencies?: { [dep: string]: string };
30+
/** Do not actually deploy to codesanbox. Used to for testing alongside the return values of the render prop. **/
31+
skipRedirect?: boolean;
32+
ignoreInternalImports?: boolean;
33+
/** Called once loading has finished, whether it preloaded or not **/
34+
onLoadComplete?: (
35+
params: { parameters: string; files: Files } | { error: any }
36+
) => void;
37+
/** Called once a deploy has occurred. This will still be called if skipRedirect is chosen **/
38+
afterDeploy?: (sandboxUrl: string, sandboxId: string) => void;
39+
/** Called once a deploy has occurred. This will still be called if skipRedirect is chosen **/
40+
afterDeployError?: (e: Error) => void;
41+
/** Pass in files separately to fetching them. Useful to go alongisde specific replacements in importReplacements **/
42+
providedFiles?: Files;
43+
/**
44+
* Allow codesandboxer to accept more extensions like .jsx
45+
**/
46+
extensions?: string[];
47+
template?: 'create-react-app' | 'create-react-app-typescript';
48+
}
49+
50+
export interface Props {
51+
/**
52+
* Consumers may need access to the wrapper's style
53+
**/
54+
style?: Object;
55+
/**
56+
* What to show during loading
57+
*/
58+
children?: JSX.Element;
59+
/**
60+
* The height of the embed, defaults to 500px. Will be overridden if custom styles are given.
61+
*/
62+
height?: number | string;
63+
/**
64+
* The width of the embed, defaults to '100%'. Will be overridden if custom styles are given.
65+
*/
66+
width?: number | string;
67+
/**
68+
* What to render when there's an error
69+
*/
70+
renderError?: (e: Error) => JSX.Element;
71+
/**
72+
* The sandbox information to deploy
73+
*/
74+
sandboxOptions?: CodeSandboxerProps;
75+
/**
76+
* Configuration options for the embed
77+
*/
78+
embedOptions?: {
79+
/**
80+
* Hide the navigation bar of the preview
81+
*/
82+
hidenavigation?: boolean;
83+
/**
84+
* Only evaluate the module that is opened in the editor
85+
*/
86+
moduleview?: boolean;
87+
/**
88+
* Use the CodeMirror editor instead of Monaco (results in smaller payload size of the embed)
89+
*/
90+
codemirror?: boolean;
91+
/**
92+
* Enable eslint (increases payload slightly)
93+
*/
94+
eslint?: boolean;
95+
/**
96+
* Force a full refresh of the frame after every edit
97+
*/
98+
forcerefresh?: boolean;
99+
/**
100+
* Start with the devtools open
101+
*/
102+
expanddevtools?: boolean;
103+
/**
104+
* Only load the preview when the user clicks on a button
105+
*/
106+
runonclick?: boolean;
107+
/**
108+
* Which view to open by default
109+
*/
110+
view?: 'editor' | 'split' | 'preview';
111+
/**
112+
* Which preview window to open by default
113+
*/
114+
previewwindow?: 'console' | 'tests' | 'browser';
115+
/**
116+
* Which module to open by default (absolute path starting with a '/')
117+
*/
118+
module?: string;
119+
/**
120+
* Which url to initially load in address bar
121+
*/
122+
initialpath?: string;
123+
/**
124+
* The font size of the editor (in px)
125+
*/
126+
fontsize?: number;
127+
/**
128+
* Which lines to hightlight (only works in codemirror).
129+
*/
130+
highlights?: number[];
131+
/**
132+
* Size of the editor (in percentage)
133+
*/
134+
editorsize?: number;
135+
/**
136+
* Whether to show the editor and preview vertically
137+
*/
138+
verticallayout?: boolean;
139+
};
140+
}
141+
142+
export default class SandboxEmbed extends React.PureComponent<Props> {
143+
state: {
144+
sandboxId: string | null;
145+
oldSandboxId: string | null;
146+
error: {
147+
name: string;
148+
message: string;
149+
} | null;
150+
} = {
151+
sandboxId: null,
152+
oldSandboxId: null,
153+
error: null,
154+
};
155+
newSandboxTimeout = null;
156+
157+
generateEmbedURL = (sandboxId: string) => {
158+
let url = `https://codesandbox.io/embed/${sandboxId}`;
159+
function getValue(option, value) {
160+
if (typeof value === 'boolean') {
161+
return value ? 1 : 0;
162+
}
163+
164+
if (option === 'highlights') {
165+
return value.join(',');
166+
}
167+
168+
return value;
169+
}
170+
171+
const { embedOptions = {} } = this.props;
172+
173+
embedOptions.module = embedOptions.module || '/example.js';
174+
const options = Object.keys(embedOptions)
175+
.map(option => `${option}=${getValue(option, embedOptions[option])}`)
176+
.join('&');
177+
178+
if (options) {
179+
url += `?${options}`;
180+
}
181+
182+
return url;
183+
};
184+
185+
afterDeploy = (sandboxUrl: string, sandboxId: string) => {
186+
this.setState({ sandboxId, oldSandBoxId: null, error: null });
187+
188+
if (this.props.sandboxOptions && this.props.sandboxOptions.afterDeploy) {
189+
this.props.sandboxOptions.afterDeploy(sandboxUrl, sandboxId);
190+
}
191+
};
192+
193+
onLoadComplete = (
194+
params: { parameters: string; files: Files } & { error: any }
195+
) => {
196+
if (params.error) {
197+
this.setState({ sandboxId: null, error: params.error });
198+
}
199+
200+
if (this.props.sandboxOptions && this.props.sandboxOptions.onLoadComplete) {
201+
this.props.sandboxOptions.onLoadComplete(params);
202+
}
203+
};
204+
205+
componentWillUpdate(nextProps) {
206+
if (nextProps.sandboxOptions !== this.props.sandboxOptions) {
207+
// This will help smoothen the transition between two sandboxes
208+
209+
this.setState({
210+
sandboxId: null,
211+
oldSandboxId: this.state.sandboxId,
212+
error: null,
213+
});
214+
215+
this.newSandboxTimeout = setTimeout(() => {
216+
this.setState({
217+
oldSandboxId: null,
218+
});
219+
}, 600);
220+
}
221+
}
222+
223+
componentWillUnmount() {
224+
clearTimeout(this.newSandboxTimeout);
225+
}
226+
227+
render() {
228+
const {
229+
style = {
230+
width: this.props.width == null ? '100%' : this.props.width,
231+
height: this.props.height == null ? 500 : this.props.height,
232+
outline: 0,
233+
border: 0,
234+
borderRadius: 4,
235+
},
236+
} = this.props;
237+
238+
if (this.state.error) {
239+
return this.props.renderError ? (
240+
this.props.renderError(this.state.error)
241+
) : (
242+
<div style={this.props.style}>
243+
Something went wrong while fetching the sandbox:{' '}
244+
{this.state.error.message}
245+
</div>
246+
);
247+
}
248+
249+
const usedSandboxId = this.state.sandboxId || this.state.oldSandboxId;
250+
251+
return (
252+
<React.Fragment>
253+
{usedSandboxId && (
254+
<iframe
255+
name="codesandbox"
256+
style={style}
257+
src={this.generateEmbedURL(usedSandboxId)}
258+
/>
259+
)}
260+
{!this.state.sandboxId && (
261+
<CodeSandboxer
262+
{...this.props.sandboxOptions}
263+
autoDeploy
264+
skipRedirect
265+
afterDeploy={this.afterDeploy}
266+
onLoadComplete={this.onLoadComplete}
267+
>
268+
{() =>
269+
this.state.oldSandboxId
270+
? null
271+
: this.props.children || 'Loading Sandbox...'
272+
}
273+
</CodeSandboxer>
274+
)}
275+
</React.Fragment>
276+
);
277+
}
278+
}
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
// Synced with https://github.com/Noviny/codesandboxer/blob/master/packages/codesandboxer/src/types.js
2+
3+
export type GitInfo = {
4+
account: string;
5+
repository: string;
6+
branch?: string;
7+
host: 'bitbucket' | 'github';
8+
};
9+
10+
export type Files = {
11+
[key: string]: {
12+
content: string;
13+
};
14+
};
15+
16+
export type ParsedFile = {
17+
file: string;
18+
deps: { [key: string]: string };
19+
internalImports: Array<string>;
20+
path: string;
21+
};
22+
23+
export type parsedFileFirst = {
24+
file: string;
25+
deps: { [key: string]: string };
26+
internalImports: Array<string>;
27+
};
28+
29+
export type Package = {
30+
name: string;
31+
version: string;
32+
dependencies: {
33+
[key: string]: string;
34+
};
35+
devDependencies: {
36+
[key: string]: string;
37+
};
38+
peerDependencies: {
39+
[key: string]: string;
40+
};
41+
};
42+
43+
export type Dependencies = { [key: string]: string };
44+
45+
export type Config = { extensions: string[] };
46+
47+
export type Import = string;
48+
49+
export type ImportReplacement = [string, string];
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
import SandboxEmbed from './SandboxEmbed';
2+
3+
export default SandboxEmbed;

packages/react-embed/src/index.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
import SandboxEmbed from './SandboxEmbed';
2+
3+
export { SandboxEmbed };

0 commit comments

Comments
 (0)