Skip to content

🖼️ Add persistent per-view layout modes (table / tiles / gallery)#2407

Open
Rello wants to merge 5 commits into
mainfrom
codex/implement-persistent-layout-modes-in-nextcloud-tables
Open

🖼️ Add persistent per-view layout modes (table / tiles / gallery)#2407
Rello wants to merge 5 commits into
mainfrom
codex/implement-persistent-layout-modes-in-nextcloud-tables

Conversation

@Rello

@Rello Rello commented Mar 18, 2026

Copy link
Copy Markdown
Contributor

Motivation

  • Implement optional gallery and tile views, as available in SharePoint
  • Both views are using the first column to get the picture url for the preview
  • Tile is only showing the picture plus its title from the 2nd column. Use case: Index pages
  • Gallery view is showing all additional columns from the current view. Use case: Product catalogue or inventory
  • Provide persistent, per-view layout modes so users can choose between table, tiles and gallery and have that preference stored with the view.
  • Expose layout selection in the View settings modal and apply the persisted layout automatically when a view is opened, while keeping backward compatibility (missing/old values => table).

Implementation

Source data

Bildschirmfoto 2026-03-19 um 20 34 35

Tile View

Bildschirmfoto 2026-03-19 um 20 52 47

Gallery View

Bildschirmfoto 2026-03-19 um 20 53 05

Settings modal

Bildschirmfoto 2026-03-19 um 20 54 28

Sharepoint reference

565593259-fda68f2f-2a04-4ace-b1f9-fb2efbc6c77a-2

@Rello Rello requested review from blizzz and enjeck as code owners March 18, 2026 11:50
@Rello Rello linked an issue Mar 18, 2026 that may be closed by this pull request
@Rello

Rello commented Mar 18, 2026

Copy link
Copy Markdown
Contributor Author

@jancborchardt as new layouts will be relevant for your opinion

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

@marcoambrosini one for you, check it out. :)

@Rello do you have reference screenshots from Sharepoint as you mention?
List and Gallery view currently also look very similar (with Gallery in need of some design improvement).

@Rello Rello marked this pull request as draft March 19, 2026 15:16
@Rello

Rello commented Mar 19, 2026

Copy link
Copy Markdown
Contributor Author

@marcoambrosini can you please have a look now? the PR was now implemented and tested on the branch and I added the actual screenshots from the working app

@Rello Rello marked this pull request as ready for review March 19, 2026 20:37
@jancborchardt jancborchardt moved this to 🏗️ At engineering in 🖍 Design team Mar 30, 2026
@AndyScherzinger AndyScherzinger added enhancement New feature or request 3. to review Waiting for reviews labels Apr 12, 2026
@Rello Rello changed the title Add persistent per-view layout modes (table / tiles / gallery) 🖼️ Add persistent per-view layout modes (table / tiles / gallery) Apr 13, 2026
@Rello Rello requested a review from jancborchardt April 13, 2026 10:47
marcoambrosini

This comment was marked as outdated.

@Rello

This comment was marked as resolved.

Comment thread lib/Migration/Version1000Date20260318000000.php

@enjeck enjeck left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Just confirming that the state when there's no image is designer-approved? Here, for a tile view, I don't see why say "No image" instead of just text-based tiling like in https://media.geeksforgeeks.org/wp-content/uploads/20231218102144/Screenshot-2023-12-18-102058.png, for example.

Image

cc @marcoambrosini

Comment thread src/modules/modals/ViewSettings.vue
Comment thread src/modules/modals/ViewSettings.vue
@Rello Rello marked this pull request as draft April 28, 2026 08:37
@Rello

This comment was marked as outdated.

This comment was marked as outdated.

@Rello

Rello commented Apr 29, 2026

Copy link
Copy Markdown
Contributor Author

Just confirming that the state when there's no image is designer-approved? Here, for a tile view, I don't see why say "No image" instead of just text-based tiling like in https://media.geeksforgeeks.org/wp-content/uploads/20231218102144/Screenshot-2023-12-18-102058.png, for example.
2BK1ZfxUS9CBdrnTws71-prkJ5-t8t_fhbs">
cc @marcoambrosini

@enjeck what do you mean by this? I am not sure if I understand. what would you show instead of the "no image"?

Signed-off-by: Rello <github@scherello.de>

Signed-off-by: Rello <Rello@users.noreply.github.com>

AI-assistant: Codex (GPT-5)
@Rello Rello force-pushed the codex/implement-persistent-layout-modes-in-nextcloud-tables branch from 31001dd to 253b902 Compare May 11, 2026 12:17
@Rello Rello marked this pull request as ready for review May 11, 2026 12:19
chatgpt-codex-connector[bot]

This comment was marked as outdated.

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

@Rello As @enjeck says, it is indeed weird to show "No image", especially as it underlays the text.

Instead, it would be better to show nothing as @enjeck proposed and just focus on the text.

FYI @marcoambrosini

…extcloud-tables

Signed-off-by: Rello <Rello@users.noreply.github.com>

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: a0456bd268

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread lib/Service/ViewService.php
Comment on lines +198 to +199
currentLayout() {
return ['tiles', 'gallery'].includes(this.localViewSetting?.layout) ? this.localViewSetting.layout : 'table'

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Honor saved card layouts in context resources

When a saved view is rendered inside an application/context, src/pages/Context.vue passes the shared empty viewSetting into CustomView rather than the MainWrapper-initialized settings, so this computed property falls back to table and tiles/gallery views display as normal tables in that context. The layout should be derived from the view itself or initialized for every caller, not only the main view route.

Useful? React with 👍 / 👎.

Comment thread src/shared/components/ncTable/sections/CustomTable.vue
Rello added 2 commits June 17, 2026 10:56
Signed-off-by: Rello <github@scherello.de>
…t-modes-in-nextcloud-tables' into codex/implement-persistent-layout-modes-in-nextcloud-tables
@Rello

Rello commented Jun 17, 2026

Copy link
Copy Markdown
Contributor Author

Just confirming that the state when there's no image is designer-approved? Here, for a tile view, I don't see why say "No image" instead of just text-based tiling like in https://media.geeksforgeeks.org/wp-content/uploads/20231218102144/Screenshot-2023-12-18-102058.png, for example.

Hello @enjeck , @jancborchardt

it was corrected

Bildschirmfoto 2026-06-17 um 10 34 16

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 8588c7515a

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread lib/Service/ViewService.php
Signed-off-by: Rello <github@scherello.de>

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 7666b30f5d

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

computed: {
...mapState(useTablesStore, ['appNavCollapsed']),
currentLayout() {
return ['tiles', 'gallery'].includes(this.localViewSetting?.layout) ? this.localViewSetting.layout : 'table'

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Hydrate public shares with the saved card layout

When a saved tiles/gallery view is opened through a public link, PublicMainWrapper/PublicElement do not pass any viewSetting, and the public node data currently does not include layout or viewSettings; because the renderer only reads localViewSetting.layout here, public shares always fall back to table even for views that were saved as cards. Please initialize the public view setting from the persisted view data (or let this component consume the persisted element settings) so shared links render the selected layout.

Useful? React with 👍 / 👎.

<div class="container">
<table class="tables-list__table">
<div class="container" :class="{ 'container--cards': currentLayout !== 'table' }">
<table v-if="currentLayout === 'table'" class="tables-list__table">

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Keep the actions slot available in card layouts

For tiles or gallery, this v-if removes the whole table header, which is the only place that renders the named actions slot passed by View.vue, DataTable.vue, and PublicElement.vue. Those slots contain controls such as edit view, create column, import/export, share, and integration, so opening a card-layout view makes the main action menu disappear; render the actions somewhere outside the table header for card layouts.

Useful? React with 👍 / 👎.

@Rello

Rello commented Jun 17, 2026

Copy link
Copy Markdown
Contributor Author

@enjeck can you give it another look when possible? thank you

@enjeck enjeck left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

more comments:

Comment on lines +80 to +115
<div v-if="totalPages > 1" class="pagination-footer" :class="{'large-width': !appNavCollapsed || isMobile}">
<div class="pagination-items">
<NcButton type="tertiary" :disabled="totalPages === 1 || pageNumber <= 1" :aria-label="t('tables', 'Go to first page')" @click="pageNumber = 1">
<template #icon>
<PageFirstIcon :size="20" />
</template>
</NcButton>
<NcButton type="tertiary" :disabled="totalPages === 1 || pageNumber <= 1" :aria-label="t('tables', 'Go to previous page')" @click="pageNumber--">
<template #icon>
<ChevronLeftIcon :size="20" />
</template>
</NcButton>
<div class="page-number">
<NcSelect
v-model="pageNumber"
:options="allPageNumbersArray"
:clearable="false"
:aria-label-combobox="t('tables', 'Page number')">
<template #selected-option-container="{ option }">
<span class="selected-page">
{{ option.label }} of {{ totalPages }}
</span>
</template>
</NcSelect>
</div>
<NcButton type="tertiary" :disabled="totalPages === 1 || pageNumber >= totalPages" :aria-label="t('tables', 'Go to next page')" @click="pageNumber++">
<template #icon>
<ChevronRightIcon :size="20" />
</template>
</NcButton>
<NcButton type="tertiary" :disabled="totalPages === 1 || pageNumber >= totalPages" :aria-label="t('tables', 'Go to last page')" @click="pageNumber = totalPages">
<template #icon>
<PageLastIcon :size="20" />
</template>
</NcButton>
</div>

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

can we reuse PaginationBlock.vue ?

Comment on lines +232 to +236
updated() {
if (this.pageNumber > this.totalPages || this.totalPages === 1) {
this.pageNumber = this.totalPages
}
},

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Mutating reactive state in updated() could cause re-render loops, and the totalPages === 1 branch fires on every update. PaginationBlock handles this with a watcher

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

ideally renamed to later version, since new migration files have been added since this was started

* Ensures that card view settings reference columns that are part of the view.
* @throws InvalidArgumentException
*/
protected function assertCardSourceColumnsAreValid(View $view): void {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

assertCardSourceColumnsAreValid() blocks unrelated view updates.
ViewService::update() calls assertCardSourceColumnsAreValid() on every update. That method re-validates the view's stored cardBackgroundSource and cardTitleSource against the view's column set even when the current request does not touch viewSettings at all.
The result is that once a card source points at a column, any later update that removes that column from the view by changing columnSettings gets rejected even though the request never mentions the card settings. The caller is then forced to also rewrite viewSettings in the same request just to make an unrelated column change succeed.

To reproduce:

# 1. View with two columns; cardTitleSource = column 8
PUT /index.php/apps/tables/api/1/views/3
{"data":{"columnSettings":[{"columnId":7,"order":0},{"columnId":8,"order":1}],
         "layout":"gallery",
         "viewSettings":{"cardBackgroundSource":7,"cardTitleSource":8}}}
→ 200 OK

# 2. Remove column 8 from the view, nothing else changed
PUT /index.php/apps/tables/api/1/views/3
{"data":{"columnSettings":[{"columnId":7,"order":0}]}}
→ 400  {"message":"Invalid cardTitleSource column ID: 8"}

# 3. Identical column change, but also repoint card sources to column 7
PUT /index.php/apps/tables/api/1/views/3
{"data":{"columnSettings":[{"columnId":7,"order":0}],
         "viewSettings":{"cardBackgroundSource":7,"cardTitleSource":7}}}
→ 200 OK

The second and third requests send the same column edit, so the only thing that changes the outcome is whether viewSettings gets rewritten

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

3. to review Waiting for reviews AI assisted codex enhancement New feature or request

Projects

Status: 🏗️ At engineering

Development

Successfully merging this pull request may close these issues.

Add view mode: Tiles

7 participants