diff --git a/packages/designer/src/component-meta.ts b/packages/designer/src/component-meta.ts index 1954f6091..0de4a9224 100644 --- a/packages/designer/src/component-meta.ts +++ b/packages/designer/src/component-meta.ts @@ -7,6 +7,8 @@ import { TitleContent, TransformedComponentMetadata, NestingFilter, + isTitleConfig, + I18nData, } from '@ali/lowcode-types'; import { computed } from '@ali/lowcode-editor-core'; import { Node, ParentalNode } from './document'; @@ -17,6 +19,7 @@ import { IconPage } from './icons/page'; import { IconComponent } from './icons/component'; import { IconRemove } from './icons/remove'; import { IconClone } from './icons/clone'; +import { ReactElement } from 'react'; function ensureAList(list?: string | string[]): string[] | null { if (!list) { @@ -91,12 +94,20 @@ export class ComponentMeta { private childWhitelist?: NestingFilter | null; private _title?: TitleContent; - get title() { + get title(): string | I18nData | ReactElement { + // TODO: 标记下。这块需要康师傅加一下API,页面正常渲染。 + // string | i18nData | ReactElement + // TitleConfig title.label + if (isTitleConfig(this._title)) { + return (this._title.label as any) || this.componentName; + } return this._title || this.componentName; } @computed get icon() { + // TODO: 标记下。这块需要康师傅加一下API,页面正常渲染。 // give Slot default icon + // if _title is TitleConfig get _title.icon return ( this._transformedMetadata?.icon || (this.componentName === 'Page' ? IconPage : this.isContainer ? IconContainer : IconComponent) @@ -131,10 +142,10 @@ export class ComponentMeta { this._title = typeof title === 'string' ? { - type: 'i18n', - 'en-US': this.componentName, - 'zh-CN': title, - } + type: 'i18n', + 'en-US': this.componentName, + 'zh-CN': title, + } : title; } diff --git a/packages/designer/src/document/node/node.ts b/packages/designer/src/document/node/node.ts index 4cc95b4e1..a8d469309 100644 --- a/packages/designer/src/document/node/node.ts +++ b/packages/designer/src/document/node/node.ts @@ -7,6 +7,7 @@ import { PropsList, NodeData, TitleContent, + I18nData, SlotSchema, PageSchema, ComponentSchema, @@ -19,6 +20,7 @@ import { Prop } from './props/prop'; import { ComponentMeta } from '../../component-meta'; import { ExclusiveGroup, isExclusiveGroup } from './exclusive-group'; import { TransformStage } from './transform-stage'; +import { ReactElement } from 'react'; /** * 基础节点 @@ -122,7 +124,7 @@ export class Node { return 0; } - @computed get title(): TitleContent { + @computed get title(): string | I18nData | ReactElement { let t = this.getExtraProp('title'); if (!t && this.componentMeta.descriptor) { t = this.getProp(this.componentMeta.descriptor, false); @@ -136,6 +138,10 @@ export class Node { return this.componentMeta.title; } + get icon() { + return this.componentMeta.icon; + } + constructor(readonly document: DocumentModel, nodeSchema: Schema) { const { componentName, id, children, props, ...extras } = nodeSchema; this.id = id || `node$${document.nextId()}`; diff --git a/packages/types/src/title.ts b/packages/types/src/title.ts index 3be21d84e..658ddf73a 100644 --- a/packages/types/src/title.ts +++ b/packages/types/src/title.ts @@ -13,3 +13,6 @@ export interface TitleConfig { export type TitleContent = string | I18nData | ReactElement | TitleConfig; +export function isTitleConfig(obj: any): obj is TitleConfig { + return obj && (obj.label || obj.tip || obj.icon); +} diff --git a/packages/vision-preset/src/components/index.less b/packages/vision-preset/src/components/index.less new file mode 100644 index 000000000..c630e0914 --- /dev/null +++ b/packages/vision-preset/src/components/index.less @@ -0,0 +1,82 @@ +@import '~@ali/ve-less-variables/index.less'; + +// 样式直接沿用之前的样式,优化了下命名 +.instance-node-selector { + position: relative; + margin-right: 2px; + color: var(--color-icon-white, @title-bgcolor); + border-radius: @global-border-radius; + margin-right: 2px; + pointer-events: auto; + flex-grow: 0; + flex-shrink: 0; + + svg { + width: 16px; + height: 16px; + margin-right: 5px; + flex-grow: 0; + flex-shrink: 0; + max-width: inherit; + path { + fill: var(--color-icon-white, @title-bgcolor); + } + } + &-current { + background: var(--color-brand, @brand-color-1); + padding: 0 6px; + display: flex; + align-items: center; + height: 20px; + cursor: pointer; + color: var(--color-icon-white, @title-bgcolor); + border-radius: 3px; + + &-title { + padding-right: 6px; + color: var(--color-icon-white, @title-bgcolor); + } + } + &-list { + position: absolute; + left: 0; + right: 0; + opacity: 0; + visibility: hidden; + } + &-node { + margin: 2px 0; + &-content { + padding-left: 6px; + background: #78869a; + display: inline-flex; + border-radius: 3px; + align-items: center; + height: 20px; + color: var(--color-icon-white, @title-bgcolor); + cursor: pointer; + overflow: visible; + } + &-title { + padding-right: 6px; + // margin-left: 5px; + color: var(--color-icon-white, @title-bgcolor); + cursor: pointer; + overflow: visible; + } + &:hover { + opacity: 0.8; + } + } +} + +&:hover { + .instance-node-selector-current { + color: ar(--color-text-reverse, @white-alpha-2); + } + .instance-node-selector-popup { + visibility: visible; + opacity: 1; + transition: 0.2s all ease-in; + } +} diff --git a/packages/vision-preset/src/components/index.tsx b/packages/vision-preset/src/components/index.tsx new file mode 100644 index 000000000..2518705aa --- /dev/null +++ b/packages/vision-preset/src/components/index.tsx @@ -0,0 +1,95 @@ +import { Overlay } from '@alifd/next'; +import React from 'react'; +import './index.less'; +import { Title } from '@ali/lowcode-editor-core'; + +import { Node, ParentalNode } from '@ali/lowcode-designer'; + +const { Popup } = Overlay; + +export interface IProps { + node: Node; +} + +export interface IState { + parentNodes: Node[]; +} + +type UnionNode = Node | ParentalNode | null; + +export class InstanceNodeSelector extends React.Component { + state: IState = { + parentNodes: [], + }; + + componentDidMount() { + const parentNodes = this.getParentNodes(this.props.node); + this.setState({ + parentNodes, + }); + } + + // 获取节点的父级节点(最多获取5层) + getParentNodes = (node: Node) => { + const parentNodes = []; + let currentNode: UnionNode = node; + + while (currentNode && parentNodes.length < 5) { + currentNode = currentNode.getParent(); + if (currentNode) { + parentNodes.push(currentNode); + } + } + return parentNodes; + }; + + onSelect = (node: Node) => () => { + if (node && typeof node.select === 'function') { + node.select(); + } + }; + + renderNodes = (node: Node) => { + const nodes = this.state.parentNodes || []; + const children = nodes.map((node, key) => { + return ( +
+
+ + </div> + </div> + ); + }); + return children; + }; + + render() { + const { node } = this.props; + return ( + <div className="instance-node-selector"> + <Popup + trigger={ + <div className="instance-node-selector-current"> + <Title + className="instance-node-selector-node-title" + title={{ + label: node.title, + icon: node.icon, + }} + /> + </div> + } + triggerType="hover" + > + <div className="instance-node-selector">{this.renderNodes(node)}</div> + </Popup> + </div> + ); + } +} diff --git a/packages/vision-preset/src/editor.ts b/packages/vision-preset/src/editor.ts index 333e1a42f..5c064a3b0 100644 --- a/packages/vision-preset/src/editor.ts +++ b/packages/vision-preset/src/editor.ts @@ -1,15 +1,18 @@ import { isJSBlock, isJSSlot } from '@ali/lowcode-types'; import { isPlainObject } from '@ali/lowcode-utils'; -import { globalContext, Editor, registerSetter } from '@ali/lowcode-editor-core'; -import { Designer, TransformStage } from '@ali/lowcode-designer'; -// import { registerSetters } from '@ali/lowcode-setters'; -import Outline from '@ali/lowcode-plugin-outline-pane'; +import { globalContext, Editor } from '@ali/lowcode-editor-core'; +import { Designer, TransformStage, addBuiltinComponentAction } from '@ali/lowcode-designer'; +import { registerSetters } from '@ali/lowcode-setters'; +// import Outline from '@ali/lowcode-plugin-outline-pane'; + import DesignerPlugin from '@ali/lowcode-plugin-designer'; import { Skeleton, SettingsPrimaryPane } from '@ali/lowcode-editor-skeleton'; import Preview from '@ali/lowcode-plugin-sample-preview'; // import SourceEditor from '@ali/lowcode-plugin-source-editor'; import { i18nReducer } from './i18n-reducer'; +import { InstanceNodeSelector } from './components'; +import { Divider } from '@alifd/next'; export const editor = new Editor(); globalContext.register(editor, Editor); @@ -112,3 +115,10 @@ skeleton.add({ // }, // content: SourceEditor, // }); + +// 实例节点选择器,线框高亮 +addBuiltinComponentAction({ + name: 'instance-node-selector', + content: InstanceNodeSelector, + important: true, +});