Skip to content

Commit aa5ccb4

Browse files
authored
Merge pull request #1715 from nextcloud-libraries/feat/vue3
refactor!: Migrate to vue3
2 parents b975b68 + 5b37db7 commit aa5ccb4

29 files changed

Lines changed: 981 additions & 1699 deletions

.eslintrc.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
{
2-
"extends": ["@nextcloud/eslint-config/typescript"],
2+
"extends": ["@nextcloud/eslint-config/vue3"],
33
"overrides": [
44
// https://github.com/mysticatea/eslint-plugin-node/issues/248#issuecomment-1052550467
55
{

README.md

Lines changed: 10 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -15,15 +15,22 @@ npm i -S @nextcloud/dialogs
1515
```
1616

1717
### Version compatibility
18-
Since version 4.2 this package provides a Vue.js based file picker, so this package depends on `@nextcloud/vue`. So to not introduce style collisions stick with the supported versions:
18+
Since version 4.2 this package provides a Vue.js based file picker, so this package depends on `@nextcloud/vue`.
19+
So to not introduce style collisions stick with the supported versions:
1920

2021
| `@nextcloud/dialogs` | maintained | `@nextcloud/vue` dependency | Nextcloud server version |
2122
|----------------------|------------|-----------------------------|----------------------------|
23+
| 7.x || 9.x (Vue 3)¹ | Nextcloud 30 and newer |
2224
| 6.x || 8.x | Nextcloud 29 and newer |
2325
| 5.x || 8.x | Nextcloud 28, 29, 30 |
2426
| 4.2+ || 7.12 | Nextcloud 25, 26, 27, 27.1 |
2527
| 4.1 || *any* | *any* |
2628

29+
¹: In version 7.x the `@nextcloud/vue` dependency is moved to `dependencies` so you can also use this library
30+
with an old version of `@nextcloud/vue` in your app dependencies if your app still uses Vue 2.
31+
Note that this might increase the bundled app size.
32+
If your app also already uses `@nextcloud/vue` version 9.x and Vue 3 then the bundle size will not increase.
33+
2734
## Usage
2835

2936
### General
@@ -62,11 +69,8 @@ showError('This is an error shown without a timeout', { timeout: -1 })
6269
A full list of available options can be found in the [documentation](https://nextcloud-libraries.github.io/nextcloud-dialogs/).
6370

6471
### FilePicker
65-
There are two ways to spawn a FilePicker provided by the library:
66-
67-
#### Use the FilePickerBuilder
68-
This way you do not need to use Vue, but can programatically spawn a FilePicker.
69-
The FilePickerBuilder is included in the main entry point of this library, so you can use it like this:
72+
To spawn the FilePicker provided by the library you have to use the *FilePickerBuilder*.
73+
The *FilePickerBuilder* is included in the main entry point of this library, so you can use it like this:
7074

7175
```js
7276
import { getFilePickerBuilder } from '@nextcloud/dialogs'
@@ -82,41 +86,6 @@ const filepicker = getFilePickerBuilder('Pick plain text files')
8286
const paths = await filepicker.pick()
8387
```
8488

85-
#### Use the Vue component directly
86-
87-
> [!WARNING]
88-
> The Vue component is deprecated and will no longer be exported in a future version.
89-
90-
We also provide the `@nextcloud/dialogs/filepicker.js` entry point to allow using the Vue component directly:
91-
92-
```vue
93-
<template>
94-
<FilePicker name="Pick some files" :buttons="buttons" />
95-
</template>
96-
<script setup lang="ts">
97-
import {
98-
FilePickerVue as FilePicker,
99-
type IFilePickerButton,
100-
} from '@nextcloud/dialogs/filepicker.js'
101-
import type { Node } from '@nextcloud/files'
102-
import IconShare from 'vue-material-design-icons/Share.vue'
103-
104-
const buttons: IFilePickerButton[] = [
105-
{
106-
label: 'Pick',
107-
callback: (nodes: Node[]) => console.log('Picked', nodes),
108-
type: 'primary'
109-
},
110-
{
111-
label: 'Share',
112-
callback: (nodes: Node[]) => console.log('Share picked files', nodes),
113-
type: 'secondary',
114-
icon: IconShare,
115-
}
116-
]
117-
</script>
118-
```
119-
12089
## Development
12190
### Testing
12291
For testing all components provide `data-testid` attributes as selectors, so the tests are independent from code or styling changes.

lib/components/FilePicker/FileList.spec.ts

Lines changed: 17 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ describe('FilePicker FileList', () => {
6262
const consoleWarning = vi.spyOn(console, 'warn')
6363

6464
const wrapper = shallowMount(FileList, {
65-
propsData: {
65+
props: {
6666
currentView: 'files',
6767
multiselect: false,
6868
allowPickDirectory: false,
@@ -80,7 +80,7 @@ describe('FilePicker FileList', () => {
8080

8181
it('header checkbox is not shown if multiselect is `false`', () => {
8282
const wrapper = shallowMount(FileList, {
83-
propsData: {
83+
props: {
8484
currentView: 'files',
8585
multiselect: false,
8686
allowPickDirectory: false,
@@ -95,7 +95,7 @@ describe('FilePicker FileList', () => {
9595

9696
it('header checkbox is shown if multiselect is `true`', async () => {
9797
const wrapper = shallowMount(FileList, {
98-
propsData: {
98+
props: {
9999
currentView: 'files',
100100
multiselect: true,
101101
allowPickDirectory: false,
@@ -111,15 +111,15 @@ describe('FilePicker FileList', () => {
111111
const selectAll = wrapper.find('[data-testid="select-all-checkbox"]')
112112
expect(selectAll.exists()).toBe(true)
113113
// there is an aria label
114-
expect(selectAll.props('ariaLabel')).toBeTruthy()
114+
expect(selectAll.attributes('arialabel')).toBeTruthy()
115115
// no checked
116-
expect(selectAll.props('modelValue')).toBe(false)
116+
expect(selectAll.attributes('modelvalue')).toBe('false')
117117
})
118118

119119
it('header checkbox is checked when all nodes are selected', async () => {
120120
const nodes = [...exampleNodes]
121121
const wrapper = shallowMount(FileList, {
122-
propsData: {
122+
props: {
123123
currentView: 'files',
124124
multiselect: true,
125125
allowPickDirectory: false,
@@ -131,14 +131,14 @@ describe('FilePicker FileList', () => {
131131
})
132132

133133
const selectAll = wrapper.find('[data-testid="select-all-checkbox"]')
134-
expect(selectAll.props('modelValue')).toBe(true)
134+
expect(selectAll.attributes('modelvalue')).toBe('true')
135135
})
136136

137137
describe('file list sorting', () => {
138138
it('is sorted initially by name', async () => {
139139
const nodes = [...exampleNodes]
140140
const wrapper = mount(FileList, {
141-
propsData: {
141+
props: {
142142
currentView: 'files',
143143
multiselect: true,
144144
allowPickDirectory: false,
@@ -158,18 +158,18 @@ describe('FilePicker FileList', () => {
158158
// all nodes are shown
159159
expect(rows.length).toBe(nodes.length)
160160
// by default favorites are sorted before other files
161-
expect(rows.at(0).attributes('data-filename')).toBe('favorite.txt')
161+
expect(rows.at(0)!.attributes('data-filename')).toBe('favorite.txt')
162162
// folder are sorted first
163-
expect(rows.at(1).attributes('data-filename')).toBe('directory')
163+
expect(rows.at(1)!.attributes('data-filename')).toBe('directory')
164164
// other files are ascending
165-
expect(rows.at(2).attributes('data-filename')).toBe('a-file.txt')
166-
expect(rows.at(3).attributes('data-filename')).toBe('b-file.txt')
165+
expect(rows.at(2)!.attributes('data-filename')).toBe('a-file.txt')
166+
expect(rows.at(3)!.attributes('data-filename')).toBe('b-file.txt')
167167
})
168168

169169
it('can sort descending by name', async () => {
170170
const nodes = [...exampleNodes]
171171
const wrapper = mount(FileList, {
172-
propsData: {
172+
props: {
173173
currentView: 'files',
174174
multiselect: true,
175175
allowPickDirectory: false,
@@ -193,12 +193,12 @@ describe('FilePicker FileList', () => {
193193
// all nodes are shown
194194
expect(rows.length).toBe(nodes.length)
195195
// by default favorites are sorted before other files
196-
expect(rows.at(0).attributes('data-filename')).toBe('favorite.txt')
196+
expect(rows.at(0)!.attributes('data-filename')).toBe('favorite.txt')
197197
// folder are sorted first
198-
expect(rows.at(1).attributes('data-filename')).toBe('directory')
198+
expect(rows.at(1)!.attributes('data-filename')).toBe('directory')
199199
// other files are descending
200-
expect(rows.at(2).attributes('data-filename')).toBe('b-file.txt')
201-
expect(rows.at(3).attributes('data-filename')).toBe('a-file.txt')
200+
expect(rows.at(2)!.attributes('data-filename')).toBe('b-file.txt')
201+
expect(rows.at(3)!.attributes('data-filename')).toBe('a-file.txt')
202202
})
203203
})
204204
})

lib/components/FilePicker/FileList.vue

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,9 @@
2020
<th :aria-sort="sortByName" class="row-name">
2121
<div class="header-wrapper">
2222
<span class="file-picker__header-preview" />
23-
<NcButton :wide="true"
24-
type="tertiary"
25-
data-test="file-picker_sort-name"
23+
<NcButton data-test="file-picker_sort-name"
24+
variant="tertiary"
25+
wide
2626
@click="toggleSorting('basename')">
2727
<template #icon>
2828
<IconSortAscending v-if="sortByName === 'ascending'" :size="20" />
@@ -34,7 +34,7 @@
3434
</div>
3535
</th>
3636
<th :aria-sort="sortBySize" class="row-size">
37-
<NcButton :wide="true" type="tertiary" @click="toggleSorting('size')">
37+
<NcButton variant="tertiary" wide @click="toggleSorting('size')">
3838
<template #icon>
3939
<IconSortAscending v-if="sortBySize === 'ascending'" :size="20" />
4040
<IconSortDescending v-else-if="sortBySize === 'descending'" :size="20" />
@@ -44,7 +44,7 @@
4444
</NcButton>
4545
</th>
4646
<th :aria-sort="sortByModified" class="row-modified">
47-
<NcButton :wide="true" type="tertiary" @click="toggleSorting('mtime')">
47+
<NcButton variant="tertiary" wide @click="toggleSorting('mtime')">
4848
<template #icon>
4949
<IconSortAscending v-if="sortByModified === 'ascending'" :size="20" />
5050
<IconSortDescending v-else-if="sortByModified === 'descending'" :size="20" />

lib/components/FilePicker/FileListRow.spec.ts

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ describe('FilePicker: FileListRow', () => {
2929
const consoleError = vi.spyOn(console, 'error')
3030

3131
const wrapper = shallowMount(FileListRow, {
32-
propsData: {
32+
props: {
3333
allowPickDirectory: true,
3434
selected: false,
3535
showCheckbox: true,
@@ -50,7 +50,7 @@ describe('FilePicker: FileListRow', () => {
5050

5151
it('shows checkbox based on `showCheckbox` property', async () => {
5252
const wrapper = shallowMount(FileListRow, {
53-
propsData: {
53+
props: {
5454
allowPickDirectory: true,
5555
selected: false,
5656
showCheckbox: true,
@@ -67,17 +67,19 @@ describe('FilePicker: FileListRow', () => {
6767

6868
it('Click checkbox triggers select', async () => {
6969
const wrapper = shallowMount(FileListRow, {
70-
propsData: {
70+
props: {
7171
allowPickDirectory: false,
7272
selected: false,
7373
showCheckbox: true,
7474
canPick: true,
7575
node,
7676
cropImagePreviews: true,
7777
},
78-
stubs: {
79-
NcCheckboxRadioSwitch: {
80-
template: '<label><input type="checkbox" @click="$emit(\'update:model-value\', true)" ></label>',
78+
global: {
79+
stubs: {
80+
NcCheckboxRadioSwitch: {
81+
template: '<label><input type="checkbox" @click="$emit(\'update:model-value\', true)" ></label>',
82+
},
8183
},
8284
},
8385
})
@@ -90,7 +92,7 @@ describe('FilePicker: FileListRow', () => {
9092

9193
it('Click element triggers select', async () => {
9294
const wrapper = shallowMount(FileListRow, {
93-
propsData: {
95+
props: {
9496
allowPickDirectory: false,
9597
selected: false,
9698
showCheckbox: true,
@@ -108,7 +110,7 @@ describe('FilePicker: FileListRow', () => {
108110

109111
it('Click element without checkbox triggers select', async () => {
110112
const wrapper = shallowMount(FileListRow, {
111-
propsData: {
113+
props: {
112114
allowPickDirectory: false,
113115
selected: false,
114116
showCheckbox: false,
@@ -126,7 +128,7 @@ describe('FilePicker: FileListRow', () => {
126128

127129
it('Enter triggers select', async () => {
128130
const wrapper = shallowMount(FileListRow, {
129-
propsData: {
131+
props: {
130132
allowPickDirectory: false,
131133
selected: false,
132134
showCheckbox: false,

lib/components/FilePicker/FileListRow.vue

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@
3333
{{ formatFileSize(node.size || 0) }}
3434
</td>
3535
<td class="row-modified">
36-
<NcDateTime :timestamp="node.mtime" :ignore-seconds="true" />
36+
<NcDateTime :timestamp ignore-seconds />
3737
</td>
3838
</tr>
3939
</template>
@@ -65,11 +65,13 @@ const props = defineProps<{
6565
6666
const emit = defineEmits<{
6767
/** Emitted when the selected state is changed */
68-
(e: 'update:selected', v: boolean): void
68+
'update:selected': [selected: boolean]
6969
/** Emitted when a directory was not selected but entered */
70-
(e: 'enter-directory', node: INode): void
70+
enterDirectory: [node: INode]
7171
}>()
7272
73+
const timestamp = computed(() => props.node.mtime ?? 0)
74+
7375
/**
7476
* The displayname of the current node (excluding file extension)
7577
*/
@@ -102,7 +104,7 @@ function toggleSelected() {
102104
*/
103105
function handleClick() {
104106
if (isDirectory.value) {
105-
emit('enter-directory', props.node)
107+
emit('enterDirectory', props.node)
106108
} else {
107109
toggleSelected()
108110
}
@@ -120,7 +122,7 @@ function handleKeyDown(event: KeyboardEvent) {
120122
</script>
121123

122124
<style scoped lang="scss">
123-
@use './FileList.scss';
125+
@use './FileList';
124126
125127
.file-picker {
126128
&__row {

0 commit comments

Comments
 (0)