Skip to content

Commit

Permalink
feat: add registering nav items via extenion
Browse files Browse the repository at this point in the history
  • Loading branch information
JammingBen committed Oct 16, 2023
1 parent ec27758 commit 4f42f68
Show file tree
Hide file tree
Showing 8 changed files with 117 additions and 45 deletions.
12 changes: 8 additions & 4 deletions packages/web-app-admin-settings/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,8 @@ const navItems = ({ $ability }: { $ability: Ability }): AppNavigationItem[] => [
},
enabled: () => {
return $ability.can('read-all', 'Setting')
}
},
priority: 10
},
{
name: $gettext('Users'),
Expand All @@ -119,7 +120,8 @@ const navItems = ({ $ability }: { $ability: Ability }): AppNavigationItem[] => [
},
enabled: () => {
return $ability.can('read-all', 'Account')
}
},
priority: 20
},
{
name: $gettext('Groups'),
Expand All @@ -129,7 +131,8 @@ const navItems = ({ $ability }: { $ability: Ability }): AppNavigationItem[] => [
},
enabled: () => {
return $ability.can('read-all', 'Group')
}
},
priority: 30
},
{
name: $gettext('Spaces'),
Expand All @@ -139,7 +142,8 @@ const navItems = ({ $ability }: { $ability: Ability }): AppNavigationItem[] => [
},
enabled: () => {
return $ability.can('read-all', 'Drive')
}
},
priority: 40
}
]

Expand Down
15 changes: 15 additions & 0 deletions packages/web-app-files/src/extensions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,21 @@ export const extensions = ({ applicationConfig }: ApplicationSetupOptions) => {
id: 'com.github.owncloud.web.files.search',
type: 'search',
searchProvider: new SDKSearch(store, router, clientService, configurationManager)
},
{
id: 'com.github.owncloud.web.files.nav1',
type: 'sidebarNav',
scopes: ['admin-settings', 'files'],
navItem: {
isActive: () => false,
enabled: () => true,
name: () => 'Foo Bar',
icon: 'close',
handler: () => {
console.log('HI')
},
priority: 10
}
}
] satisfies Extension[]
)
Expand Down
15 changes: 10 additions & 5 deletions packages/web-app-files/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,8 @@ export const navItems = (context): AppNavigationItem[] => {
return !!context?.$store?.getters['runtime/spaces/spaces'].find(
(drive) => isPersonalSpaceResource(drive) && drive.isOwner(context.$store.getters.user)
)
}
},
priority: 10
},
{
name: $gettext('Favorites'),
Expand All @@ -72,7 +73,8 @@ export const navItems = (context): AppNavigationItem[] => {
},
enabled(capabilities) {
return capabilities.files?.favorites
}
},
priority: 20
},
{
name: $gettext('Shares'),
Expand All @@ -91,7 +93,8 @@ export const navItems = (context): AppNavigationItem[] => {
],
enabled(capabilities) {
return capabilities.files_sharing?.api_enabled !== false
}
},
priority: 30
},
{
name: $gettext('Spaces'),
Expand All @@ -102,7 +105,8 @@ export const navItems = (context): AppNavigationItem[] => {
activeFor: [{ path: `/${appInfo.id}/spaces/project` }],
enabled(capabilities) {
return capabilities.spaces?.projects
}
},
priority: 40
},
{
name: $gettext('Deleted files'),
Expand All @@ -113,7 +117,8 @@ export const navItems = (context): AppNavigationItem[] => {
activeFor: [{ path: `/${appInfo.id}/trash` }],
enabled(capabilities) {
return capabilities.dav?.trashbin === '1.0' && capabilities.files?.undelete
}
},
priority: 50
}
]
}
Expand Down
2 changes: 2 additions & 0 deletions packages/web-pkg/src/apps/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ export interface AppNavigationItem {
name?: string | ((capabilities?: Record<string, any>) => string)
route?: RouteLocationRaw
tag?: string
handler?: () => void
priority?: number
}

/**
Expand Down
20 changes: 17 additions & 3 deletions packages/web-pkg/src/composables/piniaStores/extensionRegistry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,12 @@ import { defineStore } from 'pinia'
import { Ref, hasInjectionContext, unref } from 'vue'
import { useConfigurationManager } from '../configuration'
import { ConfigurationManager } from '../../configuration'
import { AppNavigationItem } from '../../apps'

export type BaseExtension = {
id: string
type: string
scopes?: string[]
}

export interface ActionExtension extends BaseExtension {
Expand All @@ -20,7 +22,12 @@ export interface SearchExtension extends BaseExtension {
searchProvider: SearchProvider
}

export type Extension = ActionExtension | SearchExtension
export interface SidebarNavExtension extends BaseExtension {
type: 'sidebarNav'
navItem: AppNavigationItem
}

export type Extension = ActionExtension | SearchExtension | SidebarNavExtension

export const useExtensionRegistry = ({
configurationManager
Expand All @@ -41,13 +48,20 @@ export const useExtensionRegistry = ({
getters: {
requestExtensions:
(state) =>
<ExtensionType extends Extension>(type: string) => {
<ExtensionType extends Extension>(type: string, scope?: string) => {
return state.extensions
.map((e) =>
unref(e).filter((e) => e.type === type && !options.disabledExtensions.includes(e.id))
unref(e).filter(
(e) =>
e.type === type &&
!options.disabledExtensions.includes(e.id) &&
(!scope || e.scopes?.includes(scope))
)
)
.flat() as ExtensionType[]
}
}
})()
}

export type ExtensionRegistry = ReturnType<typeof useExtensionRegistry>
7 changes: 5 additions & 2 deletions packages/web-runtime/src/components/SidebarNav/SidebarNav.vue
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
:name="link.name"
:collapsed="navigation.closed"
:tag="link.tag"
:handler="link.handler"
/>
</oc-list>
</nav>
Expand Down Expand Up @@ -189,7 +190,8 @@ export default defineComponent({
justify-content: flex-end !important;
}
.oc-sidebar-nav li a:not(.active) {
.oc-sidebar-nav li a:not(.active),
.oc-sidebar-nav li button:not(.active) {
&:hover,
&:focus {
text-decoration: none !important;
Expand All @@ -198,7 +200,8 @@ export default defineComponent({
}
}
.oc-sidebar-nav li a.active {
.oc-sidebar-nav li a.active,
.oc-sidebar-nav li button.active {
&:focus,
&:hover {
color: var(--oc-color-swatch-primary-contrast);
Expand Down
28 changes: 23 additions & 5 deletions packages/web-runtime/src/components/SidebarNav/SidebarNavItem.vue
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,13 @@
<li class="oc-sidebar-nav-item oc-pb-xs oc-px-s" :aria-current="active ? 'page' : null">
<oc-button
v-oc-tooltip="toolTip"
type="router-link"
:type="handler ? 'button' : 'router-link'"
:appearance="active ? 'raw-inverse' : 'raw'"
:variation="active ? 'primary' : 'passive'"
:class="['oc-sidebar-nav-item-link', { active: active }]"
:to="target"
:class="['oc-sidebar-nav-item-link', 'oc-oc-width-1-1', { active: active }]"
:data-nav-id="index"
:data-nav-name="navName"
v-bind="attrs"
>
<span class="oc-flex">
<oc-icon :name="icon" :fill-type="fillType" variation="inherit" />
Expand All @@ -19,7 +19,7 @@
</li>
</template>
<script lang="ts">
import { defineComponent, PropType } from 'vue'
import { computed, defineComponent, PropType } from 'vue'
import { RouteLocationRaw } from 'vue-router'
export default defineComponent({
Expand Down Expand Up @@ -60,11 +60,29 @@ export default defineComponent({
type: String,
required: false,
default: null
},
handler: {
type: Function as PropType<() => void>,
required: false,
default: null
}
},
setup(props) {
const attrs = computed(() => {
return {
...(props.handler && { onClick: props.handler }),
...(props.target && { to: props.target })
}
})
return { attrs }
},
computed: {
navName() {
return this.$router?.resolve(this.target, this.$route)?.name || 'route.name'
if (this.target) {
return this.$router?.resolve(this.target, this.$route)?.name || 'route.name'
}
return this.name
},
toolTip() {
const value = this.collapsed
Expand Down
63 changes: 37 additions & 26 deletions packages/web-runtime/src/layouts/Application.vue
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,8 @@

<script lang="ts">
import { mapActions, mapGetters } from 'vuex'
import { AppLoadingSpinner } from '@ownclouders/web-pkg'
import orderBy from 'lodash-es/orderBy'
import { AppLoadingSpinner, SidebarNavExtension, 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'
Expand Down Expand Up @@ -76,6 +77,13 @@ export default defineComponent({
const { $gettext } = useGettext()
const isUserContext = useUserContext({ store })
const activeApp = useActiveApp()
const extensionRegistry = useExtensionRegistry()
const extensionNavItems = computed(() =>
extensionRegistry
.requestExtensions<SidebarNavExtension>('sidebarNav', unref(activeApp))
.map(({ navItem }) => navItem)
)
// 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
watch(
Expand Down Expand Up @@ -117,36 +125,39 @@ export default defineComponent({
return []
}
const items = store.getters['getNavItemsByExtension'](unref(activeApp)) as AppNavigationItem[]
if (!items) {
return []
}
const items = [
...store.getters['getNavItemsByExtension'](unref(activeApp)),
...unref(extensionNavItems)
] as AppNavigationItem[]
const { href: currentHref } = router.resolve(unref(route))
return items.map((item) => {
let active = typeof item.isActive !== 'function' || item.isActive()
return orderBy(
items.map((item) => {
let active = typeof item.isActive !== 'function' || item.isActive()
if (active) {
active = [item.route, ...(item.activeFor || [])].filter(Boolean).some((currentItem) => {
try {
const comparativeHref = router.resolve(currentItem).href
return currentHref.startsWith(comparativeHref)
} catch (e) {
console.error(e)
return false
}
})
}
if (active) {
active = [item.route, ...(item.activeFor || [])].filter(Boolean).some((currentItem) => {
try {
const comparativeHref = router.resolve(currentItem).href
return currentHref.startsWith(comparativeHref)
} catch (e) {
console.error(e)
return false
}
})
}
const name =
typeof item.name === 'function' ? item.name(store.getters['capabilities']) : item.name
const name =
typeof item.name === 'function' ? item.name(store.getters['capabilities']) : item.name
return {
...item,
name: $gettext(name),
active
}
})
return {
...item,
name: $gettext(name),
active
}
}),
['priority', 'name']
)
})
const isSidebarVisible = computed(() => {
Expand Down

0 comments on commit 4f42f68

Please sign in to comment.