@memberjunction/ng-user-avatar 6.2.0-edge.2 → 6.2.0-edge.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -1,6 +1,13 @@
|
|
|
1
1
|
import { HttpClient } from '@angular/common/http';
|
|
2
|
+
import { IEntityDataProvider, IMetadataProvider } from '@memberjunction/core';
|
|
2
3
|
import { MJUserEntity } from '@memberjunction/core-entities';
|
|
3
4
|
import * as i0 from "@angular/core";
|
|
5
|
+
/** The outcome of {@link UserAvatarService.UpdateMyAvatar}. */
|
|
6
|
+
export interface UpdateMyAvatarResult {
|
|
7
|
+
Success: boolean;
|
|
8
|
+
/** The server's reason when `Success` is false. */
|
|
9
|
+
ErrorMessage?: string;
|
|
10
|
+
}
|
|
4
11
|
/**
|
|
5
12
|
* Service for managing user avatar operations across the application.
|
|
6
13
|
*
|
|
@@ -12,10 +19,24 @@ export declare class UserAvatarService {
|
|
|
12
19
|
private http;
|
|
13
20
|
constructor(http: HttpClient);
|
|
14
21
|
/**
|
|
15
|
-
*
|
|
16
|
-
*
|
|
22
|
+
* Sets the SIGNED-IN user's avatar through the server's self-service `UpdateMyAvatar` mutation.
|
|
23
|
+
*
|
|
24
|
+
* Use this rather than saving the `MJ: Users` row: the mutation needs no Update permission on
|
|
25
|
+
* `MJ: Users`, which locked-down deployments do not grant, and it writes only the two avatar
|
|
26
|
+
* columns. The server validates the values and always acts on the caller's own row.
|
|
27
|
+
*
|
|
28
|
+
* @param imageURL - a base64 image data URI or an http(s) URL; null clears it
|
|
29
|
+
* @param iconClass - a Font Awesome class list; null clears it
|
|
30
|
+
* @param provider - the GraphQL provider to call; defaults to the global provider
|
|
31
|
+
* @returns the server's outcome; never throws
|
|
32
|
+
*/
|
|
33
|
+
UpdateMyAvatar(imageURL: string | null, iconClass: string | null, provider?: IMetadataProvider | IEntityDataProvider): Promise<UpdateMyAvatarResult>;
|
|
34
|
+
/**
|
|
35
|
+
* Syncs the signed-in user's avatar from an image URL (typically from the auth provider profile).
|
|
36
|
+
* Downloads the image, converts it to a Base64 data URI and saves it through {@link UpdateMyAvatar},
|
|
37
|
+
* then reloads `user` so it is clean and current.
|
|
17
38
|
*
|
|
18
|
-
* @param user -
|
|
39
|
+
* @param user - the signed-in user's MJUserEntity; any other user is refused
|
|
19
40
|
* @param imageUrl - URL to the image (can be from Microsoft Graph, Google, etc.)
|
|
20
41
|
* @param authHeaders - Optional headers for authenticated requests (e.g., { 'Authorization': 'Bearer token' })
|
|
21
42
|
* @returns Promise<boolean> - true if avatar was synced and saved, false otherwise
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"user-avatar.service.d.ts","sourceRoot":"","sources":["../../src/lib/user-avatar.service.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAC;AAClD,OAAO,EAAE,YAAY,EAAE,MAAM,+BAA+B,CAAC;;
|
|
1
|
+
{"version":3,"file":"user-avatar.service.d.ts","sourceRoot":"","sources":["../../src/lib/user-avatar.service.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAC;AAClD,OAAO,EAAE,mBAAmB,EAAE,iBAAiB,EAAY,MAAM,sBAAsB,CAAC;AACxF,OAAO,EAAE,YAAY,EAAE,MAAM,+BAA+B,CAAC;;AAK7D,+DAA+D;AAC/D,MAAM,WAAW,oBAAoB;IACnC,OAAO,EAAE,OAAO,CAAC;IACjB,mDAAmD;IACnD,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAWD;;;;;;GAMG;AACH,qBAGa,iBAAiB;IAChB,OAAO,CAAC,IAAI;gBAAJ,IAAI,EAAE,UAAU;IAEpC;;;;;;;;;;;OAWG;IACG,cAAc,CAClB,QAAQ,EAAE,MAAM,GAAG,IAAI,EACvB,SAAS,EAAE,MAAM,GAAG,IAAI,EACxB,QAAQ,CAAC,EAAE,iBAAiB,GAAG,mBAAmB,GACjD,OAAO,CAAC,oBAAoB,CAAC;IAiBhC;;;;;;;;;OASG;IACG,gBAAgB,CACpB,IAAI,EAAE,YAAY,EAClB,QAAQ,EAAE,MAAM,EAChB,WAAW,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GACnC,OAAO,CAAC,OAAO,CAAC;IAiCnB,gDAAgD;IAC1C,gBAAgB,CACpB,IAAI,EAAE,YAAY,EAClB,QAAQ,EAAE,MAAM,EAChB,WAAW,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GACnC,OAAO,CAAC,OAAO,CAAC;IAInB;;;OAGG;IACH,OAAO,CAAC,YAAY;IASpB;;;OAGG;IACH,YAAY,CAAC,IAAI,EAAE,IAAI,GAAG,OAAO,CAAC,MAAM,CAAC;IASzC,4CAA4C;IAC5C,YAAY,CAAC,IAAI,EAAE,IAAI,GAAG,OAAO,CAAC,MAAM,CAAC;IAIzC;;OAEG;IACH,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO;IAahC,0CAA0C;IAC1C,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO;IAIhC;;OAEG;IACH,oBAAoB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO;IAS9C,oDAAoD;IACpD,oBAAoB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO;IAI9C;;;;;;;OAOG;IACH,mBAAmB,CAAC,IAAI,EAAE,YAAY,EAAE,UAAU,GAAE,MAA0B,GAAG,MAAM,GAAG,IAAI;IAY9F,mDAAmD;IACnD,mBAAmB,CAAC,IAAI,EAAE,YAAY,EAAE,UAAU,GAAE,MAA0B,GAAG,MAAM,GAAG,IAAI;IAI9F;;OAEG;IACH,kBAAkB,CAAC,IAAI,EAAE,YAAY,EAAE,WAAW,GAAE,MAA2B,GAAG,MAAM,GAAG,IAAI;IAY/F,kDAAkD;IAClD,kBAAkB,CAAC,IAAI,EAAE,YAAY,EAAE,WAAW,GAAE,MAA2B,GAAG,MAAM,GAAG,IAAI;yCA1MpF,iBAAiB;6CAAjB,iBAAiB;CA6M7B"}
|
|
@@ -1,7 +1,17 @@
|
|
|
1
1
|
import { Injectable } from '@angular/core';
|
|
2
|
+
import { Metadata } from '@memberjunction/core';
|
|
3
|
+
import { UUIDsEqual } from '@memberjunction/global';
|
|
2
4
|
import { firstValueFrom } from 'rxjs';
|
|
3
5
|
import * as i0 from "@angular/core";
|
|
4
6
|
import * as i1 from "@angular/common/http";
|
|
7
|
+
const UPDATE_MY_AVATAR_MUTATION = `
|
|
8
|
+
mutation UpdateMyAvatar($ImageURL: String, $IconClass: String) {
|
|
9
|
+
UpdateMyAvatar(ImageURL: $ImageURL, IconClass: $IconClass) {
|
|
10
|
+
Success
|
|
11
|
+
ErrorMessage
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
`;
|
|
5
15
|
/**
|
|
6
16
|
* Service for managing user avatar operations across the application.
|
|
7
17
|
*
|
|
@@ -15,10 +25,40 @@ export class UserAvatarService {
|
|
|
15
25
|
this.http = http;
|
|
16
26
|
}
|
|
17
27
|
/**
|
|
18
|
-
*
|
|
19
|
-
*
|
|
28
|
+
* Sets the SIGNED-IN user's avatar through the server's self-service `UpdateMyAvatar` mutation.
|
|
29
|
+
*
|
|
30
|
+
* Use this rather than saving the `MJ: Users` row: the mutation needs no Update permission on
|
|
31
|
+
* `MJ: Users`, which locked-down deployments do not grant, and it writes only the two avatar
|
|
32
|
+
* columns. The server validates the values and always acts on the caller's own row.
|
|
20
33
|
*
|
|
21
|
-
* @param
|
|
34
|
+
* @param imageURL - a base64 image data URI or an http(s) URL; null clears it
|
|
35
|
+
* @param iconClass - a Font Awesome class list; null clears it
|
|
36
|
+
* @param provider - the GraphQL provider to call; defaults to the global provider
|
|
37
|
+
* @returns the server's outcome; never throws
|
|
38
|
+
*/
|
|
39
|
+
async UpdateMyAvatar(imageURL, iconClass, provider) {
|
|
40
|
+
try {
|
|
41
|
+
const gql = (provider ?? Metadata.Provider);
|
|
42
|
+
const response = (await gql.ExecuteGQL(UPDATE_MY_AVATAR_MUTATION, {
|
|
43
|
+
ImageURL: imageURL,
|
|
44
|
+
IconClass: iconClass
|
|
45
|
+
}));
|
|
46
|
+
const outcome = response?.UpdateMyAvatar;
|
|
47
|
+
if (outcome?.Success) {
|
|
48
|
+
return { Success: true };
|
|
49
|
+
}
|
|
50
|
+
return { Success: false, ErrorMessage: outcome?.ErrorMessage || 'The server did not save the avatar.' };
|
|
51
|
+
}
|
|
52
|
+
catch (error) {
|
|
53
|
+
return { Success: false, ErrorMessage: error instanceof Error ? error.message : String(error) };
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Syncs the signed-in user's avatar from an image URL (typically from the auth provider profile).
|
|
58
|
+
* Downloads the image, converts it to a Base64 data URI and saves it through {@link UpdateMyAvatar},
|
|
59
|
+
* then reloads `user` so it is clean and current.
|
|
60
|
+
*
|
|
61
|
+
* @param user - the signed-in user's MJUserEntity; any other user is refused
|
|
22
62
|
* @param imageUrl - URL to the image (can be from Microsoft Graph, Google, etc.)
|
|
23
63
|
* @param authHeaders - Optional headers for authenticated requests (e.g., { 'Authorization': 'Bearer token' })
|
|
24
64
|
* @returns Promise<boolean> - true if avatar was synced and saved, false otherwise
|
|
@@ -29,24 +69,23 @@ export class UserAvatarService {
|
|
|
29
69
|
console.warn('No image URL provided for avatar sync');
|
|
30
70
|
return false;
|
|
31
71
|
}
|
|
32
|
-
|
|
72
|
+
const provider = user.ProviderToUse;
|
|
73
|
+
if (!UUIDsEqual(user.ID, provider.CurrentUser?.ID)) {
|
|
74
|
+
console.warn('Avatar sync only updates the signed-in user; refusing to sync another user');
|
|
75
|
+
return false;
|
|
76
|
+
}
|
|
77
|
+
// Fetch the image as a blob and convert it to a Base64 data URI
|
|
33
78
|
const headers = authHeaders || {};
|
|
34
79
|
const blob = await firstValueFrom(this.http.get(imageUrl, { headers, responseType: 'blob' }));
|
|
35
|
-
// Convert blob to Base64 data URI
|
|
36
80
|
const base64 = await this.blobToBase64(blob);
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
// Save to database
|
|
41
|
-
const saved = await user.Save();
|
|
42
|
-
if (saved) {
|
|
43
|
-
console.log('Successfully synced avatar from image URL');
|
|
44
|
-
return true;
|
|
45
|
-
}
|
|
46
|
-
else {
|
|
47
|
-
console.warn('Failed to save avatar to database');
|
|
81
|
+
const result = await this.UpdateMyAvatar(base64, null, provider);
|
|
82
|
+
if (!result.Success) {
|
|
83
|
+
console.warn('Failed to save avatar:', result.ErrorMessage);
|
|
48
84
|
return false;
|
|
49
85
|
}
|
|
86
|
+
await user.Load(user.ID); // pick up the saved values; leaves the entity clean
|
|
87
|
+
console.log('Successfully synced avatar from image URL');
|
|
88
|
+
return true;
|
|
50
89
|
}
|
|
51
90
|
catch (error) {
|
|
52
91
|
console.warn('Could not sync avatar from image URL:', error);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"user-avatar.service.js","sourceRoot":"","sources":["../../src/lib/user-avatar.service.ts"],"names":[],"mappings":"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","sourcesContent":["import { Injectable } from '@angular/core';\nimport { HttpClient } from '@angular/common/http';\nimport { MJUserEntity } from '@memberjunction/core-entities';\nimport { firstValueFrom } from 'rxjs';\n\n/**\n * Service for managing user avatar operations across the application.\n *\n * NOTE: This service does NOT depend on any Explorer-specific packages to remain\n * usable across different Angular applications. All auth provider logic should\n * be handled by the calling code.\n */\n@Injectable({\n providedIn: 'root'\n})\nexport class UserAvatarService {\n constructor(private http: HttpClient) {}\n\n /**\n * Syncs user avatar from an image URL (typically from auth provider profile).\n * Downloads the image, converts to Base64, and saves to the user entity.\n *\n * @param user - The MJUserEntity to update with avatar data\n * @param imageUrl - URL to the image (can be from Microsoft Graph, Google, etc.)\n * @param authHeaders - Optional headers for authenticated requests (e.g., { 'Authorization': 'Bearer token' })\n * @returns Promise<boolean> - true if avatar was synced and saved, false otherwise\n */\n async SyncFromImageUrl(\n user: MJUserEntity,\n imageUrl: string,\n authHeaders?: Record<string, string>\n ): Promise<boolean> {\n try {\n if (!imageUrl || imageUrl.trim().length === 0) {\n console.warn('No image URL provided for avatar sync');\n return false;\n }\n\n // Fetch the image as a blob\n const headers: Record<string, string> = authHeaders || {};\n const blob: Blob = await firstValueFrom(\n this.http.get(imageUrl, { headers, responseType: 'blob' })\n );\n\n // Convert blob to Base64 data URI\n const base64 = await this.blobToBase64(blob);\n\n // Update user entity\n user.UserImageURL = base64;\n user.UserImageIconClass = null; // Clear icon if we have an image\n\n // Save to database\n const saved = await user.Save();\n\n if (saved) {\n console.log('Successfully synced avatar from image URL');\n return true;\n } else {\n console.warn('Failed to save avatar to database');\n return false;\n }\n } catch (error) {\n console.warn('Could not sync avatar from image URL:', error);\n return false;\n }\n }\n\n /** @deprecated Use {@link SyncFromImageUrl}. */\n async syncFromImageUrl(\n user: MJUserEntity,\n imageUrl: string,\n authHeaders?: Record<string, string>\n ): Promise<boolean> {\n return this.SyncFromImageUrl(user, imageUrl, authHeaders);\n }\n\n /**\n * Converts a Blob to a Base64 data URI string\n * Returns format: \"data:image/png;base64,iVBORw0KG...\"\n */\n private blobToBase64(blob: Blob): Promise<string> {\n return new Promise((resolve, reject) => {\n const reader = new FileReader();\n reader.onloadend = () => resolve(reader.result as string);\n reader.onerror = reject;\n reader.readAsDataURL(blob);\n });\n }\n\n /**\n * Converts a File to a Base64 data URI string\n * Used for file uploads in settings UI\n */\n FileToBase64(file: File): Promise<string> {\n return new Promise((resolve, reject) => {\n const reader = new FileReader();\n reader.onload = () => resolve(reader.result as string);\n reader.onerror = error => reject(error);\n reader.readAsDataURL(file);\n });\n }\n\n /** @deprecated Use {@link FileToBase64}. */\n fileToBase64(file: File): Promise<string> {\n return this.FileToBase64(file);\n }\n\n /**\n * Validates if a string is a valid URL\n */\n IsValidUrl(url: string): boolean {\n if (!url || url.trim().length === 0) {\n return false;\n }\n\n try {\n new URL(url);\n return true;\n } catch {\n return false;\n }\n }\n\n /** @deprecated Use {@link IsValidUrl}. */\n isValidUrl(url: string): boolean {\n return this.IsValidUrl(url);\n }\n\n /**\n * Validates if a string is a valid Base64 data URI\n */\n IsValidBase64DataUri(dataUri: string): boolean {\n if (!dataUri || !dataUri.startsWith('data:')) {\n return false;\n }\n\n const regex = /^data:image\\/(png|jpeg|jpg|gif|webp);base64,/;\n return regex.test(dataUri);\n }\n\n /** @deprecated Use {@link IsValidBase64DataUri}. */\n isValidBase64DataUri(dataUri: string): boolean {\n return this.IsValidBase64DataUri(dataUri);\n }\n\n /**\n * Gets the display URL for an avatar based on user settings\n * Priority: UserImageURL > UserImageIconClass > default\n *\n * @param user - The MJUserEntity\n * @param defaultUrl - Optional default URL if no avatar is set\n * @returns The URL to display, or null if using an icon\n */\n GetAvatarDisplayUrl(user: MJUserEntity, defaultUrl: string = 'assets/user.png'): string | null {\n if (user.UserImageURL) {\n return user.UserImageURL;\n }\n\n if (user.UserImageIconClass) {\n return null; // Indicates icon should be used instead\n }\n\n return defaultUrl;\n }\n\n /** @deprecated Use {@link GetAvatarDisplayUrl}. */\n getAvatarDisplayUrl(user: MJUserEntity, defaultUrl: string = 'assets/user.png'): string | null {\n return this.GetAvatarDisplayUrl(user, defaultUrl);\n }\n\n /**\n * Gets the icon class for an avatar if using icon mode\n */\n GetAvatarIconClass(user: MJUserEntity, defaultIcon: string = 'fa-solid fa-user'): string | null {\n if (user.UserImageIconClass) {\n return user.UserImageIconClass;\n }\n\n if (!user.UserImageURL) {\n return defaultIcon;\n }\n\n return null;\n }\n\n /** @deprecated Use {@link GetAvatarIconClass}. */\n getAvatarIconClass(user: MJUserEntity, defaultIcon: string = 'fa-solid fa-user'): string | null {\n return this.GetAvatarIconClass(user, defaultIcon);\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"user-avatar.service.js","sourceRoot":"","sources":["../../src/lib/user-avatar.service.ts"],"names":[],"mappings":"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","sourcesContent":["import { Injectable } from '@angular/core';\nimport { HttpClient } from '@angular/common/http';\nimport { IEntityDataProvider, IMetadataProvider, Metadata } from '@memberjunction/core';\nimport { MJUserEntity } from '@memberjunction/core-entities';\nimport { UUIDsEqual } from '@memberjunction/global';\nimport { GraphQLDataProvider } from '@memberjunction/graphql-dataprovider';\nimport { firstValueFrom } from 'rxjs';\n\n/** The outcome of {@link UserAvatarService.UpdateMyAvatar}. */\nexport interface UpdateMyAvatarResult {\n Success: boolean;\n /** The server's reason when `Success` is false. */\n ErrorMessage?: string;\n}\n\nconst UPDATE_MY_AVATAR_MUTATION = `\n mutation UpdateMyAvatar($ImageURL: String, $IconClass: String) {\n UpdateMyAvatar(ImageURL: $ImageURL, IconClass: $IconClass) {\n Success\n ErrorMessage\n }\n }\n`;\n\n/**\n * Service for managing user avatar operations across the application.\n *\n * NOTE: This service does NOT depend on any Explorer-specific packages to remain\n * usable across different Angular applications. All auth provider logic should\n * be handled by the calling code.\n */\n@Injectable({\n providedIn: 'root'\n})\nexport class UserAvatarService {\n constructor(private http: HttpClient) {}\n\n /**\n * Sets the SIGNED-IN user's avatar through the server's self-service `UpdateMyAvatar` mutation.\n *\n * Use this rather than saving the `MJ: Users` row: the mutation needs no Update permission on\n * `MJ: Users`, which locked-down deployments do not grant, and it writes only the two avatar\n * columns. The server validates the values and always acts on the caller's own row.\n *\n * @param imageURL - a base64 image data URI or an http(s) URL; null clears it\n * @param iconClass - a Font Awesome class list; null clears it\n * @param provider - the GraphQL provider to call; defaults to the global provider\n * @returns the server's outcome; never throws\n */\n async UpdateMyAvatar(\n imageURL: string | null,\n iconClass: string | null,\n provider?: IMetadataProvider | IEntityDataProvider\n ): Promise<UpdateMyAvatarResult> {\n try {\n const gql = (provider ?? Metadata.Provider) as GraphQLDataProvider;\n const response = (await gql.ExecuteGQL(UPDATE_MY_AVATAR_MUTATION, {\n ImageURL: imageURL,\n IconClass: iconClass\n })) as { UpdateMyAvatar?: UpdateMyAvatarResult } | null;\n const outcome = response?.UpdateMyAvatar;\n if (outcome?.Success) {\n return { Success: true };\n }\n return { Success: false, ErrorMessage: outcome?.ErrorMessage || 'The server did not save the avatar.' };\n } catch (error) {\n return { Success: false, ErrorMessage: error instanceof Error ? error.message : String(error) };\n }\n }\n\n /**\n * Syncs the signed-in user's avatar from an image URL (typically from the auth provider profile).\n * Downloads the image, converts it to a Base64 data URI and saves it through {@link UpdateMyAvatar},\n * then reloads `user` so it is clean and current.\n *\n * @param user - the signed-in user's MJUserEntity; any other user is refused\n * @param imageUrl - URL to the image (can be from Microsoft Graph, Google, etc.)\n * @param authHeaders - Optional headers for authenticated requests (e.g., { 'Authorization': 'Bearer token' })\n * @returns Promise<boolean> - true if avatar was synced and saved, false otherwise\n */\n async SyncFromImageUrl(\n user: MJUserEntity,\n imageUrl: string,\n authHeaders?: Record<string, string>\n ): Promise<boolean> {\n try {\n if (!imageUrl || imageUrl.trim().length === 0) {\n console.warn('No image URL provided for avatar sync');\n return false;\n }\n const provider = user.ProviderToUse as GraphQLDataProvider;\n if (!UUIDsEqual(user.ID, provider.CurrentUser?.ID)) {\n console.warn('Avatar sync only updates the signed-in user; refusing to sync another user');\n return false;\n }\n\n // Fetch the image as a blob and convert it to a Base64 data URI\n const headers: Record<string, string> = authHeaders || {};\n const blob: Blob = await firstValueFrom(\n this.http.get(imageUrl, { headers, responseType: 'blob' })\n );\n const base64 = await this.blobToBase64(blob);\n\n const result = await this.UpdateMyAvatar(base64, null, provider);\n if (!result.Success) {\n console.warn('Failed to save avatar:', result.ErrorMessage);\n return false;\n }\n await user.Load(user.ID); // pick up the saved values; leaves the entity clean\n console.log('Successfully synced avatar from image URL');\n return true;\n } catch (error) {\n console.warn('Could not sync avatar from image URL:', error);\n return false;\n }\n }\n\n /** @deprecated Use {@link SyncFromImageUrl}. */\n async syncFromImageUrl(\n user: MJUserEntity,\n imageUrl: string,\n authHeaders?: Record<string, string>\n ): Promise<boolean> {\n return this.SyncFromImageUrl(user, imageUrl, authHeaders);\n }\n\n /**\n * Converts a Blob to a Base64 data URI string\n * Returns format: \"data:image/png;base64,iVBORw0KG...\"\n */\n private blobToBase64(blob: Blob): Promise<string> {\n return new Promise((resolve, reject) => {\n const reader = new FileReader();\n reader.onloadend = () => resolve(reader.result as string);\n reader.onerror = reject;\n reader.readAsDataURL(blob);\n });\n }\n\n /**\n * Converts a File to a Base64 data URI string\n * Used for file uploads in settings UI\n */\n FileToBase64(file: File): Promise<string> {\n return new Promise((resolve, reject) => {\n const reader = new FileReader();\n reader.onload = () => resolve(reader.result as string);\n reader.onerror = error => reject(error);\n reader.readAsDataURL(file);\n });\n }\n\n /** @deprecated Use {@link FileToBase64}. */\n fileToBase64(file: File): Promise<string> {\n return this.FileToBase64(file);\n }\n\n /**\n * Validates if a string is a valid URL\n */\n IsValidUrl(url: string): boolean {\n if (!url || url.trim().length === 0) {\n return false;\n }\n\n try {\n new URL(url);\n return true;\n } catch {\n return false;\n }\n }\n\n /** @deprecated Use {@link IsValidUrl}. */\n isValidUrl(url: string): boolean {\n return this.IsValidUrl(url);\n }\n\n /**\n * Validates if a string is a valid Base64 data URI\n */\n IsValidBase64DataUri(dataUri: string): boolean {\n if (!dataUri || !dataUri.startsWith('data:')) {\n return false;\n }\n\n const regex = /^data:image\\/(png|jpeg|jpg|gif|webp);base64,/;\n return regex.test(dataUri);\n }\n\n /** @deprecated Use {@link IsValidBase64DataUri}. */\n isValidBase64DataUri(dataUri: string): boolean {\n return this.IsValidBase64DataUri(dataUri);\n }\n\n /**\n * Gets the display URL for an avatar based on user settings\n * Priority: UserImageURL > UserImageIconClass > default\n *\n * @param user - The MJUserEntity\n * @param defaultUrl - Optional default URL if no avatar is set\n * @returns The URL to display, or null if using an icon\n */\n GetAvatarDisplayUrl(user: MJUserEntity, defaultUrl: string = 'assets/user.png'): string | null {\n if (user.UserImageURL) {\n return user.UserImageURL;\n }\n\n if (user.UserImageIconClass) {\n return null; // Indicates icon should be used instead\n }\n\n return defaultUrl;\n }\n\n /** @deprecated Use {@link GetAvatarDisplayUrl}. */\n getAvatarDisplayUrl(user: MJUserEntity, defaultUrl: string = 'assets/user.png'): string | null {\n return this.GetAvatarDisplayUrl(user, defaultUrl);\n }\n\n /**\n * Gets the icon class for an avatar if using icon mode\n */\n GetAvatarIconClass(user: MJUserEntity, defaultIcon: string = 'fa-solid fa-user'): string | null {\n if (user.UserImageIconClass) {\n return user.UserImageIconClass;\n }\n\n if (!user.UserImageURL) {\n return defaultIcon;\n }\n\n return null;\n }\n\n /** @deprecated Use {@link GetAvatarIconClass}. */\n getAvatarIconClass(user: MJUserEntity, defaultIcon: string = 'fa-solid fa-user'): string | null {\n return this.GetAvatarIconClass(user, defaultIcon);\n }\n}\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@memberjunction/ng-user-avatar",
|
|
3
|
-
"version": "6.2.0-edge.
|
|
3
|
+
"version": "6.2.0-edge.4",
|
|
4
4
|
"description": "MemberJunction: Angular User Avatar Service - Manages user avatar synchronization from auth providers and avatar operations",
|
|
5
5
|
"main": "./dist/public-api.js",
|
|
6
6
|
"typings": "./dist/public-api.d.ts",
|
|
@@ -27,9 +27,10 @@
|
|
|
27
27
|
"rxjs": "^7.8.2"
|
|
28
28
|
},
|
|
29
29
|
"dependencies": {
|
|
30
|
-
"@memberjunction/core": "6.2.0-edge.
|
|
31
|
-
"@memberjunction/core-entities": "6.2.0-edge.
|
|
32
|
-
"@memberjunction/global": "6.2.0-edge.
|
|
30
|
+
"@memberjunction/core": "6.2.0-edge.4",
|
|
31
|
+
"@memberjunction/core-entities": "6.2.0-edge.4",
|
|
32
|
+
"@memberjunction/global": "6.2.0-edge.4",
|
|
33
|
+
"@memberjunction/graphql-dataprovider": "6.2.0-edge.4",
|
|
33
34
|
"tslib": "^2.8.1"
|
|
34
35
|
},
|
|
35
36
|
"sideEffects": false,
|