Skip to content

Commit 51f3392

Browse files
committed
Set favicon color based on build status & support dark mode favicon
1 parent d6684a7 commit 51f3392

File tree

14 files changed

+130
-5
lines changed

14 files changed

+130
-5
lines changed

app/favicon/BUILD.bazel

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
load("@npm_bazel_typescript//:index.bzl", "ts_library")
2+
3+
package(default_visibility = ["//visibility:public"])
4+
5+
ts_library(
6+
name = "favicon",
7+
srcs = glob(["*.ts"]),
8+
deps = [
9+
],
10+
)

app/favicon/favicon.ts

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
export enum IconType {
2+
Default,
3+
Success,
4+
Failure,
5+
InProgress,
6+
Unknown,
7+
}
8+
9+
class FaviconService {
10+
getDefaultFavicon() {
11+
if (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)')?.matches) {
12+
return "/favicon/favicon_white.svg"
13+
}
14+
return "/favicon/favicon_black.svg";
15+
}
16+
17+
getFaviconForType(type: IconType) {
18+
switch (type) {
19+
case IconType.Success:
20+
return "/favicon/favicon_green.svg"
21+
case IconType.Failure:
22+
return "/favicon/favicon_red.svg"
23+
case IconType.InProgress:
24+
return "/favicon/favicon_blue.svg"
25+
case IconType.Unknown:
26+
return "/favicon/favicon_grey.svg"
27+
default:
28+
return this.getDefaultFavicon();
29+
}
30+
}
31+
32+
setFaviconForType(type: IconType) {
33+
document.getElementById("favicon")?.setAttribute("href", this.getFaviconForType(type))
34+
}
35+
36+
setDefaultFavicon() {
37+
this.setFaviconForType(IconType.Default);
38+
}
39+
}
40+
41+
export default new FaviconService();

app/invocation/BUILD

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ ts_library(
99
"//app/auth",
1010
"//app/capabilities",
1111
"//app/docs",
12+
"//app/favicon",
1213
"//app/format",
1314
"//app/router",
1415
"//app/service",

app/invocation/invocation.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import moment from 'moment';
44
import rpcService from '../service/rpc_service'
55
import authService, { User } from '../auth/auth_service';
66
import capabilities from '../capabilities/capabilities';
7+
import faviconService from '../favicon/favicon';
78

89
import InvocationModel from './invocation_model'
910

@@ -85,13 +86,13 @@ export default class InvocationComponent extends React.Component {
8586
showInProgressScreen = response.invocation[0].event.length == 0;
8687
this.fetchUpdatedProgress();
8788
}
88-
8989
this.setState({
9090
inProgress: showInProgressScreen,
9191
model: InvocationModel.modelFromInvocations(response.invocation as invocation.Invocation[]),
9292
loading: false
9393
});
9494
document.title = `${this.state.model.getUser(true)} ${this.state.model.getCommand()} ${this.state.model.getPattern()} | BuildBuddy`;
95+
faviconService.setFaviconForType(this.state.model.getFaviconType());
9596
}).catch((error: any) => {
9697
console.error(error);
9798
this.setState({

app/invocation/invocation_model.tsx

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { invocation } from '../../proto/invocation_ts_proto';
44
import { build_event_stream } from '../../proto/build_event_stream_ts_proto';
55
import { command_line } from '../../proto/command_line_ts_proto';
66
import format from '../format/format';
7+
import { IconType } from '../favicon/favicon';
78

89
export default class InvocationModel {
910
invocations: invocation.Invocation[] = [];
@@ -298,6 +299,20 @@ export default class InvocationModel {
298299
return this.finished.exitCode.code == 0 ? "success" : "failure";
299300
}
300301

302+
getFaviconType() {
303+
let invocationStatus = this.invocations.find(() => true)?.invocationStatus;
304+
if (invocationStatus == invocation.Invocation.InvocationStatus.DISCONNECTED_INVOCATION_STATUS) {
305+
return IconType.Unknown;
306+
}
307+
if (!this.started) {
308+
return IconType.Unknown;
309+
}
310+
if (!this.finished) {
311+
return IconType.InProgress;
312+
}
313+
return this.finished.exitCode.code == 0 ? IconType.Success : IconType.Failure;
314+
}
315+
301316
getStatusIcon() {
302317
let invocationStatus = this.invocations.find(() => true)?.invocationStatus;
303318
if (invocationStatus == invocation.Invocation.InvocationStatus.DISCONNECTED_INVOCATION_STATUS) {

app/root/BUILD.bazel

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ ts_library(
99
"//app/auth",
1010
"//app/capabilities",
1111
"//app/docs",
12+
"//app/favicon",
1213
"//app/footer",
1314
"//app/invocation",
1415
"//app/menu",

app/root/root.tsx

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import capabilities from '../capabilities/capabilities'
77
import router, { Path } from '../router/router';
88
import authService, { AuthService } from '../auth/auth_service';
99
import { User } from '../auth/auth_service';
10+
import faviconService from '../favicon/favicon';
1011

1112
const viewModeKey = "VIEW_MODE";
1213
const denseModeValue = "DENSE";
@@ -38,9 +39,13 @@ export default class RootComponent extends React.Component {
3839
authService.userStream.addListener(AuthService.userEventName, (user: User) => {
3940
this.setState({ ...this.state, user })
4041
});
42+
faviconService.setDefaultFavicon();
4143
}
4244

4345
handlePathChange() {
46+
if (this.state.path != window.location.pathname) {
47+
faviconService.setDefaultFavicon();
48+
}
4449
this.setState({
4550
hash: window.location.hash,
4651
path: window.location.pathname,

static/favicon/favicon_black.svg

Lines changed: 9 additions & 0 deletions
Loading

0 commit comments

Comments
 (0)