Skip to content

feat(dashboard): rearrange panels via drag and drop - #2611

Open
Hannott wants to merge 5 commits into
mainsail-crew:developfrom
Hannott:feat/dashboard-drag-and-drop
Open

feat(dashboard): rearrange panels via drag and drop#2611
Hannott wants to merge 5 commits into
mainsail-crew:developfrom
Hannott:feat/dashboard-drag-and-drop

Conversation

@Hannott

@Hannott Hannott commented Jul 26, 2026

Copy link
Copy Markdown
Contributor

Description

This PR adds drag and drop rearranging of panels directly on the dashboard, as an alternative to the sortable lists in the settings dialog.

  • Panels are grabbed by the icon in their header and can be moved within their column or into another column of the current viewport. Grabbing the icon starts the drag immediately; the rest of the header (buttons, collapse) behaves as before.
  • While dragging, the panel itself stays rendered as a live, slightly dimmed preview at the current drop position, and the element following the cursor is a translucent box with just the panel header, which follows the size of the preview. The columns are highlighted as drop targets, and hover on elements below the cursor is suppressed for the duration of the drag.
  • The new order is written to the same dashboard.<viewport>Layout<n> settings used by the settings dialog, so both stay in sync. Because the dashboard only renders visible and currently available panels, the reordered list is merged back into the stored layout, so hidden panels and panels of currently unavailable services (e.g. Spoolman offline) keep their position relative to their stored neighbours. The merge logic is covered by unit tests.

Related Tickets & Documents

None - feature is described above.

Mobile & Desktop Screenshots/Recordings

A screen recording will be added as a comment below.

[optional] Are there any post-deployment tasks we need to perform?

None.

Signed-off-by: Åsmund Collin aakjaergaard@gmail.com

🤖 This Pull Request was created with the help of Claude Code.

Panels can now be reordered directly on the dashboard by dragging their
header icon, in the same or into another column. The new order is saved
to the same layout settings used by the settings dialog, so both stay
in sync. Hidden panels and panels of currently unavailable services
keep their position relative to their stored neighbours.

Signed-off-by: Åsmund Collin <aakjaergaard@gmail.com>
@github-actions

Copy link
Copy Markdown
Contributor

Hi @Hannott, thanks for your interest in contributing to Mainsail! 👋

This pull request has been automatically closed because pull requests may only be opened by vouched contributors, and you are not yet on our vouched list.

This is not a rejection of your work. A maintainer can vouch for you and once that happens, simply reopen this PR or comment /recheck.

Please see our contributing guidelines for more details:

@github-actions github-actions Bot closed this Jul 26, 2026
@github-actions github-actions Bot added the unvouched Closed automatically: Author is not a vouched contributor label Jul 26, 2026
@coderabbitai

coderabbitai Bot commented Jul 26, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Changes

The dashboard now renders responsive columns with cross-column draggable panels. It merges stored layouts, preserves panel metadata, resizes drag clones, and persists reordered columns. Tests cover layout merging. Panel header icons receive a CSS class when rendered from the icon prop.

Changes

Dashboard layout

Layer / File(s) Summary
Stored layout merging
src/pages/Dashboard.vue, tests/pages/Dashboard.spec.ts
The dashboard maps active columns to stored panels and merges reordered, hidden, unavailable, and implicitly materialized panels. Tests cover these merge cases.
Column drag and persistence
src/pages/Dashboard.vue
Responsive columns use vuedraggable. Drag lifecycle handlers resize clones, update state, clean up observers, apply drag styling, and persist merged layouts through gui/saveSetting.
Panel header icon styling
src/components/ui/Panel.vue
Toolbar header icons rendered from the icon prop now use the panel-header-icon class when no icon slot is present.

Sequence Diagram(s)

sequenceDiagram
  participant Dashboard
  participant draggable
  participant gui_saveSetting
  Dashboard->>draggable: Render panels in responsive columns
  draggable->>Dashboard: Emit drag start and drag end events
  Dashboard->>Dashboard: Merge stored and reordered panels
  Dashboard->>gui_saveSetting: Save the merged column layout
Loading

Possibly related issues

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely describes the main dashboard change: rearranging panels through drag and drop.
Description check ✅ Passed The description directly explains drag-and-drop panel rearrangement, layout persistence, merge behavior, and test coverage.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@Hannott

Hannott commented Jul 27, 2026

Copy link
Copy Markdown
Contributor Author
20260726-2251-39.6646299.mp4
screen-20260727-144625-1785156379169.mp4

@Hannott

Hannott commented Jul 31, 2026

Copy link
Copy Markdown
Contributor Author

/recheck

@github-actions github-actions Bot reopened this Jul 31, 2026
@github-actions github-actions Bot removed the unvouched Closed automatically: Author is not a vouched contributor label Jul 31, 2026
Comment thread src/pages/Dashboard.vue Outdated
Comment thread src/store/gui/getters.ts Outdated
Comment thread src/pages/Dashboard.vue Outdated
Hannott and others added 3 commits July 31, 2026 22:38
Fold the global style block back into the scoped one. The three selectors
that reach into the panel go through ::v-deep, the rules for the dropzone
and for the dragged panel itself match the scoped elements directly.

Signed-off-by: Åsmund Collin <aakjaergaard@gmail.com>

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Signed-off-by: Åsmund Collin <aakjaergaard@gmail.com>

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Only one viewport is rendered at a time, so the drag group never had to
be viewport specific. The group itself stays, sortable rejects drops
between two lists that have no group name.

Signed-off-by: Åsmund Collin <aakjaergaard@gmail.com>

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Hannott added a commit to Hannott/mainsail that referenced this pull request Jul 31, 2026
develop already carried the original squashed macros fix (30cd662) and
an earlier draft of the dashboard drag and drop feature (e617487).
Brings both up to the state of the currently open upstream PRs after
review:

- SettingsMacrosTabExpert.vue: macroListLoaded now treats a genuinely
  empty, fully loaded macro list as loaded instead of requiring
  allMacros.length > 0, so real deletions are reported once Klipper is
  ready rather than only once at least one macro exists.
- Dashboard.vue / store/gui/getters.ts: scoped styles use ::v-deep for
  the three selectors that reach into Panel.vue instead of a global
  style block, the drag group is a constant instead of a per viewport
  getter (only one viewport ever renders), and the comment on
  getStoredPanels is gone, per meteyou's review on mainsail-crew#2611.

All other files already matched between develop and the current PR
branches.

Signed-off-by: Åsmund Collin <aakjaergaard@gmail.com>

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

@meteyou meteyou left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

In this case, the location of the tests fits and I think we can merge these tests.

Comment thread src/components/mixins/dashboard.ts Outdated
Comment thread src/pages/Dashboard.vue
Comment thread src/store/gui/getters.ts Outdated
- Move mergeLayout from DashboardMixin to src/pages/dashboardLayout.ts
- Move getStoredPanels from the gui store getters to a PageDashboard method
- Move the mergeLayout unit tests to tests/pages/dashboardLayout.spec.ts

Signed-off-by: Åsmund Collin <aakjaergaard@gmail.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
src/pages/Dashboard.vue (1)

155-160: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Extract the duplicated layoutName construction into one helper.

getStoredPanels (Line 156) and saveColumn (Line 205) both compute column ? \${viewport}Layout${column}` : `${viewport}Layout`` independently. Keeping one formula in one place avoids the two implementations drifting apart if the naming convention changes.

♻️ Proposed refactor
+    getLayoutName(viewport: string, column: number): keyof GuiStateDashboard {
+        return (column ? `${viewport}Layout${column}` : `${viewport}Layout`) as keyof GuiStateDashboard
+    }
+
     getStoredPanels(viewport: string, column: number): GuiStateLayoutoption[] {
-        const layoutName = (column ? `${viewport}Layout${column}` : `${viewport}Layout`) as keyof GuiStateDashboard
+        const layoutName = this.getLayoutName(viewport, column)
         const panels = this.$store.state.gui.dashboard[layoutName] as GuiStateLayoutoption[]

         return panels?.filter((element) => element !== null) ?? []
     }
     saveColumn(column: number, panels: GuiStateLayoutoption[]) {
-        const layoutName = column ? `${this.viewport}Layout${column}` : `${this.viewport}Layout`
+        const layoutName = this.getLayoutName(this.viewport, column)
         const storedPanels = this.getStoredPanels(this.viewport, column)

         this.$store.dispatch('gui/saveSetting', {
             name: `dashboard.${layoutName}`,
             value: this.mergeLayout(storedPanels, this.panelsByColumn[column] ?? [], panels),
         })
     }

Also applies to: 204-212

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/pages/Dashboard.vue` around lines 155 - 160, Extract the shared
layout-name construction used by getStoredPanels and saveColumn into a single
helper, preserving the existing viewport and optional column naming behavior.
Update both methods to call that helper and remove their duplicated inline
formula.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@src/pages/Dashboard.vue`:
- Around line 155-160: Extract the shared layout-name construction used by
getStoredPanels and saveColumn into a single helper, preserving the existing
viewport and optional column naming behavior. Update both methods to call that
helper and remove their duplicated inline formula.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 22250c41-e04a-4cde-bc43-6c739ca58fce

📥 Commits

Reviewing files that changed from the base of the PR and between aa68748 and 1db5245.

📒 Files selected for processing (2)
  • src/pages/Dashboard.vue
  • tests/pages/Dashboard.spec.ts

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.

2 participants