support color avatar
This commit is contained in:
@@ -38,6 +38,8 @@ pub struct ProfileSummary {
|
||||
pub email: Option<String>,
|
||||
pub avatar_data_url: Option<String>,
|
||||
pub avatar_icon: Option<String>,
|
||||
pub default_avatar_fill_color: Option<i64>,
|
||||
pub default_avatar_stroke_color: Option<i64>,
|
||||
pub avatar_label: String,
|
||||
pub path: String,
|
||||
}
|
||||
@@ -69,6 +71,8 @@ pub struct AssociatedProfileSummary {
|
||||
pub name: String,
|
||||
pub avatar_data_url: Option<String>,
|
||||
pub avatar_icon: Option<String>,
|
||||
pub default_avatar_fill_color: Option<i64>,
|
||||
pub default_avatar_stroke_color: Option<i64>,
|
||||
pub avatar_label: String,
|
||||
}
|
||||
|
||||
@@ -79,6 +83,8 @@ pub struct BookmarkAssociatedProfileSummary {
|
||||
pub name: String,
|
||||
pub avatar_data_url: Option<String>,
|
||||
pub avatar_icon: Option<String>,
|
||||
pub default_avatar_fill_color: Option<i64>,
|
||||
pub default_avatar_stroke_color: Option<i64>,
|
||||
pub avatar_label: String,
|
||||
pub bookmark_path: String,
|
||||
}
|
||||
|
||||
@@ -159,6 +159,12 @@ fn build_profile_summary(
|
||||
.and_then(Value::as_str)
|
||||
.filter(|value| !value.is_empty())
|
||||
.map(str::to_string);
|
||||
let default_avatar_fill_color = profile_info
|
||||
.and_then(|value| value.get("default_avatar_fill_color"))
|
||||
.and_then(Value::as_i64);
|
||||
let default_avatar_stroke_color = profile_info
|
||||
.and_then(|value| value.get("default_avatar_stroke_color"))
|
||||
.and_then(Value::as_i64);
|
||||
let avatar_label = name
|
||||
.chars()
|
||||
.find(|character| !character.is_whitespace())
|
||||
@@ -171,6 +177,8 @@ fn build_profile_summary(
|
||||
email,
|
||||
avatar_data_url,
|
||||
avatar_icon,
|
||||
default_avatar_fill_color,
|
||||
default_avatar_stroke_color,
|
||||
avatar_label,
|
||||
path: profile_path.display().to_string(),
|
||||
}
|
||||
@@ -262,6 +270,8 @@ fn scan_extensions_for_profile(
|
||||
name: profile.name.clone(),
|
||||
avatar_data_url: profile.avatar_data_url.clone(),
|
||||
avatar_icon: profile.avatar_icon.clone(),
|
||||
default_avatar_fill_color: profile.default_avatar_fill_color,
|
||||
default_avatar_stroke_color: profile.default_avatar_stroke_color,
|
||||
avatar_label: profile.avatar_label.clone(),
|
||||
});
|
||||
}
|
||||
@@ -423,6 +433,8 @@ fn collect_bookmarks(
|
||||
name: profile.name.clone(),
|
||||
avatar_data_url: profile.avatar_data_url.clone(),
|
||||
avatar_icon: profile.avatar_icon.clone(),
|
||||
default_avatar_fill_color: profile.default_avatar_fill_color,
|
||||
default_avatar_stroke_color: profile.default_avatar_stroke_color,
|
||||
avatar_label: profile.avatar_label.clone(),
|
||||
bookmark_path,
|
||||
});
|
||||
|
||||
@@ -10,6 +10,8 @@ export type ProfileSummary = {
|
||||
email: string | null;
|
||||
avatarDataUrl: string | null;
|
||||
avatarIcon: string | null;
|
||||
defaultAvatarFillColor: number | null;
|
||||
defaultAvatarStrokeColor: number | null;
|
||||
avatarLabel: string;
|
||||
path: string;
|
||||
};
|
||||
@@ -35,6 +37,8 @@ export type AssociatedProfileSummary = {
|
||||
name: string;
|
||||
avatarDataUrl: string | null;
|
||||
avatarIcon: string | null;
|
||||
defaultAvatarFillColor: number | null;
|
||||
defaultAvatarStrokeColor: number | null;
|
||||
avatarLabel: string;
|
||||
};
|
||||
|
||||
@@ -43,6 +47,8 @@ export type BookmarkAssociatedProfileSummary = {
|
||||
name: string;
|
||||
avatarDataUrl: string | null;
|
||||
avatarIcon: string | null;
|
||||
defaultAvatarFillColor: number | null;
|
||||
defaultAvatarStrokeColor: number | null;
|
||||
avatarLabel: string;
|
||||
bookmarkPath: string;
|
||||
};
|
||||
|
||||
@@ -41,9 +41,39 @@ const avatarMap = Object.entries(avatarModules).reduce<Record<string, Record<str
|
||||
);
|
||||
|
||||
type ProfileAvatarLike =
|
||||
| Pick<ProfileSummary, "avatarDataUrl" | "avatarIcon">
|
||||
| Pick<AssociatedProfileSummary, "avatarDataUrl" | "avatarIcon">
|
||||
| Pick<BookmarkAssociatedProfileSummary, "avatarDataUrl" | "avatarIcon">;
|
||||
| Pick<
|
||||
ProfileSummary,
|
||||
| "avatarDataUrl"
|
||||
| "avatarIcon"
|
||||
| "defaultAvatarFillColor"
|
||||
| "defaultAvatarStrokeColor"
|
||||
>
|
||||
| Pick<
|
||||
AssociatedProfileSummary,
|
||||
| "avatarDataUrl"
|
||||
| "avatarIcon"
|
||||
| "defaultAvatarFillColor"
|
||||
| "defaultAvatarStrokeColor"
|
||||
>
|
||||
| Pick<
|
||||
BookmarkAssociatedProfileSummary,
|
||||
| "avatarDataUrl"
|
||||
| "avatarIcon"
|
||||
| "defaultAvatarFillColor"
|
||||
| "defaultAvatarStrokeColor"
|
||||
>;
|
||||
|
||||
const chromeProfileSvg = `
|
||||
<svg version="1.1" width="96" height="96" viewBox="0 0 96 96" xmlns="http://www.w3.org/2000/svg">
|
||||
<circle cx="48" cy="48" r="48" fill="#{bg_rgb}"/>
|
||||
<g fill="#{fg_rgb}">
|
||||
<circle cx="48" cy="32" r="12"/>
|
||||
<path d="M24,68 C20,50 40,48 48,48 C56,48 76,50 72,68 Z"/>
|
||||
</g>
|
||||
</svg>
|
||||
`.trim();
|
||||
|
||||
const chromeGeneratedAvatarCache = new Map<string, string>();
|
||||
|
||||
export function profileAvatarSrc(
|
||||
profile: ProfileAvatarLike,
|
||||
@@ -54,14 +84,19 @@ export function profileAvatarSrc(
|
||||
}
|
||||
|
||||
const avatarKey = normalizeAvatarIcon(profile.avatarIcon);
|
||||
if (!avatarKey) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (avatarKey) {
|
||||
const familyMap = browserFamilyId ? avatarMap[browserFamilyId] : undefined;
|
||||
if (familyMap?.[avatarKey]) {
|
||||
return familyMap[avatarKey];
|
||||
}
|
||||
}
|
||||
|
||||
if (browserFamilyId === "chrome") {
|
||||
return createChromeGeneratedAvatar(
|
||||
profile.defaultAvatarFillColor,
|
||||
profile.defaultAvatarStrokeColor,
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
@@ -74,3 +109,34 @@ function normalizeAvatarIcon(value: string | null | undefined) {
|
||||
const lastSegment = withoutQuery.split("/").pop() ?? withoutQuery;
|
||||
return lastSegment.replace(/\.png$/i, "");
|
||||
}
|
||||
|
||||
function createChromeGeneratedAvatar(
|
||||
backgroundArgb: number | null | undefined,
|
||||
foregroundArgb: number | null | undefined,
|
||||
) {
|
||||
if (backgroundArgb == null || foregroundArgb == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const cacheKey = `${backgroundArgb}:${foregroundArgb}`;
|
||||
const cached = chromeGeneratedAvatarCache.get(cacheKey);
|
||||
if (cached) {
|
||||
return cached;
|
||||
}
|
||||
|
||||
const backgroundRgb = argbToRgbHex(backgroundArgb);
|
||||
const foregroundRgb = argbToRgbHex(foregroundArgb);
|
||||
const svg = chromeProfileSvg
|
||||
.replace("{bg_rgb}", backgroundRgb)
|
||||
.replace("{fg_rgb}", foregroundRgb);
|
||||
const dataUrl = `data:image/svg+xml;charset=UTF-8,${encodeURIComponent(svg)}`;
|
||||
|
||||
chromeGeneratedAvatarCache.set(cacheKey, dataUrl);
|
||||
return dataUrl;
|
||||
}
|
||||
|
||||
function argbToRgbHex(argbValue: number) {
|
||||
const unsignedArgb = argbValue >>> 0;
|
||||
const rgb = unsignedArgb & 0x00ff_ffff;
|
||||
return rgb.toString(16).padStart(6, "0");
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user