Skip to content

Commit 6d704d0

Browse files
authored
Integrated console (codesandbox#266)
* [WIP] Added console. * Fixed eslint in CodeMirror - again! * Fixed CSS. * Forked react-highlight and fixed it to depend on react 16. * Small CSS fix. * Fixed console scroll bar. * Style and move console to sandbox * Center input * Add console history * Create DevTools header * Styling adjustments * Warning adjustments * 0.0.13 * Update codesandbox-api * Fix up warning * Support circular json * Fix warnings * Fix undefined returns * Adjust devtool icons styling * Fix closing devtools on fast browsers * Allow devtools to take full space of iframe * Make console full on by default * Fix split resizing for iframe * Remove redundant type * Reset console between sandboxes * Change ShareView settings * Fix console going too wide * Allow default expand on embeds * Render linebreaks * Allow local testing in LOCAL_SERVER scenarios * Allow clearing of console * Update snapshots * Disable tooltip if action is not there
1 parent aeac3e0 commit 6d704d0

File tree

30 files changed

+1549
-76
lines changed

30 files changed

+1549
-76
lines changed

generate-test-screenshots.sh

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
#!/bin/bash
2+
docker run -v $(pwd):/app/ --name test-container -t codesandbox/test yarn start:test && \
3+
id=$(docker inspect --format="{{.Id}}" test-container) && \
4+
docker exec $(id) yarn test:integrations

package.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -105,8 +105,9 @@
105105
"babel-standalone": "^6.25.0",
106106
"base64-loader": "^1.0.0",
107107
"browser-resolve": "CompuIves/node-browser-resolve",
108+
"circular-json": "^0.4.0",
108109
"codemirror": "^5.27.4",
109-
"codesandbox-api": "^0.0.12",
110+
"codesandbox-api": "^0.0.14",
110111
"color": "^0.11.4",
111112
"css-modules-loader-core": "^1.1.0",
112113
"cssnano": "^3.10.0",
@@ -115,6 +116,7 @@
115116
"eslint-config-react-app": "^1.0.5",
116117
"file-saver": "^1.3.3",
117118
"glamor": "^2.20.25",
119+
"gsap": "^1.20.3",
118120
"gulp": "^3.9.1",
119121
"gulp-filter": "^5.0.0",
120122
"gulp-postcss": "^6.4.0",
@@ -141,6 +143,7 @@
141143
"react-dom": "16",
142144
"react-error-overlay": "^1.0.10",
143145
"react-icons": "^2.2.7",
146+
"react-inspector": "^2.2.0",
144147
"react-instantsearch": "^4.0.6",
145148
"react-loadable": "^3.3.1",
146149
"react-media": "^1.6.1",

src/app/components/sandbox/CodeEditor/CodeMirror.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,8 @@ type Props = {
3838
const Container = styled.div`
3939
width: 100%;
4040
height: 100%;
41+
display: flex;
42+
flex-direction: column;
4143
`;
4244

4345
const fadeInAnimation = keyframes`
@@ -57,7 +59,7 @@ const CodeContainer = styled.div`
5759
position: relative;
5860
overflow: auto;
5961
width: 100%;
60-
height: calc(100% - ${props => (props.hideNavigation ? 3 : 6)}rem);
62+
flex: 1 1 auto;
6163
.CodeMirror {
6264
font-family: ${props =>
6365
fontFamilies(props.fontFamily, 'Source Code Pro', 'monospace')};

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

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,8 @@ const Container = styled.div`
5656
width: 100%;
5757
height: 100%;
5858
z-index: 30;
59+
display: flex;
60+
flex-direction: column;
5961
`;
6062

6163
/*
@@ -71,8 +73,8 @@ const fontFamilies = (...families) =>
7173
const CodeContainer = styled.div`
7274
position: relative;
7375
width: 100%;
74-
height: calc(100% - ${props => (props.hideNavigation ? 3 : 6)}rem);
7576
z-index: 30;
77+
flex: 1 1 auto;
7678
7779
.margin-view-overlays {
7880
background: ${theme.background2()};

src/app/components/sandbox/CodeEditor/monaco/MonacoReactComponent.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,8 @@ class MonacoEditor extends React.Component {
8787
const style = {
8888
width: fixedWidth,
8989
height: fixedHeight,
90+
overflow: 'hidden',
91+
position: 'absolute',
9092
};
9193

9294
return (
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
import React from 'react';
2+
import styled from 'styled-components';
3+
4+
import ChevronRight from 'react-icons/lib/md/chevron-right';
5+
import theme from 'common/theme';
6+
7+
import { IconContainer } from './styles';
8+
9+
const Container = styled.div`
10+
position: relative;
11+
height: 2rem;
12+
min-height: 2rem;
13+
width: 100%;
14+
background-color: ${props => props.theme.background.darken(0.3)};
15+
16+
display: flex;
17+
align-items: center;
18+
`;
19+
20+
const Input = styled.input`
21+
position: relative;
22+
height: 1.5rem;
23+
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;
30+
`;
31+
32+
type Props = {
33+
evaluateConsole: (command: string) => void,
34+
};
35+
36+
export default class ConsoleInput extends React.PureComponent<Props> {
37+
state = {
38+
command: '',
39+
commandHistory: [],
40+
commandCursor: -1,
41+
};
42+
43+
onChange = e => {
44+
if (e.target) {
45+
this.setState({ command: e.target.value });
46+
}
47+
};
48+
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+
}
79+
};
80+
81+
render() {
82+
return (
83+
<Container>
84+
<IconContainer style={{ color: theme.secondary() }}>
85+
<ChevronRight />
86+
</IconContainer>
87+
<Input
88+
value={this.state.command}
89+
onChange={this.onChange}
90+
onKeyUp={this.handleKeyDown}
91+
/>
92+
</Container>
93+
);
94+
}
95+
}
Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
// @flow
2+
import React from 'react';
3+
import styled, { css } from 'styled-components';
4+
import { ObjectInspector } from 'react-inspector';
5+
6+
import theme from 'common/theme';
7+
8+
import type { IMessage } from './';
9+
10+
import formatMessageString from './format-message';
11+
import MessageIcon from './MessageIcon';
12+
import { IconContainer } from './styles';
13+
14+
const inspectorTheme = {
15+
BASE_FONT_FAMILY: 'Menlo, monospace',
16+
BASE_FONT_SIZE: '14px',
17+
BASE_LINE_HEIGHT: '18px',
18+
19+
BASE_BACKGROUND_COLOR: theme.background(),
20+
BASE_COLOR: 'rgb(213, 213, 213)',
21+
22+
OBJECT_NAME_COLOR: theme.secondary(),
23+
OBJECT_VALUE_NULL_COLOR: 'rgb(127, 127, 127)',
24+
OBJECT_VALUE_UNDEFINED_COLOR: 'rgb(127, 127, 127)',
25+
OBJECT_VALUE_REGEXP_COLOR: '#fac863',
26+
OBJECT_VALUE_STRING_COLOR: '#fac863',
27+
OBJECT_VALUE_SYMBOL_COLOR: '#fac863',
28+
OBJECT_VALUE_NUMBER_COLOR: 'hsl(252, 100%, 75%)',
29+
OBJECT_VALUE_BOOLEAN_COLOR: 'hsl(252, 100%, 75%)',
30+
OBJECT_VALUE_FUNCTION_KEYWORD_COLOR: 'rgb(242, 85, 217)',
31+
32+
HTML_TAG_COLOR: 'rgb(93, 176, 215)',
33+
HTML_TAGNAME_COLOR: 'rgb(93, 176, 215)',
34+
HTML_TAGNAME_TEXT_TRANSFORM: 'lowercase',
35+
HTML_ATTRIBUTE_NAME_COLOR: 'rgb(155, 187, 220)',
36+
HTML_ATTRIBUTE_VALUE_COLOR: 'rgb(242, 151, 102)',
37+
HTML_COMMENT_COLOR: 'rgb(137, 137, 137)',
38+
HTML_DOCTYPE_COLOR: 'rgb(192, 192, 192)',
39+
40+
ARROW_COLOR: 'rgb(145, 145, 145)',
41+
ARROW_MARGIN_RIGHT: 3,
42+
ARROW_FONT_SIZE: 12,
43+
44+
TREENODE_FONT_FAMILY: 'Menlo, monospace',
45+
TREENODE_FONT_SIZE: '13px',
46+
TREENODE_LINE_HEIGHT: '16px',
47+
TREENODE_PADDING_LEFT: 12,
48+
49+
TABLE_BORDER_COLOR: 'rgb(85, 85, 85)',
50+
TABLE_TH_BACKGROUND_COLOR: 'rgb(44, 44, 44)',
51+
TABLE_TH_HOVER_COLOR: 'rgb(48, 48, 48)',
52+
TABLE_SORT_ICON_COLOR: 'black',
53+
TABLE_DATA_BACKGROUND_IMAGE:
54+
'linear-gradient(rgba(255, 255, 255, 0), rgba(255, 255, 255, 0) 50%, rgba(51, 139, 255, 0.0980392) 50%, rgba(51, 139, 255, 0.0980392))',
55+
TABLE_DATA_BACKGROUND_SIZE: '128px 32px',
56+
};
57+
58+
function getTypeStyles(type: 'log' | 'info' | 'warn' | 'error') {
59+
switch (type) {
60+
case 'warn':
61+
return css`
62+
background-color: #332a00;
63+
color: #f5d396;
64+
border: 1px solid #665500;
65+
`;
66+
case 'error':
67+
return css`
68+
background-color: #280000;
69+
color: #fe7f7f;
70+
border: 1px solid #5b0000;
71+
`;
72+
default:
73+
return '';
74+
}
75+
}
76+
77+
const Container = styled.div`
78+
display: flex;
79+
flex-direction: row;
80+
border-bottom: 1px solid rgba(0, 0, 0, 0.3);
81+
font-size: 13px;
82+
line-height: 16px;
83+
word-break: break-all;
84+
${props => getTypeStyles(props.logType)};
85+
`;
86+
87+
const InnerItem = styled.div`
88+
display: inline-block;
89+
padding: 0.4rem 0;
90+
padding-right: 1.5rem;
91+
vertical-align: top;
92+
`;
93+
94+
function formatMessage(message: IMessage) {
95+
const formattedResult = document.createElement('span');
96+
97+
formatMessageString(
98+
message.arguments[0],
99+
message.arguments.slice(1),
100+
formattedResult
101+
);
102+
103+
return formattedResult;
104+
}
105+
106+
function getMessage(message: IMessage) {
107+
if (message.type === 'return') {
108+
return (
109+
<InnerItem>
110+
<ObjectInspector theme={inspectorTheme} data={message.arguments[0]} />
111+
</InnerItem>
112+
);
113+
}
114+
115+
if (message.type === 'command') {
116+
return <InnerItem>{message.arguments[0]}</InnerItem>;
117+
}
118+
119+
if (
120+
message.arguments.length > 0 &&
121+
typeof message.arguments[0] === 'string'
122+
) {
123+
return (
124+
<InnerItem
125+
dangerouslySetInnerHTML={{
126+
__html: formatMessage(message).outerHTML.replace(
127+
/(?:\r\n|\r|\n)/g,
128+
'<br />'
129+
),
130+
}}
131+
/>
132+
);
133+
}
134+
135+
return message.arguments.map((m, i) => (
136+
// eslint-disable-next-line react/no-array-index-key
137+
<InnerItem key={i}>
138+
<ObjectInspector theme={inspectorTheme} data={m} />
139+
</InnerItem>
140+
));
141+
}
142+
143+
export default ({ message }: { message: IMessage }) => (
144+
<Container logType={message.logType}>
145+
<div>
146+
<IconContainer>
147+
<MessageIcon type={message.type} logType={message.logType} />
148+
</IconContainer>
149+
</div>
150+
<div>{getMessage(message)}</div>
151+
</Container>
152+
);
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import React from 'react';
2+
3+
import ChevronRight from 'react-icons/lib/md/chevron-right';
4+
import ChevronLeft from 'react-icons/lib/fa/angle-double-left';
5+
import WarningIcon from 'react-icons/lib/md/warning';
6+
import ErrorIcon from 'react-icons/lib/md/error';
7+
8+
export default ({ type, logType }: { type: string, logType: string }) => {
9+
if (type === 'command') {
10+
return <ChevronRight />;
11+
}
12+
13+
if (type === 'return') {
14+
return <ChevronLeft />;
15+
}
16+
17+
switch (logType) {
18+
case 'warning':
19+
case 'warn':
20+
return <WarningIcon />;
21+
case 'error':
22+
return <ErrorIcon />;
23+
default:
24+
return false;
25+
}
26+
};

0 commit comments

Comments
 (0)