Files
rose-ash/shared/editor/src/useFileUpload.js
giles f42042ccb7
All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 1m5s
Monorepo: consolidate 7 repos into one
Combines shared, blog, market, cart, events, federation, and account
into a single repository. Eliminates submodule sync, sibling model
copying at build time, and per-app CI orchestration.

Changes:
- Remove per-app .git, .gitmodules, .gitea, submodule shared/ dirs
- Remove stale sibling model copies from each app
- Update all 6 Dockerfiles for monorepo build context (root = .)
- Add build directives to docker-compose.yml
- Add single .gitea/workflows/ci.yml with change detection
- Add .dockerignore for monorepo build context
- Create __init__.py for federation and account (cross-app imports)
2026-02-24 19:44:17 +00:00

100 lines
3.6 KiB
JavaScript

import { useState, useCallback, useRef } from "react";
/**
* Koenig expects `fileUploader.useFileUpload(type)` — a React hook it
* calls internally for each card type ("image", "audio", "file", etc.).
*
* `makeFileUploader(csrfToken, uploadUrls)` returns the object Koenig wants:
* { useFileUpload: (type) => { upload, progress, isLoading, errors, filesNumber } }
*
* `uploadUrls` is an object: { image, media, file }
* For backwards compat, a plain string is treated as the image URL.
*/
const URL_KEY_MAP = {
image: { urlKey: "image", responseKey: "images" },
audio: { urlKey: "media", responseKey: "media" },
video: { urlKey: "media", responseKey: "media" },
mediaThumbnail: { urlKey: "image", responseKey: "images" },
file: { urlKey: "file", responseKey: "files" },
};
export default function makeFileUploader(csrfToken, uploadUrls) {
// Normalise: string → object with all keys pointing to same URL
const urls =
typeof uploadUrls === "string"
? { image: uploadUrls, media: uploadUrls, file: uploadUrls }
: uploadUrls || {};
return {
fileTypes: {
image: { mimeTypes: ['image/jpeg', 'image/png', 'image/gif', 'image/webp', 'image/svg+xml'] },
audio: { mimeTypes: ['audio/mpeg', 'audio/ogg', 'audio/wav', 'audio/mp4', 'audio/aac'] },
video: { mimeTypes: ['video/mp4', 'video/webm', 'video/ogg'] },
mediaThumbnail: { mimeTypes: ['image/jpeg', 'image/png', 'image/gif', 'image/webp'] },
file: { mimeTypes: [] },
},
useFileUpload(type) {
const mapping = URL_KEY_MAP[type] || URL_KEY_MAP.image;
const [progress, setProgress] = useState(0);
const [isLoading, setIsLoading] = useState(false);
const [errors, setErrors] = useState([]);
const [filesNumber, setFilesNumber] = useState(0);
const csrfRef = useRef(csrfToken);
const urlRef = useRef(urls[mapping.urlKey] || urls.image || "/editor-api/images/upload/");
const responseKeyRef = useRef(mapping.responseKey);
const upload = useCallback(async (files) => {
const fileList = Array.from(files);
setFilesNumber(fileList.length);
setIsLoading(true);
setErrors([]);
setProgress(0);
const results = [];
for (let i = 0; i < fileList.length; i++) {
const file = fileList[i];
const formData = new FormData();
formData.append("file", file);
try {
const resp = await fetch(urlRef.current, {
method: "POST",
body: formData,
headers: {
"X-CSRFToken": csrfRef.current || "",
},
});
if (!resp.ok) {
const err = await resp.json().catch(() => ({}));
const msg =
err.errors?.[0]?.message || `Upload failed (${resp.status})`;
setErrors((prev) => [
...prev,
{ message: msg, fileName: file.name },
]);
continue;
}
const data = await resp.json();
const fileUrl = data[responseKeyRef.current]?.[0]?.url;
if (fileUrl) {
results.push({ url: fileUrl, fileName: file.name });
}
} catch (e) {
setErrors((prev) => [
...prev,
{ message: e.message, fileName: file.name },
]);
}
setProgress(Math.round(((i + 1) / fileList.length) * 100));
}
setIsLoading(false);
return results;
}, []);
return { upload, progress, isLoading, errors, filesNumber };
},
};
}