/** * upload.js — Загрузка и импорт фотографий через drag-and-drop или file picker */ import { apiFetch } from "./api.js"; import { getState, setBusy } from "./state.js"; import { setJobStatus, isHeicFile, ACCEPTED_TYPES, MAX_FILE_SIZE } from "./ui.js"; import { refreshHistory } from "./history-tree.js"; export async function importPhoto(file) { const form = new FormData(); form.append("photo", file, file.name); const data = await apiFetch("/api/history/import", { method: "POST", body: form, cache: "no-store", }); return data.version; } export async function handleFiles(fileList) { const files = Array.from(fileList || []).filter((file) => { const fileType = String(file.type || "").toLowerCase(); if (!ACCEPTED_TYPES.has(fileType) && !isHeicFile(file)) { setJobStatus("error", "Поддерживаются только фотографии JPEG, PNG, WebP или HEIC."); return false; } if (file.size > MAX_FILE_SIZE) { setJobStatus("error", "Файл слишком большой. Максимальный размер — 25 МБ."); return false; } return true; }); if (files.length === 0) return; setBusy(true); for (let i = 0; i < files.length; i++) { const file = files[i]; setJobStatus( "progress", files.length > 1 ? `Загрузка ${i + 1} из ${files.length}…` : "Загружаем фотографию в историю…" ); try { await importPhoto(file); } catch (error) { setBusy(false); setJobStatus( "error", error instanceof Error ? error.message : "Не удалось загрузить фотографию." ); return; } } await refreshHistory(true); setJobStatus("idle", ""); setBusy(false); } export function initUpload() { const dropZone = document.querySelector("#drop-zone"); const photoInput = document.querySelector("#photo-input"); if (dropZone && photoInput) { dropZone.addEventListener("click", () => { const state = getState(); if (!state.isBusy) { photoInput.click(); } }); dropZone.addEventListener("keydown", (event) => { if (event.key === "Enter" || event.key === " ") { event.preventDefault(); const state = getState(); if (!state.isBusy) { photoInput.click(); } } }); photoInput.addEventListener("change", (event) => { handleFiles(event.target.files); photoInput.value = ""; }); const preventDefaults = (e) => { e.preventDefault(); e.stopPropagation(); }; ["dragenter", "dragover", "dragleave", "drop"].forEach((evt) => { dropZone.addEventListener(evt, preventDefaults, false); document.body.addEventListener(evt, preventDefaults, false); }); ["dragenter", "dragover"].forEach((evt) => { dropZone.addEventListener(evt, () => dropZone.classList.add("dragover"), false); }); ["dragleave", "drop"].forEach((evt) => { dropZone.addEventListener(evt, () => dropZone.classList.remove("dragover"), false); }); dropZone.addEventListener("drop", (event) => { const state = getState(); if (!state.isBusy && event.dataTransfer?.files) { handleFiles(event.dataTransfer.files); } }); } }