icynet-auth-server/src/fe/ts/modal/avatar.ts

180 lines
4.3 KiB
TypeScript

import { Modal } from './modal';
import Cropper from 'cropperjs';
export class AvatarModal extends Modal {
private cropper?: Cropper;
private csrf!: string;
private stages!: NodeListOf<HTMLElement>;
private fileInput!: HTMLInputElement;
private continueBtn!: HTMLElement;
private resetBtn!: HTMLElement;
private uploadBtn!: HTMLElement;
private cropRoot!: HTMLImageElement;
private previewRoot!: HTMLImageElement;
private currentAvatar!: HTMLImageElement;
private cropResultUrl?: string;
private cropResultBlob?: Blob;
private currentStep = 1;
constructor() {
super('avatar');
}
public reset(): void {
super.reset();
if (!this.modal) {
return;
}
if (this.cropper) {
this.cropper.destroy();
}
this.cropResultUrl = null;
this.cropResultBlob = null;
this.fileInput.value = null;
this.previewRoot.removeAttribute('src');
this.cropRoot.removeAttribute('src');
this.setStep(1);
}
public initialize(): void {
super.initialize();
if (!this.modal) {
return;
}
this.csrf = (document.querySelector('#csrf') as HTMLInputElement).value;
this.stages = this.modal?.querySelectorAll(
'[data-upload-step]',
) as NodeListOf<HTMLElement>;
this.fileInput = this.modal?.querySelector(
'#image-file',
) as HTMLInputElement;
this.cropRoot = this.modal?.querySelector('#cropper') as HTMLImageElement;
this.previewRoot = this.modal?.querySelector(
'#crop-result',
) as HTMLImageElement;
this.currentAvatar = document.querySelector(
'#current-avatar',
) as HTMLImageElement;
this.continueBtn = this.modal?.querySelector('#continue') as HTMLElement;
this.resetBtn = this.modal?.querySelector('#reset') as HTMLElement;
this.uploadBtn = this.modal?.querySelector('#upload') as HTMLElement;
this.setSteps();
this.registerEvents();
}
public setSteps(): void {
this.stages?.forEach((item) => {
const itemState = parseInt(item.getAttribute('data-upload-step'), 10);
item.style.display = itemState === this.currentStep ? null : 'none';
});
}
public setStep(index: number): void {
this.currentStep = index;
this.setSteps();
}
private startCrop(): void {
const imgf = this.fileInput.files[0];
if (!imgf.type.includes('image/') || imgf.type.includes('svg')) {
// TODO: error
return;
}
const reader = new FileReader();
reader.onerror = () => {
// TODO: error
this.reset();
};
reader.onload = () => {
this.cropRoot.src = reader.result as string;
this.createCropper();
this.setStep(2);
};
reader.readAsDataURL(imgf);
}
private registerEvents(): void {
if (!this.modal) {
return;
}
this.fileInput.addEventListener('change', () => {
this.startCrop();
});
this.continueBtn.addEventListener('click', () => {
this.createCropResult();
this.setStep(3);
});
this.resetBtn.addEventListener('click', () => {
if (this.cropper) {
this.cropper.destroy();
}
this.fileInput.value = null;
this.cropResultUrl = null;
this.cropResultBlob = null;
this.previewRoot.removeAttribute('src');
this.setStep(1);
});
this.uploadBtn.addEventListener('click', () => {
const formData = new FormData();
formData.append('file', this.cropResultBlob);
formData.append('_csrf', this.csrf);
// TODO: error
fetch('/account/avatar', {
method: 'POST',
body: formData,
})
.then((res) => res.json())
.then((data) => {
this.reset();
if (data.file) {
this.currentAvatar.src = `/uploads/${data.file}`;
}
});
});
}
private createCropper(): void {
this.cropper = new Cropper(this.cropRoot, {
aspectRatio: 1,
viewMode: 1,
modal: false,
});
}
private createCropResult(): void {
const cropCanvas = this.cropper.getCroppedCanvas({
maxHeight: 1024,
maxWidth: 1024,
imageSmoothingEnabled: true,
imageSmoothingQuality: 'high',
});
this.cropResultUrl = cropCanvas.toDataURL();
this.previewRoot.src = this.cropResultUrl;
cropCanvas.toBlob((blob) => {
this.cropResultBlob = blob;
this.setStep(3);
});
}
}