@rezaser/nexus-filepicker
v0.1.0
Published
Vue 3 library for a Nextcloud-style attachment picker, file and folder uploads via WebDAV, and resumable downloads (e.g. for chat or meetings).
Readme
@nexus/nexus-filepicker
Vue 3 library for a Nextcloud-style attachment picker, file and folder uploads via WebDAV, and resumable downloads (e.g. for chat or meetings).
Installation
npm install @nexus/nexus-filepickerPeer dependency: Vue 3 (^3.5.0).
When using the attachment picker UI, import the package styles once (e.g. in your app entry or root component):
import '@nexus/nexus-filepicker/dist/nexus-filepicker.css'Quick start
Register the attachment picker and render it with your DAV config:
import { registerAttachmentPicker, renderAttachment } from '@nexus/nexus-filepicker'
import '@nexus/nexus-filepicker/dist/nexus-filepicker.css'
registerAttachmentPicker()
renderAttachment({
baseUrl: 'https://your-nextcloud.example.com',
remoteURL: 'https://dav.example.com/remote.php/dav',
rootPath: '/files/User',
token: 'your-auth-token',
}, document.getElementById('app'))Or use the upload API only (no UI):
import { uploadOne } from '@nexus/nexus-filepicker'
const config = {
remoteURL: 'https://dav.example.com/remote.php/dav',
rootPath: '/files/User',
token: 'your-auth-token',
context: 'context application',
contextId: 'id of chat-calendar',
contextName: 'name of group',
owner: 'your-user-id(sub)',
}
await uploadOne(config, 'folder/file.txt', file)Example: React
The picker is a Vue-based custom element; you can use it from React by rendering into a DOM node and using the imperative upload/download APIs. Config can include context, contextName, contextId, owner, and multiSelect for context-aware uploads.
import { useState, useRef, useEffect } from 'react'
import {
renderAttachment,
getUploaderForConfig,
uploadOne,
UploadStatus,
createDownloadController,
} from '@nexus/nexus-filepicker'
import '@nexus/nexus-filepicker/dist/nexus-filepicker.css'
const config = {
remoteURL: 'https://localhost/remote.php/dav',
rootPath: '/files/your-user-id',
token: 'your-jwt-or-token',
context: 'calendar',
contextId: '123',
contextName: 'test',
multiSelect: true,
owner: 'your-user-id',
}
function App() {
const containerRef = useRef(null)
const attachmentElRef = useRef(null)
const uploaderRef = useRef(null)
if (!uploaderRef.current) {
uploaderRef.current = getUploaderForConfig(config)
}
useEffect(() => {
const container = containerRef.current
if (!container) return
if (attachmentElRef.current?.parentNode === container) {
container.removeChild(attachmentElRef.current)
}
const el = renderAttachment({ ...config, baseUrl: 'https://your-nextcloud.example.com' }, container)
attachmentElRef.current = el
return () => {
if (attachmentElRef.current?.parentNode === container) {
container.removeChild(attachmentElRef.current)
}
}
}, [])
// Poll uploader queue for progress
const [uploads, setUploads] = useState([])
useEffect(() => {
const uploader = uploaderRef.current
if (!uploader) return
const intervalId = setInterval(() => {
const queue = uploader.queue || []
setUploads(queue.map((u) => ({
id: u.source || u.file?.name,
name: u.file?.name,
percent: u.size ? Math.round((u.uploaded / u.size) * 100) : 0,
status: u.status,
uploadRef: u,
})))
}, 500)
return () => clearInterval(intervalId)
}, [])
// Notify when an upload finishes
useEffect(() => {
const notifier = (upload) => {
if (upload.status === UploadStatus.FINISHED) {
// e.g. setUploadedItems((prev) => [...prev, upload])
}
}
uploaderRef.current?.addNotifier(notifier)
}, [])
const handleUpload = () => {
if (!file) return
uploadOne(config, file.name, file)
}
// Download with progress, pause, resume, cancel
const onDownload = async (link, token) => {
const ctrl = createDownloadController(link, token, {
onProgress: ({ percent, loaded, total, paused }) => {
// Update your UI: percent, loaded, total, paused
},
})
try {
await ctrl.start()
} catch (err) {
if (err?.name !== 'AbortError') console.error(err)
}
}
// Use ctrl.pause(), ctrl.resume(), ctrl.cancel() as needed
return (
<>
<div ref={containerRef} />
{/* Upload list (uploads + status), uploaded items table, download buttons */}
</>
)
}- Attachment picker: Mount once in
useEffectwith a ref; clean up on unmount. Pass the same config plusbaseUrlintorenderAttachment. - Upload progress: Read
uploader.queue(e.g. on an interval); each item hasstatus(UploadStatus),uploaded,size,file, and.cancel(). - Finished uploads:
uploader.addNotifier(notifier); in the callback, checkupload.status === UploadStatus.FINISHED. - Download:
createDownloadController(link, token, { onProgress })thenctrl.start(). Usectrl.pause(),ctrl.resume(),ctrl.cancel()for lifecycle;ctrl.statehas{ started, paused, cancelled, loaded, total, rangeSupported }.
API reference
Attachment picker
registerAttachmentPicker(): void— Registers the<attachment-picker>custom element for the current window. Idempotent.renderAttachment(props, container?): HTMLElement— Renders the attachment picker with the given props intocontainer(defaultdocument.body). Registers the element if needed. Returns the created element.Type
AttachmentPickerProps— Required:baseUrl,remoteURL,rootPath,token. Optional:mode?: 'upload' | 'picker',multiSelect?: boolean; labelstitle,filePickerText,uploadFileText,uploadFolderText,modalTitle,pickerButtonLabel; contextcontext,contextName,contextId,owner;dropdownPlacement?: 'top' | 'bottom'. See the type definition for the full list.
Upload
getUploaderForConfig(config: NexusFilePickerConfig): Uploader— Returns the shared uploader instance for the given DAV config (cached per config). Use for direct control (queue, pause, progress).uploadOne(config, destinationPath, file): UploadOneResult— Uploads a single file todestinationPath(relative to config root). Returns a cancelable promise resolving to theUpload.uploadMultiple(config, destinationPath, files): UploadBatchResult— Uploads multiple files into one folder; no directory structure preserved. Returns a cancelable promise ofUpload[].uploadBatch(config, destinationPath, filesAndDirs, conflictCallback?): UploadBatchResult— Uploads files and/or directories (e.g. from<input webkitdirectory>), preserving structure. OptionalconflictCallback(nodes, currentPath)for rename/skip. Returns a cancelable promise ofUpload[].UploadStatus— Enum (re-exported from nextcloud-upload). Values:INITIALIZED,UPLOADING,ASSEMBLING,FINISHED,CANCELLED,FAILED. Used onUpload.status.Types:
NexusFilePickerConfig— base DAV config plus optional context.NexusFilePickerUploadConfig— extends it withowner; use when upload behavior depends on owner/context.
Download
createDownloadController(link, token, options?): { start, pause, resume, cancel, state }— Creates a controller for resumable/cancelable download. Options:filename?: string,onProgress?: (p: DownloadProgress) => void,eventTarget?: EventTarget. Returned object:start()to begin,pause()/resume()when range is supported,cancel()to abort;stateholds{ started, paused, cancelled, loaded, total, rangeSupported }. Progress and lifecycle events can be observed viaonProgressoreventTarget(e.g.download:progress,download:complete).
HTTP / context
postContext(baseUrl, token, context, contextId, fileId): Promise<PostDataModel | null>— POSTs file context to the Spreed/context API (e.g. for UPLOAD_FINISHED). Returns response data ornullif nofileId.getDirectDownloadLink(baseUrl, token): Promise<Object>— GETs the direct download link JSON frombaseUrl?token=....
Configuration types
NexusFilePickerConfig —
remoteURL,rootPath,token; optionalcontext,contextName,contextId. Used for both picker and upload.NexusFilePickerUploadConfig — Extends
NexusFilePickerConfigwith optionalowner. Use when upload/context logic depends on owner.
When context and contextName are set, uploads and the picker root use rootPath/context/contextName as the DAV base.
Build / development
Build:
npm run build— cleans and builds; output is indist/(ESM, UMD, and TypeScript types).Dev:
npm run dev— local development server.
License
See project repository for license information.
