1 import { prepareRequest } from "WoltLabSuite/Core/Ajax/Backend";
2 import { StatusNotOk } from "WoltLabSuite/Core/Ajax/Error";
3 import { isPlainObject } from "WoltLabSuite/Core/Core";
4 import { wheneverFirstSeen } from "WoltLabSuite/Core/Helper/Selector";
5 import { upload as filesUpload } from "WoltLabSuite/Core/Api/Files/Upload";
6 import WoltlabCoreFileElement from "./woltlab-core-file";
14 export type UploadCompleted = {
15 endpointThumbnails: string;
20 data: Record<string, unknown>;
23 export type ThumbnailsGenerated = {
24 data: GenerateThumbnailsResponse;
28 type ThumbnailData = {
33 type GenerateThumbnailsResponse = ThumbnailData[];
35 async function upload(element: WoltlabCoreFileUploadElement, file: File): Promise<void> {
36 const typeName = element.dataset.typeName!;
38 const fileHash = await getSha256Hash(await file.arrayBuffer());
40 const fileElement = document.createElement("woltlab-core-file");
41 fileElement.dataset.filename = file.name;
43 const event = new CustomEvent<WoltlabCoreFileElement>("uploadStart", { detail: fileElement });
44 element.dispatchEvent(event);
46 const response = await filesUpload(file.name, file.size, fileHash, typeName, element.dataset.context || "");
48 const validationError = response.error.getValidationError();
49 if (validationError === undefined) {
50 fileElement.uploadFailed();
55 console.log(validationError);
59 const { identifier, numberOfChunks } = response.value;
61 const chunkSize = Math.ceil(file.size / numberOfChunks);
63 // TODO: Can we somehow report any meaningful upload progress?
65 for (let i = 0; i < numberOfChunks; i++) {
66 const start = i * chunkSize;
67 const end = start + chunkSize;
68 const chunk = file.slice(start, end);
71 throw new Error("TODO: fix the url");
72 const endpoint = new URL(String(i));
74 const checksum = await getSha256Hash(await chunk.arrayBuffer());
75 endpoint.searchParams.append("checksum", checksum);
77 let response: UploadResponse;
79 response = (await prepareRequest(endpoint.toString()).post(chunk).fetchAsJson()) as UploadResponse;
81 // TODO: Handle errors
84 fileElement.uploadFailed();
88 await chunkUploadCompleted(fileElement, response);
92 async function chunkUploadCompleted(fileElement: WoltlabCoreFileElement, response: UploadResponse): Promise<void> {
93 if (!response.completed) {
97 const hasThumbnails = response.endpointThumbnails !== "";
98 fileElement.uploadCompleted(response.fileID, response.mimeType, response.link, response.data, hasThumbnails);
101 await generateThumbnails(fileElement, response.endpointThumbnails);
105 async function generateThumbnails(fileElement: WoltlabCoreFileElement, endpoint: string): Promise<void> {
106 let response: GenerateThumbnailsResponse;
109 response = (await prepareRequest(endpoint).get().fetchAsJson()) as GenerateThumbnailsResponse;
111 // TODO: Handle errors
116 fileElement.setThumbnails(response);
119 async function getSha256Hash(data: BufferSource): Promise<string> {
120 const buffer = await window.crypto.subtle.digest("SHA-256", data);
122 return Array.from(new Uint8Array(buffer))
123 .map((b) => b.toString(16).padStart(2, "0"))
127 export function setup(): void {
128 wheneverFirstSeen("woltlab-core-file-upload", (element) => {
129 element.addEventListener("upload", (event: CustomEvent<File>) => {
130 void upload(element, event.detail);