import React from 'react'; import {toast, render, makeTranslator} from 'amis'; import {normalizeLink} from 'amis-core'; import {isMobile} from 'amis-core'; import {attachmentAdpator} from 'amis-core'; import {alert, confirm} from 'amis-ui'; import axios from 'axios'; import JSON5 from 'json5'; import {Editor as CodeEditor} from 'amis-ui'; import copy from 'copy-to-clipboard'; import {matchPath} from 'react-router-dom'; import {Drawer} from 'amis-ui'; const DEFAULT_CONTENT = `{ "$schema": "/schemas/page.json#", "type": "page", "title": "Title", "body": "Body", "aside": "Aside", "toolbar": "Toolbar" }`; const scopes = { 'none': ``, 'body': `{ "type": "page", "body": SCHEMA_PLACEHOLDER }`, 'form': `{ "type": "page", "body": { "title": "", "type": "form", "autoFocus": false, "api": "/api/mock/saveForm?waitSeconds=1", "mode": "horizontal", "body": SCHEMA_PLACEHOLDER, "submitText": null, "actions": [] } }`, 'form2': `{ "type": "page", "body": { "title": "", "type": "form", "autoFocus": false, "api": "/api/mock/saveForm?waitSeconds=1", "mode": "horizontal", "body": SCHEMA_PLACEHOLDER, "submitText": null, "actions": [] } }`, 'form-item': `{ "type": "page", "body": { "title": "", "type": "form", "mode": "horizontal", "autoFocus": false, "body": [ SCHEMA_PLACEHOLDER ], "submitText": null, "actions": [] } }`, 'form-item2': `{ "type": "page", "body": { "title": "", "type": "form", "mode": "horizontal", "autoFocus": false, "body": [ SCHEMA_PLACEHOLDER ], "submitText": null, "actions": [] } }` }; export default class PlayGround extends React.Component { state = null; startX = 0; oldContents = ''; frameTemplate; iframeRef; static defaultProps = { vertical: false }; constructor(props) { super(props); this.iframeRef = React.createRef(); const {history} = props; 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 }; 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); this.env = { 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 }); }, jumpTo: (to, action) => { to = normalizeLink(to); if (action && action.actionType === 'url') { action.blank === true ? window.open(to) : (window.location.href = to); 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); } 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); config.headers['Content-Type'] = 'application/json'; } // 支持返回各种报错信息 config.validateStatus = function () { return true; }; let response = await axios(config); response = await attachmentAdpator(response, __, api); 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; }, isCancel: value => axios.isCancel(value), notify: (type, msg, conf) => toast[type] ? toast[type](msg, conf) : console.warn('[Notify]', type, msg), alert, confirm, 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' }; } }; }); } }; 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} }, '*' ); } } componentDidUpdate(preProps) { const props = this.props; if (preProps.code !== props.code) { const schema = this.buildSchema(props.code || DEFAULT_CONTENT, props); this.setState({ schema: schema, schemaCode: JSON.stringify(schema, null, 2) }); } } componentDidMount() { this.props.setAsideFolded && this.props.setAsideFolded(true); } componentWillUnmount() { this.props.setAsideFolded && this.props.setAsideFolded(false); window.removeEventListener('message', this.watchIframeReady, false); } buildSchema(schemaContent, props = this.props) { const query = props.location.query; try { const scope = props.scope; if (scope && scopes[scope]) { schemaContent = scopes[scope].replace( 'SCHEMA_PLACEHOLDER', schemaContent ); } schemaContent = schemaContent.replace(/('|")raw:/g, '$1'); // 去掉 raw const json = JSON5.parse(schemaContent); return json; } catch (e) { console.error(this.formatMessage(e.message, schemaContent)); } 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 }。`; } return message; } renderPreview() { const schema = this.state.schema; const props = { ...this.schemaProps, theme: this.props.theme, locale: this.props.locale, affixHeader: false, affixFooter: false }; if (this.props.viewMode === 'mobile' && !isMobile()) { return ( ); } return render(schema, props, this.env); } handleChange(value) { this.setState({ schemaCode: value }); try { const schema = JSON5.parse(value); this.setState( { schema }, () => { this.updateIframe(); } ); } catch (e) { //ignore } } 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 }); } 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 // }); // }; renderEditor() { const {theme} = this.props; return ( ); } render() { const {vertical, mini, height, theme, classPrefix} = this.props; if (mini) { return (
编辑代码
编辑代码(支持编辑实时预览)
{this.renderEditor()}
{this.renderPreview()}
); } else if (vertical) { return (
{this.renderPreview()}
{this.renderEditor()}
); } return (
{this.renderPreview()}
{this.renderEditor()}
); } }