From 582da643bedeeebadf3f5b944644955700a9afa7 Mon Sep 17 00:00:00 2001 From: kangwei Date: Thu, 30 Apr 2020 14:19:28 +0800 Subject: [PATCH] fix variable setter --- packages/demo/package.json | 1 + packages/demo/src/vision/index.ts | 21 +- packages/demo/src/vision/module.d.ts | 3 +- packages/designer/src/designer/designer.ts | 2 +- .../designer/setting/setting-prop-entry.ts | 42 ++- .../designer/src/document/document-model.ts | 3 + packages/designer/src/document/node/node.ts | 4 + .../editor-core/src/intl/ali-global-locale.ts | 89 +++---- packages/editor-core/src/intl/index.ts | 15 +- .../src/components/field/fields.tsx | 13 +- .../src/components/mixed-setter/index.tsx | 242 ++++++++++++------ .../editor-skeleton/src/icons/variable.tsx | 11 + .../editor-skeleton/src/locale/en-US.json | 5 + packages/editor-skeleton/src/locale/index.ts | 10 + .../editor-skeleton/src/locale/zh-CN.json | 5 + packages/vision-preset/src/bundle/trunk.ts | 5 +- .../src/bundle/upgrade-metadata.ts | 37 ++- packages/vision-preset/src/const.ts | 44 ---- packages/vision-preset/src/context.ts | 24 +- packages/vision-preset/src/editor.ts | 1 + packages/vision-preset/src/field.tsx | 142 +++++++++- packages/vision-preset/src/i18n-reducer.ts | 1 + packages/vision-preset/src/index.ts | 6 +- packages/vision-preset/src/pages.ts | 6 + 24 files changed, 503 insertions(+), 229 deletions(-) create mode 100644 packages/editor-skeleton/src/icons/variable.tsx create mode 100644 packages/editor-skeleton/src/locale/en-US.json create mode 100644 packages/editor-skeleton/src/locale/index.ts create mode 100644 packages/editor-skeleton/src/locale/zh-CN.json delete mode 100644 packages/vision-preset/src/const.ts diff --git a/packages/demo/package.json b/packages/demo/package.json index 6b89db2ae..763fe5012 100644 --- a/packages/demo/package.json +++ b/packages/demo/package.json @@ -28,6 +28,7 @@ "@ali/lowcode-runtime": "^0.8.13", "@ali/lowcode-setters": "^0.8.11", "@ali/lowcode-utils": "^0.8.2", + "@ali/vs-variable-setter": "^3.1.0", "@alifd/next": "^1.19.12", "@alife/theme-lowcode-dark": "^0.1.0", "@alife/theme-lowcode-light": "^0.1.0", diff --git a/packages/demo/src/vision/index.ts b/packages/demo/src/vision/index.ts index 96e545409..b216de07c 100644 --- a/packages/demo/src/vision/index.ts +++ b/packages/demo/src/vision/index.ts @@ -6,8 +6,25 @@ import EventBindDialog from '@ali/lowcode-plugin-event-bind-dialog'; import loadUrls from './loader'; import { upgradeAssetsBundle } from './upgrade-assets'; import { isCSSUrl } from '@ali/lowcode-utils'; +import { I18nSetter } from '@ali/visualengine-utils'; +import VariableSetter from '@ali/vs-variable-setter'; -const { editor, skeleton } = Engine; +const { editor, skeleton, context, HOOKS, Trunk } = Engine; + +Trunk.registerSetter('I18nSetter', { + component: I18nSetter, + // todo: add icon + title: { + type: 'i18n', + 'zh-CN': '国际化输入', + 'en-US': 'International Input' + }, + // TODO: below + // condition?: (field: any) => boolean; + // initialValue?: any | ((field: any) => any); + recommend: true, +}); +context.use(HOOKS.VE_SETTING_FIELD_VARIABLE_SETTER, VariableSetter); // demo skeleton.add({ @@ -149,7 +166,7 @@ async function loadAssets() { assets.packages.push({ library: '_prototypesStyle', package: '_prototypes-style', - urls: prototypeStyles + urls: prototypeStyles, }); } await Promise.all(tasks); diff --git a/packages/demo/src/vision/module.d.ts b/packages/demo/src/vision/module.d.ts index 3e142d5dd..21aab4290 100644 --- a/packages/demo/src/vision/module.d.ts +++ b/packages/demo/src/vision/module.d.ts @@ -1,3 +1,4 @@ declare module "@ali/visualengine"; declare module "@ali/visualengine-utils"; -declare module "@ali/ve-trunk-pane" +declare module "@ali/ve-trunk-pane"; +declare module "@ali/vs-variable-setter"; diff --git a/packages/designer/src/designer/designer.ts b/packages/designer/src/designer/designer.ts index bc0a7bf83..f5c08b888 100644 --- a/packages/designer/src/designer/designer.ts +++ b/packages/designer/src/designer/designer.ts @@ -71,7 +71,7 @@ export class Designer { this.hovering.enable = false; const { dragObject } = e; if (isDragNodeObject(dragObject)) { - if (dragObject.nodes.length === 1) { + if (dragObject.nodes.length === 1 && dragObject.nodes[0].parent) { // ensure current selecting dragObject.nodes[0].select(); } diff --git a/packages/designer/src/designer/setting/setting-prop-entry.ts b/packages/designer/src/designer/setting/setting-prop-entry.ts index 34bf07f9b..7a4913e69 100644 --- a/packages/designer/src/designer/setting/setting-prop-entry.ts +++ b/packages/designer/src/designer/setting/setting-prop-entry.ts @@ -1,5 +1,5 @@ import { obx, computed } from '@ali/lowcode-editor-core'; -import { IEditor } from '@ali/lowcode-types'; +import { IEditor, isJSExpression } from '@ali/lowcode-types'; import { uniqueId } from '@ali/lowcode-utils'; import { SettingEntry } from './setting-entry'; import { Node } from '../../document'; @@ -191,4 +191,44 @@ export class SettingPropEntry implements SettingEntry { return this.config; } */ + getVariableValue() { + const v = this.getValue(); + if (isJSExpression(v)) { + return v.value; + } + return ''; + } + setVariableValue(value: string) { + const v = this.getValue(); + this.setValue({ + type: 'JSExpression', + value, + mock: isJSExpression(v) ? v.mock : v, + }); + } + setUseVariable(flag: boolean) { + if (this.isUseVariable() === flag) { + return; + } + const v = this.getValue(); + if (isJSExpression(v)) { + this.setValue(v.mock); + } else { + this.setValue({ + type: 'JSExpression', + value: '', + mock: v, + }); + } + } + isUseVariable() { + return isJSExpression(this.getValue()); + } + getMockOrValue() { + const v = this.getValue(); + if (isJSExpression(v)) { + return v.mock; + } + return v; + } } diff --git a/packages/designer/src/document/document-model.ts b/packages/designer/src/document/document-model.ts index 6e8c37e24..eb92fb1c7 100644 --- a/packages/designer/src/document/document-model.ts +++ b/packages/designer/src/document/document-model.ts @@ -465,6 +465,9 @@ export class DocumentModel { getRoot() { return this.rootNode; } + get root() { + return this.rootNode; + } } export function isDocumentModel(obj: any): obj is DocumentModel { diff --git a/packages/designer/src/document/node/node.ts b/packages/designer/src/document/node/node.ts index a4bb4c21a..32e5205ac 100644 --- a/packages/designer/src/document/node/node.ts +++ b/packages/designer/src/document/node/node.ts @@ -618,6 +618,10 @@ export class Node { } return { container: this.parent, ref: this }; } + getAddonData() { + // TODO: + return { online: [] }; + } toString() { return this.id; } diff --git a/packages/editor-core/src/intl/ali-global-locale.ts b/packages/editor-core/src/intl/ali-global-locale.ts index 2a26727dd..0eef45403 100644 --- a/packages/editor-core/src/intl/ali-global-locale.ts +++ b/packages/editor-core/src/intl/ali-global-locale.ts @@ -1,5 +1,5 @@ import { EventEmitter } from 'events'; -import { obx } from '../utils/obx'; +import { obx, computed } from '../utils/obx'; const languageMap: { [key: string]: string } = { en: 'en-US', zh: 'zh-CN', @@ -29,8 +29,50 @@ const languageMap: { [key: string]: string } = { const LowcodeConfigKey = 'ali-lowcode-config'; class AliGlobalLocale { - @obx.ref private locale: string = ''; private emitter = new EventEmitter(); + @obx.ref private _locale?: string; + @computed get locale() { + if (this._locale != null) { + return this._locale; + } + const { g_config, navigator } = window as any; + if (hasLocalStorage(window)) { + const store = window.localStorage; + let config: any; + try { + config = JSON.parse(store.getItem(LowcodeConfigKey) || ''); + } catch (e) { + // ignore; + } + if (config?.locale) { + return (config.locale || '').replace('_', '-'); + } + } else if (g_config) { + if (g_config.locale) { + return languageMap[g_config.locale] || (g_config.locale || '').replace('_', '-'); + } + } + + let locale: string = ''; + if (navigator.language) { + locale = (navigator.language as string).replace('_', '-'); + } + + // IE10 及更低版本使用 browserLanguage + if (navigator.browserLanguage) { + const it = navigator.browserLanguage.split('-'); + locale = it[0]; + if (it[1]) { + locale += '-' + it[1].toUpperCase(); + } + } + + if (!locale) { + locale = 'zh-CN'; + } + + return locale; + } constructor() { this.emitter.setMaxListeners(0); @@ -40,7 +82,7 @@ class AliGlobalLocale { if (locale === this.locale) { return; } - this.locale = locale; + this._locale = locale; if (hasLocalStorage(window)) { const store = window.localStorage; let config: any; @@ -62,47 +104,6 @@ class AliGlobalLocale { } getLocale() { - if (this.locale) { - return this.locale; - } - - const { g_config, navigator } = window as any; - if (hasLocalStorage(window)) { - const store = window.localStorage; - let config: any; - try { - config = JSON.parse(store.getItem(LowcodeConfigKey) || ''); - } catch (e) { - // ignore; - } - if (config?.locale) { - this.locale = (config.locale || '').replace('_', '-'); - return this.locale; - } - } else if (g_config) { - if (g_config.locale) { - this.locale = languageMap[g_config.locale] || (g_config.locale || '').replace('_', '-'); - return this.locale; - } - } - - if (navigator.language) { - this.locale = (navigator.language as string).replace('_', '-'); - } - - // IE10 及更低版本使用 browserLanguage - if (navigator.browserLanguage) { - const it = navigator.browserLanguage.split('-'); - this.locale = it[0]; - if (it[1]) { - this.locale += '-' + it[1].toUpperCase(); - } - } - - if (!this.locale) { - this.locale = 'zh-CN'; - } - return this.locale; } diff --git a/packages/editor-core/src/intl/index.ts b/packages/editor-core/src/intl/index.ts index 3deb3e8b4..f7319fb00 100644 --- a/packages/editor-core/src/intl/index.ts +++ b/packages/editor-core/src/intl/index.ts @@ -98,19 +98,20 @@ export function createIntl( const locale = globalLocale.getLocale(); if (typeof instance === 'string') { if ((window as any)[instance]) { - data.messages = (window as any)[instance][locale] || {}; - } else { - const key = `${instance}_${locale.toLocaleLowerCase()}`; - data.messages = (window as any)[key] || {}; + return (window as any)[instance][locale] || {}; } - } else if (instance && typeof instance === 'object') { - data.messages = (instance as any)[locale] || {}; + const key = `${instance}_${locale.toLocaleLowerCase()}`; + return (window as any)[key] || {}; } + if (instance && typeof instance === 'object') { + return (instance as any)[locale] || {}; + } + return {}; }); function intl(key: string, params?: object): string { // TODO: tries lost language - const str = data[key]; + const str = data.value[key]; if (str == null) { return `##intl@${key}##`; diff --git a/packages/editor-skeleton/src/components/field/fields.tsx b/packages/editor-skeleton/src/components/field/fields.tsx index 59d67df2f..74a596520 100644 --- a/packages/editor-skeleton/src/components/field/fields.tsx +++ b/packages/editor-skeleton/src/components/field/fields.tsx @@ -157,17 +157,12 @@ export class EntryField extends Component { fieldProps['data-stage-target'] = stageName; } - const innerElements = [ - - {title} - , - // renderTip(tip, { propName }), - // , - ]; - return (
- {innerElements} +
+ + </div> + <Icon className="lc-field-icon" type="arrow-left" size="xs" /> </div> ); } diff --git a/packages/editor-skeleton/src/components/mixed-setter/index.tsx b/packages/editor-skeleton/src/components/mixed-setter/index.tsx index 68530e9f8..4e1ed2e2a 100644 --- a/packages/editor-skeleton/src/components/mixed-setter/index.tsx +++ b/packages/editor-skeleton/src/components/mixed-setter/index.tsx @@ -1,4 +1,4 @@ -import React, { Component, isValidElement } from 'react'; +import React, { Component, ComponentClass, ReactNode } from 'react'; import classNames from 'classnames'; import { Dropdown, Menu } from '@alifd/next'; import { @@ -9,7 +9,6 @@ import { TitleContent, isSetterConfig, isDynamicSetter, - isI18nData, } from '@ali/lowcode-types'; import { getSetter, @@ -20,13 +19,14 @@ import { createSetterContent, observer, shallowIntl, - Tip, } from '@ali/lowcode-editor-core'; import { IconConvert } from '../../icons/convert'; +import { intlNode } from '../../locale'; import './style.less'; import { SettingField } from '@ali/lowcode-designer'; +import { IconVariable } from 'editor-skeleton/src/icons/variable'; export interface SetterItem { name: string; @@ -101,6 +101,10 @@ function nomalizeSetters(setters?: Array<string | SetterConfig | CustomView | Dy }); } +interface VariableSetter extends ComponentClass { + show(params: object): void; +} + @observer export default class MixedSetter extends Component<{ field: SettingField; @@ -129,11 +133,21 @@ export default class MixedSetter extends Component<{ return firstMatched; } + // dirty fix vision variable setter logic + private hasVariableSetter = this.setters.some((item) => item.name === 'VariableSetter'); + private useSetter = (name: string) => { + const { field, onChange } = this.props; + if (name === 'VariableSetter') { + const setterComponent = getSetter('VariableSetter')?.component as any; + if (setterComponent && setterComponent.isPopup) { + setterComponent.show({ prop: field }); + return; + } + } if (name === this.used) { return; } - const { field, onChange } = this.props; const setter = this.setters.find((item) => item.name === name); this.used = name; if (setter) { @@ -163,99 +177,161 @@ export default class MixedSetter extends Component<{ this.checkIsBlockField(); } - render() { + private renderCurrentSetter(currentSetter?: SetterItem, extraProps?: object) { const { className, field, setters, onSetterChange, ...restProps } = this.props; - - const currentSetter = this.getCurrentSetter(); - const isTwoType = this.setters.length < 3; - - let setterContent: any; - const triggerTitle: any = { - tip: { - type: 'i18n', - 'zh-CN': '切换格式', - 'en-US': 'Switch Format', - }, - icon: <IconConvert size={24} />, - }; - if (currentSetter) { - const { setter, title, props } = currentSetter; - let setterProps: any = {}; - let setterType: any; - if (isDynamicSetter(setter)) { - setterType = setter.call(field, field); - } else { - setterType = setter; - } - if (props) { - setterProps = props; - if (typeof setterProps === 'function') { - setterProps = setterProps(field); - } - } - - setterContent = createSetterContent(setterType, { - ...shallowIntl(setterProps), - field, - ...restProps, - }); - if (title) { - if (typeof title !== 'object' || isI18nData(title) || isValidElement(title)) { - triggerTitle.tip = title; - } else { - triggerTitle.tip = title.tip || title.label; - } - } - } else { - // 未匹配的 null 值,显示 NullValue 空值 - // 未匹配的 其它 值,显示 InvalidValue 非法值 + if (!currentSetter) { + // TODO: use intl if (restProps.value == null) { - setterContent = <span>NullValue</span>; + return <span>NullValue</span>; } else { - setterContent = <span>InvalidValue</span>; + return <span>InvalidValue</span>; } } + const { setter, props } = currentSetter; + let setterProps: any = {}; + let setterType: any; + if (isDynamicSetter(setter)) { + setterType = setter.call(field, field); + } else { + setterType = setter; + } + if (props) { + setterProps = props; + if (typeof setterProps === 'function') { + setterProps = setterProps(field); + } + } + + return createSetterContent(setterType, { + ...shallowIntl(setterProps), + field, + ...restProps, + ...extraProps, + }); + } + + private contentsFromPolyfill(setterComponent: VariableSetter) { + const { field } = this.props; + + const n = this.setters.length; + + let setterContent: any; + let actions: any; + if (n < 3) { + const tipContent = field.isUseVariable() + ? intlNode('Binded: {expr}', { expr: field.getVariableValue() }) + : intlNode('Variable Binding'); + if (n === 1) { + // =1: 原地展示<当前绑定的值,点击调用 VariableSetter.show>,icon 高亮是否->isUseVaiable,点击 VariableSetter.show + setterContent = ( + <a + onClick={() => { + setterComponent.show({ prop: field }); + }} + > + {tipContent} + </a> + ); + } else { + // =2: 另外一个 Setter 原地展示,icon 高亮,点击弹出调用 VariableSetter.show + const otherSetter = this.setters.find((item) => item.name !== 'VariableSetter')!; + setterContent = this.renderCurrentSetter(otherSetter, { + value: field.getMockOrValue(), + }); + } + actions = ( + <Title + className={field.isUseVariable() ? 'variable-binded' : ''} + title={{ + icon: IconVariable, + tip: tipContent, + }} + onClick={() => { + setterComponent.show({ prop: field }); + }} + /> + ); + } else { + // >=3: 原地展示当前 setter<当前绑定的值,点击调用 VariableSetter.show>,icon tip 提示绑定的值,点击展示切换 Setter,点击其它 setter 直接切换,点击 Variable Setter-> VariableSetter.show + const currentSetter = field.isUseVariable() + ? this.setters.find((item) => item.name === 'VariableSetter') + : this.getCurrentSetter(); + if (currentSetter?.name === 'VariableSetter') { + setterContent = ( + <a + onClick={() => { + setterComponent.show({ prop: field }); + }} + > + {intlNode('Binded: {expr}', { expr: field.getVariableValue() })} + </a> + ); + } else { + setterContent = this.renderCurrentSetter(currentSetter); + } + actions = this.renderSwitchAction(currentSetter); + } + + return { + setterContent, + actions, + }; + } + + private renderSwitchAction(currentSetter?: SetterItem) { const usedName = currentSetter?.name || this.used; - let moreBtnNode = ( + const triggerNode = ( <Title - title={triggerTitle} + title={{ + tip: intlNode('Switch Setter'), + // FIXME: got a beautiful icon + icon: <IconConvert size={24} />, + }} className="lc-switch-trigger" - onClick={ - isTwoType - ? () => { - if (this.setters[0]?.name === usedName) { - this.useSetter(this.setters[1]?.name); - } else { - this.useSetter(this.setters[0]?.name); - } - } - : undefined - } /> ); - if (!isTwoType) { - moreBtnNode = ( - <Dropdown trigger={moreBtnNode} triggerType="click" align="tr br"> - <Menu selectMode="single" hasSelectedIcon={true} selectedKeys={usedName} onItemClick={this.useSetter}> - {this.setters - .filter((setter) => setter.list || setter.name === usedName) - .map((setter) => { - return ( - <Menu.Item key={setter.name}> - <Title title={setter.title} /> - </Menu.Item> - ); - })} - </Menu> - </Dropdown> - ); + return ( + <Dropdown trigger={triggerNode} triggerType="click" align="tr br"> + <Menu selectMode="single" hasSelectedIcon={true} selectedKeys={usedName} onItemClick={this.useSetter}> + {this.setters + .filter((setter) => setter.list || setter.name === usedName) + .map((setter) => { + return ( + <Menu.Item key={setter.name}> + <Title title={setter.title} /> + </Menu.Item> + ); + })} + </Menu> + </Dropdown> + ); + } + + render() { + const { className } = this.props; + let contents: { + setterContent: ReactNode, + actions: ReactNode, + } | undefined; + if (this.hasVariableSetter) { + // FIXME: polyfill vision variable setter logic + const setterComponent = getSetter('VariableSetter')?.component as any; + if (setterComponent && setterComponent.isPopup) { + contents = this.contentsFromPolyfill(setterComponent); + } + } + if (!contents) { + const currentSetter = this.getCurrentSetter(); + contents = { + setterContent: this.renderCurrentSetter(currentSetter), + actions: this.renderSwitchAction(currentSetter), + }; } return ( <div ref={(shell) => (this.shell = shell)} className={classNames('lc-setter-mixed', className)}> - {setterContent} - - <div className="lc-setter-actions">{moreBtnNode}</div> + {contents.setterContent} + <div className="lc-setter-actions">{contents.actions}</div> </div> ); } diff --git a/packages/editor-skeleton/src/icons/variable.tsx b/packages/editor-skeleton/src/icons/variable.tsx new file mode 100644 index 000000000..a45e5df00 --- /dev/null +++ b/packages/editor-skeleton/src/icons/variable.tsx @@ -0,0 +1,11 @@ +import { SVGIcon, IconProps } from "@ali/lowcode-utils"; + +export function IconVariable(props: IconProps) { + return ( + <SVGIcon viewBox="0 0 1024 1024" {...props}> + <path d="M596.32 263.392c18.048 6.56 27.328 26.496 20.8 44.512l-151.04 414.912a34.72 34.72 0 1 1-65.28-23.744l151.04-414.912a34.72 34.72 0 0 1 44.48-20.768zM220.64 192H273.6v55.488H233.024c-26.112 0-38.464 14.4-38.464 44.544v134.304c0 42.496-19.936 71.264-59.104 85.664 39.168 16.448 59.104 44.544 59.104 85.664v134.976c0 28.8 12.352 43.84 38.464 43.84H273.6V832H220.672c-30.24 0-53.6-10.272-70.08-29.44-15.136-17.856-22.72-42.496-22.72-72.64v-128.832c0-19.872-4.096-34.24-12.352-43.2-9.6-10.944-26.784-16.416-51.52-17.792v-56.192c24.736-1.376 41.92-7.52 51.52-17.824 8.256-9.6 12.384-24 12.384-43.168V294.784c0-30.848 7.552-55.488 22.688-73.312C167.04 201.6 190.4 192 220.672 192z m529.792 0h52.896c30.24 0 53.6 9.6 70.08 29.44 15.136 17.856 22.72 42.496 22.72 73.344v128.128c0 19.2 4.096 34.24 13.024 43.84 8.96 9.6 26.112 15.776 50.848 17.152v56.192c-24.736 1.376-41.92 6.848-51.52 17.824-8.256 8.896-12.384 23.296-12.384 43.168v128.8c0 30.176-7.552 54.816-22.688 72.64-16.48 19.2-39.84 29.472-70.08 29.472h-52.896v-55.488h40.544c25.408 0 38.464-15.104 38.464-43.84v-135.04c0-41.088 19.232-69.184 59.104-85.632-39.872-14.4-59.104-43.168-59.104-85.664V292.032c0-30.144-13.056-44.544-38.464-44.544H750.4V192z" /> + </SVGIcon> + ); +} + +IconVariable.displayName = 'Variable'; diff --git a/packages/editor-skeleton/src/locale/en-US.json b/packages/editor-skeleton/src/locale/en-US.json new file mode 100644 index 000000000..1d50bd8be --- /dev/null +++ b/packages/editor-skeleton/src/locale/en-US.json @@ -0,0 +1,5 @@ +{ + "Binded: {expr}": "Binded: {expr}", + "Variable Binding": "Variable Binding", + "Switch Setter": "Switch Setter" +} diff --git a/packages/editor-skeleton/src/locale/index.ts b/packages/editor-skeleton/src/locale/index.ts new file mode 100644 index 000000000..26507f0ef --- /dev/null +++ b/packages/editor-skeleton/src/locale/index.ts @@ -0,0 +1,10 @@ +import { createIntl } from '@ali/lowcode-editor-core'; +import en_US from './en-US.json'; +import zh_CN from './zh-CN.json'; + +const { intl, intlNode, getLocale, setLocale } = createIntl({ + 'en-US': en_US, + 'zh-CN': zh_CN, +}); + +export { intl, intlNode, getLocale, setLocale }; diff --git a/packages/editor-skeleton/src/locale/zh-CN.json b/packages/editor-skeleton/src/locale/zh-CN.json new file mode 100644 index 000000000..1bac4b1e6 --- /dev/null +++ b/packages/editor-skeleton/src/locale/zh-CN.json @@ -0,0 +1,5 @@ +{ + "Binded: {expr}": "已绑定: {expr}", + "Variable Binding": "变量绑定", + "Switch Setter": "切换设置器" +} diff --git a/packages/vision-preset/src/bundle/trunk.ts b/packages/vision-preset/src/bundle/trunk.ts index a35625282..04efc2f10 100644 --- a/packages/vision-preset/src/bundle/trunk.ts +++ b/packages/vision-preset/src/bundle/trunk.ts @@ -1,7 +1,8 @@ import { ReactElement, ComponentType } from 'react'; import { EventEmitter } from 'events'; -import { registerSetter } from '@ali/lowcode-editor-core'; +import { registerSetter, RegisteredSetter } from '@ali/lowcode-editor-core'; import Bundle from './bundle'; +import { CustomView } from '@ali/lowcode-types'; export class Trunk { private trunk: any[] = []; @@ -51,7 +52,7 @@ export class Trunk { }; } - registerSetter(type: string, setter: ReactElement | ComponentType<any>) { + registerSetter(type: string, setter: CustomView | RegisteredSetter) { console.warn('Trunk.registerSetter is deprecated'); registerSetter(type, setter); } diff --git a/packages/vision-preset/src/bundle/upgrade-metadata.ts b/packages/vision-preset/src/bundle/upgrade-metadata.ts index e64ed5556..0df3e1ad4 100644 --- a/packages/vision-preset/src/bundle/upgrade-metadata.ts +++ b/packages/vision-preset/src/bundle/upgrade-metadata.ts @@ -342,11 +342,11 @@ export function upgradePropConfig(config: OldPropConfig, addInitial: AddIntial) }, ]; if (allowTextInput !== false) { - setters.unshift('StringSetter'); + setters.unshift('I18nSetter'); // FIXME: use I18nSetter } if (supportVariable) { - setters.push('ExpressionSetter'); + setters.push('VariableSetter'); } newConfig.setter = setters.length > 1 ? setters : setters[0]; @@ -403,25 +403,24 @@ export function upgradePropConfig(config: OldPropConfig, addInitial: AddIntial) primarySetter = setter; } } + if (!primarySetter) { + primarySetter = 'I18nSetter'; + } if (supportVariable) { - if (primarySetter) { - const setters = Array.isArray(primarySetter) - ? primarySetter.concat('ExpressionSetter') - : [primarySetter, 'ExpressionSetter']; - primarySetter = { - componentName: 'MixedSetter', - props: { - setters, - onSetterChange: (field: Field, name: string) => { - if (useVariableChange) { - useVariableChange.call(field, { isUseVariable: name === 'ExpressionSetter' }); - } - }, + const setters = Array.isArray(primarySetter) + ? primarySetter.concat('VariableSetter') + : [primarySetter, 'VariableSetter']; + primarySetter = { + componentName: 'MixedSetter', + props: { + setters, + onSetterChange: (field: Field, name: string) => { + if (useVariableChange) { + useVariableChange.call(field, { isUseVariable: name === 'VariableSetter' }); + } }, - }; - } else { - primarySetter = 'ExpressionSetter'; - } + }, + }; } newConfig.setter = primarySetter; diff --git a/packages/vision-preset/src/const.ts b/packages/vision-preset/src/const.ts deleted file mode 100644 index 1fe234190..000000000 --- a/packages/vision-preset/src/const.ts +++ /dev/null @@ -1,44 +0,0 @@ -/** - * Storage the const variables - */ - - /** - * Global - */ -export const VERSION = '5.3.0'; - -/** - * schema version defined in alibaba - */ -export const ALI_SCHEMA_VERSION = '1.0.0'; - -export const VE_EVENTS = { - /** - * node props to be dynamically replaced - * @event props the new props object been replaced - */ - VE_NODE_CREATED: 've.node.created', - VE_NODE_DESTROY: 've.node.destroyed', - VE_NODE_PROPS_REPLACE: 've.node.props.replaced', - // copy / clone node - VE_OVERLAY_ACTION_CLONE_NODE: 've.overlay.cloneElement', - // remove / delete node - VE_OVERLAY_ACTION_REMOVE_NODE: 've.overlay.removeElement', - // one page successfully mount on the DOM - VE_PAGE_PAGE_READY: 've.page.pageReady', -}; - -export const VE_HOOKS = { - // a decorator function - VE_NODE_PROPS_DECORATOR: 've.leaf.props.decorator', - // a remove callback function - VE_NODE_REMOVE_HELPER: 've.outline.actions.removeHelper', - /** - * provide customization field - */ - VE_SETTING_FIELD_PROVIDER: 've.settingField.provider', - /** - * VariableSetter for variable mode of a specified prop - */ - VE_SETTING_FIELD_VARIABLE_SETTER: 've.settingField.variableSetter', -}; diff --git a/packages/vision-preset/src/context.ts b/packages/vision-preset/src/context.ts index f60258b74..39322a971 100644 --- a/packages/vision-preset/src/context.ts +++ b/packages/vision-preset/src/context.ts @@ -3,30 +3,32 @@ import { assign } from 'lodash'; import { Component, ReactElement } from 'react'; import VisualManager from './base/visualManager'; import Prototype from './bundle/prototype'; +import { VE_HOOKS } from './base/const'; +import { registerSetter } from '@ali/lowcode-editor-core'; // TODO: Env 本地引入后需要兼容方法 getDesignerLocale // import Env from './env'; -let contextInstance: VisualEngineContext; // prop is Prop object in Designer export type SetterProvider = (prop: any, componentPrototype: Prototype) => Component | ReactElement<any>; -export default class VisualEngineContext { +export class VisualEngineContext { private managerMap: { [name: string]: VisualManager } = {}; private moduleMap: { [name: string]: any } = {}; private pluginsMap: { [name: string]: any } = {}; - constructor() { - if (!contextInstance) { - contextInstance = this; - } else { - return contextInstance; - } - } - use(pluginName: string, plugin: any) { this.pluginsMap[pluginName || 'unknown'] = plugin; + if (pluginName === VE_HOOKS.VE_SETTING_FIELD_VARIABLE_SETTER) { + registerSetter('VariableSetter', { + component: plugin, + title: { type: 'i18n', 'zh-CN': '变量绑定', 'en-US': 'Variable Binding' }, + // TODO: add logic below + // condition?: (field: any) => boolean; + // initialValue?: any | ((field: any) => any); + }); + } } getPlugin(name: string) { @@ -102,3 +104,5 @@ export default class VisualEngineContext { } } } + +export default new VisualEngineContext(); diff --git a/packages/vision-preset/src/editor.ts b/packages/vision-preset/src/editor.ts index 8d1d5fc03..9c98374a9 100644 --- a/packages/vision-preset/src/editor.ts +++ b/packages/vision-preset/src/editor.ts @@ -61,6 +61,7 @@ function upgradePropsReducer(props: any) { val = val.value; } } + // todo: type: variable newProps[key] = val; }); return newProps; diff --git a/packages/vision-preset/src/field.tsx b/packages/vision-preset/src/field.tsx index 8ae36fce7..346ed36b1 100644 --- a/packages/vision-preset/src/field.tsx +++ b/packages/vision-preset/src/field.tsx @@ -1,7 +1,145 @@ -import { Component } from "react"; +import { Component, ReactNode } from 'react'; +import { + PopupField, + Field as NormalField, + EntryField, + PlainField, + createSettingFieldView, + SettingsPane, + createField, +} from '@ali/lowcode-editor-skeleton'; +import { createSetterContent } from '@ali/lowcode-editor-core'; +import { isPlainObject } from '@ali/lowcode-utils'; +import { isSetterConfig } from '@ali/lowcode-types'; +import context from './context'; +import { VE_HOOKS } from './base/const'; export class Placeholder extends Component { + render() { + console.info(this.props); + return 'rending placeholder here'; + } +} +export class SettingField extends Component<{ + prop: any; + selected?: boolean; + forceDisplay?: string; + className?: string; + children?: ReactNode; + compact?: boolean; + key?: string; + addonProps?: object; +}> { + constructor(props: any) { + super(props); + + console.info(props); + } + + render() { + const { prop, selected, addonProps } = this.props; + const display = this.props.forceDisplay || prop.getDisplay(); + + if (display === 'none') { + return null; + } + + // 标准的属性,即每一个 Field 在 VE 下都拥有的属性 + const standardProps = { + className: this.props.className, + compact: this.props.compact, + + isSupportMultiSetter: this.supportMultiSetter(), + isSupportVariable: prop.isSupportVariable(), + isUseVariable: prop.isUseVariable(), + prop, + setUseVariable: () => prop.setUseVariable(!prop.isUseVariable()), + tip: prop.getTip(), + title: prop.getTitle(), + }; + + // 部分 Field 所需要的额外 fieldProps + const extraProps = {}; + const ctx = context; + const plugin = ctx.getPlugin(VE_HOOKS.VE_SETTING_FIELD_PROVIDER); + let Field; + if (typeof plugin === 'function') { + Field = plugin(display, FIELD_TYPE_MAP, prop); + } + if (!Field) { + Field = FIELD_TYPE_MAP[display] || PlainField; + } + createField() + this._prepareProps(display, extraProps); + + if (display === 'entry') { + return <Field {...{ ...standardProps, ...extraProps }} />; + } + + let setter; + const props: any = { + prop, + selected, + }; + const fieldProps = { ...standardProps, ...extraProps }; + + if (prop.isUseVariable() && !this.variableSetter.isPopup) { + props.placeholder = '请输入表达式: ${var}'; + props.key = `${prop.getId()}-variable`; + setter = React.createElement(this.variableSetter, props); + return <Field {...fieldProps}>{setter}</Field>; + } + + // for composited prop + if (prop.getVisibleItems) { + setter = prop + .getVisibleItems() + .map((item: any) => <SettingField {...{ key: item.getId(), prop: item, selected }} />); + return <Field {...fieldProps}>{setter}</Field>; + } + + setter = createSetterContent(prop.getSetter(), { + ...addonProps, + ...props, + }); + + return <Field {...fieldProps}>{setter}</Field>; + } + + private supportMultiSetter() { + const { prop } = this.props; + const setter = prop && prop.getConfig && prop.getConfig('setter'); + return prop.isSupportVariable() || Array.isArray(setter); + } + + private _prepareProps(displayType: string, extraProps: IExtraProps): void { + const { prop } = this.props; + extraProps.propName = prop.isGroup() ? '组合属性,无属性名称' : prop.getName(); + switch (displayType) { + case 'title': + break; + case 'block': + assign(extraProps, { isGroup: prop.isGroup() }); + break; + case 'accordion': + assign(extraProps, { + headDIY: true, + isExpand: prop.isExpand(), + isGroup: prop.isGroup(), + onExpandChange: () => prop.onExpandChange(() => this.forceUpdate()), + toggleExpand: () => { + prop.toggleExpand(); + }, + }); + break; + case 'entry': + assign(extraProps, { stageName: prop.getName() }); + break; + default: + break; + } + } } const Field = { @@ -11,7 +149,7 @@ const Field = { EntryField: Placeholder, AccordionField: Placeholder, BlockField: Placeholder, - InlineField: Placeholder + InlineField: Placeholder, }; export default Field; diff --git a/packages/vision-preset/src/i18n-reducer.ts b/packages/vision-preset/src/i18n-reducer.ts index 693ee0cae..1c8511840 100644 --- a/packages/vision-preset/src/i18n-reducer.ts +++ b/packages/vision-preset/src/i18n-reducer.ts @@ -9,6 +9,7 @@ interface I18nObject { } export function i18nReducer(obj?: any): any { + console.info(obj); if (!obj) { return obj; } if (Array.isArray(obj)) { return obj.map((item) => i18nReducer(item)); diff --git a/packages/vision-preset/src/index.ts b/packages/vision-preset/src/index.ts index 51e39de0e..31185f2d2 100644 --- a/packages/vision-preset/src/index.ts +++ b/packages/vision-preset/src/index.ts @@ -5,13 +5,13 @@ import { render } from 'react-dom'; import I18nUtil from '@ali/ve-i18n-util'; import { hotkey as Hotkey } from '@ali/lowcode-editor-core'; import { createElement } from 'react'; -import { VE_EVENTS as EVENTS, VE_HOOKS as HOOKS } from './const'; +import { VE_EVENTS as EVENTS, VE_HOOKS as HOOKS } from './base/const'; import Bus from './bus'; import { skeleton } from './editor'; import { Workbench } from '@ali/lowcode-editor-skeleton'; import Panes from './panes'; import Exchange from './exchange'; -import VisualEngineContext from './context'; +import context from './context'; import VisualManager from './base/visualManager'; import Trunk from './bundle/trunk'; import Prototype from './bundle/prototype'; @@ -58,8 +58,6 @@ const modules = { Prop, }; -const context = new VisualEngineContext(); - const VisualEngine = { designer, editor, diff --git a/packages/vision-preset/src/pages.ts b/packages/vision-preset/src/pages.ts index 4cb868667..81430a4f1 100644 --- a/packages/vision-preset/src/pages.ts +++ b/packages/vision-preset/src/pages.ts @@ -49,4 +49,10 @@ const pages = Object.assign(project, { } }); +Object.defineProperty(pages, 'currentPage', { + get() { + return project.currentDocument; + } +}) + export default pages;