@nextcloud/vue
Version:
Nextcloud vue components
1 lines • 28.5 kB
Source Map (JSON)
{"version":3,"file":"NcUploadPicker.mjs","sources":["../../node_modules/@nextcloud/paths/dist/index.mjs","../../src/components/NcUploadPicker/NcUploadPicker.vue"],"sourcesContent":["function encodePath(path) {\n if (!path) {\n return path;\n }\n return path.split(\"/\").map(encodeURIComponent).join(\"/\");\n}\nfunction basename(path, extname2) {\n path = path.replace(/\\\\/g, \"/\").replace(/\\/+$/g, \"\").replace(/.*\\//, \"\");\n if (extname2 && extname2 !== path && path.endsWith(extname2)) {\n return path.substring(0, path.length - extname2.length);\n }\n return path;\n}\nfunction dirname(path) {\n path = path.replaceAll(/\\\\/g, \"/\");\n const sections = path.split(\"/\");\n if (sections.length <= 1) {\n return \".\";\n }\n sections.pop();\n if (sections.length === 1 && sections[0] === \"\") {\n return \"/\";\n }\n return sections.join(\"/\");\n}\nfunction extname(path) {\n const base = basename(path);\n const index = base.lastIndexOf(\".\");\n if (index > 0) {\n return base.substring(index);\n }\n return \"\";\n}\nfunction join(...args) {\n if (arguments.length < 1) {\n return \"\";\n }\n const nonEmptyArgs = args.filter((arg) => arg.length > 0);\n if (nonEmptyArgs.length < 1) {\n return \"\";\n }\n const lastArg = nonEmptyArgs[nonEmptyArgs.length - 1];\n const leadingSlash = nonEmptyArgs[0].charAt(0) === \"/\";\n const trailingSlash = lastArg.charAt(lastArg.length - 1) === \"/\";\n const sections = nonEmptyArgs.reduce((acc, section) => acc.concat(section.split(\"/\")), []);\n let first = !leadingSlash;\n const path = sections.reduce((acc, section) => {\n if (section === \"\") {\n return acc;\n }\n if (first) {\n first = false;\n return acc + section;\n }\n return acc + \"/\" + section;\n }, \"\");\n if (trailingSlash) {\n return path + \"/\";\n }\n return path;\n}\nfunction isSamePath(path1, path2) {\n const pathSections1 = (path1 || \"\").split(\"/\").filter((p) => p !== \".\");\n const pathSections2 = (path2 || \"\").split(\"/\").filter((p) => p !== \".\");\n path1 = join(...pathSections1);\n path2 = join(...pathSections2);\n return path1 === path2;\n}\nfunction normalize(path) {\n const sections = path.split(\"/\").filter((p, index, arr) => p !== \"\" || index === 0 || index === arr.length - 1).filter((p) => p !== \".\");\n const sanitizedSections = [];\n for (const section of sections) {\n const lastSection = sanitizedSections.at(-1);\n if (section === \"..\" && lastSection !== \"..\") {\n if (lastSection === void 0) {\n sanitizedSections.push(section);\n } else if (lastSection !== \"\") {\n sanitizedSections.pop();\n }\n } else {\n sanitizedSections.push(section);\n }\n }\n return sanitizedSections.join(\"/\");\n}\nexport {\n basename,\n dirname,\n encodePath,\n extname,\n isSamePath,\n join,\n normalize\n};\n//# sourceMappingURL=index.mjs.map\n","<!--\n - SPDX-FileCopyrightText: 2022 Nextcloud GmbH and Nextcloud contributors\n - SPDX-License-Identifier: AGPL-3.0-or-later\n-->\n\n<script setup lang=\"ts\">\nimport type { Folder, IFolder, INode } from '@nextcloud/files'\nimport type { IUpload } from '@nextcloud/files/upload'\nimport type { FilePickerItem, FilePickerItemGroup } from '../NcFilePicker/NcFilePicker.vue'\n\nimport { mdiClose, mdiPlus } from '@mdi/js'\nimport { openConflictPicker } from '@nextcloud/dialogs'\nimport { getUniqueName } from '@nextcloud/files'\nimport { getUploader, UploaderStatus, UploadStatus } from '@nextcloud/files/upload'\nimport { basename } from '@nextcloud/paths'\nimport { computed, onBeforeMount, onUnmounted, ref, useTemplateRef, watch } from 'vue'\nimport NcButton from '../NcButton/NcButton.vue'\nimport NcFilePicker from '../NcFilePicker/NcFilePicker.vue'\nimport NcIconSvgWrapper from '../NcIconSvgWrapper/NcIconSvgWrapper.vue'\nimport NcProgressBar from '../NcProgressBar/NcProgressBar.vue'\nimport { useFormatRelativeTime } from '../../composables/index.ts'\nimport { t } from '../../l10n.ts'\nimport { createElementId } from '../../utils/createElementId.ts'\nimport { logger } from '../../utils/logger.ts'\n\nconst props = withDefaults(defineProps<{\n\t/**\n\t * Actions to be shown in the upload picker menu.\n\t */\n\tactions?: FilePickerItem[] | FilePickerItemGroup[]\n\n\t/**\n\t * The upload destination folder\n\t */\n\tdestination: IFolder\n\n\t/**\n\t * A callback function to get the list of files present in the destination folder.\n\t */\n\tcontent: (relativePath?: string) => Promise<INode[]>\n\n\t/**\n\t * Allowed MIME types to upload\n\t */\n\taccept?: string[]\n\n\t/**\n\t * Disable the upload picker\n\t */\n\tdisabled?: boolean\n\n\t/**\n\t * The label of the upload picker button\n\t */\n\tlabel?: string\n\n\t/**\n\t * Allow uploading multiple files\n\t */\n\tmultiple?: boolean\n\n\t/**\n\t * Only show the icon without text label\n\t */\n\ticonOnly?: boolean\n\n\t/**\n\t * The variant of the menu button\n\t */\n\tvariant?: 'primary' | 'secondary' | 'tertiary'\n\n\t/**\n\t * Allow uploading directories\n\t */\n\tdirectory?: boolean\n}>(), {\n\tactions: () => [],\n\taccept: () => [],\n\tlabel: () => t('New'),\n\tvariant: 'secondary',\n})\n\nconst emit = defineEmits<{\n\t/**\n\t * Emitted when an upload has started.\n\t */\n\t'upload:started': [upload: IUpload]\n\t/**\n\t * Emitted when an upload has finished successfully.\n\t */\n\t'upload:finished': [upload: IUpload]\n\t/**\n\t * The queue has been paused, and no more uploads will be processed until resumed.\n\t */\n\tpaused: [queue: IUpload[]]\n\t/**\n\t * The queue has resumed uploading after being paused.\n\t */\n\tresumed: [queue: IUpload[]]\n\t/**\n\t * The queue has finished uploading all files.\n\t */\n\tfinished: []\n}>()\n\ndefineExpose({\n\treset,\n})\n\nconst filePickerElement = useTemplateRef('filePicker')\nconst progressTimeId = createElementId()\n\nconst uploadManager = getUploader()\nonBeforeMount(() => {\n\twindow.addEventListener('beforeunload', onBeforePageUnload)\n\tuploadManager.addEventListener('uploadFinished', onUploadFinished)\n\tuploadManager.addEventListener('uploadStarted', onUploadStarted)\n\tuploadManager.addEventListener('uploadProgress', updateEta)\n\tuploadManager.addEventListener('finished', onUploaderFinished)\n\tuploadManager.addEventListener('paused', onUploaderPaused)\n\tuploadManager.addEventListener('reset', updateUploadStatus)\n\tuploadManager.addEventListener('resumed', onUploaderResumed)\n\tupdateUploadStatus()\n})\nonUnmounted(() => {\n\twindow.removeEventListener('beforeunload', onBeforePageUnload)\n\tuploadManager.removeEventListener('uploadFinished', onUploadFinished)\n\tuploadManager.removeEventListener('uploadStarted', onUploadStarted)\n\tuploadManager.removeEventListener('uploadProgress', updateEta)\n\tuploadManager.removeEventListener('finished', onUploaderFinished)\n\tuploadManager.removeEventListener('paused', onUploaderPaused)\n\tuploadManager.removeEventListener('reset', updateUploadStatus)\n\tuploadManager.removeEventListener('resumed', onUploaderResumed)\n})\n\nwatch(() => props.destination, () => setDestination(props.destination), { immediate: true })\n\nconst isPaused = ref(uploadManager.status === UploaderStatus.PAUSED)\n/** Handle uploader paused event */\nfunction onUploaderPaused() {\n\tupdateUploadStatus()\n\temit('paused', [...uploadManager.queue])\n}\n/** Handle uploader resumed event */\nfunction onUploaderResumed() {\n\tupdateUploadStatus()\n\temit('resumed', [...uploadManager.queue])\n}\n\nconst hasFailure = ref(false)\nconst isUploading = ref(false)\nconst isAssembling = ref(false)\nconst isOnlyAssembling = ref(false)\n\n/**\n * Update the upload status flags based on the current queue\n */\nfunction updateUploadStatus() {\n\tisPaused.value = uploadManager.status === UploaderStatus.PAUSED\n\t// While paused the queue is not processed, but the queued uploads are still pending\n\tisUploading.value = uploadManager.status === UploaderStatus.UPLOADING\n\t\t|| (isPaused.value && uploadManager.queue.length > 0)\n\thasFailure.value = uploadManager.queue.some((upload: IUpload) => upload.status === UploadStatus.FAILED)\n\tisAssembling.value = uploadManager.queue.some((upload: IUpload) => upload.status === UploadStatus.ASSEMBLING)\n\t// only assembling if assembling at all AND all other uploads are already finished (or failed)\n\tisOnlyAssembling.value = isAssembling.value\n\t\t&& uploadManager.queue.every((upload: IUpload) => upload.status >= UploadStatus.ASSEMBLING)\n}\n\n/**\n * Reset the file input form\n */\nfunction reset() {\n\tfilePickerElement.value?.reset()\n}\n\n/**\n * Set the upload destination\n *\n * @param destination - The new upload destination\n */\nfunction setDestination(destination: IFolder) {\n\tif (!destination) {\n\t\tlogger.debug('Invalid destination')\n\t\treturn\n\t}\n\n\tuploadManager.destination = destination as Folder\n}\n\nconst etaProgress = ref(0)\nconst etaSpeed = ref('')\nconst etaRaw = ref<number>(Infinity)\nconst etaTimeRaw = computed(() => etaRaw.value === Infinity ? Infinity : new Date(Date.now() + etaRaw.value))\nconst etaTimeFormatted = useFormatRelativeTime(etaTimeRaw, { ignoreSeconds: true })\nconst etaTime = computed(() => etaTimeRaw.value === Infinity ? t('Estimating …') : etaTimeFormatted.value)\n/** Update the ETA and speed values */\nfunction updateEta() {\n\tetaRaw.value = uploadManager.statistics.eta === Infinity ? Infinity : (uploadManager.statistics.eta * 1000)\n\tetaSpeed.value = uploadManager.statistics.speedReadable\n\tetaProgress.value = uploadManager.statistics.progress\n}\n\n/**\n * Handle uploader finished event\n */\nfunction onUploaderFinished() {\n\tupdateUploadStatus()\n\temit('finished')\n}\n\n/**\n * Handle upload finished event\n *\n * @param event - The upload finished event\n */\nfunction onUploadFinished(event: CustomEvent<IUpload>) {\n\tupdateUploadStatus()\n\temit('upload:finished', event.detail)\n}\n\n/**\n * Handle upload started event\n *\n * @param event - The upload started event\n */\nfunction onUploadStarted(event: CustomEvent<IUpload>) {\n\tupdateUploadStatus()\n\temit('upload:started', event.detail)\n}\n\n/**\n * Start uploading\n *\n * @param files - The files to upload\n */\nasync function onPick(files: File[]) {\n\ttry {\n\t\tawait uploadManager\n\t\t\t.batchUpload('', files, { callback: handleConflicts })\n\t} catch (error) {\n\t\tlogger.debug('Error while uploading', { error })\n\t} finally {\n\t\treset()\n\t}\n}\n\n/**\n * Handle conflicts during upload\n *\n * @param nodes - The nodes that might conflict\n * @param currentPath - The path of the current directory\n */\nasync function handleConflicts(nodes: string[], currentPath: string): Promise<Record<string, string> | false> {\n\ttry {\n\t\tconst content = await props.content(currentPath)\n\t\tconst conflicts = content.filter((node) => nodes.includes(node.displayname) || nodes.includes(node.basename))\n\t\tconst uploadMapping = Object.fromEntries(nodes.map((name) => [name, name]))\n\t\tif (conflicts.length === 0) {\n\t\t\treturn uploadMapping\n\t\t}\n\n\t\t// The conflict picker requires the incoming and the existing nodes to be aligned,\n\t\t// so the existing content has to be filtered to only contain the conflicting nodes.\n\t\tconst existingNodes = content.filter((node) => nodes.includes(node.displayname) || nodes.includes(node.basename))\n\t\tconst result = await openConflictPicker(basename(currentPath), conflicts, existingNodes, { recursive: props.directory })\n\t\tif (result) {\n\t\t\tconst usedNames = content.map((node) => node.basename)\n\t\t\tfor (const node of conflicts) {\n\t\t\t\tif ((result.skipped as unknown as INode[]).some((skipped) => skipped.basename === node.basename)) {\n\t\t\t\t\tdelete uploadMapping[node.basename]\n\t\t\t\t} else if ((result.renamed as unknown as INode[]).some((renamed) => renamed.basename === node.basename)) {\n\t\t\t\t\tconst newName = getUniqueName(basename(node.basename), usedNames)\n\t\t\t\t\tuploadMapping[node.basename] = newName\n\t\t\t\t\tusedNames.push(newName)\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn uploadMapping\n\t\t}\n\t} catch (error) {\n\t\tlogger.error('Error during conflict resolution - skipping upload', { error })\n\t}\n\treturn false\n}\n\n/**\n * Cancel ongoing queue\n */\nfunction onCancel() {\n\tuploadManager.reset()\n\treset()\n}\n\n/**\n * Handle page unload.\n * Block the unload if there are ongoing uploads.\n *\n * @param event - The event\n */\nfunction onBeforePageUnload(event: BeforeUnloadEvent) {\n\tif (uploadManager.queue.length > 0) {\n\t\tevent.preventDefault()\n\t\tevent.returnValue = ''\n\t}\n}\n</script>\n\n<template>\n\t<NcFilePicker\n\t\tref=\"filePicker\"\n\t\t:accept\n\t\t:actionCaption=\"actions.length > 0 || directory ? t('Upload from device') : undefined\"\n\t\t:actions\n\t\t:directory\n\t\t:disabled\n\t\t:iconOnly\n\t\t:label\n\t\t:multiple\n\t\t:variant\n\t\t@pick=\"onPick\">\n\t\t<template #icon>\n\t\t\t<NcIconSvgWrapper :path=\"mdiPlus\" />\n\t\t</template>\n\n\t\t<!-- Progressbar and status -->\n\t\t<div\n\t\t\tv-show=\"isUploading\"\n\t\t\t:class=\"[$style.uploadPicker_progress, {\n\t\t\t\t[$style.uploadPicker_progress__uploading]: isUploading,\n\t\t\t\t[$style.uploadPicker_progress__paused]: isPaused,\n\t\t\t}]\">\n\t\t\t<NcProgressBar\n\t\t\t\t:ariaLabel=\"t('Upload progress')\"\n\t\t\t\t:ariaDescribedby=\"progressTimeId\"\n\t\t\t\t:error=\"hasFailure\"\n\t\t\t\t:value=\"etaProgress\"\n\t\t\t\tsize=\"medium\" />\n\t\t\t<p :id=\"progressTimeId\" :class=\"$style.uploadPicker_progressLabel\">\n\t\t\t\t<span v-if=\"isPaused\">\n\t\t\t\t\t{{ t('paused') /* TRANSLATORS: State of the current upload - it is paused */ }}\n\t\t\t\t</span>\n\t\t\t\t<span v-else-if=\"isOnlyAssembling\">\n\t\t\t\t\t{{ t('assembling') /* TRANSLATORS: State of the current upload - chunks of the uploaded files are being assembled into the final file */ }}\n\t\t\t\t</span>\n\t\t\t\t<span v-else :title=\"`${etaTime} (${etaSpeed})`\">\n\t\t\t\t\t{{ etaTime }}\n\t\t\t\t\t<!-- the speed is included in the tooltip / title so we only show it in the text content if there is enough space (not showing \"a few seconds left\") -->\n\t\t\t\t\t<span v-if=\"etaSpeed && etaRaw > 31000\" :class=\"$style.uploadPicker_progressLabelSpeed\">\n\t\t\t\t\t\t({{ etaSpeed }})\n\t\t\t\t\t</span>\n\t\t\t\t</span>\n\t\t\t</p>\n\t\t</div>\n\n\t\t<!-- Cancel upload button -->\n\t\t<NcButton\n\t\t\tv-if=\"isUploading && !isOnlyAssembling\"\n\t\t\t:class=\"$style.uploadPicker_cancelButton\"\n\t\t\t:aria-label=\"t('Cancel uploads')\"\n\t\t\tvariant=\"tertiary\"\n\t\t\t@click=\"onCancel\">\n\t\t\t<template #icon>\n\t\t\t\t<NcIconSvgWrapper :path=\"mdiClose\" />\n\t\t\t</template>\n\t\t</NcButton>\n\t</NcFilePicker>\n</template>\n\n<style module>\n.uploadPicker_progress {\n\t--upload-picker-progress-width: 200px;\n\twidth: var(--upload-picker-progress-width);\n\t/* Animate show/hide */\n\tmax-width: 0;\n\ttransition: max-width var(--animation-quick) ease-in-out;\n\t/* Align progress/text separation with the middle */\n\tmargin-top: 8px;\n}\n\n.uploadPicker_progress__uploading {\n\tmax-width: var(--upload-picker-progress-width);\n\n\t/* Visually more pleasing spacing */\n\tmargin-inline: 8px 20px;\n}\n\n.uploadPicker_progress__paused {\n\tanimation: breathing 3s ease-out infinite normal;\n}\n\n.uploadPicker_progressLabel {\n\toverflow: hidden;\n\twhite-space: nowrap;\n\ttext-overflow: ellipsis;\n}\n\n.uploadPicker_progressLabelSpeed{\n\tcolor: var(--color-text-maxcontrast);\n}\n\n@keyframes breathing {\n\t0% {\n\t\topacity: .5;\n\t}\n\t25% {\n\t\topacity: 1;\n\t}\n\t60% {\n\t\topacity: .5;\n\t}\n\t100% {\n\t\topacity: .5;\n\t}\n}\n</style>\n\n<docs>\n## Requirements\n\nTo use the `NcUploadPicker` component, you need to install the following packages:\n- `@nextcloud/dialogs`\n- `@nextcloud/files`\n\n## Usage\n\nThe upload picker lets users pick files – or whole directories – from their device and uploads them\nto a folder on the Nextcloud server, using the shared uploader of `@nextcloud/files/upload`.\nWhile uploading it shows the progress, the estimated remaining time and a button to cancel all queued uploads.\n\nTwo props are required to set it up:\n\n- `destination`: The folder to upload into, as an `IFolder` of `@nextcloud/files`.\n- `content`: A callback that resolves with the nodes that already exist in a folder that is uploaded into.\n It is called with the path relative to the `destination` – the empty string for the destination itself –\n and is used to detect upload conflicts. If there are conflicts, the user is asked to skip or rename\n the conflicting files.\n\n### Exposed methods\n\n- `function reset(): void`\n Reset the internal state of the picker, e.g. to clear the current selection.\n\n**Note**: All upload pickers share the global uploader instance of `getUploader()`,\nincluding its queue and its destination. Because of that only a single upload picker\nshould be mounted at a time – in the examples below the picker that was mounted last defines the destination.\n\n**Note about the examples**: There is no Nextcloud server behind the styleguide,\nso the examples use a fake WebDAV server that simulates slow – but always successful – uploads.\nUploading a file called `Photo.jpg` or `Notes.md` triggers the conflict dialog,\nfiles larger than 10 MiB are uploaded in chunks.\n\n### Basic usage\n\n```vue\n<template>\n\t<div>\n\t\t<NcUploadPicker\n\t\t\t:content=\"fetchContent\"\n\t\t\t:destination=\"destination\"\n\t\t\tmultiple\n\t\t\t@finished=\"log('finished')\"\n\t\t\t@upload:finished=\"log('upload:finished', $event)\"\n\t\t\t@upload:started=\"log('upload:started', $event)\" />\n\n\t\t<NcNoteCard v-if=\"events.length === 0\" type=\"info\">\n\t\t\tPick some files to upload them to <code>{{ destination.path }}</code>.\n\t\t</NcNoteCard>\n\t\t<ul v-else>\n\t\t\t<li v-for=\"(event, index) in events\" :key=\"index\">\n\t\t\t\t{{ event }}\n\t\t\t</li>\n\t\t</ul>\n\t</div>\n</template>\n<script>\nimport { File as NcFile, Folder, Permission } from '@nextcloud/files'\nimport { generateRemoteUrl } from '@nextcloud/router'\nimport { ref } from 'vue'\n\n// The folder the picked files are uploaded into\nconst destination = new Folder({\n\tid: 42,\n\towner: 'admin',\n\tpermissions: Permission.ALL,\n\troot: '/files/admin',\n\tsource: generateRemoteUrl('dav/files/admin/Uploads'),\n})\n\n// The nodes that already exist in the destination, in a real app this is the result of a PROPFIND\nconst existingNodes = ['Photo.jpg', 'Notes.md'].map((name) => new NcFile({\n\tmime: 'application/octet-stream',\n\towner: 'admin',\n\troot: '/files/admin',\n\tsource: `${destination.source}/${name}`,\n}))\n\nexport default {\n\tsetup() {\n\t\tconst events = ref([])\n\n\t\treturn {\n\t\t\tdestination,\n\t\t\tevents,\n\n\t\t\t// Provide the content of the folder that is uploaded into to allow detecting conflicts\n\t\t\tasync fetchContent(relativePath) {\n\t\t\t\t// Folders created by the upload itself are empty, so they never conflict\n\t\t\t\treturn relativePath ? [] : existingNodes\n\t\t\t},\n\n\t\t\tlog(name, upload) {\n\t\t\t\t// One `upload:*` event is emitted for the destination itself - shown as `/` - and one per picked file\n\t\t\t\tconst path = upload && decodeURIComponent(upload.source.slice(destination.source.length))\n\t\t\t\tevents.value.unshift(upload ? `${name}: ${path}` : name)\n\t\t\t},\n\t\t}\n\t},\n}\n</script>\n```\n\n### Directories and custom actions\n\nSetting `directory` adds an entry to upload a whole directory tree, which is recreated in the destination.\nAdditional entries – like the \"New folder\" action of the Files app – can be added using the `actions` prop,\neither as a flat list or grouped with a caption.\nThe `accept` prop restricts the file types that can be picked.\n\n```vue\n<template>\n\t<div>\n\t\t<NcUploadPicker\n\t\t\t:accept=\"['image/jpeg', 'image/png']\"\n\t\t\t:actions=\"actions\"\n\t\t\t:content=\"fetchContent\"\n\t\t\t:destination=\"destination\"\n\t\t\tdirectory\n\t\t\tlabel=\"Add media\"\n\t\t\tmultiple\n\t\t\tvariant=\"primary\" />\n\n\t\t<NcNoteCard v-if=\"lastAction\" type=\"success\">\n\t\t\t{{ lastAction }}\n\t\t</NcNoteCard>\n\t</div>\n</template>\n<script>\nimport svgFolderPlus from '@mdi/svg/svg/folder-plus-outline.svg?raw'\nimport svgLink from '@mdi/svg/svg/link-plus.svg?raw'\nimport { Folder, Permission } from '@nextcloud/files'\nimport { generateRemoteUrl } from '@nextcloud/router'\nimport { ref } from 'vue'\n\nconst destination = new Folder({\n\tid: 42,\n\towner: 'admin',\n\tpermissions: Permission.ALL,\n\troot: '/files/admin',\n\tsource: generateRemoteUrl('dav/files/admin/Uploads'),\n})\n\nexport default {\n\tsetup() {\n\t\tconst lastAction = ref('')\n\n\t\treturn {\n\t\t\tdestination,\n\t\t\tlastAction,\n\n\t\t\t// The destination is empty, so no upload will ever conflict\n\t\t\tasync fetchContent() {\n\t\t\t\treturn []\n\t\t\t},\n\n\t\t\tactions: [\n\t\t\t\t{\n\t\t\t\t\tcaption: 'Create new',\n\t\t\t\t\tactions: [\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tlabel: 'New folder',\n\t\t\t\t\t\t\ticonSvg: svgFolderPlus,\n\t\t\t\t\t\t\tonClick: () => {\n\t\t\t\t\t\t\t\tlastAction.value = 'Clicked \"New folder\"'\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tlabel: 'Add from link',\n\t\t\t\t\t\t\ticonSvg: svgLink,\n\t\t\t\t\t\t\tonClick: () => {\n\t\t\t\t\t\t\t\tlastAction.value = 'Clicked \"Add from link\"'\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t},\n\t\t\t\t\t],\n\t\t\t\t},\n\t\t\t],\n\t\t}\n\t},\n}\n</script>\n```\n\n### Controlling the uploader\n\nThe uploader itself is not owned by the picker, so it can also be controlled directly.\nThis is useful to pause the queue – picked files are queued but not uploaded until it is resumed –\nor to change the destination while the picker is mounted.\n\n```vue\n<template>\n\t<div class=\"uploader-controls\">\n\t\t<NcUploadPicker\n\t\t\tref=\"picker\"\n\t\t\t:content=\"fetchContent\"\n\t\t\t:destination=\"destination\"\n\t\t\tmultiple />\n\n\t\t<NcButton @click=\"uploader.pause()\">\n\t\t\tPause\n\t\t</NcButton>\n\t\t<NcButton @click=\"uploader.start()\">\n\t\t\tResume\n\t\t</NcButton>\n\t</div>\n</template>\n<script>\nimport { Folder, Permission } from '@nextcloud/files'\nimport { getUploader } from '@nextcloud/files/upload'\nimport { generateRemoteUrl } from '@nextcloud/router'\n\n/**\n * Create a folder for the given path within the users files.\n *\n * @param {string} path - Path of the folder, e.g. `/Documents`\n */\nfunction createFolder(path) {\n\treturn new Folder({\n\t\tid: 42,\n\t\towner: 'admin',\n\t\tpermissions: Permission.ALL,\n\t\troot: '/files/admin',\n\t\tsource: generateRemoteUrl(`dav/files/admin${path}`),\n\t})\n}\n\nexport default {\n\tsetup() {\n\t\treturn {\n\t\t\tdestination: createFolder('/Uploads'),\n\t\t\totherDestination: createFolder('/Documents'),\n\t\t\tuploader: getUploader(),\n\n\t\t\tasync fetchContent() {\n\t\t\t\treturn []\n\t\t\t},\n\t\t}\n\t},\n}\n</script>\n<style scoped>\n.uploader-controls {\n\tdisplay: flex;\n\tflex-wrap: wrap;\n\tgap: 8px;\n\talign-items: center;\n}\n</style>\n```\n</docs>\n"],"names":["_createBlock","_unref","_createVNode","_createElementVNode","_normalizeClass","$style","_openBlock","_createElementBlock","_createTextVNode","_toDisplayString"],"mappings":"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","x_google_ignoreList":[0]}