amis2/examples/components/Play.jsx

622 lines
16 KiB
React
Raw Normal View History

2019-06-11 17:00:02 +08:00
import React from 'react';
2022-06-01 15:06:00 +08:00
import {toast, render, makeTranslator} from 'amis';
2022-06-01 21:35:49 +08:00
import {normalizeLink} from 'amis-core';
import {isMobile} from 'amis-core';
import {attachmentAdpator} from 'amis-core';
import {alert, confirm} from 'amis-ui';
2019-06-11 17:00:02 +08:00
import axios from 'axios';
import JSON5 from 'json5';
2022-06-01 21:35:49 +08:00
import {Editor as CodeEditor} from 'amis-ui';
import copy from 'copy-to-clipboard';
2022-02-11 19:45:33 +08:00
import {matchPath} from 'react-router-dom';
2022-06-01 21:35:49 +08:00
import {Drawer} from 'amis-ui';
2019-04-30 11:11:25 +08:00
const DEFAULT_CONTENT = `{
2020-09-03 11:45:31 +08:00
"$schema": "/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",
2022-02-10 20:08:17 +08:00
"body": SCHEMA_PLACEHOLDER,
2019-04-30 11:11:25 +08:00
"submitText": null,
"actions": []
}
}`,
2021-06-03 22:09:30 +08:00
'form2': `{
"type": "page",
"body": {
"title": "",
"type": "form",
"autoFocus": false,
"api": "/api/mock/saveForm?waitSeconds=1",
"mode": "horizontal",
"body": 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,
2022-02-10 20:08:17 +08:00
"body": [
2019-04-30 11:11:25 +08:00
SCHEMA_PLACEHOLDER
],
"submitText": null,
"actions": []
}
2021-06-03 22:25:26 +08:00
}`,
'form-item2': `{
"type": "page",
"body": {
"title": "",
"type": "form",
"mode": "horizontal",
"autoFocus": false,
"body": [
SCHEMA_PLACEHOLDER
],
"submitText": null,
"actions": []
}
}`
2019-04-30 11:11:25 +08:00
};
export default class PlayGround extends React.Component {
2019-11-07 10:41:14 +08:00
state = null;
startX = 0;
oldContents = '';
frameTemplate;
iframeRef;
2019-11-07 10:41:14 +08:00
static defaultProps = {
vertical: false
};
constructor(props) {
super(props);
this.iframeRef = React.createRef();
const {history} = 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),
isOpened: false
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.toggleDrawer = this.toggleDrawer.bind(this);
this.close = this.close.bind(this);
this.schemaProps = {};
const __ = makeTranslator(props.locale);
2019-11-07 10:41:14 +08:00
this.env = {
2020-08-01 00:26:55 +08:00
session: 'doc',
updateLocation: (location, replace) => {
history[replace ? 'replace' : 'push'](normalizeLink(location));
},
isCurrentUrl: to => {
if (!to) {
return false;
}
const link = normalizeLink(to);
return !!matchPath(history.location.pathname, {
path: link,
exact: true
});
},
2020-08-20 16:01:19 +08:00
jumpTo: (to, action) => {
to = normalizeLink(to);
2020-08-20 16:01:19 +08:00
if (action && action.actionType === 'url') {
2020-11-03 11:02:04 +08:00
action.blank === true ? window.open(to) : (window.location.href = to);
2020-08-20 16:01:19 +08:00
return;
}
if (action && to && action.target) {
window.open(to, action.target);
return;
}
if (/^https?:\/\//.test(to)) {
window.location.replace(to);
} else {
history.push(to);
}
},
fetcher: async api => {
let {url, method, data, responseType, config, headers} = api;
config = config || {};
// 如果在 gh-pages 里面
if (
/^\/amis/.test(window.location.pathname) &&
typeof url === 'string' &&
url.startsWith('/examples/static/')
) {
url = url.replace('/examples/static/', '/amis/static/');
}
config.url = url;
responseType && (config.responseType = responseType);
if (config.cancelExecutor) {
config.cancelToken = new axios.CancelToken(config.cancelExecutor);
}
2019-04-30 11:11:25 +08:00
config.headers = headers || {};
config.method = method;
config.data = data;
if (method === 'get' && data) {
config.params = data;
} else if (data && data instanceof FormData) {
// config.headers['Content-Type'] = 'multipart/form-data';
} else if (
data &&
typeof data !== 'string' &&
!(data instanceof Blob) &&
!(data instanceof ArrayBuffer)
) {
data = JSON.stringify(data);
2019-11-07 10:41:14 +08:00
config.headers['Content-Type'] = 'application/json';
2019-04-30 11:11:25 +08:00
}
// 支持返回各种报错信息
config.validateStatus = function () {
return true;
};
let response = await axios(config);
response = await attachmentAdpator(response, __);
if (response.status >= 400) {
if (response.data) {
// 主要用于 raw: 模式下,后端自己校验登录,
if (
response.status === 401 &&
response.data.location &&
response.data.location.startsWith('http')
) {
location.href = response.data.location.replace(
'{{redirect}}',
encodeURIComponent(location.href)
);
return new Promise(() => {});
} else if (response.data.msg) {
throw new Error(response.data.msg);
} else {
throw new Error(
__('System.requestError') +
JSON.stringify(response.data, null, 2)
);
}
} else {
throw new Error(
`${__('System.requestErrorStatus')} ${response.status}`
);
}
}
return response;
2019-11-07 10:41:14 +08:00
},
isCancel: value => axios.isCancel(value),
notify: (type, msg, conf) =>
toast[type]
? toast[type](msg, conf)
: console.warn('[Notify]', type, msg),
alert,
confirm,
2021-10-13 11:41:43 +08:00
copy: (content, options) => {
copy(content, options);
toast.success(__('System.copy'));
},
tracker(eventTrack) {
console.debug('eventTrack', eventTrack);
},
replaceText: {
AMIS_HOST: 'https://baidu.gitee.io/amis'
},
loadTinymcePlugin: async tinymce => {
// 参考https://www.tiny.cloud/docs/advanced/creating-a-plugin/
/*
Note: We have included the plugin in the same JavaScript file as the TinyMCE
instance for display purposes only. Tiny recommends not maintaining the plugin
with the TinyMCE instance and using the `external_plugins` option.
*/
tinymce.PluginManager.add('example', function (editor, url) {
var openDialog = function () {
return editor.windowManager.open({
title: 'Example plugin',
body: {
type: 'panel',
items: [
{
type: 'input',
name: 'title',
label: 'Title'
}
]
},
buttons: [
{
type: 'cancel',
text: 'Close'
},
{
type: 'submit',
text: 'Save',
primary: true
}
],
onSubmit: function (api) {
var data = api.getData();
/* Insert content when the window form is submitted */
editor.insertContent('Title: ' + data.title);
api.close();
}
});
};
/* Add a button that opens a window */
editor.ui.registry.addButton('example', {
text: 'My button',
onAction: function () {
/* Open window */
openDialog();
}
});
/* Adds a menu item, which can then be included in any menu via the menu/menubar configuration */
editor.ui.registry.addMenuItem('example', {
text: 'Example plugin',
onAction: function () {
/* Open window */
openDialog();
}
});
/* Return the metadata for the help plugin */
return {
getMetadata: function () {
return {
name: 'Example plugin',
url: 'http://exampleplugindocsurl.com'
};
}
};
});
}
2019-11-07 10:41:14 +08:00
};
2019-04-30 11:11:25 +08:00
this.watchIframeReady = this.watchIframeReady.bind(this);
window.addEventListener('message', this.watchIframeReady, false);
}
watchIframeReady(event) {
// iframe 里面的 amis 初始化了就可以发数据
if (event.data && event.data === 'amisReady') {
this.updateIframe();
}
}
updateIframe() {
if (this.iframeRef && this.iframeRef.current) {
this.iframeRef.current.contentWindow.postMessage(
{
schema: this.state.schema,
props: {theme: this.props.theme, locale: this.props.locale}
},
'*'
);
}
2019-11-07 10:41:14 +08:00
}
componentDidUpdate(preProps) {
2019-11-07 10:41:14 +08:00
const props = this.props;
if (preProps.code !== props.code) {
const schema = this.buildSchema(props.code || DEFAULT_CONTENT, props);
2019-11-07 10:41:14 +08:00
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);
window.removeEventListener('message', this.watchIframeReady, false);
2019-11-07 10:41:14 +08:00
}
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 = 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
schemaContent = schemaContent.replace(/('|")raw:/g, '$1'); // 去掉 raw
2019-04-30 11:11:25 +08:00
const json = JSON5.parse(schemaContent);
2020-09-03 11:45:31 +08:00
return json;
2019-11-07 10:41:14 +08:00
} 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
};
if (this.props.viewMode === 'mobile' && !isMobile()) {
return (
<iframe
width="375"
height="100%"
frameBorder={0}
className="mobile-frame"
ref={this.iframeRef}
// @ts-ignore
src={__uri('../mobile.html')}
></iframe>
);
2019-04-30 11:11:25 +08:00
}
return render(schema, props, this.env);
2019-11-07 10:41:14 +08:00
}
handleChange(value) {
this.setState({
schemaCode: value
});
try {
const schema = JSON5.parse(value);
this.setState(
{
schema
},
() => {
this.updateIframe();
}
);
2019-11-07 10:41:14 +08:00
} 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);
}
toggleDrawer() {
this.setState({
isOpened: !this.state.isOpened
});
}
close() {
this.setState({
isOpened: false
});
}
2020-09-16 23:41:51 +08:00
editorDidMount = (editor, monaco) => {
this.editor = editor;
this.monaco = monaco;
let host = `${window.location.protocol}//${window.location.host}`;
// 如果在 gh-pages 里面
if (/^\/amis/.test(window.location.pathname)) {
host += '/amis';
}
const schemaUrl = `${host}/schema.json`;
monaco.languages.json.jsonDefaults.setDiagnosticsOptions({
schemas: [
{
uri: schemaUrl,
fileMatch: ['*']
}
],
validate: true,
enableSchemaRequest: true,
allowComments: true
});
};
// editorFactory = (containerElement, monaco, options) => {
// this.model = monaco.editor.createModel(
// this.state.schemaCode,
// 'json',
// monaco.Uri.parse(`isuda://schemas/page.json`)
// );
// return monaco.editor.create(containerElement, {
// autoIndent: true,
// formatOnType: true,
// formatOnPaste: true,
// selectOnLineNumbers: true,
// scrollBeyondLastLine: false,
// folding: true,
// minimap: {
// enabled: false
// },
// ...options,
// model: this.model
// });
// };
2019-11-07 10:41:14 +08:00
renderEditor() {
2020-12-01 11:43:30 +08:00
const {theme} = this.props;
2019-11-07 10:41:14 +08:00
return (
<CodeEditor
value={this.state.schemaCode}
onChange={this.handleChange}
options={{
lineNumbers: 'off'
}}
2020-09-16 23:41:51 +08:00
// editorFactory={this.editorFactory}
editorDidMount={this.editorDidMount}
2019-11-07 10:41:14 +08:00
language="json"
2020-12-01 11:43:30 +08:00
editorTheme={theme === 'dark' ? 'vs-dark' : 'vs'}
2019-11-07 10:41:14 +08:00
/>
);
}
render() {
const {vertical, mini, height, theme, classPrefix} = this.props;
if (mini) {
return (
<div className="Playgroud Playgroud--mini">
<a onClick={this.toggleDrawer} className="Playgroud-edit-btn">
编辑代码 <i className="fa fa-code p-l-xs"></i>
</a>
<Drawer
showCloseButton
closeOnOutside
resizable
theme={theme}
overlay={false}
position="right"
show={this.state.isOpened}
onHide={this.close}
>
<div className={`${classPrefix}Drawer-header`}>
编辑代码支持编辑实时预览
</div>
<div className={`${classPrefix}Drawer-body no-padder`}>
{this.renderEditor()}
</div>
</Drawer>
<div style={{minHeight: height}} className="Playgroud-preview">
{this.renderPreview()}
</div>
</div>
);
} else if (vertical) {
2019-11-07 10:41:14 +08:00
return (
2020-12-09 00:35:24 +08:00
<div className="Playgroud">
<div style={{minHeight: height}} className="Playgroud-preview">
{this.renderPreview()}
2019-11-07 10:41:14 +08:00
</div>
2020-12-09 00:35:24 +08:00
<div className="Playgroud-code">{this.renderEditor()}</div>
2019-11-07 10:41:14 +08:00
</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">
2020-10-12 11:19:25 +08:00
<div className="scroll-y h-full pos-abt w-full b-b">
2019-11-07 10:41:14 +08:00
{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
}