Skip to content

Commit 2b823e0

Browse files
committed
documented the profile picture settings component for better understanding
1 parent 9e6a64d commit 2b823e0

2 files changed

Lines changed: 29 additions & 4 deletions

File tree

src/main/webapp/app/shared/settings/profile-picture-settings/profile-picture-settings.component.ts

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,9 +49,12 @@ export class ProfilePictureSettingsComponent {
4949
readonly faMagnifyingGlassPlus = faMagnifyingGlassPlus;
5050
readonly faRotateRight = faRotateRight;
5151

52+
// Dialog + image source state used by the cropper overlay.
5253
cropDialogVisible = signal(false);
5354
rawImageSrc = signal<string | null>(null);
5455

56+
// User-controlled crop transform. `zoomFactor` is applied on top of `coverScale()`,
57+
// which already guarantees that the circular crop area is fully covered.
5558
zoomFactor = signal(1);
5659
minZoomFactor = 0.5;
5760
maxZoomFactor = 3;
@@ -66,6 +69,8 @@ export class ProfilePictureSettingsComponent {
6669

6770
currentProfilePictureUrl = computed<string | null>(() => this.normalizeAvatarUrl(this.accountService.loadedUser()?.avatar));
6871

72+
// Foreground preview shown inside the crop dialog. The image is centered,
73+
// then translated by the user's pan and finally scaled for the active zoom level.
6974
imageStyle = computed<Record<string, string>>(() => {
7075
const scale = this.effectiveScale();
7176
const pan = this.clampedPanForScale(scale);
@@ -83,6 +88,8 @@ export class ProfilePictureSettingsComponent {
8388
};
8489
});
8590

91+
// Soft blurred background behind the sharp foreground image so empty corners
92+
// never appear while the user pans a portrait-oriented or landscape-oriented image.
8693
blurImageStyle = computed<Record<string, string>>(() => {
8794
const scale = this.blurScale();
8895
const pan = this.clampedPanForScale(scale, CROP_RADIUS + BLUR_PADDING_PX);
@@ -196,6 +203,10 @@ export class ProfilePictureSettingsComponent {
196203
}
197204
}
198205

206+
/**
207+
* Rotates the currently loaded image by 90 degrees clockwise and rebuilds the
208+
* crop state from the rotated result so panning limits and zoom are recalculated.
209+
*/
199210
onRotate(): void {
200211
if (!this.img) return;
201212

@@ -221,6 +232,13 @@ export class ProfilePictureSettingsComponent {
221232
rotated.src = rotatedSrc;
222233
}
223234

235+
/**
236+
* Renders the visible crop result into a square canvas, masks it into a circle,
237+
* uploads the generated JPEG and updates the in-memory avatar shown in the app shell.
238+
*
239+
* The preview uses a 360px interaction area with a 150px crop radius, while the
240+
* exported avatar is normalized to a fixed 300x300px output for storage/display.
241+
*/
224242
async onSave(): Promise<void> {
225243
if (!this.img) return;
226244

@@ -249,6 +267,7 @@ export class ProfilePictureSettingsComponent {
249267
const cropTop = cc - CROP_RADIUS;
250268
const scaleToOut = outSize / (CROP_RADIUS * 2);
251269

270+
// Convert from dialog-space coordinates into export-canvas coordinates.
252271
const fgLeft = cc + fgPan.x - fgDisplayW / 2;
253272
const fgTop = cc + fgPan.y - fgDisplayH / 2;
254273
const bgLeft = cc + bgPan.x - bgDisplayW / 2;
@@ -299,6 +318,8 @@ export class ProfilePictureSettingsComponent {
299318
}
300319
}
301320

321+
// Reset to a safe initial crop: centered image with the minimum zoom required
322+
// to fully cover the circular crop area.
302323
private resetCropState(): void {
303324
if (this.imgNatW() <= 0 || this.imgNatH() <= 0) return;
304325
const initialZoom = Math.max(this.minZoomFactor, Math.min(1, this.maxZoomFactor));
@@ -326,13 +347,15 @@ export class ProfilePictureSettingsComponent {
326347
return this.coverScale() * 1.2;
327348
}
328349

350+
// Enforces the current pan limits after any zoom or drag update.
329351
private clampPan(): void {
330352
const scale = this.effectiveScale();
331353
const pan = this.clampedPanForScale(scale);
332354
this.panX.set(pan.x);
333355
this.panY.set(pan.y);
334356
}
335357

358+
// Prevents the visible crop circle from ever exposing empty space around the image.
336359
private clampedPanForScale(scale: number, coverRadius: number = CROP_RADIUS): { x: number; y: number } {
337360
const natW = this.imgNatW();
338361
const natH = this.imgNatH();
@@ -347,6 +370,10 @@ export class ProfilePictureSettingsComponent {
347370
};
348371
}
349372

373+
/**
374+
* Validates the selected file, loads it as a data URL and initializes the crop dialog
375+
* once the browser has decoded the image dimensions.
376+
*/
350377
private loadFileForCrop(file: File): void {
351378
if (file.size > MAX_UPLOAD_SIZE_BYTES) {
352379
this.toastService.showErrorKey('settings.profilePicture.fileTooLarge');

src/main/webapp/app/usermanagement/research-group/research-group-add-members/research-group-add-members.component.ts

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -229,10 +229,8 @@ export class ResearchGroupAddMembersComponent {
229229
} finally {
230230
// only touch loading/timeout if this is the latest request
231231
if (requestId === this.latestRequestId) {
232-
if (this.loaderTimeout !== null) {
233-
clearTimeout(this.loaderTimeout);
234-
this.loaderTimeout = null;
235-
}
232+
clearTimeout(this.loaderTimeout);
233+
this.loaderTimeout = null;
236234
this.loading.set(false);
237235
}
238236
}

0 commit comments

Comments
 (0)