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: page tree supports dragging #948

Merged
merged 9 commits into from
Dec 24, 2024

Conversation

gene9831
Copy link
Collaborator

@gene9831 gene9831 commented Dec 20, 2024

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?

  • 新增层级线
  • 树节点支持拖拽修改父节点
  • 拖拽修改父节点后立即发起请求保存
  • 拖拽时,光标悬停位置的节点高亮
  • 树节点信息更新,无论成功还是失败,最后都会请求页面数据,刷新页面树

Does this PR introduce a breaking change?

  • Yes
  • No

Other information

Summary by CodeRabbit

  • New Features

    • Introduced a new Vue component, LayerLines, for visual representation of lines based on provided properties.
    • Enhanced PageTree component with new methods for updating pages and folders, and improved drag-and-drop functionality.
    • Added filtering capabilities to the Tree component for better node management.
  • Bug Fixes

    • Refined logic for form validation and tree data management in PageGeneral.
  • Improvements

    • Streamlined page data management and tree data manipulation in usePage.
    • Enhanced user feedback during operations with notifications in PageTree.

Copy link
Contributor

coderabbitai bot commented Dec 20, 2024

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

🗂️ Base branches to auto review (2)
  • refactor/develop
  • ospp-*

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Walkthrough

This pull request introduces several enhancements to the page management system in the Vue application. The changes primarily focus on improving the tree structure, search functionality, and visual representation of pages. A new LayerLines.vue component is added to visually render lines based on level and line data. The PageTree.vue and Tree.vue components receive updates to support more advanced drag-and-drop interactions, filtering, and node management. The usePage.js composable is refined to better handle tree data manipulation and page grouping.

Changes

File Change Summary
packages/plugins/page/src/LayerLines.vue New Vue component for rendering visual lines with dynamic SVG generation based on level and lineData props
packages/plugins/page/src/PageGeneral.vue Updated template and script logic for form validation, tree data management, and conditional rendering
packages/plugins/page/src/PageTree.vue Added methods for page and folder updates, introduced node movement handling, enhanced user feedback
packages/plugins/page/src/Tree.vue Implemented advanced filtering, drag-and-drop functionality, and improved node relationship tracking
packages/plugins/page/src/composable/usePage.js Refined tree data manipulation logic, removed pageTreeKey, improved page grouping

Sequence Diagram

sequenceDiagram
    participant User
    participant PageTree
    participant Tree
    participant LayerLines
    User->>PageTree: Interact with page tree
    PageTree->>Tree: Manage node interactions
    Tree-->>PageTree: Update tree structure
    Tree->>LayerLines: Render visual representation
    LayerLines-->>Tree: Display lines based on data
Loading

Possibly related PRs

Suggested labels

ospp-2024

Suggested reviewers

  • hexqi

Poem

🐰 Lines of code, a tree unfurls,
Dragging nodes with magical swirls,
Pages dance, search gleams bright,
CodeRabbit's magic takes flight!
Hop, hop, innovation's delight! 🌟


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 the enhancement New feature or request label Dec 20, 2024
@gene9831
Copy link
Collaborator Author

@coderabbitai review

Copy link
Contributor

coderabbitai bot commented Dec 20, 2024

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

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: 3

🧹 Nitpick comments (10)
packages/plugins/page/src/LayerLines.vue (3)

1-9: Consider improving maintainability and accessibility of the template.

Several improvements could enhance the code:

  1. Extract hardcoded values into CSS variables or component props
  2. Move bitwise operations to computed properties
  3. Add ARIA attributes for accessibility

Consider applying these changes:

 <template>
   <span v-for="i in level - 1" :key="i" class="gap">
-    <svg v-if="lineData[i]" width="24" height="24" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
+    <svg v-if="lineData[i]" 
+         :width="size" 
+         :height="size" 
+         :viewBox="`0 0 ${size} ${size}`" 
+         xmlns="http://www.w3.org/2000/svg"
+         role="img"
+         aria-label="Tree connection line">
       <line x1="8" y1="0" x2="8" y2="12" stroke="#EBEBEB" stroke-width="1" />
-      <line v-if="(lineData[i] & 1) === 1" x1="8" y1="12" x2="20" y2="12" stroke="#EBEBEB" stroke-width="1" />
-      <line v-if="((lineData[i] >> 1) & 1) === 1" x1="8" y1="12" x2="8" y2="24" stroke="#EBEBEB" stroke-width="1" />
+      <line v-if="hasHorizontalLine(i)" x1="8" y1="12" x2="20" y2="12" :stroke="lineColor" stroke-width="1" />
+      <line v-if="hasVerticalLine(i)" x1="8" y1="12" x2="8" y2="24" :stroke="lineColor" stroke-width="1" />
     </svg>
   </span>
 </template>

Add these properties and methods to the script section:

const props = defineProps({
  // ... existing props ...
  size: {
    type: Number,
    default: 24
  },
  lineColor: {
    type: String,
    default: '#EBEBEB'
  }
})

const hasHorizontalLine = (index) => (props.lineData[index] & 1) === 1
const hasVerticalLine = (index) => ((props.lineData[index] >> 1) & 1) === 1

26-31: Make styles more maintainable and responsive.

The gap dimensions should be consistent with the SVG size and potentially responsive:

Consider applying these changes:

 <style scoped>
 .gap {
-  width: 24px;
-  height: 24px;
+  width: v-bind('size + "px"');
+  height: v-bind('size + "px"');
+  display: inline-block;
 }
 </style>

1-31: Consider architectural improvements for better reusability.

As this component is part of a tree visualization feature:

  1. Consider extracting it to a shared/common components directory for reuse in other tree visualizations
  2. Add JSDoc comments explaining the bitwise operations in lineData
  3. Consider adding unit tests for the line rendering logic

Would you like me to help with:

  1. Moving this component to a shared location?
  2. Generating documentation for the bitwise operations?
  3. Creating unit tests for the component?
packages/plugins/page/src/Tree.vue (3)

7-11: Add drag event handling checks for improved stability.
While the drag-and-drop setup is correct, it's good practice to verify that the node is valid before setting draggable attributes. Also consider adding checks for event.dataTransfer support or restricting dragstart if necessary.

+  @dragstart="(event) => node && handleDragStart(event, node)"

157-163: Check for multi-drag scenarios.
draggedNode is stored in a single ref, which may lead to unexpected behavior if multiple drags happen simultaneously in different parts of the UI. While less common, ensure the UX is guaranteed to limit one drag at a time or handle concurrency gracefully.


239-241: Be cautious with hover border rendering.
This border may visually shift elements, causing layout changes. Consider using an outline or a subtle background highlight for improved user experience.

-&.hover {
-  border: 1px solid var(--te-common-border-checked);
+&.hover {
+  outline: 1px solid var(--te-common-border-checked);
}
packages/plugins/page/src/PageTree.vue (4)

16-16: Add logic for lock and home icons.
The TODO comment highlights missing functionality for page/file lock and home indicator icons. Consider including these icons to align with your design specifications.

Would you like help adding the logic for these icons? I can open a new GitHub issue and propose an implementation.


106-106: Retrieving page list from the server.
The addition of "getPageList" supports the dynamic update of tree data. Verify caching or debouncing if server calls are frequent.


281-281: Popover closure for improved accessibility.
Calling "popoverRefs[node.id]?.doClose?.()" is a standard approach to closing the popover. Consider reviewing ARIA attributes or adding a minor accessibility enhancement to avoid console warnings.


296-310: Review of updatePage function.

  1. Merges page_content and sets the fileName before sending the update.
  2. Calls handlePageUpdate with an option to refresh the current page if it matches the updated page ID.
  3. The usage is consistent with your page update flow, but consider wrapping the promise in a try/catch if you need custom error messages or additional logging in calling functions.
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 15d9400 and c44d13f.

📒 Files selected for processing (5)
  • packages/plugins/page/src/LayerLines.vue (1 hunks)
  • packages/plugins/page/src/PageGeneral.vue (0 hunks)
  • packages/plugins/page/src/PageTree.vue (7 hunks)
  • packages/plugins/page/src/Tree.vue (6 hunks)
  • packages/plugins/page/src/composable/usePage.js (1 hunks)
💤 Files with no reviewable changes (1)
  • packages/plugins/page/src/PageGeneral.vue
🔇 Additional comments (14)
packages/plugins/page/src/Tree.vue (4)

14-14: Validate the layerLine indexing to avoid out-of-bounds errors.
Ensure that layerLine[rowIndex] is always defined by verifying that layerLine computed property produces an entry for every displayed node. In some edge cases—e.g., dynamic modifications—this mapping could fail and cause an undefined reference.


127-150: Validate hierarchical line calculations for edge-case scenarios.
The code constructing layer lines is relatively intricate. Edge cases like single-child trees or deep nesting might produce unexpected line rendering. Consider unit tests or verifying visual outcomes, particularly when the node's parent is not found in the immediate set.


3-5: Avoid potential index conflicts when using rowIndex as a key.
Currently, node.id is used as the key, but rowIndex is used to reference certain arrays (like layerLine). If node order changes or new nodes are added, manually referencing rowIndex may cause mismatch in scenarios where filtering or reordering modifies the data order. Consider ensuring the layerLine array always stays in sync with node order or consider a more robust way to map nodes to line data.


50-56: Props accept multiple types; ensure consistent usage.
The props “filterValue” and “rootId” are typed as [String, Object]/[String, Number], which is flexible but can introduce unexpected behaviors if not handled carefully. Validate that all code branches properly handle each potential type.

✅ Verification successful

Let me gather more information about how these props are used in the component, particularly focusing on type handling.


Let me analyze how these props are used in the component. From the search results, I can see:

  1. filterValue (String | Object) is used in a computed property to filter nodes based on label:
return nodes.value.filter((node) => node.label.toLowerCase().includes(props.filterValue))
  1. rootId (String | Number) is used in:
  • Initial data flattening: flattenTreeData({ [props.idKey]: props.rootId, [props.childrenKey]: props.data })
  • Parent node check: if (node.parentId !== props.rootId)

The code handles both props appropriately:

  • filterValue is used with includes() which works with string conversion
  • rootId is used in property access and equality comparison which works with both strings and numbers

Props are handled safely for all declared types.

  • The filterValue prop is used in string operations where type coercion ensures safe handling of both String and Object types
  • The rootId prop is used in comparisons and as an object key where both String and Number types are valid
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Searching usage of filterValue and rootId to ensure consistent type checks
rg -A 3 '(filterValue|rootId)'

Length of output: 1400


Script:

#!/bin/bash
# Search for computed properties and methods that might handle these props
ast-grep --pattern 'computed(() => {
  $$$
})'

# Also check for watch handlers
ast-grep --pattern 'watch($$$)'

# And check for any type assertions or checks
rg -A 2 'typeof|instanceof'

Length of output: 65949

packages/plugins/page/src/PageTree.vue (10)

3-3: Usage of v-model on search input is consistent with Vue best practices.
The binding to "state.pageSearchValue" with clearable functionality is straightforward and improves usability.


24-24: Drag-and-drop event handling seems well-integrated.
The new "@move-node" event references "handleMoveNode," which orchestrates node restructuring. Verify consistent handling of all drag events in the Tree.vue or any relevant child components.


110-110: fetchPageDetail and requestUpdatePage destructured from http.
It’s good that you separated out these HTTP methods to keep the code modular. Please ensure that these methods encapsulate robust error handling.


311-330: Review of updateFolder function.

  1. Makes a direct requestUpdatePage call, only handling partial data since page_content is null.
  2. Includes success and failure notifications to enhance user feedback.
  3. On success, triggers pageSettingState.updateTreeData() to refresh the UI. This is a good pattern.

394-394: Exposing handleMoveNode from setup.
The method is returned from the setup, enabling the parent or sibling components to handle its logic. Ensure that the parent has no conflicting watchers that might override the node's movement logic.


66-66: Notify and Message utilities.
Importing "useNotify" and "useMessage" is a good approach for standardized user feedback. Confirm that these message hooks gracefully handle concurrent notifications.

✅ Verification successful

Based on the search results, I can provide a final response since I have enough information about how useNotify and useMessage are used across the codebase:

Concurrent notifications are properly handled in the codebase

The implementation shows that:

  • useNotify is used for one-off notifications that don't overlap or conflict
  • useMessage implements a proper pub/sub pattern with:
    • Unique topic/subscriber identifiers
    • Clean unsubscription on component unmount
    • Proper message queue handling through subscribe/unsubscribe lifecycle

The notifications are used appropriately across the codebase with:

  • Clear message types (success, error, warning, info)
  • Proper error handling and cleanup
  • No race conditions in notification display
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
#!/bin/bash
# Searching for concurrent usage of useNotify or useMessage across the codebase 
rg -A 5 'useNotify\(|useMessage\('

Length of output: 35283


21-22: Filtering and root ID binding are appropriate.
The usage of ":filter-value" and ":root-id" binds well to your tree data. Ensure that the draggble-tree component uses these values properly for root-level node identification and search filtering.

✅ Verification successful

Props are correctly used in the draggable tree component

The verification confirms that both filterValue and rootId props are properly implemented and utilized in the Tree component:

  • filterValue is used to filter nodes based on label text in the filteredNodes computed property
  • rootId is used as expected for:
    • Identifying root-level nodes in the tree structure
    • Proper parent-child relationship handling during tree traversal
    • Correct rendering of the tree hierarchy
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Verify usage of filterValue and rootId in DraggbleTree
rg -A 6 'props:\s*{\s*filterValue:\s*{\s*type:\s*String' packages/plugins/page/src/Tree.vue
rg -A 6 'props:\s*{\s*rootId:\s*{\s*type:\s*Number' packages/plugins/page/src/Tree.vue

Length of output: 455


Script:

#!/bin/bash
# Let's fix the regex and search for prop definitions
rg -A 6 "props.*filterValue" packages/plugins/page/src/Tree.vue
rg -A 6 "props.*rootId" packages/plugins/page/src/Tree.vue

# Also search for the component name and its usage
fd Tree.vue --exec cat {} \; packages/plugins/page/src/

Length of output: 20753


73-73: Importing handlePageUpdate for remote update calls.
Ensure that all calls to "handlePageUpdate" are properly wrapped with error handling or promise chaining if not consumed by a top-level await.


117-117: Initializing currentNodeData with the pageId.
Binding directly to GlobalService is convenient for referencing the currently open page. Confirm if there's a fallback or default behavior when pageId is undefined.


332-361: handleMoveNode logic for post-drag operations.

  1. Checks whether current data is saved; if yes, we finalize the move by updating the node’s parent.
  2. Fetches new page detail and updates with the new parent ID, then decides whether to call updatePage or updateFolder.
  3. The fallback behavior discards unsaved changes if the user confirms a prompt.
    • Potential Issue: If changes are partially saved in the drag chain, concurrency conflicts might arise.

Would you like to ensure concurrency safety? We can run a deeper codebase script to see if there are any concurrency safeguards during drag and drop operations.

✅ Verification successful

Let me search for more specific drag and drop related code to analyze concurrency handling.


Based on the code analysis, I can now provide a final response about the concurrency safety of the drag and drop operations:

Drag and drop operations have sufficient safeguards against concurrency issues.

The implementation shows several safety mechanisms:

  • Single source of truth: The draggedNode state is cleared after each drop operation
  • Sequential execution: The drag chain follows a clear sequence:
    1. handleDragStart sets the dragged node
    2. handleDrop clears state and validates before proceeding
    3. changeTreeData updates the tree structure
    4. fetchPageDetail gets fresh data before updating
    5. updatePage/updateFolder performs the final server update
  • Unsaved changes protection: The code checks for unsaved changes and prompts user confirmation before proceeding
  • Ancestor validation: Prevents invalid drops by checking if target is a descendant of dragged node
  • Atomic updates: The server-side updates are performed as single operations per page/folder
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Searching for concurrency or race condition handling in drag operations
rg -A 5 'dragstart|dragend|drop|handleMoveNode'

Length of output: 78894


Script:

#!/bin/bash
# Search for drag and drop related code and event handlers
ast-grep --pattern 'handleMoveNode($$$)' 
rg -A 10 'handleDragStart|handleDragEnd|handleDrop|changeTreeData|updatePage|updateFolder'

Length of output: 24002

@gene9831 gene9831 marked this pull request as ready for review December 23, 2024 07:27
@rhlin rhlin merged commit ad794d8 into opentiny:feat/router-page Dec 24, 2024
@gene9831 gene9831 deleted the feat/page-tree-draggable branch December 26, 2024 07:44
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants