Skip to content

Commit 59214b1

Browse files
committed
fix: make sure workspace avatars display correctly when URLs fail to load
1 parent 8403dd5 commit 59214b1

File tree

4 files changed

+52
-9
lines changed

4 files changed

+52
-9
lines changed

site/src/components/AvatarData/AvatarData.tsx

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,19 +8,34 @@ export interface AvatarDataProps {
88
subtitle?: ReactNode;
99
src?: string;
1010
avatar?: React.ReactNode;
11+
12+
/**
13+
* Useful for when you need to pass in a ReactNode for the title of the
14+
* component.
15+
*
16+
* MUI will try to take any string titles and turn them into the first
17+
* character, but if you pass in a ReactNode, MUI can't do that, because it
18+
* has no way to reliably grab the text content during renders.
19+
*
20+
* Tried writing some layout effect/JSX parsing logic to do the extraction,
21+
* but it added complexity and render overhead, and wasn't reliable enough
22+
*/
23+
fallbackLetter?: string;
1124
}
1225

1326
export const AvatarData: FC<AvatarDataProps> = ({
1427
title,
1528
subtitle,
1629
src,
30+
fallbackLetter,
1731
avatar,
1832
}) => {
1933
const theme = useTheme();
20-
21-
if (!avatar) {
22-
avatar = <Avatar src={src}>{title}</Avatar>;
23-
}
34+
avatar ??= (
35+
<Avatar background src={src}>
36+
{typeof title === "string" ? title : (fallbackLetter?.slice(0, 1) ?? "-")}
37+
</Avatar>
38+
);
2439

2540
return (
2641
<Stack

site/src/pages/WorkspacePage/WorkspaceTopbar.tsx

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ import { WorkspaceActions } from "./WorkspaceActions/WorkspaceActions";
3030
import { WorkspaceNotifications } from "./WorkspaceNotifications/WorkspaceNotifications";
3131
import { WorkspaceScheduleControls } from "./WorkspaceScheduleControls";
3232
import type { WorkspacePermissions } from "./permissions";
33+
import { isEmojiUrl } from "utils/appearance";
3334

3435
export type WorkspaceError =
3536
| "getBuildsError"
@@ -344,7 +345,12 @@ const OrganizationBreadcrumb: FC<OrganizationBreadcrumbProps> = ({
344345
subtitle="Organization"
345346
avatar={
346347
orgIconUrl && (
347-
<ExternalAvatar src={orgIconUrl} variant="square" fitImage />
348+
<ExternalAvatar
349+
src={orgIconUrl}
350+
title={orgName}
351+
variant={isEmojiUrl(orgIconUrl) ? "square" : "circular"}
352+
fitImage
353+
/>
348354
)
349355
}
350356
/>
@@ -405,7 +411,12 @@ const WorkspaceBreadcrumb: FC<WorkspaceBreadcrumbProps> = ({
405411
</Link>
406412
}
407413
avatar={
408-
<ExternalAvatar src={templateIconUrl} variant="square" fitImage />
414+
<ExternalAvatar
415+
src={templateIconUrl}
416+
title={workspaceName}
417+
variant={isEmojiUrl(templateIconUrl) ? "square" : "circular"}
418+
fitImage
419+
/>
409420
}
410421
/>
411422
</HelpTooltipContent>

site/src/theme/externalImages.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -75,8 +75,8 @@ const parseInvertFilterParameters = (
7575

7676
let extraStyles: CSSObject | undefined;
7777

78-
const brightness = params.get("brightness");
79-
if (multiplier.test(brightness!)) {
78+
const brightness = params.get("brightness") ?? "";
79+
if (multiplier.test(brightness)) {
8080
let filter = baseStyles.filter ?? "";
8181
filter += ` brightness(${brightness})`;
8282
extraStyles = { ...extraStyles, filter };
@@ -131,7 +131,7 @@ export function getExternalImageStylesFromUrl(
131131
) {
132132
return parseImageParameters(
133133
modes,
134-
defaultParametersForBuiltinIcons.get(url.pathname)!,
134+
defaultParametersForBuiltinIcons.get(url.pathname) as string,
135135
);
136136
}
137137

site/src/utils/appearance.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import type { ReactFragment, ReactNode, ReactPortal } from "react";
2+
13
export const getApplicationName = (): string => {
24
const c = document.head
35
.querySelector("meta[name=application-name]")
@@ -14,3 +16,18 @@ export const getLogoURL = (): string => {
1416
?.getAttribute("content");
1517
return c && !c.startsWith("{{ .") ? c : "";
1618
};
19+
20+
/**
21+
* Exposes an easy way to determine if a given URL is for an emoji hosted on
22+
* the Coder deployment.
23+
*
24+
* Helps when you need to style emojis differently (i.e., not adding rounding to
25+
* the container so that the emoji doesn't get cut off).
26+
*/
27+
export function isEmojiUrl(url: string | undefined): boolean {
28+
if (!url) {
29+
return false;
30+
}
31+
32+
return url.startsWith("/emojis/");
33+
}

0 commit comments

Comments
 (0)