Skip to content

Commit 3847663

Browse files
xycCompuIves
authored andcommitted
Multi-line input for DevTools Console (codesandbox#454)
* add monaco editor in console * style and theme fixes for monaco, next up adjust input height * multiline input basically works * keyboard interactions; code cleanup * minor code cleanup * use css to style background color of prompt instead of theme; add resize handling * fix style issue on firefox * add self to contributors * replace Input.js with MonacoInput.js * remove unused var * fix styles for CodeMirror * extract define theme extract define theme, remove unused state command * remove CodeMirror specific styles since we have define theme; add CodeSandbox theme styles * optimize: only resize editor and container when line count changes * redo add contributor
1 parent 424490e commit 3847663

File tree

6 files changed

+205
-71
lines changed

6 files changed

+205
-71
lines changed

.all-contributorsrc

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -265,6 +265,15 @@
265265
"code",
266266
"ideas"
267267
]
268+
},
269+
{
270+
"login": "xyc",
271+
"name": "Xiaoyi Chen",
272+
"avatar_url": "https://avatars3.githubusercontent.com/u/679275?v=4",
273+
"profile": "https://twitter.com/chxy",
274+
"contributions": [
275+
"code"
276+
]
268277
}
269278
]
270279
}

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# [CodeSandbox](https://codesandbox.io) [![Chat](https://img.shields.io/badge/chat-on%20discord-7289da.svg)](https://discord.gg/KE3TbEZ) [![All Contributors](https://img.shields.io/badge/all_contributors-26-orange.svg?style=flat-square)](#contributors) [![Build Status](https://travis-ci.org/CompuIves/codesandbox-client.svg?branch=master)](https://travis-ci.org/CompuIves/codesandbox-client) [![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=flat-square)](http://makeapullrequest.com) [![first-timers-only Friendly](https://img.shields.io/badge/first--timers--only-friendly-blue.svg)](http://www.firsttimersonly.com/)
1+
# [CodeSandbox](https://codesandbox.io) [![Chat](https://img.shields.io/badge/chat-on%20discord-7289da.svg)](https://discord.gg/KE3TbEZ) [![All Contributors](https://img.shields.io/badge/all_contributors-27-orange.svg?style=flat-square)](#contributors) [![Build Status](https://travis-ci.org/CompuIves/codesandbox-client.svg?branch=master)](https://travis-ci.org/CompuIves/codesandbox-client) [![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=flat-square)](http://makeapullrequest.com) [![first-timers-only Friendly](https://img.shields.io/badge/first--timers--only-friendly-blue.svg)](http://www.firsttimersonly.com/)
22

33
An online code editor tailored for web applications.
44

@@ -40,5 +40,5 @@ Thanks goes to these wonderful people
4040
| :---: | :---: | :---: | :---: | :---: | :---: | :---: |
4141
| [<img src="https://avatars3.githubusercontent.com/u/1239401?v=4" width="100px;"/><br /><sub><b>Johann Hubert Sonntagbauer</b></sub>](https://github.com/johann-sonntagbauer)<br />[🐛](https://github.com/CompuIves/codesandbox-client/issues?q=author%3Ajohann-sonntagbauer "Bug reports") [💻](https://github.com/CompuIves/codesandbox-client/commits?author=johann-sonntagbauer "Code") | [<img src="https://avatars2.githubusercontent.com/u/9586897?v=4" width="100px;"/><br /><sub><b>Joachim Seminck</b></sub>](https://github.com/jseminck)<br />[💻](https://github.com/CompuIves/codesandbox-client/commits?author=jseminck "Code") | [<img src="https://avatars3.githubusercontent.com/u/5210019?v=4" width="100px;"/><br /><sub><b>Subramanya Chakravarthy</b></sub>](http://chakrihacker.github.io)<br />[💻](https://github.com/CompuIves/codesandbox-client/commits?author=chakrihacker "Code") | [<img src="https://avatars3.githubusercontent.com/u/23088?v=4" width="100px;"/><br /><sub><b>Robert (Robby) O'Connor</b></sub>](http://robby.oconnor.ninja)<br />[🚇](#infra-robbyoconnor "Infrastructure (Hosting, Build-Tools, etc)") | [<img src="https://avatars0.githubusercontent.com/u/2083930?v=4" width="100px;"/><br /><sub><b>Bogdan Luca</b></sub>](https://github.com/lbogdan)<br />[🐛](https://github.com/CompuIves/codesandbox-client/issues?q=author%3Albogdan "Bug reports") [💻](https://github.com/CompuIves/codesandbox-client/commits?author=lbogdan "Code") | [<img src="https://avatars3.githubusercontent.com/u/6177621?v=4" width="100px;"/><br /><sub><b>Divjot Singh</b></sub>](http://bogas04.github.io)<br />[💻](https://github.com/CompuIves/codesandbox-client/commits?author=bogas04 "Code") | [<img src="https://avatars3.githubusercontent.com/u/5249539?v=4" width="100px;"/><br /><sub><b>Jason Nall</b></sub>](http://www.jsonnull.com)<br />[💻](https://github.com/CompuIves/codesandbox-client/commits?author=jsonnull "Code") |
4242
| [<img src="https://avatars3.githubusercontent.com/u/784056?v=4" width="100px;"/><br /><sub><b>Lionel</b></sub>](https://elrumordelaluz.com)<br />[💻](https://github.com/CompuIves/codesandbox-client/commits?author=elrumordelaluz "Code") [🎨](#design-elrumordelaluz "Design") | [<img src="https://avatars3.githubusercontent.com/u/170500?v=4" width="100px;"/><br /><sub><b>Philipp Brumm</b></sub>](https://github.com/brumm)<br />[💻](https://github.com/CompuIves/codesandbox-client/commits?author=brumm "Code") | [<img src="https://avatars2.githubusercontent.com/u/2678610?v=4" width="100px;"/><br /><sub><b>Valentin Hervieu</b></sub>](http://valentin-hervieu.fr)<br />[💻](https://github.com/CompuIves/codesandbox-client/commits?author=ValentinH "Code") [🐛](https://github.com/CompuIves/codesandbox-client/issues?q=author%3AValentinH "Bug reports") | [<img src="https://avatars0.githubusercontent.com/u/1499218?v=4" width="100px;"/><br /><sub><b>Anenth</b></sub>](http://anenth.js.org)<br />[💻](https://github.com/CompuIves/codesandbox-client/commits?author=Anenth "Code") [🎨](#design-Anenth "Design") [🤔](#ideas-Anenth "Ideas, Planning, & Feedback") | [<img src="https://avatars0.githubusercontent.com/u/410792?v=4" width="100px;"/><br /><sub><b>Dony Sukardi</b></sub>](http://dsds.io)<br />[🐛](https://github.com/CompuIves/codesandbox-client/issues?q=author%3Adonysukardi "Bug reports") [💻](https://github.com/CompuIves/codesandbox-client/commits?author=donysukardi "Code") | [<img src="https://avatars3.githubusercontent.com/u/89046?v=4" width="100px;"/><br /><sub><b>Geoffrey Dhuyvetters</b></sub>](https://github.com/duivvv)<br />[🎨](#design-duivvv "Design") [💻](https://github.com/CompuIves/codesandbox-client/commits?author=duivvv "Code") | [<img src="https://avatars3.githubusercontent.com/u/3381746?v=4" width="100px;"/><br /><sub><b>Eswar Yaganti</b></sub>](http://nyaganti.com)<br />[💻](https://github.com/CompuIves/codesandbox-client/commits?author=nagamalli9999 "Code") [🚇](#infra-nagamalli9999 "Infrastructure (Hosting, Build-Tools, etc)") |
43-
| [<img src="https://avatars3.githubusercontent.com/u/9488719?v=4" width="100px;"/><br /><sub><b>Frank Tan</b></sub>](https://github.com/tansongyang)<br />[💻](https://github.com/CompuIves/codesandbox-client/commits?author=tansongyang "Code") | [<img src="https://avatars0.githubusercontent.com/u/1650995?v=4" width="100px;"/><br /><sub><b>Bilal Budhani</b></sub>](https://bilalbudhani.com)<br />[💻](https://github.com/CompuIves/codesandbox-client/commits?author=BilalBudhani "Code") | [<img src="https://avatars3.githubusercontent.com/u/843342?v=4" width="100px;"/><br /><sub><b>El Juli</b></sub>](https://github.com/JulianMayorga)<br />[💻](https://github.com/CompuIves/codesandbox-client/commits?author=JulianMayorga "Code") | [<img src="https://avatars0.githubusercontent.com/u/13774309?v=4" width="100px;"/><br /><sub><b>Arthur Denner</b></sub>](https://github.com/arthurdenner)<br />[💻](https://github.com/CompuIves/codesandbox-client/commits?author=arthurdenner "Code") | [<img src="https://avatars3.githubusercontent.com/u/12954909?v=4" width="100px;"/><br /><sub><b>Radi Cho</b></sub>](https://github.com/radi-cho)<br />[🐛](https://github.com/CompuIves/codesandbox-client/issues?q=author%3Aradi-cho "Bug reports") [💻](https://github.com/CompuIves/codesandbox-client/commits?author=radi-cho "Code") [🤔](#ideas-radi-cho "Ideas, Planning, & Feedback") |
43+
| [<img src="https://avatars3.githubusercontent.com/u/9488719?v=4" width="100px;"/><br /><sub><b>Frank Tan</b></sub>](https://github.com/tansongyang)<br />[💻](https://github.com/CompuIves/codesandbox-client/commits?author=tansongyang "Code") | [<img src="https://avatars0.githubusercontent.com/u/1650995?v=4" width="100px;"/><br /><sub><b>Bilal Budhani</b></sub>](https://bilalbudhani.com)<br />[💻](https://github.com/CompuIves/codesandbox-client/commits?author=BilalBudhani "Code") | [<img src="https://avatars3.githubusercontent.com/u/843342?v=4" width="100px;"/><br /><sub><b>El Juli</b></sub>](https://github.com/JulianMayorga)<br />[💻](https://github.com/CompuIves/codesandbox-client/commits?author=JulianMayorga "Code") | [<img src="https://avatars0.githubusercontent.com/u/13774309?v=4" width="100px;"/><br /><sub><b>Arthur Denner</b></sub>](https://github.com/arthurdenner)<br />[💻](https://github.com/CompuIves/codesandbox-client/commits?author=arthurdenner "Code") | [<img src="https://avatars3.githubusercontent.com/u/12954909?v=4" width="100px;"/><br /><sub><b>Radi Cho</b></sub>](https://github.com/RSG-Group)<br />[🐛](https://github.com/CompuIves/codesandbox-client/issues?q=author%3Aradi-cho "Bug reports") [💻](https://github.com/CompuIves/codesandbox-client/commits?author=radi-cho "Code") [🤔](#ideas-radi-cho "Ideas, Planning, & Feedback") | [<img src="https://avatars3.githubusercontent.com/u/679275?v=4" width="100px;"/><br /><sub><b>Xiaoyi Chen</b></sub>](https://twitter.com/chxy)<br />[💻](https://github.com/CompuIves/codesandbox-client/commits?author=xyc "Code") |
4444
<!-- ALL-CONTRIBUTORS-LIST:END -->

packages/app/src/app/components/sandbox/CodeEditor/Monaco.js

Lines changed: 2 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import TypingsFetcherWorker from 'worker-loader?name=monaco-typings-ata.[hash].w
2121
/* eslint-enable import/no-webpack-loader-syntax */
2222

2323
import MonacoEditor from './monaco/MonacoReactComponent';
24+
import defineTheme from './monaco/define-theme';
2425
import FuzzySearch from './FuzzySearch/index';
2526

2627
let modelCache = {};
@@ -583,18 +584,6 @@ export default class CodeEditor extends React.Component<Props, State> {
583584
this.lint(newCode, this.props.title, this.editor.getModel().getVersionId());
584585
};
585586

586-
editorWillMount = monaco => {
587-
monaco.editor.defineTheme('CodeSandbox', {
588-
base: 'vs-dark', // can also be vs-dark or hc-black
589-
inherit: true, // can also be false to completely replace the builtin rules
590-
rules: [
591-
{ token: 'comment', foreground: '626466' },
592-
{ token: 'keyword', foreground: '6CAEDD' },
593-
{ token: 'identifier', foreground: 'fac863' },
594-
],
595-
});
596-
};
597-
598587
hasNativeTypescript = () => {
599588
const template = getTemplate(this.props.template);
600589
return template.sourceConfig && template.sourceConfig.typescript;
@@ -928,7 +917,7 @@ export default class CodeEditor extends React.Component<Props, State> {
928917
theme="CodeSandbox"
929918
options={options}
930919
editorDidMount={this.configureEditor}
931-
editorWillMount={this.editorWillMount}
920+
editorWillMount={defineTheme}
932921
openReference={this.openReference}
933922
/>
934923
</CodeContainer>
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
let isThemeDefined = false;
2+
const defineTheme = monaco => {
3+
if (!isThemeDefined) {
4+
monaco.editor.defineTheme('CodeSandbox', {
5+
base: 'vs-dark', // can also be vs-dark or hc-black
6+
inherit: true, // can also be false to completely replace the builtin rules
7+
rules: [
8+
{ token: 'comment', foreground: '626466' },
9+
{ token: 'keyword', foreground: '6CAEDD' },
10+
{ token: 'identifier', foreground: 'fac863' },
11+
],
12+
});
13+
isThemeDefined = true;
14+
}
15+
};
16+
17+
export default defineTheme;
Lines changed: 173 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -1,95 +1,213 @@
1+
/* @flow */
12
import React from 'react';
23
import styled from 'styled-components';
34

45
import ChevronRight from 'react-icons/lib/md/chevron-right';
56
import theme from 'common/theme';
67

8+
import MonacoEditor from 'app/components/sandbox/CodeEditor/monaco/MonacoReactComponent';
9+
import defineTheme from 'app/components/sandbox/CodeEditor/monaco/define-theme';
10+
711
import { IconContainer } from './styles';
812

13+
const CONSOLE_INPUT_TOP_PADDING = 6;
14+
const CONSOLE_INPUT_BOTTOM_PADDING = 6;
15+
const CONSOLE_INPUT_PADDING =
16+
CONSOLE_INPUT_TOP_PADDING + CONSOLE_INPUT_BOTTOM_PADDING;
17+
const CONSOLE_INPUT_LINE_HEIGHT = 20;
18+
const CONSOLE_INPUT_MAX_HEIGHT = 110;
19+
920
const Container = styled.div`
21+
flex-shrink: 0;
1022
position: relative;
11-
height: 2rem;
23+
height: ${props => props.height}px;
1224
min-height: 2rem;
1325
width: 100%;
1426
background-color: ${props => props.theme.background.darken(0.3)};
15-
1627
display: flex;
17-
align-items: center;
28+
align-items: flex-start;
29+
30+
.monaco-editor-background {
31+
background-color: ${props => props.theme.background.darken(0.3)};
32+
}
33+
34+
.react-monaco-editor-container {
35+
overflow: visible !important;
36+
}
37+
38+
.mtk5 {
39+
color: #99c794 !important;
40+
}
41+
.mtk12.PropertyAssignment {
42+
color: #99c794;
43+
}
44+
.mtk12.PropertyAssignment.PropertyAccessExpression {
45+
color: #fac863;
46+
}
47+
.Identifier.CallExpression
48+
+ .OpenParenToken.CallExpression
49+
+ .Identifier.CallExpression {
50+
color: #fac863 !important;
51+
}
1852
`;
1953

20-
const Input = styled.input`
54+
type Props = {
55+
evaluateConsole: (command: string) => void,
56+
};
57+
58+
const InputWrapper = styled.div`
2159
position: relative;
22-
height: 1.5rem;
60+
height: 100%;
61+
padding-top: ${CONSOLE_INPUT_TOP_PADDING}px;
62+
padding-bottom: ${CONSOLE_INPUT_BOTTOM_PADDING}px;
63+
box-sizing: border-box;
2364
width: 100%;
24-
background-color: ${props => props.theme.background.darken(0.3)};
25-
border: none;
26-
outline: none;
27-
color: rgba(255, 255, 255, 0.8);
28-
font-family: Menlo, monospace;
29-
font-size: 13px;
3065
`;
3166

32-
type Props = {
33-
evaluateConsole: (command: string) => void,
67+
const monacoOptions = {
68+
language: 'javascript',
69+
wordWrap: 'on',
70+
overviewRulerLanes: 0,
71+
glyphMargin: false,
72+
lineNumbers: 'off',
73+
folding: false,
74+
selectOnLineNumbers: false,
75+
selectionHighlight: false,
76+
cursorStyle: 'line-thin',
77+
scrollbar: {
78+
useShadows: false,
79+
horizontal: 'hidden',
80+
verticalScrollbarSize: 9,
81+
},
82+
lineDecorationsWidth: 0,
83+
scrollBeyondLastLine: false,
84+
renderLineHighlight: 'none',
85+
minimap: {
86+
enabled: false,
87+
},
88+
contextmenu: false,
89+
ariaLabel: 'ConsoleInput',
90+
fontFamily: 'Menlo, monospace',
91+
fontSize: 13,
3492
};
3593

36-
export default class ConsoleInput extends React.PureComponent<Props> {
94+
function noop() {
95+
return Promise.resolve();
96+
}
97+
98+
class ConsoleInput extends React.PureComponent<Props> {
99+
sizeProbeInterval: number;
100+
37101
state = {
38-
command: '',
39102
commandHistory: [],
40103
commandCursor: -1,
104+
105+
editorHeight: CONSOLE_INPUT_LINE_HEIGHT,
41106
};
42107

43-
onChange = e => {
44-
if (e.target) {
45-
this.setState({ command: e.target.value });
46-
}
108+
resizeEditor = () => {
109+
this.editor.layout();
47110
};
48111

49-
handleKeyDown = e => {
50-
const { evaluateConsole } = this.props;
51-
52-
if (e.keyCode === 13) {
53-
e.preventDefault();
54-
e.stopPropagation();
55-
// Enter
56-
evaluateConsole(this.state.command);
57-
this.setState({
58-
command: '',
59-
commandHistory: [this.state.command, ...this.state.commandHistory],
60-
});
61-
} else if (e.keyCode === 38) {
62-
const newCursor = Math.min(
63-
this.state.commandCursor + 1,
64-
this.state.commandHistory.length - 1
65-
);
66-
// Up arrow
67-
this.setState({
68-
command: this.state.commandHistory[newCursor] || '',
69-
commandCursor: newCursor,
70-
});
71-
} else if (e.keyCode === 40) {
72-
const newCursor = Math.max(this.state.commandCursor - 1, -1);
73-
// Down arrow
74-
this.setState({
75-
command: this.state.commandHistory[newCursor] || '',
76-
commandCursor: newCursor,
77-
});
78-
}
112+
editorDidMount = async editor => {
113+
this.editor = editor;
114+
115+
let lastLineCount = 1;
116+
editor.onDidChangeModelContent(() => {
117+
const lineCount = editor.getModel().getLineCount();
118+
if (lineCount !== lastLineCount) {
119+
this.setState({
120+
editorHeight: Math.min(
121+
CONSOLE_INPUT_MAX_HEIGHT,
122+
lineCount * CONSOLE_INPUT_LINE_HEIGHT
123+
),
124+
});
125+
this.resizeEditor();
126+
lastLineCount = lineCount;
127+
}
128+
});
129+
130+
editor.onKeyDown(event => {
131+
const e = event.browserEvent;
132+
const { evaluateConsole } = this.props;
133+
134+
if (e.keyCode === 13) {
135+
// Enter
136+
if (e.shiftKey) {
137+
return;
138+
}
139+
e.preventDefault();
140+
e.stopPropagation();
141+
const command = editor.getModel().getValue();
142+
evaluateConsole(command);
143+
editor.setValue('');
144+
this.setState({
145+
commandCursor: -1,
146+
commandHistory: [command, ...this.state.commandHistory],
147+
});
148+
} else if (e.keyCode === 38) {
149+
// Up arrow
150+
const lineNumber = editor.getPosition().lineNumber;
151+
if (lineNumber !== 1) {
152+
return;
153+
}
154+
155+
const newCursor = Math.min(
156+
this.state.commandCursor + 1,
157+
this.state.commandHistory.length - 1
158+
);
159+
editor.setValue(this.state.commandHistory[newCursor] || '');
160+
this.setState({
161+
commandCursor: newCursor,
162+
});
163+
} else if (e.keyCode === 40) {
164+
// Down arrow
165+
const lineNumber = editor.getPosition().lineNumber;
166+
const lineCount = editor.getModel().getLineCount();
167+
if (lineNumber !== lineCount) {
168+
return;
169+
}
170+
171+
const newCursor = Math.max(this.state.commandCursor - 1, -1);
172+
editor.setValue(this.state.commandHistory[newCursor] || '');
173+
this.setState({
174+
commandCursor: newCursor,
175+
});
176+
}
177+
});
178+
179+
window.addEventListener('resize', this.resizeEditor);
180+
this.sizeProbeInterval = setInterval(this.resizeEditor.bind(this), 3000);
79181
};
80182

183+
componentWillUnmount() {
184+
window.removeEventListener('resize', this.resizeEditor);
185+
clearTimeout(this.sizeProbeInterval);
186+
if (this.editor) {
187+
this.editor.dispose();
188+
}
189+
}
190+
81191
render() {
82192
return (
83-
<Container>
193+
<Container height={this.state.editorHeight + 12}>
84194
<IconContainer style={{ color: theme.secondary() }}>
85195
<ChevronRight />
86196
</IconContainer>
87-
<Input
88-
value={this.state.command}
89-
onChange={this.onChange}
90-
onKeyUp={this.handleKeyDown}
91-
/>
197+
<InputWrapper>
198+
<MonacoEditor
199+
width="100%"
200+
height={`calc(100% - ${CONSOLE_INPUT_PADDING}px)`}
201+
options={monacoOptions}
202+
theme="CodeSandbox"
203+
editorWillMount={defineTheme}
204+
editorDidMount={this.editorDidMount}
205+
openReference={noop}
206+
/>
207+
</InputWrapper>
92208
</Container>
93209
);
94210
}
95211
}
212+
213+
export default ConsoleInput;

packages/app/src/app/components/sandbox/Preview/DevTools/Console/index.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,11 @@ const Container = styled.div`
1717
height: 100%;
1818
display: flex;
1919
flex-direction: column;
20+
max-height: calc(100% - 2rem);
2021
`;
2122

2223
const Messages = styled.div`
23-
height: 100%;
24+
flex-grow: 1;
2425
overflow-y: auto;
2526
overflow-x: hidden;
2627
white-space: pre-wrap;

0 commit comments

Comments
 (0)