Skip to content

Commit 4b88e15

Browse files
committed
feat: Wayfinding Header/Section Implementation
- image_path config for Departure Header can now be used to implement a Wayfinding image - we can use it to define images above departures, image only sections and full screen images using `header_only` - addeds a container div around sections that exposes a true-grey-70 section break for image section headers
1 parent c823a8d commit 4b88e15

File tree

9 files changed

+70
-30
lines changed

9 files changed

+70
-30
lines changed

assets/css/lcd/departures/section.scss

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,15 @@
11
@use "CSS/colors";
22

3+
.departures-section-container--header-image {
4+
&:not(:first-child) {
5+
padding-top: 24px;
6+
background-color: colors.$true-grey-70;
7+
}
8+
}
9+
310
.departures-section {
11+
background-color: #e6e4e1;
12+
413
& > .departure-row:not(:last-child)::after {
514
position: absolute;
615
bottom: 0;

assets/src/components/departures/header.tsx

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,14 @@ import cx from "classnames";
22
import { type JSX } from "react";
33

44
import Arrow45 from "Images/arrow-45.svg";
5+
import { extensionForAsset } from "Util/utils";
56

67
type CardinalDirection = "n" | "ne" | "e" | "se" | "s" | "sw" | "w" | "nw";
78

89
type Header = {
910
title: string | null;
1011
arrow: CardinalDirection | null;
12+
image_path: string | null;
1113
subtitle: string | null;
1214
};
1315

@@ -25,13 +27,20 @@ const DirectionArrow = ({ arrow }: { arrow: CardinalDirection }) => (
2527
/>
2628
);
2729

28-
const Header = ({ title, arrow, subtitle }: Header) => {
30+
const IMAGE_EXTENSIONS = ["png", "jpg", "jpeg", "svg", "gif", "webp"];
31+
32+
const Header = ({ title, arrow, subtitle, image_path: imagePath }: Header) => {
2933
return (
3034
<>
3135
<header className="departures-header">
3236
{(title || arrow) && <span>{title}</span>}
3337
{arrow && <DirectionArrow arrow={arrow} />}
3438
</header>
39+
{imagePath && IMAGE_EXTENSIONS.includes(extensionForAsset(imagePath)) && (
40+
<div className="departures-header__image-container">
41+
<img className="departures-header__image" src={imagePath} />
42+
</div>
43+
)}
3544
{subtitle && (
3645
<div className="departures-header__subtitle">
3746
{formatSubtitle(subtitle)}

assets/src/components/departures/normal_section.tsx

Lines changed: 29 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import DepartureRow from "./departure_row";
55
import NoticeRow from "./notice_row";
66
import Header from "./header";
77
import LaterDepartures, { MIN_LATER_DEPARTURES } from "./later_departures";
8+
import { classWithModifier } from "Util/utils";
89

910
export type Layout = {
1011
base: number | null;
@@ -69,28 +70,35 @@ export const NormalSection: ComponentType<FoldedSection> = ({
6970
});
7071

7172
return (
72-
<div className="departures-section">
73-
<Header {...header} />
74-
{aboveFold.map((row, index) => {
75-
if (row.type === "departure_row") {
76-
return (
77-
<DepartureRow
78-
{...row}
79-
key={row.id}
80-
isBeforeDirectionSplit={
81-
groupingType === "destination" &&
82-
index === rowToAddDivider(aboveFold)
83-
}
84-
/>
85-
);
86-
} else {
87-
return <NoticeRow row={row} key={weakKey(row)} />;
88-
}
89-
})}
90-
91-
{includeLater && belowFold.length >= MIN_LATER_DEPARTURES && (
92-
<LaterDepartures rows={belowFold} />
73+
<div
74+
className={classWithModifier(
75+
"departures-section-container",
76+
header.image_path ? "header-image" : "",
9377
)}
78+
>
79+
<div className="departures-section">
80+
<Header {...header} />
81+
{aboveFold.map((row, index) => {
82+
if (row.type === "departure_row") {
83+
return (
84+
<DepartureRow
85+
{...row}
86+
key={row.id}
87+
isBeforeDirectionSplit={
88+
groupingType === "destination" &&
89+
index === rowToAddDivider(aboveFold)
90+
}
91+
/>
92+
);
93+
} else {
94+
return <NoticeRow row={row} key={weakKey(row)} />;
95+
}
96+
})}
97+
98+
{includeLater && belowFold.length >= MIN_LATER_DEPARTURES && (
99+
<LaterDepartures rows={belowFold} />
100+
)}
101+
</div>
94102
</div>
95103
);
96104
};

assets/src/components/evergreen_content.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import type { ComponentType } from "react";
22

33
import LoopingVideoPlayer from "Components/looping_video_player";
4+
import { extensionForAsset } from "Util/utils";
45

56
const IMAGE_EXTENSIONS = ["png", "jpg", "jpeg", "svg", "gif", "webp"];
67
const VIDEO_EXTENSIONS = ["mp4", "ogg", "ogv", "webm"];
@@ -14,8 +15,7 @@ const EvergreenContent: ComponentType<Props> = ({
1415
asset_url: assetUrl,
1516
isPlaying = true,
1617
}) => {
17-
const parts = assetUrl.split(".");
18-
const extension = parts[parts.length - 1].toLowerCase();
18+
const extension = extensionForAsset(assetUrl);
1919

2020
if (IMAGE_EXTENSIONS.includes(extension)) {
2121
return <Image assetUrl={assetUrl} />;

assets/src/util/utils.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,3 +68,8 @@ export const firstWord = (str: string): string => str.split(" ")[0];
6868

6969
export const formatCause = (cause: string) =>
7070
(cause.charAt(0).toUpperCase() + cause.substring(1)).replace("_", " ");
71+
72+
export const extensionForAsset = (assetPath: string) => {
73+
const parts = assetPath.split(".");
74+
return parts[parts.length - 1].toLowerCase();
75+
};

assets/tests/components/departures/factories.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ export const normalSection = Factory.define<Section>(() => ({
88
type: "normal_section",
99
layout: { min: 1, base: null, max: null, include_later: false },
1010
rows: departureRow.buildList(1),
11-
header: { title: null, arrow: null, subtitle: null },
11+
header: { title: null, arrow: null, subtitle: null, image_path: null },
1212
grouping_type: "time",
1313
}));
1414

lib/screens/util/assets.ex

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
defmodule Screens.Util.Assets do
22
@moduledoc false
33

4+
def s3_asset_url(nil), do: ""
5+
def s3_asset_url(""), do: ""
6+
47
def s3_asset_url(asset_path) do
58
env = Application.get_env(:screens, :environment_name, "screens-dev")
69
"https://mbta-screens.s3.amazonaws.com/#{env}/#{asset_path}"

lib/screens/v2/widget_instance/departures.ex

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ defmodule Screens.V2.WidgetInstance.Departures do
88
alias Screens.Schedules.Schedule
99
alias Screens.Stops.Stop
1010
alias Screens.Util
11+
alias Screens.Util.Assets
1112
alias Screens.V2.Departure
1213
alias Screens.V2.WidgetInstance.Departures
1314
alias Screens.V2.WidgetInstance.Serializer.RoutePill
@@ -194,7 +195,7 @@ defmodule Screens.V2.WidgetInstance.Departures do
194195
def serialize_section(
195196
%NormalSection{
196197
rows: rows,
197-
header: header,
198+
header: %Header{image_path: image_path} = header,
198199
layout: %Layout{max: max},
199200
grouping_type: :destination
200201
},
@@ -218,13 +219,18 @@ defmodule Screens.V2.WidgetInstance.Departures do
218219
min: 2,
219220
include_later: false
220221
}),
221-
header: Header.to_json(header),
222+
header: Map.put(header, :image_path, Assets.s3_asset_url(image_path)) |> Header.to_json(),
222223
grouping_type: :destination
223224
}
224225
end
225226

226227
def serialize_section(
227-
%NormalSection{rows: rows, layout: layout, header: header, grouping_type: grouping_type},
228+
%NormalSection{
229+
rows: rows,
230+
layout: layout,
231+
header: %Header{image_path: image_path} = header,
232+
grouping_type: grouping_type
233+
},
228234
screen,
229235
now,
230236
_is_only_section
@@ -239,7 +245,7 @@ defmodule Screens.V2.WidgetInstance.Departures do
239245
type: :normal_section,
240246
rows: serialized_rows,
241247
layout: Layout.to_json(layout),
242-
header: Header.to_json(header),
248+
header: Map.put(header, :image_path, Assets.s3_asset_url(image_path)) |> Header.to_json(),
243249
grouping_type: grouping_type
244250
}
245251
end

mix.lock

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@
6464
"recon": {:hex, :recon, "2.5.6", "9052588e83bfedfd9b72e1034532aee2a5369d9d9343b61aeb7fbce761010741", [:mix, :rebar3], [], "hexpm", "96c6799792d735cc0f0fd0f86267e9d351e63339cbe03df9d162010cefc26bb0"},
6565
"remote_ip": {:hex, :remote_ip, "1.2.0", "fb078e12a44414f4cef5a75963c33008fe169b806572ccd17257c208a7bc760f", [:mix], [{:combine, "~> 0.10", [hex: :combine, repo: "hexpm", optional: false]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "2ff91de19c48149ce19ed230a81d377186e4412552a597d6a5137373e5877cb7"},
6666
"retry": {:hex, :retry, "0.19.0", "aeb326d87f62295d950f41e1255fe6f43280a1b390d36e280b7c9b00601ccbc2", [:mix], [], "hexpm", "85ef376aa60007e7bff565c366310966ec1bd38078765a0e7f20ec8a220d02ca"},
67-
"screens_config": {:git, "https://github.com/mbta/screens-config-lib.git", "a7b27b92f1a667b33e8959a4fdb340d9556c77cd", []},
67+
"screens_config": {:git, "https://github.com/mbta/screens-config-lib.git", "bfbb2a5b6e54b01fd596b938b2754af8a7af6322", []},
6868
"sentry": {:hex, :sentry, "12.0.3", "0d5f681b4a7b57c4df16a37f4b90a554d10e577dad01d4457c844ffa24800861", [:mix], [{:finch, "~> 0.21", [hex: :finch, repo: "hexpm", optional: true]}, {:hackney, "~> 1.8", [hex: :hackney, repo: "hexpm", optional: true]}, {:igniter, "~> 0.5", [hex: :igniter, repo: "hexpm", optional: true]}, {:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: true]}, {:nimble_options, "~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:nimble_ownership, "~> 0.3.0 or ~> 1.0", [hex: :nimble_ownership, repo: "hexpm", optional: false]}, {:opentelemetry, ">= 0.0.0", [hex: :opentelemetry, repo: "hexpm", optional: true]}, {:opentelemetry_api, ">= 0.0.0", [hex: :opentelemetry_api, repo: "hexpm", optional: true]}, {:opentelemetry_exporter, ">= 0.0.0", [hex: :opentelemetry_exporter, repo: "hexpm", optional: true]}, {:opentelemetry_semantic_conventions, ">= 0.0.0", [hex: :opentelemetry_semantic_conventions, repo: "hexpm", optional: true]}, {:phoenix, "~> 1.6", [hex: :phoenix, repo: "hexpm", optional: true]}, {:phoenix_live_view, "~> 0.20 or ~> 1.0", [hex: :phoenix_live_view, repo: "hexpm", optional: true]}, {:plug, "~> 1.6", [hex: :plug, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: true]}], "hexpm", "3c2f571ea43e8d3c893c5ca88ab1d1b9501f02925d094fc23811ce5b7f228b65"},
6969
"ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.7", "354c321cf377240c7b8716899e182ce4890c5938111a1296add3ec74cf1715df", [:make, :mix, :rebar3], [], "hexpm", "fe4c190e8f37401d30167c8c405eda19469f34577987c76dde613e838bbc67f8"},
7070
"stream_data": {:hex, :stream_data, "1.3.0", "bde37905530aff386dea1ddd86ecbf00e6642dc074ceffc10b7d4e41dfd6aac9", [:mix], [], "hexpm", "3cc552e286e817dca43c98044c706eec9318083a1480c52ae2688b08e2936e3c"},

0 commit comments

Comments
 (0)