Skip to content

feat(content-drive): grid view with a table/grid switcher - #38009

Merged
rjvelazco merged 10 commits into
mainfrom
issue-37930-content-drive-grid-view-impl
Oct 14, 2026
Merged

rjvelazco merged 10 commits into
mainfrom
issue-37930-content-drive-grid-view-impl

Conversation

@rjvelazco

@rjvelazco rjvelazco commented Oct 13, 2026 •

Copy link
Copy Markdown
Member

Closes #37930. Implementation for the spec merged in #37959 (PR 2 of 2).

⚠️ The spec changed after it was approved. This PR carries edits to specs/37930-content-drive-grid-view/spec.md from the 2026-10-13 session: the card follows the design team's revised layout (status and avatar over the image, click opens, checkbox selects, 240px cards, lock always shown), and the view now opens from viewType in the URL → the view remembered for this screen → a custom tool's defaultViewType → the table. The review on this PR added a few more (Back/Forward keep the view, FR-019a; the avatar shows the editor's initial and never calls Gravatar, FR-007; drags highlight folders only). Those edits need a reviewer's re-approval alongside the code.

Proposed Changes

  • View switcher (p-selectbutton) beside the search box toggles table ↔ grid.
  • Grid view: a second layout of the shared dot-folder-list-view (layout="grid"), with the Drive's dot-content-drive-grid-card (p-card) passed in as a #card template. The card: preview on top (video = still frame, type icon fallback), the editor's initial and the status badge over its top-left corner, checkbox top-right; title; language, content type, lock state and a hover ⋮ menu. Cards are at least 240px wide and fill the row.
  • Same behaviour as the table, because it is the same component: shared selection, pagination and page size, sort, context menu, ⋮ menu, drag to folders, file drop to upload, busy cards, skeletons, empty and search-failed states. A click opens the item; the checkbox selects; Shift-click on it selects a range.
  • Keyboard: one tab stop; Left/Right/Up/Down/Home/End; Shift+arrows for ranges; Enter/Space select; Shift+F10 opens the actions menu. Focus and anchor carry across a switch. Documented in docs/frontend/KEYBOARD_SHORTCUTS.md.
  • Which view opens: viewType URL param (written back on every switch, so a copied link opens in the same view) → the view remembered in localStorage per portlet (dotcms.content-drive.view-type.<portletId>, key worked out once when the Drive opens) → defaultViewType (for Open custom tools in Content Drive with their content types locked #37931) → table. Back/Forward keep the view showing.
  • Shared listing (dot-folder-list-view), all opt-in: layout, the #card template (DotFolderListViewCardContext), rows, sortField/sortOrder (a switch back rebuilds the table, which must show the caller's page size and sort). The grid's paginator shares the table's lock, with Previous/Next truly disabled while a page loads. Drag pictures are found by dataKey, and drags highlight folders only (both layouts). Unbound, pickers, the Action Center, edit-content, publishing-queue and experiments behave as before.

Checklist

  • Tests — Vitest: portlets-content-drive (2180), ui (2222, including the new dot-folder-list-view.grid.spec.ts), plus the listing's consumers edit-content (2474), publishing-queue (1400), experiments (277). Lint, format:check, strict gate and nx build dotcms-ui pass locally.
  • Translations — 8 new keys in Language.properties.
  • Security Implications Contemplated — no new data is exposed and nothing is sent to outside services: the avatar draws the first letter of the editor's name with PrimeNG's avatar and never uses the Gravatar directive, since a user without first/last names is named after their email.

Additional Info

  • Assumptions under review: the decisions taken without asking (31 of them, with reasons and alternatives) are being reviewed with the author before this leaves draft; any changes they bring will land here.
  • No Gravatar on cards: the Drive search returns editor names only, and a name can be an email. Real photos would need the search to return an email or hash: a backend change with a privacy question, left as a possible follow-up.
  • Open custom tools in Content Drive with their content types locked #37931 should send the tool's view as defaultViewType, not viewMode/viewType, so it never outranks the user's remembered choice.
  • The main shell spec was already at the Vitest worker's heap ceiling, so the shell's grid wiring tests live in dot-content-drive-shell.grid-view.spec.ts; the listing's grid tests live in dot-folder-list-view.grid.spec.ts.
  • Browser-checked against a local instance (Chrome, 1440px and 1000px): switching, the URL param, the remembered view, viewType=List overriding it, selection kept across a switch, arrow keys (4 columns at 1440px), folder cards entering the folder, right-click menu, card click opening the editor, and the selected-card border. After the review refactor: no table in the DOM while the grid shows, the tab stop kept across table → grid → table, ⋮ menu closes on a second click, no Gravatar request from cards. No page errors.

Screenshots

Local instance, Chrome, 1440px wide unless noted. Every avatar reads "A" because the editor here is "Admin User".

Grid view, all site content
1-grid-view

One card on hover: the ⋮ button appears
2-card-hover

The view switcher beside the search box
3-view-switcher

Folder cards. An owner the listing can't name gets the neutral person icon
4-folder-cards

Right-click opens the same menu as a table row
5-right-click-menu

1000px window with the folder tree open: two columns
6-narrow-window

🤖 Generated with Claude Code

This PR fixes: #37930

rjvelazco and others added 2 commits October 13, 2026 10:55
Content Drive can now show the current page as cards. A switcher beside
the search box toggles between the table and the grid.

- Cards follow the design: preview on top with the editor's avatar and
  the status over its corner and the checkbox opposite, then the title,
  language, type, lock state and a hover "more actions" button. A click
  opens the item; the checkbox selects; Shift on it selects a range.
- The grid shares the table's selection, pagination, page size, sort,
  context menu, drag-and-drop and upload drop zone. The table stays
  mounted (hidden) while the grid shows, so switching never re-runs
  the search.
- Keyboard: one tab stop, Left/Right/Up/Down/Home/End, Shift+arrows for
  ranges, Enter/Space to select, Shift+F10 for the actions menu. Focus
  and anchor carry across a switch.
- The view opens from `viewType` in the URL, then the view remembered
  for the screen (localStorage, per portlet), then a custom tool's
  `defaultViewType`, then the table. Switching writes `viewType` to the
  URL so a copied link opens in the same view.
- Shared table: optional `rows`, `activeKey`/`activeKeyChange` and
  `revealActive()`; unbound, it behaves as before.
- Spec updated with the 2026-10-13 design and view-precedence answers.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@github-actions github-actions Bot mentioned this pull request Oct 13, 2026
10 tasks
@github-actions github-actions Bot added Area : Documentation PR changes documentation files Area : Frontend PR changes Angular/TypeScript frontend code labels Oct 13, 2026
@claude

claude Bot commented Oct 13, 2026 •

Copy link
Copy Markdown
Contributor

Claude finished @rjvelazco's task in 4m 4s —— View job


Review

I read the refactored listing (dot-folder-list-view.component.ts/.html), the grid card, the view-type utils, the store and shell wiring, and the toolbar switcher, and rechecked each prior finding against the current diff.

New Issues

No new blocking issues found. The refactor to a single layout="grid" listing is clean, the shared selection/anchor/page-lock/drag paths are genuinely one code path now, and the message keys all resolve.

A few things I confirmed behave correctly (not findings, just the ones worth stating because they were the risky parts):

  • Content cards are not drop targets. onDragOver returns before preventDefault() for non-folders (dot-folder-list-view.component.ts:1573), so the browser never fires drop on a content card — the earlier "UI says allowed, drop silently ignored" case can't happen in either layout.
  • No hidden table in grid mode. @if ($isGrid()) renders only the grid (...component.html:10), so dataTable() is undefined and $paginatorPt correctly falls back to $rows() for the prev/next boundary.
  • Back/Forward keeps the view. locationSub no longer reads viewType (dot-content-drive.store.ts:759), and the shell's isFolderNavigation now excludes a URL that already names the folder (...shell.component.ts:1659), so a view-only rewrite replaces rather than pushes and Forward survives.

Resolved

  • ✅ dot-folder-list-view grid as a separate component → now a second layout of the shared listing; selection, ranges, page lock, drag image and languages map are single copies (dot-folder-list-view.component.ts).
  • ✅ #followActiveKey dropping a later report of the old key → the activeKey/activeKeyChange handoff is gone; both layouts read one $activeRowIndex and only the range anchor is carried across (...component.ts:1813).
  • ✅ Gravatar request from cards when a display name is an email → the card renders <p-avatar [label]> from $editorInitial and never imports DotGravatarDirective (dot-content-drive-grid-card.component.ts:142, .html:68).
  • ✅ Drag preview keyed by identifier → cards and lookup now key on dataKey via data-drag-key/[data-drag-preview], scoped to the host (dot-folder-list-view.component.ts:1666, .html:507, card .html:47).
  • ✅ onDragOver accepting any card + missing dragleave → onDragOver gates on #isFolder, onDragLeave clears the highlight and ignores moves onto children (...component.ts:1573,1594).
  • ✅ Grid paginator could desync on Enter-while-loading → the grid shares the table's $paginatorPt lock with truly-disabled prev/next, and onGridPageChange resets the paginator on a refused change (...component.ts:672,2018).
  • ✅ Back silently switching the view → view is now a preference; viewType is applied once at open via resolveViewType and not re-read on history events (dot-content-drive.store.ts, view-type.ts).
  • ✅ Unreachable try/catch + storage key recomputed per switch → key is resolved once in onInit and held in state as viewTypeStorageKey (dot-content-drive.store.ts:698, state models.ts).
  • ✅ [pt] with inline body styles → [dt]="{ body: { padding, gap } }", pt carries only the content class (dot-content-drive-grid-card.component.html:25).

Test coverage is substantial for the new behavior (dot-folder-list-view.grid.spec.ts, dot-content-drive-shell.grid-view.spec.ts, view-type.spec.ts, grid-navigation.spec.ts, grid-card.component.spec.ts, store spec additions).

Nice work on the refactor — this reads much better as one component than two listings kept in sync.
• issue-37930-content-drive-grid-view-impl

)

The cards' visually hidden labels are absolutely positioned. With no
positioned ancestor inside the grid they anchored to the upload drop
zone, escaped the grid's own scroll box and stretched the drop zone,
which then scrolled as well. The card and the grid's scroll box are now
positioned, so the drop zone's content fits it again.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A click on a card opens the item, so the card now shows a pointer. Not
in a read-only grid or on a busy card, where a click does nothing, and
not over the avatar or the lock icon, which only show a tooltip.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
rjvelazco and others added 2 commits October 13, 2026 11:46
)"

This reverts commit d9d64f9: the cards keep the design's cursor.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…#37930)

The card stopped the ⋮ click from bubbling so it would not open the
item. That also kept it from PrimeNG's document click listener, which
is what closes the menu, so a second click re-opened it instead of
closing it as the table's ⋮ does. The click now bubbles, and the card
ignores clicks that come from its ⋮ button.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@rjvelazco
rjvelazco marked this pull request as ready for review October 13, 2026 16:49

@zJaaal zJaaal 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.

A few more findings from a review pass, on top of the reuse comments above. They're in the code but aren't covered by those threads.

Written by Claude Code on behalf of @zJaaal.

rjvelazco and others added 2 commits October 13, 2026 17:25
…ew (#37930)

Addresses the review on #38009:

- The grid is now `layout="grid"` on the shared dot-folder-list-view, with the
  Drive card passed in as a `#card` template. One component owns selection and
  ranges, the anchor and tab stop, the page lock and paginator, drag and drop,
  the skeleton and the empty state. The hidden table and the activeKey handoff
  between two listings are gone, which also removes the stale-key bug in
  #followActiveKey. Opt-in: other consumers keep the table unchanged.
- The grid paginator shares the table's lock, with Previous/Next truly
  disabled while a page loads, so Enter can no longer move it past the page.
- Drag pictures are looked up by dataKey (inode), so a language version drags
  its own preview; drags are accepted and highlighted over folders only, and
  dragleave clears the highlight (table and grid).
- The card avatar shows the editor's initial and never calls Gravatar; card
  spacing goes through p-card's design tokens ([dt]).
- Back/Forward keep the current view (it is a preference); the URL is fixed in
  place without a history push. The remembered-view storage key is worked out
  once when the Drive opens.
- Spec, data model and UI contract updated to match.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@rjvelazco rjvelazco changed the title feat(content-drive): grid view with a table/grid switcher (#37930) feat(content-drive): grid view with a table/grid switcher Oct 13, 2026
@rjvelazco
rjvelazco added this pull request to the merge queue Oct 13, 2026
Merged via the queue into main with commit c711383 Oct 14, 2026
53 checks passed
@rjvelazco
rjvelazco deleted the issue-37930-content-drive-grid-view-impl branch October 14, 2026 00:29
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

AI: Safe To Rollback Area : Documentation PR changes documentation files Area : Frontend PR changes Angular/TypeScript frontend code

Projects

Status: No status

Development

Successfully merging this pull request may close these issues.

Add a grid view to Content Drive

2 participants