diff --git a/deploy-space/static/index.html b/deploy-space/static/index.html index 5b34a9a1c..1e79181e9 100644 --- a/deploy-space/static/index.html +++ b/deploy-space/static/index.html @@ -76,6 +76,7 @@ + diff --git a/packages/demo/package.json b/packages/demo/package.json index ed3092db1..ab4a4afd5 100644 --- a/packages/demo/package.json +++ b/packages/demo/package.json @@ -31,6 +31,7 @@ "@ali/ve-i18n-manage-pane": "^4.3.0", "@ali/ve-i18n-pane": "^4.0.0-beta.0", "@ali/ve-trunk-pane": "^5.1.0-beta.14", + "@ali/vs-variable-setter": "^3.1.0", "@ali/vu-legao-design-fetch-context": "^1.0.3", "@alifd/next": "^1.19.12", "@alife/theme-lowcode-dark": "^0.1.0", diff --git a/packages/demo/src/vision/index.ts b/packages/demo/src/vision/index.ts index 83b03730c..2427d10e6 100644 --- a/packages/demo/src/vision/index.ts +++ b/packages/demo/src/vision/index.ts @@ -13,8 +13,22 @@ 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' + }, + recommend: true, +}); +context.use(HOOKS.VE_SETTING_FIELD_VARIABLE_SETTER, VariableSetter); const externals = ['react', 'react-dom', 'prop-types', 'react-router', 'react-router-dom', '@ali/recore']; @@ -53,7 +67,7 @@ async function loadAssets() { assets.packages.push({ library: '_prototypesStyle', package: '_prototypes-style', - urls: prototypeStyles + urls: prototypeStyles, }); } await Promise.all(tasks); @@ -96,7 +110,7 @@ function initDemoPanes() { description: '帮助', }, }); - + skeleton.add({ area: 'topArea', type: 'Dock', @@ -310,6 +324,10 @@ function initActionPane() { async function init() { Engine.Env.setEnv('RE_VERSION', '7.2.0'); + Engine.Env.setSupportFeatures({ + subview: true, + i18nPane: true, + }); await loadAssets(); await loadSchema(); await initTrunkPane(); diff --git a/packages/demo/src/vision/module.d.ts b/packages/demo/src/vision/module.d.ts index 3e142d5dd..2217b3e06 100644 --- a/packages/demo/src/vision/module.d.ts +++ b/packages/demo/src/vision/module.d.ts @@ -1,3 +1,8 @@ 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"; +declare module "@ali/ve-datapool-pane"; +declare module "@ali/ve-i18n-manage-pane"; +declare module "@ali/ve-action-pane"; +declare module "@ali/vu-legao-design-fetch-context"; diff --git a/packages/designer/src/designer/designer.ts b/packages/designer/src/designer/designer.ts index bc0a7bf83..f5881b262 100644 --- a/packages/designer/src/designer/designer.ts +++ b/packages/designer/src/designer/designer.ts @@ -72,8 +72,12 @@ export class Designer { const { dragObject } = e; if (isDragNodeObject(dragObject)) { if (dragObject.nodes.length === 1) { - // ensure current selecting - dragObject.nodes[0].select(); + if (dragObject.nodes[0].parent) { + // ensure current selecting + dragObject.nodes[0].select(); + } else { + this.currentSelection?.clear(); + } } } else { this.currentSelection?.clear(); diff --git a/packages/designer/src/designer/setting/setting-prop-entry.ts b/packages/designer/src/designer/setting/setting-prop-entry.ts index 34bf07f9b..a231eaf2b 100644 --- a/packages/designer/src/designer/setting/setting-prop-entry.ts +++ b/packages/designer/src/designer/setting/setting-prop-entry.ts @@ -1,10 +1,11 @@ -import { obx, computed } from '@ali/lowcode-editor-core'; -import { IEditor } from '@ali/lowcode-types'; +import { obx, computed, autorun } from '@ali/lowcode-editor-core'; +import { IEditor, isJSExpression } from '@ali/lowcode-types'; import { uniqueId } from '@ali/lowcode-utils'; import { SettingEntry } from './setting-entry'; import { Node } from '../../document'; import { ComponentMeta } from '../../component-meta'; import { Designer } from '../designer'; +import { EventEmitter } from 'events'; export class SettingPropEntry implements SettingEntry { // === static properties === @@ -20,6 +21,8 @@ export class SettingPropEntry implements SettingEntry { readonly type: 'field' | 'group'; readonly id = uniqueId('entry'); + readonly emitter = new EventEmitter(); + // ==== dynamic properties ==== @obx.ref private _name: string | number; get name() { @@ -59,6 +62,14 @@ export class SettingPropEntry implements SettingEntry { this.isSingle = parent.isSingle; this.designer = parent.designer; this.top = parent.top; + + autorun(({ firstRun }) => { + const value = this.getValue(); + if (firstRun) { + return; + } + this.emitter.emit('valuechange', value); + }); } getId() { @@ -88,7 +99,7 @@ export class SettingPropEntry implements SettingEntry { const propName = this.path.join('.'); let l = this.nodes.length; while (l-- > 1) { - this.nodes[l].getProp(propName)?.remove() + this.nodes[l].getProp(propName)?.remove(); } } @@ -117,7 +128,6 @@ export class SettingPropEntry implements SettingEntry { if (setValue) { setValue(this, val); } - // TODO: emit value change } /** @@ -170,9 +180,21 @@ export class SettingPropEntry implements SettingEntry { return this.top; } - onValueChange() { - // TODO: - return () => {}; + onValueChange(func: () => any) { + this.emitter.on('valuechange', func); + + return () => { + this.emitter.removeListener('valuechange', func); + }; + } + + /** + * @deprecated + */ + valueChange() { + console.warn('valueChange deprecated'); + + this.emitter.emit('valuechange'); } getDefaultValue() { @@ -191,4 +213,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-children.ts b/packages/designer/src/document/node/node-children.ts index a8a4a364e..43701b4ba 100644 --- a/packages/designer/src/document/node/node-children.ts +++ b/packages/designer/src/document/node/node-children.ts @@ -2,9 +2,13 @@ import { obx, computed } from '@ali/lowcode-editor-core'; import { Node, ParentalNode } from './node'; import { TransformStage } from './transform-stage'; import { NodeData, isNodeSchema } from '@ali/lowcode-types'; +import { shallowEqual } from '@ali/lowcode-utils'; +import { EventEmitter } from 'events'; export class NodeChildren { @obx.val private children: Node[]; + private emitter = new EventEmitter(); + constructor(readonly owner: ParentalNode, data: NodeData | NodeData[]) { this.children = (Array.isArray(data) ? data : [data]).map(child => { return this.owner.document.createNode(child); @@ -52,6 +56,9 @@ export class NodeChildren { this.children = children; this.interalInitParent(); + if (!shallowEqual(children, originChildren)) { + this.emitter.emit('change'); + } } /** @@ -86,6 +93,7 @@ export class NodeChildren { deleted.internalSetParent(null); deleted.purge(); } + this.emitter.emit('change'); return false; } @@ -118,6 +126,8 @@ export class NodeChildren { children.splice(index, 0, node); } + this.emitter.emit('change'); + // check condition group if (node.conditionGroup) { if ( @@ -208,14 +218,7 @@ export class NodeChildren { } mergeChildren(remover: () => any, adder: (children: Node[]) => NodeData[] | null, sorter: () => any) { - /* - const children = this.children.slice(); - children.forEach(child => child.internalSetParent(null)); - - this.children = children; - this.interalInitParent(); - */ - + let changed = false; if (remover) { const willRemove = this.children.filter(remover); if (willRemove.length > 0) { @@ -226,6 +229,7 @@ export class NodeChildren { node.remove(); } }); + changed = true; } } if (adder) { @@ -236,11 +240,23 @@ export class NodeChildren { this.children.push(node); node.internalSetParent(this.owner); }); + changed = true; } } if (sorter) { this.children = this.children.sort(sorter); + changed = true; } + if (changed) { + this.emitter.emit('change'); + } + } + + onChange(fn: () => void) { + this.emitter.on('change', fn); + return () => { + this.emitter.removeListener('change', fn); + }; } private purged = false; diff --git a/packages/designer/src/document/node/node.ts b/packages/designer/src/document/node/node.ts index c7d4fe98c..9f19ce0eb 100644 --- a/packages/designer/src/document/node/node.ts +++ b/packages/designer/src/document/node/node.ts @@ -10,6 +10,7 @@ import { SlotSchema, PageSchema, ComponentSchema, + NodeStatus, } from '@ali/lowcode-types'; import { Props, EXTRA_KEY_PREFIX } from './props/props'; import { DocumentModel } from '../document-model'; @@ -483,7 +484,7 @@ export class Node { } const { props = {}, extras } = this.props.export(stage) || {}; - const _extras_: {[key: string]: any} = { + const _extras_: { [key: string]: any } = { ...extras, }; if (_extras_) { @@ -593,20 +594,42 @@ export class Node { return this.props; } + onChildrenChange(fn: () => void) { + return this.children?.onChange(fn); + } + mergeChildren(remover: () => any, adder: (children: Node[]) => NodeData[] | null, sorter: () => any) { this.children?.mergeChildren(remover, adder, sorter); } + @obx.val status: NodeStatus = { + inPlaceEditing: false, + locking: false, + pseudo: false, + }; + /** * @deprecated */ - getStatus() { - return 'default'; + getStatus(field?: keyof NodeStatus) { + if (field && this.status[field] != null) { + return this.status[field]; + } + + return this.status; } /** * @deprecated */ - setStatus() {} + setStatus(field: keyof NodeStatus, flag: boolean) { + if (!this.status.hasOwnProperty(field)) { + return; + } + + if (flag !== this.status[field]) { + this.status[field] = flag; + } + } /** * @deprecated */ @@ -653,7 +676,11 @@ export class Node { this._addons[key] = exportData; } - + + getRect(): DOMRect | null { + return this.document.simulator?.computeRect(this) || null; + } + toString() { return this.id; } @@ -666,9 +693,9 @@ export interface LeafNode extends Node { readonly children: null; } -export interface SlotNode extends ParentalNode {} -export interface PageNode extends ParentalNode {} -export interface ComponentNode extends ParentalNode {} +export type SlotNode = ParentalNode; +export type PageNode = ParentalNode; +export type ComponentNode = ParentalNode; export type RootNode = PageNode | ComponentNode; export function isNode(node: any): node is Node { diff --git a/packages/editor-core/src/di/setter.ts b/packages/editor-core/src/di/setter.ts index b66bfc06c..428b0a7ea 100644 --- a/packages/editor-core/src/di/setter.ts +++ b/packages/editor-core/src/di/setter.ts @@ -39,9 +39,24 @@ export function registerSetter( title: (setter as any).displayName || (setter as any).name || 'CustomSetter', }; } + if (!setter.initialValue) { + const initial = getInitialFromSetter(setter.component); + if (initial) { + setter.initialValue = (field: any) => { + return initial.call(field, field.getValue()); + }; + } + } settersMap.set(typeOrMaps, { type: typeOrMaps, ...setter }); } +function getInitialFromSetter(setter: any) { + return setter && ( + setter.initial || setter.Initial + || (setter.type && (setter.type.initial || setter.type.Initial)) + ) || null; // eslint-disable-line +} + export function getSetter(type: string): RegisteredSetter | null { return settersMap.get(type) || null; } 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..67e020925 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 '../../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; @@ -116,24 +120,32 @@ export default class MixedSetter extends Component<{ const { field } = this.props; let firstMatched: SetterItem | undefined; for (const setter of this.setters) { + if (setter.name === this.used) { + return setter; + } const matched = !setter.condition || setter.condition(field); - if (matched) { - if (setter.name === this.used) { - return setter; - } - if (!firstMatched) { - firstMatched = setter; - } + if (matched && !firstMatched) { + firstMatched = setter; } } 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 +175,162 @@ 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 + // FIXME! use variable placeholder setter + 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 size={24} />, + 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) { + // 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/components/mixed-setter/style.less b/packages/editor-skeleton/src/components/mixed-setter/style.less index bc4fa76a2..d277ae322 100644 --- a/packages/editor-skeleton/src/components/mixed-setter/style.less +++ b/packages/editor-skeleton/src/components/mixed-setter/style.less @@ -16,6 +16,14 @@ opacity: 1; } } + .lc-title { + cursor: pointer; + } + .variable-binded { + background: var(--color-brand, #006cff); + color: #fff!important; + border-radius: 3px; + } } .next-input,.next-date-picker { width: 100%; @@ -39,4 +47,4 @@ height: 32px; transform: none; } -} \ No newline at end of file +} diff --git a/packages/editor-skeleton/src/components/widget-views.tsx b/packages/editor-skeleton/src/components/widget-views.tsx index e097c3407..cdd646be7 100644 --- a/packages/editor-skeleton/src/components/widget-views.tsx +++ b/packages/editor-skeleton/src/components/widget-views.tsx @@ -95,7 +95,7 @@ export class TitledPanelView extends Component<{ panel: Panel }> { hidden: !panel.visible, })}> <PanelTitle panel={panel} /> - <div className="lc-pane-body">{panel.body}</div> + <div className="lc-panel-body">{panel.body}</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/layouts/workbench.less b/packages/editor-skeleton/src/layouts/workbench.less index c4019db63..94d335a90 100644 --- a/packages/editor-skeleton/src/layouts/workbench.less +++ b/packages/editor-skeleton/src/layouts/workbench.less @@ -50,24 +50,28 @@ body { width: 100%; height: 100%; position: relative; - .pane-title { - // height: var(--pane-title-height); + &.hidden { + display: none; + } + .lc-panel-title { + height: 32px; background-color: var(--pane-title-bg-color); display: flex; align-items: center; padding: 0 15px; - .my-help-tip { + .lc-help-tip { margin-left: 4px; } } - .pane-body { + .lc-panel-body { position: absolute; - top: 0; + top: 32px; bottom: 0; left: 0; right: 0; overflow: auto; + /* .my-tabs { width: 100%; height: 100%; @@ -99,10 +103,7 @@ body { overflow: hidden; } } - } - - &.titled > .pane-body { - top: var(--pane-title-height); + */ } } .lc-panel { @@ -112,16 +113,7 @@ body { &.hidden { display: none; } - .pane-title { - height: var(--pane-title-height); - background-color: var(--pane-title-bg-color); - display: flex; - align-items: center; - padding: 0 15px; - .my-help-tip { - margin-left: 4px; - } - } + /* .my-tabs { width: 100%; height: 100%; @@ -154,6 +146,7 @@ body { overflow: hidden; } } + */ } .my-dock { 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/editor-skeleton/src/transducers/addon-combine.ts b/packages/editor-skeleton/src/transducers/addon-combine.ts index c3072df54..c0ee2a480 100644 --- a/packages/editor-skeleton/src/transducers/addon-combine.ts +++ b/packages/editor-skeleton/src/transducers/addon-combine.ts @@ -181,7 +181,7 @@ export default function(metadata: TransformedComponentMetadata): TransformedComp title: { type: 'i18n', 'zh-CN': '高级', 'en-US': 'Advance' }, items: [ { - name: '__condition', + name: '___condition', title: { type: 'i18n', 'zh-CN': '条件显示', 'en-US': 'Condition' }, setter: 'ExpressionSetter', }, @@ -190,7 +190,7 @@ export default function(metadata: TransformedComponentMetadata): TransformedComp title: { type: 'i18n', 'zh-CN': '循环', 'en-US': 'Loop' }, items: [ { - name: '__loop', + name: '___loop', title: { type: 'i18n', 'zh-CN': '循环数据', 'en-US': 'Loop Data' }, setter: { componentName: 'MixinSetter', @@ -215,7 +215,7 @@ export default function(metadata: TransformedComponentMetadata): TransformedComp }, }, { - name: '__loopArgs.0', + name: '___loopArgs.0', title: { type: 'i18n', 'zh-CN': '迭代变量名', 'en-US': 'Loop Item' }, setter: { componentName: 'StringSetter', @@ -225,7 +225,7 @@ export default function(metadata: TransformedComponentMetadata): TransformedComp }, }, { - name: '__loopArgs.1', + name: '___loopArgs.1', title: { type: 'i18n', 'zh-CN': '索引变量名', 'en-US': 'Loop Index' }, setter: { componentName: 'StringSetter', diff --git a/packages/editor-skeleton/src/widget/panel-dock.ts b/packages/editor-skeleton/src/widget/panel-dock.ts index 7b06882fe..105da7be4 100644 --- a/packages/editor-skeleton/src/widget/panel-dock.ts +++ b/packages/editor-skeleton/src/widget/panel-dock.ts @@ -6,6 +6,7 @@ import { PanelDockConfig } from '../types'; import Panel from './panel'; import { PanelDockView, WidgetView } from '../components/widget-views'; import { IWidget } from './widget'; +import { composeTitle } from './utils'; export default class PanelDock implements IWidget { readonly isWidget = true; diff --git a/packages/editor-skeleton/src/widget/panel.ts b/packages/editor-skeleton/src/widget/panel.ts index 0fbcd809d..d0336fe6c 100644 --- a/packages/editor-skeleton/src/widget/panel.ts +++ b/packages/editor-skeleton/src/widget/panel.ts @@ -74,6 +74,8 @@ export default class Panel implements IWidget { ); content.forEach((item) => this.add(item)); } + // compatiable for vision, init at first + this.initBody(); // todo: process shortcut } @@ -164,15 +166,21 @@ export default class Panel implements IWidget { /** * @deprecated */ - getCurrentPosition() { - return 'left'; + getSupportedPositions() { + return ['default']; } /** * @deprecated */ - getSupportedPositions() { - return ['left']; + getCurrentPosition() { + return 'default'; + } + + /** + * @deprecated + */ + setPosition(position: string) { } } diff --git a/packages/react-simulator-renderer/src/renderer-view.tsx b/packages/react-simulator-renderer/src/renderer-view.tsx index 8036d21c7..7b41312c8 100644 --- a/packages/react-simulator-renderer/src/renderer-view.tsx +++ b/packages/react-simulator-renderer/src/renderer-view.tsx @@ -5,6 +5,36 @@ import { SimulatorRenderer } from './renderer'; import { host } from './host'; import './renderer.less'; +// patch cloneElement avoid lost keyProps +const originCloneElement = window.React.cloneElement; +(window as any).React.cloneElement = (child: any, { _leaf, ...props}: any = {}) => { + if (child.ref && props.ref) { + const dRef = props.ref; + const cRef = child.ref; + props.ref = (x: any) => { + if (cRef) { + if (typeof cRef === 'function') { + cRef(x); + } else { + try { + cRef.current = x; + } catch (e) { } + } + } + if (dRef) { + if (typeof dRef === 'function') { + dRef(x); + } else { + try { + dRef.current = x; + } catch (e) { } + } + } + } + }; + return originCloneElement(child, props); +} + export default class SimulatorRendererView extends Component<{ renderer: SimulatorRenderer }> { render() { const { renderer } = this.props; diff --git a/packages/types/src/index.ts b/packages/types/src/index.ts index 31aa669f3..4129097f2 100644 --- a/packages/types/src/index.ts +++ b/packages/types/src/index.ts @@ -13,3 +13,4 @@ export * from './utils'; export * from './value-type'; export * from './setter-config'; export * from './setting-target'; +export * from './node'; diff --git a/packages/types/src/node.ts b/packages/types/src/node.ts new file mode 100644 index 000000000..400f6d987 --- /dev/null +++ b/packages/types/src/node.ts @@ -0,0 +1,5 @@ +export interface NodeStatus { + locking: boolean; + pseudo: boolean; + inPlaceEditing: boolean; +} 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..3ea38d90e 100644 --- a/packages/vision-preset/src/bundle/upgrade-metadata.ts +++ b/packages/vision-preset/src/bundle/upgrade-metadata.ts @@ -284,6 +284,17 @@ export function upgradePropConfig(config: OldPropConfig, addInitial: AddIntial) } let initialFn = (slotName ? null : initial) || initialValue; + if (slotName && initialValue === true) { + initialFn = (field: any, value: any) => { + if (isJSSlot(value)) { + return value; + } + return { + type: 'JSSlot', + value: initialChildren, + }; + }; + } if (accessor && !slotName) { extraProps.getValue = (field: Field, fieldValue: any) => { @@ -294,17 +305,25 @@ export function upgradePropConfig(config: OldPropConfig, addInitial: AddIntial) } } + const setterInitial = getInitialFromSetter(setter); + addInitial({ name: slotName || name, initial: (field: Field, currentValue: any) => { // FIXME! read from prototype.defaultProps const defaults = extraProps.defaultValue; - if (typeof initialFn === 'function') { - return initialFn.call(field, currentValue, defaults); + if (typeof initialFn !== 'function') { + initialFn = defaultInitial; } - return currentValue == null ? defaults : currentValue; + const v = initialFn.call(field, currentValue, defaults); + + if (setterInitial) { + return setterInitial.call(field, v, defaults); + } + + return v; }, }); @@ -342,11 +361,10 @@ export function upgradePropConfig(config: OldPropConfig, addInitial: AddIntial) }, ]; if (allowTextInput !== false) { - setters.unshift('StringSetter'); - // FIXME: use I18nSetter + setters.unshift('I18nSetter'); } if (supportVariable) { - setters.push('ExpressionSetter'); + setters.push('VariableSetter'); } newConfig.setter = setters.length > 1 ? setters : setters[0]; @@ -403,25 +421,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; @@ -430,6 +447,18 @@ export function upgradePropConfig(config: OldPropConfig, addInitial: AddIntial) type AddIntial = (initialItem: InitialItem) => void; +function getInitialFromSetter(setter: any) { + return setter && ( + setter.initial || setter.Initial + || (setter.type && (setter.type.initial || setter.type.Initial)) + ) || null; // eslint-disable-line +} + +function defaultInitial(value: any, defaultValue: any) { + return value == null ? defaultValue : value; +} + + export function upgradeConfigure(items: OldPropConfig[], addInitial: AddIntial) { const configure: any[] = []; let ignoreSlotName: any = null; 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..47a6917a4 100644 --- a/packages/vision-preset/src/context.ts +++ b/packages/vision-preset/src/context.ts @@ -3,30 +3,31 @@ 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 + // initialValue?: any | ((field: any) => any); + }); + } } getPlugin(name: string) { @@ -102,3 +103,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 770c8e0db..96cc11e03 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 90d3070bb..a7578b39c 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'; @@ -26,6 +26,11 @@ import { designer, editor } from './editor'; import './vision.less'; function init(container?: Element) { + //TODO: dirty fix + // 之前的组件库依赖了这个样式,临时fix一下。 + // 取决于预览模式是否保留。 + document.documentElement.classList.add('engine-design-mode'); + if (!container) { container = document.createElement('div'); document.body.appendChild(container); @@ -59,8 +64,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;