UNPKG

@inceptiongt/json-schema-editor-visual-new

Version:
740 lines (676 loc) 23.5 kB
import React, { Component, PureComponent } from 'react'; import { Dropdown, Menu, Row, Col, Form, Select, Checkbox, Button, Icon, Input, Modal, message, Tooltip, InputNumber } from 'antd'; import FieldInput from './FieldInput' const FormItem = Form.Item; const Option = Select.Option; const { TextArea } = Input; import './schemaJson.css'; import _ from 'underscore'; import { connect } from 'react-redux'; import PropTypes from 'prop-types'; import { JSONPATH_JOIN_CHAR, SCHEMA_TYPE } from '../../utils.js'; const InputGroup = Input.Group; import LocaleProvider from '../LocalProvider/index.js'; import utils from '../../utils'; import MockSelect from '../MockSelect/index.js'; const mapping = (name, data, showEdit, showAdv) => { switch (data.type) { case 'array': return <SchemaArray prefix={name} data={data} showEdit={showEdit} showAdv={showAdv} />; break; case 'object': let nameArray = [].concat(name, 'properties'); return <SchemaObject prefix={nameArray} data={data} showEdit={showEdit} showAdv={showAdv} />; break; default: return null; } }; class SchemaArray extends PureComponent { constructor(props, context) { super(props); this._tagPaddingLeftStyle = {}; this.Model = context.Model.schema; } componentWillMount() { const { prefix } = this.props; let length = prefix.filter(name => name != 'properties').length; this.__tagPaddingLeftStyle = { paddingLeft: `${20 * (length + 1)}px` }; } getPrefix() { return [].concat(this.props.prefix, 'items'); } // 修改数据类型 handleChangeType = value => { let prefix = this.getPrefix(); let key = [].concat(prefix, 'type'); this.Model.changeTypeAction({ key, value }); }; // 修改备注信息 handleChangeDesc = e => { let prefix = this.getPrefix(); let key = [].concat(prefix, `description`); let value = e.target.value; this.Model.changeValueAction({ key, value }); }; // 修改mock信息 handleChangeMock = e => { let prefix = this.getPrefix(); let key = [].concat(prefix, `mock`); let value = e ? { mock: e } : ''; this.Model.changeValueAction({ key, value }); }; handleChangeTitle = e =>{ let prefix = this.getPrefix(); let key = [].concat(prefix, `title`); let value = e.target.value; this.Model.changeValueAction({ key, value }); } handleChangeDefault = (e, type) =>{ let prefix = this.getPrefix(); let key = [].concat(prefix, `default`); let value; switch(type){ case 'boolean': value = e === 'true' ? true : false break; case 'number': value = e; break; default: value = e.target.value; } this.Model.changeValueAction({ key, value }); } // 增加子节点 handleAddChildField = () => { let prefix = this.getPrefix(); let keyArr = [].concat(prefix, 'properties'); this.Model.addChildFieldAction({ key: keyArr }); this.Model.setOpenValueAction({ key: keyArr, value: true }); }; handleClickIcon = () => { let prefix = this.getPrefix(); // 数据存储在 properties.name.properties下 let keyArr = [].concat(prefix, 'properties'); this.Model.setOpenValueAction({ key: keyArr }); }; handleShowEdit = (name, type) => { let prefix = this.getPrefix(); this.props.showEdit(prefix, name, this.props.data.items[name], type); }; handleShowAdv = () => { this.props.showAdv(this.getPrefix(), this.props.data.items); }; renderDefault = (items) => { let rst switch(items.type) { case 'object': case 'array': rst = ( <Col span={this.context.isMock ? 4 : 5} className="col-item col-item-mock"> <Input // addonAfter={<Icon type="edit" onClick={() => this.handleShowEdit('default')} />} placeholder={LocaleProvider('default')} value={JSON.stringify(items.default)} // onChange={this.handleChangeDefault} disabled /> </Col> ); break; case 'integer': case 'number': rst = ( <Col span={this.context.isMock ? 4 : 5} className="col-item col-item-mock"> <InputNumber // addonAfter={<Icon type="edit" onClick={() => this.handleShowEdit('default')} />} placeholder={LocaleProvider('default')} value={items.default} onChange={e => this.handleChangeDefault(e, 'number')} style={{width: '100%'}} /> </Col> ); break; case 'boolean': let data = items let value = _.isUndefined(data.default) ? '' : data.default ? 'true' : 'false'; rst = ( <Col span={this.context.isMock ? 4 : 5} className="col-item col-item-mock"> {/* <InputNumber // addonAfter={<Icon type="edit" onClick={() => this.handleShowEdit('default')} />} placeholder={LocaleProvider('default')} value={items.default} onChange={this.handleChangeDefault} /> */} <Select value={value} placeholder={LocaleProvider('default')} // onChange={e => // changeOtherValue( // e === 'true' ? true : false, // 'default', // data, // context.changeCustomValue // ) // } onChange={e => this.handleChangeDefault(e, 'boolean')} // style={{ width: 200 }} style={{width: '100%'}} > <Option value={true}>true</Option> <Option value={false}>false</Option> </Select> </Col> ); break; default: rst = ( <Col span={this.context.isMock ? 4 : 5} className="col-item col-item-mock"> <Input // addonAfter={<Icon type="edit" onClick={() => this.handleShowEdit('default')} />} placeholder={LocaleProvider('default')} value={items.default} onChange={this.handleChangeDefault} /> </Col> ); } return rst } render() { const { data, prefix, showEdit, showAdv } = this.props; const items = data.items; let prefixArray = [].concat(prefix, 'items'); let prefixArrayStr = [].concat(prefixArray, 'properties').join(JSONPATH_JOIN_CHAR); let showIcon = this.context.getOpenValue([prefixArrayStr]); return ( !_.isUndefined(data.items) && ( <div className="array-type"> <Row className="array-item-type" type="flex" justify="space-around" align="middle"> <Col span={8} className="col-item name-item col-item-name" style={this.__tagPaddingLeftStyle} > <Row type="flex" justify="space-around" align="middle"> <Col span={2} className="down-style-col"> {items.type === 'object' ? ( <span className="down-style" onClick={this.handleClickIcon}> {showIcon ? ( <Icon className="icon-object" type="caret-down" /> ) : ( <Icon className="icon-object" type="caret-right" /> )} </span> ) : null} </Col> <Col span={22}> <Input addonAfter={<Checkbox disabled />} disabled value="Items" /> </Col> </Row> </Col> <Col span={3} className="col-item col-item-type"> <Select name="itemtype" className="type-select-style" onChange={this.handleChangeType} value={items.type} > {SCHEMA_TYPE.map((item, index) => { return ( <Option value={item} key={index}> {item} </Option> ); })} </Select> </Col> {this.context.isMock && ( <Col span={3} className="col-item col-item-mock"> <MockSelect schema={items} showEdit={() => this.handleShowEdit('mock', items.type)} onChange={this.handleChangeMock} /> </Col> )} {/* title列 */} {/* <Col span={this.context.isMock ? 4 : 5} className="col-item col-item-mock"> <Input addonAfter={<Icon type="edit" onClick={() => this.handleShowEdit('title')} />} placeholder={LocaleProvider('title')} value={items.title} onChange={this.handleChangeTitle} /> </Col> */} {/* default列 */} {this.renderDefault(items)} <Col span={this.context.isMock ? 4 : 5} className="col-item col-item-desc"> <Input addonAfter={<Icon type="edit" onClick={() => this.handleShowEdit('description')} />} placeholder={LocaleProvider('description')} value={items.description} onChange={this.handleChangeDesc} /> </Col> <Col span={this.context.isMock ? 2: 3} className="col-item col-item-setting"> <span className="adv-set" onClick={this.handleShowAdv}> <Tooltip placement="top" title={LocaleProvider('adv_setting')}> <Icon type="setting" /> </Tooltip> </span> {items.type === 'object' ? ( <span onClick={this.handleAddChildField}> <Tooltip placement="top" title={LocaleProvider('add_child_node')}> <Icon type="plus" className="plus" /> </Tooltip> </span> ) : null} </Col> </Row> <div className="option-formStyle">{mapping(prefixArray, items, showEdit, showAdv)}</div> </div> ) ); } } SchemaArray.contextTypes = { getOpenValue: PropTypes.func, Model: PropTypes.object, isMock: PropTypes.bool }; class SchemaItem extends PureComponent { constructor(props, context) { super(props); this._tagPaddingLeftStyle = {}; // this.num = 0 this.Model = context.Model.schema; } componentWillMount() { const { prefix } = this.props; let length = prefix.filter(name => name != 'properties').length; this.__tagPaddingLeftStyle = { paddingLeft: `${20 * (length + 1)}px` }; } getPrefix() { return [].concat(this.props.prefix, this.props.name); } // 修改节点字段名 handleChangeName = e => { const { data, prefix, name } = this.props; let value = e.target.value; if (data.properties[value] && typeof data.properties[value] === 'object') { return message.error(`The field "${value}" already exists.`); } this.Model.changeNameAction({ value, prefix, name }); }; // 修改备注信息 handleChangeDesc = e => { let prefix = this.getPrefix(); let key = [].concat(prefix, 'description'); let value = e.target.value; this.Model.changeValueAction({ key, value }); }; // 修改mock 信息 handleChangeMock = e => { let prefix = this.getPrefix(); let key = [].concat(prefix, `mock`); let value = e ? { mock: e } : ''; this.Model.changeValueAction({ key, value }); }; handleChangeTitle = e => { let prefix = this.getPrefix(); let key = [].concat(prefix, `title`); let value = e.target.value; this.Model.changeValueAction({ key, value }); } handleChangeDefault = (e, type) =>{ debugger let prefix = this.getPrefix(); let key = [].concat(prefix, `default`); let value; switch(type){ case 'boolean': value = e === 'true' ? true : false break; case 'number': value = e; break; default: value = e.target.value; } this.Model.changeValueAction({ key, value }); } // 修改数据类型 handleChangeType = e => { let prefix = this.getPrefix(); let key = [].concat(prefix, 'type'); this.Model.changeTypeAction({ key, value: e }); }; // 删除节点 handleDeleteItem = () => { const { prefix, name } = this.props; let nameArray = this.getPrefix(); this.Model.deleteItemAction({ key: nameArray }); this.Model.enableRequireAction({ prefix, name, required: false }); }; /* 展示备注编辑弹窗 editorName: 弹窗名称 ['description', 'mock'] type: 如果当前字段是object || array showEdit 不可用 */ handleShowEdit = (editorName, type) => { const { data, name, showEdit } = this.props; showEdit(this.getPrefix(), editorName, data.properties[name][editorName], type); }; // 展示高级设置弹窗 handleShowAdv = () => { const { data, name, showAdv } = this.props; showAdv(this.getPrefix(), data.properties[name]); }; // 增加子节点 handleAddField = () => { const { prefix, name } = this.props; this.Model.addFieldAction({ prefix, name }); }; // 控制三角形按钮 handleClickIcon = () => { let prefix = this.getPrefix(); // 数据存储在 properties.xxx.properties 下 let keyArr = [].concat(prefix, 'properties'); this.Model.setOpenValueAction({ key: keyArr }); }; // 修改是否必须 handleEnableRequire = e => { const { prefix, name } = this.props; let required = e.target.checked; // this.enableRequire(this.props.prefix, this.props.name, e.target.checked); this.Model.enableRequireAction({ prefix, name, required }); }; renderDefault = (items) => { let rst switch(items.type) { case 'object': case 'array': rst = ( <Col span={this.context.isMock ? 4 : 5} className="col-item col-item-mock"> <Input // addonAfter={<Icon type="edit" onClick={() => this.handleShowEdit('default')} />} placeholder={LocaleProvider('default')} value={JSON.stringify(items.default)} // onChange={this.handleChangeDefault} disabled /> </Col> ); break; case 'integer': case 'number': rst = ( <Col span={this.context.isMock ? 4 : 5} className="col-item col-item-mock"> <InputNumber // addonAfter={<Icon type="edit" onClick={() => this.handleShowEdit('default')} />} placeholder={LocaleProvider('default')} value={items.default} onChange={e => this.handleChangeDefault(e, 'number')} style={{width: '100%'}} /> </Col> ); break; case 'boolean': let data = items let value = _.isUndefined(data.default) ? '' : data.default ? 'true' : 'false'; rst = ( <Col span={this.context.isMock ? 4 : 5} className="col-item col-item-mock"> {/* <InputNumber // addonAfter={<Icon type="edit" onClick={() => this.handleShowEdit('default')} />} placeholder={LocaleProvider('default')} value={items.default} onChange={this.handleChangeDefault} /> */} <Select value={value} placeholder={LocaleProvider('default')} // onChange={e => // changeOtherValue( // e === 'true' ? true : false, // 'default', // data, // context.changeCustomValue // ) // } onChange={e => this.handleChangeDefault(e, 'boolean')} // style={{ width: 200 }} style={{width: '100%'}} > <Option value="true">true</Option> <Option value="false">false</Option> </Select> </Col> ); break; default: rst = ( <Col span={this.context.isMock ? 4 : 5} className="col-item col-item-mock"> <Input // addonAfter={<Icon type="edit" onClick={() => this.handleShowEdit('default')} />} placeholder={LocaleProvider('default')} value={items.default} onChange={this.handleChangeDefault} /> </Col> ); } return rst } render() { let { name, data, prefix, showEdit, showAdv } = this.props; let value = data.properties[name]; let prefixArray = [].concat(prefix, name); let prefixStr = prefix.join(JSONPATH_JOIN_CHAR); let prefixArrayStr = [].concat(prefixArray, 'properties').join(JSONPATH_JOIN_CHAR); let show = this.context.getOpenValue([prefixStr]); let showIcon = this.context.getOpenValue([prefixArrayStr]); return show ? ( <div> <Row type="flex" justify="space-around" align="middle"> <Col span={8} className="col-item name-item col-item-name" style={this.__tagPaddingLeftStyle} > <Row type="flex" justify="space-around" align="middle"> <Col span={2} className="down-style-col"> {value.type === 'object' ? ( <span className="down-style" onClick={this.handleClickIcon}> {showIcon ? ( <Icon className="icon-object" type="caret-down" /> ) : ( <Icon className="icon-object" type="caret-right" /> )} </span> ) : null} </Col> <Col span={22}> <FieldInput addonAfter={ <Tooltip placement="top" title={LocaleProvider('required')}> <Checkbox onChange={this.handleEnableRequire} checked={ _.isUndefined(data.required) ? false : data.required.indexOf(name) != -1 } /> </Tooltip> } onChange={this.handleChangeName} value={name} /> </Col> </Row> </Col> <Col span={3} className="col-item col-item-type"> <Select className="type-select-style" onChange={this.handleChangeType} value={value.type} > {SCHEMA_TYPE.map((item, index) => { return ( <Option value={item} key={index}> {item} </Option> ); })} </Select> </Col> {this.context.isMock && ( <Col span={3} className="col-item col-item-mock"> {/* <Input addonAfter={ <Icon type="edit" onClick={() => this.handleShowEdit('mock', value.type)} /> } placeholder={LocaleProvider('mock')} value={value.mock ? value.mock.mock : ''} onChange={this.handleChangeMock} disabled={value.type === 'object' || value.type === 'array'} /> */} <MockSelect schema={value} showEdit={() => this.handleShowEdit('mock', value.type)} onChange={this.handleChangeMock} /> </Col> )} {/* <Col span={this.context.isMock ? 4 : 5} className="col-item col-item-mock"> <Input addonAfter={<Icon type="edit" onClick={() => this.handleShowEdit('title')} />} placeholder={LocaleProvider('title')} value={value.title} onChange={this.handleChangeTitle} /> </Col> */} {this.renderDefault(value)} <Col span={this.context.isMock ? 4 : 5} className="col-item col-item-desc"> <Input addonAfter={<Icon type="edit" onClick={() => this.handleShowEdit('description')} />} placeholder={LocaleProvider('description')} value={value.description} onChange={this.handleChangeDesc} /> </Col> <Col span={this.context.isMock ? 2: 3} className="col-item col-item-setting"> <span className="adv-set" onClick={this.handleShowAdv}> <Tooltip placement="top" title={LocaleProvider('adv_setting')}> <Icon type="setting" /> </Tooltip> </span> <span className="delete-item" onClick={this.handleDeleteItem}> <Icon type="close" className="close" /> </span> {value.type === 'object' ? ( <DropPlus prefix={prefix} name={name} /> ) : ( <span onClick={this.handleAddField}> <Tooltip placement="top" title={LocaleProvider('add_sibling_node')}> <Icon type="plus" className="plus" /> </Tooltip> </span> )} </Col> </Row> <div className="option-formStyle">{mapping(prefixArray, value, showEdit, showAdv)}</div> </div> ) : null; } } SchemaItem.contextTypes = { getOpenValue: PropTypes.func, Model: PropTypes.object, isMock: PropTypes.bool }; class SchemaObjectComponent extends Component { shouldComponentUpdate(nextProps) { if ( _.isEqual(nextProps.data, this.props.data) && _.isEqual(nextProps.prefix, this.props.prefix) && _.isEqual(nextProps.open, this.props.open) ) { return false; } return true; } render() { const { data, prefix, showEdit, showAdv } = this.props; return ( <div className="object-style"> {Object.keys(data.properties).map((name, index) => ( <SchemaItem key={index} data={this.props.data} name={name} prefix={prefix} showEdit={showEdit} showAdv={showAdv} /> ))} </div> ); } } const SchemaObject = connect(state => ({ open: state.schema.open }))(SchemaObjectComponent); const DropPlus = (props, context) => { const { prefix, name, add } = props; const Model = context.Model.schema; const menu = ( <Menu> <Menu.Item> <span onClick={() => Model.addFieldAction({ prefix, name })}> {LocaleProvider('sibling_node')} </span> </Menu.Item> <Menu.Item> <span onClick={() => { Model.setOpenValueAction({ key: [].concat(prefix, name, 'properties'), value: true }); Model.addChildFieldAction({ key: [].concat(prefix, name, 'properties') }); }} > {LocaleProvider('child_node')} </span> </Menu.Item> </Menu> ); return ( <Tooltip placement="top" title={LocaleProvider('add_node')}> <Dropdown overlay={menu}> <Icon type="plus" className="plus" /> </Dropdown> </Tooltip> ); }; DropPlus.contextTypes = { Model: PropTypes.object }; const SchemaJson = props => { const item = mapping([], props.data, props.showEdit, props.showAdv); return <div className="schema-content">{item}</div>; }; export default SchemaJson;