├── .dockerignore
├── .gitignore
├── .prettierrc
├── Dockerfile
├── LICENSE
├── README.md
├── package.json
├── public
├── CNAME
├── favicon.ico
├── index.html
└── manifest.json
├── src
├── App.js
├── App.module.css
├── CodeMirror.js
├── CodeMirrorPanel.js
├── CodeMirrorPanel.module.css
├── Header.js
├── Header.module.css
├── OptionsSection.js
├── OptionsSection.module.css
├── Repl.js
├── Repl.module.css
├── Repl.test.js
├── ReplOptions.js
├── ReplOptions.module.css
├── Svg.js
├── Svg.module.css
├── index.css
├── index.js
├── lib
│ ├── helpers.js
│ └── terser-options.js
├── paraiso-dark.css
├── paraiso-light.css
└── serviceWorker.js
└── yarn.lock
/.dockerignore:
--------------------------------------------------------------------------------
1 | node_modules/
2 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 |
8 | # testing
9 | /coverage
10 |
11 | # production
12 | /build
13 |
14 | # misc
15 | .DS_Store
16 | .env.local
17 | .env.development.local
18 | .env.test.local
19 | .env.production.local
20 |
21 | npm-debug.log*
22 | yarn-debug.log*
23 | yarn-error.log*
24 |
--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "tabWidth": 2,
3 | "semi": true,
4 | "singleQuote": true
5 | }
6 |
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM node:16
2 |
3 | WORKDIR /app
4 |
5 | EXPOSE 3000 35729
6 |
7 | COPY package.json /app
8 | COPY yarn.lock /app
9 |
10 | RUN yarn install
11 |
12 | COPY . /app
13 |
14 | CMD ["yarn", "start"]
15 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright 2019 Rogério Vicente
2 |
3 | Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies.
4 |
5 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
6 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # repl
2 |
3 | ## About
4 |
5 | This is the repo for terser's online repl available at [try.terser.org](https://try.terser.org).
6 |
7 | ### Screenshot
8 |
9 | 
10 |
11 | ## Development
12 |
13 | In the project directory, you can run:
14 |
15 | ### Start
16 | `yarn start`
17 |
18 | Runs the app in the development mode.
19 | Open [http://localhost:3000](http://localhost:3000) to view it in the browser.
20 |
21 | ### Test
22 | `yarn test`
23 |
24 | Runs tests with Jest.
25 |
26 | ### Build
27 | `npm run build`
28 |
29 | Builds the app for production to the `build` folder.
30 |
31 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "try.terser.org",
3 | "version": "0.1.0",
4 | "private": true,
5 | "dependencies": {
6 | "classnames": "^2.3.1",
7 | "codemirror": "^5.65.2",
8 | "lodash-es": "^4.17.21",
9 | "react": "^17.0.2",
10 | "react-dom": "^17.0.2",
11 | "react-scripts": "5.0.0"
12 | },
13 | "scripts": {
14 | "start": "react-scripts start",
15 | "build": "react-scripts build",
16 | "test": "react-scripts test",
17 | "eject": "react-scripts eject",
18 | "predeploy": "yarn build",
19 | "deploy": "gh-pages -d build"
20 | },
21 | "eslintConfig": {
22 | "extends": "react-app"
23 | },
24 | "browserslist": {
25 | "production": [
26 | ">0.2%",
27 | "not dead",
28 | "not op_mini all"
29 | ],
30 | "development": [
31 | "last 1 chrome version",
32 | "last 1 firefox version",
33 | "last 1 safari version"
34 | ]
35 | },
36 | "devDependencies": {
37 | "gh-pages": "^2.1.1"
38 | },
39 | "homepage": "https://try.terser.org"
40 | }
41 |
--------------------------------------------------------------------------------
/public/CNAME:
--------------------------------------------------------------------------------
1 | try.terser.org
2 |
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/terser/repl/0d37a136073d1bae81dbb79443d61caf2301eeeb/public/favicon.ico
--------------------------------------------------------------------------------
/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
15 |
16 |
25 | Terser REPL
26 |
27 |
28 |
29 |
30 |
40 |
41 |
42 |
--------------------------------------------------------------------------------
/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "short_name": "Terser REPL",
3 | "name": "Terser Online REPL",
4 | "icons": [
5 | {
6 | "src": "favicon.ico",
7 | "sizes": "64x64 32x32 24x24 16x16",
8 | "type": "image/x-icon"
9 | }
10 | ],
11 | "start_url": ".",
12 | "display": "standalone",
13 | "theme_color": "#000000",
14 | "background_color": "#ffffff"
15 | }
16 |
--------------------------------------------------------------------------------
/src/App.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | import Repl from './Repl'
4 | import Header from './Header';
5 |
6 | import styles from './App.module.css'
7 |
8 | export default class App extends React.Component {
9 | state = {
10 | error: null,
11 | }
12 | componentDidMount() {
13 | if (!window.Terser) {
14 | this.setState({
15 | error: new Error('Could not load Terser from jsdelivr')
16 | })
17 | }
18 | }
19 | render() {
20 | const { error } = this.state
21 |
22 | const body = (() => {
23 | if (error) {
24 | return (
25 |
26 | {error?.message || 'An error has occurred'}
27 |
28 | )
29 | } else {
30 | return
31 | }
32 | })()
33 |
34 | return (
35 |
36 |
37 | {body}
38 |
39 | )
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/src/App.module.css:
--------------------------------------------------------------------------------
1 | .message {
2 | display: flex;
3 | justify-content: center;
4 | align-items: center;
5 | flex: 1 0 auto;
6 | }
7 |
8 | .error {
9 | color: lightred;
10 | font-weight: bold;
11 | }
12 |
13 | .container {
14 | min-height: 100vh;
15 | display: flex;
16 | flex-direction: column;
17 | }
18 |
--------------------------------------------------------------------------------
/src/CodeMirror.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import PropTypes from 'prop-types';
3 | import CodeMirror from 'codemirror';
4 |
5 | require('codemirror/lib/codemirror.css');
6 | require('./paraiso-dark.css');
7 | require('./paraiso-light.css');
8 | require('codemirror/mode/xml/xml');
9 | require('codemirror/mode/javascript/javascript');
10 | require('codemirror/keymap/sublime');
11 |
12 | const DEFAULT_CODE_MIRROR_OPTIONS = {
13 | autoCloseBrackets: true,
14 | keyMap: 'sublime',
15 | lineNumbers: true,
16 | matchBrackets: true,
17 | mode: 'javascript',
18 | showCursorWhenSelecting: true,
19 | styleActiveLine: true,
20 | tabWidth: 2
21 | };
22 |
23 | class CodeMirrorReact extends Component {
24 | static propTypes = {
25 | autoFocus: PropTypes.bool,
26 | preserveScrollPosition: PropTypes.bool,
27 | options: PropTypes.object,
28 | placeholder: PropTypes.string,
29 | value: PropTypes.string,
30 | theme: PropTypes.string
31 | };
32 |
33 | static defaultProps = {
34 | autoFocus: false,
35 | preserveScrollPosition: false,
36 | onChange: null,
37 | options: {},
38 | placeholder: null,
39 | value: null,
40 | theme: 'default'
41 | };
42 |
43 | state = {
44 | isFocused: false
45 | };
46 |
47 | _codeMirror = null;
48 | _textAreaRef = null;
49 |
50 | componentDidMount() {
51 | this._codeMirror = CodeMirror.fromTextArea(this._textAreaRef, {
52 | ...DEFAULT_CODE_MIRROR_OPTIONS,
53 | ...this.props.options
54 | });
55 |
56 | this._codeMirror.setOption('theme', this.props.theme)
57 | this._codeMirror.on('change', this._onChange);
58 | this._codeMirror.setValue(this.props.value || '');
59 | }
60 |
61 | // TODO: refactor this with the new lyfecycle methods
62 | UNSAFE_componentWillMount() {
63 | if (this._codeMirror) {
64 | this._codeMirror.toTextArea();
65 | }
66 | }
67 |
68 | // TODO: refactor this with the new lyfecycle methods
69 | UNSAFE_componentWillReceiveProps(nextProps) {
70 | if (
71 | nextProps.value &&
72 | nextProps.value !== this.props.value &&
73 | this._codeMirror.getValue() !== nextProps.value
74 | ) {
75 | if (nextProps.preserveScrollPosition) {
76 | const prevScrollPosition = this._codeMirror.getScrollInfo();
77 |
78 | this._codeMirror.setValue(nextProps.value);
79 | this._codeMirror.scrollTo(
80 | prevScrollPosition.left,
81 | prevScrollPosition.top
82 | );
83 | } else {
84 | this._codeMirror.setValue(nextProps.value);
85 | }
86 | } else if (!nextProps.value) {
87 | this._codeMirror.setValue('');
88 | }
89 |
90 | for (const optionName in nextProps.options) {
91 | if (nextProps.options.hasOwnProperty(optionName)) {
92 | this._updateOption(optionName, nextProps.options[optionName]);
93 | }
94 | }
95 | }
96 |
97 | focus() {
98 | this._codeMirror && this._codeMirror.focus();
99 | }
100 |
101 | render() {
102 | return (
103 |
110 | );
111 | }
112 |
113 | _updateOption(optionName, newValue) {
114 | const oldValue = this._codeMirror.getOption(optionName);
115 |
116 | if (oldValue !== newValue) {
117 | this._codeMirror.setOption(optionName, newValue);
118 | }
119 | }
120 |
121 | _onChange = (doc, change) => {
122 | if (change.origin !== 'setValue') {
123 | this.props.onChange(doc.getValue());
124 | }
125 | };
126 |
127 | _setTextAreaRef = (ref = null) => {
128 | this._textAreaRef = ref;
129 | };
130 | }
131 |
132 | export default CodeMirrorReact;
133 |
--------------------------------------------------------------------------------
/src/CodeMirrorPanel.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import cx from 'classnames';
4 |
5 | import CodeMirror from './CodeMirror';
6 |
7 | import styles from './CodeMirrorPanel.module.css';
8 |
9 | const CodeMirrorPanel = props => {
10 | const {
11 | className,
12 | onChange,
13 | options,
14 | placeholder,
15 | code,
16 | fileSize,
17 | info,
18 | errorMessage,
19 | theme
20 | } = props;
21 |
22 | const containerClass = cx(styles.container, className);
23 | const infoClass = cx(styles.info, styles.sharedBox);
24 | const errorClass = cx(styles.error, styles.sharedBox);
25 |
26 | return (
27 |
28 |
29 |
40 | {options.fileSize &&
{fileSize} bytes
}
41 |
42 | {info &&
{info}
}
43 | {errorMessage &&
{errorMessage}
}
44 |
45 | );
46 | };
47 |
48 | CodeMirrorPanel.propTypes = {
49 | className: PropTypes.string,
50 | onChange: PropTypes.func,
51 | options: PropTypes.object,
52 | placeholder: PropTypes.string,
53 | code: PropTypes.string,
54 | fileSize: PropTypes.number
55 | };
56 |
57 | CodeMirrorPanel.defaultProps = {
58 | className: null,
59 | onChange: null,
60 | options: {},
61 | placeholder: null,
62 | code: null,
63 | fileSize: null
64 | };
65 |
66 | export default CodeMirrorPanel;
67 |
--------------------------------------------------------------------------------
/src/CodeMirrorPanel.module.css:
--------------------------------------------------------------------------------
1 | .container {
2 | height: 100%;
3 | display: flex;
4 | flex-direction: column;
5 | justify-content: stretch;
6 | overflow: auto;
7 | }
8 |
9 | .sharedBox {
10 | flex: 0 0 auto;
11 | max-height: 33%;
12 | overflow: auto;
13 | margin: 0;
14 | padding: 0.5rem 0.75rem;
15 | font-family: monospace;
16 | white-space: pre-wrap;
17 | -webkit-overflow-scrolling: touch;
18 | }
19 |
20 | .codeMirror {
21 | display: block;
22 | height: 100%;
23 | width: 100%;
24 | overflow: auto;
25 | position: relative;
26 | }
27 |
28 | .info {
29 | order: 1;
30 | background-color: #fffbe5;
31 | border-top: 1px solid #fff5c2;
32 | color: #5c3c00;
33 | }
34 |
35 | .error {
36 | order: 2;
37 | background-color: #fff0f0;
38 | border-top: 1px solid #ffd6d6;
39 | color: #ff0000;
40 |
41 | }
42 |
43 | .fileSize {
44 | position: absolute;
45 | bottom: 1rem;
46 | right: 2rem;
47 | z-index: 2;
48 | border-radius: 0.5rem;
49 | padding: 0.5rem;
50 | background-color: rgba(225, 225, 225, 0.75);
51 | color: rgba(0, 0, 0, 0.5);
52 | border: 0;
53 | }
54 |
--------------------------------------------------------------------------------
/src/Header.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | import styles from './Header.module.css';
4 |
5 | const Header = () => {
6 | return (
7 |
20 | );
21 | };
22 |
23 | export default Header;
24 |
--------------------------------------------------------------------------------
/src/Header.module.css:
--------------------------------------------------------------------------------
1 | .container {
2 | background-color: #20232a;
3 | color: #fff;
4 | /* min-height: 50px; */
5 | padding: 8px 0;
6 | width: 100%;
7 | transform: translateZ(0);
8 | }
9 |
10 | .container header {
11 | display: -webkit-box;
12 | display: flex;
13 | -webkit-box-orient: horizontal;
14 | flex-flow: row nowrap;
15 | align-items: center;
16 | position: relative;
17 | text-align: left;
18 | margin-left: 1.20%;
19 | user-select: none;
20 | }
21 |
22 | .container header img {
23 | border-style: none;
24 | box-sizing: content-box;
25 | max-width: 100%;
26 | height: 100%;
27 | margin-right: 10px;
28 | }
29 |
30 | .container header h1 {
31 | display: block;
32 | font-size: 1.25rem;
33 | margin: 0;
34 | position: relative;
35 | }
36 |
37 | .container header p {
38 | display: block;
39 | font-size: 1rem;
40 | margin: 0;
41 | margin-left: 1rem;
42 | position: relative;
43 | /* Vertically align with the title */
44 | padding-top: 0.2em;
45 | }
46 |
47 | .container a {
48 | -webkit-box-align: center;
49 | align-items: center;
50 | border: 0;
51 | color: #fff;
52 | display: -webkit-box;
53 | display: flex;
54 | -webkit-box-orient: horizontal;
55 | -webkit-box-direction: normal;
56 | flex-flow: row nowrap;
57 | height: 34px;
58 | text-decoration: none;
59 | }
60 |
--------------------------------------------------------------------------------
/src/OptionsSection.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import cx from 'classnames';
4 |
5 | import styles from './OptionsSection.module.css';
6 |
7 | const OptionsSection = (props) => {
8 | const containerClass = cx(styles.container, props.className);
9 |
10 | return (
11 |
12 |
15 |
{props.children}
16 |
17 | );
18 | };
19 |
20 | OptionsSection.propTypes = {
21 | className: PropTypes.string,
22 | label: PropTypes.string,
23 | children: PropTypes.object
24 | };
25 |
26 | OptionsSection.defaultProps = {
27 | className: '',
28 | label: '',
29 | children: null
30 | };
31 |
32 | export default OptionsSection;
33 |
--------------------------------------------------------------------------------
/src/OptionsSection.module.css:
--------------------------------------------------------------------------------
1 | .container {
2 | flex: 1 0 1.5rem;
3 | cursor: default;
4 | border-bottom: 1px solid #181a1f;
5 | user-select: none;
6 | }
7 |
8 | .headerRow {
9 | display: flex;
10 | flex-direction: row;
11 | align-items: center;
12 | padding: 0.625rem 0.9375rem;
13 | transition: background-color 250ms ease-in-out, color 250ms ease-in-out;
14 | color: #9da5b4;
15 | cursor: pointer;
16 | }
17 |
18 | .headerRow:hover {
19 | background-color: #292d36;
20 | color: #fff;
21 | }
22 |
23 | .label {
24 | flex: 1;
25 | font-size: 0.75rem;
26 | font-weight: 700;
27 | text-transform: uppercase;
28 | }
29 |
30 | .content {
31 | display: flex;
32 | flex-direction: column;
33 | padding: 0rem 0.5rem 0.5rem;
34 | }
35 |
36 | @media(min-width: 601px) and (max-width: 1000px) {
37 | .container {
38 | border-right: 1px solid #181a1f;
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/src/Repl.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import { debounce, cloneDeep } from 'lodash-es';
3 | import cx from 'classnames';
4 |
5 | import CodeMirrorPanel from './CodeMirrorPanel';
6 | import { getCodeSizeInBytes } from './lib/helpers';
7 | import terserOptions, { evalOptions } from './lib/terser-options';
8 |
9 | import styles from './Repl.module.css';
10 |
11 | const DEBOUNCE_DELAY = 500;
12 |
13 | class Repl extends Component {
14 | state = {
15 | optionsCode: terserOptions,
16 | code: '// write or paste code here\n\n',
17 | minified: "// terser's output will be shown here",
18 | terserOptions: evalOptions(),
19 | rawSize: 0,
20 | minifiedSize: 0
21 | };
22 |
23 | options = {
24 | lineWrapping: true,
25 | fileSize: true
26 | };
27 |
28 | render() {
29 | return (
30 |
31 |
40 |
50 |
58 |
59 | );
60 | }
61 |
62 | _updateCode = code => {
63 | this.setState({
64 | code,
65 | rawSize: getCodeSizeInBytes(code)
66 | });
67 | this._minifyToState(code);
68 | };
69 |
70 | _updateTerserOptions = options => {
71 | try {
72 | const parsedOptions = evalOptions(options);
73 |
74 | this.setState({
75 | terserOptions: parsedOptions,
76 | optionsErrorMessage: null
77 | });
78 | } catch (e) {
79 | this.setState({ optionsErrorMessage: e.message });
80 | }
81 |
82 | this._minify(this.state.code);
83 | };
84 |
85 | _minifyToState = debounce(
86 | code => this._minify(code, this._persistState),
87 | DEBOUNCE_DELAY
88 | );
89 |
90 | _minify = async (code, setStateCallback) => {
91 | // we need to clone this because terser mutates the options object :(
92 | const terserOpts = cloneDeep(this.state.terserOptions);
93 |
94 | // TODO: put this in a worker to avoid blocking the UI on heavy content
95 | try {
96 | const result = await this.props.terser.minify(code, terserOpts);
97 |
98 | if (result.error) {
99 | this.setState({ errorMessage: result.error.message });
100 | } else {
101 | this.setState({
102 | minified: result.code,
103 | minifiedSize: getCodeSizeInBytes(result.code),
104 | errorMessage: null
105 | });
106 | }
107 | } catch (e) {
108 | this.setState({ errorMessage: e.message });
109 | }
110 | };
111 | }
112 |
113 | export default Repl;
114 |
--------------------------------------------------------------------------------
/src/Repl.module.css:
--------------------------------------------------------------------------------
1 | .container {
2 | height: 100vh;
3 | height: calc(100vh - 50px); /* 50px is the header's height */
4 | width: 100%;
5 |
6 | font-size: 0.875rem;
7 | line-height: 1.25rem;
8 | }
9 |
10 | .panel {
11 | height: calc((100vh - 50px - 1px) / 3); /* 50px is the header's height; 1px border; */
12 | border-bottom: 1 solid transparent;
13 | }
14 |
15 | .panelOptions {
16 | grid-area: panelOptions;
17 | border-bottom: 1.5px solid #ccc;
18 | }
19 |
20 | .panelInput {
21 | grid-area: panelInput;
22 | }
23 |
24 | .panelOutput {
25 | grid-area: panelOutput;
26 | }
27 |
28 | @supports (display: grid) {
29 | @media (min-width: 520px) {
30 | .container {
31 | display: grid;
32 | grid-template-areas:
33 | "panelOptions panelOutput"
34 | "panelInput panelOutput";
35 | grid-template-columns: 1fr 1fr;
36 | grid-template-rows: 270px 1fr;
37 | }
38 |
39 | .panel {
40 | height: unset;
41 | }
42 | }
43 | }
--------------------------------------------------------------------------------
/src/Repl.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import Repl from './Repl';
4 |
5 | xit('renders without crashing', () => {
6 | const div = document.createElement('div');
7 | ReactDOM.render(, div);
8 | ReactDOM.unmountComponentAtNode(div);
9 | });
10 |
--------------------------------------------------------------------------------
/src/ReplOptions.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | import OptionsSection from './OptionsSection';
4 |
5 | import styles from './ReplOptions.module.css';
6 |
7 | const ReplOptions = (props) => {
8 | return (
9 |
44 | );
45 | };
46 |
47 | export default ReplOptions;
48 |
--------------------------------------------------------------------------------
/src/ReplOptions.module.css:
--------------------------------------------------------------------------------
1 | .container {
2 | flex: 0 0 auto;
3 | position: relative;
4 | overflow: visible;
5 | z-index: 6;
6 | background-color: #21252b;
7 | color: #9da5b4;
8 | transition: transform 0.25s ease-in-out;
9 | overflow: auto;
10 | box-shadow: rgba(0, 0, 0, 0.12) 0px 1px 6px, rgba(0, 0, 0, 0.24) 0px 1px 4px;
11 | }
12 |
13 | .expandedContainer {
14 | overflow: auto;
15 | box-shadow: rgba(0, 0, 0, 0.12) 0px 1px 6px, rgba(0, 0, 0, 0.24) 0px 1px 4px;
16 | }
17 |
18 | .sectionsWrapper {
19 | flex: 1 1 auto;
20 | display: flex;
21 | flex-direction: column;
22 | width: 100%;
23 | flex-wrap: wrap;
24 | justify-content: space-between;
25 | min-height: 100%;
26 | }
27 |
28 | .section {
29 | position: relative;
30 | z-index: 7;
31 | display: flex;
32 | flex-direction: column;
33 | overflow: auto;
34 | flex: 0 0 auto;
35 | max-height: 100%;
36 | }
37 |
38 | .label {
39 | align-items: center;
40 | display: flex;
41 | flex: 0 0 1.5em;
42 | flex-direction: row;
43 | font-size: 0.875rem;
44 | font-weight: normal;
45 | margin: 0 -0.5rem;
46 | padding: 0 1rem;
47 | transition: background-color 250ms ease-in-out, color 250ms ease-in-out;
48 | }
49 |
50 | .label:hover {
51 | background-color: #21252b;
52 | color: #fff;
53 | }
54 |
55 | .checkbox {
56 | margin: 0 0.75rem 0 0 !important;
57 | }
58 |
59 | .checkbox:disabled {
60 | opacity: 0.5;
61 | }
62 |
63 | @media(max-width: 600px) {
64 | .sectionsWrapper {
65 | max-height: 300px;
66 | display: block;
67 | overflow: auto;
68 | -webkit-overflow-scrolling: touch;
69 | }
70 | }
71 |
72 | @media(max-width: 1000px) {
73 | .section {
74 | flex: 1 0 100px;
75 | max-height: 100%;
76 | overflow: auto;
77 | }
78 | }
79 |
80 | @media(min-width: 1001px) {
81 | .container {
82 | height: 100%; /* Safari fix for scrolling/overflow */
83 | }
84 |
85 | .expandedContainer {
86 | height: calc(100% - 38px); /* 38px is babel-version tab's height */
87 | width: 18rem;
88 | }
89 | }
90 |
--------------------------------------------------------------------------------
/src/Svg.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import cx from 'classnames';
4 |
5 | import styles from './Svg.module.css';
6 |
7 | const Svg = ({ className, path, ...rest }) => {
8 | const svgClass = cx(styles.container, className);
9 |
10 | return (
11 |
14 | )
15 | };
16 |
17 | Svg.propTypes = {
18 | className: PropTypes.string,
19 | path: PropTypes.string
20 | };
21 |
22 | Svg.defaultProps = {
23 | className: '',
24 | path: ''
25 | };
26 |
27 | export default Svg;
28 |
--------------------------------------------------------------------------------
/src/Svg.module.css:
--------------------------------------------------------------------------------
1 | .container {
2 | height: 1rem;
3 | width: 1rem;
4 | }
5 |
6 | .path {
7 | fill: currentColor;
8 | }
9 |
--------------------------------------------------------------------------------
/src/index.css:
--------------------------------------------------------------------------------
1 | body {
2 | margin: 0;
3 | font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen",
4 | "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue",
5 | sans-serif;
6 | -webkit-font-smoothing: antialiased;
7 | -moz-osx-font-smoothing: grayscale;
8 | }
9 |
10 | code {
11 | font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New",
12 | monospace;
13 | }
14 |
15 | .CodeMirror {
16 | height: 100% !important;
17 | width: 100% !important;
18 | -webkit-overflow-scrolling: touch;
19 | }
20 |
21 | .CodeMirror-lines pre.CodeMirror-placeholder {
22 | color: #aaa;
23 | }
24 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import App from './App';
4 | import * as serviceWorker from './serviceWorker';
5 |
6 | import './index.css';
7 |
8 | ReactDOM.render(, document.getElementById('root'));
9 |
10 | // If you want your app to work offline and load faster, you can change
11 | // unregister() to register() below. Note this comes with some pitfalls.
12 | // Learn more about service workers: https://bit.ly/CRA-PWA
13 | serviceWorker.unregister();
14 |
--------------------------------------------------------------------------------
/src/lib/helpers.js:
--------------------------------------------------------------------------------
1 | export const getCodeSizeInBytes = code => {
2 | return new Blob([code], { type: 'text/plain' }).size;
3 | };
4 |
--------------------------------------------------------------------------------
/src/lib/terser-options.js:
--------------------------------------------------------------------------------
1 | // All terser options available for reference
2 |
3 | // const options = `// edit terser options
4 | // {
5 | // compress: {
6 | // arguments: false,
7 | // arrows: true,
8 | // booleans: true,
9 | // booleans_as_integers: false,
10 | // collapse_vars: true,
11 | // comparisons: true,
12 | // computed_props: true,
13 | // conditionals: true,
14 | // dead_code: true,
15 | // defaults: true,
16 | // directives: true,
17 | // drop_console: false,
18 | // drop_debugger: true,
19 | // ecma: 5,
20 | // evaluate: true,
21 | // expression: false,
22 | // global_defs: false,
23 | // hoist_funs: false,
24 | // hoist_props: true,
25 | // hoist_vars: false,
26 | // ie8: false,
27 | // if_return: true,
28 | // inline: 3,
29 | // join_vars: true,
30 | // keep_classnames: false,
31 | // keep_fargs: true,
32 | // keep_fnames: false,
33 | // keep_infinity: false,
34 | // loops: true,
35 | // module: false,
36 | // negate_iife: true,
37 | // passes: 1,
38 | // properties: true,
39 | // pure_getters: 'strict',
40 | // pure_funcs: null,
41 | // reduce_funcs: true,
42 | // reduce_vars: true,
43 | // sequences: true,
44 | // side_effects: true,
45 | // switches: true,
46 | // top_retain: null,
47 | // toplevel: false,
48 | // typeofs: true,
49 | // unsafe: false,
50 | // unsafe_arrows: false,
51 | // unsafe_comps: false,
52 | // unsafe_Function: false,
53 | // unsafe_math: false,
54 | // unsafe_methods: false,
55 | // unsafe_proto: false,
56 | // unsafe_regexp: false,
57 | // unsafe_undefined: false,
58 | // unused: true,
59 | // warnings: false
60 | // },
61 | // enclose: false,
62 | // ie8: false,
63 | // keep_classnames: false,
64 | // keep_fnames: false,
65 | // mangle: {
66 | // cache: null,
67 | // eval: false,
68 | // ie8: false,
69 | // keep_classnames: false,
70 | // keep_fnames: false,
71 | // module: false,
72 | // properties: false,
73 | // reserved: ['arguments'],
74 | // safari10: false,
75 | // toplevel: false
76 | // },
77 | // module: false,
78 | // nameCache: null,
79 | // output: {
80 | // ascii_only: false,
81 | // beautify: false,
82 | // braces: false,
83 | // comments: false,
84 | // ecma: 5,
85 | // ie8: false,
86 | // indent_level: 4,
87 | // indent_start: 0,
88 | // inline_script: true,
89 | // keep_quoted_props: false,
90 | // max_line_len: false,
91 | // preamble: null,
92 | // quote_keys: false,
93 | // quote_style: 0,
94 | // safari10: false,
95 | // semicolons: true,
96 | // shebang: true,
97 | // shorthand: false,
98 | // source_map: null,
99 | // webkit: false,
100 | // width: 80,
101 | // wrap_iife: false
102 | // },
103 | // parse: {
104 | // toplevel: {},
105 | // filename: '0',
106 | // bare_returns: false,
107 | // ecma: 8,
108 | // expression: false,
109 | // html5_comments: true,
110 | // module: false,
111 | // shebang: true,
112 | // strict: false
113 | // },
114 | // rename: {
115 | // cache: null,
116 | // eval: false,
117 | // ie8: false,
118 | // keep_classnames: false,
119 | // keep_fnames: false,
120 | // module: false,
121 | // properties: false,
122 | // reserved: ['arguments'],
123 | // safari10: false,
124 | // toplevel: false
125 | // },
126 | // safari10: false,
127 | // sourceMap: false,
128 | // timings: false,
129 | // toplevel: false,
130 | // warnings: false,
131 | // wrap: false
132 | // }`;
133 |
134 | const options = `// edit terser options
135 | {
136 | module: true,
137 | compress: {},
138 | mangle: {},
139 | output: {},
140 | parse: {},
141 | rename: {},
142 | }`;
143 |
144 | /* eslint-disable-next-line no-eval */
145 | export const evalOptions = (opts) => eval(`(${opts||options})`)
146 |
147 | export default options;
148 |
--------------------------------------------------------------------------------
/src/paraiso-dark.css:
--------------------------------------------------------------------------------
1 | /*
2 |
3 | Name: Paraiso (Dark)
4 | Author: Jan T. Sott
5 |
6 | Color scheme by Jan T. Sott (https://github.com/idleberg/Paraiso-CodeMirror)
7 | Inspired by the art of Rubens LP (http://www.rubenslp.com.br)
8 |
9 | */
10 |
11 | .cm-s-paraiso-dark.CodeMirror { background: #2f1e2e; color: #b9b6b0; }
12 | .cm-s-paraiso-dark div.CodeMirror-selected { background: #41323f; }
13 | .cm-s-paraiso-dark .CodeMirror-line::selection, .cm-s-paraiso-dark .CodeMirror-line > span::selection, .cm-s-paraiso-dark .CodeMirror-line > span > span::selection { background: rgba(65, 50, 63, .99); }
14 | .cm-s-paraiso-dark .CodeMirror-line::-moz-selection, .cm-s-paraiso-dark .CodeMirror-line > span::-moz-selection, .cm-s-paraiso-dark .CodeMirror-line > span > span::-moz-selection { background: rgba(65, 50, 63, .99); }
15 | .cm-s-paraiso-dark .CodeMirror-gutters { background: #2f1e2e; border-right: 0px; }
16 | .cm-s-paraiso-dark .CodeMirror-guttermarker { color: #ef6155; }
17 | .cm-s-paraiso-dark .CodeMirror-guttermarker-subtle { color: #776e71; }
18 | .cm-s-paraiso-dark .CodeMirror-linenumber { color: #776e71; }
19 | .cm-s-paraiso-dark .CodeMirror-cursor { border-left: 1px solid #8d8687; }
20 |
21 | .cm-s-paraiso-dark span.cm-comment { color: #e96ba8; }
22 | .cm-s-paraiso-dark span.cm-atom { color: #815ba4; }
23 | .cm-s-paraiso-dark span.cm-number { color: #815ba4; }
24 |
25 | .cm-s-paraiso-dark span.cm-property, .cm-s-paraiso-dark span.cm-attribute { color: #48b685; }
26 | .cm-s-paraiso-dark span.cm-keyword { color: #ef6155; }
27 | .cm-s-paraiso-dark span.cm-string { color: #fec418; }
28 |
29 | .cm-s-paraiso-dark span.cm-variable { color: #48b685; }
30 | .cm-s-paraiso-dark span.cm-variable-2 { color: #06b6ef; }
31 | .cm-s-paraiso-dark span.cm-def { color: #f99b15; }
32 | .cm-s-paraiso-dark span.cm-bracket { color: #b9b6b0; }
33 | .cm-s-paraiso-dark span.cm-tag { color: #ef6155; }
34 | .cm-s-paraiso-dark span.cm-link { color: #815ba4; }
35 | .cm-s-paraiso-dark span.cm-error { background: #ef6155; color: #8d8687; }
36 |
37 | .cm-s-paraiso-dark .CodeMirror-activeline-background { background: #4D344A; }
38 | .cm-s-paraiso-dark .CodeMirror-matchingbracket { text-decoration: underline; color: white !important; }
39 |
--------------------------------------------------------------------------------
/src/paraiso-light.css:
--------------------------------------------------------------------------------
1 | /*
2 |
3 | Name: Paraiso (Light)
4 | Author: Jan T. Sott
5 |
6 | Color scheme by Jan T. Sott (https://github.com/idleberg/Paraiso-CodeMirror)
7 | Inspired by the art of Rubens LP (http://www.rubenslp.com.br)
8 |
9 | */
10 |
11 | .cm-s-paraiso-light.CodeMirror { background: #e7e9db; color: #41323f; }
12 | .cm-s-paraiso-light div.CodeMirror-selected { background: #b9b6b0; }
13 | .cm-s-paraiso-light .CodeMirror-line::selection, .cm-s-paraiso-light .CodeMirror-line > span::selection, .cm-s-paraiso-light .CodeMirror-line > span > span::selection { background: #b9b6b0; }
14 | .cm-s-paraiso-light .CodeMirror-line::-moz-selection, .cm-s-paraiso-light .CodeMirror-line > span::-moz-selection, .cm-s-paraiso-light .CodeMirror-line > span > span::-moz-selection { background: #b9b6b0; }
15 | .cm-s-paraiso-light .CodeMirror-gutters { background: #e7e9db; border-right: 0px; }
16 | .cm-s-paraiso-light .CodeMirror-guttermarker { color: black; }
17 | .cm-s-paraiso-light .CodeMirror-guttermarker-subtle { color: #8d8687; }
18 | .cm-s-paraiso-light .CodeMirror-linenumber { color: #8d8687; }
19 | .cm-s-paraiso-light .CodeMirror-cursor { border-left: 1px solid #776e71; }
20 |
21 | .cm-s-paraiso-light span.cm-comment { color: #e96ba8; }
22 | .cm-s-paraiso-light span.cm-atom { color: #815ba4; }
23 | .cm-s-paraiso-light span.cm-number { color: #815ba4; }
24 |
25 | .cm-s-paraiso-light span.cm-property, .cm-s-paraiso-light span.cm-attribute { color: #48b685; }
26 | .cm-s-paraiso-light span.cm-keyword { color: #ef6155; }
27 |
28 | /* this color was customized to improve contrast. original was #fec418 */
29 | .cm-s-paraiso-light span.cm-string { color: #d09c01; }
30 |
31 | .cm-s-paraiso-light span.cm-variable { color: #48b685; }
32 | .cm-s-paraiso-light span.cm-variable-2 { color: #06b6ef; }
33 | .cm-s-paraiso-light span.cm-def { color: #f99b15; }
34 | .cm-s-paraiso-light span.cm-bracket { color: #41323f; }
35 | .cm-s-paraiso-light span.cm-tag { color: #ef6155; }
36 | .cm-s-paraiso-light span.cm-link { color: #815ba4; }
37 | .cm-s-paraiso-light span.cm-error { background: #ef6155; color: #776e71; }
38 |
39 | .cm-s-paraiso-light .CodeMirror-activeline-background { background: #CFD1C4; }
40 | .cm-s-paraiso-light .CodeMirror-matchingbracket { text-decoration: underline; color: #333 !important; }
41 |
--------------------------------------------------------------------------------
/src/serviceWorker.js:
--------------------------------------------------------------------------------
1 | // This optional code is used to register a service worker.
2 | // register() is not called by default.
3 |
4 | // This lets the app load faster on subsequent visits in production, and gives
5 | // it offline capabilities. However, it also means that developers (and users)
6 | // will only see deployed updates on subsequent visits to a page, after all the
7 | // existing tabs open on the page have been closed, since previously cached
8 | // resources are updated in the background.
9 |
10 | // To learn more about the benefits of this model and instructions on how to
11 | // opt-in, read https://bit.ly/CRA-PWA
12 |
13 | const isLocalhost = Boolean(
14 | window.location.hostname === 'localhost' ||
15 | // [::1] is the IPv6 localhost address.
16 | window.location.hostname === '[::1]' ||
17 | // 127.0.0.1/8 is considered localhost for IPv4.
18 | window.location.hostname.match(
19 | /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/
20 | )
21 | );
22 |
23 | export function register(config) {
24 | if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) {
25 | // The URL constructor is available in all browsers that support SW.
26 | const publicUrl = new URL(process.env.PUBLIC_URL, window.location.href);
27 | if (publicUrl.origin !== window.location.origin) {
28 | // Our service worker won't work if PUBLIC_URL is on a different origin
29 | // from what our page is served on. This might happen if a CDN is used to
30 | // serve assets; see https://github.com/facebook/create-react-app/issues/2374
31 | return;
32 | }
33 |
34 | window.addEventListener('load', () => {
35 | const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`;
36 |
37 | if (isLocalhost) {
38 | // This is running on localhost. Let's check if a service worker still exists or not.
39 | checkValidServiceWorker(swUrl, config);
40 |
41 | // Add some additional logging to localhost, pointing developers to the
42 | // service worker/PWA documentation.
43 | navigator.serviceWorker.ready.then(() => {
44 | console.log(
45 | 'This web app is being served cache-first by a service ' +
46 | 'worker. To learn more, visit https://bit.ly/CRA-PWA'
47 | );
48 | });
49 | } else {
50 | // Is not localhost. Just register service worker
51 | registerValidSW(swUrl, config);
52 | }
53 | });
54 | }
55 | }
56 |
57 | function registerValidSW(swUrl, config) {
58 | navigator.serviceWorker
59 | .register(swUrl)
60 | .then(registration => {
61 | registration.onupdatefound = () => {
62 | const installingWorker = registration.installing;
63 | if (installingWorker == null) {
64 | return;
65 | }
66 | installingWorker.onstatechange = () => {
67 | if (installingWorker.state === 'installed') {
68 | if (navigator.serviceWorker.controller) {
69 | // At this point, the updated precached content has been fetched,
70 | // but the previous service worker will still serve the older
71 | // content until all client tabs are closed.
72 | console.log(
73 | 'New content is available and will be used when all ' +
74 | 'tabs for this page are closed. See https://bit.ly/CRA-PWA.'
75 | );
76 |
77 | // Execute callback
78 | if (config && config.onUpdate) {
79 | config.onUpdate(registration);
80 | }
81 | } else {
82 | // At this point, everything has been precached.
83 | // It's the perfect time to display a
84 | // "Content is cached for offline use." message.
85 | console.log('Content is cached for offline use.');
86 |
87 | // Execute callback
88 | if (config && config.onSuccess) {
89 | config.onSuccess(registration);
90 | }
91 | }
92 | }
93 | };
94 | };
95 | })
96 | .catch(error => {
97 | console.error('Error during service worker registration:', error);
98 | });
99 | }
100 |
101 | function checkValidServiceWorker(swUrl, config) {
102 | // Check if the service worker can be found. If it can't reload the page.
103 | fetch(swUrl)
104 | .then(response => {
105 | // Ensure service worker exists, and that we really are getting a JS file.
106 | const contentType = response.headers.get('content-type');
107 | if (
108 | response.status === 404 ||
109 | (contentType != null && contentType.indexOf('javascript') === -1)
110 | ) {
111 | // No service worker found. Probably a different app. Reload the page.
112 | navigator.serviceWorker.ready.then(registration => {
113 | registration.unregister().then(() => {
114 | window.location.reload();
115 | });
116 | });
117 | } else {
118 | // Service worker found. Proceed as normal.
119 | registerValidSW(swUrl, config);
120 | }
121 | })
122 | .catch(() => {
123 | console.log(
124 | 'No internet connection found. App is running in offline mode.'
125 | );
126 | });
127 | }
128 |
129 | export function unregister() {
130 | if ('serviceWorker' in navigator) {
131 | navigator.serviceWorker.ready.then(registration => {
132 | registration.unregister();
133 | });
134 | }
135 | }
136 |
--------------------------------------------------------------------------------