Files
RomhackPlaza/resources/js/SubmissionsClass/FSFileData.js

160 lines
4.2 KiB
JavaScript
Raw Normal View History

2026-05-20 18:25:15 +02:00
/** @typedef { import('types/UploadchunkResponse.js').UploadchunkResponse} UploadchunkResponse */
export const CHUNK_SIZE = 8192;
/**
* An uploaded file instance.
* Create a new file data.
*
* @param {string} name Filename
* @param {number} totalChunks Total number of chunks of the file.
* @param rawFile The JS file element relation.
*/
export function FSFileData(name, totalChunks, rawFile ) {
return {
/**
* Filename.
* @type {string}
*/
name,
/**
* Number of total chunks based on CHUNK_SIZE.
* @type {number}
*/
totalChunks,
/**
* The JS file element relation.
*/
rawFile,
/**
* Upload progression value.
* @type {number}
*/
progressValue: 0,
/**
* Current chunk uploaded.
* @type {number}
*/
currentChunk: 0,
/**
* If the upload of the file is finished.
* @type {boolean}
*/
done: false,
/**
* If there is an error during file uploading.
* @type {any|null}
*/
error: null,
/**
* UUID v4 for the file.
* @type {`${string}-${string}-${string}-${string}-${string}`}
*/
uuid: crypto.randomUUID(),
2026-06-02 20:54:10 +02:00
/**
* Current file state
*/
state: 'public',
2026-05-20 18:25:15 +02:00
/**
2026-06-02 20:54:10 +02:00
* Look if this file is currently uploading.
* @returns {boolean}
2026-05-20 18:25:15 +02:00
*/
2026-06-02 20:54:10 +02:00
get isUploading()
{
return !this.done && !this.error;
},
2026-05-20 18:25:15 +02:00
2026-06-02 20:54:10 +02:00
/**
* Build API url.
* @param {string} section
* @returns {string} The API url.
*/
buildUrl(section)
{
return `/api/fs/upload-chunk/${section}`;
},
2026-05-20 18:25:15 +02:00
2026-06-02 20:54:10 +02:00
/**
* Upload the file.
* @param {string} section section of the file.
* @returns {Promise<void>}
*/
async upload(section)
{
2026-05-20 18:25:15 +02:00
2026-06-02 20:54:10 +02:00
if (!this.rawFile)
return; // Can't upload in that case.
2026-05-20 18:25:15 +02:00
2026-06-02 20:54:10 +02:00
/**
* Get CSRF token for uploading request.
* @type {string}
*/
const CSRF = document.querySelector('meta[name=csrf-token]')?.content ?? '';
2026-05-20 18:25:15 +02:00
2026-06-02 20:54:10 +02:00
for (let i = 0; i < this.totalChunks; i++) {
2026-05-20 18:25:15 +02:00
2026-06-02 20:54:10 +02:00
if (this.error)
return; // Abort the process.
2026-05-20 18:25:15 +02:00
2026-06-02 20:54:10 +02:00
const start = i * CHUNK_SIZE;
const end = Math.min(start + CHUNK_SIZE, this.rawFile.size);
const chunk = this.rawFile.slice(start, end);
2026-05-20 18:25:15 +02:00
2026-06-02 20:54:10 +02:00
const formData = new FormData();
2026-05-20 18:25:15 +02:00
2026-06-02 20:54:10 +02:00
formData.append('file', chunk);
formData.append('file_uuid', this.uuid);
formData.append('current_chunk', i);
formData.append('total_chunks', this.totalChunks);
formData.append('filename', this.rawFile.name);
formData.append('_token', CSRF);
2026-05-20 18:25:15 +02:00
2026-06-02 20:54:10 +02:00
// -----
// UPLOAD TIME !
// -----
2026-05-20 18:25:15 +02:00
2026-06-02 20:54:10 +02:00
try {
const RESPONSE = await fetch(this.buildUrl(section), {method: 'POST', body: formData});
2026-05-20 18:25:15 +02:00
2026-06-02 20:54:10 +02:00
if (!RESPONSE.ok) // Problem with the request.
throw new Error(`${RESPONSE.status} ${RESPONSE.statusText}`);
/** @type {UploadchunkResponse} */
const DATA = await RESPONSE.json();
if (DATA.success !== true || DATA.uploaded !== true)
// The request reached the file server but could not be sent.
throw new Error(`${DATA.error}`);
this.currentChunk = i + 1;
this.progressValue = Math.round(((i + 1) / this.totalChunks) * 100);
if (DATA.finished === true) {
this.done = true;
return;
}
} catch (err) {
this.error = 'Error on chunk ' + (i + 1) + '. ' + err.message;
this.progressValue = 0;
2026-05-20 18:25:15 +02:00
return;
}
}
}
}
}