feat: support drill down logic by kangwei to #35417409

This commit is contained in:
alex.mm 2021-07-15 14:07:09 +08:00
parent cfde6e49b9
commit 98065b7a62
11 changed files with 115 additions and 48 deletions

View File

@ -80,6 +80,26 @@ export class BorderDetecting extends Component<{ host: BuiltinSimulatorHost }> {
if (!canHover || !current || host.viewport.scrolling || host.liveEditing.editing) { if (!canHover || !current || host.viewport.scrolling || host.liveEditing.editing) {
return null; return null;
} }
// rootNode, hover whole viewport
const focusNode = current.document.focusNode;
if (current.contains(focusNode)) {
const bounds = host.viewport.bounds;
return (
<BorderDetectingInstance
key="line-root"
title={current.title}
scale={this.scale}
scrollX={host.viewport.scrollX}
scrollY={host.viewport.scrollY}
rect={new DOMRect(0, 0, bounds.width, bounds.height)}
/>
);
} else if (!focusNode.contains(current)) {
return null;
}
const instances = host.getComponentInstances(current); const instances = host.getComponentInstances(current);
if (!instances || instances.length < 1) { if (!instances || instances.length < 1) {
return null; return null;

View File

@ -540,7 +540,8 @@ export class BuiltinSimulatorHost implements ISimulatorHost<BuiltinSimulatorProp
// FIXME: dirty fix remove label-for fro liveEditing // FIXME: dirty fix remove label-for fro liveEditing
(downEvent.target as HTMLElement).removeAttribute('for'); (downEvent.target as HTMLElement).removeAttribute('for');
const nodeInst = this.getNodeInstanceFromElement(downEvent.target as Element); const nodeInst = this.getNodeInstanceFromElement(downEvent.target as Element);
const node = getClosestClickableNode(nodeInst?.node || documentModel?.rootNode, downEvent); const focusNode = documentModel.focusNode;
const node = getClosestClickableNode(nodeInst?.node || focusNode, downEvent);
// 如果找不到可点击的节点, 直接返回 // 如果找不到可点击的节点, 直接返回
if (!node) { if (!node) {
return; return;
@ -624,7 +625,7 @@ export class BuiltinSimulatorHost implements ISimulatorHost<BuiltinSimulatorProp
if (!isShaken(downEvent, e) || isRGLNode) { if (!isShaken(downEvent, e) || isRGLNode) {
let { id } = node; let { id } = node;
designer.activeTracker.track({ node, instance: nodeInst?.instance }); designer.activeTracker.track({ node, instance: nodeInst?.instance });
if (isMulti && !isRootNode(node) && selection.has(id)) { if (isMulti && !node.contains(focusNode) && selection.has(id)) {
selection.remove(id); selection.remove(id);
} else { } else {
// TODO: 避免选中 Page 组件,默认选中第一个子节点;新增规则 或 判断 Live 模式 // TODO: 避免选中 Page 组件,默认选中第一个子节点;新增规则 或 判断 Live 模式
@ -632,7 +633,7 @@ export class BuiltinSimulatorHost implements ISimulatorHost<BuiltinSimulatorProp
const firstChildId = node.getChildren()?.get(0)?.getId(); const firstChildId = node.getChildren()?.get(0)?.getId();
if (firstChildId) id = firstChildId; if (firstChildId) id = firstChildId;
} }
selection.select(id); selection.select(node.contains(focusNode) ? focusNode.id : id);
// dirty code should refector // dirty code should refector
const editor = this.designer?.editor; const editor = this.designer?.editor;
@ -647,7 +648,8 @@ export class BuiltinSimulatorHost implements ISimulatorHost<BuiltinSimulatorProp
} }
} }
}; };
if (isLeftButton && !isRootNode(node)) {
if (isLeftButton && !node.contains(focusNode)) {
let nodes: Node[] = [node]; let nodes: Node[] = [node];
let ignoreUpSelected = false; let ignoreUpSelected = false;
if (isMulti) { if (isMulti) {
@ -657,7 +659,7 @@ export class BuiltinSimulatorHost implements ISimulatorHost<BuiltinSimulatorProp
selection.add(node.id); selection.add(node.id);
ignoreUpSelected = true; ignoreUpSelected = true;
} }
selection.remove(documentModel.rootNode.id); selection.remove(focusNode.id);
// 获得顶层 nodes // 获得顶层 nodes
nodes = selection.getTopNodes(); nodes = selection.getTopNodes();
} else if (selection.containsNode(node, true)) { } else if (selection.containsNode(node, true)) {
@ -748,7 +750,16 @@ export class BuiltinSimulatorHost implements ISimulatorHost<BuiltinSimulatorProp
return; return;
} }
const nodeInst = this.getNodeInstanceFromElement(e.target as Element); const nodeInst = this.getNodeInstanceFromElement(e.target as Element);
detecting.capture(nodeInst?.node || null); if (nodeInst?.node) {
let node = nodeInst.node;
const focusNode = node.document.focusNode;
if (node.contains(focusNode)) {
node = focusNode;
}
detecting.capture(node);
} else {
detecting.capture(null);
}
if (!engineConfig.get('enableMouseEventPropagationInCanvas', false) || dragon.dragging) { if (!engineConfig.get('enableMouseEventPropagationInCanvas', false) || dragon.dragging) {
e.stopPropagation(); e.stopPropagation();
} }
@ -794,7 +805,8 @@ export class BuiltinSimulatorHost implements ISimulatorHost<BuiltinSimulatorProp
if (!nodeInst) { if (!nodeInst) {
return; return;
} }
const node = nodeInst.node || this.project.currentDocument?.rootNode; const focusNode = this.project.currentDocument!.focusNode;
const node = nodeInst.node || focusNode;
if (!node || isLowCodeComponent(node)) { if (!node || isLowCodeComponent(node)) {
return; return;
} }
@ -851,7 +863,7 @@ export class BuiltinSimulatorHost implements ISimulatorHost<BuiltinSimulatorProp
if (!nodeInst) { if (!nodeInst) {
return; return;
} }
const node = nodeInst.node || this.project.currentDocument?.rootNode; const node = nodeInst.node || this.project.currentDocument?.focusNode;
if (!node) { if (!node) {
return; return;
} }
@ -1300,7 +1312,7 @@ export class BuiltinSimulatorHost implements ISimulatorHost<BuiltinSimulatorProp
e.dragObject.nodes[0].componentMeta.isModal e.dragObject.nodes[0].componentMeta.isModal
) { ) {
return this.designer.createLocation({ return this.designer.createLocation({
target: document.rootNode, target: document.focusNode,
detail, detail,
source: `simulator${document.id}`, source: `simulator${document.id}`,
event: e, event: e,
@ -1454,7 +1466,7 @@ export class BuiltinSimulatorHost implements ISimulatorHost<BuiltinSimulatorProp
} }
} }
if (p !== container) { if (p !== container) {
container = p || document.rootNode; container = p || document.focusNode;
drillDownExcludes.add(container); drillDownExcludes.add(container);
} }
} }
@ -1543,8 +1555,9 @@ export class BuiltinSimulatorHost implements ISimulatorHost<BuiltinSimulatorProp
handleAccept({ container, instance }: DropContainer, e: LocateEvent): boolean { handleAccept({ container, instance }: DropContainer, e: LocateEvent): boolean {
const { dragObject } = e; const { dragObject } = e;
const document = this.currentDocument!; const document = this.currentDocument!;
if (isRootNode(container)) { const focusNode = document.focusNode;
return document.checkDropTarget(container, dragObject as any); if (isRootNode(container) || container.contains(focusNode)) {
return document.checkDropTarget(focusNode, dragObject as any);
} }
const meta = (container as Node).componentMeta; const meta = (container as Node).componentMeta;

View File

@ -31,7 +31,13 @@ export default class InstanceNodeSelector extends React.Component<IProps, IState
// 获取节点的父级节点最多获取5层 // 获取节点的父级节点最多获取5层
getParentNodes = (node: Node) => { getParentNodes = (node: Node) => {
const parentNodes = []; const parentNodes: any[] = [];
const focusNode = node.document.focusNode;
if (node.contains(focusNode) || !focusNode.contains(node)) {
return parentNodes;
}
let currentNode: UnionNode = node; let currentNode: UnionNode = node;
while (currentNode && parentNodes.length < 5) { while (currentNode && parentNodes.length < 5) {
@ -39,6 +45,9 @@ export default class InstanceNodeSelector extends React.Component<IProps, IState
if (currentNode) { if (currentNode) {
parentNodes.push(currentNode); parentNodes.push(currentNode);
} }
if (currentNode === focusNode) {
break;
}
} }
return parentNodes; return parentNodes;
}; };
@ -72,7 +81,10 @@ export default class InstanceNodeSelector extends React.Component<IProps, IState
}; };
renderNodes = (/* node: Node */) => { renderNodes = (/* node: Node */) => {
const nodes = this.state.parentNodes || []; const nodes = this.state.parentNodes;
if (!nodes || nodes.length < 1) {
return null;
}
const children = nodes.map((node, key) => { const children = nodes.map((node, key) => {
return ( return (
<div <div

View File

@ -13,14 +13,7 @@ import {
} from '@ali/lowcode-types'; } from '@ali/lowcode-types';
import { megreAssets, AssetsJson } from '@ali/lowcode-utils'; import { megreAssets, AssetsJson } from '@ali/lowcode-utils';
import { Project } from '../project'; import { Project } from '../project';
import { import { Node, DocumentModel, insertChildren, ParentalNode, TransformStage } from '../document';
Node,
DocumentModel,
insertChildren,
isRootNode,
ParentalNode,
TransformStage,
} from '../document';
import { ComponentMeta } from '../component-meta'; import { ComponentMeta } from '../component-meta';
import { INodeSelector, Component } from '../simulator'; import { INodeSelector, Component } from '../simulator';
import { Scroller, IScrollable } from './scroller'; import { Scroller, IScrollable } from './scroller';
@ -45,6 +38,7 @@ export interface DesignerProps {
suspensed?: boolean; suspensed?: boolean;
componentMetadatas?: ComponentMetadata[]; componentMetadatas?: ComponentMetadata[];
globalComponentActions?: ComponentAction[]; globalComponentActions?: ComponentAction[];
focusNodeSelector?: (rootNode: Node) => Node;
onMount?: (designer: Designer) => void; onMount?: (designer: Designer) => void;
onDragstart?: (e: LocateEvent) => void; onDragstart?: (e: LocateEvent) => void;
onDrag?: (e: LocateEvent) => void; onDrag?: (e: LocateEvent) => void;
@ -328,19 +322,20 @@ export class Designer {
target: activedDoc.rootNode as ParentalNode, target: activedDoc.rootNode as ParentalNode,
}; };
} }
const focusNode = activedDoc.focusNode;
const nodes = activedDoc.selection.getNodes(); const nodes = activedDoc.selection.getNodes();
const refNode = nodes.find(item => focusNode.contains(item));
let target; let target;
let index: number | undefined; let index: number | undefined;
if (!nodes || nodes.length < 1) { if (!refNode || refNode === focusNode) {
target = activedDoc.rootNode; target = focusNode;
} else { } else {
const node = nodes[0]; if (refNode.componentMeta.isContainer) {
if (isRootNode(node) || node.componentMeta.isContainer) { target = refNode;
target = node;
} else { } else {
// FIXME!!, parent maybe null // FIXME!!, parent maybe null
target = node.parent!; target = refNode.parent!;
index = node.index + 1; index = refNode.index + 1;
} }
} }

View File

@ -105,7 +105,8 @@ export class OffsetObserver {
this.node = node; this.node = node;
const doc = node.document; const doc = node.document;
const host = doc.simulator!; const host = doc.simulator!;
this.isRoot = isRootNode(node); const focusNode = doc.focusNode;
this.isRoot = node.contains(focusNode);
this.viewport = host.viewport; this.viewport = host.viewport;
if (this.isRoot) { if (this.isRoot) {
this.hasOffset = true; this.hasOffset = true;

View File

@ -93,6 +93,23 @@ export class DocumentModel {
this.rootNode?.getExtraProp('fileName', true)?.setValue(fileName); this.rootNode?.getExtraProp('fileName', true)?.setValue(fileName);
} }
@computed get focusNode() {
if (this._drillDownNode) {
return this._drillDownNode;
}
const selector = this.designer.get('focusNodeSelector');
if (typeof selector === 'function') {
return selector(this.rootNode);
}
return this.rootNode;
}
@obx.ref private _drillDownNode: Node | null = null;
drillDown(node: Node | null) {
this._drillDownNode = node;
}
private _modalNode?: ParentalNode; private _modalNode?: ParentalNode;
private _blank?: boolean; private _blank?: boolean;
@ -151,7 +168,7 @@ export class DocumentModel {
} }
get currentRoot() { get currentRoot() {
return this.modalNode || this.rootNode; return this.modalNode || this.focusNode;
} }
addWillPurge(node: Node) { addWillPurge(node: Node) {
@ -346,6 +363,7 @@ export class DocumentModel {
} }
import(schema: RootSchema, checkId = false) { import(schema: RootSchema, checkId = false) {
const drillDownNodeId = this._drillDownNode?.id;
// TODO: 暂时用饱和式删除,原因是 Slot 节点并不是树节点,无法正常递归删除 // TODO: 暂时用饱和式删除,原因是 Slot 节点并不是树节点,无法正常递归删除
this.nodes.forEach(node => { this.nodes.forEach(node => {
if (node.isRoot()) return; if (node.isRoot()) return;
@ -363,6 +381,9 @@ export class DocumentModel {
} }
// todo: select added and active track added // todo: select added and active track added
if (drillDownNodeId) {
this.drillDown(this.getNode(drillDownNodeId));
}
} }
export(stage: TransformStage = TransformStage.Serilize) { export(stage: TransformStage = TransformStage.Serilize) {

View File

@ -102,7 +102,7 @@ export class Selection {
containsNode(node: Node, excludeRoot = false) { containsNode(node: Node, excludeRoot = false) {
for (const id of this._selected) { for (const id of this._selected) {
const parent = this.doc.getNode(id); const parent = this.doc.getNode(id);
if (excludeRoot && parent === this.doc.rootNode) { if (excludeRoot && parent?.contains(this.doc.focusNode)) {
continue; continue;
} }
if (parent?.contains(node)) { if (parent?.contains(node)) {
@ -134,7 +134,7 @@ export class Selection {
for (const id of this._selected) { for (const id of this._selected) {
const node = this.doc.getNode(id); const node = this.doc.getNode(id);
// 排除根节点 // 排除根节点
if (!node || (!includeRoot && node === this.doc.rootNode)) { if (!node || (!includeRoot && node.contains(this.doc.focusNode))) {
continue; continue;
} }
let i = nodes.length; let i = nodes.length;

View File

@ -64,13 +64,18 @@ export class SettingsPrimaryPane extends Component<{ editor: Editor; config: any
const designer = editor.get(Designer); const designer = editor.get(Designer);
const current = designer?.currentSelection?.getNodes()?.[0]; const current = designer?.currentSelection?.getNodes()?.[0];
let node: Node | null = settings.first; let node: Node | null = settings.first;
const focusNode = node.document.focusNode;
const items = []; const items = [];
let l = 3; let l = 3;
while (l-- > 0 && node) { while (l-- > 0 && node) {
const _node = node; const _node = node;
// dirty code: should remove
if (shouldIgnoreRoot && node.isRoot()) { if (shouldIgnoreRoot && node.isRoot()) {
node = null; break;
continue; }
if (node.contains(focusNode)) {
l = 0;
} }
const props = const props =
l === 2 l === 2

View File

@ -164,7 +164,7 @@ export class OutlineMain implements ISensor, ITreeBoard, IScrollable {
const componentMeta = e.dragObject.nodes ? e.dragObject.nodes[0].componentMeta : null; const componentMeta = e.dragObject.nodes ? e.dragObject.nodes[0].componentMeta : null;
if (e.dragObject.type === 'node' && componentMeta && componentMeta.isModal) { if (e.dragObject.type === 'node' && componentMeta && componentMeta.isModal) {
return designer.createLocation({ return designer.createLocation({
target: document.rootNode, target: document.focusNode,
detail: { detail: {
type: LocationDetailType.Children, type: LocationDetailType.Children,
index: 0, index: 0,
@ -229,7 +229,7 @@ export class OutlineMain implements ISensor, ITreeBoard, IScrollable {
} }
} }
if (p !== node) { if (p !== node) {
node = p || document.rootNode; node = p || document.focusNode;
treeNode = tree.getTreeNode(node); treeNode = tree.getTreeNode(node);
focusSlots = false; focusSlots = false;
} }

View File

@ -1,18 +1,17 @@
import { DocumentModel, Node } from '@ali/lowcode-designer'; import { DocumentModel, Node } from '@ali/lowcode-designer';
import { computed } from '@ali/lowcode-editor-core';
import TreeNode from './tree-node'; import TreeNode from './tree-node';
export class Tree { export class Tree {
private treeNodesMap = new Map<string, TreeNode>(); private treeNodesMap = new Map<string, TreeNode>();
readonly root: TreeNode;
readonly id: string; readonly id: string;
readonly document: DocumentModel; @computed get root(): TreeNode {
return this.getTreeNode(this.document.focusNode);
}
constructor(document: DocumentModel) { constructor(readonly document: DocumentModel) {
this.document = document;
this.root = this.getTreeNode(document.rootNode);
this.id = document.id; this.id = document.id;
} }

View File

@ -51,11 +51,11 @@ export default class TreeView extends Component<{ tree: Tree }> {
const { node } = treeNode; const { node } = treeNode;
const { designer } = treeNode; const { designer } = treeNode;
const doc = node.document; const doc = node.document;
const { selection } = doc; const { selection, focusNode } = doc;
const { id } = node; const { id } = node;
const isMulti = e.metaKey || e.ctrlKey || e.shiftKey; const isMulti = e.metaKey || e.ctrlKey || e.shiftKey;
designer.activeTracker.track(node); designer.activeTracker.track(node);
if (isMulti && !isRootNode(node) && selection.has(id)) { if (isMulti && !node.contains(focusNode) && selection.has(id)) {
if (!isFormEvent(e.nativeEvent)) { if (!isFormEvent(e.nativeEvent)) {
selection.remove(id); selection.remove(id);
} }
@ -107,13 +107,13 @@ export default class TreeView extends Component<{ tree: Tree }> {
const { node } = treeNode; const { node } = treeNode;
const { designer } = treeNode; const { designer } = treeNode;
const doc = node.document; const doc = node.document;
const { selection } = doc; const { selection, focusNode } = doc;
// TODO: shift selection // TODO: shift selection
const isMulti = e.metaKey || e.ctrlKey || e.shiftKey; const isMulti = e.metaKey || e.ctrlKey || e.shiftKey;
const isLeftButton = e.button === 0; const isLeftButton = e.button === 0;
if (isLeftButton && !isRootNode(node)) { if (isLeftButton && !node.contains(focusNode)) {
let nodes: Node[] = [node]; let nodes: Node[] = [node];
this.ignoreUpSelected = false; this.ignoreUpSelected = false;
if (isMulti) { if (isMulti) {
@ -123,7 +123,8 @@ export default class TreeView extends Component<{ tree: Tree }> {
selection.add(node.id); selection.add(node.id);
this.ignoreUpSelected = true; this.ignoreUpSelected = true;
} }
selection.remove(doc.rootNode.id); // todo: remove rootNodes id
selection.remove(focusNode.id);
// 获得顶层 nodes // 获得顶层 nodes
nodes = selection.getTopNodes(); nodes = selection.getTopNodes();
} else if (selection.has(node.id)) { } else if (selection.has(node.id)) {