From 235452992b0b31677175c58fc30af0d973d273ce Mon Sep 17 00:00:00 2001 From: Ruben van der Linde Date: Fri, 16 Oct 2026 11:28:53 +0200 Subject: [PATCH] feat(applications): page listing all applications An "All applications" entry in the Tables navigation opens /applications: a card per application, sorted by name, with its icon, description, number of menu items and technical name. A card opens the application's overview, and an application can be created from the page. Assisted-by: ClaudeCode:claude-opus-5-5 --- playwright/e2e/application.spec.ts | 17 ++ src/App.vue | 3 + .../navigation/sections/Navigation.vue | 8 + src/pages/Applications.vue | 147 ++++++++++++++++++ src/router.js | 6 + 5 files changed, 181 insertions(+) create mode 100644 src/pages/Applications.vue diff --git a/playwright/e2e/application.spec.ts b/playwright/e2e/application.spec.ts index 401e06b70d..b72fda9427 100644 --- a/playwright/e2e/application.spec.ts +++ b/playwright/e2e/application.spec.ts @@ -6,6 +6,7 @@ import type { APIRequestContext, Page } from '@playwright/test' import { test, expect } from '../support/fixtures' import { ocsRequest, type TestUser } from '../support/api' +import { createContext, ensureNavigationOpen } from '../support/commands' test.use({ viewport: { width: 1600, height: 1000 } }) @@ -200,6 +201,22 @@ test.describe('Applications', () => { await expect(page).toHaveURL(/\/menu\/dashboard$/) }) + test('All applications are listed on one page that opens each of them', async ({ userPage: { page } }) => { + await page.goto('/index.php/apps/tables') + await createContext(page, 'listed application') + + // Opening the new application collapses the Tables navigation + await ensureNavigationOpen(page) + await page.locator('[data-cy="navigationAllApplications"] a').first().click() + const card = page.locator('[data-cy="application-card"]').filter({ hasText: 'listed application' }) + await expect(card).toBeVisible() + await expect(card).toContainText('1 menu item') + await card.click() + + await expect(page).toHaveURL(/\/application\/\d+\/overview$/) + await expect(page.locator('[data-cy="context-title"]')).toContainText('listed application') + }) + test('The overview lists the pages; a page added there gets a menu entry and opens in edit mode', async ({ userPage: { page, user } }) => { const contextResponse = await ocsRequest(page.request, user, { method: 'POST', url: `${API}/contexts?format=json`, data: { name: 'overview application', iconName: 'star' } }) const contextId = (await contextResponse.json()).ocs.data.id diff --git a/src/App.vue b/src/App.vue index 39c01dd331..d153ab3fa5 100644 --- a/src/App.vue +++ b/src/App.vue @@ -147,6 +147,9 @@ export default { || document.querySelector(`header .header-left .app-menu a[href="${url}"]`) this.switchActiveMenuEntry(targetElement) } + } else if (currentRoute.path === '/applications') { + this.setActiveContextId(null) + this.setPageTitle(t('tables', 'Applications')) } else if (currentRoute.path.startsWith('/application/')) { const contextId = parseInt(currentRoute.params.contextId) this.setActiveContextId(contextId) diff --git a/src/modules/navigation/sections/Navigation.vue b/src/modules/navigation/sections/Navigation.vue index 8eb153920a..e478528f61 100644 --- a/src/modules/navigation/sections/Navigation.vue +++ b/src/modules/navigation/sections/Navigation.vue @@ -66,6 +66,12 @@ + + + + @@ -108,6 +114,7 @@ import { emit } from '@nextcloud/event-bus' import { useEventBusSubscriptions } from '../../../shared/composables/useEventBusSubscriptions.js' import Magnify from 'vue-material-design-icons/Magnify.vue' import ArchiveOutline from 'vue-material-design-icons/ArchiveOutline.vue' +import ViewGridOutline from 'vue-material-design-icons/ViewGridOutline.vue' import { getCurrentUser } from '@nextcloud/auth' import { loadState } from '@nextcloud/initial-state' import { mapState, mapActions } from 'pinia' @@ -125,6 +132,7 @@ export default { NcTextField, Magnify, ArchiveOutline, + ViewGridOutline, NcButton, NcCounterBubble, NcEmptyContent, diff --git a/src/pages/Applications.vue b/src/pages/Applications.vue new file mode 100644 index 0000000000..d407782205 --- /dev/null +++ b/src/pages/Applications.vue @@ -0,0 +1,147 @@ + + + + + + diff --git a/src/router.js b/src/router.js index adfe3c1cb7..09d70556e5 100644 --- a/src/router.js +++ b/src/router.js @@ -8,6 +8,7 @@ import MainViewWrapper from './pages/View.vue' import MainDashboardWrapper from './pages/Table.vue' import Startpage from './pages/Startpage.vue' import Context from './pages/Context.vue' +import Applications from './pages/Applications.vue' import PublicTableView from './pages/PublicTableView.vue' export default createRouter({ @@ -18,6 +19,11 @@ export default createRouter({ path: '/', component: Startpage, }, + { + path: '/applications', + component: Applications, + name: 'applications', + }, { path: '/application/:contextId', component: Context,