2018-04-12 00:22:03 +03:00
|
|
|
import React from "react";
|
|
|
|
|
2018-04-12 21:09:04 +03:00
|
|
|
import { Button, ClipboardButton, LinkButton } from "./buttons";
|
2018-04-12 00:22:03 +03:00
|
|
|
import EditorState from "./EditorState";
|
|
|
|
import { DebugPanel, InputPanel, OutputPanel } from "./panels";
|
2018-04-12 04:28:50 +03:00
|
|
|
import PrettierFormat from "./PrettierFormat";
|
2018-04-12 21:09:04 +03:00
|
|
|
import VersionLink from "./VersionLink";
|
2018-04-12 19:27:34 +03:00
|
|
|
import { shallowEqual } from "./helpers";
|
|
|
|
import * as urlHash from "./urlHash";
|
|
|
|
import formatMarkdown from "./markdown";
|
2018-04-12 18:16:16 +03:00
|
|
|
|
|
|
|
import { Sidebar, SidebarCategory } from "./sidebar/components";
|
|
|
|
import Option from "./sidebar/options";
|
|
|
|
import { Checkbox } from "./sidebar/inputs";
|
|
|
|
|
2018-04-12 00:22:03 +03:00
|
|
|
import WorkerApi from "./WorkerApi";
|
|
|
|
|
2018-04-12 18:16:16 +03:00
|
|
|
const CATEGORIES_ORDER = ["Global", "JavaScript", "Markdown", "Special"];
|
|
|
|
|
2018-04-12 00:22:03 +03:00
|
|
|
const ENABLED_OPTIONS = [
|
|
|
|
"parser",
|
|
|
|
"printWidth",
|
|
|
|
"tabWidth",
|
|
|
|
"useTabs",
|
|
|
|
{ name: "semi", inverted: true },
|
|
|
|
"singleQuote",
|
|
|
|
{ name: "bracketSpacing", inverted: true },
|
|
|
|
"jsxBracketSameLine",
|
|
|
|
"arrowParens",
|
|
|
|
"trailingComma",
|
|
|
|
"proseWrap",
|
|
|
|
"insertPragma",
|
|
|
|
"requirePragma"
|
|
|
|
].map(option => (typeof option === "string" ? { name: option } : option));
|
|
|
|
|
|
|
|
class Playground extends React.Component {
|
|
|
|
constructor() {
|
|
|
|
super();
|
2018-04-12 19:27:34 +03:00
|
|
|
|
|
|
|
this.state = Object.assign(
|
|
|
|
{
|
|
|
|
content: "",
|
|
|
|
loaded: false,
|
|
|
|
options: null
|
|
|
|
},
|
|
|
|
urlHash.read()
|
|
|
|
);
|
2018-04-12 00:22:03 +03:00
|
|
|
this.handleOptionValueChange = this.handleOptionValueChange.bind(this);
|
|
|
|
|
|
|
|
this.setContent = content => this.setState({ content });
|
2018-04-12 17:51:49 +03:00
|
|
|
this.clearContent = this.setContent.bind(this, "");
|
|
|
|
this.resetOptions = () =>
|
|
|
|
this.setState(state => ({
|
|
|
|
options: getDefaultOptions(state.availableOptions)
|
|
|
|
}));
|
2018-04-12 00:22:03 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
componentDidMount() {
|
|
|
|
this._worker = new WorkerApi("/worker.js");
|
|
|
|
|
|
|
|
this._worker
|
|
|
|
.postMessage({ type: "meta" })
|
|
|
|
.then(({ supportInfo, version }) => {
|
|
|
|
const availableOptions = parsePrettierOptions(supportInfo);
|
2018-04-12 17:51:49 +03:00
|
|
|
const options = Object.assign(
|
|
|
|
getDefaultOptions(availableOptions),
|
|
|
|
this.state.options
|
|
|
|
);
|
2018-04-12 00:22:03 +03:00
|
|
|
|
|
|
|
this.setState({ loaded: true, availableOptions, options, version });
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2018-04-12 19:27:34 +03:00
|
|
|
componentDidUpdate(_, prevState) {
|
|
|
|
const { content, options } = this.state;
|
|
|
|
if (
|
|
|
|
!shallowEqual(prevState.options, this.state.options) ||
|
|
|
|
prevState.content !== content
|
|
|
|
) {
|
|
|
|
urlHash.replace({ content, options });
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-04-12 00:22:03 +03:00
|
|
|
handleOptionValueChange(option, value) {
|
|
|
|
this.setState(state => ({
|
|
|
|
options: Object.assign({}, state.options, { [option.name]: value })
|
|
|
|
}));
|
|
|
|
}
|
|
|
|
|
|
|
|
render() {
|
2018-04-12 19:27:34 +03:00
|
|
|
const { availableOptions, content, loaded, options } = this.state;
|
2018-04-12 00:22:03 +03:00
|
|
|
|
|
|
|
if (!loaded) return "Loading...";
|
|
|
|
|
|
|
|
return (
|
2018-04-12 21:09:04 +03:00
|
|
|
<React.Fragment>
|
|
|
|
<VersionLink version={this.state.version} />
|
|
|
|
<EditorState>
|
|
|
|
{editorState => (
|
|
|
|
<div className="playground-container">
|
|
|
|
<div className="editors-container">
|
|
|
|
<Sidebar visible={editorState.showSidebar}>
|
|
|
|
{categorizeOptions(
|
|
|
|
availableOptions,
|
|
|
|
(category, categoryOptions) => (
|
|
|
|
<SidebarCategory key={category} title={category}>
|
|
|
|
{categoryOptions.map(option => (
|
|
|
|
<Option
|
|
|
|
key={option.name}
|
|
|
|
option={option}
|
|
|
|
value={options[option.name]}
|
|
|
|
onChange={this.handleOptionValueChange}
|
|
|
|
/>
|
|
|
|
))}
|
|
|
|
</SidebarCategory>
|
|
|
|
)
|
|
|
|
)}
|
|
|
|
<SidebarCategory title="Debug">
|
|
|
|
<Checkbox
|
|
|
|
label="show AST"
|
|
|
|
checked={editorState.showAst}
|
|
|
|
onChange={editorState.toggleAst}
|
2018-04-12 04:28:50 +03:00
|
|
|
/>
|
2018-04-12 21:09:04 +03:00
|
|
|
<Checkbox
|
|
|
|
label="show doc"
|
|
|
|
checked={editorState.showDoc}
|
|
|
|
onChange={editorState.toggleDoc}
|
2018-04-12 04:28:50 +03:00
|
|
|
/>
|
2018-04-12 21:09:04 +03:00
|
|
|
</SidebarCategory>
|
|
|
|
<div className="sub-options">
|
|
|
|
<Button onClick={this.resetOptions}>
|
|
|
|
Reset to defaults
|
|
|
|
</Button>
|
2018-04-12 04:28:50 +03:00
|
|
|
</div>
|
2018-04-12 21:09:04 +03:00
|
|
|
</Sidebar>
|
|
|
|
<PrettierFormat
|
|
|
|
worker={this._worker}
|
|
|
|
code={content}
|
|
|
|
options={options}
|
|
|
|
debugAst={editorState.showAst}
|
|
|
|
debugDoc={editorState.showDoc}
|
|
|
|
>
|
|
|
|
{({ formatted, debugAst, debugDoc }) => (
|
|
|
|
<div className="editors">
|
|
|
|
<InputPanel
|
|
|
|
mode={getCodemirrorMode(options.parser)}
|
|
|
|
rulerColumn={options.printWidth}
|
|
|
|
value={content}
|
|
|
|
onChange={this.setContent}
|
|
|
|
/>
|
|
|
|
{editorState.showAst ? (
|
|
|
|
<DebugPanel value={debugAst} />
|
|
|
|
) : null}
|
|
|
|
{editorState.showDoc ? (
|
|
|
|
<DebugPanel value={debugDoc} />
|
|
|
|
) : null}
|
|
|
|
<OutputPanel
|
|
|
|
mode={getCodemirrorMode(options.parser)}
|
|
|
|
value={formatted}
|
|
|
|
rulerColumn={options.printWidth}
|
|
|
|
/>
|
|
|
|
</div>
|
|
|
|
)}
|
|
|
|
</PrettierFormat>
|
2018-04-12 00:22:03 +03:00
|
|
|
</div>
|
2018-04-12 21:09:04 +03:00
|
|
|
<div className="bottom-bar">
|
|
|
|
<div className="bottom-bar-buttons">
|
|
|
|
<Button onClick={editorState.toggleSidebar}>
|
|
|
|
{editorState.showSidebar ? "Hide" : "Show"} options
|
|
|
|
</Button>
|
|
|
|
<Button onClick={this.clearContent}>Clear</Button>
|
|
|
|
</div>
|
|
|
|
<div className="bottom-bar-buttons bottom-bar-buttons-right">
|
|
|
|
<ClipboardButton clipboardValue={window.location.href}>
|
|
|
|
Copy link
|
|
|
|
</ClipboardButton>
|
|
|
|
<Button>Copy markdown</Button>
|
|
|
|
<LinkButton href="#" target="_blank" rel="noopener">
|
|
|
|
Report issue
|
|
|
|
</LinkButton>
|
|
|
|
</div>
|
2018-04-12 00:22:03 +03:00
|
|
|
</div>
|
|
|
|
</div>
|
2018-04-12 21:09:04 +03:00
|
|
|
)}
|
|
|
|
</EditorState>
|
|
|
|
</React.Fragment>
|
2018-04-12 00:22:03 +03:00
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-04-12 21:09:04 +03:00
|
|
|
function createVersionLink(version) {
|
|
|
|
const link = document.createElement("a");
|
|
|
|
const match = version.match(/^\d+\.\d+\.\d+-pr.(\d+)$/);
|
|
|
|
if (match) {
|
|
|
|
link.href = "https://github.com/prettier/prettier/pull/" + match[1];
|
|
|
|
link.textContent = `PR #${match[1]}`;
|
|
|
|
} else {
|
|
|
|
if (version.match(/\.0$/)) {
|
|
|
|
link.href =
|
|
|
|
"https://github.com/prettier/prettier/releases/tag/" + version;
|
|
|
|
} else {
|
|
|
|
link.href =
|
|
|
|
"https://github.com/prettier/prettier/blob/master/CHANGELOG.md#" +
|
|
|
|
version.replace(/\./g, "");
|
|
|
|
}
|
|
|
|
link.textContent = `v${version}`;
|
|
|
|
}
|
|
|
|
return link;
|
|
|
|
}
|
|
|
|
|
2018-04-12 18:16:16 +03:00
|
|
|
function categorizeOptions(availableOptions, render) {
|
|
|
|
const optionsByCategory = availableOptions.reduce((acc, option) => {
|
|
|
|
let options;
|
|
|
|
acc[option.category] = options = acc[option.category] || [];
|
|
|
|
options.push(option);
|
|
|
|
return acc;
|
|
|
|
}, {});
|
|
|
|
|
|
|
|
return CATEGORIES_ORDER.filter(c => optionsByCategory[c]).map(category =>
|
|
|
|
render(category, optionsByCategory[category])
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2018-04-12 17:51:49 +03:00
|
|
|
function getDefaultOptions(availableOptions) {
|
|
|
|
return availableOptions.reduce((acc, option) => {
|
|
|
|
acc[option.name] = option.default;
|
|
|
|
return acc;
|
|
|
|
}, {});
|
|
|
|
}
|
|
|
|
|
2018-04-12 03:01:36 +03:00
|
|
|
function getCodemirrorMode(parser) {
|
|
|
|
switch (parser) {
|
|
|
|
case "css":
|
|
|
|
case "less":
|
|
|
|
case "scss":
|
|
|
|
return "css";
|
|
|
|
case "graphql":
|
|
|
|
return "graphql";
|
|
|
|
case "markdown":
|
|
|
|
return "markdown";
|
|
|
|
default:
|
|
|
|
return "jsx";
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-04-12 00:22:03 +03:00
|
|
|
function parsePrettierOptions(supportInfo) {
|
|
|
|
const supportedOptions = supportInfo.options.reduce((acc, option) => {
|
|
|
|
acc[option.name] = option;
|
|
|
|
return acc;
|
|
|
|
}, {});
|
|
|
|
|
|
|
|
return ENABLED_OPTIONS.reduce((optionsList, optionConfig) => {
|
|
|
|
if (!supportedOptions[optionConfig.name]) return optionsList;
|
|
|
|
|
|
|
|
const option = Object.assign(
|
|
|
|
{},
|
|
|
|
optionConfig,
|
|
|
|
supportedOptions[optionConfig.name]
|
|
|
|
);
|
|
|
|
option.cliName =
|
|
|
|
"--" +
|
|
|
|
(option.inverted ? "no-" : "") +
|
|
|
|
option.name.replace(/([a-z])([A-Z])/g, "$1-$2").toLowerCase();
|
|
|
|
|
|
|
|
optionsList.push(option);
|
|
|
|
return optionsList;
|
|
|
|
}, []);
|
|
|
|
}
|
|
|
|
|
|
|
|
export default Playground;
|