From a14992174b65b1241e7bb82561c7efdfd6589606 Mon Sep 17 00:00:00 2001 From: "jianfang.rjf" Date: Mon, 30 Mar 2020 16:26:43 +0800 Subject: [PATCH] feat: add color-setter --- .../src/setters/color-setter/index.scss | 35 ++ .../src/setters/color-setter/index.tsx | 99 +++ .../src/setters/expression-setter/index.scss | 130 ++++ .../src/setters/expression-setter/index.tsx | 377 ++++++++++++ .../expression-setter/locale/snippets.js | 242 ++++++++ .../setters/expression-setter/locale/utils.js | 21 + .../setters/expression-setter/locale/zh-CN.js | 36 ++ .../src/setters/json-setter/index.scss | 83 +++ .../src/setters/json-setter/index.tsx | 573 ++++++++++++++++++ .../setters/json-setter/locale/snippets.js | 242 ++++++++ .../src/setters/json-setter/locale/utils.js | 21 + .../src/setters/json-setter/locale/zh-CN.js | 36 ++ .../src/setters/mixin-setter/index.scss | 130 ++++ .../src/setters/mixin-setter/index.tsx | 302 +++++++++ .../setters/mixin-setter/locale/snippets.js | 242 ++++++++ .../src/setters/mixin-setter/locale/utils.js | 21 + .../src/setters/mixin-setter/locale/zh-CN.js | 36 ++ 17 files changed, 2626 insertions(+) create mode 100644 packages/plugin-settings-pane/src/setters/color-setter/index.scss create mode 100644 packages/plugin-settings-pane/src/setters/color-setter/index.tsx create mode 100644 packages/plugin-settings-pane/src/setters/expression-setter/index.scss create mode 100644 packages/plugin-settings-pane/src/setters/expression-setter/index.tsx create mode 100644 packages/plugin-settings-pane/src/setters/expression-setter/locale/snippets.js create mode 100644 packages/plugin-settings-pane/src/setters/expression-setter/locale/utils.js create mode 100644 packages/plugin-settings-pane/src/setters/expression-setter/locale/zh-CN.js create mode 100644 packages/plugin-settings-pane/src/setters/json-setter/index.scss create mode 100644 packages/plugin-settings-pane/src/setters/json-setter/index.tsx create mode 100644 packages/plugin-settings-pane/src/setters/json-setter/locale/snippets.js create mode 100644 packages/plugin-settings-pane/src/setters/json-setter/locale/utils.js create mode 100644 packages/plugin-settings-pane/src/setters/json-setter/locale/zh-CN.js create mode 100644 packages/plugin-settings-pane/src/setters/mixin-setter/index.scss create mode 100644 packages/plugin-settings-pane/src/setters/mixin-setter/index.tsx create mode 100644 packages/plugin-settings-pane/src/setters/mixin-setter/locale/snippets.js create mode 100644 packages/plugin-settings-pane/src/setters/mixin-setter/locale/utils.js create mode 100644 packages/plugin-settings-pane/src/setters/mixin-setter/locale/zh-CN.js diff --git a/packages/plugin-settings-pane/src/setters/color-setter/index.scss b/packages/plugin-settings-pane/src/setters/color-setter/index.scss new file mode 100644 index 000000000..0f7a1c767 --- /dev/null +++ b/packages/plugin-settings-pane/src/setters/color-setter/index.scss @@ -0,0 +1,35 @@ +// color-setter +.lowcode-color-box { + margin-right: -5px; + padding: 3px 0 3px 3px; + width: 26px; + height: 26px; + display: inline-block; + div { + width: 20px; + height: 20px; + border: 1px solid #ddd; + } +} +.next-balloon-normal.lowcode-color-content { + padding: 0; + background: #ffffff; + border-radius: 0; + border: 1px solid #e5e5e5; + box-shadow: 0 2px 8px 0 rgba(0, 0, 0, 0.1); + &:after { + display: none; + } + .sketch-picker { + border-radius: 0 !important; + border: none !important; + box-shadow: none !important; + .flexbox-fix { + input { + width: 100% !important; + min-width: 30px; + text-align: center; + } + } + } +} \ No newline at end of file diff --git a/packages/plugin-settings-pane/src/setters/color-setter/index.tsx b/packages/plugin-settings-pane/src/setters/color-setter/index.tsx new file mode 100644 index 000000000..aebc21aad --- /dev/null +++ b/packages/plugin-settings-pane/src/setters/color-setter/index.tsx @@ -0,0 +1,99 @@ +import React, { PureComponent } from 'react'; +import PropTypes from 'prop-types'; +import { SketchPicker } from 'react-color'; +import { Input, Balloon } from '@alife/next'; +import './index.scss'; + +interface Color { + rgb: any; + onChange: function; +} + +export interface PluginProps { + value: string; + onChange: any; +} + +export default class ColorPickerView extends PureComponent { + static display = 'ColorPicker'; + static propTypes = { + onChange: PropTypes.func, + value: PropTypes.string + }; + static defaultProps = { + onChange: () => {}, + value: '' + }; + constructor(props: Readonly<{value: string; defaultValue: string}>) { + super(props); + this.state = { + value: props.value || props.defaultValue + }; + } + static getDerivedStateFromProps(props: { value: string; }, state: { preValue: string; }) { + if (props.value != state.preValue) { + return { + preValue: props.value, + value: props.value + }; + } + return null; + } + onChangeComplete = (color: Color): void => { + let value; + if (color.rgb.a < 1) { + let rgb = color.rgb; + let rgba = [rgb.r, rgb.g, rgb.b, rgb.a]; + value = `rgba(${rgba.join(',')})`; + } else { + value = color.hex; + } + this.setState({ + value + }); + this.props.onChange && this.props.onChange(value); + } + onInputChange = (value: string): void => { + if (/^[0-9a-zA-Z]{6}$/.test(value)) value = '#' + value; + this.setState({ + value + }); + this.props.onChange && this.props.onChange(value); + } + render(): React.ReactNode { + const { value, onChange, ...restProps } = this.props; + let boxStyle = { + backgroundColor: this.state.value + }; + let triggerNode = ( +
+
+
+ ); + let InnerBeforeNode = ( + + + + ); + return ( + + ); + } +} \ No newline at end of file diff --git a/packages/plugin-settings-pane/src/setters/expression-setter/index.scss b/packages/plugin-settings-pane/src/setters/expression-setter/index.scss new file mode 100644 index 000000000..19287099a --- /dev/null +++ b/packages/plugin-settings-pane/src/setters/expression-setter/index.scss @@ -0,0 +1,130 @@ +// mixin +.lowcode-setter-mixin > * { + vertical-align: middle; +} +.lowcode-setter-mixin { + width: 86%; +} +.lowcode-setter-mixin .next-input { + width: 100%; +} +.lowcode-setter-mixin .next-select-trigger { + width: 100%; +} +// json-setter +// :global { + .nrs-monaco-form { + .next-form-item:last-child { + margin: 0 !important; + } + } + .monaco-editor-wrap { + .luna-monaco-button .next-icon-first { + height: 26px; + } + .monaco_fullscreen_icon { + position: absolute; + line-height: 1; + z-index: 7; + color: #ddd; + &:hover { + color: #fff; + } + } + .btns-eare { + text-align: left; + line-height: initial; + margin-top: 5px; + // button{ + // margin-right: 10px; + // } + } + &.monaco-nofullscreen { + position: relative !important; + .monaco_fullscreen_icon { + position: absolute; + top: 5px; + right: 5px; + line-height: 1; + z-index: 7; + i:before { + font-size: 16px; + } + } + } + &.monaco-fullscreen { + position: fixed !important; + height: 100% !important; + width: 100% !important; + border: 0; + margin: 0; + top: 0; + bottom: 0; + left: 0; + right: 0; + z-index: 1001; + overflow: hidden; + .monaco_fullscreen_icon { + top: 10px; + right: 10px; + i:before { + font-size: 24px; + } + } + } + } + .luna-monaco-button button { + width: 100%; + } + .luna-monaco-button-dialog { + .next-dialog-body { + padding: 0; + .next-form-item { + height: 100%; + margin-bottom: 0; + .next-form-item-control, + .next-form-item-control > div { + height: 100% !important; + } + .next-form-item-help { + position: absolute; + } + } + } + } +// } +// color-setter +.lowcode-color-box { + margin-right: -5px; + padding: 3px 0 3px 3px; + width: 26px; + height: 26px; + display: inline-block; + div { + width: 20px; + height: 20px; + border: 1px solid #ddd; + } +} +.next-balloon-normal.lowcode-color-content { + padding: 0; + background: #ffffff; + border-radius: 0; + border: 1px solid #e5e5e5; + box-shadow: 0 2px 8px 0 rgba(0, 0, 0, 0.1); + &:after { + display: none; + } + .sketch-picker { + border-radius: 0 !important; + border: none !important; + box-shadow: none !important; + .flexbox-fix { + input { + width: 100% !important; + min-width: 30px; + text-align: center; + } + } + } +} \ No newline at end of file diff --git a/packages/plugin-settings-pane/src/setters/expression-setter/index.tsx b/packages/plugin-settings-pane/src/setters/expression-setter/index.tsx new file mode 100644 index 000000000..7e4ecd14b --- /dev/null +++ b/packages/plugin-settings-pane/src/setters/expression-setter/index.tsx @@ -0,0 +1,377 @@ +import React, { PureComponent } from 'react'; +import PropTypes from 'prop-types'; +import { Select, Balloon } from '@alife/next'; +import * as acorn from 'acorn'; + +import { isJSExpression, generateI18n } from './locale/utils'; +import zhCN from './locale/zh-CN'; + +const { Option, AutoComplete } = Select; +const { Tooltip } = Balloon; +const helpMap = { + this: '容器上下文对象', + 'this.state': '容器的state', + 'this.props': '容器的props', + 'this.context': '容器的context', + 'this.page': '页面上下文对象', + 'this.component': '组件上下文对象', + 'this.constants': '应用常量对象', + 'this.utils': '应用工具对象', + 'this.dataSourceMap': '容器数据源Map', + 'this.field': '表单Field对象' +} + +export default class ExpressionView extends PureComponent { + static displayName = 'Expression'; + static propTypes = { + context: PropTypes.object, + dataSource: PropTypes.array, + locale: PropTypes.string, + messages: PropTypes.object, + onChange: PropTypes.func, + placeholder: PropTypes.string, + value: PropTypes.string + }; + static defaultProps = { + context: {}, + dataSource: [], + locale: 'zh-CN', + messages: zhCN, + onChange: () => {}, + placeholder: '', + value: '' + }; + expression: React.RefObject; + i18n: any; + t: void; + $input: any; + listenerFun: (event: any) => void; + + static getInitValue(val: { value: any; match: (arg0: RegExp) => any; }) { + if (isJSExpression(val)) { + if (typeof val === 'object') { + return val.value; + } else if (typeof val === 'string') { + let arr = val.match(/^\{\{(.*?)\}\}$/); + if (arr) return arr[1]; + } + } + return val; + } + constructor(props: Readonly<{}>) { + super(props); + this.expression = React.createRef(); + this.i18n = generateI18n(props.locale, props.messages); + this.state = { + value: ExpressionView.getInitValue(props.value), + context: props.context || {}, + dataSource: props.dataSource || [] + }; + } + static getDerivedStateFromProps(props: { value: any; }, state: { preValue: any; }) { + let curValue = ExpressionView.getInitValue(props.value); + if (curValue !== state.preValue) { + return { + preValue: curValue, + value: curValue + }; + } + return null; + } + onChange(value: string, actionType: string) { + let realInputValue = value; + let realDataSource = null; + const cursorIndex = this.getInputCursorPosition(); + let nextCursorIndex: number; + //更新值 + if (actionType === 'itemClick' || actionType === 'enter') { + let curValue = this.state.value; + if (curValue) { + //如果是非.结束,则替换当前这个变量; + let preStr = curValue.substr(0, cursorIndex); + let nextStr = curValue.substr(cursorIndex); + let preArr = preStr.split('.'); + let preArrLen = preArr.length; + let tarPreStr = ''; + if (!preArr[preArrLen - 1]) { + //如果是.结束,则增加到.后面 + if (preArr[preArrLen - 2] === 'this') { + preArr = preArr.slice(0, preArrLen - 2); + preArr.push(value); + tarPreStr = preArr.join('.'); + } else { + tarPreStr = preStr + value; + } + } else { + if (preArr[preArrLen - 2] === 'this') { + preArr = preArr.slice(0, preArrLen - 2); + } else { + preArr = preArr.slice(0, preArrLen - 1); + } + preArr.push(value); + tarPreStr = preArr.join('.'); + } + realInputValue = tarPreStr + nextStr; + realDataSource = this.getDataSource(tarPreStr + '.') || []; + nextCursorIndex = tarPreStr.length; + } + } else { + let tarPreStr = value.substr(0, cursorIndex); + if (tarPreStr) { + let lastChar = tarPreStr.charAt(tarPreStr.length - 1); + if (lastChar === '.') { + realDataSource = this.getDataSource(tarPreStr) || []; + } else { + realDataSource = this.getDataSource(tarPreStr + '.'); + } + } else { + realDataSource = this.getDataSource('this.'); + } + } + //更新数据源 + let newState = { + value: realInputValue + }; + if (realDataSource !== null) newState.dataSource = realDataSource; + this.setState(newState, () => { + nextCursorIndex && this.setInputCursorPosition(nextCursorIndex); + }); + //默认加上变量表达式 + this.t && clearTimeout(this.t); + this.t = setTimeout(() => { + const { onChange } = this.props; + // realInputValue = realInputValue ? `{{${realInputValue}}}` : undefined; + onChange && onChange({ + type: 'JSExpression', + value: realInputValue + }); + }, 300); + } + + /** + * 获取AutoComplete数据源 + * @param {String} + * @return {Array} + */ + getDataSource(tempStr: string): Array { + if (tempStr === '' || /[^\w\.]$/.test(tempStr)) { + return this.getDataSource('this.') || []; + } else if (/\w\.$/.test(tempStr)) { + let currentField = this.getCurrentFiled(tempStr); + if (!currentField) return null; + let tempKeys = this.getObjectKeys(currentField.str); + tempKeys = this.getContextKeys(tempKeys); + if (!tempKeys) return null; + //给默认情况增加this + if (tempStr === 'this.') { + tempKeys = tempKeys.map((item: string) => { + return 'this.' + item; + }); + tempKeys.unshift('this'); + } + return tempKeys; + } else if (/\.$/.test(tempStr)) { + return []; + } else { + return null; + } + } + + /** + * 获取光标前的对象字符串,语法解析获取对象字符串 + * @param {String} str 模板字符串 + * @return {String} 光标前的对象字符串 + */ + getCurrentFiled(str: string | any[]) { + str += 'x'; //.后面加一个x字符,便于acorn解析 + try { + let astTree = acorn.parse(str); + let right = astTree.body[0].expression.right || astTree.body[0].expression; + if (right.type === 'MemberExpression') { + let { start, end } = right; + str = str.slice(start, end); + return { str, start, end }; + } + } catch (e) { + return null; + } + } + + /** + * 获取输入的上下文信息 + * @param {Array} + * @return {Array} + */ + getContextKeys(keys: any) { + // let context = {}; + // const { appHelper } = this.context; + // const activeKey = appHelper && appHelper.activeKey; + // if (!activeKey) return; + // const activeCtx = appHelper.schemaHelper.compCtxMap && appHelper.schemaHelper.compCtxMap[activeKey]; + // if (!activeCtx) return null; + // let __self = activeCtx; + // if (keys && keys.length > 1) { + // keys.shift(0); + // let path = '/' + keys.join('/'); + // path = path.replace(/[\[\]]/g, '/'); + // context = jsonuri.get(__self, path); + // if (context && typeof context === 'object') { + // return this.filterKey(context); + // } + // } else if (keys && keys[0] === 'this') { + // return this.filterKey(__self); + // } + // return null; + return [ + "page", + "component" + ] + } + + /*过滤key */ + filterKey(obj: any) { + let filterKeys = [ + 'reloadDataSource', + 'REACT_HOT_LOADER_RENDERED_GENERATION', + 'refs', + 'updater', + 'appHelper', + 'isReactComponent', + 'forceUpdate', + 'setState', + 'isPureReactComponent' + ]; + let result = []; + for (let key in obj) { + if (key.indexOf('_') !== 0 && filterKeys.indexOf(key) === -1) { + result.push(key); + } + } + return result; + } + + /** + * 根据输入项进行筛选 + * @param {String} + * @param {String} + * @return {Boolen} + */ + filterOption(inputValue: string, item: { value: string | any[]; }) { + const cursorIndex = this.getInputCursorPosition(); + let preStr = inputValue.substr(0, cursorIndex); + let lastKey = preStr.split('.').slice(-1); + if (!lastKey) return true; + if (item.value.indexOf(lastKey) > -1) return true; + return false; + } + + render() { + const { value, dataSource } = this.state; + const { placeholder } = this.props; + const isValObject = !!(value == '[object Object]'); + let title = isValObject + ? this.i18n('valueIllegal') + : (value || placeholder || this.i18n('jsExpression')).toString(); + const cursorIndex = this.getInputCursorPosition(); + let childNode = cursorIndex ? ( +
+ {title.substr(0, cursorIndex)} + | + {title.substr(cursorIndex)} +
+ ) : ( + title + ); + + return ( +
+ {'{{'}} + innerAfter={{'}}'}} + itemRender={({ value }) => { + return ( + + ); + }} + onChange={this.onChange.bind(this)} + filter={this.filterOption.bind(this)} + /> + ) + } + > + {childNode} + +
+ ); + } + + componentDidMount() { + this.$input = this.findInputElement(); + if (this.$input) { + this.listenerFun = event => { + let isMoveKey = !!(event.type == 'keyup' && ~[37, 38, 39, 91].indexOf(event.keyCode)); + let isMouseup = event.type == 'mouseup'; + if (isMoveKey || isMouseup) { + let dataSource = this.getDataSource(this.state.value) || []; + this.setState({ + dataSource + }); + } + }; + this.$input.addEventListener('keyup', this.listenerFun, false); + this.$input.addEventListener('mouseup', this.listenerFun, false); + } + } + componentWillUnmount() { + if (this.listenerFun && this.$input) { + this.$input.removeEventListener('keyup', this.listenerFun, false); + this.$input.removeEventListener('mouseup', this.listenerFun, false); + } + } + /** + * 获取Input输入框DOM节点 + */ + findInputElement() { + return this.expression.current.children[0].getElementsByTagName('input')[0]; + } + /** + * 获取光标位置 + * + */ + getInputCursorPosition() { + if (!this.$input) return; + return this.$input.selectionStart; + } + /* + * 字符串取得对象keys + */ + getObjectKeys(str: string) { + let keys = []; + if (str) keys = str.split('.'); + return keys.slice(0, keys.length - 1); + } + /* + * 设置input组件光标位置在闭合}前 + */ + setInputCursorPosition(idx: number) { + this.$input.setSelectionRange(idx, idx); + this.forceUpdate(); + } +} diff --git a/packages/plugin-settings-pane/src/setters/expression-setter/locale/snippets.js b/packages/plugin-settings-pane/src/setters/expression-setter/locale/snippets.js new file mode 100644 index 000000000..7c8484c4f --- /dev/null +++ b/packages/plugin-settings-pane/src/setters/expression-setter/locale/snippets.js @@ -0,0 +1,242 @@ +export default [ + { + label: 'constants', + kind: 'Class', + insertText: 'constants', + detail: '应用全局常量', + documentation: '应用范围定义的通用常量' + }, + { + label: 'utils', + kind: 'Class', + insertText: 'utils', + detail: '应用全局公共函数', + documentation: '应用范围扩展的公共函数' + }, + { + label: 'state', + kind: 'Enum', + insertText: 'state', + detail: '当前所在容器组件内部状态', + documentation: 'React Class内部状态state' + }, + { + label: 'setState', + kind: 'Function', + insertText: 'setState({\n\t$0\n})', + insertTextRules: 'InsertAsSnippet', + detail: '设置当前所在容器组件的state数据', + documentation: '原生React方法,会自动更新组件视图' + }, + { + label: 'reloadDataSource', + kind: 'Function', + insertText: 'reloadDataSource(${1:${2:namespace}, ${3:false}, ${4:callback}})', + insertTextRules: 'InsertAsSnippet', + detail: '刷新当前所在的容器组件', + documentation: '触发当前所在的容器组件,重新发送异步请求,并用最新数据更新视图' + }, + { + label: 'location', + kind: 'Class', + insertText: 'location', + detail: '路由解析对象' + }, + { + label: 'location.query', + kind: 'Value', + insertText: 'location.query.${1:xxxx}', + insertTextRules: 'InsertAsSnippet', + detail: '从路由解析对象中获取参数信息' + }, + { + label: 'history', + kind: 'Class', + insertText: 'history', + detail: '路由历史对象' + }, + { + label: 'React', + kind: 'Keyword', + insertText: 'React', + detail: 'React对象' + }, + { + label: 'ReactDOM', + kind: 'Keyword', + insertText: 'ReactDOM', + detail: 'ReactDom对象' + }, + { + label: 'ReactDOM.findDOMNode', + kind: 'Function', + insertText: 'ReactDOM.findDOMNode(${1:this.refs.xxxx})', + insertTextRules: 'InsertAsSnippet', + detail: 'ReactDom查找真实dom node' + }, + { + label: 'Dialog.alert', + kind: 'Method', + insertText: [ + 'Dialog.alert({', + "\tcontent: '${1:Alert content}',", + "\ttitle: '${2:Title}',", + '\tonOk: () => {', + '\t\t$3', + '\t}', + '})' + ].join('\n'), + insertTextRules: 'InsertAsSnippet', + detail: 'alert弹框 By Fusion' + }, + { + label: 'Dialog.confirm', + kind: 'Method', + insertText: [ + 'Dialog.confirm({', + "\tcontent: '${1:Confirm content}',", + "\ttitle: '${2:Title}',", + '\tonOk: () => {', + '\t\t$3', + '\t},', + '\tonCancel: () => {', + '\t\t$4', + '\t}', + '})' + ].join('\n'), + insertTextRules: 'InsertAsSnippet', + detail: '确认弹出框 By Fusion' + }, + { + label: 'Message.success', + kind: 'Method', + insertText: 'Message.success(${1:content})', + insertTextRules: 'InsertAsSnippet', + detail: '成功反馈提示 By Fusion' + }, + { + label: 'Message.error', + kind: 'Method', + insertText: 'Message.error(${1:content})', + insertTextRules: 'InsertAsSnippet', + detail: '错误反馈提示 By Fusion' + }, + { + label: 'Message.help', + kind: 'Method', + insertText: 'Message.help(${1:content})', + insertTextRules: 'InsertAsSnippet', + detail: '帮助反馈提示 By Fusion' + }, + { + label: 'Message.loading', + kind: 'Method', + insertText: 'Message.loading(${1:content})', + insertTextRules: 'InsertAsSnippet', + detail: 'loading反馈提示 By Fusion' + }, + { + label: 'Message.notice', + kind: 'Method', + insertText: 'Message.notice(${1:content})', + insertTextRules: 'InsertAsSnippet', + detail: '注意反馈提示 By Fusion' + }, + { + label: 'Message.waining', + kind: 'Method', + insertText: 'Message.waining(${1:content})', + insertTextRules: 'InsertAsSnippet', + detail: '警告反馈提示 By Fusion' + }, + { + label: 'Modal.confirm', + kind: 'Method', + insertText: [ + 'Modal.confirm({', + "\tcontent: '${1:Confirm content}',", + "\ttitle: '${2:Title}',", + '\tonOk: () => {', + '\t\t$3', + '\t},', + '\tonCancel: () => {', + '\t\t$4', + '\t}', + '})' + ].join('\n'), + insertTextRules: 'InsertAsSnippet', + detail: '确认弹出框 By Antd' + }, + { + label: 'Modal.info', + kind: 'Method', + insertText: [ + 'Modal.info({', + "\tcontent: '${1:Info content}',", + "\ttitle: '${2:Title}',", + '\tonOk: () => {', + '\t\t$3', + '\t},', + '\tonCancel: () => {', + '\t\t$4', + '\t}', + '})' + ].join('\n'), + insertTextRules: 'InsertAsSnippet', + detail: '信息弹出框 By Antd' + }, + { + label: 'Modal.success', + kind: 'Method', + insertText: [ + 'Modal.success({', + "\tcontent: '${1:Success content}',", + "\ttitle: '${2:Title}',", + '\tonOk: () => {', + '\t\t$3', + '\t},', + '\tonCancel: () => {', + '\t\t$4', + '\t}', + '})' + ].join('\n'), + insertTextRules: 'InsertAsSnippet', + detail: '成功弹出框 By Antd' + }, + { + label: 'Modal.error', + kind: 'Method', + insertText: [ + 'Modal.error({', + "\tcontent: '${1:Error content}',", + "\ttitle: '${2:Title}',", + '\tonOk: () => {', + '\t\t$3', + '\t},', + '\tonCancel: () => {', + '\t\t$4', + '\t}', + '})' + ].join('\n'), + insertTextRules: 'InsertAsSnippet', + detail: '错误弹出框 By Antd' + }, + { + label: 'Modal.warning', + kind: 'Method', + insertText: [ + 'Modal.warning({', + "\tcontent: '${1:Warning content}',", + "\ttitle: '${2:Title}',", + '\tonOk: () => {', + '\t\t$3', + '\t},', + '\tonCancel: () => {', + '\t\t$4', + '\t}', + '})' + ].join('\n'), + insertTextRules: 'InsertAsSnippet', + detail: '警告弹出框 By Antd' + } +]; \ No newline at end of file diff --git a/packages/plugin-settings-pane/src/setters/expression-setter/locale/utils.js b/packages/plugin-settings-pane/src/setters/expression-setter/locale/utils.js new file mode 100644 index 000000000..a02a8dd06 --- /dev/null +++ b/packages/plugin-settings-pane/src/setters/expression-setter/locale/utils.js @@ -0,0 +1,21 @@ +import IntlMessageFormat from 'intl-messageformat'; + +export const isJSExpression = (obj = '') => { + if(obj && typeof obj === 'object' && obj.type === 'JSExpression') { + return true; + } + return false; +} + +/** + * 用于构造国际化字符串处理函数 + * @param {*} locale 国际化标识,例如 zh-CN、en-US + * @param {*} messages 国际化语言包 + */ +export const generateI18n = (locale = 'zh-CN', messages = {}) => { + return function (key, values = {}) { + if (!messages || !messages[key]) return ''; + const formater = new IntlMessageFormat(messages[key], locale); + return formater.format(values); + }; +} diff --git a/packages/plugin-settings-pane/src/setters/expression-setter/locale/zh-CN.js b/packages/plugin-settings-pane/src/setters/expression-setter/locale/zh-CN.js new file mode 100644 index 000000000..951596009 --- /dev/null +++ b/packages/plugin-settings-pane/src/setters/expression-setter/locale/zh-CN.js @@ -0,0 +1,36 @@ +export default { + // function + setting: '点击设置', + edit: '编辑', + submitConfirm: '确认提交 cmd+s', + close: '关闭 esc', + fullScreen: '全屏', + cancelFullScreen: '取消全屏', + jsonIllegal: '非json格式', + functionIllegal: '非function格式', + objectIllegal: '非object格式', + circularRef: '对象中出现循环引用的对象', + formatError: '格式错误', + saved: '已保存', + // expression + valueIllegal: '值类型为对象类型,与当前组件属性设置的控件类型不匹配,请在属性“代码编辑模式”下进行编辑', + jsExpression: '请输入JS表达式', + // Mixin + input: '字符串Input', + textarea: '多行字符串Textarea', + expression: '变量控件Expression', + monacoEditor: '编辑器MonacoEditor', + numberPicker: '数字NumberPicker', + bool: '布尔Switch', + datePicker: '日期选择DatePicker', + select: '下拉选择Select', + radio: '单项选择RadioGroup', + date: '日期选择DatePicker', + dateYear: '年选择DatePicker', + dateMonth: '月选择DatePicker', + dateRange: '日期区间选择DatePicker', + list: '数组List', + object: '对象ObjectButton', + reactNode: '节点类型ReactNode', + typeError: 'Minix组件属性Types配置错误,存在不支持类型[{type}],请检查组件属性配置', +}; \ No newline at end of file diff --git a/packages/plugin-settings-pane/src/setters/json-setter/index.scss b/packages/plugin-settings-pane/src/setters/json-setter/index.scss new file mode 100644 index 000000000..39a4e5300 --- /dev/null +++ b/packages/plugin-settings-pane/src/setters/json-setter/index.scss @@ -0,0 +1,83 @@ + +// json-setter +// :global { + .nrs-monaco-form { + .next-form-item:last-child { + margin: 0 !important; + } + } + .monaco-editor-wrap { + .luna-monaco-button .next-icon-first { + height: 26px; + } + .monaco_fullscreen_icon { + position: absolute; + line-height: 1; + z-index: 7; + color: #ddd; + &:hover { + color: #fff; + } + } + .btns-eare { + text-align: left; + line-height: initial; + margin-top: 5px; + // button{ + // margin-right: 10px; + // } + } + &.monaco-nofullscreen { + position: relative !important; + .monaco_fullscreen_icon { + position: absolute; + top: 5px; + right: 5px; + line-height: 1; + z-index: 7; + i:before { + font-size: 16px; + } + } + } + &.monaco-fullscreen { + position: fixed !important; + height: 100% !important; + width: 100% !important; + border: 0; + margin: 0; + top: 0; + bottom: 0; + left: 0; + right: 0; + z-index: 1001; + overflow: hidden; + .monaco_fullscreen_icon { + top: 10px; + right: 10px; + i:before { + font-size: 24px; + } + } + } + } + .luna-monaco-button button { + width: 100%; + } + .luna-monaco-button-dialog { + .next-dialog-body { + padding: 0; + .next-form-item { + height: 100%; + margin-bottom: 0; + .next-form-item-control, + .next-form-item-control > div { + height: 100% !important; + } + .next-form-item-help { + position: absolute; + } + } + } + } +// } \ No newline at end of file diff --git a/packages/plugin-settings-pane/src/setters/json-setter/index.tsx b/packages/plugin-settings-pane/src/setters/json-setter/index.tsx new file mode 100644 index 000000000..d699bd373 --- /dev/null +++ b/packages/plugin-settings-pane/src/setters/json-setter/index.tsx @@ -0,0 +1,573 @@ +import React, { PureComponent } from 'react'; +import PropTypes from 'prop-types'; +import { js_beautify, css_beautify } from 'js-beautify'; +import MonacoEditor from 'react-monaco-editor'; +import classNames from 'classnames'; + +import { Icon, Message } from '@alife/next'; +import ObjectButton from '@ali/iceluna-comp-object-button'; +import FormItem from '@ali/iceluna-comp-form/lib/item'; +import { serialize, jsonuri, generateI18n } from '@ali/iceluna-sdk/lib/utils'; +import localeConfig from '@ali/iceluna-sdk/lib/hoc/localeConfig'; + +import Snippets from './locale/snippets'; +import zhCN from './locale/zh-CN'; +import './index.scss'; + +let registerApiAndSnippetStatus = false; //判断注册api机制 + +window.bt = js_beautify; +class MonacoEditorView extends PureComponent { + static displayName = 'MonacoEditor'; + render() { + const { type, ...restProps } = this.props; + let Node = type == 'button' ? MonacoEditorButtonView : MonacoEditorDefaultView; + return Object.assign(this, apis)} />; + } +} + +localeConfig('MonacoEditor', MonacoEditorView); + +//monaco编辑器存在3种主题:vs、vs-dark、hc-black +class MonacoEditorDefaultView extends PureComponent { + static displayName = 'MonacoEditorDefault'; + static propTypes = { + locale: PropTypes.string, + messages: PropTypes.object + }; + static defaultProps = { + locale: 'zh-CN', + messages: zhCN, + width: '100%', + height: '300px', + language: 'javascript', + autoFocus: false, //自动获得焦点 + autoSubmit: true, //自动提交 + placeholder: '', //默认占位内容 + btnText: '提交', + btnSize: 'small', + rules: [], //校验规则 + options: { + readOnly: false, + automaticLayout: true, + folding: true, //默认开启折叠代码功能 + lineNumbers: 'on', + wordWrap: 'off', + formatOnPaste: true, + fontSize: 12, + tabSize: 2, + scrollBeyondLastLine: false, + fixedOverflowWidgets: false, + snippetSuggestions: 'top', + minimap: { + enabled: true + }, + scrollbar: { + vertical: 'hidden', + horizontal: 'hidden', + verticalScrollbarSize: 0 + } + } + }; + strValue: string; + i18n: any; + editorRef: React.RefObject; + options: any; + fullScreenOptions: any; + position: any; + editor: any; + editorNode: unknown; + editorParentNode: any; + constructor(props: Readonly<{}>) { + super(props); + this.strValue = ''; + this.i18n = generateI18n(props.locale, props.messages); + this.editorRef = React.createRef(); + this.options = Object.assign({}, MonacoEditorDefaultView.defaultProps.options, props.options); + this.fullScreenOptions = { + ...this.options, + lineNumbers: 'on', + folding: true, + scrollBeyondLastLine: true, + minimap: { + enabled: true + } + }; + this.state = { + isFullScreen: false + }; + this.onChange = this.onChange.bind(this); + this.onSubmit = this.onSubmit.bind(this); + this.fullScreen = this.fullScreen.bind(this); + this.format = this.format.bind(this); + } + + componentDidUpdate() { + //如果是全屏操作,获得焦点,光标保留在原来位置; + if (this.position) { + this.editor.focus(); + this.editor.setPosition(this.position); + delete this.position; + } + } + componentDidMount() { + this.editorNode = this.editorRef.current; //记录当前dom节点; + this.editorParentNode = this.editorNode.parentNode; //记录父节点; + //自动获得焦点, 格式化需要时间 + if (this.props.autoFocus) { + setTimeout(() => { + this.editor.setPosition({ + column: 4, + lineNumber: 2 + }); + this.editor.focus(); + }, 100); + } + //快捷键编码 + let CtrlCmd = 2048; + let KEY_S = 49; + let Shift = 1024; + let KEY_F = 36; + let KEY_B = 32; + let Escape = 9; + + this.editor.addCommand(CtrlCmd | KEY_S, () => { + this.onSubmit(); //保存快捷键 + }); + this.editor.addCommand(CtrlCmd | Shift | KEY_F, () => { + this.fullScreen(); //全屏快捷键 + }); + this.editor.addCommand(CtrlCmd | KEY_B, () => { + this.format(); //美化快捷键 + }); + this.editor.addCommand(Escape, () => { + this.props.onEscape && this.props.onEscape(); + }); + //注册api + this.editor.submit = this.onSubmit; + this.editor.format = this.format; + this.editor.fullScreen = this.fullScreen; + this.editor.toJson = this.toJson; + this.editor.toObject = this.toObject; + this.editor.toFunction = this.toFunction; + //针对object情况,改写setValue和getValue api + if (this.props.language === 'object') { + let getValue = this.editor.getValue; + let setValue = this.editor.setValue; + this.editor.getValue = () => { + return getValue.call(this.editor).substring(this.valuePrefix.length); + }; + this.editor.setValue = value => { + return setValue.call(this.editor, [this.valuePrefix + value]); + }; + } + } + + render() { + const { + value, + placeholder, + style, + className, + width, + height, + language, + theme, + editorWillMount, + editorDidMount, + registerApi + } = this.props; + + const { isFullScreen } = this.state; + this.valuePrefix = ''; //值前缀 + if (language === 'object') this.valuePrefix = 'export default '; + if (!this.isFullScreenAction) { + //将值转换成目标值 + let nowValue = this.valueHandler(value || placeholder, language); + let curValue = this.valueHandler(this.strValue, language); + if (nowValue !== curValue) this.strValue = nowValue; + if (language === 'object') this.strValue = this.strValue || placeholder || '{\n\t\n}'; //设置初始化值 + if (language === 'json' && this.strValue === '{}') this.strValue = '{\n\t\n}'; + } + this.isFullScreenAction = false; + //真实高亮语言 + let tarLanguage = language; + if (language === 'object' || language === 'function') { + tarLanguage = 'javascript'; + } + let classes = classNames('monaco-editor-wrap', { + ['monaco-fullscreen']: !!isFullScreen, + ['monaco-nofullscreen']: !isFullScreen + }); + let tarStyle = Object.assign({ minHeight: 60, width, height }, style); + return ( +
+
+ { + this.editor = editor; + registerApi({ editor }); + this.registerApiAndSnippet(monaco); + editorDidMount && editorDidMount.call(this, arguments); + }} + /> + + + +
+
+ ); + } + + //值变化 + onChange(curValue) { + if (curValue === this.valuePrefix + this.strValue) return; + const { onAfterChange, language, autoSubmit, onChange } = this.props; + this.strValue = curValue; //记录当前格式 + if (this.ct) clearTimeout(this.ct); + this.ct = setTimeout(() => { + this.position = this.editor.getPosition(); + let ret = this.resultHandler(curValue, language); + if (autoSubmit) onChange && onChange(ret.value); + onAfterChange && onAfterChange(ret.value, ret.error, this.editor); + }, 300); + } + + //提交动作 + onSubmit() { + const { onSubmit, onChange, language } = this.props; + let curValue = this.editor.getValue(); + let ret = this.resultHandler(curValue, language); + if (!ret.error) onChange && onChange(ret.value); + onSubmit && onSubmit(ret.value, ret.error, this.editor); + } + + //值类型转换处理 + valueHandler(value, language) { + let tarValue = value || ''; + if (language === 'json') { + if (value && typeof value === 'object') { + tarValue = JSON.stringify(value, null, 2); + } else if (value && typeof value === 'string') { + try { + let ret = this.toJson(value); + if (!ret.error) tarValue = JSON.stringify(ret.value, null, 2); + } catch (err) {} + } + } else if (language === 'function') { + if (typeof value === 'function') { + tarValue = value.toString(); + } + if (tarValue && typeof tarValue === 'string') { + tarValue = js_beautify(tarValue, { indent_size: 2, indent_empty_lines: true }); + } + } else if (language === 'object') { + //先转成对象,在进行序列化和格式化; + value = value || {}; + if (value && typeof value === 'object') { + try { + tarValue = serialize(value, { unsafe: true }); + tarValue = js_beautify(tarValue, { indent_size: 2, indent_empty_lines: true }); + } catch (err) {} + } else if (typeof value === 'string') { + try { + let ret = this.resultHandler(value, 'object'); + tarValue = ret.error ? ret.value : serialize(ret.value, { unsafe: true }); + tarValue = js_beautify(tarValue, { indent_size: 2, indent_empty_lines: true }); + } catch (err) {} + } + } + return tarValue; + } + + //结果处理 + resultHandler(value, language) { + let ret = { value }; + if (language === 'json') { + ret = this.toJson(value); + } else if (language === 'object') { + ret = this.toObject(value); + } else if (language === 'function') { + ret = this.toFunction(value); + } + return ret; + } + + //设置全屏时的动作 + fullScreen() { + if (!this.editorRef) return; + //还原到原来位置; + this.position = this.editor.getPosition(); + if (this.state.isFullScreen) { + if (this.editorParentNode) { + if (this.editorParentNode.firstChild) { + this.editorParentNode.insertBefore(this.editorNode, this.editorParentNode.firstChild); + } else { + this.editorParentNode.appendChild(this.editorNode); + } + } + } else { + document.body.appendChild(this.editorNode); + } + let nextFs = !this.state.isFullScreen; + this.isFullScreenAction = true; //记录是全屏幕操作 + this.setState( + { + isFullScreen: nextFs + }, + () => { + this.editor.updateOptions(nextFs ? this.fullScreenOptions : this.options); + } + ); + } + + //美化代码 + format() { + if (!this.editor) return; + if (/^\$_obj?\{.*?\}$/m.test(this.editor.getValue())) return; + if (this.props.language === 'json' || this.props.language === 'object' || this.props.language === 'function') { + let tarValue = js_beautify(this.editor.getValue(), { indent_size: 2 }); + this.editor.setValue(tarValue); + } else if (this.props.language === 'less' || this.props.language === 'css' || this.props.language === 'scss') { + let tarValue = css_beautify(this.editor.getValue(), { indent_size: 2 }); + this.editor.setValue(tarValue); + } else { + this.editor.getAction('editor.action.formatDocument').run(); + } + } + + //校验是否是json + toJson(value) { + try { + let obj = new Function(`'use strict'; return ${value.replace(/[\r\n\t]/g, '')}`)(); + if (typeof obj === 'object' && obj) { + let tarValue = new Function(`'use strict'; return ${value}`)(); + return { value: JSON.parse(JSON.stringify(tarValue)) }; + } + return { error: this.i18n('jsonIllegal'), value }; + } catch (err) { + return { error: err, value }; + } + } + + //校验是否为object对象 + toObject(value) { + try { + let obj = new Function(`'use strict';return ${value}`)(); + if (obj && typeof obj === 'object') { + if (jsonuri.isCircular(obj)) return { error: this.i18n('circularRef'), value }; + return { value: obj }; + } else { + return { error: this.i18n('objectIllegal'), value }; + } + } catch (err) { + return { error: err, value }; + } + } + + //校验是否为function + toFunction(value) { + try { + let fun = new Function(`'use strict';return ${value}`)(); + if (fun && typeof fun === 'function') { + return { value: fun }; + } else { + return { error: this.i18n('functionIllegal'), value }; + } + } catch (err) { + return { error: err, value }; + } + } + + //注册api和代码片段 + registerApiAndSnippet(monaco) { + if (registerApiAndSnippetStatus) return; + registerApiAndSnippetStatus = true; + //注册this.提示的方法; + let thisSuggestions = []; + Snippets.map(item => { + if (!item.label || !item.kind || !item.insertText) return; + let tarItem = Object.assign(item, { + label: item.label, + kind: monaco.languages.CompletionItemKind[item.kind], + insertText: item.insertText + }); + if (item.insertTextRules) + tarItem.insertTextRules = monaco.languages.CompletionItemInsertTextRule[item.insertTextRules]; + thisSuggestions.push(tarItem); + }); + monaco.languages.registerCompletionItemProvider('javascript', { + provideCompletionItems: (model, position) => { + let textUntilPosition = model.getValueInRange({ + startLineNumber: position.lineNumber, + startColumn: 1, + endLineNumber: position.lineNumber, + endColumn: position.column + }); + let match = textUntilPosition.match(/(^this\.)|(\sthis\.)/); + let suggestions = match ? thisSuggestions : []; + return { suggestions: suggestions }; + }, + triggerCharacters: ['.'] + }); + } +} +const prefix = 'data:text/javascript;charset=utf-8,'; +const baseUrl = 'https://g.alicdn.com/iceluna/iceluna-vendor/0.0.1/'; +window.MonacoEnvironment = { + getWorkerUrl: function(label: string) { + if (label === 'json') { + return `${prefix}${encodeURIComponent(` + importScripts('${baseUrl}json.worker.js');`)}`; + } + if (['css', 'less', 'scss'].includes(label)) { + return `${prefix}${encodeURIComponent(` + importScripts('${baseUrl}css.worker.js');`)}`; + } + if (label === 'html') { + return `${prefix}${encodeURIComponent(` + importScripts('${baseUrl}html.worker.js');`)}`; + } + if (['typescript', 'javascript'].includes(label)) { + return `${prefix}${encodeURIComponent(` + importScripts('${baseUrl}typescript.worker.js');`)}`; + } + return `${prefix}${encodeURIComponent(` + importScripts('${baseUrl}editor.worker.js');`)}`; + } +}; + +export default class MonacoEditorButtonView extends PureComponent { + static displayName = 'MonacoEditorButton'; + static propTypes = { + locale: PropTypes.string, + messages: PropTypes.object + }; + static defaultProps = { + locale: 'zh-CN', + messages: zhCN + }; + i18n: any; + objectButtonRef: React.RefObject; + constructor(props: Readonly<{}>) { + super(props); + this.i18n = generateI18n(props.locale, props.messages); + this.objectButtonRef = React.createRef(); + // 兼容代码,待去除 + window.__ctx.appHelper.constants = window.__ctx.appHelper.constants || {}; + } + afterHandler(value: { nrs_temp_field: any; }) { + if (!value) return; + return value.nrs_temp_field; + } + beforeHandler(value: any) { + if (!value) return; + return { nrs_temp_field: value }; + } + message(type: string, title: any, dom: Element | null) { + Message.show({ + type, + title, + duration: 1000, + align: 'cc cc', + overlayProps: { + target: dom + } + }); + } + componentDidMount() { + const { registerApi } = this.props; + let objectButtonThis = this.objectButtonRef; + registerApi && + registerApi({ + show: objectButtonThis.showModal, + hide: objectButtonThis.hideModal, + submit: objectButtonThis.submitHandler, + setValues: objectButtonThis.setValues + }); + } + render() { + const self = this; + const { locale, messages, value, onChange, field, ...restProps } = this.props; + const { id } = field; + let tarRestProps = { ...restProps }; + tarRestProps.autoSubmit = true; + tarRestProps.autoFocus = true; + let tarOnSubmit = tarRestProps.onSubmit; + //确保monaco快捷键保存,能出发最外层的保存 + tarRestProps.onSubmit = (value, error) => { + let msgDom = document.querySelector('.object-button-overlay .next-dialog-body'); + if (error) return this.message('error', this.i18n('formatError'), msgDom); + this.objectButtonRef && + this.objectButtonRef.current && + this.objectButtonRef.current.submitHandler(() => { + this.message('success', this.i18n('saved'), msgDom); + }); + }; + let tarObjProps = { }; + tarObjProps.className = 'luna-monaco-button'; + if (tarRestProps['data-meta']) { + delete tarRestProps['data-meta']; + tarObjProps['data-meta'] = 'Field'; + } + tarObjProps.id = id; + tarObjProps.value = value || ''; + tarObjProps.onChange = onChange; + let tarRule = []; + //判断,如果是json,function, object等类型,自动追加校验规则; + if (tarRestProps.language && ['json', 'function', 'object'].includes(tarRestProps.language)) { + if (['json', 'object'].includes(tarRestProps.language)) { + tarRule.push({ + validator: function(value: any, callback: (arg0: undefined) => void) { + if (typeof value !== 'object') { + callback(self.i18n('formatError')); + } else { + callback(); + } + } + }); + } else { + tarRule.push({ + validator: function(value: any, callback: (arg0: undefined) => void) { + if (typeof value !== 'function') { + callback(self.i18n('formatError')); + } else { + callback(); + } + } + }); + } + } + return ( +
+ + + Object.assign(this, apis)} /> + + +
+ ); + } +} \ No newline at end of file diff --git a/packages/plugin-settings-pane/src/setters/json-setter/locale/snippets.js b/packages/plugin-settings-pane/src/setters/json-setter/locale/snippets.js new file mode 100644 index 000000000..7c8484c4f --- /dev/null +++ b/packages/plugin-settings-pane/src/setters/json-setter/locale/snippets.js @@ -0,0 +1,242 @@ +export default [ + { + label: 'constants', + kind: 'Class', + insertText: 'constants', + detail: '应用全局常量', + documentation: '应用范围定义的通用常量' + }, + { + label: 'utils', + kind: 'Class', + insertText: 'utils', + detail: '应用全局公共函数', + documentation: '应用范围扩展的公共函数' + }, + { + label: 'state', + kind: 'Enum', + insertText: 'state', + detail: '当前所在容器组件内部状态', + documentation: 'React Class内部状态state' + }, + { + label: 'setState', + kind: 'Function', + insertText: 'setState({\n\t$0\n})', + insertTextRules: 'InsertAsSnippet', + detail: '设置当前所在容器组件的state数据', + documentation: '原生React方法,会自动更新组件视图' + }, + { + label: 'reloadDataSource', + kind: 'Function', + insertText: 'reloadDataSource(${1:${2:namespace}, ${3:false}, ${4:callback}})', + insertTextRules: 'InsertAsSnippet', + detail: '刷新当前所在的容器组件', + documentation: '触发当前所在的容器组件,重新发送异步请求,并用最新数据更新视图' + }, + { + label: 'location', + kind: 'Class', + insertText: 'location', + detail: '路由解析对象' + }, + { + label: 'location.query', + kind: 'Value', + insertText: 'location.query.${1:xxxx}', + insertTextRules: 'InsertAsSnippet', + detail: '从路由解析对象中获取参数信息' + }, + { + label: 'history', + kind: 'Class', + insertText: 'history', + detail: '路由历史对象' + }, + { + label: 'React', + kind: 'Keyword', + insertText: 'React', + detail: 'React对象' + }, + { + label: 'ReactDOM', + kind: 'Keyword', + insertText: 'ReactDOM', + detail: 'ReactDom对象' + }, + { + label: 'ReactDOM.findDOMNode', + kind: 'Function', + insertText: 'ReactDOM.findDOMNode(${1:this.refs.xxxx})', + insertTextRules: 'InsertAsSnippet', + detail: 'ReactDom查找真实dom node' + }, + { + label: 'Dialog.alert', + kind: 'Method', + insertText: [ + 'Dialog.alert({', + "\tcontent: '${1:Alert content}',", + "\ttitle: '${2:Title}',", + '\tonOk: () => {', + '\t\t$3', + '\t}', + '})' + ].join('\n'), + insertTextRules: 'InsertAsSnippet', + detail: 'alert弹框 By Fusion' + }, + { + label: 'Dialog.confirm', + kind: 'Method', + insertText: [ + 'Dialog.confirm({', + "\tcontent: '${1:Confirm content}',", + "\ttitle: '${2:Title}',", + '\tonOk: () => {', + '\t\t$3', + '\t},', + '\tonCancel: () => {', + '\t\t$4', + '\t}', + '})' + ].join('\n'), + insertTextRules: 'InsertAsSnippet', + detail: '确认弹出框 By Fusion' + }, + { + label: 'Message.success', + kind: 'Method', + insertText: 'Message.success(${1:content})', + insertTextRules: 'InsertAsSnippet', + detail: '成功反馈提示 By Fusion' + }, + { + label: 'Message.error', + kind: 'Method', + insertText: 'Message.error(${1:content})', + insertTextRules: 'InsertAsSnippet', + detail: '错误反馈提示 By Fusion' + }, + { + label: 'Message.help', + kind: 'Method', + insertText: 'Message.help(${1:content})', + insertTextRules: 'InsertAsSnippet', + detail: '帮助反馈提示 By Fusion' + }, + { + label: 'Message.loading', + kind: 'Method', + insertText: 'Message.loading(${1:content})', + insertTextRules: 'InsertAsSnippet', + detail: 'loading反馈提示 By Fusion' + }, + { + label: 'Message.notice', + kind: 'Method', + insertText: 'Message.notice(${1:content})', + insertTextRules: 'InsertAsSnippet', + detail: '注意反馈提示 By Fusion' + }, + { + label: 'Message.waining', + kind: 'Method', + insertText: 'Message.waining(${1:content})', + insertTextRules: 'InsertAsSnippet', + detail: '警告反馈提示 By Fusion' + }, + { + label: 'Modal.confirm', + kind: 'Method', + insertText: [ + 'Modal.confirm({', + "\tcontent: '${1:Confirm content}',", + "\ttitle: '${2:Title}',", + '\tonOk: () => {', + '\t\t$3', + '\t},', + '\tonCancel: () => {', + '\t\t$4', + '\t}', + '})' + ].join('\n'), + insertTextRules: 'InsertAsSnippet', + detail: '确认弹出框 By Antd' + }, + { + label: 'Modal.info', + kind: 'Method', + insertText: [ + 'Modal.info({', + "\tcontent: '${1:Info content}',", + "\ttitle: '${2:Title}',", + '\tonOk: () => {', + '\t\t$3', + '\t},', + '\tonCancel: () => {', + '\t\t$4', + '\t}', + '})' + ].join('\n'), + insertTextRules: 'InsertAsSnippet', + detail: '信息弹出框 By Antd' + }, + { + label: 'Modal.success', + kind: 'Method', + insertText: [ + 'Modal.success({', + "\tcontent: '${1:Success content}',", + "\ttitle: '${2:Title}',", + '\tonOk: () => {', + '\t\t$3', + '\t},', + '\tonCancel: () => {', + '\t\t$4', + '\t}', + '})' + ].join('\n'), + insertTextRules: 'InsertAsSnippet', + detail: '成功弹出框 By Antd' + }, + { + label: 'Modal.error', + kind: 'Method', + insertText: [ + 'Modal.error({', + "\tcontent: '${1:Error content}',", + "\ttitle: '${2:Title}',", + '\tonOk: () => {', + '\t\t$3', + '\t},', + '\tonCancel: () => {', + '\t\t$4', + '\t}', + '})' + ].join('\n'), + insertTextRules: 'InsertAsSnippet', + detail: '错误弹出框 By Antd' + }, + { + label: 'Modal.warning', + kind: 'Method', + insertText: [ + 'Modal.warning({', + "\tcontent: '${1:Warning content}',", + "\ttitle: '${2:Title}',", + '\tonOk: () => {', + '\t\t$3', + '\t},', + '\tonCancel: () => {', + '\t\t$4', + '\t}', + '})' + ].join('\n'), + insertTextRules: 'InsertAsSnippet', + detail: '警告弹出框 By Antd' + } +]; \ No newline at end of file diff --git a/packages/plugin-settings-pane/src/setters/json-setter/locale/utils.js b/packages/plugin-settings-pane/src/setters/json-setter/locale/utils.js new file mode 100644 index 000000000..a02a8dd06 --- /dev/null +++ b/packages/plugin-settings-pane/src/setters/json-setter/locale/utils.js @@ -0,0 +1,21 @@ +import IntlMessageFormat from 'intl-messageformat'; + +export const isJSExpression = (obj = '') => { + if(obj && typeof obj === 'object' && obj.type === 'JSExpression') { + return true; + } + return false; +} + +/** + * 用于构造国际化字符串处理函数 + * @param {*} locale 国际化标识,例如 zh-CN、en-US + * @param {*} messages 国际化语言包 + */ +export const generateI18n = (locale = 'zh-CN', messages = {}) => { + return function (key, values = {}) { + if (!messages || !messages[key]) return ''; + const formater = new IntlMessageFormat(messages[key], locale); + return formater.format(values); + }; +} diff --git a/packages/plugin-settings-pane/src/setters/json-setter/locale/zh-CN.js b/packages/plugin-settings-pane/src/setters/json-setter/locale/zh-CN.js new file mode 100644 index 000000000..951596009 --- /dev/null +++ b/packages/plugin-settings-pane/src/setters/json-setter/locale/zh-CN.js @@ -0,0 +1,36 @@ +export default { + // function + setting: '点击设置', + edit: '编辑', + submitConfirm: '确认提交 cmd+s', + close: '关闭 esc', + fullScreen: '全屏', + cancelFullScreen: '取消全屏', + jsonIllegal: '非json格式', + functionIllegal: '非function格式', + objectIllegal: '非object格式', + circularRef: '对象中出现循环引用的对象', + formatError: '格式错误', + saved: '已保存', + // expression + valueIllegal: '值类型为对象类型,与当前组件属性设置的控件类型不匹配,请在属性“代码编辑模式”下进行编辑', + jsExpression: '请输入JS表达式', + // Mixin + input: '字符串Input', + textarea: '多行字符串Textarea', + expression: '变量控件Expression', + monacoEditor: '编辑器MonacoEditor', + numberPicker: '数字NumberPicker', + bool: '布尔Switch', + datePicker: '日期选择DatePicker', + select: '下拉选择Select', + radio: '单项选择RadioGroup', + date: '日期选择DatePicker', + dateYear: '年选择DatePicker', + dateMonth: '月选择DatePicker', + dateRange: '日期区间选择DatePicker', + list: '数组List', + object: '对象ObjectButton', + reactNode: '节点类型ReactNode', + typeError: 'Minix组件属性Types配置错误,存在不支持类型[{type}],请检查组件属性配置', +}; \ No newline at end of file diff --git a/packages/plugin-settings-pane/src/setters/mixin-setter/index.scss b/packages/plugin-settings-pane/src/setters/mixin-setter/index.scss new file mode 100644 index 000000000..19287099a --- /dev/null +++ b/packages/plugin-settings-pane/src/setters/mixin-setter/index.scss @@ -0,0 +1,130 @@ +// mixin +.lowcode-setter-mixin > * { + vertical-align: middle; +} +.lowcode-setter-mixin { + width: 86%; +} +.lowcode-setter-mixin .next-input { + width: 100%; +} +.lowcode-setter-mixin .next-select-trigger { + width: 100%; +} +// json-setter +// :global { + .nrs-monaco-form { + .next-form-item:last-child { + margin: 0 !important; + } + } + .monaco-editor-wrap { + .luna-monaco-button .next-icon-first { + height: 26px; + } + .monaco_fullscreen_icon { + position: absolute; + line-height: 1; + z-index: 7; + color: #ddd; + &:hover { + color: #fff; + } + } + .btns-eare { + text-align: left; + line-height: initial; + margin-top: 5px; + // button{ + // margin-right: 10px; + // } + } + &.monaco-nofullscreen { + position: relative !important; + .monaco_fullscreen_icon { + position: absolute; + top: 5px; + right: 5px; + line-height: 1; + z-index: 7; + i:before { + font-size: 16px; + } + } + } + &.monaco-fullscreen { + position: fixed !important; + height: 100% !important; + width: 100% !important; + border: 0; + margin: 0; + top: 0; + bottom: 0; + left: 0; + right: 0; + z-index: 1001; + overflow: hidden; + .monaco_fullscreen_icon { + top: 10px; + right: 10px; + i:before { + font-size: 24px; + } + } + } + } + .luna-monaco-button button { + width: 100%; + } + .luna-monaco-button-dialog { + .next-dialog-body { + padding: 0; + .next-form-item { + height: 100%; + margin-bottom: 0; + .next-form-item-control, + .next-form-item-control > div { + height: 100% !important; + } + .next-form-item-help { + position: absolute; + } + } + } + } +// } +// color-setter +.lowcode-color-box { + margin-right: -5px; + padding: 3px 0 3px 3px; + width: 26px; + height: 26px; + display: inline-block; + div { + width: 20px; + height: 20px; + border: 1px solid #ddd; + } +} +.next-balloon-normal.lowcode-color-content { + padding: 0; + background: #ffffff; + border-radius: 0; + border: 1px solid #e5e5e5; + box-shadow: 0 2px 8px 0 rgba(0, 0, 0, 0.1); + &:after { + display: none; + } + .sketch-picker { + border-radius: 0 !important; + border: none !important; + box-shadow: none !important; + .flexbox-fix { + input { + width: 100% !important; + min-width: 30px; + text-align: center; + } + } + } +} \ No newline at end of file diff --git a/packages/plugin-settings-pane/src/setters/mixin-setter/index.tsx b/packages/plugin-settings-pane/src/setters/mixin-setter/index.tsx new file mode 100644 index 000000000..47978b704 --- /dev/null +++ b/packages/plugin-settings-pane/src/setters/mixin-setter/index.tsx @@ -0,0 +1,302 @@ +import React, { PureComponent } from 'react'; +import PropTypes from 'prop-types'; +import classNames from 'classnames'; +import { Dropdown, Button, Menu, Icon, Input, NumberPicker, Switch, Select, Radio, DatePicker } from '@alifd/next'; +import MonacoEditor from '@ali/iceluna-comp-monaco-editor'; + +import { isJSExpression, generateI18n } from './locale/utils'; +import Expression from '../expression-setter'; +import zhCN from './locale/zh-CN'; +import './index.scss'; + +const { Group: RadioGroup } = Radio; +// const isJSExpression = (obj) => { +// if(typeof obj === 'object' && obj.type === 'JSExpression') { +// return true; +// } +// return false; +// } + +export default class Mixin extends PureComponent { + static displayName = 'Mixin'; + static propTypes = { + locale: PropTypes.string, + messages: PropTypes.object, + defaultType: PropTypes.string, + types: PropTypes.arrayOf(PropTypes.string), + onlyChangeType: PropTypes.bool, + inputProps: PropTypes.object, + expressionProps: PropTypes.object, + monacoEditorProps: PropTypes.object, + switchProps: PropTypes.object, + selectProps: PropTypes.object, + radioGroupProps: PropTypes.object, + }; + static defaultProps = { + locale: 'zh-CN', + messages: zhCN, + types: ['StringSetter', 'ExpressionSetter', 'NumberSetter', 'BoolSetter', 'SelectSetter', 'RadioGroupSetter'], + }; + constructor(props) { + super(props); + let type = judgeTypeHandler(props, {}); + this.i18n = generateI18n(props.locale, props.messages); + this.state = { + preType: type, + type + }; + } + static getDerivedStateFromProps(props, state) { + if ('value' in props) { + let curType = judgeTypeHandler(props, state); + if (curType !== state.preType) { + return { + type: curType + }; + } + } + return null; + } + changeType(type) { + if (typeof type === 'object' || type === this.state.type) return; + let { onlyChangeType, value, onChange } = this.props; + if (onlyChangeType) { + this.setState({ type }); + onChange && onChange(value); + } else { + let newValue = undefined; + if (this.typeMap[type]['props']) { + if (this.typeMap[type]['props']['value'] !== undefined) { + newValue = this.typeMap[type]['props']['value']; + } else if (this.typeMap[type]['props']['defaultValue'] !== undefined) { + newValue = this.typeMap[type]['props']['defaultValue']; + } + } + if (type === 'BoolSetter' && newValue === undefined) { + newValue = false; //给切换到switch默认值为false + } + this.setState({ type }); + onChange && onChange(newValue); + } + } + render() { + const { + style = {}, + className, + locale, + messages, + types = [], + defaultType, + // inputProps, + // expressionProps, + // monacoEditorProps, + // numberPickerProps, + // switchProps, + // selectProps, + // radioGroupProps, + ...restProps + } = this.props; + this.typeMap = { + StringSetter: { + label: this.i18n('input'), + component: Input, + // props: inputProps + }, + ExpressionSetter: { + label: this.i18n('expression'), + component: Expression, + // props: expressionProps + }, + // MonacoEditor: { + // label: this.i18n('monacoEditor'), + // component: MonacoEditor, + // props: monacoEditorProps + // }, + NumberSetter: { + label: this.i18n('numberPicker'), + component: NumberPicker, + }, + BoolSetter: { + label: this.i18n('bool'), + component: Switch, + }, + SelectSetter: { + label: this.i18n('select'), + component: Select, + }, + RadioGroupSetter: { + label: this.i18n('radio'), + component: RadioGroup, + }, + TextAreaSetter: { + label: this.i18n('textarea'), + component: Input.TextArea, + }, + DateSetter: { + label: this.i18n('date'), + component: DatePicker, + }, + DateYearSetter: { + label: this.i18n('dateYear'), + component: DatePicker, + }, + DateMonthSetter: { + label: this.i18n('dateMonth'), + component: DatePicker, + }, + DateRangeSetter: { + label: this.i18n('dateRange'), + component: DatePicker, + } + }; + let realTypes = []; + types.forEach( el => { + const { name, props } = el; + if (this.typeMap[name]) { + this.typeMap[name].props = props; + realTypes.push(name); + } + }) + let moreBtnNode = null; + //如果只有2种,且有变量表达式,则直接展示变量按钮 + if (realTypes.length > 1) { + let isTwoType = !!(realTypes.length === 2 && ~realTypes.indexOf('ExpressionSetter')); + let btnProps = { + size: 'small', + text: true, + style: { + position: 'absolute', + left: '100%', + top: 0, + bottom: 0, + margin: 'auto 0 auto 8px', + padding: 0, + width: 16, + height: 16, + lineHeight: '16px', + textAlign: 'center' + } + }; + if (isTwoType) { + btnProps.onClick = this.changeType.bind(this, realTypes.indexOf(this.state.type) ? realTypes[0] : realTypes[1]); + } + let triggerNode = ( + + ); + if (isTwoType) { + moreBtnNode = triggerNode; + } else { + let MenuItems = []; + realTypes.map(type => { + if (this.typeMap[type]) { + MenuItems.push({this.typeMap[type]['label']}); + } else { + console.error( + this.i18n('typeError', { + type + }) + ); + } + }); + let MenuNode = ( + + {MenuItems} + + ); + + moreBtnNode = ( + + {MenuNode} + + ); + } + } + let TargetNode = this.typeMap[this.state.type] ? this.typeMap[this.state.type]['component'] : 'div'; + let targetProps = this.typeMap[this.state.type] ? this.typeMap[this.state.type]['props'] : {}; + + // 特殊处理Switch的值 + if (['BoolSetter', 'RadioGroupSetter'].includes(this.state.type)) { + restProps.checked = this.props.checked !== undefined ? this.props.checked : this.props.value; + } + //判断如果Mixin内部有设置onChange, 则同时触发2处onChange + if (targetProps && targetProps.onChange && typeof targetProps.onChange === 'function') { + let tarOnChange = targetProps.onChange; + targetProps.onChange = function() { + tarOnChange.apply(null, arguments); + restProps.onChange && restProps.onChange.apply(null, arguments); + }; + } + let tarStyle = { position: 'relative', ...style }; + let classes = classNames(className, 'lowcode-setter-mixin'); + return ( +
+ + {moreBtnNode} +
+ ); + } +} + +// 判断值类型 +function judgeTypeHandler(props, state) { + let { defaultType, types, value } = props; + let selectProps: { dataSource: any[]; }; + let radioGroupProps: { dataSource: any[]; }; + let typeKeys: any[] = []; + types.forEach( el => { + typeKeys.push(el.name); + }) + + types.forEach((el: { name: string; props: {}; }) => { + if (el.name === 'SelectSetter') {selectProps === el.props;} + if (el.name === 'RadioGroupSetter') {radioGroupProps === el.props;} + }) + if (!defaultType || !typeKeys) return; + // 如果defaultType不在typeKeys列表中,默认返回typeKeys的第一项 + if (!typeKeys.includes(defaultType)) return typeKeys[0]; + if (isJSExpression(value)) return 'ExpressionSetter'; + if (value && typeof value === 'string') { + if (~typeKeys.indexOf('SelectSetter') && selectProps && selectProps.dataSource) { + let hasOption = selectProps.dataSource.some(item => { + if (typeof item === 'string' && item === value) return true; + if (typeof item === 'object' && item.value === value) return true; + }); + if (hasOption) return 'SelectSetter'; + } + if (~typeKeys.indexOf('RadioGroupSetter') && radioGroupProps && radioGroupProps.dataSource) { + let hasOption = radioGroupProps.dataSource.some(item => { + if (typeof item === 'object' && item.value === value) return true; + }); + if (hasOption) return 'RadioGroupSetter'; + } + if (~typeKeys.indexOf('StringSetter')) return 'StringSetter'; + } + if (typeof value === 'number') { + if (~typeKeys.indexOf('SelectSetter') && selectProps && selectProps.dataSource) { + let hasOption = selectProps.dataSource.some(item => { + if (typeof item === 'object' && item.value === value) return true; + }); + if (hasOption) return 'Select'; + } + if (~typeKeys.indexOf('RadioGroupSetter') && radioGroupProps && radioGroupProps.dataSource) { + let hasOption = radioGroupProps.dataSource.some(item => { + if (typeof item === 'object' && item.value === value) return true; + }); + if (hasOption) return 'RadioGroupSetter'; + } + if (~typeKeys.indexOf('NumberSetter')) return 'NumberSetter'; + } + if (~typeKeys.indexOf('NumberSetter') && typeof value === 'number') return 'NumberSetter'; + if (~typeKeys.indexOf('BoolSetter') && (value === false || value === true)) return 'BoolSetter'; + if (Array.isArray(value)) { + if (~typeKeys.indexOf('SelectSetter') && typeof value[0] === 'string') return 'SelectSetter'; + } + return state.type || defaultType; +} diff --git a/packages/plugin-settings-pane/src/setters/mixin-setter/locale/snippets.js b/packages/plugin-settings-pane/src/setters/mixin-setter/locale/snippets.js new file mode 100644 index 000000000..7c8484c4f --- /dev/null +++ b/packages/plugin-settings-pane/src/setters/mixin-setter/locale/snippets.js @@ -0,0 +1,242 @@ +export default [ + { + label: 'constants', + kind: 'Class', + insertText: 'constants', + detail: '应用全局常量', + documentation: '应用范围定义的通用常量' + }, + { + label: 'utils', + kind: 'Class', + insertText: 'utils', + detail: '应用全局公共函数', + documentation: '应用范围扩展的公共函数' + }, + { + label: 'state', + kind: 'Enum', + insertText: 'state', + detail: '当前所在容器组件内部状态', + documentation: 'React Class内部状态state' + }, + { + label: 'setState', + kind: 'Function', + insertText: 'setState({\n\t$0\n})', + insertTextRules: 'InsertAsSnippet', + detail: '设置当前所在容器组件的state数据', + documentation: '原生React方法,会自动更新组件视图' + }, + { + label: 'reloadDataSource', + kind: 'Function', + insertText: 'reloadDataSource(${1:${2:namespace}, ${3:false}, ${4:callback}})', + insertTextRules: 'InsertAsSnippet', + detail: '刷新当前所在的容器组件', + documentation: '触发当前所在的容器组件,重新发送异步请求,并用最新数据更新视图' + }, + { + label: 'location', + kind: 'Class', + insertText: 'location', + detail: '路由解析对象' + }, + { + label: 'location.query', + kind: 'Value', + insertText: 'location.query.${1:xxxx}', + insertTextRules: 'InsertAsSnippet', + detail: '从路由解析对象中获取参数信息' + }, + { + label: 'history', + kind: 'Class', + insertText: 'history', + detail: '路由历史对象' + }, + { + label: 'React', + kind: 'Keyword', + insertText: 'React', + detail: 'React对象' + }, + { + label: 'ReactDOM', + kind: 'Keyword', + insertText: 'ReactDOM', + detail: 'ReactDom对象' + }, + { + label: 'ReactDOM.findDOMNode', + kind: 'Function', + insertText: 'ReactDOM.findDOMNode(${1:this.refs.xxxx})', + insertTextRules: 'InsertAsSnippet', + detail: 'ReactDom查找真实dom node' + }, + { + label: 'Dialog.alert', + kind: 'Method', + insertText: [ + 'Dialog.alert({', + "\tcontent: '${1:Alert content}',", + "\ttitle: '${2:Title}',", + '\tonOk: () => {', + '\t\t$3', + '\t}', + '})' + ].join('\n'), + insertTextRules: 'InsertAsSnippet', + detail: 'alert弹框 By Fusion' + }, + { + label: 'Dialog.confirm', + kind: 'Method', + insertText: [ + 'Dialog.confirm({', + "\tcontent: '${1:Confirm content}',", + "\ttitle: '${2:Title}',", + '\tonOk: () => {', + '\t\t$3', + '\t},', + '\tonCancel: () => {', + '\t\t$4', + '\t}', + '})' + ].join('\n'), + insertTextRules: 'InsertAsSnippet', + detail: '确认弹出框 By Fusion' + }, + { + label: 'Message.success', + kind: 'Method', + insertText: 'Message.success(${1:content})', + insertTextRules: 'InsertAsSnippet', + detail: '成功反馈提示 By Fusion' + }, + { + label: 'Message.error', + kind: 'Method', + insertText: 'Message.error(${1:content})', + insertTextRules: 'InsertAsSnippet', + detail: '错误反馈提示 By Fusion' + }, + { + label: 'Message.help', + kind: 'Method', + insertText: 'Message.help(${1:content})', + insertTextRules: 'InsertAsSnippet', + detail: '帮助反馈提示 By Fusion' + }, + { + label: 'Message.loading', + kind: 'Method', + insertText: 'Message.loading(${1:content})', + insertTextRules: 'InsertAsSnippet', + detail: 'loading反馈提示 By Fusion' + }, + { + label: 'Message.notice', + kind: 'Method', + insertText: 'Message.notice(${1:content})', + insertTextRules: 'InsertAsSnippet', + detail: '注意反馈提示 By Fusion' + }, + { + label: 'Message.waining', + kind: 'Method', + insertText: 'Message.waining(${1:content})', + insertTextRules: 'InsertAsSnippet', + detail: '警告反馈提示 By Fusion' + }, + { + label: 'Modal.confirm', + kind: 'Method', + insertText: [ + 'Modal.confirm({', + "\tcontent: '${1:Confirm content}',", + "\ttitle: '${2:Title}',", + '\tonOk: () => {', + '\t\t$3', + '\t},', + '\tonCancel: () => {', + '\t\t$4', + '\t}', + '})' + ].join('\n'), + insertTextRules: 'InsertAsSnippet', + detail: '确认弹出框 By Antd' + }, + { + label: 'Modal.info', + kind: 'Method', + insertText: [ + 'Modal.info({', + "\tcontent: '${1:Info content}',", + "\ttitle: '${2:Title}',", + '\tonOk: () => {', + '\t\t$3', + '\t},', + '\tonCancel: () => {', + '\t\t$4', + '\t}', + '})' + ].join('\n'), + insertTextRules: 'InsertAsSnippet', + detail: '信息弹出框 By Antd' + }, + { + label: 'Modal.success', + kind: 'Method', + insertText: [ + 'Modal.success({', + "\tcontent: '${1:Success content}',", + "\ttitle: '${2:Title}',", + '\tonOk: () => {', + '\t\t$3', + '\t},', + '\tonCancel: () => {', + '\t\t$4', + '\t}', + '})' + ].join('\n'), + insertTextRules: 'InsertAsSnippet', + detail: '成功弹出框 By Antd' + }, + { + label: 'Modal.error', + kind: 'Method', + insertText: [ + 'Modal.error({', + "\tcontent: '${1:Error content}',", + "\ttitle: '${2:Title}',", + '\tonOk: () => {', + '\t\t$3', + '\t},', + '\tonCancel: () => {', + '\t\t$4', + '\t}', + '})' + ].join('\n'), + insertTextRules: 'InsertAsSnippet', + detail: '错误弹出框 By Antd' + }, + { + label: 'Modal.warning', + kind: 'Method', + insertText: [ + 'Modal.warning({', + "\tcontent: '${1:Warning content}',", + "\ttitle: '${2:Title}',", + '\tonOk: () => {', + '\t\t$3', + '\t},', + '\tonCancel: () => {', + '\t\t$4', + '\t}', + '})' + ].join('\n'), + insertTextRules: 'InsertAsSnippet', + detail: '警告弹出框 By Antd' + } +]; \ No newline at end of file diff --git a/packages/plugin-settings-pane/src/setters/mixin-setter/locale/utils.js b/packages/plugin-settings-pane/src/setters/mixin-setter/locale/utils.js new file mode 100644 index 000000000..a02a8dd06 --- /dev/null +++ b/packages/plugin-settings-pane/src/setters/mixin-setter/locale/utils.js @@ -0,0 +1,21 @@ +import IntlMessageFormat from 'intl-messageformat'; + +export const isJSExpression = (obj = '') => { + if(obj && typeof obj === 'object' && obj.type === 'JSExpression') { + return true; + } + return false; +} + +/** + * 用于构造国际化字符串处理函数 + * @param {*} locale 国际化标识,例如 zh-CN、en-US + * @param {*} messages 国际化语言包 + */ +export const generateI18n = (locale = 'zh-CN', messages = {}) => { + return function (key, values = {}) { + if (!messages || !messages[key]) return ''; + const formater = new IntlMessageFormat(messages[key], locale); + return formater.format(values); + }; +} diff --git a/packages/plugin-settings-pane/src/setters/mixin-setter/locale/zh-CN.js b/packages/plugin-settings-pane/src/setters/mixin-setter/locale/zh-CN.js new file mode 100644 index 000000000..951596009 --- /dev/null +++ b/packages/plugin-settings-pane/src/setters/mixin-setter/locale/zh-CN.js @@ -0,0 +1,36 @@ +export default { + // function + setting: '点击设置', + edit: '编辑', + submitConfirm: '确认提交 cmd+s', + close: '关闭 esc', + fullScreen: '全屏', + cancelFullScreen: '取消全屏', + jsonIllegal: '非json格式', + functionIllegal: '非function格式', + objectIllegal: '非object格式', + circularRef: '对象中出现循环引用的对象', + formatError: '格式错误', + saved: '已保存', + // expression + valueIllegal: '值类型为对象类型,与当前组件属性设置的控件类型不匹配,请在属性“代码编辑模式”下进行编辑', + jsExpression: '请输入JS表达式', + // Mixin + input: '字符串Input', + textarea: '多行字符串Textarea', + expression: '变量控件Expression', + monacoEditor: '编辑器MonacoEditor', + numberPicker: '数字NumberPicker', + bool: '布尔Switch', + datePicker: '日期选择DatePicker', + select: '下拉选择Select', + radio: '单项选择RadioGroup', + date: '日期选择DatePicker', + dateYear: '年选择DatePicker', + dateMonth: '月选择DatePicker', + dateRange: '日期区间选择DatePicker', + list: '数组List', + object: '对象ObjectButton', + reactNode: '节点类型ReactNode', + typeError: 'Minix组件属性Types配置错误,存在不支持类型[{type}],请检查组件属性配置', +}; \ No newline at end of file