support color avatar
This commit is contained in:
@@ -38,6 +38,8 @@ pub struct ProfileSummary {
|
|||||||
pub email: Option<String>,
|
pub email: Option<String>,
|
||||||
pub avatar_data_url: Option<String>,
|
pub avatar_data_url: Option<String>,
|
||||||
pub avatar_icon: 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 avatar_label: String,
|
||||||
pub path: String,
|
pub path: String,
|
||||||
}
|
}
|
||||||
@@ -69,6 +71,8 @@ pub struct AssociatedProfileSummary {
|
|||||||
pub name: String,
|
pub name: String,
|
||||||
pub avatar_data_url: Option<String>,
|
pub avatar_data_url: Option<String>,
|
||||||
pub avatar_icon: 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 avatar_label: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -79,6 +83,8 @@ pub struct BookmarkAssociatedProfileSummary {
|
|||||||
pub name: String,
|
pub name: String,
|
||||||
pub avatar_data_url: Option<String>,
|
pub avatar_data_url: Option<String>,
|
||||||
pub avatar_icon: 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 avatar_label: String,
|
||||||
pub bookmark_path: String,
|
pub bookmark_path: String,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -159,6 +159,12 @@ fn build_profile_summary(
|
|||||||
.and_then(Value::as_str)
|
.and_then(Value::as_str)
|
||||||
.filter(|value| !value.is_empty())
|
.filter(|value| !value.is_empty())
|
||||||
.map(str::to_string);
|
.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
|
let avatar_label = name
|
||||||
.chars()
|
.chars()
|
||||||
.find(|character| !character.is_whitespace())
|
.find(|character| !character.is_whitespace())
|
||||||
@@ -171,6 +177,8 @@ fn build_profile_summary(
|
|||||||
email,
|
email,
|
||||||
avatar_data_url,
|
avatar_data_url,
|
||||||
avatar_icon,
|
avatar_icon,
|
||||||
|
default_avatar_fill_color,
|
||||||
|
default_avatar_stroke_color,
|
||||||
avatar_label,
|
avatar_label,
|
||||||
path: profile_path.display().to_string(),
|
path: profile_path.display().to_string(),
|
||||||
}
|
}
|
||||||
@@ -262,6 +270,8 @@ fn scan_extensions_for_profile(
|
|||||||
name: profile.name.clone(),
|
name: profile.name.clone(),
|
||||||
avatar_data_url: profile.avatar_data_url.clone(),
|
avatar_data_url: profile.avatar_data_url.clone(),
|
||||||
avatar_icon: profile.avatar_icon.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(),
|
avatar_label: profile.avatar_label.clone(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -423,6 +433,8 @@ fn collect_bookmarks(
|
|||||||
name: profile.name.clone(),
|
name: profile.name.clone(),
|
||||||
avatar_data_url: profile.avatar_data_url.clone(),
|
avatar_data_url: profile.avatar_data_url.clone(),
|
||||||
avatar_icon: profile.avatar_icon.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(),
|
avatar_label: profile.avatar_label.clone(),
|
||||||
bookmark_path,
|
bookmark_path,
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -10,6 +10,8 @@ export type ProfileSummary = {
|
|||||||
email: string | null;
|
email: string | null;
|
||||||
avatarDataUrl: string | null;
|
avatarDataUrl: string | null;
|
||||||
avatarIcon: string | null;
|
avatarIcon: string | null;
|
||||||
|
defaultAvatarFillColor: number | null;
|
||||||
|
defaultAvatarStrokeColor: number | null;
|
||||||
avatarLabel: string;
|
avatarLabel: string;
|
||||||
path: string;
|
path: string;
|
||||||
};
|
};
|
||||||
@@ -35,6 +37,8 @@ export type AssociatedProfileSummary = {
|
|||||||
name: string;
|
name: string;
|
||||||
avatarDataUrl: string | null;
|
avatarDataUrl: string | null;
|
||||||
avatarIcon: string | null;
|
avatarIcon: string | null;
|
||||||
|
defaultAvatarFillColor: number | null;
|
||||||
|
defaultAvatarStrokeColor: number | null;
|
||||||
avatarLabel: string;
|
avatarLabel: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -43,6 +47,8 @@ export type BookmarkAssociatedProfileSummary = {
|
|||||||
name: string;
|
name: string;
|
||||||
avatarDataUrl: string | null;
|
avatarDataUrl: string | null;
|
||||||
avatarIcon: string | null;
|
avatarIcon: string | null;
|
||||||
|
defaultAvatarFillColor: number | null;
|
||||||
|
defaultAvatarStrokeColor: number | null;
|
||||||
avatarLabel: string;
|
avatarLabel: string;
|
||||||
bookmarkPath: string;
|
bookmarkPath: string;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -41,9 +41,39 @@ const avatarMap = Object.entries(avatarModules).reduce<Record<string, Record<str
|
|||||||
);
|
);
|
||||||
|
|
||||||
type ProfileAvatarLike =
|
type ProfileAvatarLike =
|
||||||
| Pick<ProfileSummary, "avatarDataUrl" | "avatarIcon">
|
| Pick<
|
||||||
| Pick<AssociatedProfileSummary, "avatarDataUrl" | "avatarIcon">
|
ProfileSummary,
|
||||||
| Pick<BookmarkAssociatedProfileSummary, "avatarDataUrl" | "avatarIcon">;
|
| "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(
|
export function profileAvatarSrc(
|
||||||
profile: ProfileAvatarLike,
|
profile: ProfileAvatarLike,
|
||||||
@@ -54,13 +84,18 @@ export function profileAvatarSrc(
|
|||||||
}
|
}
|
||||||
|
|
||||||
const avatarKey = normalizeAvatarIcon(profile.avatarIcon);
|
const avatarKey = normalizeAvatarIcon(profile.avatarIcon);
|
||||||
if (!avatarKey) {
|
if (avatarKey) {
|
||||||
return null;
|
const familyMap = browserFamilyId ? avatarMap[browserFamilyId] : undefined;
|
||||||
|
if (familyMap?.[avatarKey]) {
|
||||||
|
return familyMap[avatarKey];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const familyMap = browserFamilyId ? avatarMap[browserFamilyId] : undefined;
|
if (browserFamilyId === "chrome") {
|
||||||
if (familyMap?.[avatarKey]) {
|
return createChromeGeneratedAvatar(
|
||||||
return familyMap[avatarKey];
|
profile.defaultAvatarFillColor,
|
||||||
|
profile.defaultAvatarStrokeColor,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
@@ -74,3 +109,34 @@ function normalizeAvatarIcon(value: string | null | undefined) {
|
|||||||
const lastSegment = withoutQuery.split("/").pop() ?? withoutQuery;
|
const lastSegment = withoutQuery.split("/").pop() ?? withoutQuery;
|
||||||
return lastSegment.replace(/\.png$/i, "");
|
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