import bytesHumanReadable from "scripts/Utils/bytesHumanReadable" import uploadWithProgress from "scripts/Utils/uploadWithProgress" import AnimeNotifier from "../AnimeNotifier" // Select file export function selectFile(arn: AnimeNotifier, button: HTMLButtonElement) { const fileType = button.dataset.type const endpoint = button.dataset.endpoint if(endpoint === "/api/upload/user/cover" && arn.user && !arn.user.IsPro()) { alert("Please buy a PRO account to use this feature.") return } // Click on virtual file input element const input = document.createElement("input") input.setAttribute("type", "file") input.value = "" input.onchange = async () => { if(!fileType || !endpoint) { console.error("Missing data-type or data-endpoint:", button) return } if(!input.files || input.files.length === 0) { return } const file = input.files[0] // Check mime type for images if(fileType === "image" && !file.type.startsWith("image/")) { arn.statusMessage.showError(file.name + " is not an image file!") return } // Check mime type for videos if(fileType === "video" && !file.type.startsWith("video/webm")) { arn.statusMessage.showError(file.name + " is not a WebM video file!") return } // Preview image if(fileType === "image") { const previews = document.getElementsByClassName(button.id + "-preview") const dataURL = await readImageAsDataURL(file) const img = await loadImage(dataURL) switch(endpoint) { case "/api/upload/user/image": if(img.naturalWidth <= 280 || img.naturalHeight < 280) { arn.statusMessage.showError(`Your image has a resolution of ${img.naturalWidth} x ${img.naturalHeight} pixels which is too small. Recommended: 560 x 560. Minimum: 280 x 280.`, 8000) return } break case "/api/upload/user/cover": if(img.naturalWidth <= 960 || img.naturalHeight < 225) { arn.statusMessage.showError(`Your image has a resolution of ${img.naturalWidth} x ${img.naturalHeight} pixels which is too small. Recommended: 1920 x 450. Minimum: 960 x 225.`, 8000) return } break } previewImage(dataURL, endpoint, previews) } uploadFile(file, fileType, endpoint, arn) } input.click() } // Upload file function uploadFile(file: File, fileType: string, endpoint: string, arn: AnimeNotifier) { const reader = new FileReader() reader.onloadend = async () => { const result = reader.result as ArrayBuffer const fileSize = result.byteLength if(fileSize === 0) { arn.statusMessage.showError("File is empty") return } arn.statusMessage.showInfo(`Preparing to upload ${fileType} (${bytesHumanReadable(fileSize)})`, -1) try { const responseText = await uploadWithProgress(endpoint, { method: "POST", credentials: "include", headers: { "Content-Type": "application/octet-stream" }, body: reader.result }, e => { const progress = e.loaded / (e.lengthComputable ? e.total : fileSize) * 100 arn.statusMessage.showInfo(`Uploading ${fileType}...${progress.toFixed(1)}%`, -1) }) arn.statusMessage.showInfo(`Successfully uploaded your new ${fileType}.`) if(endpoint === "/api/upload/user/image") { // We received the new avatar URL updateSideBarAvatar(responseText) } } catch(err) { arn.statusMessage.showError(`Failed uploading your new ${fileType}.`) console.error(err) } } arn.statusMessage.showInfo(`Reading ${fileType} from disk...`, -1) reader.readAsArrayBuffer(file) } // Read image as data URL function readImageAsDataURL(file: File): Promise { return new Promise((resolve, reject) => { const reader = new FileReader() reader.onloadend = () => { const dataURL = reader.result as string resolve(dataURL) } reader.onerror = event => { reader.abort() reject(event) } reader.readAsDataURL(file) }) } // Load image and resolve when loading has finished function loadImage(url: string): Promise { return new Promise((resolve, reject) => { const img = new Image() img.src = url img.onload = () => { resolve(img) } img.onerror = error => { reject(error) } }) } // Preview image function previewImage(dataURL: string, endpoint: string, previews: HTMLCollectionOf) { if(endpoint === "/api/upload/user/image") { const svgPreview = document.getElementById("avatar-input-preview-svg") as HTMLImageElement if(svgPreview) { svgPreview.classList.add("hidden") } } for(const preview of previews) { const img = preview as HTMLImageElement img.classList.remove("hidden") // Make not found images visible again if(img.classList.contains("lazy")) { img.classList.remove("element-not-found") img.classList.add("element-found") } img.src = dataURL } } // Update sidebar avatar function updateSideBarAvatar(url: string) { const sidebar = document.getElementById("sidebar") as HTMLElement const userImage = sidebar.getElementsByClassName("user-image")[0] as HTMLImageElement const lazyLoad = userImage["became visible"] if(lazyLoad) { userImage.dataset.src = url lazyLoad() } else { location.reload() } }