180 lines
4.3 KiB
TypeScript
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);
|
|
});
|
|
}
|
|
}
|