Skip to content

Commit 2841bb6

Browse files
committed
website: bus: Match route colors with the official colors
Source: https://uci.nus.edu.sg/campus-life/campus-services/transportation/internal-shuttle-bus/
1 parent a83078c commit 2841bb6

File tree

7 files changed

+126
-75
lines changed

7 files changed

+126
-75
lines changed

website/src/data/bus-routes.json

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
[
2+
"A1",
3+
"A2",
4+
"D1",
5+
"D2",
6+
"E",
7+
"K",
8+
"BTC",
9+
"L"
10+
]

website/src/utils/venues.test.ts

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,20 @@
11
import venueInfo from '__mocks__/venueInformation.json';
22
import venueLocationInfo from '__mocks__/venueLocations.json';
33
import { VenueInfo, VenueLocationMap } from 'types/venues';
4+
import busRoutesJson from 'data/bus-routes.json';
45
import {
56
searchVenue,
67
filterAvailability,
78
sortVenues,
89
floorName,
910
clampClassDuration,
11+
isPublicRoute,
12+
extractRouteStyle,
13+
simplifyRouteName,
1014
} from './venues';
1115

16+
const busRoutes = busRoutesJson as string[];
17+
1218
const venues = sortVenues(venueInfo as VenueInfo);
1319
const venueLocations = venueLocationInfo as VenueLocationMap;
1420

@@ -224,3 +230,45 @@ describe(clampClassDuration, () => {
224230
).toEqual(4);
225231
});
226232
});
233+
234+
describe(isPublicRoute, () => {
235+
it('should return true for public routes', () => {
236+
expect(isPublicRoute('PUB:10')).toBe(true);
237+
expect(isPublicRoute('PUB:95')).toBe(true);
238+
expect(isPublicRoute('PUB:201')).toBe(true);
239+
});
240+
241+
it('should return false for ISB routes', () => {
242+
busRoutes.forEach((route) => {
243+
expect(isPublicRoute(route)).toBe(false);
244+
});
245+
});
246+
});
247+
248+
describe(extractRouteStyle, () => {
249+
it('should return PUBLIC for public routes', () => {
250+
expect(extractRouteStyle('PUB:10')).toBe('PUBLIC');
251+
expect(extractRouteStyle('PUB:95')).toBe('PUBLIC');
252+
expect(extractRouteStyle('PUB:201')).toBe('PUBLIC');
253+
});
254+
255+
it('should return the route name for ISB routes', () => {
256+
busRoutes.forEach((route) => {
257+
expect(extractRouteStyle(route)).toBe(route);
258+
});
259+
});
260+
});
261+
262+
describe('simplifyRouteName', () => {
263+
it('should remove PUB: prefix for public routes', () => {
264+
expect(simplifyRouteName('PUB:10')).toBe('10');
265+
expect(simplifyRouteName('PUB:95')).toBe('95');
266+
expect(simplifyRouteName('PUB:201')).toBe('201');
267+
});
268+
269+
it('should return the route name for ISB routes', () => {
270+
busRoutes.forEach((route) => {
271+
expect(simplifyRouteName(route)).toBe(route);
272+
});
273+
});
274+
});

website/src/utils/venues.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,3 +100,21 @@ export function floorName(floor: number | string): string {
100100
const floorNumber = floor < 0 ? `B${-floor}` : floor;
101101
return `floor ${floorNumber}`;
102102
}
103+
104+
export function isPublicRoute(name: string): boolean {
105+
return name.startsWith('PUB:');
106+
}
107+
108+
export function extractRouteStyle(name: string): string {
109+
if (isPublicRoute(name)) {
110+
return 'PUBLIC';
111+
}
112+
return name;
113+
}
114+
115+
export function simplifyRouteName(name: string): string {
116+
if (isPublicRoute(name)) {
117+
return name.substring(4);
118+
}
119+
return name;
120+
}

website/src/views/components/map/ArrivalTimes.test.tsx

Lines changed: 0 additions & 19 deletions
This file was deleted.

website/src/views/components/map/ArrivalTimes.tsx

Lines changed: 10 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { entries, sortBy } from 'lodash';
44

55
import { RefreshCw as Refresh } from 'react-feather';
66
import { BusTiming, NextBus, NextBusTime } from 'types/venues';
7+
import { simplifyRouteName, extractRouteStyle } from 'utils/venues';
78
import styles from './BusStops.scss';
89

910
type Props = BusTiming & {
@@ -12,17 +13,6 @@ type Props = BusTiming & {
1213
reload: (code: string) => void;
1314
};
1415

15-
/**
16-
* Extract the route name from the start of a string
17-
*/
18-
const routes = ['A1', 'A2', 'B1', 'B2', 'C', 'D1', 'D2', 'BTC1', 'BTC2'];
19-
export function extractRoute(route: string) {
20-
for (let i = 0; i < routes.length; i++) {
21-
if (route.startsWith(routes[i])) return routes[i];
22-
}
23-
return null;
24-
}
25-
2616
/**
2717
* Adds 'min' to numeric timings and highlight any buses that are arriving
2818
* soon with a <strong> tag
@@ -37,15 +27,6 @@ function renderTiming(time: NextBusTime) {
3727
return time;
3828
}
3929

40-
/**
41-
* Route names with parenthesis in them don't have a space in front of the
42-
* opening bracket, causing the text to wrap weirdly. This forces the opening
43-
* paren to always have a space in front of it.
44-
*/
45-
function fixRouteName(name: string) {
46-
return name.replace(/\s?\(/, ' (');
47-
}
48-
4930
export const ArrivalTimes = memo<Props>((props: Props) => {
5031
if (props.error) {
5132
return (
@@ -72,15 +53,14 @@ export const ArrivalTimes = memo<Props>((props: Props) => {
7253
{props.timings && (
7354
<table className={classnames(styles.timings, 'table table-sm')}>
7455
<tbody>
75-
{timings.map(([routeName, timing]: [string, NextBus]) => {
76-
const route = extractRoute(routeName);
77-
const className = route
78-
? classnames(styles.routeHeading, styles[`route${route}`])
79-
: '';
80-
56+
{timings.map(([route, timing]: [string, NextBus]) => {
57+
const className = classnames(
58+
styles.routeHeading,
59+
styles[`route${extractRouteStyle(route)}`],
60+
);
8161
return (
82-
<tr key={routeName}>
83-
<th className={className}>{fixRouteName(routeName)}</th>
62+
<tr key={route}>
63+
<th className={className}>{simplifyRouteName(route)}</th>
8464
<td>{renderTiming(timing.arrivalTime)}</td>
8565
<td>{renderTiming(timing.nextArrivalTime)}</td>
8666
</tr>
@@ -104,3 +84,5 @@ export const ArrivalTimes = memo<Props>((props: Props) => {
10484
</>
10585
);
10686
});
87+
88+
export default ArrivalTimes;

website/src/views/components/map/BusStops.scss

Lines changed: 33 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,15 @@
11
@import '~styles/utils/modules-entry';
22

3-
// Colors from https://material.io/design/color/
43
$route-colors: (
5-
A: #e53935,
6-
B: #8e24aa,
7-
C: #1e88e5,
8-
D: #558b2f,
9-
BTC: #6d4c41,
4+
A1: #c10000,
5+
A2: #ffcf09,
6+
D1: #f9b3fe,
7+
D2: #732172,
8+
E: #01823b,
9+
K: #305597,
10+
BTC: #ff9a00,
11+
L: #d9d9d9,
12+
PUBLIC: #eee,
1013
);
1114

1215
.iconWrapper {
@@ -56,7 +59,6 @@ $route-colors: (
5659
font-weight: bold;
5760
font-size: 9px;
5861
line-height: 1;
59-
color: #fff;
6062
pointer-events: auto;
6163
}
6264
}
@@ -82,7 +84,6 @@ $route-colors: (
8284

8385
.routeHeading {
8486
vertical-align: middle;
85-
color: #fff;
8687
}
8788
}
8889

@@ -105,37 +106,46 @@ $route-colors: (
105106

106107
// Define a color for each route
107108
.routeA1 {
108-
background: map-get($route-colors, 'A');
109+
color: white;
110+
background: map-get($route-colors, 'A1');
109111
}
110112

111113
.routeA2 {
112-
background: darken(map-get($route-colors, 'A'), 12);
114+
color: black;
115+
background: map-get($route-colors, 'A2');
113116
}
114117

115-
.routeB1 {
116-
background: map-get($route-colors, 'B');
118+
.routeE {
119+
color: white;
120+
background: map-get($route-colors, 'E');
117121
}
118122

119-
.routeB2 {
120-
background: lighten(map-get($route-colors, 'B'), 15);
121-
}
122-
123-
.routeC {
124-
background: map-get($route-colors, 'C');
123+
.routeK {
124+
color: white;
125+
background: map-get($route-colors, 'K');
125126
}
126127

127128
.routeD1 {
128-
background: map-get($route-colors, 'D');
129+
color: black;
130+
background: map-get($route-colors, 'D1');
129131
}
130132

131133
.routeD2 {
132-
background: lighten(map-get($route-colors, 'D'), 10);
134+
color: white;
135+
background: map-get($route-colors, 'D2');
133136
}
134137

135-
.routeBTC1 {
138+
.routeBTC {
139+
color: black;
136140
background: map-get($route-colors, 'BTC');
137141
}
138142

139-
.routeBTC2 {
140-
background: lighten(map-get($route-colors, 'BTC'), 15);
143+
.routeL {
144+
color: black;
145+
background: map-get($route-colors, 'L');
146+
}
147+
148+
.routePUBLIC {
149+
color: black;
150+
background: map-get($route-colors, 'PUBLIC');
141151
}

website/src/views/components/map/BusStops.tsx

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,14 @@ import { produce } from 'immer';
77
import type { BusTiming, BusStop } from 'types/venues';
88
import type { EmptyProps } from 'types/utils';
99

10-
import busStopsJSON from 'data/bus-stops.json';
10+
import busStopsJson from 'data/bus-stops.json';
1111
import { allowBusStopEditing } from 'utils/debug';
1212
import { nextBus } from 'apis/nextbus';
13+
import { extractRouteStyle, simplifyRouteName } from 'utils/venues';
1314
import styles from './BusStops.scss';
1415
import { ArrivalTimes } from './ArrivalTimes';
1516

16-
const busStops = busStopsJSON as BusStop[];
17+
const busStops = busStopsJson as BusStop[];
1718

1819
type Props = EmptyProps;
1920

@@ -131,9 +132,10 @@ export default class BusStops extends PureComponent<Props, State> {
131132

132133
const routeIndicators = stop.shuttles.map(
133134
(shuttle) =>
134-
`<span class="${classnames(styles.route, styles[`route${shuttle.name}`])}">${
135-
shuttle.name
136-
}</span>`,
135+
`<span class="${classnames(
136+
styles.route,
137+
styles[`route${extractRouteStyle(shuttle.name)}`],
138+
)}">${simplifyRouteName(shuttle.name)}</span>`,
137139
);
138140

139141
const icon = new DivIcon({

0 commit comments

Comments
 (0)