From 502f71f15b5a91c2b77ffa59d1dae792036877e4 Mon Sep 17 00:00:00 2001 From: Benedikt Kulmann Date: Thu, 4 Apr 2024 11:06:23 +0200 Subject: [PATCH 01/13] feat: register extensionPoints from app bootstrapping --- packages/web-app-files/src/extensionPoints.ts | 0 packages/web-pkg/src/apps/types.ts | 3 ++- .../extensionRegistry/extensionRegistry.ts | 21 ++++++++++++------- .../src/container/application/classic.ts | 4 ++++ 4 files changed, 20 insertions(+), 8 deletions(-) create mode 100644 packages/web-app-files/src/extensionPoints.ts diff --git a/packages/web-app-files/src/extensionPoints.ts b/packages/web-app-files/src/extensionPoints.ts new file mode 100644 index 00000000000..e69de29bb2d diff --git a/packages/web-pkg/src/apps/types.ts b/packages/web-pkg/src/apps/types.ts index da1734a4673..bd3cff0301b 100644 --- a/packages/web-pkg/src/apps/types.ts +++ b/packages/web-pkg/src/apps/types.ts @@ -1,6 +1,6 @@ import { App, ComponentCustomProperties, Ref } from 'vue' import { RouteLocationRaw, Router, RouteRecordRaw } from 'vue-router' -import { Extension } from '../composables/piniaStores' +import { Extension, ExtensionPoint } from '../composables/piniaStores' import { IconFillType } from '../helpers' import { Resource, SpaceResource } from '@ownclouders/web-client' @@ -94,6 +94,7 @@ export interface ClassicApplicationScript { navItems?: ((args: ComponentCustomProperties) => AppNavigationItem[]) | AppNavigationItem[] translations?: ApplicationTranslations extensions?: Ref + extensionPoints?: Ref initialize?: () => void ready?: (args: AppReadyHookArgs) => Promise | void mounted?: (args: AppReadyHookArgs) => void diff --git a/packages/web-pkg/src/composables/piniaStores/extensionRegistry/extensionRegistry.ts b/packages/web-pkg/src/composables/piniaStores/extensionRegistry/extensionRegistry.ts index 8c8396a0d92..e2127cfb8e1 100644 --- a/packages/web-pkg/src/composables/piniaStores/extensionRegistry/extensionRegistry.ts +++ b/packages/web-pkg/src/composables/piniaStores/extensionRegistry/extensionRegistry.ts @@ -30,8 +30,11 @@ export const useExtensionRegistry = defineStore('extensionRegistry', () => { ) as T[] } - const extensionPoints = ref([]) + const extensionPoints = ref[]>([]) const registerExtensionPoint = (e: ExtensionPoint) => { + extensionPoints.value.push(ref([e])) + } + const registerExtensionPoints = (e: Ref) => { extensionPoints.value.push(e) } const getExtensionPoints = ( @@ -39,12 +42,15 @@ export const useExtensionRegistry = defineStore('extensionRegistry', () => { type?: ExtensionType } = {} ) => { - return unref(extensionPoints).filter((e) => { - if (Object.hasOwn(options, 'type') && e.type !== options.type) { - return false - } - return true - }) as T[] + return unref(extensionPoints).flatMap( + (e) => + unref(e).filter((e) => { + if (Object.hasOwn(options, 'type') && e.type !== options.type) { + return false + } + return true + }) as T[] + ) } return { @@ -53,6 +59,7 @@ export const useExtensionRegistry = defineStore('extensionRegistry', () => { requestExtensions, extensionPoints, registerExtensionPoint, + registerExtensionPoints, getExtensionPoints } }) diff --git a/packages/web-runtime/src/container/application/classic.ts b/packages/web-runtime/src/container/application/classic.ts index f14df2ac701..986b9a82c27 100644 --- a/packages/web-runtime/src/container/application/classic.ts +++ b/packages/web-runtime/src/container/application/classic.ts @@ -129,5 +129,9 @@ export const convertClassicApplication = ({ extensionRegistry.registerExtensions(applicationScript.extensions) } + if (applicationScript.extensionPoints) { + extensionRegistry.registerExtensionPoints(applicationScript.extensionPoints) + } + return new ClassicApplication(runtimeApi, applicationScript, app) } From 1242b448d8c285ae8e529eb82c5e5d7044e61909 Mon Sep 17 00:00:00 2001 From: Benedikt Kulmann Date: Fri, 5 Apr 2024 09:26:22 +0200 Subject: [PATCH 02/13] feat: introduce extension points --- docs/extension-system/_index.md | 29 ++++---- .../src/components/AppBar/CreateAndUpload.vue | 5 +- .../src/components/FilesList/QuickActions.vue | 5 +- .../composables/extensions/useFileSideBars.ts | 25 +++---- .../composables/extensions/useFolderViews.ts | 14 ++-- packages/web-app-files/src/extensionPoints.ts | 55 +++++++++++++++ packages/web-app-files/src/extensions.ts | 13 ++-- packages/web-app-files/src/index.ts | 7 +- .../web-app-files/src/views/Favorites.vue | 9 ++- .../src/views/spaces/GenericSpace.vue | 5 +- .../src/views/spaces/Projects.vue | 5 +- .../components/FilesList/QuickActions.spec.ts | 13 +++- .../tests/unit/views/Favorites.spec.ts | 7 +- .../unit/views/spaces/GenericSpace.spec.ts | 7 +- .../tests/unit/views/spaces/Projects.spec.ts | 7 +- packages/web-app-importer/src/extensions.ts | 2 +- packages/web-app-ocm/src/extensions.ts | 6 +- .../web-pkg/src/components/AppBar/AppBar.vue | 12 +++- .../ContextActions/ContextActionMenu.vue | 2 +- .../components/FilesList/ContextActions.vue | 69 ++++++++++++------- .../src/components/SideBar/FileSideBar.vue | 6 +- .../web-pkg/src/composables/actions/types.ts | 1 + .../extensionRegistry/extensionRegistry.ts | 7 +- .../piniaStores/extensionRegistry/types.ts | 4 +- .../unit/components/AppBar/AppBar.spec.ts | 19 ++--- .../components/CustomComponentTarget.spec.ts | 4 +- .../extensionRegistry.spec.ts | 14 ++-- .../Account/ExtensionPreference.vue | 2 +- .../web-runtime/src/layouts/Application.vue | 2 +- packages/web-runtime/src/pages/account.vue | 2 +- 30 files changed, 253 insertions(+), 105 deletions(-) diff --git a/docs/extension-system/_index.md b/docs/extension-system/_index.md index 69eab32ccd7..356765209a6 100644 --- a/docs/extension-system/_index.md +++ b/docs/extension-system/_index.md @@ -96,7 +96,7 @@ In contrast to applications, extensions usually have a rather small scope and de The globally available extension registry provided by the ownCloud Web runtime can be used to both register and query extensions. All extensions which are being made available via an `app` get registered in the extension registry automatically. In your custom application code you can -then query any of the available extensions by providing a `type` string and optionally a list of `scopes`. Throughout the ownCloud Web platform +then query any of the available extensions by providing a `type` string and optionally a list of `extensionPointIds`. Throughout the ownCloud Web platform and most prominently also in the `files` app we have defined some extension points which automatically use certain extensions, see the `Extension Points` section below. @@ -104,8 +104,8 @@ and most prominently also in the `files` app we have defined some extension poin For building an extension you can choose from the types predefined by the ownCloud Web extension system. See the full list of available extension types below. -1. `ActionExtension` (type `action`) - An extension that can register `Action` items which then get shown in various places (e.g. context menus, batch actions), depending on their -respective scope. Most commonly used for file and folder actions (e.g. copy, rename, delete, etc.). For details, please refer to the [action docs]({{< ref "extension-types/actions.md" >}}) +1. `ActionExtension` (type `action`) - An extension that can register `Action` items which then get shown in various places (e.g. context menus, batch actions), depending on the +extension points defined in the extension respectively. Most commonly used for file and folder actions (e.g. copy, rename, delete, etc.). For details, please refer to the [action docs]({{< ref "extension-types/actions.md" >}}) 2. `SearchExtension` (type `search`) - An extension that can register additional search providers. For details, please refer to the [search docs]({{< ref "extension-types/search.md" >}}) 3. `SidebarNavExtension` (type `sidebarNav`) - An extension that can register additional navigation items to the left sidebar. These can be scoped to specific apps, and programmatically enabled/disabled. For details, please refer to the [sidebar nav docs]({{< ref "extension-types/left-sidebar-menu-item.md" >}}) @@ -134,17 +134,22 @@ You can find predefined extension point ids in the extension points section belo #### Extension Points There are standardized components and places where extensions are being used automatically. The following are the ones that are currently provided -by the ownCloud Web runtime or the `files` app. +by the ownCloud Web runtime or the `files` app. If you decide to develop an extension which fulfills the type and allows the extensionPointId of the respective extension point, +your extension will be used automatically. 1. Left Sidebar for Navigation -2. Right Sidebar in any file(s) context -3. Folder Views in the files app -4. Right click context menu in the files app -5. Batch actions in the files app -6. Upload menu in the files app -7. Quick actions in the files list of the files app -8. Search results in the search app -9. Global progress bar for the global loading state. Extension point id `app.runtime.global-progress-bar`. Allows to render a single custom component. +2. Any file(s) context (files app, viewer apps, editor apps) + 1. Right sidebar. ExtensionPointId `app.files.sidebar`. Mounts extensions of type `sidebarPanel`. +3. Files app + 1. Folder views for regular folders. ExtensionPointId `app.files.folder-views.folder`. + 2. Folder views for the project spaces overview. ExtensionPointId `app.files.folder-views.project-spaces`. + 3. Folder views for the favorites page. ExtensionPointId `app.files.folder-views.favorites`. + 4. Right click context menu. ExtensionPointId `app.files.context-actions`. + 5. Batch actions in the app bar above file lists. ExtensionPointId `app.files.batch-actions`. + 6. Upload menu. ExtensionPointId `app.files.upload-menu`. + 7. Quick actions. ExtensionPointId `app.files.quick-actions`. +4. Search results in the search app +5. Global progress bar for the global loading state. ExtensionPointId `app.runtime.global-progress-bar`. Mounts a single extensions of type `customComponent`. #### User Preferences for Extensions diff --git a/packages/web-app-files/src/components/AppBar/CreateAndUpload.vue b/packages/web-app-files/src/components/AppBar/CreateAndUpload.vue index 9986cc69ef6..bdc72d3f043 100644 --- a/packages/web-app-files/src/components/AppBar/CreateAndUpload.vue +++ b/packages/web-app-files/src/components/AppBar/CreateAndUpload.vue @@ -228,6 +228,7 @@ import { ActionExtension, useExtensionRegistry } from '@ownclouders/web-pkg' import { Action, ResourceIcon } from '@ownclouders/web-pkg' import { v4 as uuidv4 } from 'uuid' import { storeToRefs } from 'pinia' +import { uploadMenuExtensionPointId } from '../../extensionPoints' export default defineComponent({ components: { @@ -319,7 +320,9 @@ export default defineComponent({ const extensionActions = computed(() => { return [ ...extensionRegistry - .requestExtensions('action', { scopes: ['upload-menu'] }) + .requestExtensions('action', { + extensionPointIds: [uploadMenuExtensionPointId] + }) .map((e) => e.action) ].filter((e) => e.isVisible()) }) diff --git a/packages/web-app-files/src/components/FilesList/QuickActions.vue b/packages/web-app-files/src/components/FilesList/QuickActions.vue index 8cb860691de..a576eb417d4 100644 --- a/packages/web-app-files/src/components/FilesList/QuickActions.vue +++ b/packages/web-app-files/src/components/FilesList/QuickActions.vue @@ -20,6 +20,7 @@ import { computed, defineComponent, PropType } from 'vue' import { ActionExtension, useEmbedMode, useExtensionRegistry } from '@ownclouders/web-pkg' import { Resource, SpaceResource } from '@ownclouders/web-client' import { unref } from 'vue' +import { quickActionsExtensionPointId } from '../../extensionPoints' export default defineComponent({ name: 'QuickActions', @@ -39,7 +40,9 @@ export default defineComponent({ const filteredActions = computed(() => { return unref(extensionRegistry) - .requestExtensions('action', { scopes: ['resource.quick-action'] }) + .requestExtensions('action', { + extensionPointIds: [quickActionsExtensionPointId] + }) .map((e) => e.action) .filter(({ isVisible }) => isVisible({ space: props.space, resources: [props.item] })) }) diff --git a/packages/web-app-files/src/composables/extensions/useFileSideBars.ts b/packages/web-app-files/src/composables/extensions/useFileSideBars.ts index c6be6c5687b..3278ad801e5 100644 --- a/packages/web-app-files/src/composables/extensions/useFileSideBars.ts +++ b/packages/web-app-files/src/composables/extensions/useFileSideBars.ts @@ -32,6 +32,7 @@ import { import { Resource } from '@ownclouders/web-client' import { useGettext } from 'vue3-gettext' import { computed, unref } from 'vue' +import { fileSideBarExtensionPointId } from '../../extensionPoints' export const useSideBarPanels = () => { const router = useRouter() @@ -47,7 +48,7 @@ export const useSideBarPanels = () => { { id: 'com.github.owncloud.web.files.sidebar-panel.no-selection', type: 'sidebarPanel', - scopes: ['resource'], + extensionPointIds: [fileSideBarExtensionPointId], panel: { name: 'no-selection', icon: 'questionnaire-line', @@ -74,7 +75,7 @@ export const useSideBarPanels = () => { { id: 'com.github.owncloud.web.files.sidebar-panel.trash-no-selection', type: 'sidebarPanel', - scopes: ['resource'], + extensionPointIds: [fileSideBarExtensionPointId], panel: { name: 'no-selection', icon: 'questionnaire-line', @@ -89,7 +90,7 @@ export const useSideBarPanels = () => { { id: 'com.github.owncloud.web.files.sidebar-panel.details-single-selection', type: 'sidebarPanel', - scopes: ['resource'], + extensionPointIds: [fileSideBarExtensionPointId], panel: { name: 'details', icon: 'questionnaire-line', @@ -115,7 +116,7 @@ export const useSideBarPanels = () => { { id: 'com.github.owncloud.web.files.sidebar-panel.details-multi-selection', type: 'sidebarPanel', - scopes: ['resource'], + extensionPointIds: [fileSideBarExtensionPointId], panel: { name: 'details-multiple', icon: 'questionnaire-line', @@ -143,7 +144,7 @@ export const useSideBarPanels = () => { { id: 'com.github.owncloud.web.files.sidebar-panel.actions', type: 'sidebarPanel', - scopes: ['resource'], + extensionPointIds: [fileSideBarExtensionPointId], panel: { name: 'actions', icon: 'slideshow-3', @@ -166,7 +167,7 @@ export const useSideBarPanels = () => { { id: 'com.github.owncloud.web.files.sidebar-panel.sharing', type: 'sidebarPanel', - scopes: ['resource'], + extensionPointIds: [fileSideBarExtensionPointId], panel: { name: 'sharing', icon: 'user-add', @@ -210,7 +211,7 @@ export const useSideBarPanels = () => { { id: 'com.github.owncloud.web.files.sidebar-panel.versions', type: 'sidebarPanel', - scopes: ['resource'], + extensionPointIds: [fileSideBarExtensionPointId], panel: { name: 'versions', icon: 'git-branch', @@ -246,7 +247,7 @@ export const useSideBarPanels = () => { { id: 'com.github.owncloud.web.files.sidebar-panel.projects.no-selection', type: 'sidebarPanel', - scopes: ['resource'], + extensionPointIds: [fileSideBarExtensionPointId], panel: { name: 'no-selection', icon: 'questionnaire-line', @@ -265,7 +266,7 @@ export const useSideBarPanels = () => { { id: 'com.github.owncloud.web.files.sidebar-panel.projects.details-single-selection', type: 'sidebarPanel', - scopes: ['resource'], + extensionPointIds: [fileSideBarExtensionPointId], panel: { name: 'details-space', icon: 'questionnaire-line', @@ -280,7 +281,7 @@ export const useSideBarPanels = () => { { id: 'com.github.owncloud.web.files.sidebar-panel.projects.details-multi-selection', type: 'sidebarPanel', - scopes: ['resource'], + extensionPointIds: [fileSideBarExtensionPointId], panel: { name: 'details-space-multiple', icon: 'questionnaire-line', @@ -298,7 +299,7 @@ export const useSideBarPanels = () => { { id: 'com.github.owncloud.web.files.sidebar-panel.projects.actions', type: 'sidebarPanel', - scopes: ['resource'], + extensionPointIds: [fileSideBarExtensionPointId], panel: { name: 'space-actions', icon: 'slideshow-3', @@ -326,7 +327,7 @@ export const useSideBarPanels = () => { { id: 'com.github.owncloud.web.files.sidebar-panel.projects.sharing', type: 'sidebarPanel', - scopes: ['resource'], + extensionPointIds: [fileSideBarExtensionPointId], panel: { name: 'space-share', icon: 'group', diff --git a/packages/web-app-files/src/composables/extensions/useFolderViews.ts b/packages/web-app-files/src/composables/extensions/useFolderViews.ts index 80e6877f669..8e87cc40a63 100644 --- a/packages/web-app-files/src/composables/extensions/useFolderViews.ts +++ b/packages/web-app-files/src/composables/extensions/useFolderViews.ts @@ -1,6 +1,10 @@ -import { FolderViewExtension } from '@ownclouders/web-pkg' +import { FolderViewExtension, ResourceTable, ResourceTiles } from '@ownclouders/web-pkg' import { useGettext } from 'vue3-gettext' -import { ResourceTable, ResourceTiles } from '@ownclouders/web-pkg' +import { + folderViewsFavorites, + folderViewsFolder, + folderViewsProjectSpaces +} from '../../extensionPoints' export const useFolderViews = (): FolderViewExtension[] => { const { $gettext } = useGettext() @@ -9,7 +13,7 @@ export const useFolderViews = (): FolderViewExtension[] => { { id: 'com.github.owncloud.web.files.folder-view.resource-table', type: 'folderView', - scopes: ['resource', 'space', 'favorite'], + extensionPointIds: [folderViewsFolder, folderViewsProjectSpaces, folderViewsFavorites], folderView: { name: 'resource-table', label: $gettext('Switch to default table view'), @@ -23,7 +27,7 @@ export const useFolderViews = (): FolderViewExtension[] => { { id: 'com.github.owncloud.web.files.folder-view.resource-table-condensed', type: 'folderView', - scopes: ['resource'], + extensionPointIds: [folderViewsFolder], folderView: { name: 'resource-table-condensed', label: $gettext('Switch to condensed table view'), @@ -37,7 +41,7 @@ export const useFolderViews = (): FolderViewExtension[] => { { id: 'com.github.owncloud.web.files.folder-view.resource-tiles', type: 'folderView', - scopes: ['resource', 'space', 'favorite'], + extensionPointIds: [folderViewsFolder, folderViewsProjectSpaces, folderViewsFavorites], folderView: { name: 'resource-tiles', label: $gettext('Switch to tiles view'), diff --git a/packages/web-app-files/src/extensionPoints.ts b/packages/web-app-files/src/extensionPoints.ts index e69de29bb2d..2511ae2eacf 100644 --- a/packages/web-app-files/src/extensionPoints.ts +++ b/packages/web-app-files/src/extensionPoints.ts @@ -0,0 +1,55 @@ +import { ExtensionPoint } from '@ownclouders/web-pkg' +import { computed } from 'vue' + +export const uploadMenuExtensionPointId = 'app.files.upload-menu' +export const quickActionsExtensionPointId = 'app.files.quick-actions' +export const batchActionsExtensionPointId = 'app.files.batch-actions' +export const contextActionsExtensionPointId = 'app.files.context-actions' +export const fileSideBarExtensionPointId = 'app.files.sidebar' +export const folderViewsFolder = 'app.files.folder-views.folder' +export const folderViewsFavorites = 'app.files.folder-views.favorites' +export const folderViewsProjectSpaces = 'app.files.folder-views.project-spaces' + +export const extensionPoints = () => { + return computed(() => { + return [ + { + id: uploadMenuExtensionPointId, + extensionType: 'action', + multiple: true + }, + { + id: quickActionsExtensionPointId, + extensionType: 'action', + multiple: true + }, + { + id: batchActionsExtensionPointId, + extensionType: 'action', + multiple: true + }, + { + id: contextActionsExtensionPointId, + extensionType: 'action', + multiple: true + }, + { + id: fileSideBarExtensionPointId, + extensionType: 'sidebarPanel', + multiple: true + }, + { + id: folderViewsFolder, + extensionType: 'folderView' + }, + { + id: folderViewsFavorites, + extensionType: 'folderView' + }, + { + id: folderViewsProjectSpaces, + extensionType: 'folderView' + } + ] + }) +} diff --git a/packages/web-app-files/src/extensions.ts b/packages/web-app-files/src/extensions.ts index 171c6fac1b2..d46e05a16b1 100644 --- a/packages/web-app-files/src/extensions.ts +++ b/packages/web-app-files/src/extensions.ts @@ -1,15 +1,16 @@ import { Extension, - useRouter, - useSearch, - useFileActionsShowShares, + useCapabilityStore, useFileActionsCopyQuickLink, - useCapabilityStore + useFileActionsShowShares, + useRouter, + useSearch } from '@ownclouders/web-pkg' import { computed, unref } from 'vue' import { SDKSearch } from './search' import { useSideBarPanels } from './composables/extensions/useFileSideBars' import { useFolderViews } from './composables/extensions/useFolderViews' +import { quickActionsExtensionPointId } from './extensionPoints' export const extensions = () => { const capabilityStore = useCapabilityStore() @@ -34,13 +35,13 @@ export const extensions = () => { }, { id: 'com.github.owncloud.web.files.quick-action.collaborator', - scopes: ['resource', 'resource.quick-action'], + extensionPointIds: [quickActionsExtensionPointId], type: 'action', action: unref(showSharesActions)[0] }, { id: 'com.github.owncloud.web.files.quick-action.quicklink', - scopes: ['resource', 'resource.quick-action'], + extensionPointIds: [quickActionsExtensionPointId], type: 'action', action: unref(quickLinkActions)[0] } diff --git a/packages/web-app-files/src/index.ts b/packages/web-app-files/src/index.ts index bc5c199e2c6..29fb4eda5be 100644 --- a/packages/web-app-files/src/index.ts +++ b/packages/web-app-files/src/index.ts @@ -23,6 +23,7 @@ import { AppNavigationItem } from '@ownclouders/web-pkg' import SearchResults from '../../web-app-search/src/views/List.vue' import { isPersonalSpaceResource, isShareSpaceResource } from '@ownclouders/web-client' import { ComponentCustomProperties } from 'vue' +import { extensionPoints } from './extensionPoints' // just a dummy function to trick gettext tools function $gettext(msg: string) { @@ -35,7 +36,8 @@ const appInfo: ApplicationInformation = { icon: 'resource-type-folder', color: 'var(--oc-color-swatch-primary-muted)', isFileEditor: false, - extensions: [] + extensions: [], + extensionPoints: [] } export const navItems = (context: ComponentCustomProperties): AppNavigationItem[] => { @@ -158,7 +160,8 @@ export default defineWebApplication({ }), navItems, translations, - extensions: extensions() + extensions: extensions(), + extensionPoints: extensionPoints() } } }) diff --git a/packages/web-app-files/src/views/Favorites.vue b/packages/web-app-files/src/views/Favorites.vue index 2fe63973bd8..f13c1d48a7e 100644 --- a/packages/web-app-files/src/views/Favorites.vue +++ b/packages/web-app-files/src/views/Favorites.vue @@ -68,7 +68,8 @@ import { defineComponent, onBeforeUnmount, onMounted, - ref + ref, + unref } from 'vue' import { debounce } from 'lodash-es' @@ -97,7 +98,7 @@ import FilesViewWrapper from '../components/FilesViewWrapper.vue' import { useResourcesViewDefaults } from '../composables' import { useFileActions } from '@ownclouders/web-pkg' import { storeToRefs } from 'pinia' -import { unref } from 'vue' +import { folderViewsFavorites } from '../extensionPoints' const visibilityObserver = new VisibilityObserver() @@ -130,7 +131,9 @@ export default defineComponent({ const viewModes = computed(() => { return [ ...extensionRegistry - .requestExtensions('folderView', { scopes: ['favorite'] }) + .requestExtensions('folderView', { + extensionPointIds: [folderViewsFavorites] + }) .map((e) => e.folderView) ] }) diff --git a/packages/web-app-files/src/views/spaces/GenericSpace.vue b/packages/web-app-files/src/views/spaces/GenericSpace.vue index 19e4b639174..72956d56e56 100644 --- a/packages/web-app-files/src/views/spaces/GenericSpace.vue +++ b/packages/web-app-files/src/views/spaces/GenericSpace.vue @@ -203,6 +203,7 @@ import { } from 'web-app-files/src/composables/keyboardActions' import { storeToRefs } from 'pinia' import { ComponentPublicInstance } from 'vue' +import { folderViewsFolder } from '../../extensionPoints' const visibilityObserver = new VisibilityObserver() @@ -273,7 +274,9 @@ export default defineComponent({ const viewModes = computed(() => { return [ ...extensionRegistry - .requestExtensions('folderView', { scopes: ['resource'] }) + .requestExtensions('folderView', { + extensionPointIds: [folderViewsFolder] + }) .map((e) => e.folderView) ] }) diff --git a/packages/web-app-files/src/views/spaces/Projects.vue b/packages/web-app-files/src/views/spaces/Projects.vue index faab842fb87..814faf70c17 100644 --- a/packages/web-app-files/src/views/spaces/Projects.vue +++ b/packages/web-app-files/src/views/spaces/Projects.vue @@ -205,6 +205,7 @@ import { } from 'web-app-files/src/composables/keyboardActions' import { orderBy } from 'lodash-es' import { useResourcesViewDefaults } from '../../composables' +import { folderViewsProjectSpaces } from '../../extensionPoints' export default defineComponent({ components: { @@ -325,7 +326,9 @@ export default defineComponent({ const viewModes = computed(() => { return [ ...extensionRegistry - .requestExtensions('folderView', { scopes: ['space'] }) + .requestExtensions('folderView', { + extensionPointIds: [folderViewsProjectSpaces] + }) .map((e) => e.folderView) ] }) diff --git a/packages/web-app-files/tests/unit/components/FilesList/QuickActions.spec.ts b/packages/web-app-files/tests/unit/components/FilesList/QuickActions.spec.ts index b9e0cb1b393..212d2bc19df 100644 --- a/packages/web-app-files/tests/unit/components/FilesList/QuickActions.spec.ts +++ b/packages/web-app-files/tests/unit/components/FilesList/QuickActions.spec.ts @@ -5,6 +5,7 @@ import { useExtensionRegistry } from '@ownclouders/web-pkg' import { mock } from 'vitest-mock-extended' import { ref } from 'vue' import { Resource } from '@ownclouders/web-client' +import { quickActionsExtensionPointId } from '../../../../src/extensionPoints' vi.mock('@ownclouders/web-pkg', async (importOriginal) => ({ ...(await importOriginal()), @@ -20,7 +21,7 @@ const collaboratorAction = { label: () => 'Add people' } -const quicklinkAction = { +const quickLinkAction = { isVisible: vi.fn(() => false), handler: vi.fn(), icon: 'link-add', @@ -89,8 +90,14 @@ function getWrapper({ embedModeEnabled = false } = {}) { const { requestExtensions } = useExtensionRegistry() vi.mocked(requestExtensions).mockReturnValue([ - mock({ scopes: ['resource.quick-action'], action: collaboratorAction }), - mock({ scopes: ['resource.quick-action'], action: quicklinkAction }) + mock({ + extensionPointIds: [quickActionsExtensionPointId], + action: collaboratorAction + }), + mock({ + extensionPointIds: [quickActionsExtensionPointId], + action: quickLinkAction + }) ]) return { diff --git a/packages/web-app-files/tests/unit/views/Favorites.spec.ts b/packages/web-app-files/tests/unit/views/Favorites.spec.ts index 121004a2652..f664b7a89e1 100644 --- a/packages/web-app-files/tests/unit/views/Favorites.spec.ts +++ b/packages/web-app-files/tests/unit/views/Favorites.spec.ts @@ -7,6 +7,11 @@ import { Resource } from '@ownclouders/web-client' import { defaultPlugins, defaultStubs, mount, defaultComponentMocks } from 'web-test-helpers' import { RouteLocation } from 'vue-router' import { Extension, useExtensionRegistry } from '@ownclouders/web-pkg' +import { + folderViewsFavorites, + folderViewsFolder, + folderViewsProjectSpaces +} from '../../../src/extensionPoints' vi.mock('web-app-files/src/composables') vi.mock('@ownclouders/web-pkg', async (importOriginal) => ({ @@ -59,7 +64,7 @@ function getMountedWrapper({ { id: 'com.github.owncloud.web.files.folder-view.resource-table', type: 'folderView', - scopes: ['resource', 'space', 'favorite'], + extensionPointIds: [folderViewsFolder, folderViewsProjectSpaces, folderViewsFavorites], folderView: { name: 'resource-table', label: 'Switch to default view', diff --git a/packages/web-app-files/tests/unit/views/spaces/GenericSpace.spec.ts b/packages/web-app-files/tests/unit/views/spaces/GenericSpace.spec.ts index 15a8fb093b3..6b5b2ec2849 100644 --- a/packages/web-app-files/tests/unit/views/spaces/GenericSpace.spec.ts +++ b/packages/web-app-files/tests/unit/views/spaces/GenericSpace.spec.ts @@ -22,6 +22,11 @@ import { import { useBreadcrumbsFromPathMock } from '../../../mocks/useBreadcrumbsFromPathMock' import { h } from 'vue' import { BreadcrumbItem } from 'design-system/src/components/OcBreadcrumb/types' +import { + folderViewsFavorites, + folderViewsFolder, + folderViewsProjectSpaces +} from '../../../../src/extensionPoints' const mockCreateFolder = vi.fn() const mockUseEmbedMode = vi.fn().mockReturnValue({ isEnabled: computed(() => false) }) @@ -325,7 +330,7 @@ function getMountedWrapper({ { id: 'com.github.owncloud.web.files.folder-view.resource-table', type: 'folderView', - scopes: ['resource', 'space', 'favorite'], + extensionPointIds: [folderViewsFolder, folderViewsProjectSpaces, folderViewsFavorites], folderView: { name: 'resource-table', label: 'Switch to default view', diff --git a/packages/web-app-files/tests/unit/views/spaces/Projects.spec.ts b/packages/web-app-files/tests/unit/views/spaces/Projects.spec.ts index fee54a49a74..683be747988 100644 --- a/packages/web-app-files/tests/unit/views/spaces/Projects.spec.ts +++ b/packages/web-app-files/tests/unit/views/spaces/Projects.spec.ts @@ -16,6 +16,11 @@ import { RouteLocation } from 'web-test-helpers' import { AbilityRule, SpaceResource } from '@ownclouders/web-client' +import { + folderViewsFavorites, + folderViewsFolder, + folderViewsProjectSpaces +} from '../../../../src/extensionPoints' vi.mock('@ownclouders/web-pkg', async (importOriginal) => ({ ...(await importOriginal()), @@ -116,7 +121,7 @@ function getMountedWrapper({ { id: 'com.github.owncloud.web.files.folder-view.resource-table', type: 'folderView', - scopes: ['resource', 'space', 'favorite'], + extensionPointIds: [folderViewsFolder, folderViewsProjectSpaces, folderViewsFavorites], folderView: { name: 'resource-table', label: 'Switch to default view', diff --git a/packages/web-app-importer/src/extensions.ts b/packages/web-app-importer/src/extensions.ts index 0fe9b428e0c..3c603e62110 100644 --- a/packages/web-app-importer/src/extensions.ts +++ b/packages/web-app-importer/src/extensions.ts @@ -123,7 +123,7 @@ export const extensions = ({ applicationConfig }: ApplicationSetupOptions) => { { id: 'com.github.owncloud.web.import-file', type: 'action', - scopes: ['resource', 'upload-menu'], + extensionPointIds: ['app.files.upload-menu'], action: { name: 'import-files', icon: 'cloud', diff --git a/packages/web-app-ocm/src/extensions.ts b/packages/web-app-ocm/src/extensions.ts index c5870b86186..3c96bd46d1c 100644 --- a/packages/web-app-ocm/src/extensions.ts +++ b/packages/web-app-ocm/src/extensions.ts @@ -52,13 +52,17 @@ export const extensions = () => { { id: 'com.github.owncloud.web.open-file-remote', type: 'action', - scopes: ['resource', 'resource.context-menu'], + extensionPointIds: ['app.files.context-actions'], action: { name: 'open-file-remote', + category: 'actions', icon: 'remote-control', handler, label: () => $gettext('Open remotely'), isVisible: ({ resources }: FileActionOptions) => { + if (!resources?.length) { + return false + } return ( configStore.options.ocm.openRemotely && resources[0]?.storageId?.startsWith(OCM_PROVIDER_ID) diff --git a/packages/web-pkg/src/components/AppBar/AppBar.vue b/packages/web-pkg/src/components/AppBar/AppBar.vue index 9f77159b326..40acbab981e 100644 --- a/packages/web-pkg/src/components/AppBar/AppBar.vue +++ b/packages/web-pkg/src/components/AppBar/AppBar.vue @@ -91,7 +91,9 @@ import { useRouteMeta, useSpacesStore, useRouter, - FolderViewModeConstants + FolderViewModeConstants, + useExtensionRegistry, + ActionExtension } from '../../composables' import { BreadcrumbItem } from 'design-system/src/components/OcBreadcrumb/types' import { useActiveLocation } from '../../composables' @@ -151,6 +153,7 @@ export default defineComponent({ const { $gettext } = useGettext() const { can } = useAbility() const router = useRouter() + const { requestExtensions } = useExtensionRegistry() const resourcesStore = useResourcesStore() const { selectedResources } = storeToRefs(resourcesStore) @@ -207,6 +210,13 @@ export default defineComponent({ ] as FileAction[] } + const actionExtensions = requestExtensions('action', { + extensionPointIds: ['app.files.batch-actions'] + }) + if (actionExtensions.length) { + actions = [...actions, ...actionExtensions.map((e) => e.action)] + } + return actions.filter((item) => item.isVisible({ space: props.space, resources: resourcesStore.selectedResources }) ) diff --git a/packages/web-pkg/src/components/ContextActions/ContextActionMenu.vue b/packages/web-pkg/src/components/ContextActions/ContextActionMenu.vue index ef765a5098d..2d99704af31 100644 --- a/packages/web-pkg/src/components/ContextActions/ContextActionMenu.vue +++ b/packages/web-pkg/src/components/ContextActions/ContextActionMenu.vue @@ -23,7 +23,7 @@