diff --git a/packages/designer/src/builtin-simulator/bem-tools/index.tsx b/packages/designer/src/builtin-simulator/bem-tools/index.tsx index 97ce673dc..6551633e8 100644 --- a/packages/designer/src/builtin-simulator/bem-tools/index.tsx +++ b/packages/designer/src/builtin-simulator/bem-tools/index.tsx @@ -24,7 +24,7 @@ export class BemTools extends Component<{ host: BuiltinSimulatorHost }> { } return (
- + { !engineConfig.get('disableDetecting') && } { engineConfig.get('enableReactiveContainer') && } @@ -38,4 +38,4 @@ export class BemTools extends Component<{ host: BuiltinSimulatorHost }> {
); } -} +} \ No newline at end of file diff --git a/packages/designer/src/builtin-simulator/host.ts b/packages/designer/src/builtin-simulator/host.ts index 4cb8199df..de53e8cf7 100644 --- a/packages/designer/src/builtin-simulator/host.ts +++ b/packages/designer/src/builtin-simulator/host.ts @@ -1,9 +1,23 @@ -import { obx, autorun, computed, getPublicPath, hotkey, focusTracker, engineConfig } from '@ali/lowcode-editor-core'; +import { + obx, + autorun, + computed, + getPublicPath, + hotkey, + focusTracker, + engineConfig, +} from '@ali/lowcode-editor-core'; import { EventEmitter } from 'events'; -import { ISimulatorHost, Component, NodeInstance, ComponentInstance, DropContainer } from '../simulator'; +import { + ISimulatorHost, + Component, + NodeInstance, + ComponentInstance, + DropContainer, +} from '../simulator'; import Viewport from './viewport'; import { createSimulator } from './create-simulator'; -import { Node, ParentalNode, isNode, contains, isRootNode } from '../document'; +import { Node, ParentalNode, contains, isRootNode } from '../document'; import ResourceConsumer from './resource-consumer'; import { AssetLevel, @@ -23,7 +37,6 @@ import { LocateEvent, isDragAnyObject, isDragNodeObject, - LocationData, isLocationData, LocationChildrenDetail, LocationDetailType, @@ -36,7 +49,12 @@ import { } from '../designer'; import { parseMetadata } from './utils/parse-metadata'; import { getClosestClickableNode } from './utils/clickable'; -import { ComponentMetadata, ComponentSchema, TransformStage, ActivityData } from '@ali/lowcode-types'; +import { + ComponentMetadata, + ComponentSchema, + TransformStage, + ActivityData, +} from '@ali/lowcode-types'; import { BuiltinSimulatorRenderer } from './renderer'; import clipboard from '../designer/clipboard'; import { LiveEditing } from './live-editing/live-editing'; @@ -80,7 +98,10 @@ const defaultSimulatorUrl = (() => { // eslint-disable-next-line @typescript-eslint/no-unused-vars const [_, prefix = '', dev] = /^(.+?)(\/js)?\/?$/.exec(publicPath) || []; if (dev) { - urls = [`${prefix}/css/react-simulator-renderer.css`, `${prefix}/js/react-simulator-renderer.js`]; + urls = [ + `${prefix}/css/react-simulator-renderer.css`, + `${prefix}/js/react-simulator-renderer.js`, + ]; } else if (process.env.NODE_ENV === 'production') { urls = [`${prefix}/react-simulator-renderer.css`, `${prefix}/react-simulator-renderer.js`]; } else { @@ -106,12 +127,16 @@ const defaultRaxSimulatorUrl = (() => { const defaultEnvironment = [ // https://g.alicdn.com/mylib/??react/16.11.0/umd/react.production.min.js,react-dom/16.8.6/umd/react-dom.production.min.js,prop-types/15.7.2/prop-types.min.js - assetItem(AssetType.JSText, 'window.React=parent.React;window.ReactDOM=parent.ReactDOM;window.__is_simulator_env__=true;', undefined, 'react'), + assetItem( + AssetType.JSText, + 'window.React=parent.React;window.ReactDOM=parent.ReactDOM;window.__is_simulator_env__=true;', + undefined, + 'react', + ), assetItem( AssetType.JSText, 'window.PropTypes=parent.PropTypes;React.PropTypes=parent.PropTypes; window.__REACT_DEVTOOLS_GLOBAL_HOOK__ = window.parent.__REACT_DEVTOOLS_GLOBAL_HOOK__;', ), - ]; const defaultRaxEnvironment = [ @@ -217,7 +242,10 @@ export class BuiltinSimulatorHost implements ISimulatorHost void; firstRun: boolean }) => void) { + connect( + renderer: BuiltinSimulatorRenderer, + fn: (context: { dispose: () => void; firstRun: boolean }) => void, + ) { this._renderer = renderer; return autorun(fn as any, true); } @@ -294,7 +325,7 @@ export class BuiltinSimulatorHost implements ISimulatorHostwindow.${item.library}});`); + libraryExportList.push( + `Object.defineProperty(window,'${item.exportName}',{get:()=>window.${item.library}});`, + ); } if (item.urls) { libraryAsset.push(item.urls); @@ -337,7 +370,8 @@ export class BuiltinSimulatorHost implements ISimulatorHost 0) { // 加载异步Library await renderer.loadAsyncLibrary(this.asyncLibraryMap); @@ -384,7 +417,6 @@ export class BuiltinSimulatorHost implements ISimulatorHost { // console.log('add node', node); @@ -501,20 +537,82 @@ export class BuiltinSimulatorHost implements ISimulatorHost { + const _nodeInst = this.getNodeInstanceFromElement(e.target as Element); + const { isRGL: _isRGL, rglNode: _rglNode } = this.getRGLObject( + _nodeInst as NodeInstance, + ); + const status = !!( + _isRGL && + _rglNode?.id !== rglNode?.id && + _rglNode?.getParent() !== node && + nodeInst?.node !== _nodeInst?.node + ); + return { status, rglNode: _rglNode }; + }; + const move = (e: MouseEvent) => { + if (!isShaken(downEvent, e)) { + if (nodeInst.instance && nodeInst.instance.style) { + nodeInst.instance.style.pointerEvents = 'none'; + } + } + const { status, rglNode: _rglNode } = judgeEnterOtherRGL(e); + if (status) { + designer.dragon.emitter.emit('rgl.add.placeholder', { + rglNode: _rglNode, + node, + event: e, + fromRglNode: rglNode, + }); + } else { + designer.dragon.emitter.emit('rgl.remove.placeholder'); + } + }; + const over = (e: MouseEvent) => { + const { status, rglNode: _rglNode } = judgeEnterOtherRGL(e); + if (status) { + designer.dragon.emitter.emit('rgl.drop', { + rglNode: _rglNode, + node, + fromRglNode: rglNode, + }); + } + designer.dragon.emitter.emit('rgl.remove.placeholder'); + if (nodeInst.instance && nodeInst.instance.style) { + nodeInst.instance.style.pointerEvents = ''; + } + designer.dragon.emitter.emit('rgl.switch', { + action: 'end', + rglNode, + }); + doc.removeEventListener('mouseup', over, true); + doc.removeEventListener('mousemove', move, true); + }; + doc.addEventListener('mouseup', over, true); + doc.addEventListener('mousemove', move, true); + } else { + // stop response document focus event + downEvent.stopPropagation(); + downEvent.preventDefault(); + } // if (!node?.isValidComponent()) { // // 对于未注册组件直接返回 // return; @@ -522,8 +620,8 @@ export class BuiltinSimulatorHost implements ISimulatorHost { doc.removeEventListener('mouseup', checkSelect, true); - // 鼠标是否移动 - if (!isShaken(downEvent, e)) { + // 鼠标是否移动 ? - 鼠标抖动应该也需要支持选中事件,偶尔点击不能选中,磁帖块移除shaken检测 + if (!isShaken(downEvent, e) || isRGLNode) { let { id } = node; designer.activeTracker.track({ node, instance: nodeInst?.instance }); if (isMulti && !isRootNode(node) && selection.has(id)) { @@ -531,10 +629,7 @@ export class BuiltinSimulatorHost implements ISimulatorHost void; private disableDetecting?: () => void; + + /** + * 获取node实例的磁贴相关信息 + */ + getRGLObject(nodeInst: NodeInstance) { + const isContainerNode = nodeInst?.node.isContainer(); + const isEmptyNode = nodeInst?.node?.isEmpty(); + const isRGLContainerNode = nodeInst?.node?.isRGLContainer; + const isRGLNode = nodeInst?.node?.getParent()?.isRGLContainer; + const isRGL = isRGLContainerNode || (isRGLNode && (!isContainerNode || !isEmptyNode)); + let rglNode = isRGLContainerNode ? nodeInst?.node : isRGL ? nodeInst?.node?.getParent() : {}; + return { isContainerNode, isEmptyNode, isRGLContainerNode, isRGLNode, isRGL, rglNode }; + } + /** * 设置悬停处理 */ @@ -698,7 +809,10 @@ export class BuiltinSimulatorHost implements ISimulatorHost // 可能是 [null]; item && item.contains(targetElement), @@ -851,7 +965,10 @@ export class BuiltinSimulatorHost implements ISimulatorHost | null { + getClosestNodeInstance( + from: ComponentInstance, + specId?: string, + ): NodeInstance | null { return this.renderer?.getClosestNodeInstance(from, specId) || null; } @@ -978,7 +1095,7 @@ export class BuiltinSimulatorHost implements ISimulatorHost= rect.top && e.globalY <= rect.bottom && e.globalX >= rect.left && e.globalX <= rect.right; + return ( + e.globalY >= rect.top && + e.globalY <= rect.bottom && + e.globalX >= rect.left && + e.globalX <= rect.right + ); } private sensing = false; @@ -1157,7 +1279,10 @@ export class BuiltinSimulatorHost implements ISimulatorHost 1 - ? instances.find((_inst) => this.getClosestNodeInstance(_inst, container.id)?.instance === containerInstance) + ? instances.find( + (_inst) => + this.getClosestNodeInstance(_inst, container.id)?.instance === containerInstance, + ) : instances[0] : null; - const rect = inst ? this.computeComponentInstanceRect(inst, node.componentMeta.rootSelector) : null; + const rect = inst + ? this.computeComponentInstanceRect(inst, node.componentMeta.rootSelector) + : null; if (!rect) { continue; @@ -1344,7 +1474,10 @@ export class BuiltinSimulatorHost implements ISimulatorHost, e: LocateEvent) { + getNearByContainer( + { container, instance }: DropContainer, + drillDownExcludes: Set, + e: LocateEvent, + ) { const { children } = container; const document = this.project.currentDocument!; if (!children || children.isEmpty()) { diff --git a/packages/designer/src/designer/designer.ts b/packages/designer/src/designer/designer.ts index bab9d20ab..3d885088e 100644 --- a/packages/designer/src/designer/designer.ts +++ b/packages/designer/src/designer/designer.ts @@ -13,7 +13,14 @@ import { } from '@ali/lowcode-types'; import { megreAssets, AssetsJson } from '@ali/lowcode-utils'; import { Project } from '../project'; -import { Node, DocumentModel, insertChildren, isRootNode, ParentalNode, TransformStage } from '../document'; +import { + Node, + DocumentModel, + insertChildren, + isRootNode, + ParentalNode, + TransformStage, +} from '../document'; import { ComponentMeta } from '../component-meta'; import { INodeSelector, Component } from '../simulator'; import { Scroller, IScrollable } from './scroller'; @@ -125,7 +132,7 @@ export class Designer { } else if (isDragNodeDataObject(dragObject)) { // process nodeData const nodeData = Array.isArray(dragObject.data) ? dragObject.data : [dragObject.data]; - const isNotNodeSchema = nodeData.find(item => !isNodeSchema(item)); + const isNotNodeSchema = nodeData.find((item) => !isNodeSchema(item)); if (isNotNodeSchema) { return; } @@ -213,7 +220,11 @@ export class Designer { } const { currentSelection } = this; // TODO: 避免选中 Page 组件,默认选中第一个子节点;新增规则 或 判断 Live 模式 - if (currentSelection && currentSelection.selected.length === 0 && this.simulatorProps?.designMode === 'live') { + if ( + currentSelection && + currentSelection.selected.length === 0 && + this.simulatorProps?.designMode === 'live' + ) { const rootNodeChildrens = this.currentDocument.getRoot().getChildren().children; if (rootNodeChildrens.length > 0) { currentSelection.select(rootNodeChildrens[0].id); @@ -301,12 +312,18 @@ export class Designer { /** * 获得合适的插入位置 */ - getSuitableInsertion(insertNode?: Node | NodeSchema | NodeSchema[]): { target: ParentalNode; index?: number } | null { + getSuitableInsertion( + insertNode?: Node | NodeSchema | NodeSchema[], + ): { target: ParentalNode; index?: number } | null { const activedDoc = this.project.currentDocument; if (!activedDoc) { return null; } - if (Array.isArray(insertNode) && isNodeSchema(insertNode[0]) && this.getComponentMeta(insertNode[0].componentName).isModal) { + if ( + Array.isArray(insertNode) && + isNodeSchema(insertNode[0]) && + this.getComponentMeta(insertNode[0].componentName).isModal + ) { return { target: activedDoc.rootNode as ParentalNode, }; @@ -355,7 +372,10 @@ export class Designer { if (props.suspensed !== this.props.suspensed && props.suspensed != null) { this.suspensed = props.suspensed; } - if (props.componentMetadatas !== this.props.componentMetadatas && props.componentMetadatas != null) { + if ( + props.componentMetadatas !== this.props.componentMetadatas && + props.componentMetadatas != null + ) { this.buildComponentMetasMap(props.componentMetadatas); } } else { @@ -477,7 +497,10 @@ export class Designer { return this.props?.globalComponentActions || null; } - getComponentMeta(componentName: string, generateMetadata?: () => ComponentMetadata | null): ComponentMeta { + getComponentMeta( + componentName: string, + generateMetadata?: () => ComponentMetadata | null, + ): ComponentMeta { if (this._componentMetasMap.has(componentName)) { return this._componentMetasMap.get(componentName)!; } @@ -558,4 +581,8 @@ export class Designer { } export type PropsReducerContext = { stage: TransformStage }; -export type PropsReducer = (props: CompositeObject, node: Node, ctx?: PropsReducerContext) => CompositeObject; +export type PropsReducer = ( + props: CompositeObject, + node: Node, + ctx?: PropsReducerContext, +) => CompositeObject; diff --git a/packages/designer/src/designer/dragon.ts b/packages/designer/src/designer/dragon.ts index b53b6cf20..c336039ea 100644 --- a/packages/designer/src/designer/dragon.ts +++ b/packages/designer/src/designer/dragon.ts @@ -4,7 +4,7 @@ import { NodeSchema } from '@ali/lowcode-types'; import { setNativeSelection, cursor } from '@ali/lowcode-utils'; import { DropLocation } from './location'; import { Node, DocumentModel } from '../document'; -import { ISimulatorHost, isSimulatorHost } from '../simulator'; +import { ISimulatorHost, isSimulatorHost, NodeInstance, ComponentInstance } from '../simulator'; import { Designer } from './designer'; export interface LocateEvent { @@ -72,6 +72,14 @@ export interface ISensor { * 取消激活 */ deactiveSensor(): void; + /** + * 获取节点实例 + */ + getNodeInstanceFromElement(e: Element | null): NodeInstance | null; + /** + * 获取RGL相关信息 + */ + getRGLObject(nodeInst: NodeInstance): any; } export type DragObject = DragNodeObject | DragNodeDataObject | DragAnyObject; @@ -126,7 +134,9 @@ export function isShaken(e1: MouseEvent | DragEvent, e2: MouseEvent | DragEvent) if (e1.target !== e2.target) { return true; } - return Math.pow(e1.clientY - e2.clientY, 2) + Math.pow(e1.clientX - e2.clientX, 2) > SHAKE_DISTANCE; + return ( + Math.pow(e1.clientY - e2.clientY, 2) + Math.pow(e1.clientX - e2.clientX, 2) > SHAKE_DISTANCE + ); } function isInvalidPoint(e: any, last: any): boolean { @@ -199,6 +209,8 @@ export class Dragon { @obx.ref private _dragging = false; + @obx.ref private _canDrop = false; + get dragging(): boolean { return this._dragging; } @@ -239,17 +251,26 @@ export class Dragon { * @param dragObject 拖拽对象 * @param boostEvent 拖拽初始时事件 */ - boost(dragObject: DragObject, boostEvent: MouseEvent | DragEvent) { + boost(dragObject: DragObject, boostEvent: MouseEvent | DragEvent, isFromRGLNode?: boolean) { const { designer } = this; const masterSensors = this.getMasterSensors(); const handleEvents = makeEventsHandler(boostEvent, masterSensors); const newBie = !isDragNodeObject(dragObject); - const forceCopyState = isDragNodeObject(dragObject) && dragObject.nodes.some((node) => node.isSlot()); + const forceCopyState = + isDragNodeObject(dragObject) && dragObject.nodes.some((node) => node.isSlot()); const isBoostFromDragAPI = isDragEvent(boostEvent); let lastSensor: ISensor | undefined; this._dragging = false; + const getRGLObject = (e: MouseEvent | DragEvent) => { + const locateEvent = createLocateEvent(e); + const sensor = chooseSensor(locateEvent); + if (!sensor || !sensor.getNodeInstanceFromElement) return {}; + const nodeInst = sensor.getNodeInstanceFromElement(e.target as Element); + return sensor.getRGLObject(nodeInst as NodeInstance); + }; + const checkesc = (e: KeyboardEvent) => { if (e.keyCode === 27) { designer.clearLocation(); @@ -304,13 +325,31 @@ export class Dragon { const locateEvent = createLocateEvent(e); const sensor = chooseSensor(locateEvent); - if (sensor) { - sensor.fixEvent(locateEvent); - sensor.locate(locateEvent); - } else { + this._canDrop = !!sensor?.locate(locateEvent); + const { isRGL, rglNode } = getRGLObject(e); + if (isRGL) { + // 禁止原生响应 + if (!isFromRGLNode && this._canDrop) { + this.emitter.emit('rgl.add.placeholder', { + rglNode, + node: locateEvent.dragObject.nodes[0], + event: e, + }); + } designer.clearLocation(); + this.clearState(); + this.emitter.emit('drag', locateEvent); + } else { + if (!isFromRGLNode && this._canDrop) { + this.emitter.emit('rgl.remove.placeholder'); + } + if (sensor) { + sensor.fixEvent(locateEvent); + } else { + designer.clearLocation(); + } + this.emitter.emit('drag', locateEvent); } - this.emitter.emit('drag', locateEvent); }; const dragstart = () => { @@ -363,6 +402,20 @@ export class Dragon { // end-tail drag process const over = (e?: any) => { + // 发送drop事件 + if (e) { + const { isRGL, rglNode } = getRGLObject(e); + if (isRGL && !isFromRGLNode && this._canDrop) { + const tarNode = dragObject.nodes[0]; + this.emitter.emit('rgl.drop', { + rglNode, + node: tarNode, + }); + const { selection } = designer.project.currentDocument; + selection.select(tarNode.id); + } + } + /* istanbul ignore next */ if (e && isDragEvent(e)) { e.preventDefault(); @@ -461,7 +514,10 @@ export class Dragon { const chooseSensor = (e: LocateEvent) => { // this.sensors will change on dragstart const sensors: ISensor[] = (masterSensors as ISensor[]).concat(this.sensors); - let sensor = e.sensor && e.sensor.isEnter(e) ? e.sensor : sensors.find((s) => s.sensorAvailable && s.isEnter(e)); + let sensor = + e.sensor && e.sensor.isEnter(e) + ? e.sensor + : sensors.find((s) => s.sensorAvailable && s.isEnter(e)); if (!sensor) { // TODO: enter some area like componentspanel cancel if (lastSensor) { @@ -547,7 +603,7 @@ export class Dragon { } private getSimulators() { - return new Set(this.designer.project.documents.map(doc => doc.simulator)); + return new Set(this.designer.project.documents.map((doc) => doc.simulator)); } // #region ======== drag and drop helpers ============ diff --git a/packages/engine/src/engine-core.ts b/packages/engine/src/engine-core.ts index a4b1c21ab..752c7066f 100644 --- a/packages/engine/src/engine-core.ts +++ b/packages/engine/src/engine-core.ts @@ -195,6 +195,10 @@ interface EngineOptions { * 关闭画布自动渲染,在资产包多重异步加载的场景有效,默认值:false */ disableAutoRender?: boolean; + /** + * 关闭拖拽组件时的虚线响应,性能考虑,默认值:false + */ + disableDetecting?: boolean; /** * Vision-polyfill settings */ @@ -203,7 +207,7 @@ interface EngineOptions { disableCompatibleReducer?: boolean; // 是否开启在 render 阶段开启 filter reducer,默认值:false enableFilterReducerInRenderStage?: boolean; - } + }; [key: string]: any; } export async function init(container?: Element, options?: EngineOptions) {