Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor: register nav items via extension registry #10329

Merged
merged 3 commits into from
Jan 15, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 6 additions & 3 deletions packages/web-runtime/src/components/Topbar/TopBar.vue
Original file line number Diff line number Diff line change
Expand Up @@ -56,11 +56,13 @@ import {
useAuthStore,
useCapabilityStore,
useEmbedMode,
useExtensionRegistry,
useRouter,
useStore,
useThemeStore
} from '@ownclouders/web-pkg'
import { isRuntimeRoute } from '../../router'
import { getExtensionNavItems } from '../../helpers/navItems'

export default {
components: {
Expand All @@ -81,6 +83,7 @@ export default {
setup(props) {
const store = useStore()
const capabilityStore = useCapabilityStore()
const extensionRegistry = useExtensionRegistry()
const themeStore = useThemeStore()
const { currentTheme } = storeToRefs(themeStore)

Expand Down Expand Up @@ -127,9 +130,9 @@ export default {
if (app.type === 'extension') {
// check if the extension has at least one navItem with a matching menuId
return (
store.getters
.getNavItemsByExtension(app.id)
.filter((navItem) => isNavItemPermitted(permittedMenus, navItem)).length > 0 ||
getExtensionNavItems({ extensionRegistry, appId: app.id }).filter((navItem) =>
isNavItemPermitted(permittedMenus, navItem)
).length > 0 ||
(app.applicationMenu.enabled instanceof Function &&
app.applicationMenu.enabled(store, ability) &&
!permittedMenus.includes('user'))
Expand Down
25 changes: 15 additions & 10 deletions packages/web-runtime/src/container/api.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { RouteRecordRaw, Router } from 'vue-router'
import clone from 'lodash-es/clone'
import { RuntimeApi } from './types'
import { ApiError } from '@ownclouders/web-pkg'
import { ApiError, ExtensionRegistry, SidebarNavExtension } from '@ownclouders/web-pkg'
import { get, isEqual, isObject, isArray, merge } from 'lodash-es'
import { Module, Store } from 'vuex'
import { App, Component, h } from 'vue'
import { App, Component, computed, h } from 'vue'
import { ApplicationTranslations, AppNavigationItem } from '@ownclouders/web-pkg'
import type { Language } from 'vue3-gettext'

Expand Down Expand Up @@ -61,22 +61,25 @@ const announceRoutes = (applicationId: string, router: Router, routes: RouteReco
* inject application specific navigation items into runtime
*
* @param applicationId
* @param store
* @param extensionRegistry
* @param navigationItems
*/
const announceNavigationItems = (
applicationId: string,
store: Store<unknown>,
extensionRegistry: ExtensionRegistry,
navigationItems: AppNavigationItem[]
): void => {
if (!isObject(navigationItems)) {
throw new ApiError("navigationItems can't be blank")
}

store.commit('SET_NAV_ITEMS_FROM_CONFIG', {
extension: applicationId,
navItems: navigationItems
})
const navExtensions = navigationItems.map((navItem) => ({
type: 'sidebarNav',
navItem,
scopes: [`app.${applicationId}`]
})) as SidebarNavExtension[]

extensionRegistry.registerExtensions(computed(() => navExtensions))
}

/**
Expand Down Expand Up @@ -213,14 +216,16 @@ export const buildRuntimeApi = ({
store,
router,
gettext,
supportedLanguages
supportedLanguages,
extensionRegistry
}: {
applicationName: string
applicationId: string
store: Store<unknown>
gettext: Language
router: Router
supportedLanguages: { [key: string]: string }
extensionRegistry: ExtensionRegistry
}): RuntimeApi => {
if (!applicationName) {
throw new ApiError("applicationName can't be blank")
Expand All @@ -234,7 +239,7 @@ export const buildRuntimeApi = ({
announceRoutes: (routes: RouteRecordRaw[]): void =>
announceRoutes(applicationId, router, routes),
announceNavigationItems: (navigationItems: AppNavigationItem[]): void =>
announceNavigationItems(applicationId, store, navigationItems),
announceNavigationItems(applicationId, extensionRegistry, navigationItems),
announceTranslations: (appTranslations: ApplicationTranslations): void =>
announceTranslations(supportedLanguages, gettext, appTranslations),
announceStore: (applicationStore: Module<unknown, unknown>): void =>
Expand Down
7 changes: 5 additions & 2 deletions packages/web-runtime/src/container/application/classic.ts
Original file line number Diff line number Diff line change
Expand Up @@ -119,19 +119,22 @@ export const convertClassicApplication = async ({
throw new RuntimeError("appInfo.name can't be blank")
}

const extensionRegistry = useExtensionRegistry({ configurationManager })

const runtimeApi = buildRuntimeApi({
applicationName,
applicationId,
store,
router,
gettext,
supportedLanguages
supportedLanguages,
extensionRegistry
})

await store.dispatch('registerApp', applicationScript.appInfo)

if (applicationScript.extensions) {
useExtensionRegistry({ configurationManager }).registerExtensions(applicationScript.extensions)
extensionRegistry.registerExtensions(applicationScript.extensions)
}

return new ClassicApplication(runtimeApi, applicationScript, app)
Expand Down
27 changes: 21 additions & 6 deletions packages/web-runtime/src/container/bootstrap.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { registerClient } from '../services/clientRegistration'
import { RuntimeConfiguration } from './types'
import { buildApplication, NextApplication } from './application'
import { Store } from 'vuex'
import { Router } from 'vue-router'
import { RouteLocationRaw, Router, RouteRecordNormalized } from 'vue-router'
import { App, computed } from 'vue'
import { loadTheme } from '../helpers/theme'
import OwnCloud from 'owncloud-sdk'
Expand All @@ -18,7 +18,9 @@ import {
useAuthStore,
AuthStore,
useCapabilityStore,
CapabilityStore
CapabilityStore,
useExtensionRegistry,
ExtensionRegistry
} from '@ownclouders/web-pkg'
import { authService } from '../services/auth'
import {
Expand Down Expand Up @@ -46,6 +48,7 @@ import { Resource } from '@ownclouders/web-client'
import PQueue from 'p-queue'
import { extractNodeId, extractStorageId } from '@ownclouders/web-client/src/helpers'
import { storeToRefs } from 'pinia'
import { getExtensionNavItems } from '../helpers/navItems'

const getEmbedConfigFromQuery = (
doesEmbedEnabledOptionExists: boolean
Expand Down Expand Up @@ -344,11 +347,21 @@ export const announceTheme = async ({
export const announcePiniaStores = () => {
const authStore = useAuthStore()
const capabilityStore = useCapabilityStore()
const extensionRegistry = useExtensionRegistry({ configurationManager })
const messagesStore = useMessages()
const modalStore = useModals()
const spacesStore = useSpacesStore()
const userStore = useUserStore()
return { authStore, capabilityStore, messagesStore, modalStore, spacesStore, userStore }

return {
authStore,
capabilityStore,
extensionRegistry,
messagesStore,
modalStore,
spacesStore,
userStore
}
}

/**
Expand Down Expand Up @@ -539,10 +552,12 @@ export const announcePasswordPolicyService = ({ app }: { app: App }): void => {
*/
export const announceDefaults = ({
store,
router
router,
extensionRegistry
}: {
store: Store<unknown>
router: Router
extensionRegistry: ExtensionRegistry
}): void => {
// set home route
const appIds = store.getters.appIds
Expand All @@ -551,11 +566,11 @@ export const announceDefaults = ({
defaultExtensionId = appIds[0]
}

let route = router.getRoutes().find((r) => {
let route: RouteRecordNormalized | RouteLocationRaw = router.getRoutes().find((r) => {
return r.path.startsWith(`/${defaultExtensionId}`) && r.meta?.entryPoint === true
})
if (!route) {
route = store.getters.getNavItemsByExtension(defaultExtensionId)[0]?.route
route = getExtensionNavItems({ extensionRegistry, appId: defaultExtensionId })[0]?.route
}
if (route) {
router.addRoute({
Expand Down
14 changes: 13 additions & 1 deletion packages/web-runtime/src/helpers/navItems.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,18 @@
import { AppNavigationItem } from '@ownclouders/web-pkg'
import { AppNavigationItem, ExtensionRegistry, SidebarNavExtension } from '@ownclouders/web-pkg'

export interface NavItem extends Omit<AppNavigationItem, 'name'> {
name: string
active: boolean
}

export const getExtensionNavItems = ({
extensionRegistry,
appId
}: {
extensionRegistry: ExtensionRegistry
appId: string
}) =>
extensionRegistry
.requestExtensions<SidebarNavExtension>('sidebarNav', [`app.${appId}`])
.map(({ navItem }) => navItem)
.filter((n) => n.enabled())
5 changes: 3 additions & 2 deletions packages/web-runtime/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,8 @@ export const bootstrapApp = async (configurationPath: string): Promise<void> =>
const app = createApp(pages.success)
app.use(pinia)

const { authStore, capabilityStore, spacesStore, userStore } = announcePiniaStores()
const { authStore, capabilityStore, extensionRegistry, spacesStore, userStore } =
announcePiniaStores()

app.provide('$router', router)

Expand Down Expand Up @@ -145,7 +146,7 @@ export const bootstrapApp = async (configurationPath: string): Promise<void> =>
})
announceCustomStyles({ runtimeConfiguration })
announceCustomScripts({ runtimeConfiguration })
announceDefaults({ store, router })
announceDefaults({ store, router, extensionRegistry })

app.use(router)
app.use(store)
Expand Down
25 changes: 4 additions & 21 deletions packages/web-runtime/src/layouts/Application.vue
Original file line number Diff line number Diff line change
Expand Up @@ -42,18 +42,13 @@
<script lang="ts">
import { mapGetters } from 'vuex'
import orderBy from 'lodash-es/orderBy'
import {
AppLoadingSpinner,
SidebarNavExtension,
useAuthStore,
useExtensionRegistry
} from '@ownclouders/web-pkg'
import { AppLoadingSpinner, useAuthStore, useExtensionRegistry } from '@ownclouders/web-pkg'
import TopBar from '../components/Topbar/TopBar.vue'
import MessageBar from '../components/MessageBar.vue'
import SidebarNav from '../components/SidebarNav/SidebarNav.vue'
import UploadInfo from '../components/UploadInfo.vue'
import MobileNav from '../components/MobileNav.vue'
import { NavItem } from '../helpers/navItems'
import { NavItem, getExtensionNavItems } from '../helpers/navItems'
import { LoadingIndicator } from '@ownclouders/web-pkg'
import {
useActiveApp,
Expand All @@ -67,7 +62,6 @@ import { useRouter } from 'vue-router'
import { useGettext } from 'vue3-gettext'

import '@uppy/core/dist/style.min.css'
import { AppNavigationItem } from '@ownclouders/web-pkg'

const MOBILE_BREAKPOINT = 640

Expand All @@ -92,13 +86,7 @@ export default defineComponent({
const extensionRegistry = useExtensionRegistry()

const extensionNavItems = computed(() =>
extensionRegistry
.requestExtensions<SidebarNavExtension>('sidebarNav', [
unref(activeApp),
`app.${unref(activeApp)}`
])
.map(({ navItem }) => navItem)
.filter((n) => n.enabled())
getExtensionNavItems({ extensionRegistry, appId: unref(activeApp) })
)

// FIXME: we can convert to a single router-view without name (thus without the loop) and without this watcher when we release v6.0.0
Expand Down Expand Up @@ -141,14 +129,9 @@ export default defineComponent({
return []
}

const items = [
...store.getters['getNavItemsByExtension'](unref(activeApp)),
...unref(extensionNavItems)
] as AppNavigationItem[]

const { href: currentHref } = router.resolve(unref(route))
return orderBy(
items.map((item) => {
unref(extensionNavItems).map((item) => {
let active = typeof item.isActive !== 'function' || item.isActive()

if (active) {
Expand Down
2 changes: 0 additions & 2 deletions packages/web-runtime/src/store/index.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import ancestorMetaData from './ancestorMetaData'
import apps from './apps'
import config from './config'
import navigation from './navigation'

const runtime = {
namespaced: true,
Expand All @@ -14,7 +13,6 @@ export default {
modules: {
apps,
config,
navigation,
runtime
}
}
54 changes: 0 additions & 54 deletions packages/web-runtime/src/store/navigation.ts

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ export const defaultStoreMockOptions = {
commit: jest.fn(),
getters: {
newFileHandlers: jest.fn(() => []),
getNavItemsByExtension: jest.fn(),
apps: jest.fn(() => ({})),
configuration: jest.fn().mockImplementation(() => ({
options: {
Expand Down