Skip to content

Commit c3aa2d6

Browse files
authored
Add prettify button (codesandbox#356)
* Add prettify button * Fix linting errors * Add checks for model in Monaco * Fix file types
1 parent 6afd95a commit c3aa2d6

File tree

10 files changed

+314
-226
lines changed

10 files changed

+314
-226
lines changed

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

Lines changed: 26 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -224,6 +224,7 @@ export default class CodeEditor extends React.Component<Props, State> {
224224
return (
225225
nextProps.id !== this.props.id ||
226226
nextProps.errors !== this.props.errors ||
227+
nextProps.code !== this.getCode() ||
227228
this.props.preferences !== nextProps.preferences
228229
);
229230
}
@@ -295,21 +296,44 @@ export default class CodeEditor extends React.Component<Props, State> {
295296
const kind = title.match(/\.([^.]*)$/);
296297

297298
if (kind) {
298-
if (kind[1] === 'css') {
299+
if (kind[1] === 'css' || kind[1] === 'scss' || kind[1] === 'less') {
299300
await System.import(
300301
/* webpackChunkName: 'codemirror-css' */ 'codemirror/mode/css/css'
301302
);
303+
if (kind[1] === 'less') {
304+
return 'text/x-less';
305+
}
306+
if (kind[1] === 'scss') {
307+
return 'text/x-scss';
308+
}
302309
return 'css';
303310
} else if (kind[1] === 'html' || kind[1] === 'vue') {
304311
await System.import(
305312
/* webpackChunkName: 'codemirror-html' */ 'codemirror/mode/htmlmixed/htmlmixed'
306313
);
314+
315+
if (kind[1] === 'vue') {
316+
return 'text/x-vue';
317+
}
318+
307319
return 'htmlmixed';
308320
} else if (kind[1] === 'md') {
309321
await System.import(
310322
/* webpackChunkName: 'codemirror-markdown' */ 'codemirror/mode/markdown/markdown'
311323
);
312324
return 'markdown';
325+
} else if (kind[1] === 'json') {
326+
return 'application/json';
327+
} else if (kind[1] === 'sass') {
328+
await System.import(
329+
/* webpackChunkName: 'codemirror-sass' */ 'codemirror/mode/sass/sass'
330+
);
331+
return 'sass';
332+
} else if (kind[1] === 'styl') {
333+
await System.import(
334+
/* webpackChunkName: 'codemirror-sass' */ 'codemirror/mode/stylus/stylus'
335+
);
336+
return 'stylus';
313337
}
314338
}
315339

@@ -462,34 +486,8 @@ export default class CodeEditor extends React.Component<Props, State> {
462486

463487
getCode = () => this.codemirror.getValue();
464488

465-
prettify = async () => {
466-
const { id, title, preferences } = this.props;
467-
const code = this.getCode();
468-
const mode = await this.getMode(title);
469-
if (mode === 'jsx' || mode === 'typescript' || mode === 'css') {
470-
try {
471-
const prettify = await import(/* webpackChunkName: 'prettier' */ 'app/utils/codemirror/prettify');
472-
const newCode = await prettify.default(
473-
code,
474-
mode,
475-
preferences.prettierConfig
476-
);
477-
478-
if (newCode && newCode !== code) {
479-
this.props.changeCode(id, newCode);
480-
this.updateCodeMirrorCode(newCode);
481-
}
482-
} catch (e) {
483-
console.error(e);
484-
}
485-
}
486-
};
487-
488489
handleSaveCode = async () => {
489-
const { saveCode, preferences } = this.props;
490-
if (preferences.prettifyOnSaveEnabled) {
491-
await this.prettify();
492-
}
490+
const { saveCode } = this.props;
493491
const { id } = this.props;
494492
this.props.changeCode(id, this.getCode());
495493
saveCode();

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

Lines changed: 14 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -407,6 +407,14 @@ export default class CodeEditor extends React.Component<Props, State> {
407407
this.fetchDependencyTypings(nextDependencies);
408408
}
409409

410+
if (
411+
this.editor &&
412+
this.getCode() !== nextProps.code &&
413+
this.props.id === nextProps.id
414+
) {
415+
this.updateCode(nextProps.code);
416+
}
417+
410418
return (
411419
nextProps.sandboxId !== this.props.sandboxId ||
412420
nextProps.id !== this.props.id ||
@@ -491,6 +499,10 @@ export default class CodeEditor extends React.Component<Props, State> {
491499
}
492500

493501
updateCode(code: string = '') {
502+
if (!this.editor || !this.editor.getModel()) {
503+
return;
504+
}
505+
494506
const pos = this.editor.getPosition();
495507
const lines = this.editor.getModel().getLinesContent();
496508
const lastLine = lines.length;
@@ -864,40 +876,9 @@ export default class CodeEditor extends React.Component<Props, State> {
864876

865877
getCode = () => this.editor.getValue();
866878

867-
prettify = async () => {
868-
const { id, title, preferences } = this.props;
869-
const code = this.getCode();
870-
const mode = await this.getMode(title);
871-
872-
if (
873-
mode === 'javascript' ||
874-
mode === 'typescript' ||
875-
mode === 'json' ||
876-
mode === 'css'
877-
) {
878-
try {
879-
const prettify = await import(/* webpackChunkName: 'prettier' */ 'app/utils/codemirror/prettify');
880-
const newCode = await prettify.default(
881-
code,
882-
mode === 'javascript' ? 'jsx' : mode,
883-
preferences.prettierConfig
884-
);
885-
886-
if (newCode && newCode !== code) {
887-
this.props.changeCode(id, newCode);
888-
this.updateCode(newCode);
889-
}
890-
} catch (e) {
891-
console.error(e);
892-
}
893-
}
894-
};
895-
896879
handleSaveCode = async () => {
897-
const { saveCode, preferences } = this.props;
898-
if (preferences.prettifyOnSaveEnabled) {
899-
await this.prettify();
900-
}
880+
const { saveCode } = this.props;
881+
901882
const { id } = this.props;
902883
this.props.changeCode(id, this.getCode());
903884
saveCode();

packages/app/src/app/components/sandbox/CodeEditor/Tabs/index.js

Lines changed: 109 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,12 @@
11
// @flow
22
import React from 'react';
3-
import styled from 'styled-components';
3+
import styled, { css } from 'styled-components';
4+
import PrettierIcon from 'react-icons/lib/md/brush';
45

56
import type { Module, Directory } from 'common/types';
7+
import Tooltip from 'common/components/Tooltip';
8+
9+
import { canPrettify } from 'app/store/entities/sandboxes/modules/utils/prettify';
610

711
import Tab from './TabContainer';
812

@@ -13,6 +17,12 @@ const Container = styled.div`
1317
color: rgba(255, 255, 255, 0.8);
1418
1519
background-color: rgba(0, 0, 0, 0.3);
20+
`;
21+
22+
const TabsContainer = styled.div`
23+
display: flex;
24+
height: 2.5rem;
25+
flex: 1 0 2.5rem;
1626
1727
overflow-x: auto;
1828
overflow-y: hidden;
@@ -24,6 +34,34 @@ const Container = styled.div`
2434
}
2535
`;
2636

37+
const StyledPrettierIcon = styled(PrettierIcon)`
38+
transition: 0.3s ease opacity;
39+
width: 1.125rem;
40+
height: 1.125rem;
41+
cursor: pointer;
42+
43+
opacity: 0.6;
44+
45+
&:hover {
46+
opacity: 1;
47+
}
48+
49+
${props =>
50+
props.disabled &&
51+
css`
52+
opacity: 0;
53+
pointer-events: none;
54+
`};
55+
`;
56+
57+
const IconContainer = styled.div`
58+
display: flex;
59+
align-items: center;
60+
float: right;
61+
flex-shrink: 1;
62+
padding: 0 0.75rem;
63+
`;
64+
2765
type Props = {
2866
setCurrentModule: (sandboxId: string, moduleId: string) => void,
2967
closeTab: (sandboxId: string, moduleId: string) => void,
@@ -34,6 +72,7 @@ type Props = {
3472
directories: Array<Directory>,
3573
currentModuleId: string,
3674
sandboxId: string,
75+
prettifyModule: (id: string) => void,
3776
};
3877

3978
export default class EditorTabs extends React.PureComponent<Props> {
@@ -93,6 +132,18 @@ export default class EditorTabs extends React.PureComponent<Props> {
93132
this.props.setCurrentModule(this.props.sandboxId, moduleId);
94133
};
95134

135+
prettifyModule = () => {
136+
this.props.prettifyModule(this.props.currentModuleId);
137+
};
138+
139+
canPrettify = (module: ?Module) => {
140+
if (!module) {
141+
return false;
142+
}
143+
144+
return canPrettify(module.title);
145+
};
146+
96147
container: HTMLElement;
97148
tabEls = {};
98149
tabEls: {
@@ -117,50 +168,65 @@ export default class EditorTabs extends React.PureComponent<Props> {
117168
tabNamesObject[module.title].push(module.id);
118169
});
119170

120-
return (
121-
<Container
122-
innerRef={el => {
123-
this.container = el;
124-
}}
125-
>
126-
{tabs
127-
.map(tab => ({ ...tab, module: moduleObject[tab.moduleId] }))
128-
.map((tab, i) => {
129-
const { module } = tab;
130-
const modulesWithName = tabNamesObject[module.title];
131-
let dirName = null;
132-
133-
if (modulesWithName.length > 1 && module.directoryShortid != null) {
134-
const dir = directories.find(
135-
d =>
136-
d.shortid === module.directoryShortid &&
137-
d.sourceId === module.sourceId
138-
);
171+
const currentModule = moduleObject[currentModuleId];
139172

140-
if (dir) {
141-
dirName = dir.title;
173+
return (
174+
<Container>
175+
<TabsContainer
176+
innerRef={el => {
177+
this.container = el;
178+
}}
179+
>
180+
{tabs
181+
.map(tab => ({ ...tab, module: moduleObject[tab.moduleId] }))
182+
.map((tab, i) => {
183+
const { module } = tab;
184+
const modulesWithName = tabNamesObject[module.title];
185+
let dirName = null;
186+
187+
if (
188+
modulesWithName.length > 1 &&
189+
module.directoryShortid != null
190+
) {
191+
const dir = directories.find(
192+
d =>
193+
d.shortid === module.directoryShortid &&
194+
d.sourceId === module.sourceId
195+
);
196+
197+
if (dir) {
198+
dirName = dir.title;
199+
}
142200
}
143-
}
144-
145-
return (
146-
<Tab
147-
setCurrentModule={this.setCurrentModule}
148-
active={currentModuleId === tab.module.id}
149-
key={tab.module.id}
150-
module={tab.module}
151-
closeTab={this.closeTab}
152-
moveTab={this.moveTab}
153-
markNotDirty={this.markNotDirty}
154-
dirName={dirName}
155-
tabCount={tabs.length}
156-
position={i}
157-
dirty={tab.dirty}
158-
innerRef={el => {
159-
this.tabEls[tab.module.id] = el;
160-
}}
161-
/>
162-
);
163-
})}
201+
202+
return (
203+
<Tab
204+
setCurrentModule={this.setCurrentModule}
205+
active={currentModuleId === tab.module.id}
206+
key={tab.module.id}
207+
module={tab.module}
208+
closeTab={this.closeTab}
209+
moveTab={this.moveTab}
210+
markNotDirty={this.markNotDirty}
211+
dirName={dirName}
212+
tabCount={tabs.length}
213+
position={i}
214+
dirty={tab.dirty}
215+
innerRef={el => {
216+
this.tabEls[tab.module.id] = el;
217+
}}
218+
/>
219+
);
220+
})}
221+
</TabsContainer>
222+
<IconContainer>
223+
<Tooltip title="Prettify">
224+
<StyledPrettierIcon
225+
disabled={!this.canPrettify(currentModule)}
226+
onClick={this.prettifyModule}
227+
/>
228+
</Tooltip>
229+
</IconContainer>
164230
</Container>
165231
);
166232
}

packages/app/src/app/pages/Sandbox/Editor/Content/index.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -172,6 +172,7 @@ class EditorPreview extends React.PureComponent<Props, State> {
172172
closeTab={sandboxActions.closeTab}
173173
moveTab={sandboxActions.moveTab}
174174
markNotDirty={sandboxActions.markTabsNotDirty}
175+
prettifyModule={moduleActions.prettifyModule}
175176
/>
176177
<CodeEditor
177178
changeCode={moduleActions.setCode}

0 commit comments

Comments
 (0)