Port image cropper to new popup

This commit is contained in:
Cohee 2024-06-25 02:05:35 +03:00
parent 974b98ed8e
commit e0000bade6
4 changed files with 51 additions and 7 deletions

View File

@ -4873,12 +4873,15 @@
</div> </div>
</div> </div>
<!-- various fullscreen popups --> <!-- various fullscreen popups -->
<template id="popup_template" data-i18n="[popup-button-save]popup-button-save;[popup-button-yes]popup-button-yes;[popup-button-no]popup-button-no;[popup-button-cancel]popup-button-cancel;[popup-button-import]popup-button-import" popup-button-save="Save" popup-button-yes="Yes" popup-button-no="No" popup-button-cancel="Cancel" popup-button-import="Import"> <!-- localization data holder for popups --> <template id="popup_template" data-i18n="[popup-button-save]popup-button-save;[popup-button-yes]popup-button-yes;[popup-button-no]popup-button-no;[popup-button-cancel]popup-button-cancel;[popup-button-import]popup-button-import;[popup-button-crop]popup-button-crop" popup-button-save="Save" popup-button-yes="Yes" popup-button-no="No" popup-button-cancel="Cancel" popup-button-import="Import" popup-button-crop="Crop"> <!-- localization data holder for popups -->
<dialog class="popup"> <dialog class="popup">
<div class="popup-body"> <div class="popup-body">
<div class="popup-content"> <div class="popup-content">
<h3 class="popup-header">text</h3> <h3 class="popup-header">text</h3>
</div> </div>
<div class="popup-crop-wrap">
<img class="popup-crop-image" src="">
</div>
<textarea class="popup-input text_pole result-control" rows="1" data-result="1" data-result-event="submit"></textarea> <textarea class="popup-input text_pole result-control" rows="1" data-result="1" data-result-event="submit"></textarea>
<div class="popup-controls"> <div class="popup-controls">
<div class="popup-button-ok menu_button result-control" data-result="1" data-i18n="Delete">Delete</div> <div class="popup-button-ok menu_button result-control" data-result="1" data-i18n="Delete">Delete</div>

View File

@ -1,3 +1,4 @@
import { power_user } from './power-user.js';
import { removeFromArray, runAfterAnimation, uuidv4 } from './utils.js'; import { removeFromArray, runAfterAnimation, uuidv4 } from './utils.js';
/** @readonly */ /** @readonly */
@ -11,6 +12,8 @@ export const POPUP_TYPE = {
INPUT: 3, INPUT: 3,
/** Popup without any button controls. Used to simply display content, with a small X in the corner. */ /** Popup without any button controls. Used to simply display content, with a small X in the corner. */
DISPLAY: 4, DISPLAY: 4,
/** Popup that displays an image to crop. Returns a cropped image in result. */
CROP: 5,
}; };
/** @readonly */ /** @readonly */
@ -36,6 +39,8 @@ export const POPUP_RESULT = {
* @property {CustomPopupButton[]|string[]?} [customButtons=null] - Custom buttons to add to the popup. If only strings are provided, the buttons will be added with default options, and their result will be in order from `2` onward. * @property {CustomPopupButton[]|string[]?} [customButtons=null] - Custom buttons to add to the popup. If only strings are provided, the buttons will be added with default options, and their result will be in order from `2` onward.
* @property {(popup: Popup) => boolean?} [onClosing=null] - Handler called before the popup closes, return `false` to cancel the close * @property {(popup: Popup) => boolean?} [onClosing=null] - Handler called before the popup closes, return `false` to cancel the close
* @property {(popup: Popup) => void?} [onClose=null] - Handler called after the popup closes, but before the DOM is cleaned up * @property {(popup: Popup) => void?} [onClose=null] - Handler called after the popup closes, but before the DOM is cleaned up
* @property {number?} [cropAspect=null] - Aspect ratio for the crop popup
* @property {string?} [cropImage=null] - Image URL to display in the crop popup
*/ */
/** /**
@ -84,6 +89,8 @@ export class Popup {
/** @type {HTMLElement} */ okButton; /** @type {HTMLElement} */ okButton;
/** @type {HTMLElement} */ cancelButton; /** @type {HTMLElement} */ cancelButton;
/** @type {HTMLElement} */ closeButton; /** @type {HTMLElement} */ closeButton;
/** @type {HTMLElement} */ cropWrap;
/** @type {HTMLImageElement} */ cropImage;
/** @type {POPUP_RESULT|number?} */ defaultResult; /** @type {POPUP_RESULT|number?} */ defaultResult;
/** @type {CustomPopupButton[]|string[]?} */ customButtons; /** @type {CustomPopupButton[]|string[]?} */ customButtons;
@ -92,6 +99,7 @@ export class Popup {
/** @type {POPUP_RESULT|number} */ result; /** @type {POPUP_RESULT|number} */ result;
/** @type {any} */ value; /** @type {any} */ value;
/** @type {any} */ cropData;
/** @type {HTMLElement} */ lastFocus; /** @type {HTMLElement} */ lastFocus;
@ -106,7 +114,7 @@ export class Popup {
* @param {string} [inputValue=''] - The initial value of the input field * @param {string} [inputValue=''] - The initial value of the input field
* @param {PopupOptions} [options={}] - Additional options for the popup * @param {PopupOptions} [options={}] - Additional options for the popup
*/ */
constructor(content, type, inputValue = '', { okButton = null, cancelButton = null, rows = 1, wide = false, wider = false, large = false, transparent = false, allowHorizontalScrolling = false, allowVerticalScrolling = false, defaultResult = POPUP_RESULT.AFFIRMATIVE, customButtons = null, onClosing = null, onClose = null } = {}) { constructor(content, type, inputValue = '', { okButton = null, cancelButton = null, rows = 1, wide = false, wider = false, large = false, transparent = false, allowHorizontalScrolling = false, allowVerticalScrolling = false, defaultResult = POPUP_RESULT.AFFIRMATIVE, customButtons = null, onClosing = null, onClose = null, cropAspect = null, cropImage = null } = {}) {
Popup.util.popups.push(this); Popup.util.popups.push(this);
// Make this popup uniquely identifiable // Make this popup uniquely identifiable
@ -128,6 +136,8 @@ export class Popup {
this.okButton = this.dlg.querySelector('.popup-button-ok'); this.okButton = this.dlg.querySelector('.popup-button-ok');
this.cancelButton = this.dlg.querySelector('.popup-button-cancel'); this.cancelButton = this.dlg.querySelector('.popup-button-cancel');
this.closeButton = this.dlg.querySelector('.popup-button-close'); this.closeButton = this.dlg.querySelector('.popup-button-close');
this.cropWrap = this.dlg.querySelector('.popup-crop-wrap');
this.cropImage = this.dlg.querySelector('.popup-crop-image');
this.dlg.setAttribute('data-id', this.id); this.dlg.setAttribute('data-id', this.id);
if (wide) this.dlg.classList.add('wide_dialogue_popup'); if (wide) this.dlg.classList.add('wide_dialogue_popup');
@ -169,6 +179,7 @@ export class Popup {
// General styling for all types first, that might be overriden for specific types below // General styling for all types first, that might be overriden for specific types below
this.input.style.display = 'none'; this.input.style.display = 'none';
this.closeButton.style.display = 'none'; this.closeButton.style.display = 'none';
this.cropWrap.style.display = 'none';
switch (type) { switch (type) {
case POPUP_TYPE.TEXT: { case POPUP_TYPE.TEXT: {
@ -190,6 +201,22 @@ export class Popup {
this.closeButton.style.display = 'block'; this.closeButton.style.display = 'block';
break; break;
} }
case POPUP_TYPE.CROP: {
this.cropWrap.style.display = 'block';
this.cropImage.src = cropImage;
if (!okButton) this.okButton.textContent = template.getAttribute('popup-button-crop');
$(this.cropImage).cropper({
aspectRatio: cropAspect ?? 2 / 3,
autoCropArea: 1,
viewMode: 2,
rotatable: false,
crop: function (event) {
this.cropData = event.detail;
this.cropData.want_resize = !power_user.never_resize_avatars;
},
});
break;
}
default: { default: {
console.warn('Unknown popup type.', type); console.warn('Unknown popup type.', type);
break; break;
@ -347,6 +374,13 @@ export class Popup {
else value = false; // Might a custom negative value? else value = false; // Might a custom negative value?
} }
// Cropped image should be returned as a data URL
if (this.type === POPUP_TYPE.CROP) {
value = result >= POPUP_RESULT.AFFIRMATIVE
? $(this.cropImage).data('cropper').getCroppedCanvas().toDataURL('image/jpeg')
: null;
}
this.value = value; this.value = value;
this.result = result; this.result = result;

View File

@ -1,4 +1,4 @@
import { callPopup, getCropPopup, getRequestHeaders } from '../script.js'; import { getRequestHeaders } from '../script.js';
import { POPUP_RESULT, POPUP_TYPE, callGenericPopup } from './popup.js'; import { POPUP_RESULT, POPUP_TYPE, callGenericPopup } from './popup.js';
import { renderTemplateAsync } from './templates.js'; import { renderTemplateAsync } from './templates.js';
import { ensureImageFormatSupported, getBase64Async, humanFileSize } from './utils.js'; import { ensureImageFormatSupported, getBase64Async, humanFileSize } from './utils.js';
@ -727,14 +727,14 @@ async function openUserProfile() {
*/ */
async function cropAndUploadAvatar(handle, file) { async function cropAndUploadAvatar(handle, file) {
const dataUrl = await getBase64Async(await ensureImageFormatSupported(file)); const dataUrl = await getBase64Async(await ensureImageFormatSupported(file));
const croppedImage = await callPopup(getCropPopup(dataUrl), 'avatarToCrop', '', { cropAspect: 1 }); const croppedImage = await callGenericPopup('Set the crop position of the avatar image', POPUP_TYPE.CROP, '', { cropAspect: 1, cropImage: dataUrl });
if (!croppedImage) { if (!croppedImage) {
return; return;
} }
await changeAvatar(handle, String(croppedImage)); await changeAvatar(handle, String(croppedImage));
return croppedImage; return String(croppedImage);
} }
/** /**

View File

@ -4154,13 +4154,20 @@ h5 {
grid-template-columns: 340px auto; grid-template-columns: 340px auto;
} }
#avatarCropWrap { #avatarCropWrap,
.popup-crop-wrap {
margin: 10px auto; margin: 10px auto;
max-height: 90%; max-height: 90%;
max-width: 100%; max-width: 100%;
} }
#avatarToCrop { .popup-crop-wrap {
max-height: 75vh;
max-height: 75svh;
}
#avatarToCrop,
.popup-crop-wrap img {
max-width: 100%; max-width: 100%;
/* This rule is very important, please do not ignore this! */ /* This rule is very important, please do not ignore this! */
} }