lms/bootstrap/ssr/assets/chunked-uploader-input-CZfv7yqS.js
2025-12-15 12:26:23 +01:00

314 lines
11 KiB
JavaScript

import { jsxs, jsx } from "react/jsx-runtime";
import { B as Button } from "./button-CdJZJLGw.js";
import { I as Input } from "./input-BsvJqbcd.js";
import axios from "axios";
import { CheckCircle, Loader2 } from "lucide-react";
import { useState, useRef, useEffect } from "react";
import { toast } from "sonner";
import { I as InputError } from "./input-error-D1JIzedA.js";
const ChunkedUploaderInput = ({
storage,
isSubmit,
courseId,
sectionId,
filetype,
delayUpload = false,
onError,
onCancelUpload,
onFileSelected,
onFileUploaded
}) => {
const [file, setFile] = useState(null);
const [uploadId, setUploadId] = useState(null);
const [errorMessage, setErrorMessage] = useState("");
const [uploadProgress, setUploadProgress] = useState(0);
const [uploadStatus, setUploadStatus] = useState("idle");
const fileInputRef = useRef(null);
const abortControllerRef = useRef(null);
const maxFileSize = 1024 * 1024 * 1024;
const chunkSize = 5 * 1024 * 1024;
useEffect(() => {
var _a, _b;
axios.defaults.withCredentials = true;
const token = (_a = document.cookie.split("; ").find((row) => row.startsWith("XSRF-TOKEN="))) == null ? void 0 : _a.split("=")[1];
if (token) {
axios.defaults.headers.common["X-XSRF-TOKEN"] = decodeURIComponent(token);
} else {
const metaToken = (_b = document.querySelector('meta[name="csrf-token"]')) == null ? void 0 : _b.getAttribute("content");
if (metaToken) {
axios.defaults.headers.common["X-CSRF-TOKEN"] = metaToken;
}
}
}, []);
useEffect(() => {
if (isSubmit) {
initiateUpload();
}
}, [isSubmit]);
const handleFileChange = (event) => {
if (event.target.files && event.target.files.length > 0) {
const selectedFile = event.target.files[0];
if (selectedFile.size > maxFileSize) {
setErrorMessage(`File is too large. Maximum file size is ${maxFileSize / (1024 * 1024)} MB`);
return;
}
setFile(selectedFile);
setErrorMessage("");
setUploadStatus("idle");
setUploadProgress(0);
if (delayUpload) {
if (onFileSelected) {
onFileSelected(selectedFile);
}
} else {
initiateUpload();
}
}
};
const initiateUpload = async () => {
var _a, _b, _c, _d;
if (!file) return;
setUploadStatus("initializing");
setErrorMessage("");
try {
const totalChunks = Math.ceil(file.size / chunkSize);
const response = await axios.post(
"/dashboard/uploads/chunked/initialize",
{
storage,
filename: file.name,
mimetype: file.type,
filesize: (file.size || 0) / 1024,
filetype,
total_chunks: totalChunks,
course_id: courseId,
course_section_id: sectionId
},
{
timeout: 12e4
// 2 minutes timeout for initialization request
}
);
if (response.data.success) {
setUploadId(response.data.upload_id);
await uploadChunks(response.data.upload_id, totalChunks);
} else {
throw new Error(response.data.message || "Failed to initialize upload");
}
} catch (error) {
setUploadStatus("error");
setErrorMessage(((_b = (_a = error.response) == null ? void 0 : _a.data) == null ? void 0 : _b.message) || error.message || "Failed to initialize upload");
if (onError) onError(((_d = (_c = error.response) == null ? void 0 : _c.data) == null ? void 0 : _d.message) || error.message || "Failed to initialize upload");
}
};
const uploadChunks = async (uploadId2, totalChunks) => {
var _a, _b, _c, _d;
if (!file) return;
setUploadStatus("uploading");
abortControllerRef.current = new AbortController();
const signal = abortControllerRef.current.signal;
try {
const uploadedParts = [];
let uploadedChunks = 0;
for (let chunkIndex = 0; chunkIndex < totalChunks && !signal.aborted; chunkIndex++) {
const start = chunkIndex * chunkSize;
const end = Math.min(start + chunkSize, file.size);
const chunk = file.slice(start, end);
const reader = new FileReader();
const readChunk = new Promise((resolve, reject) => {
reader.onloadend = () => {
const base64data2 = reader.result;
resolve(base64data2);
};
reader.onerror = () => reject(reader.error);
reader.readAsDataURL(chunk);
});
const base64data = await readChunk;
const response = await axios.post(
`/dashboard/uploads/chunked/${uploadId2}/chunk`,
{
storage,
chunk_data: base64data,
part_number: chunkIndex + 1,
filename: file.name,
mimetype: file.type
},
{
signal,
timeout: 12e4,
// 2 minute timeout for chunk uploads
maxContentLength: Infinity,
// Allow large content
maxBodyLength: Infinity
// Allow large body
}
);
if (response.data.success) {
uploadedChunks++;
const progress = Math.round(uploadedChunks / totalChunks * 100);
setUploadProgress(progress);
uploadedParts.push({
PartNumber: chunkIndex + 1,
ETag: response.data.etag || ""
// The server should return the ETag
});
} else {
throw new Error(response.data.message || "Failed to upload chunk");
}
}
if (signal.aborted) {
return;
}
await completeUpload(uploadId2);
} catch (error) {
if (signal.aborted) {
setUploadStatus("idle");
setUploadProgress(0);
return;
}
setUploadStatus("error");
setErrorMessage(((_b = (_a = error.response) == null ? void 0 : _a.data) == null ? void 0 : _b.message) || error.message || "Failed to upload file chunks");
if (onError) onError(((_d = (_c = error.response) == null ? void 0 : _c.data) == null ? void 0 : _d.message) || error.message || "Failed to upload file chunks");
}
};
const completeUpload = async (uploadId2) => {
var _a, _b, _c, _d;
setUploadStatus("completing");
try {
const response = await axios.post(
`/dashboard/uploads/chunked/${uploadId2}/complete`,
{
storage
},
{
timeout: 12e4
// 2 minute timeout for completion request
}
);
if (response.data.success) {
setUploadStatus("completed");
const fileData = {
file_path: response.data.file_path,
file_url: response.data.file_url,
signed_url: response.data.signed_url,
mime_type: response.data.mime_type,
file_name: response.data.file_name,
file_size: response.data.file_size
};
onFileUploaded == null ? void 0 : onFileUploaded(fileData);
setTimeout(() => {
if (fileInputRef.current) fileInputRef.current.value = "";
setFile(null);
setUploadId(null);
setUploadProgress(0);
setUploadStatus("idle");
}, 3e3);
} else {
throw new Error(response.data.message || "Failed to complete upload");
}
} catch (error) {
setUploadStatus("error");
setErrorMessage(((_b = (_a = error.response) == null ? void 0 : _a.data) == null ? void 0 : _b.message) || error.message || "Failed to complete upload");
if (onError) onError(((_d = (_c = error.response) == null ? void 0 : _c.data) == null ? void 0 : _d.message) || error.message || "Failed to complete upload");
}
};
const cancelUpload = async () => {
if (uploadId && uploadStatus !== "idle" && uploadStatus !== "completed") {
onCancelUpload == null ? void 0 : onCancelUpload();
if (abortControllerRef.current) {
abortControllerRef.current.abort();
}
try {
await axios.delete(`/dashboard/uploads/chunked/${uploadId}/abort`);
} catch (error) {
toast.error("Error aborting upload:" + error);
}
setUploadStatus("idle");
setUploadProgress(0);
if (fileInputRef.current) fileInputRef.current.value = "";
setFile(null);
}
};
const renderStatus = () => {
switch (uploadStatus) {
case "initializing":
return /* @__PURE__ */ jsxs("div", { className: "flex items-center", children: [
/* @__PURE__ */ jsx(Loader2, { className: "mr-2 h-4 w-4 animate-spin" }),
"Initializing upload..."
] });
case "uploading":
return /* @__PURE__ */ jsxs("div", { className: "flex items-center", children: [
/* @__PURE__ */ jsx(Loader2, { className: "mr-2 h-4 w-4 animate-spin" }),
"Uploading file chunks..."
] });
case "completing":
return /* @__PURE__ */ jsxs("div", { className: "flex items-center", children: [
/* @__PURE__ */ jsx(Loader2, { className: "mr-2 h-4 w-4 animate-spin" }),
"Finalizing upload..."
] });
case "completed":
return /* @__PURE__ */ jsxs("div", { className: "text-secondary-foreground flex items-center", children: [
/* @__PURE__ */ jsx(CheckCircle, { className: "mr-2 h-4 w-4" }),
"Completed upload"
] });
// case 'error':
// return (
// <div className="text-destructive flex items-center text-sm">
// <AlertCircle className="mr-2 h-4 w-4" />
// Error: {errorMessage}
// </div>
// );
default:
return null;
}
};
return /* @__PURE__ */ jsxs("div", { children: [
/* @__PURE__ */ jsxs("div", { className: "relative overflow-hidden rounded-sm", children: [
/* @__PURE__ */ jsx(
Input,
{
type: "file",
name: "file",
onChange: (e) => {
var _a;
handleFileChange(e);
const file2 = (_a = e.target.files) == null ? void 0 : _a[0];
if (file2) {
setFile(file2);
onFileSelected == null ? void 0 : onFileSelected(file2);
}
}
}
),
uploadStatus === "uploading" && file && /* @__PURE__ */ jsxs("div", { className: "absolute top-0 left-0 z-10 flex h-full w-full items-center justify-between", children: [
/* @__PURE__ */ jsxs("div", { className: "relative h-full w-full overflow-hidden bg-gray-200", children: [
/* @__PURE__ */ jsx(
"div",
{
className: "bg-secondary absolute top-0 left-0 h-full transition-all duration-300 ease-in-out",
style: { width: `${uploadProgress}%` }
}
),
/* @__PURE__ */ jsxs("div", { className: "relative z-10 flex h-full items-center justify-between gap-2 px-2 text-xs", children: [
/* @__PURE__ */ jsxs("span", { children: [
uploadProgress,
"%"
] }),
renderStatus(),
/* @__PURE__ */ jsxs("span", { className: "text-gray-800", children: [
"Size: (",
(file ? file.size / (1024 * 1024) : 0).toFixed(2),
" MB)"
] })
] })
] }),
/* @__PURE__ */ jsx("div", { className: "bg-gray-200", children: /* @__PURE__ */ jsx(Button, { type: "button", variant: "destructive", onClick: cancelUpload, className: "text-xs", children: "Cancel" }) })
] })
] }),
errorMessage && /* @__PURE__ */ jsx(InputError, { message: errorMessage })
] });
};
export {
ChunkedUploaderInput as C
};