ily.li/assets/ts/fm.ts

194 lines
7.0 KiB
TypeScript

import {applyFormMessages} from "./forms";
document.addEventListener('DOMContentLoaded', () => {
const form = document.querySelector<HTMLFormElement>('#upload-form');
if (!form) return;
const upload = document.getElementById('file-upload');
const uploadLink = document.querySelector<HTMLLinkElement>('#file-upload-link');
const uploadField = document.querySelector<HTMLInputElement>('#field-upload');
const neverExpireCheckbox = document.querySelector<HTMLInputElement>('#field-never_expire');
const expireAfterDaysField = document.querySelector<HTMLInputElement>('#field-expire_after_days');
const autogenUrlCheckbox = document.querySelector<HTMLInputElement>('#field-autogen_url');
const slugField = document.querySelector<HTMLInputElement>('#field-slug');
if (expireAfterDaysField && neverExpireCheckbox) {
const updateExpireAfterDaysField = () => {
expireAfterDaysField.disabled = neverExpireCheckbox.checked;
const parent = expireAfterDaysField.parentElement?.parentElement;
if (parent) {
if (neverExpireCheckbox.checked) {
parent.classList.add('hidden');
} else {
parent.classList.remove('hidden');
}
}
};
neverExpireCheckbox.addEventListener('change', updateExpireAfterDaysField);
updateExpireAfterDaysField();
}
if (slugField && autogenUrlCheckbox) {
const updateSlugField = () => {
slugField.disabled = autogenUrlCheckbox.checked;
const parent = slugField.parentElement?.parentElement;
if (parent) {
if (autogenUrlCheckbox.checked) {
parent.classList.add('hidden');
} else {
parent.classList.remove('hidden');
}
}
};
autogenUrlCheckbox.addEventListener('change', updateSlugField);
updateSlugField();
}
let uploadForm: UploadForm | undefined;
form.addEventListener('submit', e => {
e.preventDefault();
if (upload && uploadLink && uploadField && uploadField.files && (!uploadForm || uploadForm.isFinished())) {
uploadForm = new UploadForm(form, upload, uploadLink, uploadField.files[0].name);
uploadForm.updateView();
uploadForm.start();
}
});
});
const units = ['K', 'M', 'G', 'T'];
class UploadForm {
private finished: boolean = false;
private readonly progressBar: HTMLElement | null;
private readonly progressBarContent: HTMLElement | null;
private readonly status: HTMLElement | null;
private speed: HTMLElement | null;
private xferSpeed: number[] = [];
private lastTransferTime: number | null = null;
private lastLoaded: number = 0;
private xhr: XMLHttpRequest;
public constructor(
private form: HTMLFormElement,
private upload: HTMLElement,
private uploadLink: HTMLLinkElement,
private fileName: string,
) {
this.progressBar = this.upload.querySelector('.progress-bar');
this.progressBarContent = this.progressBar?.querySelector('.content') || null;
this.status = this.upload.querySelector('.status');
this.speed = this.status?.querySelector('.speed') || null;
if (!this.progressBar)
throw new Error('Invalid html');
this.xhr = new XMLHttpRequest();
this.xhr.responseType = 'json';
this.xhr.upload.addEventListener('progress', e => {
if (this.progressBar && this.progressBarContent) {
if (e.lengthComputable) {
this.progressBar.classList.remove('undefined');
const percent = (e.loaded / e.total * 100).toFixed(2) + '%';
this.progressBar.style.setProperty('--progress', `${percent}`);
this.progressBarContent.innerText = percent;
this.updateSpeed(e.loaded);
} else {
this.progressBar.classList.add('undefined');
}
}
});
this.xhr.upload.addEventListener('loadstart', () => {
this.status?.classList.remove('hidden');
});
this.xhr.addEventListener('load', () => {
this.finished = true;
const response = this.xhr.response;
console.log('done', response);
if (response.status === 'error') {
if (response.messages) {
this.restoreView();
applyFormMessages(this.form, response.messages);
}
} else if (response.url) {
if (this.status) {
this.status.innerHTML = 'Done!';
}
const uploadLinkContent = this.uploadLink.querySelector<HTMLElement>('.content');
if (uploadLinkContent) {
uploadLinkContent.innerText = response.url;
}
this.uploadLink.classList.remove('hidden');
} else {
window.location.reload();
}
});
this.xhr.addEventListener('error', (e) => {
this.finished = true;
console.error('error', e);
if (this.status) {
this.status.innerHTML = 'Error; upload was interrupted.';
}
});
}
public isFinished(): boolean {
return this.finished;
}
public updateView(): void {
const uploadName = this.upload.querySelector<HTMLElement>('.name');
if (uploadName) {
uploadName.innerText = this.fileName;
}
this.form.classList.add('hidden');
this.upload.classList.remove('hidden');
if (this.status) {
this.status.innerHTML = `Uploading @ <span class="speed">--</span>`;
this.speed = this.status.querySelector('.speed');
}
}
public restoreView(): void {
if (this.status) {
this.status.classList.add('hidden');
}
this.upload.classList.add('hidden');
this.form.classList.remove('hidden');
}
public start(): void {
const formData = new FormData(this.form);
this.xhr.open('POST', this.form.action);
this.xhr.send(formData);
}
public updateSpeed(loaded: number): void {
const time = new Date().getTime();
if (this.lastTransferTime) {
this.xferSpeed.push((loaded - this.lastLoaded) / (time - this.lastTransferTime));
if (this.xferSpeed.length > 100) this.xferSpeed = this.xferSpeed.slice(1);
let speed = this.xferSpeed.reduce((v, c) => v + c) / this.xferSpeed.length;
let unit = 0;
while (speed >= 1000 && unit < units.length - 1) {
speed /= 1000;
unit++;
}
if (this.speed) {
this.speed.innerText = speed.toFixed(2) + units[unit] + 'Bps';
}
}
this.lastTransferTime = time;
this.lastLoaded = loaded;
}
}