From 787012127e94a921f9aa45eabf1d2c3bbaf7766e Mon Sep 17 00:00:00 2001 From: Remko Date: Wed, 14 Oct 2026 11:06:16 +0200 Subject: [PATCH] feat(applications): page listing all applications - "All applications" in the navigation opens one page with every application, its description, menu size and technical name - an application can be created from there as well Signed-off-by: Remko Assisted-by: ClaudeCode:claude-opus-5-5 --- playwright/e2e/application.spec.ts | 12 ++ src/App.vue | 3 + .../navigation/sections/Navigation.vue | 8 + src/pages/Applications.vue | 147 ++++++++++++++++++ src/router.js | 6 + 5 files changed, 176 insertions(+) create mode 100644 src/pages/Applications.vue diff --git a/playwright/e2e/application.spec.ts b/playwright/e2e/application.spec.ts index 8a694da85d..4b316ee581 100644 --- a/playwright/e2e/application.spec.ts +++ b/playwright/e2e/application.spec.ts @@ -96,6 +96,18 @@ test.describe('Applications', () => { await expect(page.locator('[data-cy="application-nav-header"]')).toContainText('named application') }) + 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') + + 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 card.click() + + await expect(page.locator('[data-cy="context-title"]')).toContainText('listed application') + }) + test('A page with widgets added on the overview gets a menu entry and opens', async ({ userPage: { page } }) => { await page.goto('/index.php/apps/tables') await createContext(page, 'overview application') 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 bdc1d6ac64..0d8194fb0c 100644 --- a/src/modules/navigation/sections/Navigation.vue +++ b/src/modules/navigation/sections/Navigation.vue @@ -66,6 +66,12 @@ + + + + @@ -121,6 +127,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' @@ -138,6 +145,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..fb0074ab6e --- /dev/null +++ b/src/pages/Applications.vue @@ -0,0 +1,147 @@ + + + + + + diff --git a/src/router.js b/src/router.js index 9989a7b006..6d53cc699a 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,