3939 :force-name =" true"
4040 :force-menu =" enabledInlineActions.length === 0 /* forceMenu only if no inline actions */"
4141 :inline =" enabledInlineActions.length"
42- :open.sync =" openedMenu" >
43- <NcActionButton v-for =" action in enabledMenuActions"
44- :key =" action.id"
45- :class =" 'files-list__row-action-' + action.id"
46- :close-after-click =" true"
47- :data-cy-files-list-row-action =" action.id"
48- :title =" action.title?.([source], currentView)"
49- @click =" onActionClick(action)" >
50- <template #icon >
51- <NcLoadingIcon v-if =" loading === action.id" :size =" 18" />
52- <NcIconSvgWrapper v-else :svg =" action.iconSvgInline([source], currentView)" />
53- </template >
54- {{ actionDisplayName(action) }}
55- </NcActionButton >
42+ :open.sync =" openedMenu"
43+ @close =" openedSubmenu = null" >
44+ <!-- Default actions list-->
45+ <template >
46+ <NcActionButton v-for =" action in enabledMenuActions"
47+ :key =" action.id"
48+ :class =" {
49+ [`files-list__row-action-${action.id}`]: true,
50+ [`files-list__row-action--menu`]: isMenu(action.id)
51+ }"
52+ :close-after-click =" !isMenu(action.id)"
53+ :data-cy-files-list-row-action =" action.id"
54+ :title =" action.title?.([source], currentView)"
55+ @click =" onActionClick(action)" >
56+ <template #icon >
57+ <ChevronRightIcon v-if =" isMenu(action.id)" />
58+ <NcLoadingIcon v-else-if =" loading === action.id" :size =" 18" />
59+ <NcIconSvgWrapper v-else :svg =" action.iconSvgInline([source], currentView)" />
60+ </template >
61+ {{ actionDisplayName(action) }}
62+ </NcActionButton >
63+ </template >
64+
65+ <!-- Submenu actions list-->
66+ <template v-if =" openedSubmenu && enabledSubmenuActions [openedSubmenu ]" >
67+ <!-- Back to top-level button -->
68+ <NcActionButton class =" files-list__row-action-back" @click =" openedSubmenu = ''" >
69+ <template #icon >
70+ <ChevronLeftIcon />
71+ </template >
72+ {{ t('files', 'Back') }}
73+ </NcActionButton >
74+ <NcActionSeparator />
75+
76+ <!-- Submenu actions -->
77+ <NcActionButton v-for =" action in enabledSubmenuActions[openedSubmenu]"
78+ :key =" action.id"
79+ :class =" `files-list__row-action-${action.id}`"
80+ class =" files-list__row-action--submenu"
81+ :close-after-click =" true"
82+ :data-cy-files-list-row-action =" action.id"
83+ :title =" action.title?.([source], currentView)"
84+ @click =" onActionClick(action)" >
85+ <template #icon >
86+ <NcLoadingIcon v-if =" loading === action.id" :size =" 18" />
87+ <NcIconSvgWrapper v-else :svg =" action.iconSvgInline([source], currentView)" />
88+ </template >
89+ {{ actionDisplayName(action) }}
90+ </NcActionButton >
91+ </template >
5692 </NcActions >
5793 </td >
5894</template >
@@ -63,8 +99,11 @@ import { showError, showSuccess } from '@nextcloud/dialogs'
6399import { translate as t } from ' @nextcloud/l10n' ;
64100import Vue , { PropType } from ' vue'
65101
102+ import ChevronLeftIcon from ' vue-material-design-icons/ChevronLeft.vue'
103+ import ChevronRightIcon from ' vue-material-design-icons/ChevronRight.vue'
66104import NcActionButton from ' @nextcloud/vue/dist/Components/NcActionButton.js'
67105import NcActions from ' @nextcloud/vue/dist/Components/NcActions.js'
106+ import NcActionSeparator from ' @nextcloud/vue/dist/Components/NcActionSeparator.js'
68107import NcIconSvgWrapper from ' @nextcloud/vue/dist/Components/NcIconSvgWrapper.js'
69108import NcLoadingIcon from ' @nextcloud/vue/dist/Components/NcLoadingIcon.js'
70109
@@ -78,11 +117,14 @@ export default Vue.extend({
78117 name: ' FileEntryActions' ,
79118
80119 components: {
120+ ChevronLeftIcon ,
121+ ChevronRightIcon ,
122+ CustomElementRender ,
81123 NcActionButton ,
82124 NcActions ,
125+ NcActionSeparator ,
83126 NcIconSvgWrapper ,
84127 NcLoadingIcon ,
85- CustomElementRender ,
86128 },
87129
88130 props: {
@@ -108,8 +150,9 @@ export default Vue.extend({
108150 },
109151 },
110152
111- setup () {
153+ data () {
112154 return {
155+ openedSubmenu: ' ' ,
113156 }
114157 },
115158
@@ -159,7 +202,13 @@ export default Vue.extend({
159202
160203 // Actions shown in the menu
161204 enabledMenuActions() {
162- return [
205+ // If we're in a submenu, only render the inline
206+ // actions before the filtered submenu
207+ if (this .openedSubmenu ) {
208+ return this .enabledInlineActions
209+ }
210+
211+ const actions = [
163212 // Showing inline first for the NcActions inline prop
164213 ... this .enabledInlineActions ,
165214 // Then the rest
@@ -168,6 +217,24 @@ export default Vue.extend({
168217 // Then we filter duplicates to prevent inline actions to be shown twice
169218 return index === self .findIndex (action => action .id === value .id )
170219 })
220+
221+ // Generate list of all top-level actions ids
222+ const topActionsIds = actions .filter (action => ! action .parent ).map (action => action .id ) as string []
223+
224+ // Filter actions that are not top-level AND have a valid parent
225+ return actions .filter (action => ! (action .parent && topActionsIds .includes (action .parent )))
226+ },
227+
228+ enabledSubmenuActions() {
229+ return this .enabledActions
230+ .filter (action => action .parent )
231+ .reduce ((arr , action ) => {
232+ if (! arr [action .parent ]) {
233+ arr [action .parent ] = []
234+ }
235+ arr [action .parent ].push (action )
236+ return arr
237+ }, {} as Record <string , FileAction >)
171238 },
172239
173240 openedMenu: {
@@ -201,6 +268,12 @@ export default Vue.extend({
201268 },
202269
203270 async onActionClick(action ) {
271+ // If the action is a submenu, we open it
272+ if (this .enabledSubmenuActions [action .id ]) {
273+ this .openedSubmenu = action .id
274+ return
275+ }
276+
204277 const displayName = action .displayName ([this .source ], this .currentView )
205278 try {
206279 // Set the loading marker
@@ -237,6 +310,10 @@ export default Vue.extend({
237310 }
238311 },
239312
313+ isMenu(id : string ) {
314+ return this .enabledSubmenuActions [id ]?.length > 0
315+ },
316+
240317 t ,
241318 },
242319})
0 commit comments