Newer
Older
navi-1 / webclient / src / composables / useFileUpload.js
import { ref } from 'vue'
import { useChatStore } from '@/stores/chat.js'
import { uploadFile } from '@/api/index.js'

export function useFileUpload() {
  const chat = useChatStore()
  const isDragging = ref(false)
  const uploadProgress = ref(0)
  const uploading = ref(false)

  // ─── Image handling ──────────────────────────────────────────────────────

  function addImageFile(file) {
    return new Promise((resolve, reject) => {
      const reader = new FileReader()
      reader.onload = (e) => {
        chat.pendingImages.push(e.target.result)
        resolve()
      }
      reader.onerror = reject
      reader.readAsDataURL(file)
    })
  }

  function removeImage(index) {
    chat.pendingImages.splice(index, 1)
  }

  // ─── File handling ───────────────────────────────────────────────────────

  async function addFile(file) {
    if (!chat.currentId) return
    uploading.value = true
    uploadProgress.value = 0
    try {
      const result = await uploadFile(chat.currentId, file)
      chat.pendingFiles.push(result)
    } finally {
      uploading.value = false
      uploadProgress.value = 0
    }
  }

  function removeFile(index) {
    chat.pendingFiles.splice(index, 1)
  }

  // ─── File picker ─────────────────────────────────────────────────────────

  async function handleFileInput(event) {
    const files = Array.from(event.target.files ?? [])
    event.target.value = ''
    await processFiles(files)
  }

  // ─── Image paste ─────────────────────────────────────────────────────────

  async function handlePaste(event) {
    const items = Array.from(event.clipboardData?.items ?? [])
    const imageItems = items.filter(i => i.type.startsWith('image/'))
    if (!imageItems.length) return
    event.preventDefault()
    for (const item of imageItems) {
      const file = item.getAsFile()
      if (file) await addImageFile(file)
    }
  }

  // ─── Drag and drop ───────────────────────────────────────────────────────

  function handleDragEnter(event) {
    event.preventDefault()
    if (hasFiles(event)) isDragging.value = true
  }

  function handleDragOver(event) {
    event.preventDefault()
  }

  function handleDragLeave(event) {
    // Only clear if leaving the drop zone entirely
    if (!event.currentTarget.contains(event.relatedTarget)) {
      isDragging.value = false
    }
  }

  async function handleDrop(event) {
    event.preventDefault()
    isDragging.value = false
    const files = Array.from(event.dataTransfer?.files ?? [])
    await processFiles(files)
  }

  // ─── Shared processing ───────────────────────────────────────────────────

  async function processFiles(files) {
    for (const file of files) {
      if (file.type.startsWith('image/')) {
        await addImageFile(file)
      } else {
        await addFile(file)
      }
    }
  }

  function clearPending() {
    chat.pendingImages = []
    chat.pendingFiles = []
  }

  function hasFiles(event) {
    return Array.from(event.dataTransfer?.types ?? []).includes('Files')
  }

  return {
    isDragging,
    uploading,
    uploadProgress,
    handleFileInput,
    handlePaste,
    handleDragEnter,
    handleDragOver,
    handleDragLeave,
    handleDrop,
    removeImage,
    removeFile,
    clearPending
  }
}