Skip to content

Commit 79d2507

Browse files
authored
Merge pull request #55 from dabbott/html-environment
Add experimental HTML environment
2 parents f1e1b88 + 53dda41 commit 79d2507

File tree

12 files changed

+159
-31
lines changed

12 files changed

+159
-31
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@ The sandbox accepts the following props/parameters.
9696

9797
| Title | Description | Default |
9898
| ------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------- |
99-
| **`preset`** | This sets reasonable defaults for other parameters. Options are `javascript`, `react`, and `react-native`. | `'javascript'` |
99+
| **`preset`** | This sets reasonable defaults for other parameters. Options are `javascript`, `react`, and `react-native`. Experimental options are `html` and `python` - make sure to lock down your javascript-playgrounds library version if you use these, since they may change. | `'javascript'` |
100100
| **`title`** | An optional title for the editor pane. | `''` |
101101
| **`code`** | The code to show/run in the player. | The sample app |
102102
| **`files`** | A map of `{ [filename]: code }`. This will take precedence over `code` if given. | `undefined` |

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "javascript-playgrounds",
3-
"version": "1.1.0",
3+
"version": "1.1.1",
44
"description": "Interactive JavaScript sandbox",
55
"main": "dist/javascript-playgrounds.js",
66
"files": [

src/components/workspace/Editor.tsx

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import { SourceLocation, LogCommand } from '../../types/Messages'
1111
import type * as ts from 'typescript'
1212
import CodeMirror from 'codemirror'
1313
import type { PlaygroundOptions } from './Workspace'
14+
import { extname } from '../../utils/path'
1415

1516
// Import scrollPosIntoView directly. The public API calls the native DOM scrollIntoView,
1617
// which will scroll the parent window when displayed in an iframe.
@@ -36,8 +37,15 @@ const styles = prefixObject({
3637

3738
const docCache: Record<string, CM.Doc> = {}
3839

39-
function getMode(filename: string) {
40-
return filename.endsWith('.py') ? 'python' : 'text/typescript-jsx'
40+
const modeMap: Record<string, string> = {
41+
'.py': 'python',
42+
'.css': 'css',
43+
'.html': 'htmlmixed',
44+
}
45+
46+
function getMode(filename: string): string {
47+
const ext = extname(filename)
48+
return modeMap[ext] || 'text/typescript-jsx'
4149
}
4250

4351
export interface Props {

src/components/workspace/PlayerFrame.tsx

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,8 @@ export default class extends PureComponent<Props, State> {
6969
id: null,
7070
}
7171

72+
iframe = React.createRef<HTMLIFrameElement>()
73+
7274
componentDidMount() {
7375
const { sharedEnvironment } = this.props
7476

@@ -128,7 +130,7 @@ export default class extends PureComponent<Props, State> {
128130
this.codeVersion = codeVersion
129131
break
130132
case 'ready':
131-
;(this.refs.iframe as HTMLIFrameElement).contentWindow!.postMessage(
133+
this.iframe.current!.contentWindow!.postMessage(
132134
{ fileMap, entry, codeVersion, source: 'rnwp' },
133135
'*'
134136
)
@@ -176,7 +178,7 @@ export default class extends PureComponent<Props, State> {
176178
return (
177179
<iframe
178180
style={styles.iframe}
179-
ref={'iframe'}
181+
ref={this.iframe}
180182
frameBorder={0}
181183
src={`player.html#${queryString}`}
182184
/>
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
import { appendCSS } from '../utils/CSS'
2+
import { initializeCommunication } from '../utils/playerCommunication'
3+
import { createAppLayout } from '../utils/PlayerUtils'
4+
import { EnvironmentOptions, IEnvironment } from './IEnvironment'
5+
6+
export class HTMLEnvironment implements IEnvironment {
7+
async initialize({
8+
id,
9+
sharedEnvironment,
10+
styles,
11+
}: EnvironmentOptions): Promise<void> {
12+
const { appElement } = createAppLayout(document, styles)
13+
const iframe = document.createElement('iframe')
14+
15+
appElement.appendChild(iframe)
16+
17+
initializeCommunication({
18+
id,
19+
prefixLineCount: 0,
20+
sharedEnvironment,
21+
onRunApplication: (context) => {
22+
const entryFile = context.fileMap[context.entry]
23+
24+
const document = iframe.contentDocument
25+
26+
if (!document) return
27+
28+
// https://stackoverflow.com/questions/5784638/replace-entire-content-of-iframe
29+
document.close()
30+
document.open()
31+
document.write(entryFile)
32+
document.close()
33+
34+
const cssFiles = Object.entries(context.fileMap).filter(([name]) =>
35+
name.endsWith('.css')
36+
)
37+
38+
cssFiles.forEach(([_name, value]) => {
39+
appendCSS(document, value)
40+
})
41+
},
42+
})
43+
}
44+
}
45+
46+
export default new HTMLEnvironment()

src/environments/javascript-environment.tsx

Lines changed: 2 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import VendorComponents, {
88
import formatError from '../utils/formatError'
99
import * as path from '../utils/path'
1010
import { initializeCommunication } from '../utils/playerCommunication'
11+
import { createAppLayout } from '../utils/PlayerUtils'
1112
import { prefixAndApply } from '../utils/Styles'
1213
import type {
1314
EnvironmentOptions,
@@ -196,19 +197,7 @@ export class JavaScriptEnvironment implements IEnvironment {
196197
detectedModules,
197198
hasModule: this.hasModule,
198199
}).then(() => {
199-
const mount = document.getElementById('player-root') as HTMLDivElement
200-
prefixAndApply(styles.playerRoot, mount)
201-
202-
const wrapperElement = document.createElement('div')
203-
prefixAndApply(styles.playerWrapper, wrapperElement)
204-
205-
mount.appendChild(wrapperElement)
206-
207-
const appElement = document.createElement('div')
208-
appElement.id = 'app'
209-
prefixAndApply(styles.playerApp, appElement)
210-
211-
wrapperElement.appendChild(appElement)
200+
const { appElement, wrapperElement } = createAppLayout(document, styles)
212201

213202
if (statusBarHeight > 0) {
214203
const statusBarStyle: CSSProperties = {

src/index.tsx

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import ReactDOM from 'react-dom'
99
import { getHashString, buildHashString } from './utils/HashString'
1010
import { prefixAndApply } from './utils/Styles'
1111
import { appendCSS } from './utils/CSS'
12-
import { normalize, InternalOptions, PublicOptions } from './utils/options'
12+
import { normalize, PublicOptions, getFileExtensions } from './utils/options'
1313
import App from './components/workspace/App'
1414

1515
const { data = '{}', preset } = getHashString()
@@ -20,10 +20,12 @@ if (preset) {
2020
publicOptions.preset = decodeURIComponent(preset)
2121
}
2222

23-
const { css, targetOrigin, ...rest }: InternalOptions = normalize(publicOptions)
23+
const internalOptions = normalize(publicOptions)
24+
25+
const { css, targetOrigin, ...rest } = internalOptions
2426

2527
if (css) {
26-
appendCSS(css)
28+
appendCSS(document, css)
2729
}
2830

2931
const mount = document.getElementById('player-root') as HTMLDivElement
@@ -35,11 +37,20 @@ function render() {
3537
ReactDOM.render(<App onChange={onChange} {...rest} />, mount)
3638
}
3739

38-
if (rest.environmentName === 'python') {
39-
import('codemirror/mode/python/python' as any).then(render)
40-
} else {
41-
render()
40+
const extensions = getFileExtensions(internalOptions)
41+
const editorModes: Promise<void>[] = []
42+
43+
if (extensions.includes('.py')) {
44+
editorModes.push(import('codemirror/mode/python/python' as any))
45+
}
46+
if (extensions.includes('.html')) {
47+
editorModes.push(import('codemirror/mode/htmlmixed/htmlmixed' as any))
4248
}
49+
if (extensions.includes('.css')) {
50+
editorModes.push(import('codemirror/mode/css/css' as any))
51+
}
52+
53+
Promise.all(editorModes).then(render)
4354

4455
function onChange(files: Record<string, string>) {
4556
const merged = {

src/player.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ if (styleSheet === 'reset') {
3131
require('./styles/player.css')
3232

3333
if (css) {
34-
appendCSS(css)
34+
appendCSS(document, css)
3535
}
3636

3737
export type PlayerStyles = {

src/utils/CSS.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
export const appendCSS = (css: string) => {
1+
export const appendCSS = (document: Document, css: string) => {
22
const textNode = document.createTextNode(css)
33
const element = document.createElement('style')
44
element.type = 'text/css'

src/utils/CodeMirror.ts

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,14 @@ export const getOptions = (mode: string) => ({
1515
extraKeys: {
1616
Tab: 'indentMore',
1717
'Cmd-/': (cm: CodeMirror.Editor) => {
18-
cm.listSelections().forEach((selection) => {
18+
// Improve commenting within JSX (the default is HTML-style comments)
19+
if (mode === 'text/typescript-jsx') {
1920
;(cm as any).toggleComment({
20-
lineComment: mode === 'python' ? '#' : '//',
21+
lineComment: '//',
2122
})
22-
})
23+
} else {
24+
cm.execCommand('toggleComment')
25+
}
2326
},
2427
},
2528
})

0 commit comments

Comments
 (0)