|
11 | 11 | cropper: null, |
12 | 12 | image: null, |
13 | 13 |
|
| 14 | + /** @type {jQuery|null} */ |
| 15 | + $originalAvatar: null, |
| 16 | + |
14 | 17 | /** @type {jQuery} */ |
15 | 18 | $form: null, |
16 | 19 |
|
|
32 | 35 | /** @type {string} */ |
33 | 36 | driverUpload: 'avatar_driver_upload', |
34 | 37 |
|
| 38 | + /** @type {{width: {min: number, max: number}, height: {min: number, max: number}}} Allowed avatar sizes */ |
| 39 | + allowedSizes: { |
| 40 | + width: { min: -1, max: -1 }, |
| 41 | + height: { min: -1, max: -1 }, |
| 42 | + }, |
| 43 | + |
35 | 44 | /** |
36 | 45 | * Initialise avatar cropping. |
37 | 46 | */ |
38 | 47 | init() { |
39 | 48 | // If the cropper library is not available |
40 | | - if (!$.isFunction($.fn.cropper)) { |
| 49 | + if (!$.fn.hasOwnProperty('cropper') || typeof $.fn.cropper !== 'function') { |
41 | 50 | return; |
42 | 51 | } |
43 | 52 |
|
| 53 | + // Set allowed sizes from data attributes |
| 54 | + const data = this.$data.data(); |
| 55 | + this.allowedSizes.width.min = data.minWidth; |
| 56 | + this.allowedSizes.width.max = data.maxWidth; |
| 57 | + this.allowedSizes.height.min = data.minHeight; |
| 58 | + this.allowedSizes.height.max = data.maxHeight; |
| 59 | + |
44 | 60 | // Correctly position the cropper buttons |
45 | 61 | this.$buttons.appendTo(this.$box); |
46 | 62 |
|
47 | | - // Ensure we have an img for the cropping |
48 | | - if (this.$box.children('img').length === 0) { |
49 | | - const $avatarImg = $('<img src="" alt="">'); |
50 | | - $avatarImg.setAttribute('width', phpbb.avatars.$data.data().maxWidth); |
51 | | - $avatarImg.setAttribute('height', phpbb.avatars.$data.data().maxHeight); |
52 | | - $avatarImg.addClass('avatar'); |
53 | | - this.image = $avatarImg; |
54 | | - this.$box.prepend($avatarImg); |
55 | | - } else { |
56 | | - this.image = this.$box.children('img'); |
| 63 | + // Add image for cropping but track original avatar if it exists |
| 64 | + const $existingImg = this.$box.find('img'); |
| 65 | + if ($existingImg.length) { |
| 66 | + this.$originalAvatar = $existingImg; |
57 | 67 | } |
58 | 68 |
|
| 69 | + const $avatarImg = $('<img src="" alt="">'); |
| 70 | + $avatarImg.attr('width', this.allowedSizes.width.max); |
| 71 | + $avatarImg.attr('height', this.allowedSizes.height.max); |
| 72 | + $avatarImg.addClass('avatar hidden'); |
| 73 | + this.image = $avatarImg; |
| 74 | + this.$box.prepend($avatarImg); |
| 75 | + |
59 | 76 | this.bindInput(); |
60 | 77 | this.bindSelect(); |
61 | 78 | this.bindSubmit(); |
|
67 | 84 | destroy() { |
68 | 85 | this.$buttons.find('[data-cropper-action]').off('click.phpbb.avatars'); |
69 | 86 | this.image.off('crop.phpbb.avatars'); |
| 87 | + this.setAvatarVisible(true); |
70 | 88 | this.$form.off('submit'); |
71 | 89 |
|
72 | 90 | this.$data.val(''); |
| 91 | + this.$input.val(null); |
73 | 92 | this.$buttons.hide(); |
74 | 93 | this.$box.removeClass('c-cropper-avatar-box'); |
75 | 94 |
|
|
87 | 106 | bindSelect() { |
88 | 107 | this.$driver.on('change', function() { |
89 | 108 | if ($(this).val() === phpbb.avatars.driverUpload) { |
90 | | - if (phpbb.avatars.$input.val() !== '') { |
91 | | - phpbb.avatars.$input.trigger('change'); |
92 | | - } |
| 109 | + // Rebind submit after switching back to upload driver |
| 110 | + phpbb.avatars.bindSubmit(); |
93 | 111 | } else { |
| 112 | + // Show placeholder avatar if it exists and was hidden |
| 113 | + if (phpbb.avatars.$box.children('.avatar-placeholder').length) { |
| 114 | + phpbb.avatars.$box.children('.avatar-placeholder').show(); |
| 115 | + } |
| 116 | + |
94 | 117 | phpbb.avatars.destroy(); |
95 | 118 | } |
96 | 119 | }); |
|
112 | 135 | fileReader.addEventListener('load', function() { |
113 | 136 | phpbb.avatars.image.cropper('destroy').attr('src', this.result).addClass('avatar'); |
114 | 137 | phpbb.avatars.$box.addClass('c-cropper-avatar-box'); |
| 138 | + phpbb.avatars.setAvatarVisible(false); |
115 | 139 | phpbb.avatars.initCropper(); |
116 | 140 | phpbb.avatars.initButtons(); |
117 | 141 | }); |
|
121 | 145 | }); |
122 | 146 | }, |
123 | 147 |
|
| 148 | + /** |
| 149 | + * Show or hide the original avatar image. |
| 150 | + * @param {boolean} visible |
| 151 | + * @return {void} |
| 152 | + */ |
| 153 | + setAvatarVisible(visible) { |
| 154 | + if (this.$originalAvatar !== null) { |
| 155 | + if (visible) { |
| 156 | + phpbb.avatars.$originalAvatar.removeClass('hidden'); |
| 157 | + } else { |
| 158 | + phpbb.avatars.$originalAvatar.addClass('hidden'); |
| 159 | + } |
| 160 | + } |
| 161 | + }, |
| 162 | + |
124 | 163 | /** |
125 | 164 | * Bind submit button to be handled by ajax submit |
126 | 165 | */ |
127 | 166 | bindSubmit() { |
128 | 167 | const $this = this; |
129 | 168 | $this.$form = this.$input.closest('form'); |
130 | 169 | $this.$form.on('submit', () => { |
131 | | - const data = phpbb.avatars.$data.data(); |
| 170 | + if ($this.$form.find('#avatar_delete').is(':checked')) { |
| 171 | + return; |
| 172 | + } |
| 173 | + |
| 174 | + const $submitButton = this.$form.find('fieldset > input[type=submit]').first(); |
132 | 175 |
|
133 | 176 | const avatarCanvas = phpbb.avatars.cropper.getCroppedCanvas({ |
134 | 177 | maxWidth: 4096, // High values for max quality cropping |
|
137 | 180 |
|
138 | 181 | // eslint-disable-next-line no-undef |
139 | 182 | const hermiteResize = new Hermite_class(); |
140 | | - hermiteResize.resample_single(avatarCanvas, data.maxWidth, data.maxHeight, true); |
| 183 | + hermiteResize.resample_single(avatarCanvas, phpbb.avatars.allowedSizes.width.max, phpbb.avatars.allowedSizes.height.max, true); |
141 | 184 |
|
142 | 185 | avatarCanvas.toBlob(blob => { |
143 | 186 | const formData = new FormData($this.$form[0]); |
144 | 187 | formData.set('avatar_upload_file', blob, $this.getUploadFileName()); |
145 | | - formData.set('submit', '1'); |
| 188 | + formData.set($submitButton.attr('name'), $submitButton.val()); |
146 | 189 |
|
147 | 190 | const canvasDataUrl = avatarCanvas.toDataURL('image/png'); |
148 | 191 |
|
|
181 | 224 |
|
182 | 225 | /** |
183 | 226 | * Handle response from avatar submission |
184 | | - * @param {Object} response AJAX response object |
| 227 | + * @param {{MESSAGE_TITLE: string, MESSAGE_TEXT: string, |
| 228 | + * REFRESH_DATA: {time: int, url: string}, |
| 229 | + * error: {title: string, messages: string[]}}} response AJAX response object |
185 | 230 | * @param {string} canvasDataUrl Uploaded canvas element as data URL |
186 | 231 | */ |
187 | 232 | uploadDone(response, canvasDataUrl) { |
|
191 | 236 |
|
192 | 237 | // Handle errors while deleting file |
193 | 238 | if (typeof response.error === 'undefined') { |
| 239 | + // Ensure image is visible after upload |
| 240 | + phpbb.avatars.image.removeClass('hidden'); |
| 241 | + |
194 | 242 | const alert = phpbb.alert(response.MESSAGE_TITLE, response.MESSAGE_TEXT); |
195 | 243 |
|
196 | 244 | setTimeout(() => { |
197 | 245 | window.location = response.REFRESH_DATA.url.replace('&', '&'); |
198 | 246 | alert.hide(); |
199 | 247 | }, response.REFRESH_DATA.time * 1000); |
200 | 248 |
|
201 | | - phpbb.avatars.image.attr('src', canvasDataUrl); |
| 249 | + // Update original avatar image if it exists or use added image |
| 250 | + if (phpbb.avatars.$originalAvatar !== null) { |
| 251 | + phpbb.avatars.$originalAvatar.attr('src', canvasDataUrl); |
| 252 | + phpbb.avatars.image.addClass('hidden'); |
| 253 | + } else { |
| 254 | + phpbb.avatars.image.attr('src', canvasDataUrl); |
| 255 | + } |
| 256 | + |
202 | 257 | phpbb.avatars.destroy(); |
203 | 258 | } else { |
204 | 259 | phpbb.alert(response.error.title, response.error.messages.join('<br>')); |
|
239 | 294 | * and registers a callback function for the 'crop' event. |
240 | 295 | */ |
241 | 296 | initCropper() { |
| 297 | + // Hide placeholder avatar |
| 298 | + this.$box.children('.avatar-placeholder').hide(); |
| 299 | + |
242 | 300 | this.cropper = this.image.cropper({ |
243 | 301 | aspectRatio: 1, |
244 | 302 | autoCropArea: 1, |
| 303 | + minContainerHeight: this.allowedSizes.height.max * 2, // Double max size for better usability |
| 304 | + minContainerWidth: this.allowedSizes.width.max * 2, // Double max size for better usability |
245 | 305 | }).data('cropper'); |
246 | 306 |
|
247 | 307 | this.image.off('crop.phpbb.avatars').on('crop.phpbb.avatars', phpbb.avatars.onCrop); |
|
261 | 321 | * @param {object} event |
262 | 322 | */ |
263 | 323 | onCrop(event) { |
264 | | - const data = phpbb.avatars.$data.data(); |
265 | 324 | let { width, height } = event.detail; |
| 325 | + const allowedSizes = phpbb.avatars.allowedSizes; |
266 | 326 |
|
267 | | - if (width < data.minWidth || height < data.minHeight) { |
268 | | - width = Math.max(data.minWidth, Math.min(data.maxWidth, width)); |
269 | | - height = Math.max(data.minHeight, Math.min(data.maxHeight, height)); |
| 327 | + if (width < allowedSizes.width.min || height < allowedSizes.height.min) { |
| 328 | + width = Math.max(allowedSizes.width.min, Math.min(allowedSizes.width.max, width)); |
| 329 | + height = Math.max(allowedSizes.height.min, Math.min(allowedSizes.height.max, height)); |
270 | 330 | phpbb.avatars.cropper.setData({ |
271 | 331 | width, |
272 | 332 | height, |
|
0 commit comments