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 = async (e) => {
        try {
          const resized = await resizeImage(e.target.result, 1024, 0.9)
          chat.pendingImages.push(resized)
          resolve()
        } catch (err) {
          reject(err)
        }
      }
      reader.onerror = reject
      reader.readAsDataURL(file)
    })
  }

  function resizeImage(dataUrl, maxDim = 1024, quality = 0.9) {
    return new Promise((resolve, reject) => {
      const img = new Image()
      img.onload = () => {
        let w = img.width
        let h = img.height
        if (w > maxDim || h > maxDim) {
          const ratio = maxDim / Math.max(w, h)
          w = Math.round(w * ratio)
          h = Math.round(h * ratio)
        }
        const canvas = document.createElement('canvas')
        canvas.width = w
        canvas.height = h
        const ctx = canvas.getContext('2d')
        ctx.drawImage(img, 0, 0, w, h)
        canvas.toBlob((blob) => {
          if (!blob) { reject(new Error('Canvas toBlob failed')); return }
          const r = new FileReader()
          r.onload = (ev) => resolve(ev.target.result)
          r.onerror = reject
          r.readAsDataURL(blob)
        }, 'image/jpeg', quality)
      }
      img.onerror = reject
      img.src = dataUrl
    })
  }

  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)
      chat.fetchFiles(chat.currentId)
    } 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
  }
}