2019-06-11 17:00:02 +08:00
|
|
|
import React from 'react';
|
2019-11-07 10:41:14 +08:00
|
|
|
import {toast} from '../../src/components/Toast';
|
2019-04-30 11:11:25 +08:00
|
|
|
import {render} from '../../src/index';
|
2020-08-05 14:59:27 +08:00
|
|
|
import {alert, confirm} from '../../src/components/Alert';
|
2019-06-11 17:00:02 +08:00
|
|
|
import axios from 'axios';
|
2019-04-30 11:11:25 +08:00
|
|
|
import Frame from 'react-frame-component';
|
2019-06-11 17:00:02 +08:00
|
|
|
import stripJsonComments from 'strip-json-comments';
|
2019-04-30 11:11:25 +08:00
|
|
|
import CodeEditor from '../../src/components/Editor';
|
2020-08-05 14:59:27 +08:00
|
|
|
import copy from 'copy-to-clipboard';
|
2019-04-30 11:11:25 +08:00
|
|
|
|
|
|
|
const DEFAULT_CONTENT = `{
|
2019-05-09 18:25:12 +08:00
|
|
|
"$schema": "https://houtai.baidu.com/v2/schemas/page.json#",
|
2019-04-30 11:11:25 +08:00
|
|
|
"type": "page",
|
|
|
|
"title": "Title",
|
|
|
|
"body": "Body",
|
|
|
|
"aside": "Aside",
|
|
|
|
"toolbar": "Toolbar"
|
|
|
|
}`;
|
|
|
|
|
|
|
|
const scopes = {
|
2019-12-06 09:58:08 +08:00
|
|
|
'none': ``,
|
2019-04-30 11:11:25 +08:00
|
|
|
|
2019-12-06 09:58:08 +08:00
|
|
|
'body': `{
|
2019-04-30 11:11:25 +08:00
|
|
|
"type": "page",
|
|
|
|
"body": SCHEMA_PLACEHOLDER
|
|
|
|
}`,
|
|
|
|
|
2019-12-06 09:58:08 +08:00
|
|
|
'form': `{
|
2019-04-30 11:11:25 +08:00
|
|
|
"type": "page",
|
|
|
|
"body": {
|
|
|
|
"title": "",
|
|
|
|
"type": "form",
|
|
|
|
"autoFocus": false,
|
|
|
|
"api": "/api/mock/saveForm?waitSeconds=1",
|
|
|
|
"mode": "horizontal",
|
|
|
|
"controls": SCHEMA_PLACEHOLDER,
|
|
|
|
"submitText": null,
|
|
|
|
"actions": []
|
|
|
|
}
|
|
|
|
}`,
|
|
|
|
|
2019-11-07 10:41:14 +08:00
|
|
|
'form-item': `{
|
2019-04-30 11:11:25 +08:00
|
|
|
"type": "page",
|
|
|
|
"body": {
|
|
|
|
"title": "",
|
|
|
|
"type": "form",
|
|
|
|
"mode": "horizontal",
|
|
|
|
"autoFocus": false,
|
|
|
|
"controls": [
|
|
|
|
SCHEMA_PLACEHOLDER
|
|
|
|
],
|
|
|
|
"submitText": null,
|
|
|
|
"actions": []
|
|
|
|
}
|
|
|
|
}`
|
|
|
|
};
|
|
|
|
|
|
|
|
export default class PlayGround extends React.Component {
|
2019-11-07 10:41:14 +08:00
|
|
|
state = null;
|
|
|
|
startX = 0;
|
|
|
|
oldContents = '';
|
|
|
|
frameTemplate;
|
|
|
|
|
|
|
|
static defaultProps = {
|
|
|
|
useIFrame: false,
|
|
|
|
vertical: false
|
|
|
|
};
|
|
|
|
|
|
|
|
constructor(props) {
|
|
|
|
super(props);
|
|
|
|
|
2020-08-05 14:59:27 +08:00
|
|
|
const {router} = props;
|
|
|
|
|
2019-11-07 10:41:14 +08:00
|
|
|
const schema = this.buildSchema(props.code || DEFAULT_CONTENT, props);
|
|
|
|
this.state = {
|
|
|
|
asideWidth: props.asideWidth || Math.max(300, window.innerWidth * 0.3),
|
|
|
|
schema: schema,
|
|
|
|
schemaCode: JSON.stringify(schema, null, 2)
|
2019-04-30 11:11:25 +08:00
|
|
|
};
|
|
|
|
|
2019-11-07 10:41:14 +08:00
|
|
|
this.handleMouseDown = this.handleMouseDown.bind(this);
|
|
|
|
this.handleMouseMove = this.handleMouseMove.bind(this);
|
|
|
|
this.handleMouseUp = this.handleMouseUp.bind(this);
|
|
|
|
this.removeWindowEvents = this.removeWindowEvents.bind(this);
|
|
|
|
this.handleChange = this.handleChange.bind(this);
|
|
|
|
this.schemaProps = {
|
|
|
|
style: {
|
|
|
|
height: '100%'
|
|
|
|
}
|
|
|
|
};
|
2020-08-05 14:59:27 +08:00
|
|
|
const normalizeLink = to => {
|
|
|
|
to = to || '';
|
|
|
|
const location = router.getCurrentLocation();
|
|
|
|
|
|
|
|
if (to && to[0] === '#') {
|
|
|
|
to = location.pathname + location.search + to;
|
|
|
|
} else if (to && to[0] === '?') {
|
|
|
|
to = location.pathname + to;
|
|
|
|
}
|
|
|
|
|
|
|
|
const idx = to.indexOf('?');
|
|
|
|
const idx2 = to.indexOf('#');
|
|
|
|
let pathname = ~idx
|
|
|
|
? to.substring(0, idx)
|
|
|
|
: ~idx2
|
|
|
|
? to.substring(0, idx2)
|
|
|
|
: to;
|
|
|
|
let search = ~idx ? to.substring(idx, ~idx2 ? idx2 : undefined) : '';
|
|
|
|
let hash = ~idx2 ? to.substring(idx2) : location.hash;
|
|
|
|
|
|
|
|
if (!pathname) {
|
|
|
|
pathname = location.pathname;
|
|
|
|
} else if (pathname[0] != '/' && !/^https?:\/\//.test(pathname)) {
|
|
|
|
let relativeBase = location.pathname;
|
|
|
|
const paths = relativeBase.split('/');
|
|
|
|
paths.pop();
|
|
|
|
let m;
|
|
|
|
while ((m = /^\.\.?\//.exec(pathname))) {
|
|
|
|
if (m[0] === '../') {
|
|
|
|
paths.pop();
|
|
|
|
}
|
|
|
|
pathname = pathname.substring(m[0].length);
|
|
|
|
}
|
|
|
|
pathname = paths.concat(pathname).join('/');
|
|
|
|
}
|
|
|
|
|
|
|
|
return pathname + search + hash;
|
|
|
|
};
|
2019-11-07 10:41:14 +08:00
|
|
|
this.env = {
|
2020-08-01 00:26:55 +08:00
|
|
|
session: 'doc',
|
2020-08-05 14:59:27 +08:00
|
|
|
updateLocation: (location, replace) => {
|
|
|
|
router[replace ? 'replace' : 'push'](normalizeLink(location));
|
|
|
|
},
|
|
|
|
isCurrentUrl: to => {
|
|
|
|
const link = normalizeLink(to);
|
|
|
|
return router.isActive(link);
|
|
|
|
},
|
|
|
|
jumpTo: to => {
|
|
|
|
to = normalizeLink(to);
|
|
|
|
|
|
|
|
if (/^https?:\/\//.test(to)) {
|
|
|
|
window.location.replace(to);
|
|
|
|
} else {
|
|
|
|
router.push(to);
|
|
|
|
}
|
|
|
|
},
|
2019-11-07 10:41:14 +08:00
|
|
|
fetcher: config => {
|
|
|
|
config = {
|
|
|
|
dataType: 'json',
|
|
|
|
...config
|
2019-04-30 11:11:25 +08:00
|
|
|
};
|
|
|
|
|
2019-11-07 10:41:14 +08:00
|
|
|
if (config.dataType === 'json' && config.data) {
|
|
|
|
config.data = JSON.stringify(config.data);
|
|
|
|
config.headers = config.headers || {};
|
|
|
|
config.headers['Content-Type'] = 'application/json';
|
2019-04-30 11:11:25 +08:00
|
|
|
}
|
|
|
|
|
2019-11-07 10:41:14 +08:00
|
|
|
return axios[config.method](config.url, config.data, config);
|
|
|
|
},
|
2020-08-05 14:59:27 +08:00
|
|
|
isCancel: value => axios.isCancel(value),
|
2019-11-07 10:41:14 +08:00
|
|
|
notify: (type, msg) =>
|
|
|
|
toast[type]
|
|
|
|
? toast[type](msg, type === 'error' ? '系统错误' : '系统消息')
|
2020-08-05 14:59:27 +08:00
|
|
|
: console.warn('[Notify]', type, msg),
|
|
|
|
alert,
|
|
|
|
confirm,
|
|
|
|
copy: content => {
|
|
|
|
copy(content);
|
|
|
|
toast.success('内容已复制到粘贴板');
|
|
|
|
}
|
2019-11-07 10:41:14 +08:00
|
|
|
};
|
2019-04-30 11:11:25 +08:00
|
|
|
|
2019-11-07 10:41:14 +08:00
|
|
|
const links = [].slice
|
|
|
|
.call(document.head.querySelectorAll('link,style'))
|
|
|
|
.map(item => item.outerHTML);
|
|
|
|
this.frameTemplate = `<!DOCTYPE html><html><head>${links.join(
|
|
|
|
''
|
|
|
|
)}</head><body><div></div></body></html>`;
|
|
|
|
}
|
|
|
|
|
|
|
|
componentWillReceiveProps(nextprops) {
|
|
|
|
const props = this.props;
|
|
|
|
|
|
|
|
if (props.code !== nextprops.code) {
|
|
|
|
const schema = this.buildSchema(
|
|
|
|
nextprops.code || DEFAULT_CONTENT,
|
|
|
|
nextprops
|
|
|
|
);
|
|
|
|
this.setState({
|
|
|
|
schema: schema,
|
|
|
|
schemaCode: JSON.stringify(schema, null, 2)
|
|
|
|
});
|
2019-04-30 11:11:25 +08:00
|
|
|
}
|
2019-11-07 10:41:14 +08:00
|
|
|
}
|
2019-04-30 11:11:25 +08:00
|
|
|
|
2019-11-07 10:41:14 +08:00
|
|
|
componentDidMount() {
|
|
|
|
this.props.setAsideFolded && this.props.setAsideFolded(true);
|
|
|
|
}
|
2019-04-30 11:11:25 +08:00
|
|
|
|
2019-11-07 10:41:14 +08:00
|
|
|
componentWillUnmount() {
|
|
|
|
this.props.setAsideFolded && this.props.setAsideFolded(false);
|
|
|
|
}
|
2019-04-30 11:11:25 +08:00
|
|
|
|
2019-11-07 10:41:14 +08:00
|
|
|
buildSchema(schemaContent, props = this.props) {
|
|
|
|
const query = props.location.query;
|
2019-04-30 11:11:25 +08:00
|
|
|
|
2019-11-07 10:41:14 +08:00
|
|
|
try {
|
|
|
|
const scope = query.scope || props.scope;
|
2019-04-30 11:11:25 +08:00
|
|
|
|
2019-11-07 10:41:14 +08:00
|
|
|
if (scope && scopes[scope]) {
|
|
|
|
schemaContent = scopes[scope].replace(
|
|
|
|
'SCHEMA_PLACEHOLDER',
|
|
|
|
schemaContent
|
2019-04-30 11:11:25 +08:00
|
|
|
);
|
2019-11-07 10:41:14 +08:00
|
|
|
}
|
2019-04-30 11:11:25 +08:00
|
|
|
|
2019-11-07 10:41:14 +08:00
|
|
|
schemaContent = stripJsonComments(schemaContent).replace(
|
|
|
|
/('|")raw:/g,
|
|
|
|
'$1'
|
|
|
|
); // 去掉注释
|
2019-04-30 11:11:25 +08:00
|
|
|
|
2019-11-07 10:41:14 +08:00
|
|
|
return JSON.parse(schemaContent);
|
|
|
|
} catch (e) {
|
|
|
|
console.error(this.formatMessage(e.message, schemaContent));
|
2019-04-30 11:11:25 +08:00
|
|
|
}
|
|
|
|
|
2019-11-07 10:41:14 +08:00
|
|
|
return {};
|
|
|
|
}
|
|
|
|
|
|
|
|
formatMessage(message, input) {
|
|
|
|
if (/position\s?(\d+)$/.test(message)) {
|
|
|
|
const lines = input
|
|
|
|
.substring(0, parseInt(RegExp.$1, 10))
|
|
|
|
.split(/\n|\r\n|\r/);
|
|
|
|
message = `Json 语法错误,请检测。出错位置:${lines.length},列:${
|
|
|
|
lines[lines.length - 1].length
|
|
|
|
}。`;
|
2019-04-30 11:11:25 +08:00
|
|
|
}
|
|
|
|
|
2019-11-07 10:41:14 +08:00
|
|
|
return message;
|
|
|
|
}
|
2019-04-30 11:11:25 +08:00
|
|
|
|
2019-11-07 10:41:14 +08:00
|
|
|
renderPreview() {
|
|
|
|
const schema = this.state.schema;
|
2019-04-30 11:11:25 +08:00
|
|
|
|
2020-07-28 10:03:53 +08:00
|
|
|
const props = {
|
|
|
|
...this.schemaProps,
|
|
|
|
theme: this.props.theme,
|
2020-08-01 00:26:55 +08:00
|
|
|
locale: this.props.locale,
|
|
|
|
affixHeader: false,
|
|
|
|
affixFooter: false
|
2020-07-28 10:03:53 +08:00
|
|
|
};
|
|
|
|
|
2019-11-07 10:41:14 +08:00
|
|
|
if (!this.props.useIFrame) {
|
2020-07-28 10:03:53 +08:00
|
|
|
return render(schema, props, this.env);
|
2019-04-30 11:11:25 +08:00
|
|
|
}
|
|
|
|
|
2019-11-07 10:41:14 +08:00
|
|
|
return (
|
|
|
|
<Frame
|
|
|
|
width="100%"
|
|
|
|
height="100%"
|
|
|
|
frameBorder={0}
|
|
|
|
initialContent={this.frameTemplate}
|
|
|
|
>
|
2020-07-28 10:03:53 +08:00
|
|
|
{render(schema, props, this.env)}
|
2019-11-07 10:41:14 +08:00
|
|
|
</Frame>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
handleChange(value) {
|
|
|
|
this.setState({
|
|
|
|
schemaCode: value
|
|
|
|
});
|
|
|
|
|
|
|
|
try {
|
|
|
|
const schema = JSON.parse(value);
|
|
|
|
|
|
|
|
this.setState({
|
|
|
|
schema
|
|
|
|
});
|
|
|
|
} catch (e) {
|
|
|
|
//ignore
|
2019-04-30 11:11:25 +08:00
|
|
|
}
|
2019-11-07 10:41:14 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
handleMouseDown(e) {
|
|
|
|
this.startX = e.clientX;
|
|
|
|
this.startWidth = this.state.asideWidth;
|
|
|
|
|
|
|
|
// this.startPosition.y = e.clientY;
|
|
|
|
|
|
|
|
window.addEventListener('mouseup', this.handleMouseUp);
|
|
|
|
window.addEventListener('mousemove', this.handleMouseMove);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
handleMouseMove(e) {
|
|
|
|
const diff = this.startX - e.clientX;
|
|
|
|
e.preventDefault();
|
|
|
|
|
|
|
|
this.setState({
|
|
|
|
asideWidth: Math.min(800, Math.max(200, this.startWidth + diff))
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
handleMouseUp() {
|
|
|
|
this.removeWindowEvents();
|
|
|
|
}
|
|
|
|
|
|
|
|
removeWindowEvents() {
|
|
|
|
window.removeEventListener('mouseup', this.handleMouseUp);
|
|
|
|
window.removeEventListener('mousemove', this.handleMouseMove);
|
|
|
|
}
|
|
|
|
|
|
|
|
renderEditor() {
|
|
|
|
return (
|
|
|
|
<CodeEditor
|
|
|
|
value={this.state.schemaCode}
|
|
|
|
onChange={this.handleChange}
|
|
|
|
language="json"
|
|
|
|
/>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
render() {
|
|
|
|
const {vertical} = this.props;
|
|
|
|
if (vertical) {
|
|
|
|
return (
|
|
|
|
<div className="vbox">
|
|
|
|
<div className="row-row">
|
|
|
|
<div className="cell pos-rlt">
|
|
|
|
<div className="scroll-y h-full pos-abt w-full">
|
|
|
|
{this.renderPreview()}
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
2020-07-23 20:31:51 +08:00
|
|
|
<div className="row-row b-t" style={{height: 200}}>
|
2019-11-07 10:41:14 +08:00
|
|
|
<div className="cell">{this.renderEditor()}</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
);
|
2019-04-30 11:11:25 +08:00
|
|
|
}
|
|
|
|
|
2019-11-07 10:41:14 +08:00
|
|
|
return (
|
|
|
|
<div
|
|
|
|
style={{
|
|
|
|
position: 'absolute',
|
|
|
|
top: 50,
|
|
|
|
bottom: 0
|
|
|
|
}}
|
|
|
|
>
|
|
|
|
<div className="hbox">
|
|
|
|
<div className="col pos-rlt">
|
|
|
|
<div className="scroll-y h-full pos-abt w-full">
|
|
|
|
{this.renderPreview()}
|
2019-04-30 11:11:25 +08:00
|
|
|
</div>
|
2019-11-07 10:41:14 +08:00
|
|
|
</div>
|
|
|
|
<div
|
|
|
|
className="col bg-light lter b-l bg-auto pos-rlt"
|
|
|
|
style={{width: this.state.asideWidth}}
|
|
|
|
>
|
|
|
|
<div className="resizer" onMouseDown={this.handleMouseDown} />
|
|
|
|
{this.renderEditor()}
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
);
|
|
|
|
}
|
2019-04-30 11:11:25 +08:00
|
|
|
}
|