Skip to content

Commit

Permalink
Merge pull request #1351 from nextcloud-libraries/fix/add-current-fol…
Browse files Browse the repository at this point in the history
…der-button-factory

fix: Also add current folder to button factory
  • Loading branch information
susnux authored Jun 10, 2024
2 parents 12ecc5d + f43e612 commit 194cf83
Show file tree
Hide file tree
Showing 4 changed files with 61 additions and 43 deletions.
58 changes: 30 additions & 28 deletions lib/components/FilePicker/FilePicker.vue
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,10 @@
<div class="file-picker__main">
<!-- Header title / file list breadcrumbs -->
<FilePickerBreadcrumbs v-if="currentView === 'files'"
:path.sync="currentPath"
:path="currentPath"
:show-menu="allowPickDirectory"
@create-node="onCreateFolder" />
@create-node="onCreateFolder"
@update:path="navigatedPath = $event" />
<div v-else class="file-picker__view">
<h3>{{ viewHeadline }}</h3>
</div>
Expand Down Expand Up @@ -67,7 +68,7 @@ import FilePickerNavigation from './FilePickerNavigation.vue'
import { emit as emitOnEventBus } from '@nextcloud/event-bus'
import { NcDialog, NcEmptyContent } from '@nextcloud/vue'
import { computed, onMounted, ref, toRef } from 'vue'
import { computed, onMounted, ref, shallowReactive, toRef, watch } from 'vue'
import { showError } from '../../toast'
import { useDAVFiles } from '../../composables/dav'
import { useMimeFilter } from '../../composables/mime'
Expand Down Expand Up @@ -148,16 +149,17 @@ const isOpen = ref(true)
* Map buttons to Dialog buttons by wrapping the callback function to pass the selected files
*/
const dialogButtons = computed(() => {
const nodes = selectedFiles.length === 0 && props.allowPickDirectory && currentFolder.value ? [currentFolder.value] : selectedFiles
const buttons = typeof props.buttons === 'function'
? props.buttons(selectedFiles.value as Node[], currentPath.value, currentView.value)
? props.buttons(nodes, currentPath.value, currentView.value)
: props.buttons
return buttons.map((button) => ({
...button,
callback: () => {
// lock default close handling
isHandlingCallback = true
handleButtonClick(button.callback)
handleButtonClick(button.callback, nodes)
},
} as IFilePickerButton))
})
Expand All @@ -168,8 +170,7 @@ const dialogButtons = computed(() => {
*/
let isHandlingCallback = false
const handleButtonClick = async (callback: IFilePickerButton['callback']) => {
const nodes = selectedFiles.value.length === 0 && props.allowPickDirectory ? [await getFile(currentPath.value)] : selectedFiles.value as Node[]
const handleButtonClick = async (callback: IFilePickerButton['callback'], nodes: Node[]) => {
callback(nodes)
emit('close', nodes)
// Unlock close
Expand All @@ -189,7 +190,7 @@ const viewHeadline = computed(() => currentView.value === 'favorites' ? t('Favor
/**
* All currently selected files
*/
const selectedFiles = ref<Node[]>([])
const selectedFiles = shallowReactive<Node[]>([])
/**
* Last path navigated to using the file picker
Expand All @@ -200,28 +201,23 @@ const savedPath = ref(window?.sessionStorage.getItem('NC.FilePicker.LastPath') |
/**
* The path the user manually navigated to using this filepicker instance
*/
const navigatedPath = ref<string>()
const navigatedPath = ref('')
// Save the navigated path to the session storage on change
watch([navigatedPath], () => {
if (props.path === undefined && navigatedPath.value) {
window.sessionStorage.setItem('NC.FilePicker.LastPath', navigatedPath.value)
// Reset selected files
selectedFiles.splice(0, selectedFiles.length)
}
})
/**
* The current path that should be picked from
*/
const currentPath = computed({
const currentPath = computed(() =>
// Only use the path for the files view as favorites and recent only works on the root
get: () => currentView.value === 'files' ? navigatedPath.value || props.path || savedPath.value : '/',
/**
* Navigate to the new path and save it to the session storage
*
* @param path The new path
*/
set: (path: string) => {
if (props.path === undefined) {
window.sessionStorage.setItem('NC.FilePicker.LastPath', path)
}
navigatedPath.value = path
// Reset selected files
selectedFiles.value = []
},
})
currentView.value === 'files' ? navigatedPath.value || props.path || savedPath.value : '/',
)
/**
* A string used to filter files in current view
Expand All @@ -230,7 +226,13 @@ const filterString = ref('')
const { isSupportedMimeType } = useMimeFilter(toRef(props, 'mimetypeFilter')) // vue 3.3 will allow cleaner syntax of toRef(() => props.mimetypeFilter)
const { files, isLoading, loadFiles, getFile, createDirectory } = useDAVFiles(currentView, currentPath, isPublic)
const {
files,
folder: currentFolder,
isLoading,
loadFiles,
createDirectory,
} = useDAVFiles(currentView, currentPath, isPublic)
onMounted(() => loadFiles())
Expand All @@ -254,7 +256,7 @@ const filteredFiles = computed(() => {
filtered = filtered.filter((file) => file.basename.toLowerCase().includes(filterString.value.toLowerCase()))
}
if (props.filterFn) {
filtered = filtered.filter((f) => props.filterFn(f as Node))
filtered = filtered.filter((f) => props.filterFn!(f as Node))
}
return filtered
})
Expand All @@ -281,7 +283,7 @@ const noFilesDescription = computed(() => {
const onCreateFolder = async (name: string) => {
try {
const folder = await createDirectory(name)
currentPath.value = folder.path
navigatedPath.value = folder.path
// emit event bus to force files app to reload that file if needed
emitOnEventBus('files:node:created', files.value.filter((file) => file.basename === name)[0])
} catch (error) {
Expand Down
6 changes: 6 additions & 0 deletions lib/components/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,12 @@ export interface IDialogButton {
* @see https://nextcloud-vue-components.netlify.app/#/Components/NcButton
*/
type?: 'primary' | 'secondary' | 'error' | 'warning' | 'success'

/**
* Disabled state of the button
* @default false
*/
disabled?: boolean
}

/**
Expand Down
21 changes: 11 additions & 10 deletions lib/composables/dav.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
*/

import type { Ref } from 'vue'
import { describe, it, expect, vi, afterEach } from 'vitest'
import { describe, it, expect, vi, beforeEach } from 'vitest'
import { shallowMount } from '@vue/test-utils'
import { defineComponent, ref, toRef, nextTick } from 'vue'
import { useDAVFiles } from './dav'
Expand Down Expand Up @@ -65,7 +65,7 @@ const TestComponent = defineComponent({
})

describe('dav composable', () => {
afterEach(() => { vi.resetAllMocks() })
beforeEach(() => { vi.resetAllMocks() })

it('Sets the inital state correctly', () => {
const client = {
Expand Down Expand Up @@ -175,24 +175,25 @@ describe('dav composable', () => {
stat: vi.fn((v) => ({ data: { path: v } })),
getDirectoryContents: vi.fn(() => ({ data: [] })),
}
nextcloudFiles.davGetClient.mockImplementationOnce(() => client)
nextcloudFiles.davResultToNode.mockImplementationOnce((v) => v)
nextcloudFiles.davGetClient.mockImplementation(() => client)
nextcloudFiles.davResultToNode.mockImplementation((v) => v)

const { getFile } = useDAVFiles(ref('files'), ref('/'), ref(false))

const node = await getFile('/some/path')
expect(node).toEqual({ path: `${nextcloudFiles.davRootPath}/some/path` })
expect(client.stat).toBeCalledWith(`${nextcloudFiles.davRootPath}/some/path`, { details: true })
expect(nextcloudFiles.davResultToNode).toBeCalledWith({ path: `${nextcloudFiles.davRootPath}/some/path` }, nextcloudFiles.davRootPath, nextcloudFiles.davRemoteURL)
const node = await getFile('/some/path/file.ext')
expect(node).toEqual({ path: `${nextcloudFiles.davRootPath}/some/path/file.ext` })
// Check mock usage
expect(client.stat).toBeCalledWith(`${nextcloudFiles.davRootPath}/some/path/file.ext`, { details: true })
expect(nextcloudFiles.davResultToNode).toBeCalledWith({ path: `${nextcloudFiles.davRootPath}/some/path/file.ext` }, nextcloudFiles.davRootPath, nextcloudFiles.davRemoteURL)
})

it('createDirectory works', async () => {
const client = {
stat: vi.fn((v) => ({ data: { path: v } })),
createDirectory: vi.fn(() => {}),
}
nextcloudFiles.davGetClient.mockImplementationOnce(() => client)
nextcloudFiles.davResultToNode.mockImplementationOnce((v) => v)
nextcloudFiles.davGetClient.mockImplementation(() => client)
nextcloudFiles.davResultToNode.mockImplementation((v) => v)

const { createDirectory } = useDAVFiles(ref('files'), ref('/foo/'), ref(false))

Expand Down
19 changes: 14 additions & 5 deletions lib/composables/dav.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ import type { FileStat, ResponseDataDetailed, SearchResult } from 'webdav'
import { davGetClient, davGetDefaultPropfind, davGetRecentSearch, davRemoteURL, davResultToNode, davRootPath, getFavoriteNodes } from '@nextcloud/files'
import { generateRemoteUrl } from '@nextcloud/router'
import { join } from 'path'
import { computed, onMounted, ref, watch } from 'vue'
import { computed, onMounted, ref, shallowRef, watch } from 'vue'
import { CancelablePromise } from 'cancelable-promise'

/**
Expand All @@ -39,8 +39,8 @@ import { CancelablePromise } from 'cancelable-promise'
export const useDAVFiles = function(
currentView: Ref<'files'|'recent'|'favorites'> | ComputedRef<'files'|'recent'|'favorites'>,
currentPath: Ref<string> | ComputedRef<string>,
isPublicEndpoint: Ref<boolean> | ComputedRef<boolean>
): { isLoading: Ref<boolean>, createDirectory: (name: string) => Promise<Folder>, files: Ref<Node[]>, loadFiles: () => Promise<void>, getFile: (path: string) => Promise<Node> } {
isPublicEndpoint: Ref<boolean> | ComputedRef<boolean>,
) {

const defaultRootPath = computed(() => isPublicEndpoint.value ? '/' : davRootPath)

Expand Down Expand Up @@ -114,7 +114,15 @@ export const useDAVFiles = function(
/**
* All files in current view and path
*/
const files = ref<Node[]>([] as Node[]) as Ref<Node[]>
const files = shallowRef<Node[]>([] as Node[]) as Ref<Node[]>

/**
* The current folder
*/
const folder = shallowRef<Folder>()
watch([currentPath], async () => {
folder.value = (files.value.find(({ path }) => path === currentPath.value) ?? await getFile(currentPath.value)) as Folder
}, { immediate: true })

/**
* Loading state of the files
Expand All @@ -136,7 +144,7 @@ export const useDAVFiles = function(

await client.value.createDirectory(join(defaultRootPath.value, path))
const directory = await getFile(path) as Folder
files.value.push(directory)
files.value = [...files.value, directory]
return directory
}

Expand Down Expand Up @@ -191,6 +199,7 @@ export const useDAVFiles = function(
return {
isLoading,
files,
folder,
loadFiles: loadDAVFiles,
getFile,
createDirectory,
Expand Down

0 comments on commit 194cf83

Please sign in to comment.