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

feat: enhance canvas foundation capabilities #1055

Open
wants to merge 5 commits into
base: refactor/develop
Choose a base branch
from

Conversation

SonyLeo
Copy link
Contributor

@SonyLeo SonyLeo commented Jan 21, 2025

English | 简体中文

PR

PR Checklist

Please check if your PR fulfills the following requirements:

  • The commit message follows our Commit Message Guidelines
  • Tests for the changes have been added (for bug fixes / features)
  • Docs have been added / updated (for bug fixes / features)
  • Built its own designer, fully self-validated

PR Type

What kind of change does this PR introduce?

  • Bugfix
  • Feature
  • Code style update (formatting, local variables)
  • Refactoring (no functional changes, no api changes)
  • Build related changes
  • CI related changes
  • Documentation content changes
  • Other... Please describe:

Background and solution

What is the current behavior?

Issue Number: N/A

What is the new behavior?

  1. 右键添加父级容器

    在画布中右键页面元素 -> 添加父级 -> 容器:

    image

    选中指定 容器 点击 鼠标左键,

    image

    点击 画布元素 或点击 大纲树,即可查看新增的父容器

    image

  2. 节点多选能力

点击 Ctrl + 鼠标左键 进行 多选节点 ,支持快捷键操作复制粘贴删除

image

  1. 保存撤销 基础快捷键

焦点在 画布 时,快捷键生效

功能 Win 快捷键 支持多选
保存 Ctrl + S
复制 Ctrl + C
粘贴 Ctrl + V
剪切 Ctrl + X
删除 Del
前进 Ctrl + Y
后退/撤销 Ctrl + Z
多选 Ctrl + 🖱

Does this PR introduce a breaking change?

  • Yes
  • No

Other information

Summary by CodeRabbit

Release Notes

  • New Features

    • Added multi-selection functionality for canvas elements.
    • Introduced container insertion option in context menu.
    • Enhanced keyboard interaction for multi-node operations.
    • Added the ability to filter components based on their group classification.
  • Improvements

    • Updated component initialization logic for materials.
    • Refined node insertion and selection handling.
    • Improved error handling for node operations.
    • Enhanced visibility and control flow in canvas action components.
  • Bug Fixes

    • Corrected style calculations for canvas actions.
    • Fixed clipboard and deletion operations for multiple nodes.

Copy link
Contributor

coderabbitai bot commented Jan 21, 2025

Walkthrough

This pull request introduces comprehensive changes to the canvas and materials management system, focusing on multi-selection functionality, node insertion, and component management. The modifications span multiple files across the canvas and materials packages, enhancing node interaction, keyboard handling, and component filtering. Key improvements include robust multi-node selection, expanded insertion logic for nodes, and more flexible component management through new utility functions.

Changes

File Change Summary
packages/canvas/DesignCanvas/src/api/useCanvas.js Enhanced node insertion logic with parent node validation, unique ID generation, and improved reference node handling.
packages/canvas/container/src/CanvasContainer.vue Added multi-selection support, new reactive references, and updated event handling for node selection.
packages/canvas/container/src/components/CanvasAction.vue Refined visibility logic for resize handles and quick actions with multi-state support.
packages/canvas/container/src/components/CanvasMenu.vue Added new menu item for container insertion.
packages/canvas/container/src/container.js Introduced multi-selection state management and new insertion functions.
packages/canvas/container/src/keyboard.js Enhanced keyboard event handling for multi-node operations.
packages/plugins/materials/src/composable/useMaterial.js Added getComponents function for filtering components by group.
packages/plugins/materials/src/meta/component/src/Main.vue Updated component initialization with group-based filtering.
packages/plugins/materials/src/meta/layout/src/Main.vue Added groupName prop for material group management.

Possibly related PRs

Suggested Labels

ospp-2024

Suggested Reviewers

  • rhlin
  • hexqi
  • gene9831

Poem

🐰 Nodes dance in multi-select glee,
Canvas expands with new energy!
Ctrl-click brings friends together,
Insertion magic, light as a feather.
Code rabbits hop with pure delight! 🎉

✨ Finishing Touches
  • 📝 Generate Docstrings (Beta)

Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media?

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR. (Beta)
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@github-actions github-actions bot added enhancement New feature or request refactor-main refactor/develop branch feature labels Jan 21, 2025
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (11)
packages/canvas/container/src/components/CanvasAction.vue (3)

184-187: Add validation for multiStateLength prop.

While the prop is correctly typed, adding validation would prevent potential issues with negative values.

 multiStateLength: {
   type: Number,
   default: () => 0,
+  validator: (value) => value >= 0
 },

243-256: Consider renaming showQuickAction for clarity.

The computed property showQuickAction could be more descriptive, such as showQuickActionForSingleNode, to better reflect its purpose.


Line range hint 1-735: Consider splitting component responsibilities.

The component currently handles multiple concerns (selection, resize, quick actions, style calculations). Consider:

  1. Extracting the style calculation logic into a composable for better reusability
  2. Splitting the component into smaller, focused components (e.g., CanvasResize, CanvasQuickActions)

This would improve maintainability and make the code more testable.

packages/plugins/materials/src/composable/useMaterial.js (1)

486-495: Consider enhancing error handling and type validation.

While the function implementation is clean and efficient, consider adding:

  1. Type validation for groupName
  2. Explicit error handling for invalid group names
 const getComponents = (components, groupName) => {
   if (!Array.isArray(components)) return []
+  if (typeof groupName !== 'string') return []
+  if (!groupName.trim()) return components
   return components.filter((item) => item.group === groupName)
 }
packages/plugins/materials/src/meta/component/src/Main.vue (1)

91-98: Consider memoizing the initialization result.

The initComponents function is well-implemented but could benefit from memoization to prevent unnecessary recalculations.

+import { computed } from 'vue'

-const initComponents = () => {
+const initComponents = computed(() => {
   const groupName = panelState.materialGroup
   if (groupName) {
     return getComponents(components, groupName)
   }
   return components
-}
+})

Then update the state initialization:

-components: initComponents(),
+components: initComponents.value,
packages/canvas/DesignCanvas/src/api/useCanvas.js (3)

264-271: Improve error handling for missing parent node.

Instead of silently returning an empty object when the parent node is missing, consider throwing an error to help with debugging.

-    if (!parentNode) {
-      return {}
-    }
+    if (!parentNode) {
+      throw new Error('Parent node not found')
+    }

291-308: Consider using a Map for position handling.

The switch statement could be replaced with a Map for better maintainability and performance.

-    switch (position) {
-      case 'before':
-        parentNode.children.unshift(newNodeData)
-        break
-      case 'out':
-        if (childrenNode) {
-          newNodeData.children = Array.isArray(childrenNode) ? [...childrenNode] : [childrenNode]
-          parentNode.children.splice(index, 1, newNodeData)
-        }
-        break
-      case 'bottom':
-        parentNode.children.splice(index + 1, 0, newNodeData)
-        break
-      default:
-        parentNode.children.push(newNodeData)
-        break
-    }
+    const positionHandlers = new Map([
+      ['before', () => parentNode.children.unshift(newNodeData)],
+      ['out', () => {
+        if (childrenNode) {
+          newNodeData.children = Array.isArray(childrenNode) ? [...childrenNode] : [childrenNode]
+          parentNode.children.splice(index, 1, newNodeData)
+        }
+      }],
+      ['bottom', () => parentNode.children.splice(index + 1, 0, newNodeData)],
+      ['default', () => parentNode.children.push(newNodeData)]
+    ])
+    const handler = positionHandlers.get(position) || positionHandlers.get('default')
+    handler()

351-352: Simplify array check using optional chaining.

The array check can be simplified while maintaining safety.

-        if (Array.isArray(nodeItem?.children) && nodeItem?.children.length) {
+        if (nodeItem?.children?.length) {
packages/canvas/container/src/CanvasContainer.vue (3)

35-42: Ensure proper cleanup of the insertContainer panel

The insertion panel for adding a parent container is conditionally rendered based on insertContainer. Ensure that this state is correctly managed to prevent the panel from remaining open unintentionally.

Consider adding a watcher or method to handle the closure of the panel under specific conditions, such as when the user clicks outside the panel or completes the insertion.


213-214: Avoid unnecessary reassignment of insertContainer

Setting insertContainer.value = false on every mousedown event may interfere with the intended functionality.

Review whether this reassignment is necessary here. If the goal is to close the insert container panel when clicking elsewhere, consider adding condition checks or handling this logic within a dedicated method.


229-229: Consistent handling of insertContainer in the mouseup event

Similar to the mousedown event, ensure that setting insertContainer.value = false aligns with the intended user experience.

Confirm that this does not prematurely close the insert container panel during user interactions.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ceb8bae and 283aee0.

📒 Files selected for processing (9)
  • packages/canvas/DesignCanvas/src/api/useCanvas.js (2 hunks)
  • packages/canvas/container/src/CanvasContainer.vue (11 hunks)
  • packages/canvas/container/src/components/CanvasAction.vue (6 hunks)
  • packages/canvas/container/src/components/CanvasMenu.vue (1 hunks)
  • packages/canvas/container/src/container.js (6 hunks)
  • packages/canvas/container/src/keyboard.js (6 hunks)
  • packages/plugins/materials/src/composable/useMaterial.js (2 hunks)
  • packages/plugins/materials/src/meta/component/src/Main.vue (2 hunks)
  • packages/plugins/materials/src/meta/layout/src/Main.vue (2 hunks)
⏰ Context from checks skipped due to timeout of 90000ms (1)
  • GitHub Check: push-check
🔇 Additional comments (34)
packages/canvas/container/src/components/CanvasAction.vue (3)

12-12: LGTM! Improved condition for quick action visibility.

The change from resize to showQuickAction provides better semantics and correctly handles multi-selection scenarios.


31-66: LGTM! Enhanced resize handle visibility logic.

The resize handles are now correctly shown only for single node selection through the showAction computed property.


420-423: LGTM! Added defensive null check.

The null check for doc prevents potential runtime errors and follows defensive programming practices.

packages/plugins/materials/src/meta/layout/src/Main.vue (2)

44-46: LGTM! Clean prop definition.

The groupName prop is well-defined with appropriate type and default value.


55-55: LGTM! Proper state initialization.

The materialGroup is correctly initialized with the prop value in the panel state.

packages/plugins/materials/src/composable/useMaterial.js (1)

525-526: LGTM! Clean export addition.

The getComponents function is properly exported alongside other utility functions.

packages/plugins/materials/src/meta/component/src/Main.vue (2)

57-57: LGTM! Clean function import.

The getComponents function is properly destructured from useMaterial.


101-101: LGTM! Proper state initialization.

The state is correctly initialized using the initComponents function.

packages/canvas/DesignCanvas/src/api/useCanvas.js (1)

276-283: LGTM! Good error handling for reference node.

The error handling for missing reference nodes is well implemented with a descriptive error message.

packages/canvas/container/src/container.js (6)

33-34: LGTM! Position constants updated correctly.

The addition of the OUT position constant aligns with the new parent container functionality.


126-147: LGTM! Well-implemented state extraction.

The getMultiState function properly extracts and validates node state with appropriate null checks.


170-181: LGTM! Efficient multi-selection handling.

The handleMultiState function efficiently manages selection toggling using array methods.


380-393: LGTM! Clean implementation of container insertion.

The insertContainer function follows the same pattern as other insertion functions and properly handles ID generation.


816-818: LGTM! Proper integration of container insertion.

The insertNode function correctly handles the new 'out' position case.


149-168: Consider adding type validation for multiSelectedStates.

The function assumes multiSelectedStates is a ref object with a value property.

packages/canvas/container/src/components/CanvasMenu.vue (1)

109-110: LGTM! Menu item properly integrated.

The new container menu item is correctly added with appropriate code and value properties, maintaining consistency with existing menu items.

packages/canvas/container/src/CanvasContainer.vue (8)

99-101: Initialize containerPanel and insertContainer correctly

The references containerPanel and insertContainer are added to manage the parent container insertion panel. Verify that these are properly initialized and that insertContainer is reactive.

The initialization looks correct. Ensure that any components or methods relying on these references handle them appropriately.


102-103: Confirm computed property multiStateLength

The computed property multiStateLength calculates the length of multiSelectedStates.

This is a good approach to reactively track the number of selected states.


Line range hint 104-116: Verify the modified setCurrentNode function for multi-selection

The setCurrentNode function now accepts a doc parameter and includes logic for handling multi-selection with getMultiState and setMultiState.

Ensure that getMultiState and setMultiState functions are correctly implemented and that they effectively manage the multi-selection state.

If these functions are defined in another file or context, confirm their integration here.


256-256: Register hotkey events consistently

The registerHotkeyEvent(doc) call registers keyboard events within the iframe's document.

Good implementation for capturing keyboard events in the iframe context.


279-279: Include insertContainer in outside click handling

In the mousedown event listener on the window, the check now includes containerPanel.

This ensures that clicks outside the insertContainer panel are handled correctly.


292-295: Implement the insertion logic for the 'out' position

When position === 'out', the insertContainer state is updated accordingly.

This enables the right-click functionality to add a parent container, aligning with the PR objectives.


303-311: Watch multiStateLength to manage the properties panel

The watcher on multiStateLength clears the properties panel when more than one state is selected.

This is a suitable approach to prevent conflicting property displays during multi-selection.


2-14: Review the usage of selectState within the v-for loop

The selectState prop in <canvas-action> is conditionally set using multiStateLength > 1 ? multiState : selectState. This logic could lead to confusion or unintended behavior when multiStateLength is exactly 1.

Consider revising the condition to accurately reflect when multiple selections are involved. For instance:

-    :selectState="multiStateLength > 1 ? multiState : selectState"
+    :selectState="multiStateLength >= 1 ? multiState : selectState"

Alternatively, confirm that when multiStateLength is 1, selectState should indeed be multiState.

packages/canvas/container/src/keyboard.js (10)

13-17: Import and define new reactive states for multi-selection

The imports and reactive references for multiSelectedStates and isCtrlPressed are added.

This sets up the necessary state management for multi-selection and control key detection.


19-28: Define key codes for KEY_S and KEY_CTRL

The constants KEY_S and KEY_CTRL are introduced for handling save shortcuts and control key presses.

Defining key codes as constants improves code readability and maintainability.


48-53: Refactor handlerDelete to support multi-node deletion

The handlerDelete function now iterates over multiSelectedStates to delete multiple nodes.

This enhancement aligns with the multi-selection functionality, allowing users to delete multiple selected nodes simultaneously.


78-83: Implement save functionality with handleSaveEvent

The handleSaveEvent function is added to trigger the save action when Ctrl+S is pressed.

This provides a convenient shortcut for users to save their work, enhancing the user experience.


84-96: Update handlerCtrl to include KEY_S for saving

The control key handler now includes a case for KEY_S to invoke the save functionality.

This integration ensures that pressing Ctrl+S will trigger the save action as expected.


116-130: Handle pasting multiple nodes from the clipboard

The new handleMultiNodesPaste function manages the pasting of multiple nodes when multiple nodes are selected.

This addition enhances the clipboard functionality to work seamlessly with multi-selection.


157-162: Manage control key state with handleKeyupEvent

The handleKeyupEvent function updates the isCtrlPressed state upon key release.

This ensures accurate detection of the control key's state, which is important for multi-selection handling.


163-165: Remove hotkey events appropriately

The removeHotkeyEvent function now also removes the keyup event listener.

Cleaning up event listeners helps prevent memory leaks and unintended behavior.


171-175: Register hotkey events including keyup listener

The registerHotkeyEvent function adds the keyup event listener to manage control key state.

This ensures all necessary events are captured for the enhanced keyboard functionality.


181-181: Export new state variables

The export statement now includes multiSelectedStates and isCtrlPressed.

Exporting these variables allows other components to access and reactively use the multi-selection state and control key status.

packages/canvas/container/src/CanvasContainer.vue Outdated Show resolved Hide resolved
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (8)
packages/canvas/container/src/keyboard.js (5)

28-30: Initialize the multiSelectedStates ref with an empty array.

For better code clarity and to prevent potential undefined errors, initialize the ref:

-const multiSelectedStates = ref([])
+const multiSelectedStates = ref([])  // Initialize with empty array for multi-node selection

46-51: Add error handling for node removal operations.

The current implementation might fail silently if node removal encounters an error. Consider adding try-catch blocks:

 function handlerDelete() {
-  multiSelectedStates.value.forEach(({ id: schemaId }) => {
-    removeNodeById(schemaId)
-  })
+  multiSelectedStates.value.forEach(({ id: schemaId }) => {
+    try {
+      removeNodeById(schemaId)
+    } catch (error) {
+      console.error(`Failed to remove node ${schemaId}:`, error)
+    }
+  })
   multiSelectedStates.value.length = 0
 }

76-93: Add user feedback for save operations.

Consider adding visual feedback to inform users when the save operation succeeds or fails:

 const handleSaveEvent = (event) => {
   const { openCommon } = getMetaApi(META_APP.Save)
   event.preventDefault()
-  openCommon()
+  try {
+    openCommon()
+    // Add toast/notification for successful save
+  } catch (error) {
+    console.error('Save failed:', error)
+    // Add toast/notification for failed save
+  }
 }

113-125: Optimize memory usage for large multi-node operations.

The current implementation creates deep copies of all selected nodes, which could be memory-intensive for large selections. Consider:

  1. Adding a size limit for multi-selection
  2. Using a more memory-efficient copying mechanism
 const handleMultiNodesPaste = (node, schema, parent) => {
+  const MAX_SELECTION = 50 // Prevent excessive memory usage
+  if (multiSelectedStates.value.length > MAX_SELECTION) {
+    console.warn(`Selection limit (${MAX_SELECTION}) exceeded. Some nodes will be skipped.`)
+    return
+  }
   if (multiSelectedStates.value.length === 1) {
     handleClipboardPaste(node, schema, parent)
     return
   }

   const selectedStates = multiSelectedStates.value.map(({ schema, parent }) => {
     return { node: copyObject(schema), schema: toRaw(schema), parent: toRaw(parent) }
   })

   selectedStates.forEach(({ node, schema, parent }) => {
     handleClipboardPaste(node, schema, parent)
   })
 }

154-168: Add JSDoc comments for the event registration functions.

The rename from 'hostkey' to 'hotkey' is good, but the functions would benefit from clear documentation:

+/**
+ * Removes keyboard and clipboard event listeners from the specified DOM element.
+ * @param {HTMLElement} dom - The DOM element to remove events from
+ */
 const removeHotkeyEvent = (dom) => {
   dom.removeEventListener('keydown', keyboardHandler)
   dom.removeEventListener('copy', handlerClipboardEvent)
   dom.removeEventListener('cut', handlerClipboardEvent)
   dom.removeEventListener('paste', handlerClipboardEvent)
 }

+/**
+ * Registers keyboard and clipboard event listeners on the specified DOM element.
+ * @param {HTMLElement} dom - The DOM element to register events on
+ */
 const registerHotkeyEvent = (dom) => {
   removeHotkeyEvent(dom)
   // ... rest of the function
 }
packages/canvas/container/src/CanvasContainer.vue (3)

2-14: Optimize rendering performance and key uniqueness.

The current implementation might have performance issues with large selections and potential key conflicts:

  1. Consider adding virtual scrolling for large selections
  2. Use a more unique key combining id with parent id:
-  <div v-for="multiState in multiSelectedStates" :key="multiState.id">
+  <div v-for="multiState in multiSelectedStates" :key="`${multiState.id}-${multiState.parent?.id}`">

303-311: Consider combining related watchers for better maintainability.

The current implementation adds a separate watcher for multi-selection. Consider combining related watchers:

-watch(
-  () => multiStateLength.value,
-  (newVal) => {
-    if (newVal > 1) {
-      // 清空属性面板
-      selectNode(null)
-    }
-  }
-)
+watch(
+  [() => multiStateLength.value, () => selectState.value],
+  ([length, state]) => {
+    if (length > 1) {
+      // Clear property panel for multi-selection
+      selectNode(null)
+    } else if (state) {
+      // Handle single selection
+      // Add your single selection logic here
+    }
+  }
+)

292-295: Add type safety for position values.

The current implementation uses string literals without type safety. Consider using an enum or constants:

+const INSERT_POSITIONS = {
+  OUT: 'out',
+  IN: 'in',
+  // Add other valid positions
+} as const
+
 const insertComponent = (position) => {
-  if (position === 'out') {
+  if (position === INSERT_POSITIONS.OUT) {
     insertContainer.value = position
     return
   }
   insertPosition.value = position
 }
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 283aee0 and e267d5e.

📒 Files selected for processing (2)
  • packages/canvas/container/src/CanvasContainer.vue (11 hunks)
  • packages/canvas/container/src/keyboard.js (7 hunks)
🔇 Additional comments (2)
packages/canvas/container/src/keyboard.js (1)

13-17: LGTM! Import statements are well-organized.

The imports are appropriate for the multi-selection and keyboard shortcut functionality being added.

packages/canvas/container/src/CanvasContainer.vue (1)

203-211: LGTM! Multi-selection trigger implementation is correct.

The implementation properly uses event.ctrlKey for multi-selection, addressing the concerns from the previous review.

@SonyLeo SonyLeo changed the title feat: right click to add parent container、node multi-selection and capability basic shortcut keys feat: enhance canvas foundation capabilities Jan 21, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request refactor-main refactor/develop branch feature
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1 participant