diff --git a/lib/dotcom/routes.ex b/lib/dotcom/routes.ex index 4c2b8037ec..33fe5b2cff 100644 --- a/lib/dotcom/routes.ex +++ b/lib/dotcom/routes.ex @@ -3,11 +3,53 @@ defmodule Dotcom.Routes do A collection of functions that help to work with routes in a unified way. """ - @subway_route_ids ["Blue", "Green", "Orange", "Red"] + alias Routes.Route + + @subway_branch_ids GreenLine.branch_ids() ++ ["Mattapan"] + @subway_line_names ["Blue", "Green", "Orange", "Red"] + + # Association of subway line names to all their respective subway routes. + # This could later be derived from the GTFS line/route relationships. + @subway_line_route_map %{ + "Blue" => ["Blue"], + "Green" => GreenLine.branch_ids(), + "Orange" => ["Orange"], + "Red" => ["Red", "Mattapan"] + } + + @doc """ + For a given route ID, return the relevant subway line name. + + ```elixir + line_name_for_subway_route("Green-B") == "Green" + line_name_for_subway_route("CR-Greenbush") == nil + ``` + """ + @spec line_name_for_subway_route(Route.id_t()) :: String.t() | Route.id_t() | nil + def line_name_for_subway_route(route_id) do + with {line_name, _} <- + Enum.find(@subway_line_route_map, fn {_, route_ids} -> + route_id in route_ids + end) do + line_name + end + end + + @doc """ + Returns a list of all subway route IDs which we'd like to show as branches. + """ + @spec subway_branch_ids() :: [Route.id_t()] + def subway_branch_ids, do: @subway_branch_ids + + @doc """ + Returns a list of all subway line names. + """ + @spec subway_line_names() :: [Route.id_t() | String.t()] + def subway_line_names, do: @subway_line_names @doc """ - Returns a list of route ids for all subway routes. + Returns the list of all subway route IDs. """ - @spec subway_route_ids() :: [String.t()] - def subway_route_ids(), do: @subway_route_ids + @spec subway_route_ids() :: [Route.id_t()] + def subway_route_ids, do: Map.values(@subway_line_route_map) |> List.flatten() end diff --git a/lib/dotcom_web/components/route_pills.ex b/lib/dotcom_web/components/route_pills.ex deleted file mode 100644 index d1ddae5215..0000000000 --- a/lib/dotcom_web/components/route_pills.ex +++ /dev/null @@ -1,73 +0,0 @@ -defmodule DotcomWeb.Components.RoutePills do - @moduledoc """ - Reusable components for displaying subway routes. - """ - - use DotcomWeb, :component - - attr :route_id, :string, required: true - attr :modifier_ids, :list, default: [] - attr :modifier_class, :string, default: "" - - def route_pill(%{modifier_ids: []} = assigns) do - ~H""" -
- {pill_text(@route_id)} -
- """ - end - - def route_pill(assigns) do - ~H""" - - <.route_pill route_id={@route_id} /> - - <.route_modifier - :for={modifier_id <- @modifier_ids} - modifier_id={modifier_id} - class={@modifier_class} - /> - - - """ - end - - defp route_modifier(assigns) do - ~H""" -
- {modifier_text(@modifier_id)} -
- """ - end - - defp pill_text(route_id) do - (route_id |> String.at(0)) <> "L" - end - - defp modifier_text("Green-" <> branch_id), do: branch_id - defp modifier_text("Mattapan"), do: "M" - - defp bg_color_class("Green-" <> _), do: bg_color_class("Green") - defp bg_color_class("Mattapan"), do: bg_color_class("Red") - - defp bg_color_class(route_id) do - "bg-#{route_id |> String.downcase()}-line" - end - - defp shared_icon_classes() do - [ - "rounded-full h-6", - "flex items-center justify-center", - "text-white font-bold font-heading select-none leading-none" - ] - end -end diff --git a/lib/dotcom_web/components/route_symbols.ex b/lib/dotcom_web/components/route_symbols.ex index 6ed4ee1843..1b76fa519a 100644 --- a/lib/dotcom_web/components/route_symbols.ex +++ b/lib/dotcom_web/components/route_symbols.ex @@ -12,6 +12,8 @@ defmodule DotcomWeb.Components.RouteSymbols do @logan_express_icon_names Route.logan_express_icon_names() @massport_icon_names Route.massport_icon_names() + @subway_branch_ids Dotcom.Routes.subway_branch_ids() + @subway_line_names Dotcom.Routes.subway_line_names() variant( :size, @@ -178,6 +180,85 @@ defmodule DotcomWeb.Components.RouteSymbols do """ end + attr :class, :string, default: "" + + attr :route_ids, :list, + doc: "A list of route IDs. These should all be associated with subway routes.", + examples: [["Blue"], ["Green-B", "Green-C"]] + + @doc """ + Renders a symbol for one or more subway routes, consisting of a colored pill + with optionally a number of circles representing branches of a subway line. + """ + def subway_route_pill(%{route_ids: [route_id]} = assigns) when route_id in @subway_line_names do + assigns = + assign(assigns, %{ + bg_color_class: "bg-#{String.downcase(route_id)}-line", + route_abbreviation: String.at(route_id, 0) <> "L" + }) + + ~H""" +
+ {@route_abbreviation} +
+ """ + end + + # Add the subway line name to the list of subway branch route_ids, since it is + # needed to render the larger pill + def subway_route_pill(%{route_ids: [route_id]} = assigns) when route_id in @subway_branch_ids do + assigns = assign(assigns, :route_ids, [subway_line_name(route_id) | assigns.route_ids]) + + ~H""" + <.subway_route_pill {assigns} /> + """ + end + + # A list of any length - find the relevant subway line. If there's any number + # of subway lines represented here other than one, fall back to subway icon. + def subway_route_pill(%{route_ids: route_ids} = assigns) when is_list(route_ids) do + with subway_line_names <- Enum.map(route_ids, &subway_line_name/1), + [subway_line_name] <- Enum.uniq(subway_line_names) |> Enum.reject(&is_nil/1) do + branch_ids = Enum.reject(assigns.route_ids, &(&1 == subway_line_name)) |> Enum.sort() + + if branch_ids == GreenLine.branch_ids() do + assigns = assign(assigns, :route_ids, ["Green"]) + + ~H""" + <.subway_route_pill {assigns} /> + """ + else + assigns = + assign(assigns, %{ + route_ids: [subway_line_name], + branch_ids: branch_ids + }) + + ~H""" + + <.subway_route_pill route_ids={@route_ids} class={"#{@class} -mr-1"} /> + <.route_symbol + :for={route_id <- @branch_ids} + route={%Routes.Route{id: route_id}} + class={"#{@class} rounded-full ring-white ring-2 mr-0.5"} + /> + + """ + end + else + _ -> + ~H""" + <.icon type="icon-svg" name="icon-mode-subway-default" class="h-6 w-6" /> + """ + end + end + # Given a route, return a machine-readable label. defp route_label(%Route{type: 2}), do: "Commuter Rail" defp route_label(%Route{type: 4}), do: "Ferry" @@ -202,4 +283,11 @@ defmodule DotcomWeb.Components.RouteSymbols do end defp route_label(%Route{long_name: long_name}), do: long_name + + defp subway_line_name(route_id) when route_id in @subway_branch_ids do + Dotcom.Routes.line_name_for_subway_route(route_id) + end + + defp subway_line_name(route_id) when route_id in @subway_line_names, do: route_id + defp subway_line_name(_), do: nil end diff --git a/lib/dotcom_web/components/system_status/subway_status.ex b/lib/dotcom_web/components/system_status/subway_status.ex index de07212cfe..b1d384b751 100644 --- a/lib/dotcom_web/components/system_status/subway_status.ex +++ b/lib/dotcom_web/components/system_status/subway_status.ex @@ -6,7 +6,7 @@ defmodule DotcomWeb.Components.SystemStatus.SubwayStatus do use DotcomWeb, :component import DotcomWeb.Components, only: [bordered_container: 1, lined_list: 1] - import DotcomWeb.Components.RoutePills + import DotcomWeb.Components.RouteSymbols, only: [subway_route_pill: 1] import DotcomWeb.Components.SystemStatus.StatusLabel @max_rows 5 @@ -35,10 +35,9 @@ defmodule DotcomWeb.Components.SystemStatus.SubwayStatus do ]} >
- <.route_pill - route_id={row.route_info.route_id} - modifier_ids={row.route_info.branch_ids} - modifier_class="group-hover/row:ring-brand-primary-lightest" + <.subway_route_pill + class="group-hover/row:ring-brand-primary-lightest" + route_ids={[row.route_info.route_id | row.route_info.branch_ids]} />

Route Pills

-
- <.route_pill route_id="Blue" /> - <.route_pill route_id="Green" /> - <.route_pill route_id="Orange" /> - <.route_pill route_id="Red" /> - <.route_pill route_id="Green" modifier_ids={["Green-B", "Green-C"]} /> -
+ <%= for ids <- [ + ["Blue"], + ["Green"], + ["Orange"], + ["Red"], + ["Mattapan"], + ["Red", "Mattapan"], + ["Orange", "Mattapan"], + ["Green-E", "Green-B", "Green-D"], + ["Fake News"], + ["Green-B", "Green-C", "Green-D", "Green-E"] + ] do %> +
+ <.subway_route_pill route_ids={ids} class="group-hover/row:ring-slate-600" /> {inspect(ids)} +
+ <% end %> """ end diff --git a/test/dotcom_web/components/route_symbols_test.exs b/test/dotcom_web/components/route_symbols_test.exs index 7fbae8be0a..1c9e0e8e2e 100644 --- a/test/dotcom_web/components/route_symbols_test.exs +++ b/test/dotcom_web/components/route_symbols_test.exs @@ -102,6 +102,62 @@ defmodule DotcomWeb.Components.RouteSymbolsTest do end end + describe "subway_route_pill/1" do + test "Subway lines render one element" do + for route_id <- Dotcom.Routes.subway_line_names() do + html = + render_component(&subway_route_pill/1, %{ + route_ids: [route_id] + }) + |> Floki.parse_fragment!() + |> List.first() + + assert html + assert Floki.children(html, include_text: false) == [] + end + end + + test "Mattapan renders pill + icon" do + html = + render_component(&subway_route_pill/1, %{ + route_ids: ["Mattapan"] + }) + |> Floki.parse_fragment!() + |> List.first() + + assert [{"div", _, _}, {"svg", _, _}] = Floki.children(html, include_text: false) + end + + test "Multiple branches render pill + multiple icons" do + num_branches = Faker.Util.pick([2, 3]) + + html = + render_component(&subway_route_pill/1, %{ + route_ids: Enum.take(GreenLine.branch_ids(), num_branches) + }) + |> Floki.parse_fragment!() + |> List.first() + + assert [{"div", _, _} | icons] = Floki.children(html, include_text: false) + assert [{"svg", _, _} | _] = icons + assert Enum.count(icons) == num_branches + end + + test "List of all Green Line branches renders one pill" do + all_gl_branches_pill = + render_component(&subway_route_pill/1, %{ + route_ids: GreenLine.branch_ids() + }) + + green_line_pill = + render_component(&subway_route_pill/1, %{ + route_ids: ["Green"] + }) + + assert all_gl_branches_pill == green_line_pill + end + end + defp matches_title?(html, text) do title = html