Skip to content

Commit 6488466

Browse files
committed
refactor: update to @nextcloud/files v4 and adjust code
Signed-off-by: Ferdinand Thiessen <opensource@fthiessen.de>
1 parent f53896c commit 6488466

10 files changed

Lines changed: 146 additions & 162 deletions

File tree

lib/components/FilePicker/FileList.vue

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@
8080
</template>
8181

8282
<script setup lang="ts">
83-
import type { FilesSortingMode, INode } from '@nextcloud/files'
83+
import type { INode } from '@nextcloud/files'
8484
import type { FileListViews } from '../../composables/filesSettings.ts'
8585
import type { IFilePickerCanPick } from '../types.ts'
8686
@@ -174,7 +174,7 @@ const sortedFiles = computed(() => {
174174
sortFoldersFirst: true,
175175
sortFavoritesFirst: sortFavoritesFirst.value,
176176
sortingOrder: sortingConfig.value.order === 'descending' ? 'desc' : 'asc',
177-
sortingMode: sortingConfig.value.sortBy as FilesSortingMode,
177+
sortingMode: sortingConfig.value.sortBy,
178178
})
179179
})
180180

lib/components/FilePicker/FilePicker.vue

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@
6666
</template>
6767

6868
<script setup lang="ts">
69-
import type { Node } from '@nextcloud/files'
69+
import type { INode } from '@nextcloud/files'
7070
import type { IFilesViewId } from '../../composables/views.ts'
7171
import type { IDialogButton, IFilePickerButton, IFilePickerButtonFactory, IFilePickerCanPick, IFilePickerFilter } from '../types.ts'
7272
@@ -153,7 +153,7 @@ const props = withDefaults(defineProps<{
153153
})
154154
155155
const emit = defineEmits<{
156-
(e: 'close', v?: Node[]): void
156+
(e: 'close', v?: INode[]): void
157157
}>()
158158
159159
const isOpen = ref(true)
@@ -191,7 +191,7 @@ const currentPath = computed({
191191
/**
192192
* All currently selected files
193193
*/
194-
const selectedFiles = shallowRef<Node[]>([])
194+
const selectedFiles = shallowRef<INode[]>([])
195195
196196
const {
197197
files,
@@ -245,7 +245,7 @@ const dialogButtons = computed(() => {
245245
* @param callback - Callback of the button
246246
* @param nodes - Currently selected nodes
247247
*/
248-
async function handleButtonClick(callback: IFilePickerButton['callback'], nodes: Node[]) {
248+
async function handleButtonClick(callback: IFilePickerButton['callback'], nodes: INode[]) {
249249
await callback(nodes)
250250
emit('close', nodes)
251251
// Unlock close
@@ -286,7 +286,7 @@ const filteredFiles = computed(() => {
286286
filtered = filtered.filter((file) => file.basename.toLowerCase().includes(filterString.value.toLowerCase()))
287287
}
288288
if (props.filterFn) {
289-
filtered = filtered.filter((f) => props.filterFn!(f as Node))
289+
filtered = filtered.filter((f) => props.filterFn!(f as INode))
290290
}
291291
return filtered
292292
})

lib/components/types.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
* SPDX-License-Identifier: AGPL-3.0-or-later
44
*/
55

6-
import type { INode, Node } from '@nextcloud/files'
6+
import type { INode } from '@nextcloud/files'
77

88
export type IDialogSeverity = 'info' | 'warning' | 'error'
99

@@ -49,15 +49,15 @@ export interface IFilePickerButton extends Omit<IDialogButton, 'callback'> {
4949
*
5050
* @param nodes Array of `@nextcloud/files` Nodes that were selected
5151
*/
52-
callback: (nodes: Node[]) => void | Promise<void>
52+
callback: (nodes: INode[]) => void | Promise<void>
5353
}
5454

55-
export type IFilePickerButtonFactory = (selectedNodes: Node[], currentPath: string, currentView: string) => IFilePickerButton[]
55+
export type IFilePickerButtonFactory = (selectedNodes: INode[], currentPath: string, currentView: string) => IFilePickerButton[]
5656

5757
/**
5858
* Type of filter functions to filter the FilePicker's file list
5959
*/
60-
export type IFilePickerFilter = (node: Node) => boolean
60+
export type IFilePickerFilter = (node: INode) => boolean
6161

6262
/**
6363
* Type of functions to allow or not picking a node

lib/composables/dav.spec.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -238,6 +238,6 @@ describe('dav composable', () => {
238238
await nextTick()
239239
view.value = 'favorites'
240240
await waitRefLoaded(isLoading)
241-
expect(abort).toBeCalledTimes(2)
241+
expect(abort).toBeCalledTimes(1)
242242
})
243243
})

lib/composables/dav.ts

Lines changed: 34 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
1-
/**
1+
/*
22
* SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors
33
* SPDX-License-Identifier: AGPL-3.0-or-later
44
*/
5-
import type { ContentsWithRoot, Folder, Node } from '@nextcloud/files'
6-
import type { CancelablePromise } from 'cancelable-promise'
5+
6+
import type { IFolder, INode } from '@nextcloud/files'
77
import type { ComputedRef, Ref } from 'vue'
88

99
import { defaultRootPath, getClient, getFavoriteNodes } from '@nextcloud/files/dav'
@@ -29,12 +29,12 @@ export function useDAVFiles(
2929
/**
3030
* All files in current view and path
3131
*/
32-
const files = shallowRef<Node[]>([] as Node[]) as Ref<Node[]>
32+
const files = shallowRef<INode[]>([] as INode[]) as Ref<INode[]>
3333

3434
/**
3535
* The current folder
3636
*/
37-
const folder = shallowRef<Folder | null>(null)
37+
const folder = shallowRef<IFolder | null>(null)
3838

3939
/**
4040
* Loading state of the files
@@ -44,7 +44,7 @@ export function useDAVFiles(
4444
/**
4545
* The cancelable promise used internally to cancel on fast navigation
4646
*/
47-
const promise = ref<null | CancelablePromise<Node[] | ContentsWithRoot>>(null)
47+
let abortController: AbortController | undefined
4848

4949
/**
5050
* Create a new directory in the current path
@@ -53,11 +53,11 @@ export function useDAVFiles(
5353
* @param name Name of the new directory
5454
* @return The created directory
5555
*/
56-
async function createDirectory(name: string): Promise<Folder> {
56+
async function createDirectory(name: string): Promise<IFolder> {
5757
const path = join(currentPath.value, name)
5858

5959
await client.createDirectory(join(defaultRootPath, path))
60-
const directory = await getFile(client, path) as Folder
60+
const directory = await getFile(client, path) as IFolder
6161
files.value = [...files.value, directory]
6262
return directory
6363
}
@@ -66,31 +66,35 @@ export function useDAVFiles(
6666
* Force reload files using the DAV client
6767
*/
6868
async function loadDAVFiles() {
69-
if (promise.value) {
70-
promise.value.cancel()
69+
if (abortController) {
70+
abortController.abort()
71+
abortController = undefined
7172
}
72-
isLoading.value = true
7373

74-
if (currentView.value === 'favorites') {
75-
promise.value = getFavoriteNodes(client, currentPath.value)
76-
} else if (currentView.value === 'recent') {
77-
promise.value = getRecentNodes(client)
78-
} else {
79-
promise.value = getNodes(client, currentPath.value)
80-
}
81-
const content = await promise.value
82-
if (!content) {
83-
return
84-
} else if ('folder' in content) {
85-
folder.value = content.folder
86-
files.value = content.contents
87-
} else {
88-
folder.value = null
89-
files.value = content
74+
abortController = new AbortController()
75+
isLoading.value = true
76+
try {
77+
if (currentView.value === 'favorites') {
78+
files.value = await getFavoriteNodes({ client, path: currentPath.value, signal: abortController.signal })
79+
folder.value = null
80+
} else if (currentView.value === 'recent') {
81+
files.value = await getRecentNodes({ client, signal: abortController.signal })
82+
folder.value = null
83+
} else {
84+
const content = await getNodes({ client, path: currentPath.value, signal: abortController.signal })
85+
folder.value = content.folder
86+
files.value = content.contents
87+
}
88+
} catch (error) {
89+
if (error instanceof Error && error.name === 'AbortError') {
90+
// ignore abort errors
91+
return
92+
}
93+
throw error
94+
} finally {
95+
abortController = undefined
96+
isLoading.value = false
9097
}
91-
92-
promise.value = null
93-
isLoading.value = false
9498
}
9599

96100
/**

lib/filepicker-builder.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
* SPDX-License-Identifier: AGPL-3.0-or-later
44
*/
55

6-
import type { Node } from '@nextcloud/files'
6+
import type { INode } from '@nextcloud/files'
77
import type { IFilePickerButton, IFilePickerButtonFactory, IFilePickerCanPick, IFilePickerFilter } from './components/types.ts'
88

99
import IconMove from '@mdi/svg/svg/folder-move.svg?raw'
@@ -73,7 +73,7 @@ export class FilePicker<IsMultiSelect extends boolean> {
7373
*
7474
* @return Promise with array of picked files or rejected promise on close without picking
7575
*/
76-
public async pickNodes(): Promise<Node[]> {
76+
public async pickNodes(): Promise<INode[]> {
7777
const { default: FilePickerVue } = await import('./components/FilePicker/FilePicker.vue')
7878

7979
const nodes = await spawnDialog(FilePickerVue, {

lib/utils/dav.ts

Lines changed: 26 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -8,59 +8,45 @@ import type { FileStat, ResponseDataDetailed, SearchResult, WebDAVClient } from
88

99
import { defaultRootPath, getDefaultPropfind, getRecentSearch, resultToNode } from '@nextcloud/files/dav'
1010
import { join } from '@nextcloud/paths'
11-
import { CancelablePromise } from 'cancelable-promise'
1211

1312
/**
1413
* Get the recently changed nodes from the last two weeks
1514
*
16-
* @param client - The WebDAV client
15+
* @param context - The context
16+
* @param context.client - The WebDAV client
17+
* @param context.signal - The abort signal to cancel the request
1718
*/
18-
export function getRecentNodes(client: WebDAVClient): CancelablePromise<Node[]> {
19-
const controller = new AbortController()
19+
export async function getRecentNodes({ client, signal }: { client: WebDAVClient, signal: AbortSignal }): Promise<Node[]> {
2020
// unix timestamp in seconds, two weeks ago
2121
const lastTwoWeek = Math.round(Date.now() / 1000) - (60 * 60 * 24 * 14)
22-
return new CancelablePromise(async (resolve, reject, onCancel) => {
23-
onCancel(() => controller.abort())
24-
try {
25-
const { data } = await client.search('/', {
26-
signal: controller.signal,
27-
details: true,
28-
data: getRecentSearch(lastTwoWeek),
29-
}) as ResponseDataDetailed<SearchResult>
30-
const nodes = data.results.map((result: FileStat) => resultToNode(result))
31-
resolve(nodes)
32-
} catch (error) {
33-
reject(error)
34-
}
35-
})
22+
const { data } = await client.search('/', {
23+
signal,
24+
details: true,
25+
data: getRecentSearch(lastTwoWeek),
26+
}) as ResponseDataDetailed<SearchResult>
27+
return data.results.map((result: FileStat) => resultToNode(result))
3628
}
3729

3830
/**
3931
* Get the directory content
4032
*
41-
* @param client - The WebDAV client
42-
* @param directoryPath - The path to fetch
33+
* @param context - The context
34+
* @param context.client - The WebDAV client
35+
* @param context.path - The path to fetch
36+
* @param context.signal - The abort signal to cancel the request
4337
*/
44-
export function getNodes(client: WebDAVClient, directoryPath: string): CancelablePromise<ContentsWithRoot> {
45-
const controller = new AbortController()
46-
return new CancelablePromise(async (resolve, reject, onCancel) => {
47-
onCancel(() => controller.abort())
48-
try {
49-
const results = await client.getDirectoryContents(join(defaultRootPath, directoryPath), {
50-
signal: controller.signal,
51-
details: true,
52-
includeSelf: true,
53-
data: getDefaultPropfind(),
54-
}) as ResponseDataDetailed<FileStat[]>
55-
const nodes = results.data.map((result: FileStat) => resultToNode(result))
56-
resolve({
57-
contents: nodes.filter(({ path }) => path !== directoryPath),
58-
folder: nodes.find(({ path }) => path === directoryPath) as Folder,
59-
})
60-
} catch (error) {
61-
reject(error)
62-
}
63-
})
38+
export async function getNodes({ client, path, signal }: { client: WebDAVClient, path: string, signal: AbortSignal }): Promise<ContentsWithRoot> {
39+
const results = await client.getDirectoryContents(join(defaultRootPath, path), {
40+
signal,
41+
details: true,
42+
includeSelf: true,
43+
data: getDefaultPropfind(),
44+
}) as ResponseDataDetailed<FileStat[]>
45+
const nodes = results.data.map((result: FileStat) => resultToNode(result))
46+
return {
47+
contents: nodes.filter(({ path: nodePath }) => nodePath !== path),
48+
folder: nodes.find(({ path: nodePath }) => path === nodePath) as Folder,
49+
}
6450
}
6551

6652
/**

0 commit comments

Comments
 (0)