From 8515e2f899e54580cf6d6a6de94dd23ad155eeeb Mon Sep 17 00:00:00 2001 From: James Manuel Date: Wed, 20 May 2026 15:38:39 +0200 Subject: [PATCH 1/6] feat(overview): add grid view preference persistence via user config Adds PUT /apps/richdocuments/settings/overview/grid_view endpoint to persist the overview view mode per user. OverviewController injects the saved preference as initial state so the frontend reads it on mount. Signed-off-by: James Manuel --- appinfo/routes.php | 1 + lib/Controller/OverviewController.php | 6 ++++++ lib/Controller/SettingsController.php | 6 ++++++ 3 files changed, 13 insertions(+) diff --git a/appinfo/routes.php b/appinfo/routes.php index 6646968513..a51d67e3b2 100644 --- a/appinfo/routes.php +++ b/appinfo/routes.php @@ -51,6 +51,7 @@ ], ], ['name' => 'settings#generateIframeToken', 'url' => 'settings/generateToken/{type}', 'verb' => 'GET'], + ['name' => 'settings#setOverviewGridView', 'url' => 'settings/overview/grid_view', 'verb' => 'PUT'], // Direct Editing: Webview ['name' => 'directView#show', 'url' => '/direct/{token}', 'verb' => 'GET'], diff --git a/lib/Controller/OverviewController.php b/lib/Controller/OverviewController.php index a6d1e14708..d752e8ccbe 100644 --- a/lib/Controller/OverviewController.php +++ b/lib/Controller/OverviewController.php @@ -15,6 +15,7 @@ use OCP\AppFramework\Http\TemplateResponse; use OCP\AppFramework\Services\IInitialState; use OCP\EventDispatcher\IEventDispatcher; +use OCP\IConfig; use OCP\IPreview; use OCP\IRequest; use OCP\Util; @@ -27,6 +28,8 @@ public function __construct( private IEventDispatcher $eventDispatcher, private IInitialState $initialState, private IPreview $preview, + private IConfig $config, + private ?string $userId, ) { parent::__construct($appName, $request); } @@ -40,6 +43,9 @@ public function index(): TemplateResponse { Util::addScript('richdocuments', 'richdocuments-overview'); $this->initialState->provideInitialState('previewEnabled', $this->preview->isMimeSupported('application/vnd.oasis.opendocument.text')); + $this->initialState->provideInitialState('config', [ + 'overview_grid_view' => $this->config->getUserValue($this->userId ?? '', 'richdocuments', 'overview_grid_view', '0') === '1', + ]); // Viewer is pre-installed in production but may not be available in other environments if (class_exists(LoadViewer::class)) { diff --git a/lib/Controller/SettingsController.php b/lib/Controller/SettingsController.php index d633f192ae..7bb0f94c8d 100644 --- a/lib/Controller/SettingsController.php +++ b/lib/Controller/SettingsController.php @@ -320,6 +320,12 @@ public function setPersonalSettings($templateFolder, return new JSONResponse($response); } + #[NoAdminRequired] + public function setOverviewGridView(bool $value): JSONResponse { + $this->config->setUserValue($this->userId ?? '', 'richdocuments', 'overview_grid_view', $value ? '1' : '0'); + return new JSONResponse(['message' => 'ok']); + } + /** * @NoAdminRequired * @PublicPage From 3b1207a2b2af5c066a09aa0b4111c92e2ec157a9 Mon Sep 17 00:00:00 2001 From: James Manuel Date: Wed, 20 May 2026 15:38:47 +0200 Subject: [PATCH 2/6] feat(overview): add list view and view mode toggle List view is now the default. A toggle in the toolbar switches between list and grid. List rows use NcListItem with mtime subname and a star indicator for favourites. The chosen mode persists via the new settings/overview/grid_view endpoint. Signed-off-by: James Manuel --- src/services/config.js | 11 +++ src/views/OfficeOverview.vue | 131 +++++++++++++++++++++++++++-------- 2 files changed, 112 insertions(+), 30 deletions(-) create mode 100644 src/services/config.js diff --git a/src/services/config.js b/src/services/config.js new file mode 100644 index 0000000000..b9b1625f2f --- /dev/null +++ b/src/services/config.js @@ -0,0 +1,11 @@ +/** + * SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +import axios from '@nextcloud/axios' +import { generateUrl } from '@nextcloud/router' + +export async function setOverviewGridView(value) { + await axios.put(generateUrl('/apps/richdocuments/settings/overview/grid_view'), { value }) +} diff --git a/src/views/OfficeOverview.vue b/src/views/OfficeOverview.vue index c0c7e15515..23bfb883c7 100644 --- a/src/views/OfficeOverview.vue +++ b/src/views/OfficeOverview.vue @@ -31,10 +31,28 @@ @@ -105,12 +142,16 @@ import { sortNodes } from '@nextcloud/files' import { loadState } from '@nextcloud/initial-state' import { generateUrl } from '@nextcloud/router' -import { NcAppContent, NcAppNavigation, NcAppNavigationItem, NcButton, NcContent, NcDateTime, NcDialog, NcEmptyContent, NcLoadingIcon, NcTextField } from '@nextcloud/vue' +import { NcAppContent, NcAppNavigation, NcAppNavigationItem, NcButton, NcContent, NcDateTime, NcDialog, NcEmptyContent, NcListItem, NcLoadingIcon, NcTextField } from '@nextcloud/vue' import FileDocumentOutline from 'vue-material-design-icons/FileDocumentOutline.vue' +import Star from 'vue-material-design-icons/Star.vue' +import ViewGrid from 'vue-material-design-icons/ViewGrid.vue' +import ViewList from 'vue-material-design-icons/ViewList.vue' import FileCard from '../components/FileCard.vue' import TemplateSection from '../components/TemplateSection.vue' import { getAllOfficeFiles, filterByMimes, invalidateOfficeFilesCache } from '../services/officeFiles.js' import { getTemplates, createFromTemplate } from '../services/templates.js' +import { setOverviewGridView } from '../services/config.js' export default { name: 'OfficeOverview', @@ -126,9 +167,13 @@ export default { NcDateTime, NcDialog, NcEmptyContent, + NcListItem, NcLoadingIcon, NcTextField, + Star, TemplateSection, + ViewGrid, + ViewList, }, data() { @@ -139,6 +184,7 @@ export default { loading: false, error: null, previewEnabled: loadState('richdocuments', 'previewEnabled', false), + viewMode: loadState('richdocuments', 'config', {}).overview_grid_view ? 'grid' : 'list', searchQuery: '', showCreateDialog: false, newFileName: '', @@ -187,6 +233,11 @@ export default { this.activeCreator = creator }, + setViewMode(mode) { + this.viewMode = mode + setOverviewGridView(mode === 'grid').catch(() => {}) + }, + getPreviewUrl(file) { return generateUrl('/core/preview?fileId={fileid}&x={x}&y={y}', { fileid: file.fileid, @@ -292,10 +343,30 @@ export default { margin: 32px auto; } -.office-overview__search { +.office-overview__toolbar { + display: flex; + align-items: center; + gap: calc(var(--default-grid-baseline) * 2); padding: calc(var(--default-grid-baseline) * 4) calc(var(--default-grid-baseline) * 4) 0; +} + +.office-overview__search { + flex: 1; max-width: 400px; - margin: 0 auto; +} + +.office-overview__view-toggle { + display: flex; + gap: calc(var(--default-grid-baseline)); + flex-shrink: 0; +} + +.office-overview__list { + padding: calc(var(--default-grid-baseline) * 2) calc(var(--default-grid-baseline) * 2); +} + +.office-overview__favourite-icon { + color: var(--color-warning); } .office-overview__create-form { From fde529247e0d857f727d8721203f31923b284587 Mon Sep 17 00:00:00 2001 From: James Manuel Date: Wed, 20 May 2026 15:57:59 +0200 Subject: [PATCH 3/6] feat(overview): add Recent section heading, template background, single toggle, a11y fixes - Center search bar; move view toggle into a files section header alongside a "Recent {category}" h2 heading - Toggle is now a single button switching icon based on current mode - TemplateSection gets a subtle background (--color-background-hover) and uses h2 + aria-labelledby for correct heading hierarchy - FileCard changed from div to button for keyboard accessibility; adds focus-visible outline Signed-off-by: James Manuel --- src/components/FileCard.vue | 19 ++++---- src/components/TemplateSection.vue | 10 ++-- src/views/OfficeOverview.vue | 76 ++++++++++++++++-------------- 3 files changed, 56 insertions(+), 49 deletions(-) diff --git a/src/components/FileCard.vue b/src/components/FileCard.vue index 32ee2a608f..86a856d9d6 100644 --- a/src/components/FileCard.vue +++ b/src/components/FileCard.vue @@ -4,7 +4,7 @@ -->