diff --git a/packages/designer/src/builtin-simulator/host.ts b/packages/designer/src/builtin-simulator/host.ts index df9899965..8b1091489 100644 --- a/packages/designer/src/builtin-simulator/host.ts +++ b/packages/designer/src/builtin-simulator/host.ts @@ -878,7 +878,7 @@ export class BuiltinSimulatorHost implements ISimulatorHost { + const designer = this; + designer._componentMetasMap.forEach((config, key) => { const metaData = config.getMetadata(); if (metaData.devMode === 'lowcode') { - maps[key] = this.currentDocument?.simulator?.createComponent(metaData.schema!); + maps[key] = metaData.schema; } else { const view = metaData.experimental?.view; if (view) { diff --git a/packages/designer/src/designer/setting/setting-field.ts b/packages/designer/src/designer/setting/setting-field.ts index f15969b75..956de1c8d 100644 --- a/packages/designer/src/designer/setting/setting-field.ts +++ b/packages/designer/src/designer/setting/setting-field.ts @@ -5,6 +5,18 @@ import { SettingEntry } from './setting-entry'; import { computed, obx } from '@ali/lowcode-editor-core'; import { cloneDeep } from '@ali/lowcode-utils'; +function getSettingFieldCollectorKey(parent: SettingEntry, config: FieldConfig) { + let top = parent; + const path = [config.name]; + while (top !== parent.top) { + if (top instanceof SettingField && top.type !== 'group') { + path.unshift(top.name); + } + top = top.parent; + } + return path.join('.'); +} + export class SettingField extends SettingPropEntry implements SettingEntry { readonly isSettingField = true; readonly isRequired: boolean; @@ -51,10 +63,11 @@ export class SettingField extends SettingPropEntry implements SettingEntry { this._expanded = extraProps?.defaultCollapsed ? false : true; // initial items - if (this.type === 'group' && items) { + if (items && items.length > 0) { this.initItems(items, settingFieldCollector); - } else if (settingFieldCollector && config.name) { - settingFieldCollector(config.name, this); + } + if (this.type !== 'group' && settingFieldCollector && config.name) { + settingFieldCollector(getSettingFieldCollectorKey(parent, config), this); } // compatiable old config diff --git a/packages/designer/src/document/node/node-children.ts b/packages/designer/src/document/node/node-children.ts index 1553a54f3..aeb306a9b 100644 --- a/packages/designer/src/document/node/node-children.ts +++ b/packages/designer/src/document/node/node-children.ts @@ -79,6 +79,10 @@ export class NodeChildren { return this.size > 0; } + @computed length() { + return this.children.length; + } + /** * 删除一个节点 */ @@ -94,6 +98,7 @@ export class NodeChildren { deleted.purge(); } this.emitter.emit('change'); + this.reportModified(node, this.owner, {type: 'remove', removeIndex: i, removeNode: node}); return false; } @@ -290,4 +295,17 @@ export class NodeChildren { // 保证向前兼容性 return "Array"; } + + private reportModified(node: Node, owner: Node, options = {}) { + if (!node) { return; } + if (node.isRoot()) { return; } + const callbacks = owner.componentMeta.getMetadata().experimental?.callbacks; + if (callbacks?.onSubtreeModified) { + callbacks?.onSubtreeModified.call(node, owner, options); + } + + if (owner.parent && !owner.parent.isRoot()) { + this.reportModified(node, owner.parent, options); + } + } } diff --git a/packages/designer/src/document/node/node.ts b/packages/designer/src/document/node/node.ts index b37566f8e..b6f0ed4c3 100644 --- a/packages/designer/src/document/node/node.ts +++ b/packages/designer/src/document/node/node.ts @@ -634,6 +634,14 @@ export class Node { this._slots.push(slotNode); } + /** + * 删除一个节点 + * @param node + */ + removeChild(node: Node) { + this.children?.delete(node); + } + private purged = false; /** * 是否已销毁 diff --git a/packages/designer/src/project/project.ts b/packages/designer/src/project/project.ts index e010c5476..2480ded99 100644 --- a/packages/designer/src/project/project.ts +++ b/packages/designer/src/project/project.ts @@ -102,7 +102,7 @@ export class Project { | string, ): any {} - open(doc?: string | DocumentModel | RootSchema): void { + open(doc?: string | DocumentModel | RootSchema): DocumentModel { if (!doc) { const got = this.documents.find((item) => item.isBlank()); if (got) { diff --git a/packages/editor-preset-vision/src/bundle/prototype.ts b/packages/editor-preset-vision/src/bundle/prototype.ts index 00cdeae29..b1406c987 100644 --- a/packages/editor-preset-vision/src/bundle/prototype.ts +++ b/packages/editor-preset-vision/src/bundle/prototype.ts @@ -220,6 +220,9 @@ class Prototype { readonly isPrototype = true; readonly meta: ComponentMeta; readonly options: OldPrototypeConfig | ComponentMetadata; + get packageName() { + return this.meta.npm?.package; + } constructor(input: OldPrototypeConfig | ComponentMetadata | ComponentMeta, lookup: boolean = false) { if (lookup) { @@ -251,7 +254,7 @@ class Prototype { } getPackageName() { - return this.meta.npm?.package; + return this.packageName; } getContextInfo(name: string): any { diff --git a/packages/editor-preset-vision/src/bundle/upgrade-metadata.ts b/packages/editor-preset-vision/src/bundle/upgrade-metadata.ts index 0db24f0b5..cf7ef1ff9 100644 --- a/packages/editor-preset-vision/src/bundle/upgrade-metadata.ts +++ b/packages/editor-preset-vision/src/bundle/upgrade-metadata.ts @@ -332,27 +332,29 @@ export function upgradePropConfig(config: OldPropConfig, collector: ConfigCollec const setterInitial = getInitialFromSetter(setter); - collector.addInitial({ - // FIXME! name could be "xxx.xxx" - name: slotName || name, - initial: (field: Field, currentValue: any) => { - // FIXME! read from prototype.defaultProps - const defaults = extraProps.defaultValue; - - if (typeof initialFn !== 'function') { - initialFn = defaultInitial; - } - - const v = initialFn.call(field, currentValue, defaults); - - if (setterInitial) { - return setterInitial.call(field, v, defaults); - } - - return v; - }, - }); - + if (type !== 'composite') { + collector.addInitial({ + // FIXME! name could be "xxx.xxx" + name: slotName || name, + initial: (field: Field, currentValue: any) => { + // FIXME! read from prototype.defaultProps + const defaults = extraProps.defaultValue; + + if (typeof initialFn !== 'function') { + initialFn = defaultInitial; + } + + const v = initialFn.call(field, currentValue, defaults); + + if (setterInitial) { + return setterInitial.call(field, v, defaults); + } + + return v; + }, + }); + } + if (ignore != null || disabled != null) { collector.addFilter({ // FIXME! name should be "xxx.xxx" @@ -431,9 +433,11 @@ export function upgradePropConfig(config: OldPropConfig, collector: ConfigCollec autorun: item.autorun, }); }, - } + }, ) : []; + newConfig.items = objItems; + const initial = (target: SettingTarget, value?: any) => { // TODO: const defaults = extraProps.defaultValue; @@ -749,10 +753,7 @@ export function upgradeMetadata(oldConfig: OldPrototypeConfig) { callbacks.onNodeRemove = didDropOut; } if (subtreeModified) { - callbacks.onSubtreeModified = (...args: any[]) => { - // FIXME! args not correct - subtreeModified.apply(args[0], args as any); - }; + callbacks.onSubtreeModified = subtreeModified; } if (onResize) { callbacks.onResize = (e: any, currentNode: any) => { diff --git a/packages/editor-preset-vision/src/editor.ts b/packages/editor-preset-vision/src/editor.ts index 186169bdf..86a89a43e 100644 --- a/packages/editor-preset-vision/src/editor.ts +++ b/packages/editor-preset-vision/src/editor.ts @@ -85,17 +85,35 @@ designer.addPropsReducer((props, node) => { const newProps: any = { ...props, }; + const getRealValue = (propValue: any) => { + if (isVariable(propValue)) { + return propValue.value; + } + if (isJSExpression(propValue)) { + return propValue.mock; + } + return propValue; + }; initials.forEach((item) => { // FIXME! this implements SettingTarget try { // FIXME! item.name could be 'xxx.xxx' const ov = props[item.name]; - const v = item.initial(node as any, isJSExpression(ov) ? ov.mock : ov); + const v = item.initial(node as any, getRealValue(ov)); if (v !== undefined) { - newProps[item.name] = isJSExpression(ov) ? { - ...ov, - mock: v, - } : v; + if (isVariable(ov)) { + newProps[item.name] = { + ...ov, + value: v, + }; + } else if (isJSExpression(ov)) { + newProps[item.name] = { + ...ov, + mock: v, + }; + } else { + newProps[item.name] = v; + } } } catch (e) { if (hasOwnProperty(props, item.name)) { @@ -153,6 +171,14 @@ function compatiableReducer(props: any) { }, } } + // 为了能降级到老版本,建议在后期版本去掉以下代码 + if (isJSExpression(val) && !val.events) { + val = { + type: 'variable', + value: val.mock, + variable: val.value, + } + } newProps[key] = val; }); return newProps; diff --git a/packages/editor-skeleton/src/area.ts b/packages/editor-skeleton/src/area.ts index aafbab857..403a93dcf 100644 --- a/packages/editor-skeleton/src/area.ts +++ b/packages/editor-skeleton/src/area.ts @@ -34,6 +34,10 @@ export default class Area + + + ); +} + +IconFix.displayName = 'Fix'; diff --git a/packages/editor-skeleton/src/icons/float.tsx b/packages/editor-skeleton/src/icons/float.tsx new file mode 100644 index 000000000..a6d88109c --- /dev/null +++ b/packages/editor-skeleton/src/icons/float.tsx @@ -0,0 +1,12 @@ +import { SVGIcon, IconProps } from "@ali/lowcode-utils"; + +export function IconFloat(props: IconProps) { + return ( + + + + + ); +} + +IconFloat.displayName = 'Float'; diff --git a/packages/editor-skeleton/src/layouts/left-fixed-pane.tsx b/packages/editor-skeleton/src/layouts/left-fixed-pane.tsx index 955304271..6baf176f9 100644 --- a/packages/editor-skeleton/src/layouts/left-fixed-pane.tsx +++ b/packages/editor-skeleton/src/layouts/left-fixed-pane.tsx @@ -6,16 +6,31 @@ import Area from '../area'; import { PanelConfig } from '../types'; import Panel from '../widget/panel'; import { Designer } from '@ali/lowcode-designer'; +import { IconFloat } from '../icons/float'; @observer export default class LeftFixedPane extends Component<{ area: Area }> { shouldComponentUpdate() { return false; } + componentDidUpdate() { // FIXME: dirty fix, need deep think this.props.area.skeleton.editor.get(Designer)?.touchOffsetObserver(); } + + // 取消固定 + setFloat() { + const { area } = this.props; + const { current } = area; + if (!current) { + return; + } + area.skeleton.leftFixedArea.remove(current); + area.skeleton.leftFloatArea.add(current); + area.skeleton.leftFloatArea.container.active(current); + } + render() { const { area } = this.props; const hideTitleBar = area.current?.config.props?.hideTitleBar; @@ -34,15 +49,24 @@ export default class LeftFixedPane extends Component<{ area: Area {!hideTitleBar && ( - + + + + )} diff --git a/packages/editor-skeleton/src/layouts/left-float-pane.tsx b/packages/editor-skeleton/src/layouts/left-float-pane.tsx index d904887f7..6d184c5ac 100644 --- a/packages/editor-skeleton/src/layouts/left-float-pane.tsx +++ b/packages/editor-skeleton/src/layouts/left-float-pane.tsx @@ -2,6 +2,7 @@ import { Component, Fragment } from 'react'; import classNames from 'classnames'; import { observer, Focusable, focusTracker } from '@ali/lowcode-editor-core'; import { Button, Icon } from '@alifd/next'; +import { IconFix } from '../icons/fix'; import Area from '../area'; import Panel from '../widget/panel'; @@ -76,6 +77,18 @@ export default class LeftFloatPane extends Component<{ area: Area }> this.dispose?.(); } + // 固定 + setFixed() { + const { area } = this.props; + const { current } = area; + if (!current) { + return; + } + area.skeleton.leftFloatArea.remove(current); + area.skeleton.leftFixedArea.add(current); + area.skeleton.leftFixedArea.container.active(current); + } + render() { const { area } = this.props; const width = area.current?.config.props?.width; @@ -93,15 +106,24 @@ export default class LeftFloatPane extends Component<{ area: Area }> > { !hideTitleBar && ( - + + + + ) } diff --git a/packages/editor-skeleton/src/layouts/workbench.less b/packages/editor-skeleton/src/layouts/workbench.less index df1e07189..a5ea65a77 100644 --- a/packages/editor-skeleton/src/layouts/workbench.less +++ b/packages/editor-skeleton/src/layouts/workbench.less @@ -175,6 +175,65 @@ body { display: flex; min-height: 0; position: relative; + + .lc-tabs-title { + width: 100%; + height: 32px; + position: relative; + display: center; + display: flex; + justify-content: center; + align-items: center; + // background: rgba(31,56,88,0.04); + border-bottom: 1px solid #EDEFF3; + .lc-tab-title{ + flex: 1; + height: 32px; + display: flex; + align-items: center; + justify-content: center; + border-bottom: 2px solid transparent; + cursor: pointer; + font-size: 12px; + &.actived { + color: #0079F2; + border-bottom-color: #0079F2; + } + } + } + + .lc-tabs-content { + position: absolute; + top: 32px; + bottom: 0; + left: 0; + right: 0; + } + + .lc-pane-icon-close { + position: absolute; + right: 16px; + top: 14px; + height: auto; + z-index: 2; + .next-icon{ + line-height: 1; + color: rgba(0,0,0,0.6); + } + } + + .lc-pane-icon-fix, .lc-pane-icon-float{ + position: absolute; + right: 38px; + top: 14px; + height: auto; + z-index: 2; + svg { + vertical-align: middle; + color: rgba(0,0,0,0.6); + } + } + .lc-left-float-pane { position: absolute; top: 0; @@ -190,48 +249,6 @@ body { &.lc-area-visible { display: block; } - .lc-pane-close{ - position: absolute; - right: 16px; - top: 16px; - height: auto; - z-index: 2; - .next-icon{ - line-height: 1; - } - } - .lc-tabs-title { - width: 100%; - height: 32px; - position: relative; - display: center; - display: flex; - justify-content: center; - align-items: center; - // background: rgba(31,56,88,0.04); - border-bottom: 1px solid #EDEFF3; - .lc-tab-title{ - flex: 1; - height: 32px; - display: flex; - align-items: center; - justify-content: center; - border-bottom: 2px solid transparent; - cursor: pointer; - font-size: 12px; - &.actived { - color: #0079F2; - border-bottom-color: #0079F2; - } - } - } - .lc-tabs-content { - position: absolute; - top: 32px; - bottom: 0; - left: 0; - right: 0; - } } .lc-left-area { height: 100%; @@ -293,16 +310,6 @@ body { &.lc-area-visible { display: block; } - .lc-pane-close { - position: absolute; - right: 16px; - top: 16px; - height: auto; - z-index: 2; - .next-icon { - line-height: 1; - } - } } .lc-left-area.lc-area-visible ~ .lc-left-fixed-pane { margin-left: 1px; diff --git a/packages/plugin-outline-pane/src/views/tree-branches.tsx b/packages/plugin-outline-pane/src/views/tree-branches.tsx index d3418277e..879d50fda 100644 --- a/packages/plugin-outline-pane/src/views/tree-branches.tsx +++ b/packages/plugin-outline-pane/src/views/tree-branches.tsx @@ -71,7 +71,7 @@ class TreeNodeChildren extends Component<{ /> ); treeNode.children?.forEach((child, index) => { - const childIsModal = child.node.getPrototype().isModal(); + const childIsModal = child.node.getPrototype()?.isModal() || false; if (isModal != childIsModal) { return; } diff --git a/packages/react-renderer/src/engine/blockEngine.jsx b/packages/react-renderer/src/engine/blockEngine.jsx index 9712ef3ea..1485b3e64 100644 --- a/packages/react-renderer/src/engine/blockEngine.jsx +++ b/packages/react-renderer/src/engine/blockEngine.jsx @@ -59,7 +59,7 @@ export default class BlockEngine extends BaseEngine { } render() { - const { __schema } = this.props; + const { __schema, __components } = this.props; if (!isSchema(__schema, true) || (__schema.componentName !== 'Block' && __schema.componentName !== 'Div')) { return '区块schema结构异常!'; @@ -69,7 +69,33 @@ export default class BlockEngine extends BaseEngine { this.__generateCtx(); this.__render(); - const { id, className, style, autoLoading, defaultHeight = 300, loading } = this.__parseData(__schema.props); + const props = this.__parseData(__schema.props); + const { id, className, style, autoLoading, defaultHeight = 300, loading } = props; + + const { Block } = __components; + if (Block) { + const { engine } = this.context || {}; + return ( + + {engine.createElement( + Block, + { + ...props, + ref: this.__getRef, + className: classnames(getFileCssName(__schema.fileName), className, this.props.className), + __id: __schema.id, + }, + this.__createDom(), + )} + + ); + } + const renderContent = () => ( }, -) { +function buildComponents(libraryMap: LibraryMap, + componentsMap: { [componentName: string]: NpmInfo | ComponentType | ComponentSchema }, + createComponent: (schema: ComponentSchema) => Component | null) { const components: any = { ...builtinComponents, }; Object.keys(componentsMap).forEach((componentName) => { let component = componentsMap[componentName]; - if (isReactComponent(component)) { + if (component && (component as ComponentSchema).componentName === 'Component') { + components[componentName] = createComponent(component as ComponentSchema); + } else if (isReactComponent(component)) { components[componentName] = component; } else { component = findComponent(libraryMap, componentName, component); diff --git a/packages/types/src/value-type.ts b/packages/types/src/value-type.ts index dd6e48f18..6e5083c0a 100644 --- a/packages/types/src/value-type.ts +++ b/packages/types/src/value-type.ts @@ -11,6 +11,10 @@ export interface JSExpression { * 模拟值 */ mock?: any; + /** + * 额外扩展属性,如 extType、events + */ + [key: string]: any; } export interface JSSlot {