Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(extension): perform topGroupZIndex calibration when add node(#1535) #1554

Merged
merged 1 commit into from
Apr 9, 2024

Conversation

wbccb
Copy link
Contributor

@wbccb wbccb commented Apr 8, 2024

fix #1535

问题发生的原因

https://examples.logic-flow.cn/demo/dist/pool的调试中,发现两个GroupNode类型的Node数据的初始化zIndex=1

当点击右边的矩形时,会触发node:click方法,一共有两个地方

// packages/core/src/view/node/BaseNode.tsx
handleClick = (e) => {
  //...
  if (model.isSelected && !isDoubleClick && isMultiple) {
    //...
  } else {
    graphModel.selectNodeById(model.id, isMultiple);
    eventOptions.isSelected = true;
    this.toFront();
  }
  //...
};
toFront() {
  const { model, graphModel } = this.props;
  if (model.autoToFront) {
    graphModel.toFront(model.id);
  }
}

由于GroupNode类型的Node默认autoToFront=false,因此不会触发上面代码中的graphModel.toFront(model.id)

class GroupNodeModel extends RectResize.model {
  initNodeData(data) {
    //...
    this.autoToFront = false;
    //...
  }
}

除了BaseNode.tsx监听node:click之外,还有Group也监听node:click

const DEFAULT_BOTTOM_Z_INDEX = -10000;
class Group {
   topGroupZIndex = DEFAULT_BOTTOM_Z_INDEX;
  constructor({ lf }) {
    lf.on("node:click", this.nodeSelected);
  }

  nodeSelected = ({ data, isMultiple, isSelected }) => {
    const nodeModel = this.lf.getNodeModelById(data.id);
    this.toFrontGroup(nodeModel);
    // 重置所有的group zIndex,防止group节点zIndex增长为正。
    if (this.topGroupZIndex > DEFAULT_TOP_Z_INDEX) {
      //...
    }
  };
  toFrontGroup = (model) => {
    if (!model || !model.isGroup) {
      return;
    }
    this.topGroupZIndex++;
    model.setZIndex(this.topGroupZIndex);
    if (model.children) {
      model.children.forEach((nodeId) => {
        const node = this.lf.getNodeModelById(nodeId);
        this.toFrontGroup(node);
      });
    }
  };
}

从上面代码我们可以清晰知道,当触发toFrontGroup()时,由于GroupNode类型isGroup=true,因此会继续往下走

  • 此时this.topGroupZIndex=-10000this.topGroupZIndex++之后就是-9999
  • 然后model.setZIndex(this.topGroupZIndex)等于model.setZIndex(-9999)

但是我们要记住一个东西,两个GroupNode类型的Node数据的初始化zIndex=1,也就是说点击的Node.zIndex变为-999,但是未点击的Node.zIndex还是1,因此就造成了#1535视频中的假选状态,本质就是点击Node的zIndex低于未点击Node的zIndex,被盖住了

解决方法

初步方案

我们从this.topGroupZIndex的字面含义就可以知道,其值代表着最顶部的groupzIndex,如果用户不在外部随便设置zIndexGroupNode类型的Node数据默认初始化就是this.zIndex = DEFAULT_BOTTOM_Z_INDEX=-10000,也就是this.topGroupZIndex的初始化值

this.topGroupZIndex的初始化值是-10000,外部设置了zIndex=1,因此导致了this.topGroupZIndex的作用消失了

因此我们增加一个校准方法

checkAndCorrectTopGroupZIndex = (nodeZIndex: number) => {
  if (nodeZIndex > DEFAULT_TOP_Z_INDEX) {
    // 说明this.topGroupZIndex已经失去意义,代表不了目前最高zIndex的group,需要重新校准
    const allGroupNodes = this.lf.graphModel.nodes.filter(
      (node: BaseNodeModel) => node.isGroup
    );
    let max = this.topGroupZIndex;
    for (let i = 0; i < allGroupNodes.length; i++) {
      const groupNode = allGroupNodes[i];
      if (groupNode.zIndex > max) {
        max = groupNode.zIndex;
      }
    }
    this.topGroupZIndex = max;
  }
};

把它放在toFrontGroup()之前,如下面所示,这样通过checkAndCorrectTopGroupZIndex()的校准,this.topGroupZIndex就等于所有group类型节点中最高的zIndex的值,然后再进行this.toFrontGroup()就可以完美将点击的node的zIndex再增加1!

当然,由于this.topGroupZIndex 大于0了,因此会触发重置所有的group zIndex的一系列逻辑,不过这并不影响已经排序好的node之间的顺序,也就是点击的node仍然是最高zIndex!

nodeSelected = ({ data, isMultiple, isSelected }) => {
  const nodeModel = this.lf.getNodeModelById(data.id);
  this.checkAndCorrectTopGroupZIndex(nodeModel.zIndex);
  this.toFrontGroup(nodeModel);
  // 重置所有的group zIndex,防止group节点zIndex增长为正。
  if (this.topGroupZIndex > DEFAULT_TOP_Z_INDEX) {
    this.topGroupZIndex = DEFAULT_BOTTOM_Z_INDEX;
    const allGroups = this.lf.graphModel.nodes
      .filter((node) => node.isGroup)
      .sort((a, b) => a.zIndex - b.zIndex);
    let preZIndex = 0;
    for (let i = 0; i < allGroups.length; i++) {
      const group = allGroups[i];
      if (group.zIndex !== preZIndex) {
        this.topGroupZIndex++;
        preZIndex = group.zIndex;
      }
      group.setZIndex(this.topGroupZIndex);
    }
  }
  //...
};

优化:去除判断条件

  • 如果此时this.topGroupZIndex=-10000
  • 然后此时一个node.zIndex=1,一个node.zIndex=-10000,一个node.zIndex=-9999
  • 当点击node.zIndex=-10000时,此时node会变为node.zIndex=-9999
  • 但是它还是比其它两个节点的zIndex小,也就是点击这个node还是无法让它zIndex最高
  • 因此无法使用nodeZIndex > DEFAULT_TOP_Z_INDEX 或者 nodeZIndex > DEFAULT_BOTTOM_Z_INDEX 或者 nodeZIndex > this.topGroupZIndex

需要每次触发node:click进行checkAndCorrectTopGroupZIndex()的校验

checkAndCorrectTopGroupZIndex = (nodeZIndex: number) => {
//   if (nodeZIndex > DEFAULT_TOP_Z_INDEX) {
    const allGroupNodes = this.lf.graphModel.nodes.filter(
      (node: BaseNodeModel) => node.isGroup
    );
    let max = this.topGroupZIndex;
    for (let i = 0; i < allGroupNodes.length; i++) {
      const groupNode = allGroupNodes[i];
      if (groupNode.zIndex > max) {
        max = groupNode.zIndex;
      }
    }
    this.topGroupZIndex = max;
//   }
};

再度优化:初始化时/新增node时进行topGroupZIndex的校准

this.topGroupZIndex代表着目前所有group中最大zIndex,那么按照常理来说,应该在初始化的时候,遍历所有group类型节点进行this.topGroupZIndex的校准

当有新增元素时,由于存在强制构建一个group类型节点的zIndex很大很大,因此我们在每一次新增元素时都得进行一次this.topGroupZIndex的校准

当删除元素时,不会影响this.topGroupZIndex代表所有group类型节点最大zIndex,因此不做处理

lf.on('node:add,node:drop,node:dnd-add', this.appendNodeToGroup);
lf.on('graph:rendered', this.graphRendered);

graphRendered = (data) => {
  if (data && data.nodes) {
    //...
    // 初始化nodes时进行this.topGroupZIndex的校准更新
    this.checkAndCorrectTopGroupZIndex(data.nodes);
  }
};
appendNodeToGroup = ({ data }) => {
    //...
    const nodeModel = this.lf.getNodeModelById(data.id);
    //...
    if (nodeModel.isGroup) {
      // 如果这个节点是分组,那么将其子节点也记录下来
      data.children.forEach((nodeId) => {
        this.nodeGroupMap.set(nodeId, data.id);
      });
      // 新增node时进行this.topGroupZIndex的校准更新
      this.checkAndCorrectTopGroupZIndex([data]);
      this.nodeSelected({ data, isSelected: false, isMultiple: false });
    }
    if (!group) return;
    //...
  };

this.topGroupZIndex的校准新增对目前nodes最大的zIndex的遍历

findNodeAndChildMaxZIndex = (nodeModel: BaseNodeModel) => {
  let maxZIndex = DEFAULT_BOTTOM_Z_INDEX;
  if (nodeModel.isGroup) {
    maxZIndex = nodeModel.zIndex > maxZIndex ? nodeModel.zIndex : maxZIndex;
  }
  if (nodeModel.children) {
    nodeModel.children.forEach((nodeId: string) => {
      if (typeof nodeId === "object") {
        // 正常情况下, GroupNodeModel.children是一个id数组,这里只是做个兼容
        // @ts-ignore
        nodeId = nodeId.id;
      }
      const child = this.lf.getNodeModelById(nodeId);
      if (child.isGroup) {
        const childMaxZIndex = this.findNodeAndChildMaxZIndex(child);
        maxZIndex = childMaxZIndex > maxZIndex ? childMaxZIndex : maxZIndex;
      }
    });
  }
  return maxZIndex;
};
checkAndCorrectTopGroupZIndex = (nodes: NodeData[]) => {
  // 初始化时/增加新节点时,找出新增nodes的最大zIndex
  let maxZIndex = DEFAULT_BOTTOM_Z_INDEX;
  nodes.forEach((node: NodeData) => {
    const nodeModel = this.lf.getNodeModelById(node.id);
    const currentNodeMaxZIndex = this.findNodeAndChildMaxZIndex(nodeModel);
    if (currentNodeMaxZIndex > maxZIndex) {
      maxZIndex = currentNodeMaxZIndex;
    }
  });

  if (this.topGroupZIndex >= maxZIndex) {
    // 一般是初始化时/增加新节点时发生,因为外部强行设置了一个很大的zIndex
    // 删除节点不会影响目前最高zIndex的赋值
    return;
  }
  // 新增nodes中如果存在zIndex比this.topGroupZIndex大
  // 说明this.topGroupZIndex已经失去意义,代表不了目前最高zIndex的group,需要重新校准

  //.........
};

这样子,我们就可以去除nodeSelected()this.topGroupZIndex校准,因为this.topGroupZIndex总是对的!

nodeSelected = ({ data, isMultiple, isSelected }) => {
          const nodeModel = this.lf.getNodeModelById(data.id);
---     this.checkAndCorrectTopGroupZIndex();
          this.toFrontGroup(nodeModel);
}

单元测试(暂无)

=_=一直报错,无法调通,有空再补,先把bug修复了

@DymoneLewis DymoneLewis merged commit aedc555 into didi:master Apr 9, 2024
@wbccb wbccb deleted the fix/1535 branch April 9, 2024 03:14
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
2 participants