Skip to content

Commit

Permalink
feat: Allow to create new folders from FilePicker
Browse files Browse the repository at this point in the history
Signed-off-by: Ferdinand Thiessen <opensource@fthiessen.de>
  • Loading branch information
susnux committed Aug 10, 2023
1 parent 5ca529f commit 9277287
Show file tree
Hide file tree
Showing 3 changed files with 63 additions and 11 deletions.
24 changes: 21 additions & 3 deletions lib/components/FilePicker/FilePicker.vue
Expand Up @@ -6,7 +6,10 @@

<div class="file-picker__main">
<!-- Header title / file list breadcrumbs -->
<FilePickerBreadcrumbs v-if="currentView === 'files'" :path.sync="currentPath" :show-menu="allowPickDirectory" />
<FilePickerBreadcrumbs v-if="currentView === 'files'"
:path.sync="currentPath"
:show-menu="allowPickDirectory"
@create-node="onCreateFolder"/>
<div v-else class="file-picker__view">
<h3>{{ viewHeadline }}</h3>
</div>
Expand All @@ -26,17 +29,19 @@

<script setup lang="ts">
import type { IDialogButton } from '../DialogButton.vue'
import type { Node } from '@nextcloud/files'
import { davRootPath, type Node } from '@nextcloud/files'
import DialogBase from '../DialogBase.vue'
import FileList from './FileList.vue'
import FilePickerBreadcrumbs from './FilePickerBreadcrumbs.vue'
import FilePickerNavigation from './FilePickerNavigation.vue'
import { t } from '../../l10n'
import { join } from 'path'
import { computed, onMounted, ref, toRef } from 'vue'
import { useDAVFiles } from '../../usables/dav'
import { useMimeFilter } from '../../usables/mime'
import { showError } from '../../toast'
export interface IFilePickerButton extends Omit<IDialogButton, 'callback'> {
callback: (nodes: Node[]) => void
Expand Down Expand Up @@ -171,7 +176,7 @@ 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 } = useDAVFiles(currentView, currentPath)
const { files, isLoading, loadFiles, getFile, client } = useDAVFiles(currentView, currentPath)
onMounted(() => loadFiles())
Expand All @@ -193,6 +198,19 @@ const filteredFiles = computed(() => {
}
return filtered
})
/**
* Handle creating new folder (breadcrumb menu)
* @param name The new folder name
*/
const onCreateFolder = (name: string) => {
client
.createDirectory(join(davRootPath, currentPath.value, name))
// reload file list
.then(() => loadFiles())
// show error to user
.catch((e) => showError(t('Could not create the new folder')))
}
</script>

<script lang="ts">
Expand Down
45 changes: 39 additions & 6 deletions lib/components/FilePicker/FilePickerBreadcrumbs.vue
Expand Up @@ -5,7 +5,7 @@
:title="t('Home')"
@click="emit('update:path', '/')">
<template #icon>
<IconFolder :size="20" />
<IconHome :size="20" />
</template>
</NcBreadcrumb>
<NcBreadcrumb v-for="dir in pathElements"
Expand All @@ -22,12 +22,14 @@
<template #icon>
<IconPlus :size="20" />
</template>
<NcActionInput :value.sync="newNodeName"
<NcActionInput ref="nameInput"
:value.sync="newNodeName"
:label="t('New folder')"
:placeholder="t('New folder name')"
@submit="onSubmit">
@submit="onSubmit"
@input="validateInput">
<template #icon>
<IconHome :size="20" />
<IconFolder :size="20" />
</template>
</NcActionInput>
</NcActions>
Expand All @@ -36,6 +38,9 @@
</template>

<script setup lang="ts">
import type Vue from 'vue'
import IconFolder from 'vue-material-design-icons/Folder.vue'
import IconHome from 'vue-material-design-icons/Home.vue'
import IconPlus from 'vue-material-design-icons/Plus.vue'
Expand Down Expand Up @@ -66,12 +71,40 @@ const emit = defineEmits<{
*/
const newNodeName = ref('')
const nameInput = ref<Vue>()
/**
* Validate user folder name input
*/
function validateInput() {
const name = newNodeName.value.trim()
const input = nameInput.value?.$el?.querySelector('input')
let validity = ''
if (name.length === 0) {
validity = t('File name cannot be empty.')
} else if (name.includes('/')) {
validity = t('"/" is not allowed inside a file name.')
} else if (['..', '.'].includes(name)) {
validity = t('"{name}" is an invalid file name.', { name })
} else if (name.match(window.OC.config.blacklist_files_regex)) {
validity = t('"{name}" is not an allowed filetype', { name })
}
if (input) {
input.setCustomValidity(validity)
}
return validity === ''
}
/**
* Handle creating a new node
*/
const onSubmit = function() {
emit('create-node', newNodeName.value)
newNodeName.value = ''
const name = newNodeName.value.trim()
if (validateInput()) {
emit('create-node', name)
newNodeName.value = ''
}
}
/**
Expand Down
5 changes: 3 additions & 2 deletions lib/usables/dav.ts
Expand Up @@ -21,7 +21,7 @@
*/
import type { Node } from '@nextcloud/files'
import type { ComputedRef, Ref } from 'vue'
import type { FileStat, ResponseDataDetailed } from 'webdav'
import type { FileStat, ResponseDataDetailed, WebDAVClient } from 'webdav'

import { davGetClient, davGetFavoritesReport, davGetRecentSearch, davResultToNode, davRootPath } from '@nextcloud/files'
import { generateRemoteUrl } from '@nextcloud/router'
Expand All @@ -33,7 +33,7 @@ import { ref, watch } from 'vue'
* @param currentView Reference to the current files view
* @param currentPath Reference to the current files path
*/
export const useDAVFiles = function(currentView: Ref<'files'|'recent'|'favorites'> | ComputedRef<'files'|'recent'|'favorites'>, currentPath: Ref<string> | ComputedRef<string>): { isLoading: Ref<boolean>, files: Ref<Node[]>, loadFiles: () => void, getFile: (path: string) => Promise<Node> } {
export const useDAVFiles = function(currentView: Ref<'files'|'recent'|'favorites'> | ComputedRef<'files'|'recent'|'favorites'>, currentPath: Ref<string> | ComputedRef<string>): { isLoading: Ref<boolean>, client: WebDAVClient, files: Ref<Node[]>, loadFiles: () => void, getFile: (path: string) => Promise<Node> } {
/**
* The WebDAV client
*/
Expand Down Expand Up @@ -107,5 +107,6 @@ export const useDAVFiles = function(currentView: Ref<'files'|'recent'|'favorites
files,
loadFiles: () => loadDAVFiles(),
getFile,
client,
}
}

0 comments on commit 9277287

Please sign in to comment.