Align default avatar and fix colors in composer pills (#30739)
* fix: align default avatar in composer pills * fix: use correct color for avatar in composer pills when there is no image * test(e2e): add test for cider mention * chore: fix typo
This commit is contained in:
@@ -14,6 +14,9 @@ const CtrlOrMeta = process.platform === "darwin" ? "Meta" : "Control";
|
|||||||
test.describe("Composer", () => {
|
test.describe("Composer", () => {
|
||||||
test.use({
|
test.use({
|
||||||
displayName: "Janet",
|
displayName: "Janet",
|
||||||
|
botCreateOpts: {
|
||||||
|
displayName: "Bob",
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
test.use({
|
test.use({
|
||||||
@@ -94,5 +97,22 @@ test.describe("Composer", () => {
|
|||||||
).toBeVisible();
|
).toBeVisible();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("can send mention", { tag: "@screenshot" }, async ({ page, bot, app }) => {
|
||||||
|
// Set up a private room so we have another user to mention
|
||||||
|
await app.client.createRoom({
|
||||||
|
is_direct: true,
|
||||||
|
invite: [bot.credentials.userId],
|
||||||
|
});
|
||||||
|
await app.viewRoomByName("Bob");
|
||||||
|
|
||||||
|
const composer = page.getByRole("textbox", { name: "Send an unencrypted message…" });
|
||||||
|
await composer.pressSequentially("@bob");
|
||||||
|
await page.getByRole("option", { name: "Bob" }).click();
|
||||||
|
await expect(composer.getByText("Bob")).toBeVisible();
|
||||||
|
await expect(composer).toMatchScreenshot("mention.png");
|
||||||
|
await composer.press("Enter");
|
||||||
|
await expect(page.locator(".mx_EventTile_body", { hasText: "Bob" })).toBeVisible();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
Binary file not shown.
|
After Width: | Height: | Size: 1.6 KiB |
@@ -10,6 +10,7 @@ Please see LICENSE files in the repository root for full details.
|
|||||||
/* These are set in Javascript */
|
/* These are set in Javascript */
|
||||||
--avatar-letter: "";
|
--avatar-letter: "";
|
||||||
--avatar-background: unset;
|
--avatar-background: unset;
|
||||||
|
--avatar-color: unset;
|
||||||
--placeholder: "";
|
--placeholder: "";
|
||||||
|
|
||||||
position: relative;
|
position: relative;
|
||||||
@@ -54,6 +55,8 @@ Please see LICENSE files in the repository root for full details.
|
|||||||
span.mx_UserPill,
|
span.mx_UserPill,
|
||||||
span.mx_RoomPill,
|
span.mx_RoomPill,
|
||||||
span.mx_SpacePill {
|
span.mx_SpacePill {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
user-select: all;
|
user-select: all;
|
||||||
position: relative;
|
position: relative;
|
||||||
cursor: unset; /* We don't want indicate clickability */
|
cursor: unset; /* We don't want indicate clickability */
|
||||||
|
|||||||
+19
-4
@@ -13,14 +13,18 @@ import DMRoomMap from "./utils/DMRoomMap";
|
|||||||
import { mediaFromMxc } from "./customisations/Media";
|
import { mediaFromMxc } from "./customisations/Media";
|
||||||
import { isLocalRoom } from "./utils/localRoom/isLocalRoom";
|
import { isLocalRoom } from "./utils/localRoom/isLocalRoom";
|
||||||
import { getFirstGrapheme } from "./utils/strings";
|
import { getFirstGrapheme } from "./utils/strings";
|
||||||
|
import ThemeWatcher from "./settings/watchers/ThemeWatcher";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Hardcoded from the Compound colors.
|
* Hardcoded from the Compound colors.
|
||||||
* Shade for background as defined in the compound web implementation
|
* Shade for background as defined in the compound web implementation
|
||||||
* https://github.com/vector-im/compound-web/blob/main/src/components/Avatar
|
* https://github.com/vector-im/compound-web/blob/main/src/components/Avatar
|
||||||
*/
|
*/
|
||||||
const AVATAR_BG_COLORS = ["#e9f2ff", "#faeefb", "#e3f7ed", "#ffecf0", "#ffefe4", "#e3f5f8", "#f1efff", "#e0f8d9"];
|
const AVATAR_BG_LIGHT_COLORS = ["#e0f8d9", "#e3f5f8", "#faeefb", "#f1efff", "#ffecf0", "#ffefe4"];
|
||||||
const AVATAR_TEXT_COLORS = ["#043894", "#671481", "#004933", "#7e0642", "#850000", "#004077", "#4c05b5", "#004b00"];
|
const AVATAR_TEXT_LIGHT_COLORS = ["#005f00", "#00548c", "#822198", "#5d26cd", "#9f0850", "#9b2200"];
|
||||||
|
|
||||||
|
const AVATAR_BG_DARK_COLORS = ["#002600", "#001b4e", "#37004e", "#22006a", "#450018", "#470000"];
|
||||||
|
const AVATAR_TEXT_DARK_COLORS = ["#56c02c", "#21bacd", "#d991de", "#ad9cfe", "#fe84a2", "#f6913d"];
|
||||||
|
|
||||||
// Not to be used for BaseAvatar urls as that has similar default avatar fallback already
|
// Not to be used for BaseAvatar urls as that has similar default avatar fallback already
|
||||||
export function avatarUrlForMember(
|
export function avatarUrlForMember(
|
||||||
@@ -42,6 +46,13 @@ export function avatarUrlForMember(
|
|||||||
return url;
|
return url;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determines if the current theme is dark
|
||||||
|
*/
|
||||||
|
function isDarkTheme(): boolean {
|
||||||
|
return new ThemeWatcher().getEffectiveTheme() === "dark";
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Determines the HEX color to use in the avatar pills
|
* Determines the HEX color to use in the avatar pills
|
||||||
* @param id the user or room ID
|
* @param id the user or room ID
|
||||||
@@ -51,7 +62,8 @@ export function getAvatarTextColor(id: string): string {
|
|||||||
// eslint-disable-next-line react-hooks/rules-of-hooks
|
// eslint-disable-next-line react-hooks/rules-of-hooks
|
||||||
const index = useIdColorHash(id);
|
const index = useIdColorHash(id);
|
||||||
|
|
||||||
return AVATAR_TEXT_COLORS[index - 1];
|
// Use light colors by default
|
||||||
|
return isDarkTheme() ? AVATAR_TEXT_DARK_COLORS[index - 1] : AVATAR_TEXT_LIGHT_COLORS[index - 1];
|
||||||
}
|
}
|
||||||
|
|
||||||
export function avatarUrlForUser(
|
export function avatarUrlForUser(
|
||||||
@@ -103,7 +115,10 @@ export function defaultAvatarUrlForString(s: string): string {
|
|||||||
// overwritten color value in custom themes
|
// overwritten color value in custom themes
|
||||||
const cssVariable = `--avatar-background-colors_${colorIndex}`;
|
const cssVariable = `--avatar-background-colors_${colorIndex}`;
|
||||||
const cssValue = getComputedStyle(document.body).getPropertyValue(cssVariable);
|
const cssValue = getComputedStyle(document.body).getPropertyValue(cssVariable);
|
||||||
const color = cssValue || AVATAR_BG_COLORS[colorIndex - 1];
|
// Light colors are the default
|
||||||
|
const color =
|
||||||
|
cssValue || isDarkTheme() ? AVATAR_BG_DARK_COLORS[colorIndex - 1] : AVATAR_BG_LIGHT_COLORS[colorIndex - 1];
|
||||||
|
|
||||||
let dataUrl = colorToDataURLCache.get(color);
|
let dataUrl = colorToDataURLCache.get(color);
|
||||||
if (!dataUrl) {
|
if (!dataUrl) {
|
||||||
// validate color as this can come from account_data
|
// validate color as this can come from account_data
|
||||||
|
|||||||
+15
-3
@@ -297,7 +297,12 @@ export abstract class PillPart extends BasePart implements IPillPart {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// helper method for subclasses
|
// helper method for subclasses
|
||||||
protected setAvatarVars(node: HTMLElement, avatarUrl: string, initialLetter: string): void {
|
protected setAvatarVars(
|
||||||
|
node: HTMLElement,
|
||||||
|
avatarUrl: string,
|
||||||
|
initialLetter: string,
|
||||||
|
avatarTextColor?: string,
|
||||||
|
): void {
|
||||||
const avatarBackground = `url('${avatarUrl}')`;
|
const avatarBackground = `url('${avatarUrl}')`;
|
||||||
const avatarLetter = `'${initialLetter}'`;
|
const avatarLetter = `'${initialLetter}'`;
|
||||||
// check if the value is changing,
|
// check if the value is changing,
|
||||||
@@ -308,6 +313,9 @@ export abstract class PillPart extends BasePart implements IPillPart {
|
|||||||
if (node.style.getPropertyValue("--avatar-letter") !== avatarLetter) {
|
if (node.style.getPropertyValue("--avatar-letter") !== avatarLetter) {
|
||||||
node.style.setProperty("--avatar-letter", avatarLetter);
|
node.style.setProperty("--avatar-letter", avatarLetter);
|
||||||
}
|
}
|
||||||
|
if (avatarTextColor && node.style.getPropertyValue("--avatar-color") !== avatarTextColor) {
|
||||||
|
node.style.setProperty("--avatar-color", avatarTextColor);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public serialize(): ISerializedPillPart {
|
public serialize(): ISerializedPillPart {
|
||||||
@@ -421,11 +429,13 @@ class RoomPillPart extends PillPart {
|
|||||||
protected setAvatar(node: HTMLElement): void {
|
protected setAvatar(node: HTMLElement): void {
|
||||||
let initialLetter = "";
|
let initialLetter = "";
|
||||||
let avatarUrl = Avatar.avatarUrlForRoom(this.room ?? null, 16, 16, "crop");
|
let avatarUrl = Avatar.avatarUrlForRoom(this.room ?? null, 16, 16, "crop");
|
||||||
|
let avatarTextColor: string | undefined;
|
||||||
if (!avatarUrl) {
|
if (!avatarUrl) {
|
||||||
initialLetter = Avatar.getInitialLetter(this.room?.name || this.resourceId) ?? "";
|
initialLetter = Avatar.getInitialLetter(this.room?.name || this.resourceId) ?? "";
|
||||||
avatarUrl = Avatar.defaultAvatarUrlForString(this.room?.roomId ?? this.resourceId);
|
avatarUrl = Avatar.defaultAvatarUrlForString(this.room?.roomId ?? this.resourceId);
|
||||||
|
avatarTextColor = Avatar.getAvatarTextColor(this.room?.roomId ?? this.resourceId);
|
||||||
}
|
}
|
||||||
this.setAvatarVars(node, avatarUrl, initialLetter);
|
this.setAvatarVars(node, avatarUrl, initialLetter, avatarTextColor);
|
||||||
}
|
}
|
||||||
|
|
||||||
public get type(): IPillPart["type"] {
|
public get type(): IPillPart["type"] {
|
||||||
@@ -479,10 +489,12 @@ class UserPillPart extends PillPart {
|
|||||||
const defaultAvatarUrl = Avatar.defaultAvatarUrlForString(this.member.userId);
|
const defaultAvatarUrl = Avatar.defaultAvatarUrlForString(this.member.userId);
|
||||||
const avatarUrl = Avatar.avatarUrlForMember(this.member, 16, 16, "crop");
|
const avatarUrl = Avatar.avatarUrlForMember(this.member, 16, 16, "crop");
|
||||||
let initialLetter = "";
|
let initialLetter = "";
|
||||||
|
let avatarTextColor: string | undefined;
|
||||||
if (avatarUrl === defaultAvatarUrl) {
|
if (avatarUrl === defaultAvatarUrl) {
|
||||||
initialLetter = Avatar.getInitialLetter(name) ?? "";
|
initialLetter = Avatar.getInitialLetter(name) ?? "";
|
||||||
|
avatarTextColor = Avatar.getAvatarTextColor(this.member.userId);
|
||||||
}
|
}
|
||||||
this.setAvatarVars(node, avatarUrl, initialLetter);
|
this.setAvatarVars(node, avatarUrl, initialLetter, avatarTextColor);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected onClick = (): void => {
|
protected onClick = (): void => {
|
||||||
|
|||||||
Reference in New Issue
Block a user