Skip to content

Support unlimited-depth ViewManager groups with bulk + group editing in the Manage dialog - #4479

Open
cnrudd wants to merge 80 commits into
developfrom
feature/issue-4469-viewmanager-groups
Open

Support unlimited-depth ViewManager groups with bulk + group editing in the Manage dialog#4479
cnrudd wants to merge 80 commits into
developfrom
feature/issue-4469-viewmanager-groups

Conversation

@cnrudd

@cnrudd cnrudd commented Jul 6, 2026

Copy link
Copy Markdown
Member

Closes #4469.

Adds unlimited-depth nesting for ViewManager groups and reworks the Manage dialog around three dedicated edit panels with selection-based routing.

Key changes

  • Group names are slash-delimited paths (e.g. Reports/Sales/Monthly), rendered as nested sub-menus in the ViewManager menu and as expandable tree grids (with open/closed folder icons) in the Manage dialog.
  • The Manage dialog's detail area routes on selection: a single view row opens the existing single-view panel; two or more view rows (across any groups) open a bulk panel for changing visibility and moving views into another group; a single group row opens a new group panel to rename/re-nest the group (cascading to all views under it, with a confirm when merging into an existing path), or bulk-update visibility and pinning across the group's views. Other selections (multiple groups, or groups mixed with views) show a placeholder.
  • Group moves throughout use simple inline selects with create-new-path support.
  • Deleting from the group panel confirms as a group deletion ("delete group X and its N nested views"); a shared formButtons component now backs all three panels.

Hoist-React Select Bug Fix:

  • Fixed Select with enableCreate to always display its "Create..." option while typing, even when createMessageFn does not echo the raw query.

Notes

cnrudd added 16 commits July 2, 2026 15:16
* Forward slashes in group names are interpreted as sub-group delimiters, with new shared path/tree helpers in `cmp/viewmanager/GroupUtils.ts` and normalization on read in `ViewInfo`.
* ViewManager menu renders groups as recursively nested sub-menus, including within per-owner shared sections.
* Manage dialog grids converted from ag-Grid row grouping to Hoist tree grids with synthetic group rows (and owner rows on the shared tab).
* New `GroupEditor` popover input replaces the group select in the Manage dialog's edit panel, supporting both moving a single view between groups and renaming/re-parenting an entire group - renames are sent as `groupRename: {from, to}` on updateInfo, cascading server-side to all views under the renamed path (requires hoist-core v41+).
* Save As dialog group select now displays existing groups as an indented hierarchy.

Drag-and-drop between groups (issue item 3) deliberately excluded - to follow separately.
Per review feedback - the GroupEditor popover trigger now renders as an outlined button within the Group field's label row (mirroring the Unpin button style), with the popover anchored to the group path display. GroupEditorModel is exported so external triggers can toggle the popover via a component ref.
Group path select options now use the full delimited path as their label, so the select's value container and typed create-entries unambiguously read from the top level - previously the leaf-only label made "subGroup1/newGroup" look relative to the current parent when it is actually rooted. Dropdown menu items still render the indented leaf-name hierarchy.
The Move-mode toggle button and rename-pane info text now use the ViewManager's configured typeDisplayName (title-cased / pluralized as appropriate) instead of hardcoded "View"/"views".
groupPathOptionRenderer now passes react-select's dynamically-injected create option (__isNew__) through untouched, so the formatted create message displays instead of being re-rendered as a depth-0 folder row showing only the leaf of the typed path. Also sets a custom createMessageFn on the move-pane select: Create path "...".
* New ViewMultiPanelModel backs the multi-select detail panel with a visibility-only form, initialized to the selection's common value (or blank when mixed) and readonly unless all selected views are editable.
* ViewMultiPanel now renders the visibility select with Save Changes/Revert buttons swapping in on dirty state, matching ViewPanel, alongside the existing bulk Pin/Unpin and Delete actions.
* New updateViewsInfoAsync plumbing through ViewManagerModel/DataAccess posts to the new xhView/bulkUpdateInfo endpoint (requires hoist-core v41+).
* Selecting one or more group rows now shows the bulk-edit multi panel instead of the empty placeholder, targeting all views beneath the selected groups (to any nesting depth, respecting any active filter).
* Group rows can be combined with individually selected views - the resulting set is deduped, so a group row plus one of its own children does not double-count.
* A single group row containing exactly one view also routes to the multi panel, rather than a blank single-view panel.
…iewManager Manage dialog

* Group rows in the owned and global grids now offer a right-click Edit Group action, opening a dialog to rename or re-parent the group, cascading to all views within it. On the global tab the action requires manageGlobal.
* ViewPanel's group field is now the same plain move-only group select used by ViewMultiPanel - the popover-based GroupEditor with its Move View / Edit Group mode toggle has been removed, along with ViewPanelModel's pendingGroupRename staging.
* Group renames anchor on one view under the group, setting its own rewritten path in the same updateInfo call since the server-side cascade excludes the source token.
* Replaced the dialog's raw model-bound inputs with a FormModel and form/formField components, gaining standard labels and inline validation display.
* Group name is validated via the required constraint plus a rule rejecting the group delimiter, with a hint to re-parent via Nest Under instead.
cnrudd added 11 commits July 7, 2026 11:18
…ners on the shared tab

Group row ids under each owner row are now owner-prefixed recursively at every depth, not just the top level - previously two owners sharing the same nested group path (e.g. Reports/Monthly) produced colliding record ids, throwing on load and breaking the entire shared tab.
Group row record ids incorporate the group's path, so a renamed or re-parented group returns from the post-save refresh as a new record - collapsed and unselected. Now the grid re-expands and the renamed group row is reselected, surfacing its views in the multi panel as immediate confirmation.
Renaming or re-parenting a group onto a path that already exists (including implied ancestor paths) previously merged the two groups silently. The dialog now confirms first, noting the existing target path and the number of views that will be added to it.
* The multi panel's group select now always starts blank - a value entered always indicates a pending move, and clearing it reverts to no-change. Replaces the previous common-value/(Mixed) initialization.
* The '(Top Level)' option carries a new TOP_LEVEL_VALUE sentinel (mapped to a null group on save) so an empty field neither displays as top-level nor conflates with it for dirty-checking.
* Info text now notes that bulk moves discard existing nested groups, and the selection count pluralizes correctly for a single view.
… createMessageFn label does not echo the typed query

The synthetic create option passes through the label-based filterOption like any other option, so a custom create message not containing the raw query verbatim caused the option to vanish, leaving 'No matches found'. The filter now always passes the create option - its visibility is governed by react-select itself.
The group selects seed their filter input with the selected option's label, so a new path typed while (Top Level) was selected arrived prefixed with that display label and was persisted literally as a group named '(Top Level)'. Group select values now resolve through a shared parseGroupSelectValue helper that strips the label prefix and handles the top-level sentinel, and create prompts display the effective parsed path.
…ctly one view

selectedView now falls back to the selection's single resolved view, so a group row containing one view routes to the standard ViewPanel rather than the bulk multi panel. Simplifies detail-panel routing to a plain count check and drops the now-unneeded hasGroupRowsSelected computed.
cnrudd added 20 commits July 9, 2026 17:06
* Confirm before any drop that moves a global view - such moves re-group the view in every user's menu.
* Single-view drag ghost now shows the view's name (e.g. 'Layout "France"') rather than a count.
* Move toasts now display top-center rather than the default corner.
* New context-menu item on group rows in the personal and global grids, above Expand All - hidden on view rows and when the group's views are not all editable by the current user.
* Opens a focused rename dialog backed by the same form and save flow as the selection-driven GroupPanel, selecting the group row on open.
* Renames of global groups now confirm before applying - they re-group global views in every user's menu. Applies to both the dialog and the GroupPanel save.
* Cancelling the rename dialog resets the shared form, so the GroupPanel does not surface the discarded edit as dirty.
* A sole-selected group row now shows the ViewMultiPanel, bulk-editing the group's views - group renames are handled solely by the "Rename Group" context-menu dialog.
* Deleted GroupPanel; converted GroupPanelModel into RenameGroupDialogModel, now dedicated to the rename dialog.
* ViewMultiPanel passes the selected group's name through to formButtons, preserving the group-contextual delete confirm, and the group pin/unpin buttons now read with a view count (e.g. "Pin 3 layouts to your Menu").
…tree grids, removing the extra leaf indent that read as an additional nesting level
…move them to the top level

* Symmetric with dropping into the empty space below the last row - exiting past the grid's top edge shows the top-level indicator and arms a one-shot drop that completes on release outside the grid, where ag-Grid fires no drop event of its own.
* Re-entering the grid, dropping normally, cancelling, or closing the dialog disarms. Escape is handled directly while outside - ag-Grid does not observe it there.
…ds, where the pinned row captures hovers/drops not intended for it
* A group row can only move on its own - selections combining a group with any other rows (more groups or views) now disable dragging across the grid.
* Disabled state grays the drag handles and blocks drag initiation, with the drag event handlers also guarding as a backstop.
* The "Configuring N layouts" header no longer fills the panel - a compact, non-expanding block showing the ViewManager's defaultViewIcon above the same text, with the edit fields directly beneath.
* The resolved defaultViewIcon prop is passed through from the ViewManager component via the manage dialog, so app-customized icons carry into the panel.
* One card per selected view - an opaque, bordered chip bearing the ViewManager's defaultViewIcon - fanned via CSS-var driven rotation around a pivot below the hand.
* Pure-CSS clamps keep large hands tidy: the per-card angle shrinks to cap the total spread, and the card overlap tightens to solve for the header width, so the hand never overflows its container.
…ple groups - no meaningful single value to display or edit
* Removed GroupField's unreachable mixed-sentinel handling - the multi panel omits the field entirely when the selection spans groups.
* Slimmed RenameGroupDialogModel of GroupPanel-era readonly plumbing - the context menu already gates the dialog to fully-editable groups.
* Renamed the newGroup field/state to subgroup ("Sub Group" in the UI) throughout the edit panels and SaveAs dialog.
* ViewPanelModel.saveAsync now skips the server call when a whitespace-only subgroup leaves no real updates.
@cnrudd
cnrudd marked this pull request as ready for review July 10, 2026 22:20
cnrudd and others added 5 commits July 16, 2026 11:36
* `ViewManagerModel.renameGroupAsync` and `DataAccess.renameGroupAsync` call hoist-core's new `xhView/renameGroup` endpoint, passing `isGlobal` to select which group namespace to rename within.
* Replaces the anchor-view approach, where the Manage dialog rewrote one member view's group and passed a `groupRename` directive for the server to cascade from. That left the scope of the rename implicit in the anchor blob's ownership, and did nothing at all if no editable view was found beneath the group. `groupRename` is removed from `ViewUpdateSpec`.
* A group rename is now a single atomic call covering every view beneath the path, so it can no longer half-apply.
…rement

* The nested-groups and bulk-editing entry had drifted under the already-shipped 86.4.0 heading - moved to 87.0.0-SNAPSHOT.
* Called out as a breaking change: v87 requires `hoist-core >= 40.4.0` for the `xhView/renameGroup` and `xhView/bulkUpdateInfo` endpoints, which do not exist on earlier servers. The hoist-core changes themselves are additive, so this is a minimum-version bump rather than a server-side break.
* Corrected the `hoist-core v41` references in `ViewManagerModel` docs to v40.4 - hoist-core has no breaking changes pending, so its next release is expected to be 40.4.0.
* `renderNdjson()` ships in the same hoist-core release as the ViewManager group APIs, so this reference is v40.4 as well.
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.

Grouping in ViewManager should allow unlimited depth

3 participants