Skip to content

Conversation

@xxMudCloudxx
Copy link
Contributor

@xxMudCloudxx xxMudCloudxx commented Jan 29, 2026

功能

新增 DragCanvas 交互,用于自由拖拽画布。默认通过按住 空格键 触发,并支持通过 trigger 参数自定义触发按键(如 ShiftLeft 等)。

自定义触发按键示例:

import { Infographic } from '@antv/infographic';

const instance = new Infographic({
  interactions: [new DragCanvas({ trigger: ['ShiftLeft'] })],
  // ... other init options
});

instance.render(/** syntax */)

为了防止在非预期情况下拦截触发键,代码在 handleKeyDown 中做了多重校验:

  • 输入框保护:如果用户正在输入框(input, textarea, contenteditable)中打字,代码会立即返回,不会拦截。
  • 焦点范围限制:确保只有当焦点在页面主体或编辑器本身时才响应。这防止了当页面上有其他弹窗或独立组件获取焦点时,触发键被误捕获。
  • 鼠标位置校验:通过 this.isHovering 判断鼠标是否在编辑器范围内。只有鼠标悬停在画布上时,按下触发键才会有反应,避免了意外干扰(例如防止用户想滚动网页时却意外触发了画布拖拽)。

Bug Fix

关于 DragElement 选择框逻辑的优化:在测试 spacebarDrag 时发现:即便 spacebarDrag 已持有互斥锁,在拖动的过程中仍会触发选中框。经排查,原因是 drag-element 在尝试竞争锁之前就先执行了 select 逻辑。因此,我把选中逻辑移到了竞争锁的回调函数中。

讨论

均已解决

1. 关于 State 直接更新与 Command 模式的权衡

  • 现状:目前的 MVC 架构中,如果 dragCanvas 的每一次移动都经过 Command 系统,会导致 Undo/Redo 栈被大量中间状态污染。

  • 实现:我采用了“过程直接更新 State,结果合并提交 Command”的策略:

    • 在拖拽过程中直接更新 State 中的 viewBox 以确保性能且不留存历史。

    • 仅在拖拽完全结束时,对比起止状态并发射一次 Command。

  • 评估:这种处理方式在当前架构中是否可接受?

2. 关于 State 类型调整 (Partial)

  • 实现:配合上述修改,为了支持仅更新部分属性(如只更新 viewBox 而非全量配置),我将 State 中相关接口的参数类型调整为了 Partial<ParsedInfographicOptions>

  • 评估:这一改动是否会破坏架构的设计预期?

  • 改动

 // src/editor/commands/UpdateOptions.ts
 export class UpdateOptionsCommand implements ICommand {
   constructor(
     private options: Partial<ParsedInfographicOptions>,
-     private original?: ParsedInfographicOptions,
+     private original?: Partial<ParsedInfographicOptions>,
   ) {}
   //...
   }

 	// src/editor/managers/state.ts
- 	updateOptions(options: ParsedInfographicOptions){}
+	updateOptions(options: Partial<ParsedInfographicOptions>){}

 	// src/editor/types/state.ts
-	updateOptions(options: ParsedInfographicOptions): void;
+	updateOptions(options: Partial<ParsedInfographicOptions>): void;

3. 关于 Space 键激活机制的 UX 探讨

  • 待优化的体验问题:只有在编辑器激活后this.interaction.isActive()才启用 spacebarDrag,但是用户在未点击聚焦的情况下按下 Space 键,预期是拖拽,实际却触发了页面滚动。
  • 建议请求:关于如何很好地处理 Space 键拦截与页面滚动的冲突,请问有什么好的建议吗?

@gemini-code-assist
Copy link
Contributor

Summary of Changes

Hello @xxMudCloudxx, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

此拉取请求主要引入了一个新的编辑器功能,即通过按住空格键来拖拽画布,显著提升了用户体验。为了确保此功能的稳定性和避免与其他交互冲突,代码中包含了精细的激活条件判断。同时,还解决了一个现有拖拽元素选择逻辑中的竞态条件问题,并对状态管理机制进行了优化,以更好地支持这种高性能的交互模式,同时保持命令历史的整洁。

Highlights

  • 新增空格键拖拽画布交互: 引入了 SpacebarDrag 交互,允许用户按住空格键拖拽画布,提升了编辑器的操作便捷性。
  • 健壮的激活逻辑: 为防止非预期行为,拖拽功能在 handleKeyDown 中进行了多重校验,包括输入框保护、焦点范围限制和鼠标悬停检测。
  • 修复拖拽元素选择逻辑: 优化了 DragElement 的选择逻辑,确保在 spacebarDrag 持有互斥锁时,不会提前触发选中框,避免了冲突。
  • 状态更新策略优化: 针对画布拖拽,采用了“过程直接更新 State,结果合并提交 Command”的策略,以平衡性能和 Undo/Redo 历史记录的清晰度。
  • State 类型调整为 Partial: 将 UpdateOptionsCommand 和 StateManager 中 updateOptions 方法的参数类型调整为 Partial,支持局部状态更新。
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

这个 PR 引入了通过空格键拖拽画布的功能,实现得非常出色,特别是对各种边界情况(如输入框保护、焦点范围限制)的考虑很周全。相关的单元测试也很详尽,覆盖了大部分场景。

我对 DragElement 中为修复 bug 而将选中逻辑后移的做法表示赞同,这解决了互斥锁的竞争问题。

关于讨论中提到的 State 更新策略和类型调整,我进行了一些深入分析,并在代码中发现了一些潜在的风险,主要集中在 UpdateOptionsCommand 的实现上。将类型调整为 Partial 是正确的方向,但这需要对 applyundo 的逻辑进行相应调整,以确保命令的正确性和类型安全。我在具体的 review comment 中给出了详细的解释和建议。

总的来说,这是一个高质量的 PR,在解决我提出的几个关键问题后,代码会更加健壮和清晰。

@codecov-commenter
Copy link

codecov-commenter commented Jan 29, 2026

Codecov Report

❌ Patch coverage is 98.38710% with 3 lines in your changes missing coverage. Please review.
✅ Project coverage is 45.22%. Comparing base (bf92fab) to head (006d09f).

Files with missing lines Patch % Lines
src/editor/utils/event.ts 50.00% 3 Missing ⚠️
Additional details and impacted files

Impacted file tree graph

@@            Coverage Diff             @@
##             main     #191      +/-   ##
==========================================
+ Coverage   44.89%   45.22%   +0.33%     
==========================================
  Files         332      334       +2     
  Lines       26473    26635     +162     
  Branches     2081     2115      +34     
==========================================
+ Hits        11884    12046     +162     
  Misses      14579    14579              
  Partials       10       10              
Flag Coverage Δ
infographic 45.22% <98.38%> (+0.33%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

Files with missing lines Coverage Δ
src/editor/commands/UpdateOptions.ts 100.00% <100.00%> (ø)
src/editor/interactions/brush-select.ts 97.35% <100.00%> (+1.80%) ⬆️
src/editor/interactions/drag-canvas.ts 100.00% <100.00%> (ø)
src/editor/interactions/drag-element.ts 84.98% <100.00%> (ø)
src/editor/interactions/index.ts 100.00% <100.00%> (ø)
src/editor/managers/state.ts 98.63% <100.00%> (-0.02%) ⬇️
src/editor/types/interaction.ts 100.00% <ø> (ø)
src/editor/types/state.ts 100.00% <100.00%> (ø)
src/options/types.ts 100.00% <ø> (ø)
src/runtime/options.ts 100.00% <100.00%> (ø)
... and 3 more
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@Aarebecca
Copy link
Contributor

建议是交互改名为 drag-canvas,支持 trigger 参数,基于 keycode 识别触发,默认值为 ['Space']

@xxMudCloudxx
Copy link
Contributor Author

ok我晚点改一下

@xxMudCloudxx xxMudCloudxx changed the title [RFC]feat(editor): add spacebar activated canvas dragging interaction [RFC]feat(editor): add DragCanvas interaction with customizable trigger keys Jan 29, 2026
@xxMudCloudxx
Copy link
Contributor Author

/gemini review

Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

本次 PR 新增了 DragCanvas 交互,允许用户通过可配置的触发键(默认为空格键)来平移画布。该功能的实现很可靠,对事件监听器、焦点和排他性交互锁的处理都非常谨慎。附带的测试也很全面,覆盖了各种激活、拖拽流程和边缘场景。

此外,PR 还包含了一个针对 DragElement 交互的重要 Bug 修复,将选择逻辑推迟到获取排他锁之后执行,这提高了元素拖拽的稳定性和可预见性。

相关的 UpdateOptionsCommandStateManager.updateOptions 的重构,以支持局部更新,也是一个显著的改进。我注意到 StateManager.updateOptions 在处理嵌套的 undefined 值以删除属性时存在一个潜在问题,如果能解决此问题,实现将更加健壮。

总体而言,这是一次出色的功能添加,并对现有代码库进行了深思熟虑的改进。

@xxMudCloudxx
Copy link
Contributor Author

更改说明(Changes)

在后续的commit中我做了以下修改:

缺陷修复 (Bug Fix)

修复 UpdateOptionsCommand 的序列化错误:原 options 参数类型 ParsedInfographicOptions 包含不可序列化的 container: HTMLElement 属性,持久化时会报错。我通过 Omit<ParsedInfographicOptions, 'container'> 构建了新的type类型,确保了指令的可序列化性。

功能重构与增强 (Refactoring)

  • 交互逻辑优化

    • 将交互行为更名为 drag-canvas
    • 新增 trigger 配置项,支持基于 KeyCode 的触发机制,默认值为 ['Space']
    • 引入 KeyCode 联合类型,在提供 IDE 自动补全建议的同时,保持了对字符串定义的灵活性。
  • 命令重构UpdateOptionsCommand目前传的是增量更改而不是全量Options,

  • 状态管理增强 (managers\state.ts)

    • 实现深度合并 (Deep Merge):将 state.updateOptions 的逻辑从浅拷贝升级为深度合并。因为原逻辑的是options是浅层复制的,这就导致了UpdateOptionsCommand传入的增量更改无法更新嵌套对象。

    • 支持属性删除语义:引入递归处理机制(applyUndefinedDeletions),支持通过传入 undefined 来显式删除嵌套对象中的特定属性。

总结

功能点 重构前 (Before) 重构后 (After)
更新模式 浅拷贝 (Shallow Copy) 深度合并 (Deep Merge)
更新命令 发送全量options 发送增量options
序列化 包含 DOM 节点(报错) 已排除非持久化字段
属性删除 不支持 支持通过 undefined 递归删除

待确认项 (Items for Review)

需要维护者确认的:
目前 mergeOptions(this.options, options)(merge options前删除undefine属性) 和this.editor.getDocument().removeAttribute('viewBox')是为了处理滚轮缩放交互中的 viewBox重置功能 ,这是否导致State层和View层有点耦合?

目前的逻辑流是:在 zoom-wheel.ts 中发送 { viewBox: undefined } 的 payload,然后在 state.ts 中触发 DOM 属性的移除,最后会调用 setSVGPadding重新建一个vewBox实现重置。

interactions\zoom-wheel.ts

    if ((event.ctrlKey || event.metaKey) && event.shiftKey) {
      const command = new UpdateOptionsCommand({
        viewBox: undefined,
      });
      void this.commander.execute(command);
      return;
    }

managers\state.ts

  updateOptions(options: UpdatableInfographicOptions) {
    mergeOptions(this.options, options);

    if (this.options.viewBox) {
      this.editor.getDocument().setAttribute('viewBox', this.options.viewBox);
    } else {
      this.editor.getDocument().removeAttribute('viewBox');
      setSVGPadding(
        this.editor.getDocument(),
        parsePadding(this.options.padding),
      );
    }
    // xxx
  }

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants