From 61f83aafd63142d12c538052388d771b70c30b08 Mon Sep 17 00:00:00 2001 From: Cora Grant Date: Thu, 31 Oct 2024 10:46:15 -0400 Subject: [PATCH] chore: remove Solari screen type and "v1" code * Remove the `solari` screen type; we have no remaining screens of this type in the wild. * Remove the "v1" screen page/API endpoints; there are now no remaining v1 screen types. * Remove a substantial amount of code that was only used by v1 screens (likely not all of it, but there are few reliable tools available for detecting unused "public" code in Elixir). * Remove the `solari_large_v2` screen type; this was added a long time ago in anticipation of the "large" Solari layout being ported to the v2 framework, but no real functionality was ever added to it, and the only screens of this type in the wild are now permanently offline and waiting to be decommissioned. --- assets/css/fonts_inter.scss | 39 - assets/css/pre_fare_v2.scss | 5 +- assets/css/solari.scss | 30 - assets/css/solari/departure.scss | 239 --- assets/css/solari/departure_crowding.scss | 53 - assets/css/solari/departure_destination.scss | 47 - assets/css/solari/departure_headway.scss | 35 - assets/css/solari/departure_route.scss | 65 - assets/css/solari/departure_time.scss | 72 - assets/css/solari/header.scss | 88 - assets/css/solari/inline_alert_badge.scss | 135 -- assets/css/solari/loading.scss | 45 - assets/css/solari/psa.scss | 46 - assets/css/solari/screen_container.scss | 15 - assets/css/solari/section.scss | 187 -- assets/css/solari/section_list.scss | 21 - assets/css/solari_large_v2.scss | 17 - assets/css/{solari => v2}/arrow.scss | 0 assets/css/v2/pre_fare/departure_time.scss | 26 + assets/css/v2/solari_large/normal_header.scss | 48 - assets/css/v2/solari_large/screen/normal.scss | 22 - .../css/v2/solari_large/screen/takeover.scss | 14 - assets/src/apps/admin.tsx | 2 - assets/src/apps/solari.tsx | 60 - assets/src/apps/v2/solari_large.tsx | 44 - .../src/components/admin/admin_add_modal.tsx | 2 - assets/src/components/admin/admin_tables.tsx | 73 - .../eink/base_departure_destination.tsx | 38 - .../components/eink/base_departure_time.tsx | 45 - .../src/components/eink/base_route_pill.tsx | 42 - assets/src/components/eink/digital_bridge.tsx | 38 - .../components/eink/full_screen_takeover.tsx | 18 - assets/src/components/eink/global_alert.tsx | 41 - .../components/eink/green_line/departures.tsx | 196 -- .../green_line/double/screen_container.tsx | 175 -- .../components/eink/green_line/fare_info.tsx | 37 - .../src/components/eink/green_line/header.tsx | 103 - .../eink/green_line/inline_alert.tsx | 53 - .../components/eink/green_line/line_map.tsx | 577 ------ .../eink/green_line/nearby_departures.tsx | 93 - .../green_line/single/screen_container.tsx | 127 -- .../eink/green_line/takeover_inline_alert.tsx | 30 - assets/src/components/eink/loading_top.tsx | 40 - .../components/eink/no_connection_bottom.tsx | 12 - .../components/eink/no_connection_single.tsx | 16 - .../src/components/eink/no_connection_top.tsx | 16 - assets/src/components/eink/no_service.tsx | 202 -- .../components/eink/overnight_departures.tsx | 18 - assets/src/components/eink/screen_page.tsx | 68 - assets/src/components/eink/takeover_alert.tsx | 11 - .../eink/takeover_screen_layout.tsx | 14 - assets/src/components/solari/departure.tsx | 188 -- assets/src/components/solari/header.tsx | 33 - .../components/solari/headway_departure.tsx | 43 - .../components/solari/inline_alert_badge.tsx | 38 - .../src/components/solari/loading_layout.tsx | 27 - assets/src/components/solari/psa.tsx | 12 - assets/src/components/solari/route_pill.tsx | 191 -- .../components/solari/screen_container.tsx | 94 - assets/src/components/solari/section.tsx | 630 ------ assets/src/components/solari/section_list.tsx | 58 - .../solari/section_list_container.tsx | 46 - .../components/solari/section_list_sizer.tsx | 206 -- .../src/components/{solari => v2}/arrow.tsx | 0 assets/src/components/v2/blue_bikes.tsx | 2 +- .../v2/cr_departures/cr_departure_time.tsx | 30 +- .../v2/cr_departures/cr_departures_table.tsx | 2 +- assets/src/components/v2/shuttle_bus_info.tsx | 2 +- .../v2/solari_large/normal_screen.tsx | 26 - assets/src/constants.tsx | 2 - assets/webpack.config.js | 2 - docs/version_upgrade.md | 6 +- lib/screens/alerts/alert.ex | 142 -- lib/screens/audio.ex | 165 -- lib/screens/audio/fetch.ex | 35 - lib/screens/config/cache.ex | 29 - lib/screens/departures/departure.ex | 15 - lib/screens/line_map.ex | 185 -- lib/screens/nearby_connections.ex | 92 - lib/screens/nearby_departures.ex | 53 - lib/screens/psa.ex | 103 - lib/screens/screen_data.ex | 60 - lib/screens/solari_screen_data.ex | 267 --- lib/screens/util.ex | 44 - .../v2/candidate_generator/solari_large.ex | 56 - .../candidate_generator/widgets/departures.ex | 5 +- lib/screens/v2/screen_data/parameters.ex | 4 - .../controllers/audio_controller.ex | 78 - .../controllers/screen_api_controller.ex | 63 - .../controllers/screen_controller.ex | 100 - .../controllers/v2/screen_controller.ex | 2 +- lib/screens_web/router.ex | 27 - lib/screens_web/templates/audio/README.md | 30 - .../audio/_departure_pill_group.ssml.eex | 4 - .../templates/audio/_header.ssml.eex | 2 - .../templates/audio/index.ssml.eex | 15 - lib/screens_web/views/audio_view.ex | 231 --- lib/screens_web/views/screen_view.ex | 3 - test/fixtures/config.json | 1750 ----------------- test/screens/line_map_test.exs | 67 - .../candidate_generator/solari_large_test.exs | 51 - .../controllers/page_controller_test.exs | 18 - .../screen_api_controller_test.exs | 6 - 103 files changed, 62 insertions(+), 8718 deletions(-) delete mode 100644 assets/css/fonts_inter.scss delete mode 100644 assets/css/solari.scss delete mode 100644 assets/css/solari/departure.scss delete mode 100644 assets/css/solari/departure_crowding.scss delete mode 100644 assets/css/solari/departure_destination.scss delete mode 100644 assets/css/solari/departure_headway.scss delete mode 100644 assets/css/solari/departure_route.scss delete mode 100644 assets/css/solari/departure_time.scss delete mode 100644 assets/css/solari/header.scss delete mode 100644 assets/css/solari/inline_alert_badge.scss delete mode 100644 assets/css/solari/loading.scss delete mode 100644 assets/css/solari/psa.scss delete mode 100644 assets/css/solari/screen_container.scss delete mode 100644 assets/css/solari/section.scss delete mode 100644 assets/css/solari/section_list.scss delete mode 100644 assets/css/solari_large_v2.scss rename assets/css/{solari => v2}/arrow.scss (100%) create mode 100644 assets/css/v2/pre_fare/departure_time.scss delete mode 100644 assets/css/v2/solari_large/normal_header.scss delete mode 100644 assets/css/v2/solari_large/screen/normal.scss delete mode 100644 assets/css/v2/solari_large/screen/takeover.scss delete mode 100644 assets/src/apps/solari.tsx delete mode 100644 assets/src/apps/v2/solari_large.tsx delete mode 100644 assets/src/components/eink/base_departure_destination.tsx delete mode 100644 assets/src/components/eink/base_departure_time.tsx delete mode 100644 assets/src/components/eink/base_route_pill.tsx delete mode 100644 assets/src/components/eink/digital_bridge.tsx delete mode 100644 assets/src/components/eink/full_screen_takeover.tsx delete mode 100644 assets/src/components/eink/global_alert.tsx delete mode 100644 assets/src/components/eink/green_line/departures.tsx delete mode 100644 assets/src/components/eink/green_line/double/screen_container.tsx delete mode 100644 assets/src/components/eink/green_line/fare_info.tsx delete mode 100644 assets/src/components/eink/green_line/header.tsx delete mode 100644 assets/src/components/eink/green_line/inline_alert.tsx delete mode 100644 assets/src/components/eink/green_line/line_map.tsx delete mode 100644 assets/src/components/eink/green_line/nearby_departures.tsx delete mode 100644 assets/src/components/eink/green_line/single/screen_container.tsx delete mode 100644 assets/src/components/eink/green_line/takeover_inline_alert.tsx delete mode 100644 assets/src/components/eink/loading_top.tsx delete mode 100644 assets/src/components/eink/no_connection_bottom.tsx delete mode 100644 assets/src/components/eink/no_connection_single.tsx delete mode 100644 assets/src/components/eink/no_connection_top.tsx delete mode 100644 assets/src/components/eink/no_service.tsx delete mode 100644 assets/src/components/eink/overnight_departures.tsx delete mode 100644 assets/src/components/eink/screen_page.tsx delete mode 100644 assets/src/components/eink/takeover_alert.tsx delete mode 100644 assets/src/components/eink/takeover_screen_layout.tsx delete mode 100644 assets/src/components/solari/departure.tsx delete mode 100644 assets/src/components/solari/header.tsx delete mode 100644 assets/src/components/solari/headway_departure.tsx delete mode 100644 assets/src/components/solari/inline_alert_badge.tsx delete mode 100644 assets/src/components/solari/loading_layout.tsx delete mode 100644 assets/src/components/solari/psa.tsx delete mode 100644 assets/src/components/solari/route_pill.tsx delete mode 100644 assets/src/components/solari/screen_container.tsx delete mode 100644 assets/src/components/solari/section.tsx delete mode 100644 assets/src/components/solari/section_list.tsx delete mode 100644 assets/src/components/solari/section_list_container.tsx delete mode 100644 assets/src/components/solari/section_list_sizer.tsx rename assets/src/components/{solari => v2}/arrow.tsx (100%) delete mode 100644 assets/src/components/v2/solari_large/normal_screen.tsx delete mode 100644 assets/src/constants.tsx delete mode 100644 lib/screens/audio/fetch.ex delete mode 100644 lib/screens/line_map.ex delete mode 100644 lib/screens/nearby_connections.ex delete mode 100644 lib/screens/nearby_departures.ex delete mode 100644 lib/screens/psa.ex delete mode 100644 lib/screens/screen_data.ex delete mode 100644 lib/screens/solari_screen_data.ex delete mode 100644 lib/screens/v2/candidate_generator/solari_large.ex delete mode 100644 lib/screens_web/controllers/audio_controller.ex delete mode 100644 lib/screens_web/controllers/screen_api_controller.ex delete mode 100644 lib/screens_web/templates/audio/README.md delete mode 100644 lib/screens_web/templates/audio/_departure_pill_group.ssml.eex delete mode 100644 lib/screens_web/templates/audio/_header.ssml.eex delete mode 100644 lib/screens_web/templates/audio/index.ssml.eex delete mode 100644 lib/screens_web/views/audio_view.ex delete mode 100644 lib/screens_web/views/screen_view.ex delete mode 100644 test/screens/line_map_test.exs delete mode 100644 test/screens/v2/candidate_generator/solari_large_test.exs delete mode 100644 test/screens_web/controllers/page_controller_test.exs delete mode 100644 test/screens_web/controllers/screen_api_controller_test.exs diff --git a/assets/css/fonts_inter.scss b/assets/css/fonts_inter.scss deleted file mode 100644 index bac647884..000000000 --- a/assets/css/fonts_inter.scss +++ /dev/null @@ -1,39 +0,0 @@ -// https://fonts.adobe.com/fonts/neue-haas-grotesk#details-section -// We're temporarily using Inter in place of Neue Haas Grotesk, -// since we can't access Typekit from the Solari signs due to SSL issues. - -@mixin font--display--65medium { - font-family: Inter, sans-serif; - font-weight: 600; -} - -@mixin font--display--75bold { - font-family: Inter, sans-serif; - font-weight: 700; -} - -@mixin font--text--55roman { - font-family: Inter, sans-serif; - font-weight: 400; -} - -@mixin font--text--55regular { - @include font--text--55roman; -} - -@mixin font--text--65medium { - font-family: Inter, sans-serif; - font-weight: 500; -} - -@mixin font--text--75bold { - font-family: Inter, sans-serif; - font-weight: 700; -} - -// https://rsms.me/inter/ - -@mixin font--paragraph--regular { - font-family: Inter, sans-serif; - font-weight: 400; -} diff --git a/assets/css/pre_fare_v2.scss b/assets/css/pre_fare_v2.scss index fc369dcd9..dec678658 100644 --- a/assets/css/pre_fare_v2.scss +++ b/assets/css/pre_fare_v2.scss @@ -2,11 +2,9 @@ @import "colors"; -// Don't import from solari... put in common place! @import "fonts"; -@import "solari/arrow"; -@import "solari/departure_time"; +@import "v2/arrow"; @import "v2/clock_icon"; @import "v2/pre_fare/viewport"; @@ -36,6 +34,7 @@ @import "v2/pre_fare/evergreen/video"; @import "v2/placeholder"; +@import "v2/pre_fare/departure_time"; @import "v2/pre_fare/elevator_status"; @import "v2/pre_fare/flex/paging_indicator"; @import "v2/pre_fare/full_line_map"; diff --git a/assets/css/solari.scss b/assets/css/solari.scss deleted file mode 100644 index 3884617d5..000000000 --- a/assets/css/solari.scss +++ /dev/null @@ -1,30 +0,0 @@ -@import "https://rsms.me/inter/inter.css"; - -@import "fonts_inter"; - -@import "screen_page"; - -@import "base/base_route_pill"; - -@import "solari/screen_container"; -@import "solari/departure"; -@import "solari/departure_crowding"; -@import "solari/departure_destination"; -@import "solari/departure_route"; -@import "solari/departure_time"; -@import "solari/departure_headway"; -@import "solari/section"; -@import "solari/section_list"; -@import "solari/header"; -@import "solari/arrow"; -@import "solari/inline_alert_badge"; -@import "solari/psa"; -@import "solari/loading"; - -body { - margin: 0; - - &.scroll-disabled { - overflow: hidden; - } -} diff --git a/assets/css/solari/departure.scss b/assets/css/solari/departure.scss deleted file mode 100644 index 1655ba939..000000000 --- a/assets/css/solari/departure.scss +++ /dev/null @@ -1,239 +0,0 @@ -.departures-container { - background-color: #f5f4f1; -} - -.section--vertical { - .departures-container { - border-left: 88px solid #e3e3e3; - } -} - -// Add a border before each group start, except for the first one -.departure-container--group-start:not(:first-child) { - border-top: 3px solid rgb(199 199 199 / 58%); -} - -// While a row is animating out, don't show a border above the next group start unless the exiting row is a group end -// This prevents us from showing a border above the new group start until the animation is complete -.departure-animated--arr-brd-exit-active:not(.departure-container--group-end) - + .departure-container--group-start { - border-top: none; -} - -// Add a border above the later departure section, but not above departure rows inside it -.later-departure { - border-top: 3px solid rgb(199 199 199 / 58%); - - .departure-container { - border-top: none; - } -} - -.departure-container--group-start:not(.departure-container--group-end) { - .departure__alerts-container { - position: absolute; - } -} - -.screen-container--size-normal { - .departure { - padding-top: 7px; - padding-bottom: 7px; - margin-right: 42px; - margin-left: 42px; - } - - .departure-container--group-start { - .departure--with-via { - padding-top: 13.5px; - } - - .departure--no-via { - padding-top: 24px; - } - } - - // Don't add extra padding above the first row in a group until the previous row is done animating. - .departure-animated--arr-brd-exit-active:not(.departure-container--group-end) - + .departure-container--group-start { - .departure--with-via { - padding-top: 7px; - } - - .departure--no-via { - padding-top: 7px; - } - } - - .departure-container--group-end { - .departure--with-via { - padding-bottom: 13.5px; - } - - .departure--no-via { - padding-bottom: 24px; - } - } - - .departure__alerts-container { - height: 48px; - margin-top: 6px; - margin-left: 168px; - } -} - -.screen-container--size-large { - .departure { - padding-top: 0; - padding-bottom: 0; - margin-right: 32px; - margin-left: 32px; - } - - .departure-container--group-start { - .departure { - padding-top: 32px; - } - } - - // Don't add extra padding above the first row in a group until the previous row is done animating. - .departure-animated--arr-brd-exit-active + .departure-container--group-start { - padding-top: 0; - } - - .departure-container--group-end { - .departure { - padding-bottom: 32px; - } - } - - .departure__alerts-container { - height: 112px; - margin-top: 6px; - margin-left: 232px; - } -} - -// Here, we animate out the departure time, unless the row which is animating out is the last of -// its departure group, in which case we animate out the whole row. -.departure-animated { - &--normal { - // hide new departures until the previous departure has animated out - &-enter { - display: none; - } - - &-enter-active { - display: none; - } - } - - &--arr-brd { - &-exit { - .departure-time { - transform: translateX(0%); - } - } - - &-exit-active { - .departure-time { - transition: transform 200ms cubic-bezier(0.32, 0, 0.67, 0); - transform: translateX(100%); - } - } - } -} - -.departure-container--group-end.departure-animated { - &--normal { - // hide new departures until the previous departure has animated out - &-enter { - display: none; - } - - &-enter-active { - display: none; - } - } - - &--arr-brd { - &-exit { - transform: translateX(0%); - } - - &-exit-active { - transition: transform 200ms cubic-bezier(0.32, 0, 0.67, 0); - transform: translateX(100%); - } - } -} - -// The following styles hide route pills, destinations and alerts when a row is animating out. -// They achieve this by setting opacity to 0 in all rows following an exiting row, but then -// overriding this by setting opacity to 1 in all rows including and following the next group start. - -// The next three styles handle the case where the exiting row isn't grouped with any other departures. -// In this case, the next group start is the first row in the next group, and we want to show it. -.departure-animated--arr-brd-exit-active.departure-container--group-start.departure-container--group-end - ~ .departure-container { - .departure-route, - .departure-destination, - .departure__alerts-container { - opacity: 0; - } -} - -.departure-animated--arr-brd-exit-active.departure-container--group-start.departure-container--group-end - ~ .departure-container--group-start { - .departure-route, - .departure-destination, - .departure__alerts-container { - opacity: 1; - } -} - -.departure-animated--arr-brd-exit-active.departure-container--group-start.departure-container--group-end - ~ .departure-container--group-start - ~ .departure-container { - .departure-route, - .departure-destination, - .departure__alerts-container { - opacity: 1; - } -} - -// The next three styles handle the case where the exiting row is grouped with other departures. -// In this case, the next group start is the new first row in the current group, and we want to hide it. -.departure-animated--arr-brd-exit-active ~ .departure-container { - .departure-route, - .departure-destination, - .departure__alerts-container { - opacity: 0; - } -} - -.departure-animated--arr-brd-exit-active - ~ .departure-container--group-start - ~ .departure-container--group-start { - .departure-route, - .departure-destination, - .departure__alerts-container { - opacity: 1; - } -} - -.departure-animated--arr-brd-exit-active - ~ .departure-container--group-start - ~ .departure-container--group-start - ~ .departure-container { - .departure-route, - .departure-destination, - .departure__alerts-container { - opacity: 1; - } -} - -// Make sure the dummy section list is easy to tell from the real one -.section-list--dummy .departures-container { - background-color: #f0f; -} diff --git a/assets/css/solari/departure_crowding.scss b/assets/css/solari/departure_crowding.scss deleted file mode 100644 index c6d1ff973..000000000 --- a/assets/css/solari/departure_crowding.scss +++ /dev/null @@ -1,53 +0,0 @@ -.departure-crowding { - display: inline-block; - vertical-align: middle; - - &--normal { - width: 52px; - padding-right: 18px; - } - - &--overhead { - width: 209px; - height: 127.5px; - padding-right: 25px; - text-align: right; - - // Only actually visible for 5 seconds, but we hold the crowding icon off-screen for an extra - // 5 seconds to ensure that we have time for the new data request to complete. - animation: swap-with-time 10000ms linear; - - @keyframes swap-with-time { - 0% { - transform: translateX(200%); - } - - // 200ms to enter/leave - 2% { - transform: translateX(0); - } - - 48% { - transform: translateX(0); - } - - // Finish leaving after 5 seconds - 50% { - transform: translateX(200%); - } - - 100% { - transform: translateX(200%); - } - } - } -} - -.departure-crowding__image--normal { - width: 52px; -} - -.departure-crowding__image--overhead { - height: 89px; - padding-top: 19px; -} diff --git a/assets/css/solari/departure_destination.scss b/assets/css/solari/departure_destination.scss deleted file mode 100644 index b158ce73d..000000000 --- a/assets/css/solari/departure_destination.scss +++ /dev/null @@ -1,47 +0,0 @@ -@import "../base/base_departure_destination"; - -.departure-destination { - display: inline-block; - color: #171f26; - vertical-align: middle; -} - -.screen-container--size-normal { - .departure-destination { - width: 558px; - font-size: 50px; - - &--no-departures-placeholder { - width: 815px; - font-size: 40px; - } - } -} - -.section--vertical { - .departure-destination { - width: 470px; - - &--no-departures-placeholder { - width: 727px; - } - } -} - -.screen-container--size-large { - .departure-destination { - @include font--text--65medium; - - width: 550px; - font-size: 96px; - - &--no-departures-placeholder { - width: 784px; - font-size: 84px; - } - } - - .base-departure-destination__secondary { - font-size: 0.875em; - } -} diff --git a/assets/css/solari/departure_headway.scss b/assets/css/solari/departure_headway.scss deleted file mode 100644 index bd26a3ea1..000000000 --- a/assets/css/solari/departure_headway.scss +++ /dev/null @@ -1,35 +0,0 @@ -.departure-headway { - display: inline-block; - width: 300px; - text-align: right; - vertical-align: middle; -} - -.departure-headway__destination { - display: inline-block; - width: 528px; - font-size: 50px; - color: #171f26; - vertical-align: middle; -} - -.departure-headway__highlight { - display: inline-block; - color: white; - background: #007373; - border-radius: 5.5px; - box-shadow: 0 8px 20px rgb(0 0 0 / 23%); -} - -.departure-headway__message { - @include font--text--55regular; - - margin-right: 10px; - margin-left: 10px; - font-size: 36px; - line-height: 52px; -} - -.departure-headway__range { - @include font--text--75bold; -} diff --git a/assets/css/solari/departure_route.scss b/assets/css/solari/departure_route.scss deleted file mode 100644 index f74745e05..000000000 --- a/assets/css/solari/departure_route.scss +++ /dev/null @@ -1,65 +0,0 @@ -.departure-route { - display: inline-block; - vertical-align: middle; -} - -.departure-route--yellow .base-route-pill__pill { - color: black; - background: #ffc72c; -} - -.departure-route--blue .base-route-pill__pill { - color: white; - background: #003da5; -} - -.departure-route--red .base-route-pill__pill { - color: white; - background: #da291c; -} - -.departure-route--orange .base-route-pill__pill { - color: white; - background: #ed8b00; -} - -.departure-route--green .base-route-pill__pill { - color: white; - background: #00843d; -} - -.departure-route--purple .base-route-pill__pill { - color: white; - background: #80276c; -} - -.departure-route--silver .base-route-pill__pill { - color: white; - background: #7c878e; -} - -.screen-container--size-normal { - .departure-route { - width: 142px; - margin-right: 26px; - font-size: 50px; - } - - .departure-route--icon { - height: 50px; - margin-top: 4.5px; - } -} - -.screen-container--size-large { - .departure-route { - width: 200px; - margin-right: 32px; - font-size: 96px; - } - - .departure-route--icon { - height: 72px; - margin-top: 4.5px; - } -} diff --git a/assets/css/solari/departure_time.scss b/assets/css/solari/departure_time.scss deleted file mode 100644 index afcfbd828..000000000 --- a/assets/css/solari/departure_time.scss +++ /dev/null @@ -1,72 +0,0 @@ -@import "../base/base_departure_time"; - -.departure-time { - display: inline-block; - color: #171f26; - text-align: right; - vertical-align: middle; - - &--animated { - animation: pulse 500ms cubic-bezier(0.65, 0, 0.35, 1) infinite; - } - - @keyframes pulse { - 0% { - opacity: 1; - } - - 50% { - opacity: 0.3; - } - - 100% { - opacity: 1; - } - } - - &--overhead-with-crowding { - // Time is visible for 10s before we switch to showing crowding - animation: swap-with-crowding 10000ms linear; - - @keyframes swap-with-crowding { - 0% { - transform: translateX(200%); - } - - // 200ms to enter/leave - 2% { - transform: translateX(0); - } - - 98% { - transform: translateX(0); - } - - 100% { - transform: translateX(200%); - } - } - } -} - -.screen-container--size-normal { - .departure-time { - width: 200px; - font-size: 50px; - } -} - -.screen-container--size-large { - .departure-time { - width: 234px; - font-size: 108px; - } - - .base-departure-time__timestamp { - font-size: 0.5em; - } - - .base-departure-time__ampm { - font-size: 0.365em; - } -} diff --git a/assets/css/solari/header.scss b/assets/css/solari/header.scss deleted file mode 100644 index 8aef52f95..000000000 --- a/assets/css/solari/header.scss +++ /dev/null @@ -1,88 +0,0 @@ -.header { - color: white; - background-color: #171f26; -} - -.header__time { - @include font--text--55regular; - - position: absolute; -} - -.header__content-container { - padding-top: 128px; - padding-left: 48px; -} - -.header__station-name { - @include font--display--75bold; - - letter-spacing: 2px; -} - -.header__subtitle { - @include font--display--65medium; - - letter-spacing: 3px; -} - -.header__environment { - @include font--text--75bold; - - position: absolute; - top: 12px; - left: 40px; - font-size: 48px; - line-height: 48px; - color: #fff; - letter-spacing: 0; -} - -.screen-container--size-normal { - .header { - height: 355px; - } - - .header__time { - top: 23px; - right: 35px; - font-size: 50px; - line-height: 72px; - } - - .header__station-name { - font-size: 111px; - line-height: 120px; - } - - .header__subtitle { - margin-top: 16px; - font-size: 50px; - line-height: 60px; - text-transform: uppercase; - } -} - -.screen-container--size-large { - .header { - height: 456px; - } - - .header__time { - top: 32px; - right: 40px; - font-size: 96px; - line-height: 96px; - } - - .header__station-name { - font-size: 156px; - line-height: 156px; - } - - .header__subtitle { - margin-top: 32px; - font-size: 96px; - line-height: 96px; - } -} diff --git a/assets/css/solari/inline_alert_badge.scss b/assets/css/solari/inline_alert_badge.scss deleted file mode 100644 index 09dfe0464..000000000 --- a/assets/css/solari/inline_alert_badge.scss +++ /dev/null @@ -1,135 +0,0 @@ -.inline-alert-badge { - display: inline-block; - vertical-align: middle; - box-shadow: 0 5px 21px rgb(0 0 0 / 20%); - - &:not(:last-child) { - margin-right: 30px; - } - - &--delay { - background: #171f26; - } - - &--snow_route { - box-sizing: border-box; - background: #fff; - border: 3px solid #171f26; - } - - &--last_trip { - background: #404274; - } -} - -.inline-alert-badge__text { - display: inline-block; - text-transform: uppercase; - vertical-align: middle; - - @include font--text--65medium; - - &--delay { - color: #fff; - } - - &--snow_route { - color: #171f26; - } - - &--last_trip { - color: #fff; - } -} - -.inline-alert-badge__icon-container { - display: inline-block; - font-size: 0; - vertical-align: middle; -} - -.inline-alert-badge__icon-image { - vertical-align: middle; -} - -.screen-container--size-normal { - .inline-alert-badge { - height: 48px; - border-radius: 5px; - } - - .inline-alert-badge__text { - margin-right: 19px; - margin-left: 12px; - font-size: 30px; - line-height: 48px; - - &--snow_route { - line-height: 42px; - } - } - - .inline-alert-badge__icon-container { - width: 31px; - height: 48px; - margin-left: 12px; - - &--snow_route { - height: 42px; - } - } - - .inline-alert-badge__icon-image { - width: 32px; - height: 32px; - margin-top: 8px; - - &--snow_route { - margin-top: 5px; - } - } -} - -.screen-container--size-large { - .inline-alert-badge { - height: 112px; - border-radius: 10px; - } - - .inline-alert-badge__text { - margin-right: 46px; - margin-left: 32px; - font-size: 60px; - line-height: 112px; - - &--snow_route { - line-height: 106px; - } - } - - .inline-alert-badge__icon-container { - width: 60px; - height: 76px; - margin-left: 30px; - - &--snow_route { - height: 70px; - } - } - - .inline-alert-badge__icon-image { - width: 60px; - height: 60px; - margin-top: 8px; - - &--snow_route { - margin-top: 5px; - } - } -} - -.departure-group--multiple-rows { - .inline-alert-badge { - position: relative; - } -} diff --git a/assets/css/solari/loading.scss b/assets/css/solari/loading.scss deleted file mode 100644 index 7091e5322..000000000 --- a/assets/css/solari/loading.scss +++ /dev/null @@ -1,45 +0,0 @@ -.page-load-no-data-container { - box-sizing: border-box; - width: 1080px; - height: 1920px; - padding-left: 80px; - margin: 0 auto; - overflow: hidden; - background: #e6e4e1; -} - -.page-load-no-data__main-content__heading { - width: 810px; - margin-bottom: 64px; - font-family: Inter, sans-serif; - font-size: 104px; - font-weight: bold; - line-height: 112px; - color: #000; - letter-spacing: 0; -} - -.page-load-no-data__main-content__subheading { - width: 916px; - height: 184px; - margin-top: 47px; - font-family: Inter, sans-serif; - font-size: 72px; - font-weight: normal; - line-height: 92px; - color: #000; - letter-spacing: 0; -} - -.page-load-no-data__main-content__loading-icon-container { - width: 236px; - height: 236px; - margin-top: 155px; - margin-bottom: 56px; -} - -.page-load-no-data__main-content__loading-icon { - width: 100%; - height: 100%; - object-fit: contain; -} diff --git a/assets/css/solari/psa.scss b/assets/css/solari/psa.scss deleted file mode 100644 index baa9fcd99..000000000 --- a/assets/css/solari/psa.scss +++ /dev/null @@ -1,46 +0,0 @@ -.psa { - position: absolute; - bottom: 100px; - left: -100%; - animation: slide 5s cubic-bezier(0.32, 0, 0.67, 0) forwards; - animation-delay: 9s; -} - -.psa__progress-bar { - position: relative; - bottom: 24px; - width: 100%; - height: 20px; - content: ""; - background-color: rgb(255 255 255 / 50%); - animation: progress 4.6s linear forwards; - animation-delay: 9.2s; -} - -@keyframes slide { - 0% { - left: -100%; - } - - 4% { - left: 0; - } - - 96% { - left: 0; - } - - 100% { - left: -100%; - } -} - -@keyframes progress { - 0% { - transform: translateX(-100%); - } - - 100% { - transform: translateX(0); - } -} diff --git a/assets/css/solari/screen_container.scss b/assets/css/solari/screen_container.scss deleted file mode 100644 index 0b34a7419..000000000 --- a/assets/css/solari/screen_container.scss +++ /dev/null @@ -1,15 +0,0 @@ -.screen-container { - position: relative; - width: 1080px; - height: 1920px; - margin: 0 auto; - overflow-y: hidden; - background: repeating-linear-gradient( - -45deg, - #f5f4f1 0%, - #f5f4f1 40%, - rgb(199 199 199 / 58%) 40%, - rgb(199 199 199 / 58%) 50% - ); - background-size: 15px 15px; -} diff --git a/assets/css/solari/section.scss b/assets/css/solari/section.scss deleted file mode 100644 index 81c344133..000000000 --- a/assets/css/solari/section.scss +++ /dev/null @@ -1,187 +0,0 @@ -.section { - border-top: 5px solid #c5c7c9; -} - -.section--vertical { - position: relative; - - .section-header { - position: absolute; - top: 0; - left: 0; - width: 88px; - color: black; - } - - .section-header__icon { - display: block; - margin: 24px auto 0; - } - - .later-departure__header-title { - font-size: 30px; - } -} - -.section--normal { - .section-header { - height: 123px; - color: #171f26; - background-color: #e1e1df; - } - - .section-header__name { - margin-left: 48px; - font-size: 50px; - line-height: 123px; - } -} - -.section-header__name { - @include font--text--75bold; - - vertical-align: middle; -} - -.section-header__arrow-container { - float: right; - width: 57px; - height: 123px; - margin-right: 50px; - font-size: 0; - vertical-align: middle; -} - -.section-header__arrow-image { - width: 57px; - height: 57px; - margin: 33px 0; -} - -.later-departure__header { - position: relative; - margin-top: 12px; - margin-right: 42px; - margin-left: 42px; - border-bottom: 5px solid #ababab; -} - -.later-departure__header-title { - display: inline-block; - margin-top: 34px; - margin-bottom: 27px; - font-size: 36px; - line-height: 40px; - color: #171f26; - text-transform: uppercase; - letter-spacing: 2px; - - @include font--text--65medium; -} - -.later-departure__header-route-list { - position: absolute; - top: 31px; - right: 0; - display: inline-block; - - &--size-normal { - .later-departure__route-pill--size-small { - transform: translateY(-4.5px); - } - } -} - -.later-departure__route-pill-caret { - position: absolute; - top: 44px; - right: 0; - display: inline-block; - width: 0; - height: 0; - content: ""; - border-style: solid; - border-width: 14px; - transition: transform 0.2s ease; - - &--before { - border-color: transparent transparent #ababab; - } - - &--after { - margin-top: 7px; - border-color: transparent transparent #f5f4f1; - } -} - -.later-departure__route-pill { - display: inline-block; - margin-left: 25px; - text-align: center; - border-radius: 46px; - - &--selected { - height: 46px; - - &.later-departure__route-pill { - // Width depends on the route - - &--width-wide { - width: 158px; - } - - &--width-normal { - width: 89px; - } - - // Different selected pill colors by mode - - &--commuter-rail { - color: #fff; - background: #80276c; - } - - &--bus { - color: #000; - background: #ffc72c; - } - } - } - - &--unselected { - height: 40px; - color: rgb(23 31 38 / 80%); - border: 3px solid rgb(23 31 38 / 80%); - - // Width depends on the route - &.later-departure__route-pill { - &--width-wide { - width: 152px; - } - - &--width-normal { - width: 83px; - } - } - } -} - -.later-departure__route-text { - @include font--text--75bold; - - &--size-small { - font-size: 24px; - } - - &--size-normal { - font-size: 36px; - } - - &--selected { - line-height: 46px; - } - - &--unselected { - line-height: 40px; - } -} diff --git a/assets/css/solari/section_list.scss b/assets/css/solari/section_list.scss deleted file mode 100644 index d8e759287..000000000 --- a/assets/css/solari/section_list.scss +++ /dev/null @@ -1,21 +0,0 @@ -// Comment out the *'d styles to view the dummy section list in dev. -// (scroll right from the visible portion) - -.section-list-container { - position: relative; - - // * - overflow: hidden; -} - -.section-list { - &--dummy { - position: absolute; - top: 0; - left: 1080px; - width: 1080px; - - // * - opacity: 0; - } -} diff --git a/assets/css/solari_large_v2.scss b/assets/css/solari_large_v2.scss deleted file mode 100644 index 66b8c7c9e..000000000 --- a/assets/css/solari_large_v2.scss +++ /dev/null @@ -1,17 +0,0 @@ -@import "https://rsms.me/inter/inter.css"; - -@import "v2/solari_large/screen/normal"; -@import "v2/solari_large/screen/takeover"; - -@import "v2/placeholder"; -@import "v2/solari_large/normal_header"; - -@import "v2/multi_screen_page"; - -body { - margin: 0; -} - -.multi-screen-page { - grid-auto-rows: 1920px; -} diff --git a/assets/css/solari/arrow.scss b/assets/css/v2/arrow.scss similarity index 100% rename from assets/css/solari/arrow.scss rename to assets/css/v2/arrow.scss diff --git a/assets/css/v2/pre_fare/departure_time.scss b/assets/css/v2/pre_fare/departure_time.scss new file mode 100644 index 000000000..120080dda --- /dev/null +++ b/assets/css/v2/pre_fare/departure_time.scss @@ -0,0 +1,26 @@ +@import "../../base/base_departure_time"; + +.departure-time { + display: inline-block; + color: #171f26; + text-align: right; + vertical-align: middle; + + &--animated { + animation: pulse 500ms cubic-bezier(0.65, 0, 0.35, 1) infinite; + } + + @keyframes pulse { + 0% { + opacity: 1; + } + + 50% { + opacity: 0.3; + } + + 100% { + opacity: 1; + } + } +} diff --git a/assets/css/v2/solari_large/normal_header.scss b/assets/css/v2/solari_large/normal_header.scss deleted file mode 100644 index 775461030..000000000 --- a/assets/css/v2/solari_large/normal_header.scss +++ /dev/null @@ -1,48 +0,0 @@ -.normal-header { - position: relative; - width: 100%; - height: 100%; - background: rgb(23 31 38); -} - -.normal-header-icon { - display: inline-block; - vertical-align: middle; -} - -.normal-header-icon__image { - width: 184px; - height: 184px; - margin-right: 28px; -} - -.normal-header-title { - position: absolute; - bottom: 40px; - left: 40px; - width: 824px; - font-size: 164px; - line-height: 136px; -} - -.normal-header-title__text { - display: inline-block; - font-family: Helvetica, sans-serif; - font-weight: 700; - color: #fff; - vertical-align: middle; - - .normal-header-title--with-icon & { - width: 612px; - } -} - -.normal-header-time { - position: absolute; - top: 40px; - right: 40px; - font-family: Inter; - font-size: 96px; - line-height: 96px; - color: #fff; -} diff --git a/assets/css/v2/solari_large/screen/normal.scss b/assets/css/v2/solari_large/screen/normal.scss deleted file mode 100644 index 70a67f966..000000000 --- a/assets/css/v2/solari_large/screen/normal.scss +++ /dev/null @@ -1,22 +0,0 @@ -.screen-normal { - position: relative; - width: 1080px; - margin-right: auto; - margin-left: auto; -} - -.screen-normal__header { - position: absolute; - top: 0; - left: 0; - width: 1080px; - height: 304px; -} - -.screen-normal__main-content { - position: absolute; - top: 304px; - left: 0; - width: 1080px; - height: 1616px; -} diff --git a/assets/css/v2/solari_large/screen/takeover.scss b/assets/css/v2/solari_large/screen/takeover.scss deleted file mode 100644 index dbee57202..000000000 --- a/assets/css/v2/solari_large/screen/takeover.scss +++ /dev/null @@ -1,14 +0,0 @@ -.screen-takeover { - position: relative; - width: 1080px; - margin-right: auto; - margin-left: auto; -} - -.screen-takeover__full-screen { - position: absolute; - top: 0; - left: 0; - width: 1080px; - height: 1920px; -} diff --git a/assets/src/apps/admin.tsx b/assets/src/apps/admin.tsx index 159c1aec3..06a9f91d8 100644 --- a/assets/src/apps/admin.tsx +++ b/assets/src/apps/admin.tsx @@ -12,7 +12,6 @@ import weakKey from "weak-key"; import { AllScreensTable, - SolariScreensTable, BusEinkV2ScreensTable, GLEinkV2ScreensTable, BuswayV2ScreensTable, @@ -38,7 +37,6 @@ const routes: [string, string, ComponentType][][] = [ ["pre-fare-v2-screens", "Pre-Fare", PreFareV2ScreensTable], ["busway-v2-screens", "Sectional", BuswayV2ScreensTable], ], - [["solari-screens", "Solari (v1)", SolariScreensTable]], [["screens-json-editor", "Config Editor", AdminScreenConfigForm]], [["image-manager", "Image Manager", ImageManager]], [["devops", "Devops", Devops]], diff --git a/assets/src/apps/solari.tsx b/assets/src/apps/solari.tsx deleted file mode 100644 index 08a327888..000000000 --- a/assets/src/apps/solari.tsx +++ /dev/null @@ -1,60 +0,0 @@ -import initSentry from "Util/sentry"; -initSentry("solari"); - -import initFullstory from "Util/fullstory"; -initFullstory(); - -require("../../css/solari.scss"); - -import React, { useEffect } from "react"; -import ReactDOM from "react-dom"; -import { BrowserRouter as Router, Route, Switch } from "react-router-dom"; - -import ScreenContainer, { - ScreenLayout, -} from "Components/solari/screen_container"; - -import { - AuditScreenPage, - MultiScreenPage, - ScreenPage, -} from "Components/eink/screen_page"; - -const App = (): JSX.Element => { - useEffect(watchdogSubscriptionEffect, []); - - return ( - - - - - - - - - - - - - - ); -}; - -const watchdogSubscriptionEffect = () => { - // Add a listener for "watchdog" events - window.addEventListener("message", handleWatchdogMessage); - - // Return a cleanup function for React to call if the component re-renders, unmounts, etc. - return () => { - window.removeEventListener("message", handleWatchdogMessage); - }; -}; - -const handleWatchdogMessage = (ev: MessageEvent) => { - // message is formatted this way {type:"watchdog", data: counter++ } - if (ev.data.type === "watchdog") { - (ev?.source as Window)?.postMessage(ev.data, "*"); - } -}; - -ReactDOM.render(, document.getElementById("app")); diff --git a/assets/src/apps/v2/solari_large.tsx b/assets/src/apps/v2/solari_large.tsx deleted file mode 100644 index 43605c774..000000000 --- a/assets/src/apps/v2/solari_large.tsx +++ /dev/null @@ -1,44 +0,0 @@ -import initSentry from "Util/sentry"; -initSentry("solari_large"); - -require("../../../css/solari_large_v2.scss"); - -import React from "react"; -import ReactDOM from "react-dom"; -import { BrowserRouter as Router, Route, Switch } from "react-router-dom"; -import ScreenPage from "Components/v2/screen_page"; -import { MappingContext } from "Components/v2/widget"; - -import NormalScreen from "Components/v2/solari_large/normal_screen"; -import TakeoverScreen from "Components/v2/takeover_screen"; -import Placeholder from "Components/v2/placeholder"; -import NormalHeader from "Components/v2/lcd/normal_header"; -import Departures from "Components/v2/departures"; -import MultiScreenPage from "Components/v2/multi_screen_page"; - -const TYPE_TO_COMPONENT = { - normal: NormalScreen, - takeover: TakeoverScreen, - placeholder: Placeholder, - normal_header: NormalHeader, - departures: Departures, -}; - -const App = (): JSX.Element => { - return ( - - - - - - - - - - - - - ); -}; - -ReactDOM.render(, document.getElementById("app")); diff --git a/assets/src/components/admin/admin_add_modal.tsx b/assets/src/components/admin/admin_add_modal.tsx index b813f921f..19ef15f6c 100644 --- a/assets/src/components/admin/admin_add_modal.tsx +++ b/assets/src/components/admin/admin_add_modal.tsx @@ -19,7 +19,6 @@ const fields = [ "elevator_v2", "gl_eink_v2", "pre_fare_v2", - "solari", ]), }, { @@ -57,7 +56,6 @@ const defaultAppParamsByAppId = { footer: footerWidgetParams, alerts: alertsWidgetParams, }, - solari: { station_name: "STATION_NAME" }, dup_v2: { header: { stop_id: "" }, evergreen_content: [], diff --git a/assets/src/components/admin/admin_tables.tsx b/assets/src/components/admin/admin_tables.tsx index 447b5d8e4..a67e56e0c 100644 --- a/assets/src/components/admin/admin_tables.tsx +++ b/assets/src/components/admin/admin_tables.tsx @@ -110,78 +110,6 @@ const AllScreensTable = (): JSX.Element => { return ; }; -const SolariScreensTable = (): JSX.Element => { - const columns = [ - { - Header: "Screen ID", - accessor: "id", - Cell: InspectorLink, - Filter: DefaultColumnFilter, - }, - { - Header: "Station Name", - accessor: buildAppParamAccessor("station_name"), - mutator: buildAppParamMutator("station_name"), - Cell: EditableCell, - Filter: DefaultColumnFilter, - FormCell: FormTextCell, - }, - { - Header: "Overhead", - accessor: buildAppParamAccessor("overhead"), - mutator: buildAppParamMutator("overhead"), - Cell: EditableCheckbox, - Filter: DefaultColumnFilter, - FormCell: FormBoolean, - }, - { - Header: "Section Headers", - accessor: buildAppParamAccessor("section_headers"), - mutator: buildAppParamMutator("section_headers"), - Cell: EditableSelect, - Filter: SelectColumnFilter, - FormCell: buildFormSelect([null, "normal", "vertical"]), - }, - { - Header: "Tags", - accessor: "tags", - Cell: EditableList, - Filter: DefaultColumnFilter, - filter: filterTags, - FormCell: FormTextCell, - }, - { - Header: "Sections", - accessor: buildAppParamAccessor("sections"), - mutator: buildAppParamMutator("sections"), - Cell: EditableTextarea, - disableFilters: true, - FormCell: FormTextarea, - }, - { - Header: "Audio PSA", - accessor: buildAppParamAccessor("audio_psa"), - mutator: buildAppParamMutator("audio_psa"), - Cell: EditableTextarea, - disableFilters: true, - FormCell: FormTextarea, - }, - { - Header: "PSA Config", - accessor: buildAppParamAccessor("psa_config"), - mutator: buildAppParamMutator("psa_config"), - Cell: EditableTextarea, - disableFilters: true, - FormCell: FormTextarea, - }, - ]; - - const dataFilter = ({ app_id }) => { - return app_id === "solari"; - }; - return ; -}; - const v2Columns = [ { Header: "Screen ID", @@ -479,5 +407,4 @@ export { ElevatorV2ScreensTable, GLEinkV2ScreensTable, PreFareV2ScreensTable, - SolariScreensTable, }; diff --git a/assets/src/components/eink/base_departure_destination.tsx b/assets/src/components/eink/base_departure_destination.tsx deleted file mode 100644 index 0b7e761ee..000000000 --- a/assets/src/components/eink/base_departure_destination.tsx +++ /dev/null @@ -1,38 +0,0 @@ -import React from "react"; - -const splitDestination = (destination) => { - const viaPattern = /(.+) (via .+)/; - const parenPattern = /(.+) (\(.+)/; - - if (viaPattern.test(destination)) { - return viaPattern.exec(destination)!.slice(1); - } else if (parenPattern.test(destination)) { - return parenPattern.exec(destination)!.slice(1); - } else { - return [destination]; - } -}; - -const BaseDepartureDestination = ({ destination }): JSX.Element | null => { - if (!destination) { - return null; - } - - const [primaryDestination, secondaryDestination] = - splitDestination(destination); - - return ( -
-
- {primaryDestination} -
- {secondaryDestination && ( -
- {secondaryDestination} -
- )} -
- ); -}; - -export default BaseDepartureDestination; diff --git a/assets/src/components/eink/base_departure_time.tsx b/assets/src/components/eink/base_departure_time.tsx deleted file mode 100644 index 9d084319f..000000000 --- a/assets/src/components/eink/base_departure_time.tsx +++ /dev/null @@ -1,45 +0,0 @@ -import React from "react"; - -import { TimeRepresentation } from "Util/time_representation"; - -interface BaseDepartureTimeProps { - time: TimeRepresentation; - hideAmPm?: boolean; -} - -const BaseDepartureTime = ({ - time, - hideAmPm, -}: BaseDepartureTimeProps): JSX.Element | null => { - if (time.type.toUpperCase() === "TEXT") { - return ( -
- {/* @ts-expect-error */} - {time.text} -
- ); - } else if (time.type.toUpperCase() === "MINUTES") { - return ( -
- {/* @ts-expect-error */} - {time.minutes} - m -
- ); - } else if (time.type.toUpperCase() === "TIMESTAMP") { - return ( -
- {/* @ts-expect-error */} - {time.timestamp} - {!hideAmPm && ( - /* @ts-expect-error */ - {time.ampm} - )} -
- ); - } else { - return null; - } -}; - -export default BaseDepartureTime; diff --git a/assets/src/components/eink/base_route_pill.tsx b/assets/src/components/eink/base_route_pill.tsx deleted file mode 100644 index ce83e7f5d..000000000 --- a/assets/src/components/eink/base_route_pill.tsx +++ /dev/null @@ -1,42 +0,0 @@ -import React from "react"; - -import { classWithModifier } from "Util/util"; - -const BaseRoutePill = ({ route }): JSX.Element => { - const isSlashRoute = typeof route === "string" && route.includes("/"); - - const modifier = isSlashRoute ? "with-slash" : "no-slash"; - - if (isSlashRoute) { - const parts = route.split("/"); - const part1 = parts[0] + "/"; - const part2 = parts[1]; - - return ( -
-
-
- {part1} -
-
- {part2} -
-
-
- ); - } - - return ( -
-
- {route} -
-
- ); -}; - -export default BaseRoutePill; diff --git a/assets/src/components/eink/digital_bridge.tsx b/assets/src/components/eink/digital_bridge.tsx deleted file mode 100644 index dd7ea75b1..000000000 --- a/assets/src/components/eink/digital_bridge.tsx +++ /dev/null @@ -1,38 +0,0 @@ -import QRCode from "qrcode.react"; -import React from "react"; - -import { imagePath } from "Util/util"; - -const DigitalBridge = ({ stopId }): JSX.Element => { - return ( -
-
- -
-
-
- Real-time predictions and stop info on the go -
-
mbta.com/stops/{stopId}
-
-
-
- -
-
-
- ); -}; - -export default DigitalBridge; diff --git a/assets/src/components/eink/full_screen_takeover.tsx b/assets/src/components/eink/full_screen_takeover.tsx deleted file mode 100644 index 6ac67715e..000000000 --- a/assets/src/components/eink/full_screen_takeover.tsx +++ /dev/null @@ -1,18 +0,0 @@ -import moment from "moment"; -import "moment-timezone"; -import React from "react"; - -const FullScreenTakeover = ({ srcPath, currentTimeString }): JSX.Element => { - const currentTime = moment(currentTimeString) - .tz("America/New_York") - .format("h:mm"); - - return ( -
-
{currentTime}
- -
- ); -}; - -export default FullScreenTakeover; diff --git a/assets/src/components/eink/global_alert.tsx b/assets/src/components/eink/global_alert.tsx deleted file mode 100644 index 07ff66cb3..000000000 --- a/assets/src/components/eink/global_alert.tsx +++ /dev/null @@ -1,41 +0,0 @@ -import moment from "moment"; -import "moment-timezone"; -import React from "react"; -import { imagePath } from "Util/util"; - -const iconForAlert = (alert) => { - // For now, shuttles will show a bus icon, and everything else will - // just show the default alert icon. - return { shuttle: "bus-negative-white" }[alert.effect] || "alert"; -}; - -const GlobalAlert = ({ alert }): JSX.Element => { - const updatedTime = moment(alert.updated_at); - return ( -
-
-
- -
-
- {alert.effect.replace("_", " ").replace(/\w\S*/g, (txt) => { - return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase(); - })} -
-
- Updated
- {updatedTime.tz("America/New_York").format("M/D/Y ยท h:mm A")} -
-
- -
-
{alert.header}
-
-
- ); -}; - -export default GlobalAlert; diff --git a/assets/src/components/eink/green_line/departures.tsx b/assets/src/components/eink/green_line/departures.tsx deleted file mode 100644 index 4f1698c89..000000000 --- a/assets/src/components/eink/green_line/departures.tsx +++ /dev/null @@ -1,196 +0,0 @@ -import React from "react"; - -import BaseDepartureTime from "Components/eink/base_departure_time"; -import InlineAlert from "Components/eink/green_line/inline_alert"; -import TakeoverInlineAlert from "Components/eink/green_line/takeover_inline_alert"; - -import { einkTimeRepresentation } from "Util/time_representation"; - -const Departure = ({ time, currentTimeString }): JSX.Element => { - return ( -
- -
- ); -}; - -enum HeadwayMessageVariant { - Main, - Sub, -} - -interface HeadwayMessageProps { - destination: string; - headway: number; - variant: HeadwayMessageVariant; -} -const HeadwayMessage = ({ - destination, - headway, - variant, -}: HeadwayMessageProps): JSX.Element => { - const range = 2; - const message = ( - <> - Trains to {destination} every{" "} - - {headway - range}-{headway + range} - {" "} - minutes - - ); - - switch (variant) { - case HeadwayMessageVariant.Main: - return ( - <> -
- {message} -
-
- Live updates are not available during this reduced service -
- - ); - case HeadwayMessageVariant.Sub: - return
{message}
; - } -}; - -// Displays up to 2 departures, padding with a headway -// message when there is only 0 or 1 departure to show. -const DepartureList = ({ - departures, - currentTimeString, - destination, - headway, -}: DepartureListProps): JSX.Element => { - const renderedDepartures = departures.map(({ id, time }) => ( - - )); - - if (renderedDepartures.length < 2 && headway) { - renderedDepartures.push( - , - ); - } - - return ( - <> - {[ - ...renderedDepartures.slice(0, 1), -
, - ...renderedDepartures.slice(1, 2), - ]} - - ); -}; - -interface DepartureListProps { - departures: { id: string; time: string }[]; - currentTimeString: string; - destination: string; - headway: number; -} - -const DeparturesListPsa = ({ psaUrl }): JSX.Element => { - return ( -
- -
- ); -}; - -const DepartureWithPsa = ({ - departures, - currentTimeString, - destination, - headway, - psaUrl, -}): JSX.Element => { - return ( - <> - {departures.length > 0 ? ( - - ) : ( - - )} - - - ); -}; - -const Departures = ({ - departures, - destination, - headway, - inlineAlert, - currentTimeString, - serviceLevel, - isHeadwayMode, - psaUrl, -}): JSX.Element => { - let departuresComponent; - - if (psaUrl) { - departuresComponent = ( - - ); - } else if (isHeadwayMode) { - departuresComponent = ( - - ); - } else { - departuresComponent = ( - - ); - } - - return ( -
-
- {departuresComponent} -
- {serviceLevel > 1 ? ( - - ) : ( - - )} -
-
-
- ); -}; - -export default Departures; diff --git a/assets/src/components/eink/green_line/double/screen_container.tsx b/assets/src/components/eink/green_line/double/screen_container.tsx deleted file mode 100644 index 4cb6a634a..000000000 --- a/assets/src/components/eink/green_line/double/screen_container.tsx +++ /dev/null @@ -1,175 +0,0 @@ -import React from "react"; - -import DigitalBridge from "Components/eink/digital_bridge"; -import GlobalAlert from "Components/eink/global_alert"; -import Departures from "Components/eink/green_line/departures"; -import FareInfo from "Components/eink/green_line/fare_info"; -import Header from "Components/eink/green_line/header"; -import LineMap from "Components/eink/green_line/line_map"; -import NearbyDepartures from "Components/eink/green_line/nearby_departures"; -import NoService from "Components/eink/no_service"; -import OvernightDepartures from "Components/eink/overnight_departures"; -import TakeoverAlert from "Components/eink/takeover_alert"; -import TakeoverScreenLayout from "Components/eink/takeover_screen_layout"; - -import useApiResponse from "Hooks/use_api_response"; - -import { EINK_REFRESH_MS } from "Constants"; -import NoConnectionTop from "Components/eink/no_connection_top"; -import NoConnectionBottom from "Components/eink/no_connection_bottom"; -import LoadingTop from "Components/eink/loading_top"; - -const TopScreenLayout = ({ - currentTimeString, - stopName, - departures, - routeId, - headway, - lineMapData, - inlineAlert, - isHeadwayMode, -}): JSX.Element => { - return ( -
-
- - {/* @ts-expect-error */} - -
- ); -}; - -const BottomScreenLayout = ({ - currentTimeString, - globalAlert, - stopId, - nearbyDepartures, - psaUrl, -}): JSX.Element => { - return ( -
-
- {psaUrl ? ( - - ) : ( - <> -
- -
-
- {globalAlert ? : null} -
- - )} -
- - -
- ); -}; - -const DefaultScreenLayout = ({ apiResponse }): JSX.Element => { - return ( -
- - -
- ); -}; - -const NoServiceScreenLayout = (): JSX.Element => { - // COVID Level 5 message - return ; -}; - -const NoDeparturesScreenLayout = ({ apiResponse }): JSX.Element => { - // We successfully fetched data, but there are no predictions, and we don't have - // a headway for the current daypart. For now, we assume that it's the middle of - // the night. - return ( - - ); -}; - -const NoConnectionScreenLayout = (): JSX.Element => { - // We weren't able to fetch data. Show a connection error message. - return ( -
- - -
- ); -}; - -const LoadingScreenLayout = (): JSX.Element => { - // We haven't recieved a response since page load. Show a loading message. - return ( -
- - -
- ); -}; - -const ScreenLayout = ({ apiResponse }): JSX.Element => { - switch (true) { - case !apiResponse || apiResponse.success === false: - return ; - case apiResponse.type === "loading": - return ; - case apiResponse.psa_type === "takeover" && apiResponse.psa_url != null: - return ; - case apiResponse.service_level === 5: - return ; - case (!apiResponse.departures || apiResponse.departures.length === 0) && - apiResponse.headway === null: - return ; - default: - return ; - } -}; - -const ScreenContainer = ({ id }): JSX.Element => { - const apiResponse = useApiResponse({ id, refreshMs: EINK_REFRESH_MS }); - return ; -}; - -export default ScreenContainer; -export { ScreenLayout }; diff --git a/assets/src/components/eink/green_line/fare_info.tsx b/assets/src/components/eink/green_line/fare_info.tsx deleted file mode 100644 index fc58c2ba5..000000000 --- a/assets/src/components/eink/green_line/fare_info.tsx +++ /dev/null @@ -1,37 +0,0 @@ -import React from "react"; -import { imagePath } from "Util/util"; - -const FareInfo = (): JSX.Element => { - return ( -
-
- -
-
-
Subway one-way
-
mbta.com/fares/subway-fares
-
-
- $2.40 - CharlieCard - - (1 free local bus transfer within 2 hrs) - -
-
- $2.40 - - CharlieTicket or cash{" "} - - (Limited transfers) -
-
-
-
- ); -}; - -export default FareInfo; diff --git a/assets/src/components/eink/green_line/header.tsx b/assets/src/components/eink/green_line/header.tsx deleted file mode 100644 index 4827e2352..000000000 --- a/assets/src/components/eink/green_line/header.tsx +++ /dev/null @@ -1,103 +0,0 @@ -import React, { useLayoutEffect, useRef, useState } from "react"; - -import { classWithModifier, formatTimeString, imagePath } from "Util/util"; - -import { getDatasetValue } from "Util/dataset"; - -const abbreviateStop = (stop) => { - if (stop === "Government Center") { - return "Government Ctr"; - } - - return stop; -}; - -const HeaderRouteIcon = ({ route }): JSX.Element => { - let path; - - if (route === "Green-B") { - path = ( - - ); - } else if (route === "Green-C") { - path = ( - - ); - } else if (route === "Green-D") { - path = ( - - ); - } else if (route === "Green-E") { - path = ( - - ); - } else { - path = null; - } - - return ( -
- - - {path} - - -
- ); -}; - -const Header = ({ stopName, routeId, currentTimeString }): JSX.Element => { - const SIZES = ["small", "large"]; - const MAX_HEIGHT = 216; - - const ref = useRef(null); - const [stopSize, setStopSize] = useState(1); - const currentTime = formatTimeString(currentTimeString); - - useLayoutEffect(() => { - if (ref.current && ref.current.clientHeight > MAX_HEIGHT) { - setStopSize(stopSize - 1); - } - }); - - const environmentName = getDatasetValue("environmentName"); - - const abbreviatedStop = abbreviateStop(stopName); - - return ( -
-
- {["screens-dev", "screens-dev-green"].includes(environmentName!) - ? environmentName - : ""} -
-
{currentTime}
-
- - UPDATED LIVE EVERY MINUTE -
-
-
- -
-
- {abbreviatedStop} -
-
-
- ); -}; - -export default Header; diff --git a/assets/src/components/eink/green_line/inline_alert.tsx b/assets/src/components/eink/green_line/inline_alert.tsx deleted file mode 100644 index 4979175fa..000000000 --- a/assets/src/components/eink/green_line/inline_alert.tsx +++ /dev/null @@ -1,53 +0,0 @@ -import React from "react"; -import { imagePath } from "Util/util"; - -const parseSeverity = (severity) => { - let delayDescription; - let delayMinutes; - - if (severity < 3) { - severity = 3; - } - if (severity > 9) { - severity = 9; - } - - if (severity >= 8) { - delayDescription = "more than"; - delayMinutes = 30 * (severity - 7); - } else { - delayDescription = "up to"; - delayMinutes = 5 * (severity - 1); - } - - return { - delayDescription, - delayMinutes, - }; -}; - -const InlineAlert = ({ alertData }): JSX.Element | null => { - if (alertData === undefined || alertData === null) { - return null; - } - - const severity = alertData.severity; - - if (severity === undefined) { - return null; - } - - const { delayDescription, delayMinutes } = parseSeverity(severity); - - return ( -
- - - Delays {delayDescription + " "} - {delayMinutes} minutes - -
- ); -}; - -export default InlineAlert; diff --git a/assets/src/components/eink/green_line/line_map.tsx b/assets/src/components/eink/green_line/line_map.tsx deleted file mode 100644 index a30b1a27e..000000000 --- a/assets/src/components/eink/green_line/line_map.tsx +++ /dev/null @@ -1,577 +0,0 @@ -import moment from "moment"; -import "moment-timezone"; -import React, { createContext, useContext } from "react"; - -import { einkTimeRepresentation } from "Util/time_representation"; - -const COLOR_WHITE = "#FFFFFF"; -const COLOR_BLACK = "#000000"; -const COLOR_LIGHT_GRAY = "#CCCCCC"; -const COLOR_DARK_GRAY = "#999999"; - -interface LineMapSchedule { - time: string; -} - -interface LineMapStops { - count_before: number; - current: string; - following: string; - next: string; - origin: string; -} - -interface LineMapVehicle { - id: string; - index: number; - time: string | null; -} - -interface LineMapData { - schedule: LineMapSchedule; - stops: LineMapStops; - vehicles: LineMapVehicle[]; -} - -const LineMapContext = createContext({}); - -const ScheduledDepartureIcon = ({ x, y, iconRadius }): JSX.Element => { - return ( - - - - - ); -}; - -const ScheduledDepartureDescription = ({ - x, - y, - iconRadius, - margin, - time, - stopName, -}): JSX.Element => { - return ( - - - {moment(time).tz("America/New_York").format("h:mm")} - - - Scheduled to depart - - - {stopName} - - - ); -}; - -const ScheduledDeparture = ({ stopName, schedule }): JSX.Element => { - const { lineBottomY, dy, radius, lineWidth } = useContext(LineMapContext); - - const time = schedule.departure_time; - const x = lineWidth / 2; - const y = lineBottomY + dy - radius; - const iconRadius = 30; - const margin = 9; - - return ( - - - - - ); -}; - -const LineMapStopLabel = ({ x, y, lines, current, origin }): JSX.Element => { - const fontSize = 24; - const lineHeight = 32; - const fontFamily = "neue-haas-grotesk-text"; - const fontWeight = current ? 700 : 400; - const fontColor = origin ? COLOR_DARK_GRAY : COLOR_BLACK; - - if (lines.length === 1) { - return ( - - {lines[0]} - - ); - } else if (lines.length === 2) { - return ( - - {lines[0]} - - {lines[1]} - - - ); - } else { - throw new Error(`unexpected value for lines: ${lines}`); - } -}; - -// Helper function -const degreesToRadians = (angleInDegrees) => { - return (angleInDegrees * Math.PI) / 180.0; -}; - -const LineMapVehicleDroplet = ({ x, y }): JSX.Element => { - // Parameters - const centerX = x; - const centerY = y; - const radius = 30; - const directionAngle = 45; - const iconSize = 44; - - // Compute points - const centerAngle = 270; - const startAngle = degreesToRadians(centerAngle - directionAngle); - const endAngle = degreesToRadians(centerAngle + directionAngle); - const startX = centerX + radius * Math.cos(startAngle); - const startY = centerY + radius * Math.sin(startAngle); - const endX = centerX + radius * Math.cos(endAngle); - const endY = centerY + radius * Math.sin(endAngle); - const pointX = centerX; - const pointY = centerY - radius / Math.cos((startAngle - endAngle) / 2); - - // Path data - const d = [ - "M", - startX, - startY, - "A", - radius, - radius, - 0, - 1, - 0, - endX, - endY, - "L", - pointX, - pointY, - "Z", - ].join(" "); - - return ( - - - - - ); -}; - -const LineMapVehicleLabel = ({ - x, - y, - time, - currentTimeString, -}): JSX.Element | null => { - const timeRep = einkTimeRepresentation(time, currentTimeString); - if (timeRep.type === "TEXT") { - return ( - - {timeRep.text} - - ); - } else if (timeRep.type === "MINUTES") { - return ( - - - {timeRep.minutes} - - - m - - - ); - } else { - return null; - } -}; - -const LineMapVehicleIcon = ({ x, y, size }): JSX.Element => { - const viewBoxSize = 128; // property of the path - const scale = size / viewBoxSize; - return ( - - - - - - ); -}; - -const LineMapVehicle = ({ vehicle, currentTimeString }): JSX.Element | null => { - const { lineWidth, radius, dy, height, stopMarginTop } = - useContext(LineMapContext); - - const x = lineWidth / 2; - const y = stopMarginTop + radius + vehicle.index * dy; - const time = vehicle.index >= 2 ? vehicle.time : null; - - if (y > height) { - return null; - } - - return ( - - - {time !== null && ( - - )} - - ); -}; - -const LineMapVehicles = ({ vehicles, currentTimeString }): JSX.Element => { - return vehicles.map((v) => ( - - )); -}; - -const LineMapLineBefore = (): JSX.Element => { - const { currentStopY, lineBottomY, lineWidth } = useContext(LineMapContext); - - const dPast = [ - "M", - 0, - currentStopY, - "L", - lineWidth, - currentStopY, - "L", - lineWidth, - lineBottomY, - "L", - 0, - lineBottomY, - "Z", - ].join(" "); - - return ; -}; - -const LineMapLineAfter = (): JSX.Element => { - const { currentStopY, lineWidth, marginTop } = useContext(LineMapContext); - - const dFuture = [ - "M", - lineWidth / 2, - marginTop, - "L", - lineWidth, - marginTop + lineWidth / 2, - "L", - lineWidth, - currentStopY, - "L", - 0, - currentStopY, - "L", - 0, - marginTop + lineWidth / 2, - "Z", - ].join(" "); - - return ; -}; - -const LineMapLine = (): JSX.Element => { - return ( - - - - - ); -}; - -const LineMapStop = ({ i, stopName }): JSX.Element => { - const { originStopIndex, lineWidth, radius, dy, stopMarginTop, textMargin } = - useContext(LineMapContext); - - return ( - - - {stopName !== null && ( - - )} - - ); -}; - -const LineMapCurrentStop = (): JSX.Element => { - const { currentStopY, lineWidth, strokeWidth, radius } = - useContext(LineMapContext); - - return ( - - ); -}; - -const LineMapOriginStop = (): JSX.Element => { - const { lineBottomY, lineWidth, strokeWidth, radius } = - useContext(LineMapContext); - - return ( - - ); -}; - -const LineMapStops = (): JSX.Element => { - const { showOriginStop, lastVisibleStopIndex, stopNames } = - useContext(LineMapContext); - return ( - - {[...Array(lastVisibleStopIndex + 1)].map((_, i) => ( - - ))} - - {showOriginStop && } - - ); -}; - -const LineMapBase = (): JSX.Element => { - return ( - - - - - ); -}; - -const LineMapContainer = ({ - data, - height, - currentTimeString, - showVehicles, -}: { - data: LineMapData; - height: number; - width: number; - currentTimeString: string; - showVehicles: boolean; -}): JSX.Element => { - const constants = { - radius: 14, - dy: 112, - lineWidth: 40, - marginLeft: 84, - marginTop: 32, - stopMarginTop: 110, - textMargin: 18, - strokeWidth: 16, - }; - - // We define the stop index to be the (zero-indexed) position - // of a stop on the line map, counting down from the top. - - // We always show two stops beyond the current stop - const currentStopIndex = 2; - const originStopIndex = currentStopIndex + data.stops.count_before; - - // The last stop which fits in the alloted vertical space - const lastVisibleStopIndex = Math.floor( - (height - constants.stopMarginTop - 2 * constants.radius) / constants.dy, - ); - - // Compute the y-position of the current stop - const currentStopY = - constants.stopMarginTop + - currentStopIndex * constants.dy + - constants.radius; - - // Determine the y-position of the bottom of the line. Initially, set this - // to the y-position of the origin stop. - let lineBottomY = - constants.stopMarginTop + originStopIndex * constants.dy + constants.radius; - - // Only show the origin if there's enough vertical space - const showOriginStop = - lineBottomY + constants.radius + constants.strokeWidth <= height; - - // If there isn't enough vertical space to show the origin, truncate the line - // to end at the given height. - if (!showOriginStop) { - lineBottomY = height; - } - - // Only show a scheduled departure if there's enough vertical space (and we have data) - const showScheduledDeparture = - data.schedule !== null && - lineBottomY + constants.dy + constants.strokeWidth <= height; - - // Build stopNames, an array of labels for stops. stopNames[i] is the label - // for stop index i. null values correspond to unlabeled stops. - const unlabeledStops = Array.from( - { length: originStopIndex - 3 }, - () => null, - ); - const stopNames = [ - data.stops.following, - data.stops.next, - data.stops.current, - ...unlabeledStops, - data.stops.origin, - ]; - - const props = { - originStopIndex, - currentStopY, - lineBottomY, - height, - showOriginStop, - stopNames, - showScheduledDeparture, - lastVisibleStopIndex, - }; - - const params = { ...constants, ...props }; - - return ( - - - - {showScheduledDeparture && ( - - )} - {showVehicles && ( - - )} - - - ); -}; - -const LineMap = ({ - data, - height, - currentTimeString, - showVehicles, -}: { - data: LineMapData; - height: number; - currentTimeString: string; - showVehicles: boolean; -}): JSX.Element => { - if (!data) { - return
; - } - - const width = 442; - return ( -
- - - -
- ); -}; - -export default LineMap; diff --git a/assets/src/components/eink/green_line/nearby_departures.tsx b/assets/src/components/eink/green_line/nearby_departures.tsx deleted file mode 100644 index e5a5119fc..000000000 --- a/assets/src/components/eink/green_line/nearby_departures.tsx +++ /dev/null @@ -1,93 +0,0 @@ -import React from "react"; - -import BaseDepartureTime from "Components/eink/base_departure_time"; -import BaseDepartureDestination from "Components/eink/base_departure_destination"; -import BaseRoutePill from "Components/eink/base_route_pill"; - -import { einkTimeRepresentation } from "Util/time_representation"; -import { imagePath } from "Util/util"; - -const NearbyDeparturesTime = ({ time, currentTimeString }): JSX.Element => { - return ( -
- -
- ); -}; - -const NearbyDeparturesRoute = ({ route }): JSX.Element => { - return ( -
- -
- ); -}; - -const NearbyDeparturesDestination = ({ destination }): JSX.Element => { - return ( -
- -
- ); -}; - -const NearbyDeparturesRow = ({ - name, - route, - time, - destination, - currentTimeString, -}): JSX.Element => { - return ( -
-
-
{name}
-
-
- - - -
-
-
- ); -}; - -const NearbyDepartures = ({ data, currentTimeString }): JSX.Element | null => { - if (!data || data.length === 0 || data.includes(null)) { - return null; - } - - return ( -
-
-
- -
-
Nearby departures
-
-
- {data.map((row, i) => ( -
- -
- ))} -
- ); -}; - -export default NearbyDepartures; diff --git a/assets/src/components/eink/green_line/single/screen_container.tsx b/assets/src/components/eink/green_line/single/screen_container.tsx deleted file mode 100644 index 6bcb811b6..000000000 --- a/assets/src/components/eink/green_line/single/screen_container.tsx +++ /dev/null @@ -1,127 +0,0 @@ -import React from "react"; - -import NoConnectionSingle from "Components/eink/no_connection_single"; -import DigitalBridge from "Components/eink/digital_bridge"; -import Departures from "Components/eink/green_line/departures"; -import Header from "Components/eink/green_line/header"; -import LineMap from "Components/eink/green_line/line_map"; -import { NoServiceTop } from "Components/eink/no_service"; -import OvernightDepartures from "Components/eink/overnight_departures"; -import TakeoverScreenLayout from "Components/eink/takeover_screen_layout"; - -import useApiResponse from "Hooks/use_api_response"; - -import { EINK_REFRESH_MS } from "Constants"; -import LoadingTop from "Components/eink/loading_top"; - -const TopScreenLayout = ({ - currentTimeString, - stopName, - departures, - stopId, - routeId, - lineMapData, - headway, - inlineAlert, - serviceLevel, - isHeadwayMode, - psaUrl, -}): JSX.Element => { - return ( -
-
- - - -
- ); -}; - -const DefaultScreenLayout = ({ apiResponse }): JSX.Element => { - return ( - - ); -}; - -const NoServiceScreenLayout = (): JSX.Element => { - // COVID Level 5 message - return ; -}; - -const NoDeparturesScreenLayout = ({ apiResponse }): JSX.Element => { - // We successfully fetched data, but there are no predictions, and we don't have - // a headway for the current daypart. For now, we assume that it's the middle of - // the night. - return ( - - ); -}; - -const NoConnectionScreenLayout = (): JSX.Element => { - // We weren't able to fetch data. Show a connection error message. - return ; -}; - -const LoadingScreenLayout = (): JSX.Element => { - // We haven't recieved a response since page load. Show a loading message. - return ; -}; - -const ScreenLayout = ({ apiResponse }): JSX.Element => { - switch (true) { - case !apiResponse || apiResponse.success === false: - return ; - case apiResponse.type === "loading": - return ; - case apiResponse.psa_type === "takeover": - return ; - case apiResponse.service_level === 5: - return ; - case (!apiResponse.departures || apiResponse.departures.length === 0) && - apiResponse.headway === null: - return ; - default: - return ; - } -}; - -const ScreenContainer = ({ id }): JSX.Element => { - const apiResponse = useApiResponse({ id, refreshMs: EINK_REFRESH_MS }); - return ; -}; - -export default ScreenContainer; -export { ScreenLayout }; diff --git a/assets/src/components/eink/green_line/takeover_inline_alert.tsx b/assets/src/components/eink/green_line/takeover_inline_alert.tsx deleted file mode 100644 index 6da9b65fe..000000000 --- a/assets/src/components/eink/green_line/takeover_inline_alert.tsx +++ /dev/null @@ -1,30 +0,0 @@ -import React from "react"; -import { imagePath } from "Util/util"; - -const TakeoverInlineAlert = (): JSX.Element => { - return ( -
-
-
- -
-
-
- We're running less service to help slow the spread of COVID-19. -
-
- More:{" "} - - mbta.com/coronavirus - -
-
-
-
- ); -}; - -export default TakeoverInlineAlert; diff --git a/assets/src/components/eink/loading_top.tsx b/assets/src/components/eink/loading_top.tsx deleted file mode 100644 index b179a0d20..000000000 --- a/assets/src/components/eink/loading_top.tsx +++ /dev/null @@ -1,40 +0,0 @@ -import React from "react"; -import { imagePath } from "Util/util"; -import Loading from "Components/v2/bundled_svg/loading"; - -const LoadingTop = (): JSX.Element => { - return ( -
-
-
- -
-
-
- Thank you for your patience -
-
-
-
-
- -
-
- Loading... -
-
-
- This should only take a moment. -
-
-
- ); -}; - -export default LoadingTop; diff --git a/assets/src/components/eink/no_connection_bottom.tsx b/assets/src/components/eink/no_connection_bottom.tsx deleted file mode 100644 index 573d2be0b..000000000 --- a/assets/src/components/eink/no_connection_bottom.tsx +++ /dev/null @@ -1,12 +0,0 @@ -import React from "react"; -import { imagePath } from "Util/util"; - -const NoConnectionBottom = (): JSX.Element => { - return ( -
- -
- ); -}; - -export default NoConnectionBottom; diff --git a/assets/src/components/eink/no_connection_single.tsx b/assets/src/components/eink/no_connection_single.tsx deleted file mode 100644 index 327539e80..000000000 --- a/assets/src/components/eink/no_connection_single.tsx +++ /dev/null @@ -1,16 +0,0 @@ -import moment from "moment"; -import React from "react"; -import { imagePath } from "Util/util"; - -const NoConnectionSingle = (): JSX.Element => { - const currentTime = moment().tz("America/New_York").format("h:mm"); - - return ( -
-
{currentTime}
- -
- ); -}; - -export default NoConnectionSingle; diff --git a/assets/src/components/eink/no_connection_top.tsx b/assets/src/components/eink/no_connection_top.tsx deleted file mode 100644 index 0c11e6d04..000000000 --- a/assets/src/components/eink/no_connection_top.tsx +++ /dev/null @@ -1,16 +0,0 @@ -import moment from "moment"; -import React from "react"; -import { imagePath } from "Util/util"; - -const NoConnectionTop = (): JSX.Element => { - const currentTime = moment().tz("America/New_York").format("h:mm"); - - return ( -
-
{currentTime}
- -
- ); -}; - -export default NoConnectionTop; diff --git a/assets/src/components/eink/no_service.tsx b/assets/src/components/eink/no_service.tsx deleted file mode 100644 index 09a9b6c42..000000000 --- a/assets/src/components/eink/no_service.tsx +++ /dev/null @@ -1,202 +0,0 @@ -import React from "react"; - -const NoServiceTop = ({ mode }): JSX.Element => { - const busIcon = ( - - ); - const subwayIcon = ( - - ); - - const start = 2736.524756; - const d = 56.605915; - const n = 38; - const rectWidth = 20; - - const stripePath = ( - - ); - - return ( -
- - - - - - All MBTA service - - - is suspended.{" "} - - - Please stay safe. - - - - - - - - - - - - - {[...Array(n)].map((_, i) => ( - - {stripePath} - - ))} - - - - - - {mode === "bus" ? busIcon : subwayIcon} - - - - - - - - No service - - - - - - - - -
- ); -}; - -const NoServiceBottom = (): JSX.Element => { - return ( -
- - - - - - - - - - Get the latest updates{" "} - - - on your phone. - - - - - mbta.com/coronavirus - - - or - - - {" "} - transitapp.com - - - - - - - - - - -
- ); -}; - -const NoService = ({ mode }): JSX.Element => { - return ( -
- - -
- ); -}; - -export { NoServiceTop, NoServiceBottom }; -export default NoService; diff --git a/assets/src/components/eink/overnight_departures.tsx b/assets/src/components/eink/overnight_departures.tsx deleted file mode 100644 index 93a893f62..000000000 --- a/assets/src/components/eink/overnight_departures.tsx +++ /dev/null @@ -1,18 +0,0 @@ -import "moment"; -import "moment-timezone"; -import React from "react"; - -import FullScreenTakeover from "Components/eink/full_screen_takeover"; -import { imagePath } from "Util/util"; - -const OvernightDepartures = ({ size, currentTimeString }): JSX.Element => { - const srcPath = imagePath(`overnight-static-${size}.png`); - return ( - - ); -}; - -export default OvernightDepartures; diff --git a/assets/src/components/eink/screen_page.tsx b/assets/src/components/eink/screen_page.tsx deleted file mode 100644 index 9895f4849..000000000 --- a/assets/src/components/eink/screen_page.tsx +++ /dev/null @@ -1,68 +0,0 @@ -import DebugErrorBoundary from "Components/helpers/debug_error_boundary"; -import React, { useState } from "react"; -import { useParams } from "react-router-dom"; -import { fetchDatasetValue } from "Util/dataset"; - -const MultiScreenPage = ({ screenContainer: ScreenContainer }): JSX.Element => { - const screenIds = JSON.parse(fetchDatasetValue("screenIds")); - - return ( -
- {screenIds.map((id) => ( - - ))} -
- ); -}; - -type QueryParams = { id?: string }; - -const ScreenPage = ({ screenContainer: ScreenContainer }): JSX.Element => { - const { id } = useParams(); - return ; -}; - -const AuditScreenPage = ({ screenLayout: ScreenLayout }): JSX.Element => { - const [data, setData] = useState(""); - - const handleChange = (event: React.ChangeEvent) => { - setData(event.target.value); - }; - - const isDataValidJson = () => { - let isValid = true; - try { - JSON.parse(data); - } catch { - isValid = false; - } - return isValid; - }; - - const parseData = () => { - try { - return JSON.parse(data); - } catch { - return ""; - } - }; - - const textareaProps = isDataValidJson() - ? {} - : { className: "audit-input-invalid" }; - - return ( -
- - - ; - -
- ); -}; - -export { ScreenPage, MultiScreenPage, AuditScreenPage }; diff --git a/assets/src/components/eink/takeover_alert.tsx b/assets/src/components/eink/takeover_alert.tsx deleted file mode 100644 index 98e13d83b..000000000 --- a/assets/src/components/eink/takeover_alert.tsx +++ /dev/null @@ -1,11 +0,0 @@ -import React from "react"; - -const TakeoverAlert = ({ psaUrl }): JSX.Element => { - return ( -
- -
- ); -}; - -export default TakeoverAlert; diff --git a/assets/src/components/eink/takeover_screen_layout.tsx b/assets/src/components/eink/takeover_screen_layout.tsx deleted file mode 100644 index cc7971a2f..000000000 --- a/assets/src/components/eink/takeover_screen_layout.tsx +++ /dev/null @@ -1,14 +0,0 @@ -import React from "react"; - -import FullScreenTakeover from "Components/eink/full_screen_takeover"; - -const TakeoverScreenLayout = ({ apiResponse }): JSX.Element => { - return ( - - ); -}; - -export default TakeoverScreenLayout; diff --git a/assets/src/components/solari/departure.tsx b/assets/src/components/solari/departure.tsx deleted file mode 100644 index be93260f6..000000000 --- a/assets/src/components/solari/departure.tsx +++ /dev/null @@ -1,188 +0,0 @@ -import React, { useState, useEffect, useRef } from "react"; - -import { standardTimeRepresentation } from "Util/time_representation"; - -import BaseDepartureTime from "Components/eink/base_departure_time"; -import BaseDepartureDestination from "Components/eink/base_departure_destination"; -import InlineAlertBadge from "Components/solari/inline_alert_badge"; -import { - DepartureRoutePill, - PlaceholderRoutePill, -} from "Components/solari/route_pill"; - -import { classWithModifier, classWithModifiers, imagePath } from "Util/util"; - -const NormalDepartureTimeAndCrowding = ({ - crowdingLevel, - timeRepresentation, - timeAnimationModifier, -}) => { - return ( - <> -
- {crowdingLevel && ( - - )} -
-
- -
- - ); -}; - -const OverheadDepartureTimeAndCrowding = ({ - crowdingLevel, - timeRepresentation, - timeAnimationModifier: arrivingModifier, - currentTimeString, -}) => { - const [showCrowding, setShowCrowding] = useState(false); - const ref = useRef(null); - - // When we load new data, show the time, which is styled to animate in from the right. - useEffect(() => { - setShowCrowding(false); - }, [currentTimeString]); - - // Timing is controlled by the timings on the animation. The animation on the time element - // ends after 10s, then the animationend event toggles crowding on. - useEffect(() => { - const onAnimationEnd = () => { - setShowCrowding(true); - }; - - if (ref.current) { - ref.current.addEventListener("animationend", onAnimationEnd); - return () => { - if (ref.current) { - ref.current.removeEventListener("animationend", onAnimationEnd); - } - }; - } else { - return () => {}; - } - }); - - const timeModifiers = crowdingLevel - ? [arrivingModifier, "overhead-with-crowding"] - : [arrivingModifier]; - - return ( - <> - {showCrowding ? ( -
- {crowdingLevel && ( - - )} -
- ) : ( -
- -
- )} - - ); -}; - -const Departure = ({ - route, - routeId, - destination, - time, - currentTimeString, - vehicleStatus, - stopType, - alerts, - crowdingLevel, - overhead, - groupStart, - groupEnd, - trackNumber, -}): JSX.Element => { - const viaPattern = /(.+) (via .+)/; - const parenPattern = /(.+) (\(.+)/; - - const viaModifier = - destination && - (viaPattern.test(destination) || parenPattern.test(destination)) - ? "with-via" - : "no-via"; - - const timeRepresentation = standardTimeRepresentation( - time, - currentTimeString, - vehicleStatus, - stopType, - ); - - const timeAnimationModifier = - timeRepresentation.type === "TEXT" ? "animated" : "static"; - - const containerModifiers: string[] = []; - if (groupStart) { - containerModifiers.push("group-start"); - } - if (groupEnd) { - containerModifiers.push("group-end"); - } - - return ( -
-
- {groupStart ? ( - - ) : ( - - )} -
- {destination && groupStart && ( - - )} -
- - {overhead ? ( - - ) : ( - - )} - - {groupStart && alerts.length > 0 && ( -
- {alerts.map((alert) => ( - - ))} -
- )} -
-
- ); -}; - -export default Departure; diff --git a/assets/src/components/solari/header.tsx b/assets/src/components/solari/header.tsx deleted file mode 100644 index b6944115d..000000000 --- a/assets/src/components/solari/header.tsx +++ /dev/null @@ -1,33 +0,0 @@ -import React from "react"; -import { getDatasetValue } from "Util/dataset"; - -import { formatTimeString } from "Util/util"; - -const Header = ({ - stationName, - currentTimeString, - sections, - overhead, -}): JSX.Element => { - const environmentName = getDatasetValue("environmentName"); - - const currentTime = formatTimeString(currentTimeString); - const subtitle = overhead ? `${sections[0].name} Trips` : "Upcoming Trips"; - - return ( -
-
- {["screens-dev", "screens-dev-green"].includes(environmentName!) - ? environmentName - : ""} -
-
{currentTime}
-
-
{stationName}
-
{subtitle}
-
-
- ); -}; - -export default Header; diff --git a/assets/src/components/solari/headway_departure.tsx b/assets/src/components/solari/headway_departure.tsx deleted file mode 100644 index fa02dbea2..000000000 --- a/assets/src/components/solari/headway_departure.tsx +++ /dev/null @@ -1,43 +0,0 @@ -import React from "react"; - -import BaseDepartureDestination from "Components/eink/base_departure_destination"; -import { SectionRoutePill } from "Components/solari/route_pill"; - -import { classWithModifier, classWithModifiers } from "Util/util"; - -const HeadwayDeparture = ({ - pill, - headsign, - rangeLow, - rangeHigh, -}): JSX.Element => { - return ( -
-
- -
- -
-
-
-
- Every - - {" "} - {rangeLow} - {rangeHigh} - - m -
-
-
-
-
- ); -}; - -export default HeadwayDeparture; diff --git a/assets/src/components/solari/inline_alert_badge.tsx b/assets/src/components/solari/inline_alert_badge.tsx deleted file mode 100644 index 54e3dd806..000000000 --- a/assets/src/components/solari/inline_alert_badge.tsx +++ /dev/null @@ -1,38 +0,0 @@ -import React from "react"; -import _ from "lodash"; - -import { classWithModifier, imagePath } from "Util/util"; - -const alertValues = _.mapValues( - { - delay: { svgPath: "solari-delay-white.svg", text: "delays" }, - snow_route: { svgPath: "solari-snowflake.svg", text: "snow route" }, - last_trip: { svgPath: "solari-moon.svg", text: "last trip" }, - }, - ({ svgPath, ...rest }) => ({ ...rest, svgPath: imagePath(svgPath) }), -); - -interface InlineAlertBadgeProps { - alert: "delay" | "snow_route" | "last_trip"; -} - -const InlineAlertBadge = ({ alert }: InlineAlertBadgeProps): JSX.Element => { - const { svgPath, text } = alertValues[alert]; - - const withModifier = (className: string) => - classWithModifier(className, alert); - - return ( -
-
- -
-
{text}
-
- ); -}; - -export default InlineAlertBadge; diff --git a/assets/src/components/solari/loading_layout.tsx b/assets/src/components/solari/loading_layout.tsx deleted file mode 100644 index c8d522d59..000000000 --- a/assets/src/components/solari/loading_layout.tsx +++ /dev/null @@ -1,27 +0,0 @@ -import React, { ComponentType } from "react"; -import Loading from "Components/v2/bundled_svg/loading"; - -const coolBlack = "#171F26"; - -const LoadingLayout: ComponentType = () => { - return ( -
-
-
- -
-
- Loading... -
-
- This should only take a moment. -
-
-
- ); -}; - -export default LoadingLayout; diff --git a/assets/src/components/solari/psa.tsx b/assets/src/components/solari/psa.tsx deleted file mode 100644 index 8b45f5631..000000000 --- a/assets/src/components/solari/psa.tsx +++ /dev/null @@ -1,12 +0,0 @@ -import React from "react"; - -const Psa = ({ psaUrl, currentTimeString }): JSX.Element => { - return ( -
- -
-
- ); -}; - -export default Psa; diff --git a/assets/src/components/solari/route_pill.tsx b/assets/src/components/solari/route_pill.tsx deleted file mode 100644 index 633f2530b..000000000 --- a/assets/src/components/solari/route_pill.tsx +++ /dev/null @@ -1,191 +0,0 @@ -import React from "react"; - -import { classWithModifier, classWithModifiers, imagePath } from "Util/util"; -import BaseRoutePill from "Components/eink/base_route_pill"; -import { WIDE_MINI_PILL_ROUTES } from "Components/solari/section"; - -interface PillType { - routeName: string | null; - routePillColor: string | null; -} - -const routeToPill = ( - route: string, - routeId: string, - trackNumber: string | null, -): PillType => { - if (route === null) { - return { routeName: null, routePillColor: null }; - } - - if (routeId === "Blue") { - return { routeName: "BL", routePillColor: "blue" }; - } - - if (routeId === "Red") { - return { routeName: "RL", routePillColor: "red" }; - } - - if (routeId === "Mattapan") { - return { routeName: "M", routePillColor: "red" }; - } - - if (routeId === "Orange") { - return { routeName: "OL", routePillColor: "orange" }; - } - - if (routeId === "Green-B") { - return { routeName: "GLยทB", routePillColor: "green" }; - } - - if (routeId === "Green-C") { - return { routeName: "GLยทC", routePillColor: "green" }; - } - - if (routeId === "Green-D") { - return { routeName: "GLยทD", routePillColor: "green" }; - } - - if (routeId === "Green-E") { - return { routeName: "GLยทE", routePillColor: "green" }; - } - - if (routeId && routeId.startsWith("CR")) { - return { - routeName: trackNumber == null ? "CR" : `TR${trackNumber}`, - routePillColor: "purple", - }; - } - - if (routeId && routeId.startsWith("Boat")) { - return { routeName: "Boat", routePillColor: "teal" }; - } - - if (route && route.startsWith("SL")) { - return { routeName: route, routePillColor: "silver" }; - } - - return { routeName: route, routePillColor: "yellow" }; -}; - -const Pill = ({ routeName, routePillColor }: PillType): JSX.Element => { - let route: JSX.Element | string | null; - - if (routeName === "CR") { - route = ( - - ); - } else if (routeName === "Boat") { - route = ( - - ); - } else if (routeName === "BUS") { - route = ( - - ); - } else { - route = routeName; - } - - return ( -
- {route && } -
- ); -}; - -const PlaceholderRoutePill = (): JSX.Element => { - return
; -}; - -const DepartureRoutePill = ({ - route, - routeId, - trackNumber, -}: { - route: string; - routeId: string; - trackNumber: string | null; -}): JSX.Element => ; - -const sectionPillMapping: Record = { - blue: { routeName: "BL", routePillColor: "blue" }, - red: { routeName: "RL", routePillColor: "red" }, - green: { routeName: "GL", routePillColor: "green" }, - mattapan: { routeName: "M", routePillColor: "red" }, - orange: { routeName: "OL", routePillColor: "orange" }, - cr: { routeName: "CR", routePillColor: "purple" }, - silver: { routeName: "SL", routePillColor: "silver" }, - bus: { routeName: "BUS", routePillColor: "yellow" }, -}; - -const sectionPillToPill = (pill: string): PillType => { - return sectionPillMapping[pill] ?? { routeName: null, routePillColor: null }; -}; - -const SectionRoutePill = ({ pill }: { pill: string }): JSX.Element => ( - -); - -// Three-letter abbreviations for commuter rail routes -const routeIdMapping: Record = { - "CR-Haverhill": "HVL", - "CR-Newburyport": "NBP", - "CR-Lowell": "LWL", - "CR-Fitchburg": "FBG", - "CR-Worcester": "WOR", - "CR-Needham": "NDM", - "CR-Franklin": "FRK", - "CR-Providence": "PVD", - "CR-Fairmount": "FMT", - "CR-Middleborough": "MID", - "CR-Kingston": "KNG", - "CR-Greenbush": "GRB", -}; - -const PagedDepartureRoutePill = ({ route, routeId, selected }): JSX.Element => { - const isCommuterRail = routeId.startsWith("CR-"); - const isSlashRoute = route.includes("/"); - const isWideRoute = WIDE_MINI_PILL_ROUTES.includes(route); - - const selectedModifier = selected ? "selected" : "unselected"; - const sizeModifier = - isCommuterRail || isSlashRoute ? "size-small" : "size-normal"; - const modeModifier = isCommuterRail ? "commuter-rail" : "bus"; - const widthModifier = isWideRoute ? "width-wide" : "width-normal"; - const modifiers = [ - selectedModifier, - sizeModifier, - modeModifier, - widthModifier, - ]; - const pillClass = classWithModifiers( - "later-departure__route-pill", - modifiers, - ); - const textClass = classWithModifiers( - "later-departure__route-text", - modifiers, - ); - - const routeText = routeId.startsWith("CR-") ? routeIdMapping[routeId] : route; - - return ( -
-
{routeText}
-
- ); -}; - -export { - DepartureRoutePill, - PlaceholderRoutePill, - SectionRoutePill, - PagedDepartureRoutePill, -}; diff --git a/assets/src/components/solari/screen_container.tsx b/assets/src/components/solari/screen_container.tsx deleted file mode 100644 index cc2a3c5f8..000000000 --- a/assets/src/components/solari/screen_container.tsx +++ /dev/null @@ -1,94 +0,0 @@ -import React from "react"; - -import Header from "Components/solari/header"; -import SectionListContainer from "Components/solari/section_list_container"; -import Psa from "Components/solari/psa"; - -import { classWithModifier, imagePath } from "Util/util"; - -import useApiResponse from "Hooks/use_api_response"; - -import { SOLARI_REFRESH_MS } from "Constants"; -import { useLocation } from "react-router-dom"; -import LoadingLayout from "Components/solari/loading_layout"; - -const DefaultScreenLayout = ({ apiResponse }): JSX.Element => { - const sizeModifier = apiResponse.overhead ? "size-large" : "size-normal"; - - return ( -
-
- - {apiResponse.psa_url && ( - - )} -
- ); -}; - -const FullScreenImageLayout = ({ srcPath }): JSX.Element => { - return ( -
- -
- ); -}; - -const NoConnectionScreenLayout = (): JSX.Element => { - const srcPath = imagePath("solari-no-data.png"); - return ; -}; - -const TakeoverScreenLayout = ({ apiResponse }): JSX.Element => { - return ; -}; - -const LoadingScreenLayout = (): JSX.Element => { - return ; -}; - -const ScreenLayout = ({ apiResponse }): JSX.Element => { - if (!apiResponse || apiResponse.success === false) { - return ; - } - - if (apiResponse.type === "loading") { - return ; - } - - if (apiResponse.psa_type === "takeover") { - return ; - } - - return ; -}; - -const ScreenContainer = ({ id }): JSX.Element => { - const query = new URLSearchParams(useLocation().search); - const datetime = query.get("datetime") || undefined; - - const apiResponse = useApiResponse({ - id, - datetime, - refreshMs: SOLARI_REFRESH_MS, - withWatchdog: true, - }); - return ; -}; - -export default ScreenContainer; -export { ScreenLayout }; diff --git a/assets/src/components/solari/section.tsx b/assets/src/components/solari/section.tsx deleted file mode 100644 index efd82047e..000000000 --- a/assets/src/components/solari/section.tsx +++ /dev/null @@ -1,630 +0,0 @@ -import React from "react"; -import { CSSTransition, TransitionGroup } from "react-transition-group"; - -import Departure from "Components/solari/departure"; -import HeadwayDeparture from "Components/solari/headway_departure"; -import Arrow, { Direction } from "Components/solari/arrow"; -import { - SectionRoutePill, - PagedDepartureRoutePill, -} from "Components/solari/route_pill"; -import BaseDepartureDestination from "Components/eink/base_departure_destination"; -import { classWithModifier, classWithModifiers, imagePath } from "Util/util"; -import { standardTimeRepresentation } from "Util/time_representation"; -import moment from "moment"; - -const WIDE_MINI_PILL_ROUTES = ["441/442"]; - -const camelizeDepartureObject = ({ - id, - route, - destination, - time, - route_id: routeId, - vehicle_status: vehicleStatus, - alerts, - stop_type: stopType, - crowding_level: crowdingLevel, - track_number: trackNumber, -}) => ({ - id, - route, - destination, - time, - routeId, - vehicleStatus, - alerts, - stopType, - crowdingLevel, - trackNumber, -}); - -const isArrivingOrBoarding = ( - { time, vehicle_status, stop_type }, - currentTimeString, -) => { - const timeRepresentation = standardTimeRepresentation( - time, - currentTimeString, - vehicle_status, - stop_type, - ); - return ( - timeRepresentation.type === "TEXT" && - ["ARR", "BRD"].includes(timeRepresentation.text) - ); -}; - -const verticalHeaderIconSrc = (name, departuresLength) => { - let iconFileName = ""; - switch (true) { - case departuresLength <= 1 && name === "Upper Busway": - iconFileName = "icon-upper-busway-arrow-only.svg"; - break; - case departuresLength <= 1 && name === "Lower Busway": - iconFileName = "icon-lower-busway-arrow-only.svg"; - break; - case name === "Upper Busway": - iconFileName = "icon-upper-busway.svg"; - break; - case name === "Lower Busway": - iconFileName = "icon-lower-busway.svg"; - break; - case name === "Commuter Rail": - iconFileName = "icon-commuter-rail.svg"; - break; - } - return imagePath(iconFileName); -}; - -const SectionHeader = ({ name, arrow }): JSX.Element => { - return ( -
- {name} - {arrow !== null && ( - - - - )} -
- ); -}; - -const SectionFrame = ({ - sectionHeaders, - name, - arrow, - overhead, - departuresLength, - children, -}): JSX.Element => { - const sectionModifier = sectionHeaders === "vertical" ? "vertical" : "normal"; - const sectionClass = classWithModifier("section", sectionModifier); - const shouldShowHeader = - sectionHeaders !== "none" && name !== null && !overhead; - - if (sectionHeaders === "vertical") { - const iconSrc = verticalHeaderIconSrc(name, departuresLength); - - name = ; - } - - return ( -
- {shouldShowHeader && } -
{children}
-
- ); -}; - -const PlaceholderMessage = ({ pill, text }): JSX.Element => ( -
-
- -
- -
-
-
-); - -const isDuringSurge = () => { - const now = moment(); - const isDuringFirstRange = now.isBetween( - moment("2024-01-03T09:30:00Z"), - moment("2024-01-13T07:30:00Z"), - ); - - const isDuringSecondRange = now.isBetween( - moment("2024-01-16T09:30:00Z"), - moment("2024-01-29T07:30:00Z"), - ); - - return isDuringFirstRange || isDuringSecondRange; -}; - -const NoDeparturesMessage = ({ pill, stationName }): JSX.Element => { - let placeholderText = "No departures currently available"; - - if (stationName === "Haymarket" && isDuringSurge()) { - placeholderText = "Service Suspended"; - } - - return ; -}; - -const NoDataMessage = ({ pill }): JSX.Element => { - return ( - - ); -}; - -interface PagedDepartureProps { - pageCount: number; - departures: object[]; - overhead: boolean; -} - -interface PagedDepartureState { - currentPageNumber: number; -} - -class PagedDeparture extends React.Component< - PagedDepartureProps, - PagedDepartureState -> { - interval: number | null; - - constructor(props: PagedDepartureProps) { - super(props); - this.state = { currentPageNumber: 0 }; - this.interval = null; - } - - componentDidMount() { - this.startPaging(); - } - - componentWillUnmount() { - this.stopPaging(); - } - - componentDidUpdate(prevProps) { - if (!this.propsEqual(prevProps)) { - this.stopPaging(); - this.setState({ currentPageNumber: 0 }); - this.startPaging(); - } - } - - propsEqual(otherProps) { - const { pageCount, departures } = this.props; - return ( - pageCount === otherProps.pageCount && - departures.length === otherProps.departures.length && - // @ts-expect-error - departures.every((d, i) => d.id === otherProps.departures[i].id) - ); - } - - startPaging() { - const refreshMs = this.pageDuration(); - if (refreshMs !== null) { - this.interval = window.setInterval( - this.updatePaging.bind(this), - refreshMs, - ); - } - } - - stopPaging() { - if (this.interval) { - clearInterval(this.interval); - this.interval = null; - } - } - - updatePaging() { - this.setState((state: PagedDepartureState, props: PagedDepartureProps) => { - if (props.pageCount === 0) { - return { currentPageNumber: 0 }; - } else { - return { - currentPageNumber: (state.currentPageNumber + 1) % props.pageCount, - }; - } - }); - } - - pageDuration() { - if (this.props.pageCount <= 1) { - // Don't set an interval if there are 0 or 1 pages - return null; - } else if (this.props.pageCount === 2) { - return 3750; - } else { - return 15000 / this.props.pageCount; - } - } - - render() { - // Don't show alert badges in the paging row - const currentPagedDeparture = { - ...this.props.departures[this.state.currentPageNumber], - alerts: [], - }; - - // Determine whether all route pills are small. - // If route pills differ in size, we need to adjust the position of the small ones. - // If all route pills are the same size, we don't want to make any adjustment. - const isSmall = (departure) => - departure.route_id.startsWith("CR-") || departure.route.includes("/"); - const sizeModifier = this.props.departures.every(isSmall) - ? "size-small" - : "size-normal"; - - const normalPillWidth = 89; // px - const widePillWidth = 158; // px - const pillSpace = 25; // px - - const selectedRightOffset = - this.props.pageCount - (this.state.currentPageNumber + 1); - const numWidePillsToTheRight = this.props.departures - .slice(this.state.currentPageNumber + 1) - // @ts-expect-error - .filter(({ route }) => WIDE_MINI_PILL_ROUTES.includes(route)).length; - const numNormalPillsToTheRight = - selectedRightOffset - numWidePillsToTheRight; - - const currentPillIsWide = WIDE_MINI_PILL_ROUTES.includes( - // @ts-expect-error - currentPagedDeparture.route, - ); - const pillCenterOffset = currentPillIsWide ? 64.5 : 30; // px - const totalPillSpaceWidth = selectedRightOffset * pillSpace; - const totalPillWidth = - numWidePillsToTheRight * widePillWidth + - numNormalPillsToTheRight * normalPillWidth; - const translateWidth = - totalPillSpaceWidth + totalPillWidth + pillCenterOffset; - - const caretBaseClass = "later-departure__route-pill-caret"; - const beforeCaretClass = classWithModifier(caretBaseClass, "before"); - const afterCaretClass = classWithModifier(caretBaseClass, "after"); - - return ( -
-
-
Later Departures
-
- {this.props.departures.map((departure, i) => { - const rightOffset = this.props.pageCount - (i + 1); - return ( - // @ts-expect-error - - {rightOffset === 0 && ( -
- )} - - {rightOffset === 0 && ( -
- )} -
- ); - })} -
-
- -
- ); - } -} - -interface DepartureListProps { - departures: any[]; - currentTimeString: string; - isAnimated: boolean; - overhead: boolean; -} - -const isGroupStart = (departures, i) => { - if (i === 0) { - return true; - } - - const departure = departures[i]; - const prev = departures[i - 1]; - return ( - departure.destination !== prev.destination || departure.route !== prev.route - ); -}; - -const isGroupEnd = (departures, i) => { - if (i === departures.length - 1) { - return true; - } - - const departure = departures[i]; - const next = departures[i + 1]; - return ( - departure.destination !== next.destination || departure.route !== next.route - ); -}; - -const DepartureList = ({ - departures, - currentTimeString, - isAnimated, - overhead, -}: DepartureListProps): JSX.Element => { - if (isAnimated) { - return ( - - {departures.map((departure, i) => { - const isImminent = isArrivingOrBoarding(departure, currentTimeString); - - const transitionProps = isImminent - ? { - timeout: { exit: 400 }, - classNames: classWithModifier("departure-animated", "arr-brd"), - enter: false, - exit: true, - } - : { - timeout: { enter: 400 }, - classNames: classWithModifier("departure-animated", "normal"), - enter: true, - exit: false, - }; - - return ( - - - - ); - })} - - ); - } else { - return ( - <> - {departures.map((departure, i) => ( - - ))} - - ); - } -}; - -const HeadwayDepartureList = ({ - pill, - headsigns, - rangeLow, - rangeHigh, -}): JSX.Element => { - return ( - <> - {headsigns.map((headsign) => ( - - ))} - - ); -}; - -const MAX_PAGE_COUNT = 5; -const MIN_PAGE_COUNT = 3; - -const getPageCount = (departures, numRows) => { - const excessDepartures = departures.length - numRows + 1; - const unadjustedPageCount = Math.min(excessDepartures, MAX_PAGE_COUNT); - - // Reduce the number of pages if needed to make room for slashed routes - // which require wider mini-pills. - const startIndex = numRows - 1; - const widePillCount = departures - .slice(startIndex, startIndex + unadjustedPageCount) - .filter(({ route }) => WIDE_MINI_PILL_ROUTES.includes(route)).length; - return unadjustedPageCount - widePillCount; -}; - -interface PagedSectionProps { - departures: object[]; - numRows: number; - arrow: Direction | null; - sectionHeaders: "normal" | "vertical" | "none"; - name: string | null; - pill: string; - overhead: boolean; - isAnimated: boolean; - currentTimeString: string; -} - -const PagedSection = ({ - departures, - numRows, - arrow, - sectionHeaders, - name, - pill, - overhead, - isAnimated, - currentTimeString, - // @ts-expect-error - disabled, -}: PagedSectionProps): JSX.Element => { - const pageCount = getPageCount(departures, numRows); - const showPagedDeparture = pageCount >= MIN_PAGE_COUNT; - const staticDepartures = showPagedDeparture - ? departures.slice(0, numRows - 1) - : departures; - - const frameProps = { - sectionHeaders, - name, - arrow: sectionHeaders === "normal" ? arrow : null, - overhead, - departuresLength: staticDepartures.length, - }; - - if (staticDepartures.length === 0) { - return ( - - {disabled ? ( - - ) : ( - // @ts-expect-error - - )} - - ); - } - - let pagedDepartures; - if (showPagedDeparture) { - const startIndex = numRows - 1; - pagedDepartures = departures.slice(startIndex, startIndex + pageCount); - } - - return ( - - - {showPagedDeparture && ( - - )} - - ); -}; - -const Section = ({ - name, - arrow, - departures, - sectionHeaders, - currentTimeString, - numRows, - overhead, - isAnimated, - pill, - headway: { active, headsigns, range_low: rangeLow, range_high: rangeHigh }, - disabled, - stationName, -}): JSX.Element => { - departures = departures.slice(0, numRows); - - if (sectionHeaders !== "normal") { - arrow = null; - } - - const frameProps = { - sectionHeaders, - name, - arrow, - overhead, - departuresLength: departures.length, - }; - - if (departures.length === 0) { - return ( - - {disabled ? ( - - ) : ( - - )} - - ); - } - - if (active) { - return ( - - - - ); - } - - return ( - - - - ); -}; - -export { PagedSection, Section, WIDE_MINI_PILL_ROUTES }; diff --git a/assets/src/components/solari/section_list.tsx b/assets/src/components/solari/section_list.tsx deleted file mode 100644 index a2d6185da..000000000 --- a/assets/src/components/solari/section_list.tsx +++ /dev/null @@ -1,58 +0,0 @@ -import React from "react"; - -import { PagedSection, Section } from "Components/solari/section"; -import { classWithModifier } from "Util/util"; - -interface Props { - sections: any[]; - sectionSizes: number[]; - sectionHeaders: string; - currentTimeString: string; - overhead: boolean; - stationName: string; - isDummy?: boolean; -} - -const SectionList = React.forwardRef( - ( - { - sections, - sectionSizes, - sectionHeaders, - currentTimeString, - overhead, - stationName, - isDummy = false, - }, - ref, - ): JSX.Element => { - const className = isDummy - ? classWithModifier("section-list", "dummy") - : "section-list"; - - return ( -
- {sections.map((section, i) => { - const SectionComponent = section?.paging?.is_enabled - ? PagedSection - : Section; - - return ( - - ); - })} -
- ); - }, -); - -export default SectionList; diff --git a/assets/src/components/solari/section_list_container.tsx b/assets/src/components/solari/section_list_container.tsx deleted file mode 100644 index 1c9df9a23..000000000 --- a/assets/src/components/solari/section_list_container.tsx +++ /dev/null @@ -1,46 +0,0 @@ -import React from "react"; - -import SectionListSizer from "Components/solari/section_list_sizer"; -import SectionList from "Components/solari/section_list"; - -interface Props { - sections: object[]; - sectionHeaders: string; - currentTimeString: string; - overhead: boolean; - stationName: string; -} - -const SectionListContainer = ({ - sections, - sectionHeaders, - currentTimeString, - overhead, - stationName, -}: Props): JSX.Element => { - const [sectionSizes, setSectionSizes] = React.useState([] as number[]); - - return ( -
- {sectionSizes.length > 0 && ( - - )} - -
- ); -}; - -export default SectionListContainer; diff --git a/assets/src/components/solari/section_list_sizer.tsx b/assets/src/components/solari/section_list_sizer.tsx deleted file mode 100644 index 9c936f722..000000000 --- a/assets/src/components/solari/section_list_sizer.tsx +++ /dev/null @@ -1,206 +0,0 @@ -/** - * SectionListSizer renders an invisible copy of the SectionList component in - * order to determine the max number of departures that can be displayed without overflow. - * - * It then communicates the optimal row counts to its parent via the `onDoneSizing` - * callback prop, to be used by the real SectionList. - */ - -import React from "react"; -import _ from "lodash"; - -import SectionList from "./section_list"; - -const totalRows = (sections) => { - return sections.reduce((acc, section) => { - if (section.paging && section.paging.is_enabled === true) { - return acc + section.paging.visible_rows; - } else { - return acc + section.departures.length; - } - }, 0); -}; - -const allRoundings = ( - obj: Record, -): Record[] => { - return _.reduce( - obj, - (list, n, key) => { - const floors = list.map((o) => ({ ...o, [key]: Math.floor(n) })); - const ceils = list.map((o) => ({ ...o, [key]: Math.ceil(n) })); - return [...floors, ...ceils]; - }, - [{}], - ); -}; - -const assignSectionSizes = (sections: any[], numRows: number): number[] => { - // set the sizes for all empty sections to 1, to accomodate the "no departures" placeholder message - const indexedAssignedEmpties = _.mapValues( - _.pickBy({ ...sections }, (section) => section.departures.length === 0), - () => 1, - ); - - const indexedNonEmpties = _.pickBy( - { ...sections }, - (section) => section.departures.length > 0, - ); - - const indexedAssignedNonEmpties = assignSectionSizesHelper( - indexedNonEmpties, - numRows - _.size(indexedAssignedEmpties), - ); - - // merge the objects and convert back to an array - return Array.from({ - ...indexedAssignedEmpties, - ...indexedAssignedNonEmpties, - length: _.size(indexedAssignedEmpties) + _.size(indexedAssignedNonEmpties), - }); -}; - -const assignSectionSizesHelper = ( - sections: Record, - numRows: number, -): Record => { - const initialSizes = _.mapValues(sections, (section) => { - if (section?.paging?.is_enabled) { - return section.paging.visible_rows; - } else { - return section.departures.length; - } - }); - - const initialRows = _.sum(Object.values(initialSizes)); - const scaledSizes = _.mapValues( - initialSizes, - (n) => (n * numRows) / initialRows, - ); - - // Choose "best" rounding - const allSizeCombinations = allRoundings(scaledSizes); - const validSizeCombinations = allSizeCombinations.filter( - (comb) => _.sum(Object.values(comb)) === numRows, - ); - const roundedSizes = _.minBy(validSizeCombinations, (comb) => { - return _.sum( - _.map(comb, (rounded, i) => Math.abs(rounded - scaledSizes[i])), - ); - }); - - return roundedSizes!; -}; - -interface Props { - sections: object[]; - sectionHeaders: string; - currentTimeString: string; - overhead: boolean; - onDoneSizing: (sectionSizes: number[]) => void; -} - -interface State { - numRows: number; - sectionSizes: number[]; -} - -const NORMAL_DEPARTURES_HEIGHT = 1565; -const OVERHEAD_DEPARTURES_HEIGHT = 1464; -const MIN_NUM_ROWS = 3; - -class SectionListSizer extends React.Component { - ref: React.RefObject; - maxDeparturesHeight: number; - - constructor(props: Props) { - super(props); - this.ref = React.createRef(); - this.maxDeparturesHeight = this.props.overhead - ? OVERHEAD_DEPARTURES_HEIGHT - : NORMAL_DEPARTURES_HEIGHT; - - this.state = SectionListSizer.getInitialStateFromProps(props); - } - - static getInitialStateFromProps(props: Props) { - const initialRows = totalRows(props.sections); - const initialSizes = assignSectionSizes(props.sections, initialRows); - return { numRows: initialRows, sectionSizes: initialSizes }; - } - - componentDidMount() { - if (this.shouldAdjustSectionSizes()) { - this.adjustSectionSizes(); - } else { - this.props.onDoneSizing(this.state.sectionSizes); - } - } - - // Prevent the component from entering a render loop when SectionListContainer updates its state - shouldComponentUpdate(nextProps: Props, nextState: State) { - return ( - this.props.currentTimeString !== nextProps.currentTimeString || - !this.stateEquals(nextState) - ); - } - - componentDidUpdate(_props: Props, prevState: State) { - const newStateFromProps = SectionListSizer.getInitialStateFromProps( - this.props, - ); - - if (this.stateEquals(prevState) && !this.stateEquals(newStateFromProps)) { - this.setState(newStateFromProps); - } else if (this.shouldAdjustSectionSizes()) { - this.adjustSectionSizes(); - } else { - this.props.onDoneSizing(this.state.sectionSizes); - } - } - - stateEquals(otherState: State) { - const { numRows, sectionSizes } = this.state; - return ( - numRows === otherState.numRows && - sectionSizes.length === otherState.sectionSizes.length && - sectionSizes.every((n, i) => n === otherState.sectionSizes[i]) - ); - } - - shouldAdjustSectionSizes() { - const departuresHeight = this.ref?.current?.clientHeight ?? 0; - return ( - departuresHeight > this.maxDeparturesHeight && - this.state.numRows > MIN_NUM_ROWS - ); - } - - adjustSectionSizes() { - this.setState((prevState, prevProps) => { - const newRows = prevState.numRows - 1; - const newSizes = assignSectionSizes(prevProps.sections, newRows); - return { numRows: newRows, sectionSizes: newSizes }; - }); - } - - render() { - const { sections, sectionHeaders, currentTimeString, overhead } = - this.props; - - return ( - // @ts-expect-error - - ); - } -} - -export default SectionListSizer; diff --git a/assets/src/components/solari/arrow.tsx b/assets/src/components/v2/arrow.tsx similarity index 100% rename from assets/src/components/solari/arrow.tsx rename to assets/src/components/v2/arrow.tsx diff --git a/assets/src/components/v2/blue_bikes.tsx b/assets/src/components/v2/blue_bikes.tsx index e3ee4941a..fb7258a99 100644 --- a/assets/src/components/v2/blue_bikes.tsx +++ b/assets/src/components/v2/blue_bikes.tsx @@ -1,5 +1,5 @@ import React, { ComponentType } from "react"; -import Arrow, { Direction } from "Components/solari/arrow"; +import Arrow, { Direction } from "Components/v2/arrow"; import ClockIcon from "Components/v2/clock_icon"; import { imagePath } from "Util/util"; diff --git a/assets/src/components/v2/cr_departures/cr_departure_time.tsx b/assets/src/components/v2/cr_departures/cr_departure_time.tsx index c5877d43f..ce63426ba 100644 --- a/assets/src/components/v2/cr_departures/cr_departure_time.tsx +++ b/assets/src/components/v2/cr_departures/cr_departure_time.tsx @@ -1,9 +1,33 @@ -import BaseDepartureTime from "Components/eink/base_departure_time"; import moment from "moment"; import React from "react"; import { TimeRepresentation } from "Util/time_representation"; import LiveDataSvg from "Images/svgr_bundled/live-data-small.svg"; +const baseDepartureTime = (time: TimeRepresentation): JSX.Element | null => { + if (time.type === "TEXT") { + return ( +
+ {time.text} +
+ ); + } else if (time.type === "MINUTES") { + return ( +
+ {time.minutes} + m +
+ ); + } else if (time.type === "TIMESTAMP") { + return ( +
+ {time.timestamp} +
+ ); + } else { + return null; + } +}; + interface CRDepartureTimeProps { departureType: "schedule" | "prediction"; time: string | TimeRepresentation; @@ -38,9 +62,7 @@ const CRDepartureTime = ({ {formattedTime} ) : ( - - - + {baseDepartureTime(time)} ); return ( diff --git a/assets/src/components/v2/cr_departures/cr_departures_table.tsx b/assets/src/components/v2/cr_departures/cr_departures_table.tsx index 7fc37497f..d7730ef24 100644 --- a/assets/src/components/v2/cr_departures/cr_departures_table.tsx +++ b/assets/src/components/v2/cr_departures/cr_departures_table.tsx @@ -1,4 +1,3 @@ -import Arrow, { Direction as ArrowDirection } from "Components/solari/arrow"; import React from "react"; import { imagePath, classWithModifier } from "Util/util"; import { @@ -7,6 +6,7 @@ import { StationService, } from "Components/v2/cr_departures/cr_departures"; import CRDepartureTime from "Components/v2/cr_departures/cr_departure_time"; +import Arrow, { Direction as ArrowDirection } from "Components/v2/arrow"; interface Props { departures: Departure[]; diff --git a/assets/src/components/v2/shuttle_bus_info.tsx b/assets/src/components/v2/shuttle_bus_info.tsx index 0b03f4a2a..29ce4a3d5 100644 --- a/assets/src/components/v2/shuttle_bus_info.tsx +++ b/assets/src/components/v2/shuttle_bus_info.tsx @@ -1,8 +1,8 @@ -import Arrow, { Direction } from "Components/solari/arrow"; import React, { ComponentType } from "react"; import { imagePath } from "Util/util"; import IsaSvg from "Images/svgr_bundled/isa-negative.svg"; import Free from "Images/svgr_bundled/free.svg"; +import Arrow, { Direction } from "Components/v2/arrow"; import ClockIcon from "Components/v2/clock_icon"; interface Props { diff --git a/assets/src/components/v2/solari_large/normal_screen.tsx b/assets/src/components/v2/solari_large/normal_screen.tsx deleted file mode 100644 index d656a68bb..000000000 --- a/assets/src/components/v2/solari_large/normal_screen.tsx +++ /dev/null @@ -1,26 +0,0 @@ -import React from "react"; - -import Widget, { WidgetData } from "Components/v2/widget"; - -interface Props { - header: WidgetData; - main_content: WidgetData; -} - -const NormalScreen: React.ComponentType = ({ - header: header, - main_content: mainContent, -}) => { - return ( -
-
- -
-
- -
-
- ); -}; - -export default NormalScreen; diff --git a/assets/src/constants.tsx b/assets/src/constants.tsx deleted file mode 100644 index 9526324bd..000000000 --- a/assets/src/constants.tsx +++ /dev/null @@ -1,2 +0,0 @@ -export const EINK_REFRESH_MS = 30000; -export const SOLARI_REFRESH_MS = 15000; diff --git a/assets/webpack.config.js b/assets/webpack.config.js index a378170a2..f2c7f6da2 100644 --- a/assets/webpack.config.js +++ b/assets/webpack.config.js @@ -116,12 +116,10 @@ module.exports = (env, argv) => { ...common_export_body, entry: { polyfills: "./src/polyfills.js", - solari: "./src/apps/solari.tsx", admin: "./src/apps/admin.tsx", bus_eink_v2: "./src/apps/v2/bus_eink.tsx", gl_eink_v2: "./src/apps/v2/gl_eink.tsx", busway_v2: "./src/apps/v2/busway.tsx", - solari_large_v2: "./src/apps/v2/solari_large.tsx", dup_v2: "./src/apps/v2/dup.tsx", bus_shelter_v2: "./src/apps/v2/bus_shelter.tsx", pre_fare_v2: "./src/apps/v2/pre_fare.tsx", diff --git a/docs/version_upgrade.md b/docs/version_upgrade.md index f62c92e14..7f3a56738 100644 --- a/docs/version_upgrade.md +++ b/docs/version_upgrade.md @@ -24,10 +24,8 @@ Check for any new credo complaints and address them as needed: `mix credo --stri Once everything looks good, start the server with `iex -S mix phx.server`. Thoroughly check that everything is looking as expected. Test loading the following: - - [ ] v1 screen (bus e-ink, GL e-ink single, GL e-ink double, solari, DUP) - - [ ] v1 audio (solari) - - [ ] v2 screen (bus e-ink, GL e-ink, bus shelter, pre-fare, others...?) - - [ ] v2 audio (bus shelter, others...?) + - [ ] screens (bus e-ink, GL e-ink, bus shelter, pre-fare, others...?) + - [ ] audio (bus shelter, others...?) - [ ] admin Create a PR with your changes and deploy to dev-green. diff --git a/lib/screens/alerts/alert.ex b/lib/screens/alerts/alert.ex index 9924cb2d3..ae342482c 100644 --- a/lib/screens/alerts/alert.ex +++ b/lib/screens/alerts/alert.ex @@ -124,17 +124,6 @@ defmodule Screens.Alerts.Alert do description: String.t() } - # V1 only - def to_map(nil), do: nil - - def to_map(alert) do - %{ - effect: alert.effect, - header: alert.header, - updated_at: DateTime.to_iso8601(alert.updated_at) - } - end - # Used by elevator status def ap_to_map({nil, end_t}) do %{"start" => nil, "end" => DateTime.to_iso8601(end_t)} @@ -148,30 +137,6 @@ defmodule Screens.Alerts.Alert do %{"start" => DateTime.to_iso8601(start_t), "end" => DateTime.to_iso8601(end_t)} end - # V1 only - @effect_order [ - :amber_alert, - :cancellation, - :delay, - :suspension, - :track_change, - :detour, - :shuttle, - :stop_closure, - :dock_closure, - :station_closure, - :stop_moved, - :extra_service, - :schedule_change, - :service_change, - :snow_route, - :stop_shoveling, - :station_issue, - :dock_issue, - :access_issue, - :policy_change - ] - @spec fetch(keyword()) :: {:ok, list(t())} | :error def fetch(opts \\ [], get_json_fn \\ &V3Api.get_json/2) do Screens.Telemetry.span([:screens, :alerts, :alert, :fetch], fn -> @@ -301,7 +266,6 @@ defmodule Screens.Alerts.Alert do defp format_query_param(_), do: [] - # V1 & V2 def happening_now?(%{active_period: aps}, now \\ DateTime.utc_now()) do Enum.any?(aps, &in_active_period(&1, now)) end @@ -318,112 +282,6 @@ defmodule Screens.Alerts.Alert do DateTime.compare(t, start_t) in [:gt, :eq] && DateTime.compare(t, end_t) in [:lt, :eq] end - defp within_two_weeks(time_1, time_2) do - diff = DateTime.diff(time_1, time_2, :second) - diff <= 14 * 24 * 60 * 60 && diff >= -14 * 24 * 60 * 60 - end - - # NEW INFO - # defined as: created_at or updated_at is within the last two weeks - # V1 only - def new_info_in_last_two_weeks( - %{created_at: created_at, updated_at: updated_at}, - now \\ DateTime.utc_now() - ) do - new_info = within_two_weeks(now, created_at) || within_two_weeks(now, updated_at) - if new_info, do: 1, else: 0 - end - - # NEW SERVICE - # defined as: next active_period start in the future is within two weeks of now - # V1 only - def new_service_in_next_two_weeks(%{active_period: active_period}, now \\ DateTime.utc_now()) do - next_t = first_future_active_period_start(active_period, now) - - case next_t do - :infinity -> - 0 - - _ -> - soon = - next_t - |> DateTime.from_unix!() - |> within_two_weeks(now) - - if soon, do: 1, else: 0 - end - end - - # (from dotcom) - # atoms are greater than any integer - # V1 only - defp first_future_active_period_start([], _now), do: :infinity - - defp first_future_active_period_start(periods, now) do - now_unix = DateTime.to_unix(now, :second) - - future_periods = - for {start, _} <- periods, - start, - # wrap in a list to avoid an Erlang 19.3 issue - unix <- [DateTime.to_unix(start)], - unix > now_unix do - unix - end - - if future_periods == [] do - :infinity - else - Enum.min(future_periods) - end - end - - # V1 only - for {name, index} <- Enum.with_index(@effect_order) do - defp effect_index(unquote(name)), do: unquote(index) - end - - # fallback - defp effect_index(_), do: unquote(length(@effect_order)) - - ### - - # V1 only - def build_delay_map(alerts) do - Enum.reduce(alerts, %{}, &delay_map_reducer/2) - end - - # V1 only - defp delay_map_reducer(%{informed_entities: ies, severity: severity}, delay_map) do - route_ids = bus_route_informed_entities(ies) - Enum.reduce(route_ids, delay_map, &delay_map_reducer_helper(&1, severity, &2)) - end - - # V1 only - defp delay_map_reducer_helper(route_id, severity, delay_map) do - case delay_map do - %{^route_id => current_severity} when current_severity >= severity -> - delay_map - - _ -> - Map.put(delay_map, route_id, severity) - end - end - - # V1 only - defp bus_route_informed_entities(informed_entities) do - Enum.flat_map(informed_entities, &bus_route_informed_entity/1) - end - - # V1 only - defp bus_route_informed_entity(%{route: route_id, route_type: 3}) do - [route_id] - end - - defp bus_route_informed_entity(_) do - [] - end - @alert_cause_mapping %{ accident: "an accident", construction: "construction", diff --git a/lib/screens/audio.ex b/lib/screens/audio.ex index 456c1445b..384035fde 100644 --- a/lib/screens/audio.ex +++ b/lib/screens/audio.ex @@ -3,23 +3,8 @@ defmodule Screens.Audio do require Logger - alias Screens.Audio.Fetch - alias Screens.Psa - alias Screens.Util - @lexicon_names ["mbtalexicon"] - @type time_representation :: - %{type: :text, value: time_value(String.t())} - | %{type: :minutes, value: time_value(integer)} - | %{type: :timestamp, value: time_value(String.t())} - - @type time_value(t) :: {t, Screens.Departures.Departure.crowding_level()} - - @type departure_group_key :: {String.t(), String.t(), String.t()} - - @type departure_group :: {departure_group_key(), [map()]} - @spec synthesize(String.t(), keyword()) :: {:ok, binary()} | :error def synthesize(ssml_string, log_meta) do result = @@ -37,156 +22,6 @@ defmodule Screens.Audio do end end - # used in V1 only - def from_api_data( - %{ - station_name: station_name, - sections: sections, - current_time: current_time - }, - screen_id - ) do - %{ - station_name: station_name, - departures_by_pill: group_departures_by_pill(sections, current_time), - psa: get_audio_psa(screen_id) - } - end - - @spec group_departures_by_pill(list(map()), String.t()) :: - keyword([departure_group()]) - defp group_departures_by_pill(sections, current_time) do - sections - |> Enum.map(&move_data_to_departures(&1, current_time)) - |> Util.group_by_with_order(& &1.pill) - |> Enum.map(fn {pill, sections} -> {pill, merge_section_departures(sections)} end) - end - - @spec merge_section_departures([map()]) :: %{ - wayfinding: String.t() | nil, - departure_groups: [departure_group()] - } - defp merge_section_departures(sections) do - sections - |> Enum.flat_map(& &1.departures) - |> Enum.sort_by(& &1.time) - |> Util.group_by_with_order(&{&1.route, &1.route_id, &1.destination}) - |> Enum.map(fn {key, departures} -> - {key, - %{ - times: group_time_types(departures), - alerts: hd(departures).alerts, - wayfinding: hd(departures).wayfinding, - track_number: hd(departures).track_number - }} - end) - |> condense_wayfinding() - end - - defp move_data_to_departures(section, current_time) do - data = %{ - pill: section.pill, - wayfinding: section.audio.wayfinding, - current_time: current_time - } - - %{section | departures: Enum.map(section.departures, &Map.merge(&1, data))} - end - - defp group_time_types(departures) do - departures - |> Enum.map( - &Map.merge(%{pill: &1.pill, crowding_level: &1.crowding_level}, get_time_representation(&1)) - ) - |> Enum.chunk_by(& &1.type) - |> ungroup_arr_brd() - |> Enum.map(&time_list_to_time_group/1) - end - - # ARR and BRD departures are never grouped together - defp ungroup_arr_brd(grouped_times) do - grouped_times - |> Enum.reduce([], fn - [%{type: :text} | _rest] = group, acc -> - ungrouped = group |> Enum.reverse() |> Enum.map(&[&1]) - ungrouped ++ acc - - group, acc -> - [group | acc] - end) - |> Enum.reverse() - end - - defp time_list_to_time_group([first_time | _] = times) do - values = Enum.map(times, &{&1.value, &1.crowding_level}) - - %{pill: first_time.pill, type: first_time.type, values: values} - end - - # Don't repeat wayfinding info if it's the same for all departure groups within a pill - defp condense_wayfinding(departure_groups) do - unique_wayfinding = - Enum.uniq_by(departure_groups, fn {_key, %{wayfinding: wayfinding}} -> wayfinding end) - - if length(unique_wayfinding) == 1 do - {_key, %{wayfinding: common_wayfinding}} = hd(unique_wayfinding) - - without_wayfinding = - Enum.map(departure_groups, fn {key, group} -> {key, %{group | wayfinding: nil}} end) - - %{ - wayfinding: common_wayfinding, - departure_groups: without_wayfinding - } - else - %{ - wayfinding: nil, - departure_groups: departure_groups - } - end - end - - defp get_time_representation(%{ - time: time, - current_time: current_time, - vehicle_status: vehicle_status, - stop_type: stop_type - }) do - {:ok, time, _} = DateTime.from_iso8601(time) - {:ok, current_time, _} = DateTime.from_iso8601(current_time) - - second_difference = DateTime.diff(time, current_time) - minute_difference = round(second_difference / 60) - - cond do - vehicle_status === :stopped_at and second_difference <= 90 -> - %{type: :text, value: :brd} - - second_difference <= 30 -> - if stop_type === :first_stop, - do: %{type: :text, value: :brd}, - else: %{type: :text, value: :arr} - - minute_difference < 60 -> - %{type: :minutes, value: minute_difference} - - true -> - timestamp = - time - |> Timex.to_datetime("America/New_York") - |> Timex.format!("{h12}:{m} {AM}") - - %{type: :timestamp, value: timestamp} - end - end - - defp get_audio_psa(screen_id) do - case Psa.current_audio_psa_for(screen_id) do - nil -> nil - psa_config -> Fetch.fetch_psa(psa_config) - end - end - defp report_error(ssml_string, error, meta) do Logger.error( "synthesize_ssml_failed string=#{inspect(ssml_string)} error=#{inspect(error)}", diff --git a/lib/screens/audio/fetch.ex b/lib/screens/audio/fetch.ex deleted file mode 100644 index 3e5af156b..000000000 --- a/lib/screens/audio/fetch.ex +++ /dev/null @@ -1,35 +0,0 @@ -defmodule Screens.Audio.Fetch do - @moduledoc false - - require Logger - - def fetch_psa({format, name, type}) do - case get_from_s3(name, format) do - {:ok, psa_text} -> {format, psa_text, type} - :error -> nil - end - end - - def get_from_s3(name, format) do - bucket = Application.get_env(:screens, :audio_psa_s3_bucket) - - get_operation = ExAws.S3.get_object(bucket, psa_path(name, format)) - - case ExAws.request(get_operation) do - {:ok, %{body: body, status_code: 200}} -> - {:ok, body} - - {:error, err} -> - _ = Logger.info("s3_audio_psa_fetch_error #{inspect(err)}") - :error - end - end - - defp psa_path(name, format) do - Application.get_env(:screens, :audio_psa_s3_directory) <> - name <> format_to_file_extension(format) - end - - defp format_to_file_extension(:plaintext), do: ".txt" - defp format_to_file_extension(:ssml), do: ".ssml" -end diff --git a/lib/screens/config/cache.ex b/lib/screens/config/cache.ex index 275dca218..27c386d96 100644 --- a/lib/screens/config/cache.ex +++ b/lib/screens/config/cache.ex @@ -49,15 +49,6 @@ defmodule Screens.Config.Cache do end end - def service_level(screen_id) do - with_table default: 1 do - case :ets.match(@table, {{:screen, screen_id}, %{app_params: %{service_level: :"$1"}}}) do - [[service_level]] -> service_level - [] -> 1 - end - end - end - def disabled?(screen_id) do with_table default: false do case :ets.match(@table, {{:screen, screen_id}, %{disabled: :"$1"}}) do @@ -77,15 +68,6 @@ defmodule Screens.Config.Cache do end end - def app_params(screen_id) do - with_table default: nil do - case :ets.match(@table, {{:screen, screen_id}, %{app_params: :"$1"}}) do - [[app_params]] -> app_params - [] -> nil - end - end - end - def devops do with_table default: nil do case :ets.match(@table, {:devops, :"$1"}) do @@ -95,17 +77,6 @@ defmodule Screens.Config.Cache do end end - @doc """ - Returns a list of all screen IDs. - """ - def screen_ids do - with_table default: [] do - @table - |> :ets.match({{:screen, :"$1"}, :_}) - |> List.flatten() - end - end - @doc """ Returns a list of all screen IDs that satisfy the given filter. The filter function will be passed a tuple of {screen_id, screen_config} and should return true if that screen ID should be included in the results. diff --git a/lib/screens/departures/departure.ex b/lib/screens/departures/departure.ex index e11276ca3..b76e76d31 100644 --- a/lib/screens/departures/departure.ex +++ b/lib/screens/departures/departure.ex @@ -466,21 +466,6 @@ defmodule Screens.Departures.Departure do DateTime.compare(departure_time, now) == :lt end - def associate_alerts_with_departures(departures, alerts) do - delay_map = Screens.Alerts.Alert.build_delay_map(alerts) - Enum.map(departures, &update_departure_with_delay_alert(delay_map, &1)) - end - - defp update_departure_with_delay_alert(delay_map, %{route_id: route_id} = departure) do - case delay_map do - %{^route_id => severity} -> - %{departure | inline_badges: [%{type: :delay, severity: severity}]} - - _ -> - departure - end - end - defp get_alerts_list(alerts) do [ delay: Enum.any?(alerts, &(&1.effect == :delay)), diff --git a/lib/screens/line_map.ex b/lib/screens/line_map.ex deleted file mode 100644 index 8810fd8ff..000000000 --- a/lib/screens/line_map.ex +++ /dev/null @@ -1,185 +0,0 @@ -defmodule Screens.LineMap do - @moduledoc false - - alias Screens.RoutePatterns.RoutePattern - alias Screens.Vehicles.Vehicle - - def by_stop_id(stop_id, route_id, direction_id, predictions) do - vehicles = Vehicle.by_route_and_direction(route_id, direction_id) - route_stops_result = RoutePattern.stops_by_route_and_direction(route_id, direction_id) - - case route_stops_result do - :error -> - {nil, :error} - - {:ok, route_stops} -> - current_stop_index = - Enum.find_index(route_stops, fn %{id: route_stop_id} -> route_stop_id == stop_id end) - - %{id: origin_stop_id} = Enum.at(route_stops, 0) - schedule = next_scheduled_departure(origin_stop_id, route_id, predictions) - - line_map_data = %{ - stops: format_stops(route_stops, current_stop_index), - vehicles: format_vehicles(vehicles, route_stops, current_stop_index, predictions), - schedule: format_schedule(schedule) - } - - filtered_predictions = - {:ok, - filter_predictions_by_vehicles(predictions, vehicles, route_stops, current_stop_index)} - - {line_map_data, filtered_predictions} - end - end - - def filter_predictions_by_vehicles(predictions, vehicles, route_stops, current_stop_index) do - departed_vehicle_trip_ids = - vehicles - |> Enum.filter(fn v -> - index = find_vehicle_index(v, route_stops) - not is_nil(index) and index > current_stop_index - end) - |> Enum.map(fn %{trip_id: trip_id} -> trip_id end) - |> Enum.reject(&is_nil/1) - - Enum.reject(predictions, fn p -> - case p do - %{trip: %{id: trip_id}} -> trip_id in departed_vehicle_trip_ids - _ -> false - end - end) - end - - defp find_vehicle_index(%{stop_id: vehicle_stop_id}, route_stops) do - Enum.find_index(route_stops, fn %{id: route_stop_id} -> route_stop_id == vehicle_stop_id end) - end - - defp next_scheduled_departure(origin_stop_id, route_id, predictions) do - # credo:disable-for-next-line Screens.Checks.UntestableDateTime - time = DateTime.add(DateTime.utc_now(), -180) - - case Screens.Schedules.Schedule.fetch(%{ - stop_ids: [origin_stop_id], - route_ids: [route_id] - }) do - {:ok, [_ | _] = schedules} -> - schedules - |> Enum.filter(&check_after(&1, time)) - |> next_unpredicted_departure(predictions) - - _ -> - nil - end - end - - defp check_after(%{departure_time: nil}, _time) do - false - end - - defp check_after(%{departure_time: t}, time) do - DateTime.compare(t, time) == :gt - end - - defp next_unpredicted_departure([], _) do - nil - end - - defp next_unpredicted_departure([first_schedule | _], []) do - first_schedule - end - - defp next_unpredicted_departure(schedules, [%{trip: %{id: trip_id}}]) do - schedules - |> Enum.reject(fn %{trip_id: t_id} -> t_id == trip_id end) - |> Enum.at(0) - end - - defp next_unpredicted_departure(_schedules, _predictions) do - # If there are two predictions, we don't want to show a schedule departure. - nil - end - - defp format_schedule(%{departure_time: t}), do: %{departure_time: t} - defp format_schedule(nil), do: nil - - defp format_stops(route_stops, current_stop_index) do - %{name: current_stop_name} = Enum.at(route_stops, current_stop_index) - %{name: next_stop_name} = Enum.at(route_stops, current_stop_index + 1) - %{name: following_stop_name} = Enum.at(route_stops, current_stop_index + 2) - %{name: origin_stop_name} = Enum.at(route_stops, 0) - - %{ - current: current_stop_name, - next: next_stop_name, - following: following_stop_name, - origin: origin_stop_name, - count_before: current_stop_index - } - end - - defp format_vehicles(vehicles, route_stops, current_stop_index, predictions) do - trip_id_to_time = - predictions - |> Enum.reject(&is_nil(&1.trip)) - |> Enum.map(&select_prediction_time/1) - |> Enum.into(%{}) - - vehicles - |> Enum.map(&format_vehicle(&1, route_stops, current_stop_index, trip_id_to_time)) - |> Enum.reject(fn - nil -> true - %{index: index} -> index < 0 - end) - |> maybe_strip_time() - end - - def select_prediction_time(%{ - arrival_time: arrival_time, - departure_time: departure_time, - trip: %{id: trip_id} - }) do - time = Screens.Departures.Departure.select_prediction_time(arrival_time, departure_time) - {trip_id, time} - end - - defp maybe_strip_time(vehicles) do - if Enum.any?(vehicles, fn v -> v.index > 2 and is_nil(v.time) end) do - Enum.map(vehicles, &strip_time/1) - else - vehicles - end - end - - defp strip_time(vehicle) do - %{vehicle | time: nil} - end - - defp format_vehicle( - %{id: id, stop_id: vehicle_stop_id, trip_id: vehicle_trip_id, current_status: status}, - route_stops, - current_stop_index, - trip_id_to_time - ) do - vehicle_stop_index = - Enum.find_index(route_stops, fn %{id: route_stop_id} -> route_stop_id == vehicle_stop_id end) - - case vehicle_stop_index do - nil -> - nil - - _ -> - index = - 2 + current_stop_index - vehicle_stop_index + - status_adjustment(vehicle_stop_index, status) - - time = Map.get(trip_id_to_time, vehicle_trip_id) - %{id: id, index: index, time: time} - end - end - - defp status_adjustment(0, _), do: 0.0 - defp status_adjustment(_, :stopped_at), do: 0.0 - defp status_adjustment(_, :in_transit_to), do: 0.7 - defp status_adjustment(_, :incoming_at), do: 0.3 -end diff --git a/lib/screens/nearby_connections.ex b/lib/screens/nearby_connections.ex deleted file mode 100644 index a166e63ba..000000000 --- a/lib/screens/nearby_connections.ex +++ /dev/null @@ -1,92 +0,0 @@ -defmodule Screens.NearbyConnections do - @moduledoc false - - alias Screens.Config.Cache - alias ScreensConfig.Bus - - def by_screen_id(screen_id) do - %Bus{stop_id: stop_id, nearby_connections: nearby_connections} = Cache.app_params(screen_id) - - nearby_stop_ids = Enum.map(nearby_connections, fn {stop_id, _} -> stop_id end) - stop_query = Enum.join([stop_id | nearby_stop_ids], ",") - - case Screens.V3Api.get_json("stops", %{"filter[id]" => stop_query}) do - {:ok, %{"data" => stops_data}} -> - result = - stops_data - |> Enum.map(&parse_stop_data/1) - |> build_nearby_connections(stop_id, nearby_connections) - - {:ok, result} - - _ -> - :error - end - end - - def parse_stop_data(%{ - "attributes" => %{"latitude" => lat, "longitude" => lon, "name" => name}, - "id" => id - }) do - %{stop_id: id, stop_name: name, stop_lat: lat, stop_lon: lon} - end - - def build_nearby_connections(stops_data, stop_id, routes_at_stops) do - {stop_data, nearby_stops_data} = split_stops_data(stops_data, stop_id) - %{stop_lat: stop_lat, stop_lon: stop_lon} = stop_data - - nearby_connections = - nearby_stops_data - |> Enum.map(&build_nearby_connection(&1, stop_lat, stop_lon, routes_at_stops)) - |> Enum.sort_by(& &1.distance) - |> Enum.map(&convert_distance/1) - - {stop_data, nearby_connections} - end - - @miles_to_feet 5280 - @feet_to_minutes 1 / 250 - - defp convert_distance(%{distance: distance_miles} = connection) do - distance_minutes = max(1, Kernel.round(distance_miles * @miles_to_feet * @feet_to_minutes)) - %{connection | distance: distance_minutes} - end - - def build_nearby_connection( - %{stop_lat: nearby_lat, stop_lon: nearby_lon, stop_name: name, stop_id: nearby_id}, - stop_lat, - stop_lon, - routes_at_stops - ) do - distance_miles = distance(stop_lat, stop_lon, nearby_lat, nearby_lon) - routes_at_stop = routes_at_stops |> Enum.into(%{}) |> Map.get(nearby_id) - %{name: name, distance: distance_miles, routes: routes_at_stop} - end - - def split_stops_data(stops_data, stop_id) do - Enum.reduce(stops_data, {nil, []}, fn %{stop_id: id} = data, {stop_data, nearby_stops_data} -> - case id do - ^stop_id -> - {data, nearby_stops_data} - - _ -> - {stop_data, [data | nearby_stops_data]} - end - end) - end - - @degrees_to_radians 0.0174533 - @twice_earth_radius_miles 7918 - - @doc "Returns the Haversine distance (in miles) between two latitude/longitude pairs" - @spec distance(number, number, number, number) :: float - def distance(latitude, longitude, latitude2, longitude2) do - # Haversine distance - a = - 0.5 - :math.cos((latitude2 - latitude) * @degrees_to_radians) / 2 + - :math.cos(latitude * @degrees_to_radians) * :math.cos(latitude2 * @degrees_to_radians) * - (1 - :math.cos((longitude2 - longitude) * @degrees_to_radians)) / 2 - - @twice_earth_radius_miles * :math.asin(:math.sqrt(a)) - end -end diff --git a/lib/screens/nearby_departures.ex b/lib/screens/nearby_departures.ex deleted file mode 100644 index caf7a7adb..000000000 --- a/lib/screens/nearby_departures.ex +++ /dev/null @@ -1,53 +0,0 @@ -defmodule Screens.NearbyDepartures do - @moduledoc false - - alias Screens.Config.Cache - alias ScreensConfig.Gl - - def by_screen_id(screen_id) do - if Cache.mode_disabled?(:bus) or Cache.mode_disabled?(:light_rail) do - [] - else - by_enabled_screen_id(screen_id) - end - end - - defp by_enabled_screen_id(screen_id) do - %Gl{nearby_departures: nearby_departure_stop_ids} = Cache.app_params(screen_id) - - prediction_result = - Screens.Predictions.Prediction.fetch(%{stop_ids: nearby_departure_stop_ids}) - - case prediction_result do - {:ok, predictions} -> - predictions - |> Enum.group_by(& &1.stop.id) - |> Enum.map(fn {stop_id, prediction_list} -> - {stop_id, Enum.min_by(prediction_list, & &1.departure_time, DateTime, fn -> nil end)} - end) - |> Enum.map(fn {_stop_id, prediction} -> format_prediction(prediction) end) - - :error -> - [] - end - end - - defp format_prediction(%{ - arrival_time: arrival_time, - departure_time: departure_time, - stop: %{name: stop_name}, - route: %{short_name: route}, - trip: %{headsign: destination} - }) do - time = Screens.Departures.Departure.select_prediction_time(arrival_time, departure_time) - - %{ - stop_name: stop_name, - route: route, - time: time, - destination: destination - } - end - - defp format_prediction(_), do: nil -end diff --git a/lib/screens/psa.ex b/lib/screens/psa.ex deleted file mode 100644 index f7896ce82..000000000 --- a/lib/screens/psa.ex +++ /dev/null @@ -1,103 +0,0 @@ -defmodule Screens.Psa do - @moduledoc false - alias Screens.Config.Cache - alias ScreensConfig.{PsaConfig, Screen, Solari} - - @eink_refresh_seconds 30 - @solari_refresh_seconds 15 - @solari_psa_period 3 - - @s3_base_url "https://mbta-screens.s3.amazonaws.com/" - - def current_psa_for(screen_id) do - %Screen{ - app_id: app_id, - app_params: %_app{psa_config: psa_config} - } = Cache.screen(screen_id) - - %PsaConfig{ - default_list: default_list, - scheduled_overrides: scheduled_overrides - } = psa_config - - {psa_type, psa_list} = get_active_psa_list(scheduled_overrides, default_list) - - psa_s3_url = - psa_list - |> choose_psa(app_id, psa_type) - |> get_s3_url() - - {psa_type, psa_s3_url} - end - - def current_audio_psa_for(screen_id) do - case Cache.app_params(screen_id) do - %Solari{audio_psa: audio_psa} -> audio_psa - _ -> nil - end - end - - defp get_active_psa_list(scheduled_overrides, default_list) do - # credo:disable-for-next-line Screens.Checks.UntestableDateTime - now = DateTime.utc_now() - - Enum.find_value(scheduled_overrides, default_list, fn override -> - if in_date_time_range?(now, {override.start_time, override.end_time}), - do: override.psa_list, - else: false - end) - end - - defp in_date_time_range?(_dt, {nil, nil}), do: true - - defp in_date_time_range?(dt, {start_time, nil}) do - DateTime.compare(dt, start_time) in [:gt, :eq] - end - - defp in_date_time_range?(dt, {nil, end_time}) do - DateTime.compare(dt, end_time) == :lt - end - - defp in_date_time_range?(dt, {start_time, end_time}) do - in_date_time_range?(dt, {start_time, nil}) and in_date_time_range?(dt, {nil, end_time}) - end - - defp choose_psa(psa_list, :solari, :slide_in) do - # How often to change the selected PSA - solari_psa_refresh_seconds = @solari_refresh_seconds * @solari_psa_period - - # Choose which PSA to show, if we're showing one this refresh - solari_psa = choose_from_rotating_list(psa_list, solari_psa_refresh_seconds) - - # Return either the current PSA or nil - solari_list = [solari_psa] ++ List.duplicate(nil, @solari_psa_period - 1) - choose_from_rotating_list(solari_list, @solari_refresh_seconds) - end - - defp choose_psa(psa_list, :solari, _psa_type) do - choose_from_rotating_list(psa_list, @solari_refresh_seconds) - end - - defp choose_psa(psa_list, _app_id, _psa_type) do - choose_from_rotating_list(psa_list, @eink_refresh_seconds) - end - - defp choose_from_rotating_list([], _), do: nil - defp choose_from_rotating_list([psa], _), do: psa - - defp choose_from_rotating_list(list, seconds_to_show) do - # credo:disable-for-next-line Screens.Checks.UntestableDateTime - t = DateTime.utc_now() - seconds_since_midnight = t.hour * 60 * 60 + t.minute * 60 + t.second - periods_since_midnight = div(seconds_since_midnight, seconds_to_show) - current_index = rem(periods_since_midnight, length(list)) - Enum.at(list, current_index) - end - - defp get_s3_url(nil), do: nil - defp get_s3_url(filename), do: @s3_base_url <> psa_images_prefix() <> filename - - defp psa_images_prefix do - Application.get_env(:screens, :environment_name, "screens-prod") <> "/images/psa/" - end -end diff --git a/lib/screens/screen_data.ex b/lib/screens/screen_data.ex deleted file mode 100644 index 3b0fa2f66..000000000 --- a/lib/screens/screen_data.ex +++ /dev/null @@ -1,60 +0,0 @@ -defmodule Screens.ScreenData do - @moduledoc false - - alias Screens.Config.Cache - alias Screens.LogScreenData - alias Screens.Util - alias ScreensConfig.Screen - - @modules_by_app_id %{ - solari: Screens.SolariScreenData - } - - @disabled_response %{force_reload: false, success: false, status: :disabled} - - @outdated_response %{force_reload: true, status: :outdated} - - def by_screen_id(screen_id, is_screen, opts \\ []) do - check_disabled = Keyword.get(opts, :check_disabled, false) - - last_refresh = Keyword.get(opts, :last_refresh, nil) - check_outdated = not is_nil(last_refresh) - - response = - cond do - check_outdated and Util.outdated?(screen_id, last_refresh) -> @outdated_response - check_disabled and disabled?(screen_id) -> @disabled_response - true -> screen_id |> fetch_data(is_screen) |> Map.put(:status, :success) - end - - _ = LogScreenData.log_api_response(response, screen_id, last_refresh, is_screen) - - response - end - - def by_screen_id_with_datetime(screen_id, datetime_str) do - app_id = app_id_from_screen_id(screen_id) - - {:ok, naive} = Timex.parse(datetime_str, "{ISO:Extended}") - {:ok, local} = DateTime.from_naive(naive, "America/New_York") - {:ok, datetime} = DateTime.shift_zone(local, "Etc/UTC") - screen_data_module = Map.get(@modules_by_app_id, app_id) - screen_data_module.by_screen_id(screen_id, false, datetime) - end - - defp disabled?(screen_id) do - Cache.disabled?(screen_id) - end - - defp fetch_data(screen_id, is_screen) do - app_id = app_id_from_screen_id(screen_id) - - screen_data_module = Map.get(@modules_by_app_id, app_id) - screen_data_module.by_screen_id(screen_id, is_screen) - end - - defp app_id_from_screen_id(screen_id) do - %Screen{app_id: app_id} = Cache.screen(screen_id) - app_id - end -end diff --git a/lib/screens/solari_screen_data.ex b/lib/screens/solari_screen_data.ex deleted file mode 100644 index f3aa98c8f..000000000 --- a/lib/screens/solari_screen_data.ex +++ /dev/null @@ -1,267 +0,0 @@ -defmodule Screens.SolariScreenData do - @moduledoc false - - alias Screens.Config.Cache - alias Screens.Departures.Departure - alias Screens.{LogScreenData, SignsUiConfig, Util} - alias ScreensConfig.{Query, Solari} - alias ScreensConfig.Query.{Opts, Params} - alias ScreensConfig.Solari.Section - alias ScreensConfig.Solari.Section.{Headway, Layout} - alias ScreensConfig.Solari.Section.Layout.{Bidirectional, Upcoming} - - def by_screen_id(screen_id, is_screen, at_historical_datetime \\ nil) do - if Cache.mode_disabled?(:bus) do - %{force_reload: false, success: false} - else - by_enabled_screen_id(screen_id, is_screen, at_historical_datetime) - end - end - - defp by_enabled_screen_id(screen_id, is_screen, at_historical_datetime) do - %Solari{ - station_name: station_name, - sections: sections, - section_headers: section_headers, - overhead: overhead - } = Cache.app_params(screen_id) - - current_time = - case at_historical_datetime do - # credo:disable-for-next-line Screens.Checks.UntestableDateTime - nil -> DateTime.utc_now() - dt -> dt - end - - {psa_type, psa_url} = Screens.Psa.current_psa_for(screen_id) - - sections_data = fetch_sections_data(sections, at_historical_datetime, current_time) - _ = LogScreenData.log_departures(screen_id, is_screen, sections_data) - - case sections_data do - {:ok, data} -> - %{ - force_reload: false, - success: true, - current_time: Screens.Util.format_time(current_time), - station_name: station_name, - sections: data, - section_headers: section_headers, - psa_type: psa_type, - psa_url: psa_url, - overhead: overhead - } - - :error -> - %{force_reload: false, success: false} - end - end - - defp fetch_sections_data(sections, at_historical_datetime, current_time) do - sections_data = - Enum.map(sections, &fetch_section_data(&1, at_historical_datetime, current_time)) - - if Enum.any?(sections_data, fn data -> data == :error end) do - :error - else - {:ok, Enum.map(sections_data, fn {:ok, data} -> data end)} - end - end - - defp fetch_section_data( - %Section{ - name: section_name, - arrow: arrow, - audio: audio_params, - pill: pill, - headway: headway_config - } = section, - at_historical_datetime, - current_time - ) do - if section_disabled?(section, headway_config) do - {:ok, - %{ - name: section_name, - arrow: arrow, - pill: pill, - audio: Map.from_struct(audio_params), - departures: [], - paging: %{is_enabled: false}, - headway: fetch_headway_mode(headway_config, current_time), - disabled: true - }} - else - fetch_enabled_section_data(section, at_historical_datetime, current_time) - end - end - - defp section_disabled?(%Section{pill: pill}, %Headway{sign_ids: sign_ids}) do - subway_disabled? = - Cache.mode_disabled?(:subway) or SignsUiConfig.Cache.all_signs_inactive?(sign_ids) - - subway_section? = pill in ~w[red orange blue]a - - commuter_rail_disabled? = Cache.mode_disabled?(:rail) - commuter_rail_section? = pill === :cr - - light_rail_disabled? = Cache.mode_disabled?(:light_rail) - light_rail_section? = pill in ~w[green mattapan]a - - (subway_section? and subway_disabled?) or (commuter_rail_section? and commuter_rail_disabled?) or - (light_rail_section? and light_rail_disabled?) - end - - defp fetch_enabled_section_data( - %Section{ - name: section_name, - arrow: arrow, - query: %Query{params: query_params, opts: query_opts}, - layout: layout_params, - audio: audio_params, - pill: pill, - headway: headway_config - }, - at_historical_datetime, - current_time - ) do - case query_data(query_params, query_opts, at_historical_datetime) do - {:ok, data} -> - departures = do_layout(data, layout_params) - - {:ok, - %{ - name: section_name, - arrow: arrow, - pill: pill, - audio: Map.from_struct(audio_params), - departures: departures, - paging: do_paging(departures, layout_params), - headway: fetch_headway_mode(headway_config, current_time), - disabled: false - }} - - :error -> - :error - end - end - - def fetch_headway_mode(%Headway{headway_id: nil}, _), do: %{active: false} - - def fetch_headway_mode( - %Headway{sign_ids: sign_ids, headway_id: headway_id, headsigns: headsigns}, - current_time - ) do - if SignsUiConfig.Cache.all_signs_in_headway_mode?(sign_ids) do - time_ranges = SignsUiConfig.Cache.time_ranges(headway_id) - current_time_period = Screens.Util.time_period(current_time) - - case time_ranges do - %{^current_time_period => {range_low, range_high}} -> - %{active: true, headsigns: headsigns, range_low: range_low, range_high: range_high} - - _ -> - %{active: false} - end - else - %{active: false} - end - end - - @spec do_paging(list(map()), Layout.t()) :: map() - defp do_paging(departures, %Upcoming{paged: true, visible_rows: :infinity}) do - %{is_enabled: true, visible_rows: length(departures)} - end - - defp do_paging(_departures, %Upcoming{paged: true, visible_rows: visible_rows}) do - %{is_enabled: true, visible_rows: visible_rows} - end - - defp do_paging(_, _) do - %{is_enabled: false} - end - - defp query_data( - %Params{} = params, - %Opts{include_schedules: include_schedules}, - at_historical_datetime - ) do - query_params = Map.from_struct(params) - - if is_nil(at_historical_datetime) do - Departure.fetch(query_params, include_schedules) - else - Departure.fetch_schedules_by_datetime(query_params, at_historical_datetime) - end - end - - @spec do_layout(list(map()), Upcoming.t()) :: list(map()) - defp do_layout(query_data, %Upcoming{num_rows: num_rows} = layout_opts) do - query_data - |> filter_by_routes(layout_opts) - |> filter_by_minutes(layout_opts) - |> Enum.sort_by( - &Util.parse_time_string(&1.time), - DateTime - ) - |> take_rows(num_rows) - |> Enum.map(&Map.from_struct/1) - end - - @spec do_layout(list(map()), Bidirectional.t()) :: list(map()) - defp do_layout(query_data, %Bidirectional{} = layout_opts) do - query_data - |> filter_by_routes(layout_opts) - |> filter_by_minutes(layout_opts) - |> Enum.sort_by( - &Util.parse_time_string(&1.time), - DateTime - ) - |> Enum.split_with(fn %{direction_id: direction_id} -> direction_id == 0 end) - |> Tuple.to_list() - |> Enum.flat_map(&Enum.slice(&1, 0, 1)) - |> Enum.sort_by( - &Util.parse_time_string(&1.time), - DateTime - ) - |> Enum.map(&Map.from_struct/1) - end - - @spec filter_by_minutes(list(map()), Layout.t()) :: list(map()) - defp filter_by_minutes(query_data, %{max_minutes: :infinity}), do: query_data - - defp filter_by_minutes(query_data, %{max_minutes: max_minutes}) do - # credo:disable-for-next-line Screens.Checks.UntestableDateTime - max_departure_time = DateTime.add(DateTime.utc_now(), 60 * max_minutes) - - Enum.reject(query_data, fn %{time: time_str} -> - {:ok, departure_time, _} = DateTime.from_iso8601(time_str) - DateTime.compare(departure_time, max_departure_time) == :gt - end) - end - - defp filter_by_minutes(query_data, _), do: query_data - - @spec filter_by_routes(list(map()), Layout.t()) :: list(map()) - defp filter_by_routes(query_data, %{routes: {action, routes}}) do - route_matchers = MapSet.new(routes) - - filter_fn = - case action do - :include -> &Enum.filter/2 - :exclude -> &Enum.reject/2 - end - - filter_fn.(query_data, fn departure -> - MapSet.member?(route_matchers, {departure.route_id, departure.direction_id}) - end) - end - - defp filter_by_routes(query_data, _), do: query_data - - defp take_rows(query_data, :infinity), do: query_data - - defp take_rows(query_data, num_rows) do - Enum.take(query_data, num_rows) - end -end diff --git a/lib/screens/util.ex b/lib/screens/util.ex index 2e67d1bc1..5317b6583 100644 --- a/lib/screens/util.ex +++ b/lib/screens/util.ex @@ -2,11 +2,6 @@ defmodule Screens.Util do @moduledoc false alias Screens.Config.Cache - alias Screens.Vehicles.Carriage - - def format_time(t) do - t |> DateTime.truncate(:second) |> DateTime.to_iso8601() - end @spec time_period(DateTime.t()) :: :peak | :off_peak def time_period(utc_time) do @@ -59,25 +54,6 @@ defmodule Screens.Util do |> Enum.map(fn {key, group} -> {key, Enum.reverse(group)} end) end - @doc """ - Gets the keys of a struct given the module where the struct is defined. - - Converts the keys to strings by default. - """ - @spec struct_keys(module(), keyword()) :: list(atom()) | list(String.t()) - def struct_keys(mod, opts \\ []) do - keys = - mod - |> Map.from_struct() - |> Map.keys() - - if Keyword.get(opts, :as_strings, true) do - Enum.map(keys, &Atom.to_string/1) - else - keys - end - end - @doc """ Similar to Enum.unzip, except it expects an enumerable of 3-element instead of 2-element tuples. """ @@ -224,26 +200,6 @@ defmodule Screens.Util do Date.add(get_service_date_today(now), 1) end - def translate_carriage_occupancy_status(%Carriage{occupancy_status: :no_data_available}), - do: :no_data - - def translate_carriage_occupancy_status(%Carriage{occupancy_status: :not_accepting_passengers}), - do: :closed - - def translate_carriage_occupancy_status(%Carriage{occupancy_percentage: occupancy_percentage}) - when occupancy_percentage <= 12, - do: :not_crowded - - def translate_carriage_occupancy_status(%Carriage{occupancy_percentage: occupancy_percentage}) - when occupancy_percentage <= 40, - do: :some_crowding - - def translate_carriage_occupancy_status(%Carriage{occupancy_percentage: occupancy_percentage}) - when occupancy_percentage > 40, - do: :crowded - - def translate_carriage_occupancy_status(_), do: nil - @doc """ Adds a timeout to a function. Mainly used for child processes of a Task.Supervisor which don't come with a timeout by default. diff --git a/lib/screens/v2/candidate_generator/solari_large.ex b/lib/screens/v2/candidate_generator/solari_large.ex deleted file mode 100644 index 37593bbf7..000000000 --- a/lib/screens/v2/candidate_generator/solari_large.ex +++ /dev/null @@ -1,56 +0,0 @@ -defmodule Screens.V2.CandidateGenerator.SolariLarge do - @moduledoc false - - alias Screens.V2.CandidateGenerator - alias Screens.V2.CandidateGenerator.Widgets - alias Screens.V2.Template.Builder - alias Screens.V2.WidgetInstance.{NormalHeader, Placeholder} - alias ScreensConfig.Screen - alias ScreensConfig.V2.Header.CurrentStopName - alias ScreensConfig.V2.SolariLarge - - @behaviour CandidateGenerator - - @impl CandidateGenerator - def screen_template do - {:screen, - %{ - normal: [:header, :main_content], - takeover: [:full_screen] - }} - |> Builder.build_template() - end - - @impl CandidateGenerator - # credo:disable-for-next-line Credo.Check.Design.DuplicatedCode - def candidate_instances( - config, - now \\ DateTime.utc_now(), - departures_instances_fn \\ &Widgets.Departures.departures_instances/2 - ) do - [ - fn -> header_instances(config, now) end, - fn -> departures_instances_fn.(config, now) end, - fn -> placeholder_instances() end - ] - |> Task.async_stream(& &1.(), timeout: 15_000) - |> Enum.flat_map(fn {:ok, instances} -> instances end) - end - - @impl CandidateGenerator - def audio_only_instances(_widgets, _config), do: [] - - defp header_instances(config, now) do - %Screen{ - app_params: %SolariLarge{header: %CurrentStopName{stop_name: stop_name}} - } = config - - [%NormalHeader{screen: config, text: stop_name, time: now}] - end - - defp placeholder_instances do - [ - %Placeholder{color: :blue, slot_names: [:main_content]} - ] - end -end diff --git a/lib/screens/v2/candidate_generator/widgets/departures.ex b/lib/screens/v2/candidate_generator/widgets/departures.ex index f2b19e490..d26888e97 100644 --- a/lib/screens/v2/candidate_generator/widgets/departures.ex +++ b/lib/screens/v2/candidate_generator/widgets/departures.ex @@ -17,8 +17,7 @@ defmodule Screens.V2.CandidateGenerator.Widgets.Departures do Busway, Departures, FreeTextLine, - GlEink, - SolariLarge + GlEink } @type options :: [ @@ -37,7 +36,7 @@ defmodule Screens.V2.CandidateGenerator.Widgets.Departures do @spec departures_instances(Screen.t(), options()) :: [widget()] def departures_instances(%Screen{app_params: %app{}} = config, now, options \\ []) - when app in [BusEink, BusShelter, Busway, GlEink, SolariLarge] do + when app in [BusEink, BusShelter, Busway, GlEink] do disabled_modes = Keyword.get(options, :disabled_modes_fn, &Screens.Config.Cache.disabled_modes/0).() diff --git a/lib/screens/v2/screen_data/parameters.ex b/lib/screens/v2/screen_data/parameters.ex index 9a18816ba..2169f757a 100644 --- a/lib/screens/v2/screen_data/parameters.ex +++ b/lib/screens/v2/screen_data/parameters.ex @@ -49,10 +49,6 @@ defmodule Screens.V2.ScreenData.Parameters do audio_active_time: {~T[04:45:00], ~T[01:45:00]}, candidate_generator: CandidateGenerator.PreFare, refresh_rate: 20 - }, - solari_large_v2: %Static{ - candidate_generator: CandidateGenerator.SolariLarge, - refresh_rate: 15 } } diff --git a/lib/screens_web/controllers/audio_controller.ex b/lib/screens_web/controllers/audio_controller.ex deleted file mode 100644 index 4f693655d..000000000 --- a/lib/screens_web/controllers/audio_controller.ex +++ /dev/null @@ -1,78 +0,0 @@ -defmodule ScreensWeb.AudioController do - use ScreensWeb, :controller - require Logger - alias Phoenix.View - alias Screens.Config.Cache - - @fallback_audio_path "assets/static/audio/readout_fallback.mp3" - - plug(:check_config) - - defp check_config(conn, _) do - if Cache.ok?() do - conn - else - conn - |> send_audio({:file, @fallback_audio_path}, disposition: :attachment) - |> halt() - end - end - - defp screen_exists?(screen_id) do - not is_nil(Cache.screen(screen_id)) - end - - def show(conn, %{"id" => screen_id} = params) do - disposition = - params - |> Map.get("disposition") - |> disposition_atom() - - is_screen = ScreensWeb.UserAgent.screen_conn?(conn, screen_id) - - _ = Screens.LogScreenData.log_audio_request(screen_id, is_screen) - - with true <- screen_exists?(screen_id), - %{success: true} = data <- - Screens.ScreenData.by_screen_id(screen_id, is_screen, check_disabled: true), - template_assigns <- Screens.Audio.from_api_data(data, screen_id), - ssml <- render_ssml(template_assigns), - {:ok, audio_data} <- - Screens.Audio.synthesize(ssml, screen_id: screen_id, is_screen: is_screen) do - send_audio(conn, {:binary, audio_data}, disposition) - else - _ -> send_fallback_audio(conn, is_screen, screen_id, disposition) - end - end - - def debug(conn, %{"id" => screen_id}) do - with true <- screen_exists?(screen_id), - %{success: true} = data <- Screens.ScreenData.by_screen_id(screen_id, false), - template_assigns <- Screens.Audio.from_api_data(data, screen_id), - ssml <- render_ssml(template_assigns) do - text(conn, ssml) - else - _ -> text(conn, "Failed to load data") - end - end - - defp render_ssml(template_assigns) do - View.render_to_string(ScreensWeb.AudioView, "index.ssml", template_assigns) - end - - defp send_fallback_audio(conn, is_screen, screen_id, disposition) do - _ = - if is_screen do - Logger.info("fallback_audio #{screen_id}") - end - - send_audio(conn, {:file, @fallback_audio_path}, disposition) - end - - defp send_audio(conn, kind, disposition) do - send_download(conn, kind, filename: "readout.mp3", disposition: disposition) - end - - defp disposition_atom("inline"), do: :inline - defp disposition_atom(_), do: :attachment -end diff --git a/lib/screens_web/controllers/screen_api_controller.ex b/lib/screens_web/controllers/screen_api_controller.ex deleted file mode 100644 index fb3365711..000000000 --- a/lib/screens_web/controllers/screen_api_controller.ex +++ /dev/null @@ -1,63 +0,0 @@ -defmodule ScreensWeb.ScreenApiController do - use ScreensWeb, :controller - require Logger - alias Screens.Config.Cache - - plug(:check_config) - - defp check_config(conn, _) do - if Cache.ok?() do - conn - else - conn - |> not_found_response() - |> halt() - end - end - - def show(conn, %{"id" => screen_id, "last_refresh" => _last_refresh, "datetime" => datetime}) do - if nonexistent_screen?(screen_id) do - not_found_response(conn) - else - data = Screens.ScreenData.by_screen_id_with_datetime(screen_id, datetime) - - json(conn, data) - end - end - - def show(conn, %{"id" => screen_id, "last_refresh" => last_refresh} = params) do - is_screen = ScreensWeb.UserAgent.screen_conn?(conn, screen_id) - - _ = - Screens.LogScreenData.log_data_request( - screen_id, - last_refresh, - is_screen, - params - ) - - if nonexistent_screen?(screen_id) do - Screens.LogScreenData.log_api_response(:nonexistent, screen_id, last_refresh, is_screen) - - not_found_response(conn) - else - data = - Screens.ScreenData.by_screen_id(screen_id, is_screen, - check_disabled: true, - last_refresh: last_refresh - ) - - json(conn, data) - end - end - - defp nonexistent_screen?(screen_id) do - is_nil(Cache.screen(screen_id)) - end - - defp not_found_response(conn) do - conn - |> put_status(:not_found) - |> text("Not found") - end -end diff --git a/lib/screens_web/controllers/screen_controller.ex b/lib/screens_web/controllers/screen_controller.ex index ba0f359e5..c7f08640f 100644 --- a/lib/screens_web/controllers/screen_controller.ex +++ b/lib/screens_web/controllers/screen_controller.ex @@ -1,107 +1,7 @@ defmodule ScreensWeb.ScreenController do use ScreensWeb, :controller - require Logger - - alias Screens.Config.Cache - alias ScreensConfig.Screen - - @default_app_id :solari - @app_ids ~w[solari]a - @app_id_strings Enum.map(@app_ids, &Atom.to_string/1) - - plug(:body_class) - plug(:check_config) - plug(:environment_name) - plug(:last_refresh) - - defp check_config(conn, _) do - if Cache.ok?() do - conn - else - conn - |> render_not_found() - |> halt() - end - end - - defp last_refresh(conn, _, now \\ DateTime.utc_now()) do - timestamp = DateTime.to_iso8601(now) - assign(conn, :last_refresh, timestamp) - end - - defp environment_name(conn, _) do - environment_name = Application.get_env(:screens, :environment_name) - assign(conn, :environment_name, environment_name) - end - - defp body_class(conn, _) do - body_class = - case Map.get(conn.params, "scroll") do - "true" -> "scroll-enabled" - _ -> "scroll-disabled" - end - - assign(conn, :body_class, body_class) - end - - defp id_sort_fn(a, b) do - case {Integer.parse(a), Integer.parse(b)} do - {{m, ""}, {n, ""}} -> - m <= n - - _ -> - a <= b - end - end - - defp screen_ids(target_app_id) do - ids = Cache.screen_ids(&match?({_screen_id, %Screen{app_id: ^target_app_id}}, &1)) - - Enum.sort(ids, &id_sort_fn/2) - end - - def index(conn, %{"id" => app_id}) - when app_id in @app_id_strings do - app_id = String.to_existing_atom(app_id) - - conn - |> assign(:app_id, app_id) - |> assign(:screen_ids, screen_ids(app_id)) - |> render("index_multi.html") - end - - def index(conn, %{"id" => screen_id} = params) do - is_screen = ScreensWeb.UserAgent.screen_conn?(conn, screen_id) - - _ = Screens.LogScreenData.log_page_load(screen_id, is_screen) - - case Cache.screen(screen_id) do - %Screen{app_id: app_id} -> - conn - |> assign(:app_id, app_id) - |> assign(:sentry_dsn, if(params["disable_sentry"], do: nil, else: Sentry.get_dsn())) - |> assign(:is_real_screen, match?(%{"is_real_screen" => "true"}, params)) - |> assign(:requestor, params["requestor"]) - |> render("index.html") - - nil -> - render_not_found(conn) - end - end - - def index(conn, _params) do - render_not_found(conn) - end def show_image(conn, %{"filename" => filename}) do redirect(conn, external: Screens.Image.get_s3_url(filename)) end - - defp render_not_found(conn) do - conn - |> assign(:app_id, @default_app_id) - |> put_status(:not_found) - |> put_view(ScreensWeb.ErrorView) - |> render("404.html") - end end diff --git a/lib/screens_web/controllers/v2/screen_controller.ex b/lib/screens_web/controllers/v2/screen_controller.ex index 68b4f848a..c4bf43a4a 100644 --- a/lib/screens_web/controllers/v2/screen_controller.ex +++ b/lib/screens_web/controllers/v2/screen_controller.ex @@ -6,7 +6,7 @@ defmodule ScreensWeb.V2.ScreenController do alias ScreensConfig.Screen @default_app_id :bus_eink_v2 - @recognized_app_ids ~w[bus_eink_v2 bus_shelter_v2 busway_v2 dup_v2 gl_eink_v2 solari_large_v2 pre_fare_v2 elevator_v2]a + @recognized_app_ids ~w[bus_eink_v2 bus_shelter_v2 busway_v2 dup_v2 gl_eink_v2 pre_fare_v2 elevator_v2]a @app_id_strings Enum.map(@recognized_app_ids, &Atom.to_string/1) plug(:check_config) diff --git a/lib/screens_web/router.ex b/lib/screens_web/router.ex index d27e5e78b..94fe8fa78 100644 --- a/lib/screens_web/router.ex +++ b/lib/screens_web/router.ex @@ -74,13 +74,6 @@ defmodule ScreensWeb.Router do delete "/image/:filename", AdminApiController, :delete_image end - scope "/screen", ScreensWeb do - pipe_through [:redirect_prod_http, :browser] - - get "/:id", ScreenController, :index - get "/:id/:rotation_index", ScreenController, :index - end - scope "/v2", ScreensWeb.V2 do scope "/widget" do pipe_through [:redirect_prod_http, :browser_no_csrf] @@ -132,26 +125,6 @@ defmodule ScreensWeb.Router do get "/:filename", ScreenController, :show_image end - scope "/audit", ScreensWeb do - pipe_through [:redirect_prod_http, :browser] - - get "/:id", ScreenController, :index - end - - scope "/api/screen", ScreensWeb do - pipe_through [:redirect_prod_http, :api, :browser] - - get "/:id", ScreenApiController, :show - end - - scope "/audio", ScreensWeb do - pipe_through [:api, :browser] - - get "/:id/readout.mp3", AudioController, :show - - get "/:id/debug", AudioController, :debug - end - scope "/api", ScreensWeb do pipe_through [:redirect_prod_http, :api] diff --git a/lib/screens_web/templates/audio/README.md b/lib/screens_web/templates/audio/README.md deleted file mode 100644 index 5a4588824..000000000 --- a/lib/screens_web/templates/audio/README.md +++ /dev/null @@ -1,30 +0,0 @@ -# Developing SSML Templates for Amazon Polly - -While tweaking or adding to these templates, keep in mind that whitespace and newlines are _*not*_ ignored by Polly's SSML parser and will add unwanted pauses. I suggest the following: - -- Temporarily disable your editor's setting to add a newline to the end of the file on save, or permanently disable it for `.ssml.eex` files if possible. -- Keep all non-rendering newlines within `<%= %>` or `<% %>` Elixir escape tags. -- Whenever you need to use a macro that uses the `do ... end` construct in a template, factor it out to a view function that returns a string or iodata, and call that function from the template. There is no way to avoid adding unnecessary newlines with inline macros in templates without making them unreadable. - - Bad (will result in unwanted pauses when synthesized): - - ```eex - <%= case @route_id do %> - <% "CR-" <> line_name -> %><%= line_name %> Commuter Rail train - <% "Blue" -> %>Blue Line train - <% "Red" -> %>Red Line train - <% "Mattapan" -> %>Mattapan Trolley - <% "Orange" -> %>Orange Line train - <% end %> - to <%= @destination %> - ``` - - Good: - - ```eex - <%= render_route_id @route_id %> to <%= destination %> - ``` - - **Note**: If this function's return value contains any tags, they _must_ be wrapped in a `{:safe, _}` tuple to prevent the template engine from escaping the brackets. See `ScreensWeb.AudioView.say_as_address` for an example. - -- Use `` and `

` to explicitly mark sentences and paragraphs. Use `` if you need to fine-tune a pause. diff --git a/lib/screens_web/templates/audio/_departure_pill_group.ssml.eex b/lib/screens_web/templates/audio/_departure_pill_group.ssml.eex deleted file mode 100644 index a57967bdf..000000000 --- a/lib/screens_web/templates/audio/_departure_pill_group.ssml.eex +++ /dev/null @@ -1,4 +0,0 @@ -<% - {pill, %{wayfinding: wayfinding, departure_groups: departure_groups}} = @pill_group -%>

-<%= render_pill_header(pill, wayfinding) %><%= render_departure_groups(departure_groups) %>

diff --git a/lib/screens_web/templates/audio/_header.ssml.eex b/lib/screens_web/templates/audio/_header.ssml.eex deleted file mode 100644 index 3a687b1a5..000000000 --- a/lib/screens_web/templates/audio/_header.ssml.eex +++ /dev/null @@ -1,2 +0,0 @@ -Upcoming trips at <%= @station_name %> -Crowding information is provided when available diff --git a/lib/screens_web/templates/audio/index.ssml.eex b/lib/screens_web/templates/audio/index.ssml.eex deleted file mode 100644 index 60739310c..000000000 --- a/lib/screens_web/templates/audio/index.ssml.eex +++ /dev/null @@ -1,15 +0,0 @@ - - -<%= if is_nil(@psa) or ({_, _, type} = @psa; type != :takeover) do %> -

-<%= render "_header.ssml", station_name: @station_name %>

-<%= render_many @departures_by_pill, ScreensWeb.AudioView, "_departure_pill_group.ssml", as: :pill_group %> -<%= render_psa @psa %> -<% else %> -<%= render_psa @psa %> -<% end %> -
diff --git a/lib/screens_web/views/audio_view.ex b/lib/screens_web/views/audio_view.ex deleted file mode 100644 index 69a9accb0..000000000 --- a/lib/screens_web/views/audio_view.ex +++ /dev/null @@ -1,231 +0,0 @@ -defmodule ScreensWeb.AudioView do - use ScreensWeb, :view - - @spec render_pill_header(atom(), String.t() | nil) :: Phoenix.HTML.safe() - defp render_pill_header(pill, wayfinding) do - ~E|<%= render_pill(pill) %> trips<%= render_wayfinding(wayfinding) %>| - end - - @spec render_pill(atom()) :: Phoenix.HTML.safe() - defp render_pill(:blue), do: ~E"Blue Line" - defp render_pill(:bus), do: ~E"Bus" - defp render_pill(:cr), do: ~E"Commuter Rail" - defp render_pill(:mattapan), do: ~E"Mattapan Line" - defp render_pill(:orange), do: ~E"Orange Line" - defp render_pill(:red), do: ~E"Red Line" - defp render_pill(:silver), do: ~E"Silver Line" - defp render_pill(:green), do: ~E"Green Line" - - @spec render_pill_mode(atom(), non_neg_integer()) :: Phoenix.HTML.safe() - defp render_pill_mode(pill, 1) when pill in ~w[blue orange red green cr]a, do: ~E"train" - defp render_pill_mode(pill, _) when pill in ~w[blue orange red green cr]a, do: ~E"trains" - defp render_pill_mode(pill, 1) when pill in ~w[bus silver]a, do: ~E"bus" - defp render_pill_mode(pill, _) when pill in ~w[bus silver]a, do: ~E"buses" - defp render_pill_mode(:mattapan, 1), do: ~E"trolley" - defp render_pill_mode(:mattapan, _), do: ~E"trolleys" - - defp render_route_descriptor({route, route_id, destination}) do - if route_id in ~w[Blue Red Mattapan Orange Green-B Green-C Green-D Green-E] or - String.starts_with?(route_id, "CR") do - ~E|<%= render_route_id(route_id) %> to <%= destination %>| - else - ~E|<%= render_route(route) %> to <%= destination %>| - end - end - - @spec render_route_id(String.t()) :: Phoenix.HTML.safe() - defp render_route_id("CR-" <> line_name) do - ~E|<%= line_name %> Line train| - end - - defp render_route_id(color) when color in ~w[Blue Red Orange] do - ~E|<%= color %> Line train| - end - - defp render_route_id("Mattapan"), do: ~E"Mattapan Trolley" - - defp render_route_id("Green-" <> branch) do - ~E|Green Line <%= branch %> branch| - end - - @spec render_route(String.t()) :: Phoenix.HTML.safe() - defp render_route("SL" <> route_number) do - ~E|Silver Line route <%= say_as_address(route_number) %>| - end - - defp render_route(route) do - route_number = - if String.contains?(route, "/") do - route - |> String.split("/") - |> Enum.map(&say_as_address/1) - else - say_as_address(route) - end - - ~E|Route <%= route_number %>| - end - - defp render_departure_groups([]) do - ~E|No departures currently available| - end - - defp render_departure_groups(departure_groups) do - departure_groups - |> Enum.map(&render_departure_group/1) - |> Enum.intersperse(~E||) - end - - defp render_departure_group( - {route_descriptor, - %{ - times: time_groups, - alerts: alerts, - wayfinding: wayfinding, - track_number: track_number - }} - ) do - [first | rest] = time_groups - - first_rendered = - render_first_departure_time_group(route_descriptor, track_number, wayfinding, first) - - rest_rendered = Enum.map(rest, &render_departure_time_group_with_prefix/1) - - alerts_rendered = render_alerts(alerts, route_descriptor) - - ~E|<%= first_rendered %><%= rest_rendered %><%= alerts_rendered %>| - end - - defp render_first_departure_time_group(route_descriptor, track_number, wayfinding, time_group) do - route_destination = render_route_descriptor(route_descriptor) - - track_number_rendered = render_track_number(track_number) - - wayfinding_rendered = render_wayfinding(wayfinding) - - times = render_departure_time_group(time_group) - - ~E|<%= route_destination %><%= track_number_rendered %><%= wayfinding_rendered %><%= times %>| - end - - defp render_departure_time_group_with_prefix(%{pill: pill, type: type, values: values}) do - number = length(values) - - prefix = - case type do - :timestamp -> ~E|Later <%= render_pill_mode(pill, number) %>| - _ -> ~E|Next <%= render_pill_mode(pill, number) %>| - end - - time_group_rendered = render_departure_time_group(%{type: type, values: values}) - - ~E|<%= prefix %> <%= time_group_rendered %>| - end - - defp render_departure_time_group(%{type: type, values: values}) do - preposition = preposition_for(type) - - times = render_time_representations(type, values) - - ~E| <%= preposition %><%= times %>| - end - - @spec preposition_for(atom()) :: Phoenix.HTML.safe() - defp preposition_for(:text), do: ~E"" - defp preposition_for(:minutes), do: ~E"in " - defp preposition_for(:timestamp), do: ~E"at " - - @spec render_time_representations(atom(), [any()]) :: [Phoenix.HTML.safe()] - defp render_time_representations(type, values) do - values - |> Enum.map(&render_time_representation_with_crowding(%{type: type, value: &1})) - |> oxford_comma_intersperse() - end - - defp render_time_representation_with_crowding(%{type: type, value: {time_value, crowding_level}}) do - time_rendered = render_time_representation(type, time_value) - crowding_rendered = render_crowding_level(crowding_level) - - ~E|<%= time_rendered %><%= crowding_rendered %>| - end - - @spec render_time_representation(atom(), atom() | pos_integer() | String.t()) :: - Phoenix.HTML.safe() - defp render_time_representation(:text, :brd), do: ~E"is now boarding" - defp render_time_representation(:text, :arr), do: ~E"is now arriving" - - defp render_time_representation(:minutes, minutes) do - ~E|<%= minutes %> <%= pluralize_minutes(minutes) %>| - end - - defp render_time_representation(:timestamp, timestamp) do - ~E|<%= timestamp %>| - end - - @spec render_crowding_level(Screens.Departures.Departure.crowding_level()) :: - Phoenix.HTML.safe() - defp render_crowding_level(1), do: ~E" (currently not crowded)" - defp render_crowding_level(2), do: ~E" (currently has some crowding)" - defp render_crowding_level(3), do: ~E" (currently crowded)" - defp render_crowding_level(nil), do: ~E"" - - @spec render_alerts([atom()], Screens.Audio.departure_group_key()) :: [Phoenix.HTML.safe()] - defp render_alerts(alerts, route_descriptor) do - Enum.map(alerts, &render_alert(&1, route_descriptor)) - end - - defp render_alert(:delay, route_descriptor) do - ~E|There are delays on <%= render_route_descriptor(route_descriptor) %>| - end - - defp render_alert(_, _), do: ~E"" - - defp render_wayfinding(nil), do: ~E"" - defp render_wayfinding(wayfinding), do: ~E| from <%= wayfinding %>| - - defp render_track_number(nil), do: ~E"" - defp render_track_number(track_number), do: ~E| on track <%= track_number %>| - - @spec render_psa({:plaintext | :ssml, String.t(), :takeover | :end} | nil) :: - Phoenix.HTML.safe() - defp render_psa(nil), do: ~E"" - - defp render_psa({:plaintext, text, _}) do - ~E|

<%= text %>

| - end - - defp render_psa({:ssml, ssml, _}) do - raw(ssml) - end - - @spec say_as_address(Phoenix.HTML.unsafe()) :: Phoenix.HTML.safe() - defp say_as_address(text) do - ~E|<%= text %>| - end - - defp pluralize_minutes(1), do: "minute" - defp pluralize_minutes(_), do: "minutes" - - defp oxford_comma_intersperse(list), do: oxford_comma_intersperse(list, :start) - - defp oxford_comma_intersperse([], _state) do - [] - end - - defp oxford_comma_intersperse([el], _state) do - [el] - end - - defp oxford_comma_intersperse([el1, el2], :start) do - [el1, ~E" and ", el2] - end - - defp oxford_comma_intersperse([el1, el2], :recurse) do - [el1, ~E", and ", el2] - end - - defp oxford_comma_intersperse([first | rest], _state) do - [first, ~E", " | oxford_comma_intersperse(rest, :recurse)] - end -end diff --git a/lib/screens_web/views/screen_view.ex b/lib/screens_web/views/screen_view.ex deleted file mode 100644 index f418c39a8..000000000 --- a/lib/screens_web/views/screen_view.ex +++ /dev/null @@ -1,3 +0,0 @@ -defmodule ScreensWeb.ScreenView do - use ScreensWeb, :view -end diff --git a/test/fixtures/config.json b/test/fixtures/config.json index 7f2381b2f..76192bf24 100644 --- a/test/fixtures/config.json +++ b/test/fixtures/config.json @@ -3,1719 +3,6 @@ "disabled_modes": ["ferry", "light_rail"] }, "screens": { - "311": { - "app_id": "solari", - "app_params": { - "audio_psa": null, - "overhead": false, - "psa_config": { - "default_list": { - "paths": ["solari-feedback.png"], - "type": "slide_in" - }, - "scheduled_overrides": [] - }, - "section_headers": "normal", - "sections": [ - { - "arrow": "w", - "audio": { - "wayfinding": "Upper Busway" - }, - "headway": { - "headsigns": [], - "headway_id": null, - "sign_ids": [] - }, - "layout": { - "opts": { - "max_minutes": "infinity", - "num_rows": 10, - "paged": true, - "routes": { - "action": "exclude", - "route_list": [ - { - "direction_id": 0, - "route": "90" - }, - { - "direction_id": 1, - "route": "90" - } - ] - }, - "visible_rows": 6 - }, - "type": "upcoming" - }, - "name": "Upper Busway", - "pill": "bus", - "query": { - "opts": { - "include_schedules": false - }, - "params": { - "direction_id": "both", - "route_ids": [], - "stop_ids": [ - "29001", - "29002", - "29003", - "29004", - "29005", - "29006" - ] - } - } - }, - { - "arrow": "sw", - "audio": { - "wayfinding": "Lower Busway" - }, - "headway": { - "headsigns": [], - "headway_id": null, - "sign_ids": [] - }, - "layout": { - "opts": { - "max_minutes": "infinity", - "num_rows": 12, - "paged": true, - "routes": { - "action": "exclude", - "route_list": [] - }, - "visible_rows": 8 - }, - "type": "upcoming" - }, - "name": "Lower Busway", - "pill": "bus", - "query": { - "opts": { - "include_schedules": false - }, - "params": { - "direction_id": "both", - "route_ids": [], - "stop_ids": [ - "29007", - "29008", - "29009", - "29010", - "29011", - "29012", - "29013", - "29014" - ] - } - } - } - ], - "station_name": "Sullivan Square" - }, - "device_id": "SOL11", - "disabled": false, - "name": "Sullivan Square SOL11", - "refresh_if_loaded_before": "2020-10-13T16:44:59.406651Z", - "tags": [], - "vendor": "solari" - }, - "304": { - "app_id": "solari", - "app_params": { - "audio_psa": null, - "overhead": true, - "psa_config": { - "default_list": { - "paths": ["solari-feedback.png"], - "type": "slide_in" - }, - "scheduled_overrides": [] - }, - "section_headers": "normal", - "sections": [ - { - "arrow": null, - "audio": { - "wayfinding": "Platform C" - }, - "headway": { - "headsigns": [], - "headway_id": null, - "sign_ids": [] - }, - "layout": { - "opts": { - "max_minutes": "infinity", - "num_rows": 8, - "paged": false, - "routes": { - "action": "include", - "route_list": [ - { - "direction_id": 1, - "route": "14" - }, - { - "direction_id": 0, - "route": "41" - }, - { - "direction_id": 0, - "route": "42" - }, - { - "direction_id": 0, - "route": "66" - } - ] - }, - "visible_rows": 1 - }, - "type": "upcoming" - }, - "name": "Platform C", - "pill": "bus", - "query": { - "opts": { - "include_schedules": false - }, - "params": { - "direction_id": "both", - "route_ids": [], - "stop_ids": ["place-dudly"] - } - } - } - ], - "station_name": "Nubian" - }, - "device_id": "SOL04", - "disabled": false, - "name": "Nubian Platform C SOL04", - "refresh_if_loaded_before": "2020-10-13T16:44:59.406651Z", - "tags": [], - "vendor": "solari" - }, - "307": { - "app_id": "solari", - "app_params": { - "audio_psa": null, - "overhead": false, - "psa_config": { - "default_list": { - "paths": ["solari-feedback.png"], - "type": "slide_in" - }, - "scheduled_overrides": [] - }, - "section_headers": "normal", - "sections": [ - { - "arrow": "e", - "audio": { - "wayfinding": "Upper Busway" - }, - "headway": { - "headsigns": [], - "headway_id": null, - "sign_ids": [] - }, - "layout": { - "opts": { - "max_minutes": "infinity", - "num_rows": 14, - "paged": true, - "routes": { - "action": "exclude", - "route_list": [ - { - "direction_id": 1, - "route": "74" - }, - { - "direction_id": 1, - "route": "75" - }, - { - "direction_id": 1, - "route": "77" - }, - { - "direction_id": 1, - "route": "78" - }, - { - "direction_id": 1, - "route": "96" - } - ] - }, - "visible_rows": 10 - }, - "type": "upcoming" - }, - "name": "Upper Busway", - "pill": "bus", - "query": { - "opts": { - "include_schedules": false - }, - "params": { - "direction_id": "both", - "route_ids": [], - "stop_ids": ["20761", "20762"] - } - } - }, - { - "arrow": "e", - "audio": { - "wayfinding": "Lower Busway" - }, - "headway": { - "headsigns": [], - "headway_id": null, - "sign_ids": [] - }, - "layout": { - "opts": { - "max_minutes": "infinity", - "num_rows": 2, - "paged": false, - "routes": { - "action": "exclude", - "route_list": [ - { - "direction_id": 1, - "route": "74" - }, - { - "direction_id": 1, - "route": "75" - }, - { - "direction_id": 1, - "route": "77" - }, - { - "direction_id": 1, - "route": "78" - }, - { - "direction_id": 1, - "route": "96" - } - ] - }, - "visible_rows": 1 - }, - "type": "upcoming" - }, - "name": "Lower Busway", - "pill": "bus", - "query": { - "opts": { - "include_schedules": false - }, - "params": { - "direction_id": "both", - "route_ids": [], - "stop_ids": ["2076"] - } - } - } - ], - "station_name": "Harvard" - }, - "device_id": "SOL07", - "disabled": false, - "name": "Harvard SOL07", - "refresh_if_loaded_before": "2020-10-13T16:44:59.406651Z", - "tags": [], - "vendor": "solari" - }, - "407": { - "app_id": "dup", - "app_params": { - "primary": { - "header": "Long Wharf South", - "sections": [ - { - "pill": "ferry", - "route_ids": [], - "stop_ids": ["Boat-Long-South"] - } - ] - }, - "secondary": { - "header": "", - "sections": [] - } - }, - "device_id": "TEST", - "disabled": false, - "name": "", - "refresh_if_loaded_before": null, - "tags": [], - "vendor": "outfront" - }, - "309": { - "app_id": "solari", - "app_params": { - "audio_psa": null, - "overhead": false, - "psa_config": { - "default_list": { - "paths": ["solari-feedback.png"], - "type": "slide_in" - }, - "scheduled_overrides": [] - }, - "section_headers": "none", - "sections": [ - { - "arrow": "e", - "audio": { - "wayfinding": null - }, - "headway": { - "headsigns": [], - "headway_id": null, - "sign_ids": [] - }, - "layout": { - "opts": { - "routes": { - "action": "exclude", - "route_list": [] - } - }, - "type": "bidirectional" - }, - "name": "Blue Line", - "pill": "blue", - "query": { - "opts": { - "include_schedules": false - }, - "params": { - "direction_id": "both", - "route_ids": [], - "stop_ids": ["70045", "70046"] - } - } - }, - { - "arrow": "w", - "audio": { - "wayfinding": null - }, - "headway": { - "headsigns": [], - "headway_id": null, - "sign_ids": [] - }, - "layout": { - "opts": { - "max_minutes": "infinity", - "num_rows": 14, - "paged": true, - "routes": { - "action": "exclude", - "route_list": [] - }, - "visible_rows": 10 - }, - "type": "upcoming" - }, - "name": "Bus", - "pill": "bus", - "query": { - "opts": { - "include_schedules": false - }, - "params": { - "direction_id": "both", - "route_ids": [], - "stop_ids": ["5740", "57400"] - } - } - } - ], - "station_name": "Maverick" - }, - "device_id": "SOL09", - "disabled": false, - "name": "Maverick SOL09", - "refresh_if_loaded_before": "2020-10-13T16:44:59.406651Z", - "tags": [], - "vendor": "solari" - }, - "314": { - "app_id": "solari", - "app_params": { - "audio_psa": null, - "overhead": false, - "psa_config": { - "default_list": { - "paths": ["solari-feedback.png"], - "type": "slide_in" - }, - "scheduled_overrides": [] - }, - "section_headers": "normal", - "sections": [ - { - "arrow": "nw", - "audio": { - "wayfinding": "Back Bay" - }, - "headway": { - "headsigns": [], - "headway_id": null, - "sign_ids": [] - }, - "layout": { - "opts": { - "max_minutes": 120, - "num_rows": 8, - "paged": false, - "routes": { - "action": "exclude", - "route_list": [] - }, - "visible_rows": 1 - }, - "type": "upcoming" - }, - "name": "Commuter Rail (Back Bay)", - "pill": "cr", - "query": { - "opts": { - "include_schedules": true - }, - "params": { - "direction_id": 0, - "route_ids": [], - "stop_ids": ["Back Bay"] - } - } - }, - { - "arrow": null, - "audio": { - "wayfinding": null - }, - "headway": { - "headsigns": [], - "headway_id": null, - "sign_ids": [] - }, - "layout": { - "opts": { - "max_minutes": "infinity", - "num_rows": 2, - "paged": false, - "routes": { - "action": "exclude", - "route_list": [] - }, - "visible_rows": 1 - }, - "type": "upcoming" - }, - "name": "Bus", - "pill": "bus", - "query": { - "opts": { - "include_schedules": false - }, - "params": { - "direction_id": "both", - "route_ids": [], - "stop_ids": ["145", "1241", "9983"] - } - } - } - ], - "station_name": "10 Park Plaza" - }, - "device_id": "C3MS02", - "disabled": false, - "name": "10 Park Plaza Charles SOL14", - "refresh_if_loaded_before": "2020-10-13T16:44:59.406651Z", - "tags": [], - "vendor": "c3ms" - }, - "310": { - "app_id": "solari", - "app_params": { - "audio_psa": null, - "overhead": false, - "psa_config": { - "default_list": { - "paths": ["solari-feedback.png"], - "type": "slide_in" - }, - "scheduled_overrides": [] - }, - "section_headers": "normal", - "sections": [ - { - "arrow": "e", - "audio": { - "wayfinding": "Lower Busway" - }, - "headway": { - "headsigns": [], - "headway_id": null, - "sign_ids": [] - }, - "layout": { - "opts": { - "max_minutes": "infinity", - "num_rows": 14, - "paged": true, - "routes": { - "action": "exclude", - "route_list": [] - }, - "visible_rows": 10 - }, - "type": "upcoming" - }, - "name": "Lower Busway", - "pill": "bus", - "query": { - "opts": { - "include_schedules": false - }, - "params": { - "direction_id": "both", - "route_ids": [], - "stop_ids": ["17862", "17863"] - } - } - }, - { - "arrow": "e", - "audio": { - "wayfinding": null - }, - "headway": { - "headsigns": [], - "headway_id": null, - "sign_ids": [] - }, - "layout": { - "opts": { - "max_minutes": "infinity", - "num_rows": 2, - "paged": false, - "routes": { - "action": "exclude", - "route_list": [] - }, - "visible_rows": 1 - }, - "type": "upcoming" - }, - "name": "Commuter Rail", - "pill": "cr", - "query": { - "opts": { - "include_schedules": true - }, - "params": { - "direction_id": "both", - "route_ids": [], - "stop_ids": ["Ruggles"] - } - } - } - ], - "station_name": "Ruggles" - }, - "device_id": "SOL10", - "disabled": false, - "name": "Ruggles SOL10", - "refresh_if_loaded_before": "2020-10-13T16:44:59.406651Z", - "tags": [], - "vendor": "solari" - }, - "405": { - "app_id": "dup", - "app_params": { - "primary": { - "header": "Kenmore", - "sections": [ - { - "pill": "green", - "route_ids": ["Green-B", "Green-C", "Green-D"], - "stop_ids": ["place-kencl"] - } - ] - }, - "secondary": { - "header": "", - "sections": [] - } - }, - "device_id": "TEST", - "disabled": false, - "name": "", - "refresh_if_loaded_before": null, - "tags": [], - "vendor": "outfront" - }, - "403": { - "app_id": "dup", - "app_params": { - "primary": { - "header": "Haymarket", - "sections": [ - { - "pill": "green", - "route_ids": ["Green-B", "Green-C", "Green-D", "Green-E"], - "stop_ids": ["place-haecl"] - }, - { - "pill": "orange", - "route_ids": ["Orange"], - "stop_ids": ["place-haecl"] - } - ] - }, - "secondary": { - "header": "", - "sections": [] - } - }, - "device_id": "TEST", - "disabled": false, - "name": "", - "refresh_if_loaded_before": null, - "tags": [], - "vendor": "outfront" - }, - "302": { - "app_id": "solari", - "app_params": { - "audio_psa": null, - "overhead": false, - "psa_config": { - "default_list": { - "paths": ["solari-feedback.png"], - "type": "slide_in" - }, - "scheduled_overrides": [] - }, - "section_headers": "none", - "sections": [ - { - "arrow": null, - "audio": { - "wayfinding": null - }, - "headway": { - "headsigns": ["Alewife", "Southbound"], - "headway_id": "red_trunk", - "sign_ids": ["central_northbound", "central_southbound"] - }, - "layout": { - "opts": { - "routes": { - "action": "exclude", - "route_list": [] - } - }, - "type": "bidirectional" - }, - "name": "Red Line", - "pill": "red", - "query": { - "opts": { - "include_schedules": false - }, - "params": { - "direction_id": "both", - "route_ids": [], - "stop_ids": ["70069", "70070"] - } - } - }, - { - "arrow": null, - "audio": { - "wayfinding": null - }, - "headway": { - "headsigns": [], - "headway_id": null, - "sign_ids": [] - }, - "layout": { - "opts": { - "max_minutes": "infinity", - "num_rows": 14, - "paged": true, - "routes": { - "action": "exclude", - "route_list": [ - { - "direction_id": 1, - "route": "70" - }, - { - "direction_id": 1, - "route": "64" - } - ] - }, - "visible_rows": 10 - }, - "type": "upcoming" - }, - "name": "Bus", - "pill": "bus", - "query": { - "opts": { - "include_schedules": false - }, - "params": { - "direction_id": "both", - "route_ids": [], - "stop_ids": ["72", "102", "1060", "1123"] - } - } - } - ], - "station_name": "Central" - }, - "device_id": "SOL02", - "disabled": false, - "name": "Central SOL02", - "refresh_if_loaded_before": "2020-10-13T16:44:59.406651Z", - "tags": [], - "vendor": "solari" - }, - "303": { - "app_id": "solari", - "app_params": { - "audio_psa": null, - "overhead": true, - "psa_config": { - "default_list": { - "paths": ["solari-feedback.png"], - "type": "slide_in" - }, - "scheduled_overrides": [] - }, - "section_headers": "normal", - "sections": [ - { - "arrow": null, - "audio": { - "wayfinding": "Platform A" - }, - "headway": { - "headsigns": [], - "headway_id": null, - "sign_ids": [] - }, - "layout": { - "opts": { - "max_minutes": "infinity", - "num_rows": 8, - "paged": false, - "routes": { - "action": "exclude", - "route_list": [] - }, - "visible_rows": 1 - }, - "type": "upcoming" - }, - "name": "Platform A", - "pill": "bus", - "query": { - "opts": { - "include_schedules": false - }, - "params": { - "direction_id": 1, - "route_ids": ["15", "23", "28", "44", "45"], - "stop_ids": ["place-dudly"] - } - } - } - ], - "station_name": "Nubian" - }, - "device_id": "SOL03", - "disabled": false, - "name": "Nubian Platform A SOL03", - "refresh_if_loaded_before": "2020-10-13T16:44:59.406651Z", - "tags": [], - "vendor": "solari" - }, - "308": { - "app_id": "solari", - "app_params": { - "audio_psa": null, - "overhead": false, - "psa_config": { - "default_list": { - "paths": ["solari-feedback.png"], - "type": "slide_in" - }, - "scheduled_overrides": [] - }, - "section_headers": "normal", - "sections": [ - { - "arrow": "se", - "audio": { - "wayfinding": "Busway" - }, - "headway": { - "headsigns": [], - "headway_id": null, - "sign_ids": [] - }, - "layout": { - "opts": { - "max_minutes": "infinity", - "num_rows": 12, - "paged": true, - "routes": { - "action": "exclude", - "route_list": [ - { - "direction_id": 1, - "route": "92" - }, - { - "direction_id": 1, - "route": "93" - } - ] - }, - "visible_rows": 8 - }, - "type": "upcoming" - }, - "name": "Busway", - "pill": "bus", - "query": { - "opts": { - "include_schedules": false - }, - "params": { - "direction_id": "both", - "route_ids": [], - "stop_ids": ["8310"] - } - } - }, - { - "arrow": "n", - "audio": { - "wayfinding": "Congress Street at Haymarket Station" - }, - "headway": { - "headsigns": [], - "headway_id": null, - "sign_ids": [] - }, - "layout": { - "opts": { - "max_minutes": "infinity", - "num_rows": 8, - "paged": true, - "routes": { - "action": "exclude", - "route_list": [ - { - "direction_id": 0, - "route": "4" - } - ] - }, - "visible_rows": 4 - }, - "type": "upcoming" - }, - "name": "Congress St @ Haymarket Sta", - "pill": "bus", - "query": { - "opts": { - "include_schedules": false - }, - "params": { - "direction_id": "both", - "route_ids": [], - "stop_ids": ["117"] - } - } - } - ], - "station_name": "Haymarket" - }, - "device_id": "SOL08", - "disabled": false, - "name": "Haymarket SOL08", - "refresh_if_loaded_before": "2020-10-13T16:44:59.406651Z", - "tags": [], - "vendor": "solari" - }, - "dup-bus-headsigns": { - "app_id": "dup", - "app_params": { - "primary": { - "header": "", - "sections": [] - }, - "secondary": { - "header": "", - "sections": [] - } - }, - "device_id": "TEST", - "disabled": false, - "name": "", - "refresh_if_loaded_before": null, - "tags": [], - "vendor": "outfront" - }, - "306": { - "app_id": "solari", - "app_params": { - "audio_psa": null, - "overhead": false, - "psa_config": { - "default_list": { - "paths": ["solari-feedback.png"], - "type": "slide_in" - }, - "scheduled_overrides": [] - }, - "section_headers": "none", - "sections": [ - { - "arrow": "w", - "audio": { - "wayfinding": null - }, - "headway": { - "headsigns": [], - "headway_id": null, - "sign_ids": [] - }, - "layout": { - "opts": { - "routes": { - "action": "exclude", - "route_list": [] - } - }, - "type": "bidirectional" - }, - "name": "Commuter Rail", - "pill": "cr", - "query": { - "opts": { - "include_schedules": true - }, - "params": { - "direction_id": "both", - "route_ids": [], - "stop_ids": ["Forest Hills"] - } - } - }, - { - "arrow": "e", - "audio": { - "wayfinding": "Upper Busway" - }, - "headway": { - "headsigns": [], - "headway_id": null, - "sign_ids": [] - }, - "layout": { - "opts": { - "max_minutes": "infinity", - "num_rows": 14, - "paged": true, - "routes": { - "action": "exclude", - "route_list": [] - }, - "visible_rows": 10 - }, - "type": "upcoming" - }, - "name": "Upper Busway", - "pill": "bus", - "query": { - "opts": { - "include_schedules": false - }, - "params": { - "direction_id": "both", - "route_ids": [], - "stop_ids": ["10642"] - } - } - } - ], - "station_name": "Forest Hills" - }, - "device_id": "SOL06", - "disabled": false, - "name": "Forest Hills Upper Busway SOL06", - "refresh_if_loaded_before": "2020-10-13T16:44:59.406651Z", - "tags": [], - "vendor": "solari" - }, - "406": { - "app_id": "dup", - "app_params": { - "primary": { - "header": "Tufts Medical Ctr", - "sections": [ - { - "pill": "silver", - "route_ids": ["749", "751"], - "stop_ids": ["49002", "6565"] - } - ] - }, - "secondary": { - "header": "", - "sections": [] - } - }, - "device_id": "TEST", - "disabled": false, - "name": "", - "refresh_if_loaded_before": null, - "tags": [], - "vendor": "outfront" - }, - "313": { - "app_id": "solari", - "app_params": { - "audio_psa": null, - "overhead": false, - "psa_config": { - "default_list": { - "paths": ["solari-feedback.png"], - "type": "slide_in" - }, - "scheduled_overrides": [] - }, - "section_headers": "normal", - "sections": [ - { - "arrow": "w", - "audio": { - "wayfinding": "South Station" - }, - "headway": { - "headsigns": [], - "headway_id": null, - "sign_ids": [] - }, - "layout": { - "opts": { - "max_minutes": 120, - "num_rows": 7, - "paged": true, - "routes": { - "action": "exclude", - "route_list": [] - }, - "visible_rows": 3 - }, - "type": "upcoming" - }, - "name": "Commuter Rail (South Station)", - "pill": "cr", - "query": { - "opts": { - "include_schedules": true - }, - "params": { - "direction_id": "both", - "route_ids": [], - "stop_ids": ["South Station"] - } - } - }, - { - "arrow": "nw", - "audio": { - "wayfinding": "Tufts Medical Center" - }, - "headway": { - "headsigns": [], - "headway_id": null, - "sign_ids": [] - }, - "layout": { - "opts": { - "routes": { - "action": "exclude", - "route_list": [] - } - }, - "type": "bidirectional" - }, - "name": "Tufts Medical Center", - "pill": "orange", - "query": { - "opts": { - "include_schedules": false - }, - "params": { - "direction_id": "both", - "route_ids": [], - "stop_ids": ["70016", "70017"] - } - } - }, - { - "arrow": null, - "audio": { - "wayfinding": "Tufts Medical Center" - }, - "headway": { - "headsigns": [], - "headway_id": null, - "sign_ids": [] - }, - "layout": { - "opts": { - "routes": { - "action": "exclude", - "route_list": [ - { - "direction_id": 0, - "route": "11" - }, - { - "direction_id": 1, - "route": "11" - } - ] - } - }, - "type": "bidirectional" - }, - "name": null, - "pill": "silver", - "query": { - "opts": { - "include_schedules": false - }, - "params": { - "direction_id": "both", - "route_ids": [], - "stop_ids": ["49002", "6565"] - } - } - }, - { - "arrow": "e", - "audio": { - "wayfinding": "Stuart Street at Charles Street South" - }, - "headway": { - "headsigns": [], - "headway_id": null, - "sign_ids": [] - }, - "layout": { - "opts": { - "max_minutes": "infinity", - "num_rows": 1, - "paged": false, - "routes": { - "action": "exclude", - "route_list": [] - }, - "visible_rows": 1 - }, - "type": "upcoming" - }, - "name": "Bus", - "pill": "bus", - "query": { - "opts": { - "include_schedules": false - }, - "params": { - "direction_id": "both", - "route_ids": [], - "stop_ids": ["9983"] - } - } - } - ], - "station_name": "10 Park Plaza" - }, - "device_id": "C3MS01", - "disabled": false, - "name": "10 Park Plaza Stuart SOL13", - "refresh_if_loaded_before": "2020-10-13T16:44:59.406651Z", - "tags": [], - "vendor": "c3ms" - }, - "404": { - "app_id": "dup", - "app_params": { - "primary": { - "header": "Haymarket", - "sections": [ - { - "pill": "bus", - "route_ids": [], - "stop_ids": ["8310"] - } - ] - }, - "secondary": { - "header": "", - "sections": [] - } - }, - "device_id": "TEST", - "disabled": false, - "name": "", - "refresh_if_loaded_before": null, - "tags": [], - "vendor": "outfront" - }, - "401": { - "app_id": "dup", - "app_params": { - "primary": { - "header": "Aquarium", - "sections": [ - { - "pill": "blue", - "route_ids": [], - "stop_ids": ["place-aqucl"] - } - ] - }, - "secondary": { - "header": "", - "sections": [] - }, - "override": [ - { - "type": "partial", - "alerts": [ - { - "color": "blue", - "content": { - "icon": "warning", - "text": ["No Blue Line service"] - } - } - ] - }, - { - "type": "fullscreen", - "header": null, - "pattern": "hatched", - "color": "blue", - "issue": { - "icon": "warning", - "text": ["No Blue Line service"] - }, - "remedy": { - "icon": "shuttle", - "text": ["Use shuttle bus"] - } - } - ] - }, - "device_id": "TEST", - "disabled": false, - "name": "", - "refresh_if_loaded_before": null, - "tags": [], - "vendor": "outfront" - }, - "301": { - "app_id": "solari", - "app_params": { - "audio_psa": null, - "overhead": false, - "psa_config": { - "default_list": { - "paths": ["solari-feedback.png"], - "type": "slide_in" - }, - "scheduled_overrides": [] - }, - "section_headers": "none", - "sections": [ - { - "arrow": "e", - "audio": { - "wayfinding": null - }, - "headway": { - "headsigns": [], - "headway_id": null, - "sign_ids": [] - }, - "layout": { - "opts": { - "max_minutes": "infinity", - "num_rows": 2, - "paged": false, - "routes": { - "action": "exclude", - "route_list": [] - }, - "visible_rows": 1 - }, - "type": "upcoming" - }, - "name": "Red Line", - "pill": "red", - "query": { - "opts": { - "include_schedules": false - }, - "params": { - "direction_id": "both", - "route_ids": [], - "stop_ids": ["70094"] - } - } - }, - { - "arrow": "e", - "audio": { - "wayfinding": null - }, - "headway": { - "headsigns": [], - "headway_id": null, - "sign_ids": [] - }, - "layout": { - "opts": { - "max_minutes": "infinity", - "num_rows": 1, - "paged": false, - "routes": { - "action": "exclude", - "route_list": [] - }, - "visible_rows": 1 - }, - "type": "upcoming" - }, - "name": "Mattapan Trolley", - "pill": "mattapan", - "query": { - "opts": { - "include_schedules": false - }, - "params": { - "direction_id": "both", - "route_ids": [], - "stop_ids": ["70261"] - } - } - }, - { - "arrow": "s", - "audio": { - "wayfinding": null - }, - "headway": { - "headsigns": [], - "headway_id": null, - "sign_ids": [] - }, - "layout": { - "opts": { - "max_minutes": "infinity", - "num_rows": 14, - "paged": true, - "routes": { - "action": "exclude", - "route_list": [] - }, - "visible_rows": 10 - }, - "type": "upcoming" - }, - "name": "Busway", - "pill": "bus", - "query": { - "opts": { - "include_schedules": false - }, - "params": { - "direction_id": "both", - "route_ids": [], - "stop_ids": ["334"] - } - } - } - ], - "station_name": "Ashmont" - }, - "device_id": "SOL01", - "disabled": false, - "name": "Ashmont SOL01", - "refresh_if_loaded_before": "2020-10-13T16:44:59.406651Z", - "tags": [], - "vendor": "solari" - }, - "312": { - "app_id": "solari", - "app_params": { - "audio_psa": null, - "overhead": false, - "psa_config": { - "default_list": { - "paths": ["solari-feedback.png"], - "type": "slide_in" - }, - "scheduled_overrides": [] - }, - "section_headers": "none", - "sections": [ - { - "arrow": "se", - "audio": { - "wayfinding": null - }, - "headway": { - "headsigns": [], - "headway_id": null, - "sign_ids": [] - }, - "layout": { - "opts": { - "max_minutes": "infinity", - "num_rows": 2, - "paged": false, - "routes": { - "action": "exclude", - "route_list": [] - }, - "visible_rows": 1 - }, - "type": "upcoming" - }, - "name": "Blue Line", - "pill": "blue", - "query": { - "opts": { - "include_schedules": false - }, - "params": { - "direction_id": "both", - "route_ids": [], - "stop_ids": ["70059"] - } - } - }, - { - "arrow": "w", - "audio": { - "wayfinding": null - }, - "headway": { - "headsigns": [], - "headway_id": null, - "sign_ids": [] - }, - "layout": { - "opts": { - "max_minutes": "infinity", - "num_rows": 14, - "paged": true, - "routes": { - "action": "exclude", - "route_list": [] - }, - "visible_rows": 10 - }, - "type": "upcoming" - }, - "name": "Bus", - "pill": "bus", - "query": { - "opts": { - "include_schedules": false - }, - "params": { - "direction_id": "both", - "route_ids": [], - "stop_ids": ["15795"] - } - } - } - ], - "station_name": "Wonderland" - }, - "device_id": "SOL12", - "disabled": false, - "name": "Wonderland SOL12", - "refresh_if_loaded_before": "2020-10-13T16:44:59.406651Z", - "tags": [], - "vendor": "solari" - }, - "305": { - "app_id": "solari", - "app_params": { - "audio_psa": null, - "overhead": false, - "psa_config": { - "default_list": { - "paths": ["solari-feedback.png"], - "type": "slide_in" - }, - "scheduled_overrides": [] - }, - "section_headers": "vertical", - "sections": [ - { - "arrow": "e", - "audio": { - "wayfinding": "Upper Busway" - }, - "headway": { - "headsigns": [], - "headway_id": null, - "sign_ids": [] - }, - "layout": { - "opts": { - "max_minutes": "infinity", - "num_rows": 10, - "paged": true, - "routes": { - "action": "exclude", - "route_list": [] - }, - "visible_rows": 6 - }, - "type": "upcoming" - }, - "name": "Upper Busway", - "pill": "bus", - "query": { - "opts": { - "include_schedules": false - }, - "params": { - "direction_id": "both", - "route_ids": [], - "stop_ids": ["10642"] - } - } - }, - { - "arrow": "n", - "audio": { - "wayfinding": "Lower Busway" - }, - "headway": { - "headsigns": [], - "headway_id": null, - "sign_ids": [] - }, - "layout": { - "opts": { - "max_minutes": "infinity", - "num_rows": 10, - "paged": true, - "routes": { - "action": "exclude", - "route_list": [] - }, - "visible_rows": 6 - }, - "type": "upcoming" - }, - "name": "Lower Busway", - "pill": "bus", - "query": { - "opts": { - "include_schedules": false - }, - "params": { - "direction_id": "both", - "route_ids": [], - "stop_ids": ["875"] - } - } - } - ], - "station_name": "Forest Hills" - }, - "device_id": "SOL05", - "disabled": false, - "name": "Forest Hills Lobby SOL05", - "refresh_if_loaded_before": "2020-10-13T16:44:59.406651Z", - "tags": [], - "vendor": "solari" - }, - "402": { - "app_id": "dup", - "app_params": { - "primary": { - "header": "Back Bay", - "sections": [ - { - "pill": "orange", - "route_ids": ["Orange"], - "stop_ids": ["place-bbsta"] - }, - { - "pill": "cr", - "route_ids": [ - "CR-Worcester", - "CR-Franklin", - "CR-Needham", - "CR-Providence" - ], - "stop_ids": ["place-bbsta"] - } - ] - }, - "secondary": { - "header": "", - "sections": [] - } - }, - "device_id": "TEST", - "disabled": false, - "name": "", - "refresh_if_loaded_before": null, - "tags": [], - "vendor": "outfront" - }, "1001": { "app_id": "bus_eink_v2", "app_params": { @@ -1839,43 +126,6 @@ "tags": [], "vendor": "solari" }, - "1353": { - "app_id": "solari_large_v2", - "app_params": { - "departures": { - "sections": [ - { - "filter": null, - "headway": { - "headway_id": null, - "override": null, - "sign_ids": [] - }, - "query": { - "opts": { - "include_schedules": false - }, - "params": { - "direction_id": "both", - "route_ids": ["1722"], - "route_type": null, - "stop_ids": [] - } - } - } - ] - }, - "header": { - "stop_name": "Nubian" - } - }, - "device_id": "TEST", - "disabled": false, - "name": "", - "refresh_if_loaded_before": null, - "tags": [], - "vendor": "gds" - }, "1401": { "app_id": "bus_shelter_v2", "app_params": { diff --git a/test/screens/line_map_test.exs b/test/screens/line_map_test.exs deleted file mode 100644 index 2e34942b3..000000000 --- a/test/screens/line_map_test.exs +++ /dev/null @@ -1,67 +0,0 @@ -defmodule Screens.LineMapTest do - use ExUnit.Case, async: true - - alias Screens.Stops.Stop - alias Screens.Vehicles.Vehicle - alias Screens.Predictions.Prediction - alias Screens.LineMap - - @route_stops [ - %Stop{id: "70196", name: "Park Street"}, - %Stop{id: "70155", name: "Copley"}, - %Stop{id: "71151", name: "Kenmore"}, - %Stop{id: "70149", name: "Blandford Street"}, - %Stop{id: "70137", name: "Babcock Street"}, - %Stop{id: "70113", name: "Chestnut Hill Avenue"}, - %Stop{id: "70107", name: "Boston College"} - ] - - @current_stop_index 3 - - describe "filter_predictions_by_vehicles/4" do - test "does not filter predictions from vehicles before or at current stop" do - predictions = [%Prediction{trip: %{id: 1}}, %Prediction{trip: %{id: 2}}] - vehicles = [%Vehicle{trip_id: 1, stop_id: "70196"}, %Vehicle{trip_id: 1, stop_id: "70149"}] - - result = - LineMap.filter_predictions_by_vehicles( - predictions, - vehicles, - @route_stops, - @current_stop_index - ) - - assert [%Prediction{trip: %{id: 1}}, %Prediction{trip: %{id: 2}}] == result - end - - test "filters predictions from vehicles after current stop" do - predictions = [%Prediction{trip: %{id: 1}}] - vehicles = [%Vehicle{trip_id: 1, stop_id: "70107"}] - - result = - LineMap.filter_predictions_by_vehicles( - predictions, - vehicles, - @route_stops, - @current_stop_index - ) - - assert [] == result - end - - test "does not filter predictions from vehicles not in route_stops" do - predictions = [%Prediction{trip: %{id: 1}}] - vehicles = [%Vehicle{trip_id: 1, stop_id: "70202"}] - - result = - LineMap.filter_predictions_by_vehicles( - predictions, - vehicles, - @route_stops, - @current_stop_index - ) - - assert [%Prediction{trip: %{id: 1}}] == result - end - end -end diff --git a/test/screens/v2/candidate_generator/solari_large_test.exs b/test/screens/v2/candidate_generator/solari_large_test.exs deleted file mode 100644 index cd5e839a9..000000000 --- a/test/screens/v2/candidate_generator/solari_large_test.exs +++ /dev/null @@ -1,51 +0,0 @@ -defmodule Screens.V2.CandidateGenerator.SolariLargeTest do - use ExUnit.Case, async: true - - alias ScreensConfig.{Screen, V2} - alias Screens.V2.CandidateGenerator.SolariLarge - alias Screens.V2.WidgetInstance.NormalHeader - - setup do - config = %Screen{ - app_params: %V2.SolariLarge{ - departures: %V2.Departures{sections: []}, - header: %V2.Header.CurrentStopName{stop_name: "Ruggles"} - }, - vendor: :gds, - device_id: "TEST", - name: "TEST", - app_id: :solari_large_v2 - } - - %{config: config} - end - - describe "screen_template/0" do - test "returns correct template" do - assert {:screen, - %{ - normal: [:header, :main_content], - takeover: [:full_screen] - }} == SolariLarge.screen_template() - end - end - - describe "candidate_instances/3" do - test "returns expected header", %{config: config} do - departures_instances_fn = fn _, _ -> [] end - now = ~U[2020-04-06T10:00:00Z] - - expected_header = %NormalHeader{ - screen: config, - icon: nil, - text: "Ruggles", - time: ~U[2020-04-06T10:00:00Z] - } - - actual_instances = - SolariLarge.candidate_instances(config, now, departures_instances_fn) - - assert expected_header in actual_instances - end - end -end diff --git a/test/screens_web/controllers/page_controller_test.exs b/test/screens_web/controllers/page_controller_test.exs deleted file mode 100644 index 401748573..000000000 --- a/test/screens_web/controllers/page_controller_test.exs +++ /dev/null @@ -1,18 +0,0 @@ -defmodule ScreensWeb.PageControllerTest do - use ScreensWeb.ConnCase - - test "GET /screen/", %{conn: conn} do - conn = get(conn, "/screen/305") - assert html_response(conn, 200) - end - - test "GET /screen/ with HTTP redirects to HTTPS", %{conn: conn} do - conn = conn |> Plug.Conn.put_req_header("x-forwarded-proto", "http") |> get("/screen/1") - - location_header = Enum.find(conn.resp_headers, fn {key, _value} -> key == "location" end) - {"location", url} = location_header - assert url =~ "https" - - assert response(conn, 301) - end -end diff --git a/test/screens_web/controllers/screen_api_controller_test.exs b/test/screens_web/controllers/screen_api_controller_test.exs deleted file mode 100644 index c24ff2a49..000000000 --- a/test/screens_web/controllers/screen_api_controller_test.exs +++ /dev/null @@ -1,6 +0,0 @@ -defmodule ScreensWeb.V2.ScreenApiControllerTest do - use ScreensWeb.ConnCase - - describe "show/2" do - end -end