Skip to content

Commit cd3442e

Browse files
authored
Merge pull request #7098 from LibreSign/backport/7010/stable32
[stable32] feat: files list header restructure
2 parents f6c46f1 + 5777457 commit cd3442e

4 files changed

Lines changed: 122 additions & 15 deletions

File tree

src/components/Request/RequestPicker.vue

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
:inline="inline ? 3 : 0"
99
:force-name="inline"
1010
:class="{column: inline}"
11+
:variant="variant"
1112
v-model:open="openedMenu">
1213
<template #icon>
1314
<NcIconSvgWrapper :path="mdiPlus" :size="20" />
@@ -147,6 +148,10 @@ export default {
147148
type: Boolean,
148149
default: false,
149150
},
151+
variant: {
152+
type: String,
153+
default: 'tertiary',
154+
},
150155
},
151156
setup() {
152157
const actionsMenuStore = useActionsMenuStore()

src/tests/components/Request/RequestPicker.spec.ts

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,39 @@ describe('RequestPicker component rules', () => {
173173
})
174174
})
175175

176+
describe('variant prop', () => {
177+
const mountWithVariantStub = (props = {}) => mount(RequestPicker, {
178+
props,
179+
global: {
180+
stubs: {
181+
NcActions: {
182+
name: 'NcActions',
183+
props: ['variant'],
184+
template: '<div class="nc-actions-stub" :data-variant="variant"><slot /></div>',
185+
},
186+
NcActionButton: true,
187+
NcButton: true,
188+
NcDialog: true,
189+
NcTextField: true,
190+
NcLoadingIcon: true,
191+
NcNoteCard: true,
192+
UploadProgress: true,
193+
},
194+
mocks: { t: tSimple },
195+
},
196+
})
197+
198+
it('defaults variant to tertiary', () => {
199+
const w = mountWithVariantStub()
200+
expect(w.find('.nc-actions-stub').attributes('data-variant')).toBe('tertiary')
201+
})
202+
203+
it('passes custom variant to NcActions', () => {
204+
const w = mountWithVariantStub({ variant: 'primary' })
205+
expect(w.find('.nc-actions-stub').attributes('data-variant')).toBe('primary')
206+
})
207+
})
208+
176209
describe('envelope support', () => {
177210
it('enables envelope mode when capabilities indicate is-available true', () => {
178211
getCapabilitiesMock.mockReturnValue({

src/tests/views/FilesList/FilesList.spec.ts

Lines changed: 55 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import { useUserConfigStore } from '../../../store/userconfig.js'
1313

1414
vi.mock('@nextcloud/l10n', () => ({
1515
t: vi.fn((_app: string, text: string) => text),
16+
isRTL: vi.fn(() => false),
1617
}))
1718

1819
vi.mock('@nextcloud/logger', () => ({
@@ -74,7 +75,17 @@ vi.mock('@nextcloud/vue/components/NcAppContent', () => ({
7475
default: { name: 'NcAppContent', template: '<div><slot /></div>' },
7576
}))
7677
vi.mock('@nextcloud/vue/components/NcBreadcrumb', () => ({
77-
default: { name: 'NcBreadcrumb', template: '<div><slot name="icon" /></div>' },
78+
default: {
79+
name: 'NcBreadcrumb',
80+
template: '<div><slot name="icon" /><slot name="menu-icon" /><slot /></div>',
81+
},
82+
}))
83+
vi.mock('@nextcloud/vue/components/NcActionButton', () => ({
84+
default: {
85+
name: 'NcActionButton',
86+
emits: ['click'],
87+
template: '<button class="nc-action-button-stub" @click="$emit(\'click\')"><slot /></button>',
88+
},
7889
}))
7990
vi.mock('@nextcloud/vue/components/NcBreadcrumbs', () => ({
8091
default: { name: 'NcBreadcrumbs', template: '<div><slot /><slot name="actions" /></div>' },
@@ -104,13 +115,6 @@ vi.mock('../../../views/FilesList/FilesListVirtual.vue', () => ({
104115
},
105116
}))
106117

107-
vi.mock('../../../views/FilesList/FileListFilters.vue', () => ({
108-
default: {
109-
name: 'FileListFilters',
110-
template: '<div class="file-list-filters-stub" />',
111-
},
112-
}))
113-
114118
vi.mock('../../../components/Request/RequestPicker.vue', () => ({
115119
default: {
116120
name: 'RequestPicker',
@@ -148,6 +152,45 @@ describe('FilesList.vue rendering rules', () => {
148152
expect(wrapper.vm.mdiFolder).toBeTruthy()
149153
expect(wrapper.vm.mdiViewGrid).toBeTruthy()
150154
expect(wrapper.vm.mdiViewList).toBeTruthy()
155+
expect(wrapper.vm.mdiChevronDown).toBeTruthy()
156+
expect(wrapper.vm.mdiChevronUp).toBeTruthy()
157+
expect(wrapper.vm.mdiReload).toBeTruthy()
158+
})
159+
160+
it('initialises isMenuOpen as false', async () => {
161+
const filesStore = useFilesStore()
162+
vi.spyOn(filesStore, 'getAllFiles').mockResolvedValue({})
163+
164+
const wrapper = mountComponent()
165+
await flushPromises()
166+
167+
expect(wrapper.vm.isMenuOpen).toBe(false)
168+
})
169+
170+
it('renders RequestPicker before the breadcrumbs in the header', async () => {
171+
const filesStore = useFilesStore()
172+
vi.spyOn(filesStore, 'getAllFiles').mockResolvedValue({})
173+
174+
const wrapper = mountComponent()
175+
await flushPromises()
176+
177+
const header = wrapper.find('.files-list__header')
178+
const firstChild = header.element.children[0]
179+
expect(firstChild.classList.contains('request-picker-stub')).toBe(true)
180+
})
181+
182+
it('calls filesStore.updateAllFiles once more when reload button is clicked', async () => {
183+
const filesStore = useFilesStore()
184+
vi.spyOn(filesStore, 'getAllFiles').mockResolvedValue({})
185+
const updateSpy = vi.spyOn(filesStore, 'updateAllFiles').mockResolvedValue({})
186+
187+
const wrapper = mountComponent()
188+
await flushPromises()
189+
190+
const callsBefore = updateSpy.mock.calls.length
191+
await wrapper.find('.nc-action-button-stub').trigger('click')
192+
193+
expect(updateSpy.mock.calls.length).toBe(callsBefore + 1)
151194
})
152195

153196
it('shows empty-state request action when user can request sign', async () => {
@@ -193,7 +236,8 @@ describe('FilesList.vue rendering rules', () => {
193236
const wrapper = mountComponent()
194237
await flushPromises()
195238

196-
const iconWithPath = wrapper.findAll('.nc-icon').find((node) => !!node.attributes('data-path'))
239+
const gridButton = wrapper.find('.files-list__header-grid-button')
240+
const iconWithPath = gridButton.findAll('.nc-icon').find((node) => !!node.attributes('data-path'))
197241
expect(iconWithPath?.attributes('data-path')).toBe(wrapper.vm.mdiViewGrid)
198242
})
199243

@@ -206,7 +250,8 @@ describe('FilesList.vue rendering rules', () => {
206250
const wrapper = mountComponent()
207251
await flushPromises()
208252

209-
const iconWithPath = wrapper.findAll('.nc-icon').find((node) => !!node.attributes('data-path'))
253+
const gridButton = wrapper.find('.files-list__header-grid-button')
254+
const iconWithPath = gridButton.findAll('.nc-icon').find((node) => !!node.attributes('data-path'))
210255
expect(iconWithPath?.attributes('data-path')).toBe(wrapper.vm.mdiViewList)
211256
})
212257
})

src/views/FilesList/FilesList.vue

Lines changed: 29 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,25 +5,40 @@
55
<template>
66
<NcAppContent :page-heading="t('libresign', 'Files')">
77
<div class="files-list__header">
8+
<!-- Request picker -->
9+
<RequestPicker variant="primary" />
10+
11+
<!-- Current folder breadcrumbs -->
812
<NcBreadcrumbs class="files-list__breadcrumbs">
913
<NcBreadcrumb :name="t('libresign', 'Files')"
1014
:title="t('libresign', 'Files')"
1115
:force-icon-text="true"
1216
:to="{ name: 'fileslist' }"
1317
:aria-description="t('libresign', 'Files')"
1418
:disable-drop="true"
15-
@click="refresh()">
19+
force-menu
20+
v-model:open="isMenuOpen">
1621
<template #icon>
1722
<NcIconSvgWrapper :size="20"
1823
:svg="viewIcon" />
1924
</template>
25+
<template #menu-icon>
26+
<NcIconSvgWrapper :path="isMenuOpen ? mdiChevronUp : mdiChevronDown" />
27+
</template>
28+
<!-- Reload button -->
29+
<NcActionButton close-after-click @click="refresh()">
30+
<template #icon>
31+
<NcIconSvgWrapper :path="mdiReload" />
32+
</template>
33+
<!-- TRANSLATORS Button inside the breadcrumb dropdown menu that reloads the file list -->
34+
{{ t('libresign', 'Reload content') }}
35+
</NcActionButton>
2036
</NcBreadcrumb>
21-
<template #actions>
22-
<RequestPicker />
23-
</template>
2437
</NcBreadcrumbs>
2538

26-
<NcLoadingIcon v-if="isRefreshing" class="files-list__refresh-icon" />
39+
<NcLoadingIcon v-if="isRefreshing"
40+
class="files-list__refresh-icon"
41+
:name="t('libresign', 'File list is reloading')" />
2742

2843
<!-- Filters that can be applied to the file list -->
2944
<FileListFilters />
@@ -78,11 +93,15 @@ import { t } from '@nextcloud/l10n'
7893
7994
import HomeSvg from '@mdi/svg/svg/home.svg?raw'
8095
import {
96+
mdiChevronDown,
97+
mdiChevronUp,
8198
mdiFolder,
99+
mdiReload,
82100
mdiViewGrid,
83101
mdiViewList,
84102
} from '@mdi/js'
85103
104+
import NcActionButton from '@nextcloud/vue/components/NcActionButton'
86105
import NcAppContent from '@nextcloud/vue/components/NcAppContent'
87106
import NcBreadcrumb from '@nextcloud/vue/components/NcBreadcrumb'
88107
import NcBreadcrumbs from '@nextcloud/vue/components/NcBreadcrumbs'
@@ -103,6 +122,7 @@ import { useSidebarStore } from '../../store/sidebar.js'
103122
export default {
104123
name: 'FilesList',
105124
components: {
125+
NcActionButton,
106126
NcAppContent,
107127
NcButton,
108128
NcBreadcrumb,
@@ -124,13 +144,17 @@ export default {
124144
filtersStore,
125145
userConfigStore,
126146
sidebarStore,
147+
mdiChevronDown,
148+
mdiChevronUp,
127149
mdiFolder,
150+
mdiReload,
128151
mdiViewGrid,
129152
mdiViewList,
130153
}
131154
},
132155
data() {
133156
return {
157+
isMenuOpen: false,
134158
loading: true,
135159
dirContentsFiltered: [],
136160
}

0 commit comments

Comments
 (0)