// @license magnet:?xt=urn:btih:0b31508aeb0634b347b8270c7bee4d411b5d4109&dn=agpl-3.0.txt AGPL-v3-or-Later Diaspora.ProfilePhotoUploader = class { /** * Initializes a new instance of ProfilePhotoUploader */ constructor() { // get several elements we will use a few times this.fileInput = document.querySelector("#file-upload"); this.picture = document.querySelector("#profile_photo_upload .avatar"); this.info = document.querySelector("#fileInfo"); this.cropContainer = document.querySelector(".crop-container"); this.spinner = document.querySelector("#file-upload-spinner"); /** * Creates a button * @param {string} icon - The entypo icon class. * @param {function} onClick - Is called when button has been clicked. */ this.createButton = (icon, onClick) => ($(``) .on("click", onClick)); /** * Shows a message using flash messages or alert for mobile. * @param {string} type - The type of the message, e.g. "error" or "success". * @param text - The text to display. */ this.showMessage = (type, text) => (app.flashMessages ? app.flashMessages[type](text) : alert(text)); this.initFineUploader(); } /** * Initializes the fine uploader component */ initFineUploader() { this.fineUploader = new qq.FineUploaderBasic({ element: this.fileInput, validation: { allowedExtensions: ["jpg", "jpeg", "png"] }, request: { endpoint: Routes.photos(), params: { /* eslint-disable camelcase */ authenticity_token: $("meta[name='csrf-token']").attr("content"), /* eslint-enable camelcase */ photo: {"pending": true, "aspect_ids": "all", "set_profile_photo": true} } }, button: this.fileInput, autoUpload: false, messages: { typeError: Diaspora.I18n.t("photo_uploader.invalid_ext"), sizeError: Diaspora.I18n.t("photo_uploader.size_error"), emptyError: Diaspora.I18n.t("photo_uploader.empty") }, callbacks: { onProgress: (id, fileName, loaded, total) => { (this.info.innerText = `${fileName} ${Math.round(loaded / total * 100)}%`); }, onSubmit: (id, name) => this.onPictureSelected(id, name), onComplete: (id, name, responseJSON) => this.onUploadCompleted(id, name, responseJSON), onError: (id, name) => this.showMessage("error", Diaspora.I18n.t("photo_uploader.error", {file: name})) }, text: { fileInputTitle: "" }, scaling: { sendOriginal: false, sizes: [ {maxSize: 1600} ] } }); } /** * Called when a picture from user's device has been selected. * @param {number} id - The current file's id. * @param {string} name - The current file's name. */ onPictureSelected(id, name) { this.setLoading(true); this.fileName = name; const file = this.fileInput.querySelector("input").files[0]; // ensure browser's file reader support if (FileReader && file) { const fileReader = new FileReader(); fileReader.onload = () => this.initCropper(fileReader.result); fileReader.readAsDataURL(file); } else { this.setLoading(false); } } /** * Initializes the cropper and all controls. * @param {object|string} imageData - The base64 image data */ initCropper(imageData) { // cache the current picture source if the user cancels this.previousPicture = this.picture.getAttribute("src"); this.mimeType = imageData.split(";base64")[0].substring(5); this.picture.onload = () => { // set the preferred size style of the cropper based on picture orientation const isPortrait = this.picture.naturalHeight > this.picture.naturalWidth; this.picture.setAttribute("style", (isPortrait ? "max-height:600px;max-width:none;" : "max-width:600px;")); this.buildControls(); this.setLoading(false); // eslint-disable-next-line no-undef this.cropper = new Cropper(this.picture, { aspectRatio: 1, zoomable: false, autoCropArea: 1, preview: ".preview" }); }; this.picture.setAttribute("src", imageData); } /** * Creates image manipulation controls and previews. */ buildControls() { this.controls = { rotateLeft: this.createButton("ccw", () => this.cropper.rotate(-45)), rotateRight: this.createButton("cw", () => this.cropper.rotate(45)), reset: this.createButton("cycle", () => this.cropper.reset()), accept: this.createButton("check", () => this.cropImage()), cancel: this.createButton("trash", () => this.cancel()) }; this.controlRow = $("