From 5bc8975c7b107bba0ead63a0900c0f72bed190bd Mon Sep 17 00:00:00 2001 From: Jon Zimbel Date: Thu, 10 Aug 2023 12:49:47 -0400 Subject: [PATCH 01/30] Rename `isDup` -> `isOFM` since we now run on two different OFM screen types --- assets/src/apps/dup.tsx | 4 ++-- assets/src/components/dup/README.md | 2 +- assets/src/components/dup/dup_screen_page.tsx | 4 ++-- assets/src/components/v2/dup/README.md | 2 +- assets/src/components/v2/screen_container.tsx | 6 +++--- assets/src/hooks/use_api_response.tsx | 6 +++--- assets/src/hooks/v2/use_api_response.tsx | 6 +++--- assets/src/util/sentry.tsx | 4 ++-- assets/src/util/util.tsx | 14 +++++++++----- 9 files changed, 26 insertions(+), 22 deletions(-) diff --git a/assets/src/apps/dup.tsx b/assets/src/apps/dup.tsx index d23e47c77..12b115963 100644 --- a/assets/src/apps/dup.tsx +++ b/assets/src/apps/dup.tsx @@ -17,10 +17,10 @@ import { MultiRotationPage, SimulationPage, } from "Components/dup/dup_screen_page"; -import { isDup } from "Util/util"; +import { isOFM } from "Util/util"; const App = (): JSX.Element => { - if (isDup()) { + if (isOFM()) { return ; } else { return ( diff --git a/assets/src/components/dup/README.md b/assets/src/components/dup/README.md index 787599f5b..6b0b74e87 100644 --- a/assets/src/components/dup/README.md +++ b/assets/src/components/dup/README.md @@ -1,7 +1,7 @@ # DUP app packaging - Ensure [Corsica](https://hexdocs.pm/corsica/Corsica.html) is used on the server to allow CORS requests (ideally limited to just the DUP-relevant routes). It should already be configured at [this line](/lib/screens_web/controllers/screen_api_controller.ex#L7) in the API controller--if it is, you don't need to do anything for this step. -- Double check that any behavior specific to the DUP screen environment happens inside of an `isDup()` check. This includes: +- Double check that any behavior specific to the DUP screen environment happens inside of an `isOFM()` check. This includes: - `buildApiPath` in use_api_response.tsx should return a full URL for the API path: prefix `apiPath` string with "https://screens.mbta.com". - `App` component in dup.tsx should just return ``. - `imagePath` in util.tsx should return relative paths (no leading `/`). diff --git a/assets/src/components/dup/dup_screen_page.tsx b/assets/src/components/dup/dup_screen_page.tsx index bea452787..c06946193 100644 --- a/assets/src/components/dup/dup_screen_page.tsx +++ b/assets/src/components/dup/dup_screen_page.tsx @@ -4,7 +4,7 @@ import { useParams } from "react-router-dom"; import useOutfrontStation from "Hooks/use_outfront_station"; import { ROTATION_INDEX } from "./rotation_index"; import { NoDataLayout } from "Components/dup/screen_container"; -import { isDup } from "Util/util"; +import { isOFM } from "Util/util"; import { fetchDatasetValue } from "Util/dataset"; import { DUP_SIMULATION_REFRESH_MS } from "Constants"; @@ -37,7 +37,7 @@ const ScreenPage = ({ }: { screenContainer: React.ComponentType; }): JSX.Element => - isDup() ? ( + isOFM() ? ( ) : ( diff --git a/assets/src/components/v2/dup/README.md b/assets/src/components/v2/dup/README.md index 3523ce80a..2b4c6cd39 100644 --- a/assets/src/components/v2/dup/README.md +++ b/assets/src/components/v2/dup/README.md @@ -1,7 +1,7 @@ # DUP app packaging v2 - Ensure [Corsica](https://hexdocs.pm/corsica/Corsica.html) is used on the server to allow CORS requests (ideally limited to just the DUP-relevant routes). It should already be configured at [this line](/lib/screens_web/controllers/v2/screen_api_controller.ex#L9) in the API controller--if it is, you don't need to do anything for this step. -- Double check that any behavior specific to the DUP screen environment happens inside of an `isDup()` check. This includes: +- Double check that any behavior specific to the DUP screen environment happens inside of an `isOFM()` check. This includes: - `buildApiPath` in use_api_response.tsx should return a full URL for the API path: prefix `apiPath` string with "https://screens.mbta.com". - `imagePath` in util.tsx should return relative paths (no leading `/`). - Create priv/static/dup-app.html if it doesn’t already exist. Copy paste the following contents in: diff --git a/assets/src/components/v2/screen_container.tsx b/assets/src/components/v2/screen_container.tsx index fd1ef004a..f7f29a736 100644 --- a/assets/src/components/v2/screen_container.tsx +++ b/assets/src/components/v2/screen_container.tsx @@ -14,7 +14,7 @@ import useApiResponse, { import WidgetTreeErrorBoundary from "Components/v2/widget_tree_error_boundary"; import Widget, { WidgetData } from "Components/v2/widget"; import useAudioReadout from "Hooks/v2/use_audio_readout"; -import { isDup } from "Util/util"; +import { isOFM } from "Util/util"; type ResponseMapper = ( apiResponse: ApiResponse @@ -93,7 +93,7 @@ const ScreenLayout: ComponentType = ({ showBlink, }) => { const responseMapper = useContext(ResponseMapperContext); - const ErrorBoundaryOrFragment = isDup() ? Fragment : WidgetTreeErrorBoundary; + const ErrorBoundaryOrFragment = isOFM() ? Fragment : WidgetTreeErrorBoundary; return (
@@ -109,7 +109,7 @@ const ScreenContainer = ({ id }) => { const blinkConfig = useContext(BlinkConfigContext); const audioConfig = useContext(AudioConfigContext); const [showBlink, setShowBlink] = useState(false); - const hook = isDup() ? useDupApiResponse : useApiResponse; + const hook = isOFM() ? useDupApiResponse : useApiResponse; const { apiResponse, requestCount, lastSuccess } = hook({ id }); diff --git a/assets/src/hooks/use_api_response.tsx b/assets/src/hooks/use_api_response.tsx index 797483e62..137d77a63 100644 --- a/assets/src/hooks/use_api_response.tsx +++ b/assets/src/hooks/use_api_response.tsx @@ -1,5 +1,5 @@ import { useEffect, useState } from "react"; -import { isDup, isRealScreen } from "Util/util"; +import { isOFM, isRealScreen } from "Util/util"; import useInterval from "Hooks/use_interval"; import { getDatasetValue } from "Util/dataset"; import * as SentryLogger from "Util/sentry"; @@ -42,7 +42,7 @@ const useIsRealScreenParam = () => { }; const useRequestorParam = () => { - if (isDup()) return `&requestor=real_screen`; + if (isOFM()) return `&requestor=real_screen`; let requestor = getDatasetValue("requestor"); if (!requestor && isRealScreen()) { @@ -163,7 +163,7 @@ const buildApiPath = ({ apiPath += `&datetime=${datetime}`; } - if (isDup()) { + if (isOFM()) { apiPath = "https://screens.mbta.com" + apiPath; } diff --git a/assets/src/hooks/v2/use_api_response.tsx b/assets/src/hooks/v2/use_api_response.tsx index 025697188..ff35be0f7 100644 --- a/assets/src/hooks/v2/use_api_response.tsx +++ b/assets/src/hooks/v2/use_api_response.tsx @@ -2,7 +2,7 @@ import { WidgetData } from "Components/v2/widget"; import useDriftlessInterval from "Hooks/use_driftless_interval"; import React, { useEffect, useState } from "react"; import { getDataset, getDatasetValue } from "Util/dataset"; -import { getScreenSide, isDup, isRealScreen } from "Util/util"; +import { getScreenSide, isOFM, isRealScreen } from "Util/util"; import * as SentryLogger from "Util/sentry"; import { ROTATION_INDEX } from "Components/v2/dup/rotation_index"; @@ -119,7 +119,7 @@ const getScreenSideParam = () => { const getRequestorParam = () => { // Adding this to v2 because we will eventually widgetize DUPs. - if (isDup()) return `&requestor=real_screen`; + if (isOFM()) return `&requestor=real_screen`; let requestor = getDatasetValue("requestor"); if (!requestor && isRealScreen()) { @@ -163,7 +163,7 @@ const useBaseApiResponse = ({ let refreshRateOffsetMs = parseInt(refreshRateOffset, 10) * 1000; let apiPath = `/v2/api/screen/${id}${routePart}?last_refresh=${lastRefresh}${isRealScreenParam}${screenSideParam}${requestorParam}`; - if (isDup()) { + if (isOFM()) { apiPath = `https://screens.mbta.com${apiPath}&rotation_index=${ROTATION_INDEX}`; } diff --git a/assets/src/util/sentry.tsx b/assets/src/util/sentry.tsx index 4c35a50f7..d650a5b9d 100644 --- a/assets/src/util/sentry.tsx +++ b/assets/src/util/sentry.tsx @@ -1,4 +1,4 @@ -import { isDup, isRealScreen } from "Util/util"; +import { isOFM, isRealScreen } from "Util/util"; import { getDataset } from "Util/dataset"; // Previously tried @sentry/react and @sentry/browser as the SDK, but the QtWeb browser on e-inks could not // use them. Raven is an older stable SDK that better works with older browsers. @@ -33,7 +33,7 @@ const initSentry = (appString: string) => { if (sentryDsn && isRealScreen() && !disableSentry) { Raven.config(sentryDsn, { environment: env }).install(); - if (isDup()) { + if (isOFM()) { const today = new Date(); const hour = today.getHours(); const min = today.getMinutes(); diff --git a/assets/src/util/util.tsx b/assets/src/util/util.tsx index b26bc0f97..f136899f8 100644 --- a/assets/src/util/util.tsx +++ b/assets/src/util/util.tsx @@ -24,16 +24,20 @@ export const classWithModifiers = (baseClass, modifiers) => { export const formatTimeString = (timeString: string) => moment(timeString).tz("America/New_York").format("h:mm"); -export const isDup = () => location.href.startsWith("file:"); +/** + * Returns true if this client is running on an Outfront Media screen. + * (A DUP or a triptych.) + */ +export const isOFM = () => location.href.startsWith("file:"); export const imagePath = (fileName: string): string => - isDup() ? `images/${fileName}` : `/images/${fileName}`; + isOFM() ? `images/${fileName}` : `/images/${fileName}`; export const pillPath = (fileName: string): string => - isDup() ? `images/pills/${fileName}` : `/images/pills/${fileName}`; + isOFM() ? `images/pills/${fileName}` : `/images/pills/${fileName}`; export const isRealScreen = () => - isDup() || getDatasetValue("isRealScreen") === "true"; + isOFM() || getDatasetValue("isRealScreen") === "true"; type ScreenSide = "left" | "right"; const isScreenSide = (value: any): value is ScreenSide => { @@ -56,7 +60,7 @@ const isRotationIndex = (value: string | undefined) => { }; export const getRotationIndex = () => { - const rotationIndex = isDup() + const rotationIndex = isOFM() ? ROTATION_INDEX.toString() : getDatasetValue("rotationIndex"); From 5154c37a7eedc4ccede53a43c880ebda7547acbd Mon Sep 17 00:00:00 2001 From: Jon Zimbel Date: Thu, 10 Aug 2023 15:43:02 -0400 Subject: [PATCH 02/30] Triptych app setup + packaging instructions --- README.md | 7 +- assets/css/v2/triptych/viewport.scss | 31 +++++++ assets/src/apps/v2/triptych.tsx | 32 ++++++- assets/src/components/dup/README.md | 6 +- assets/src/components/v2/dup/README.md | 8 +- assets/src/components/v2/triptych/README.md | 82 ++++++++++++++++++ assets/src/components/v2/triptych/pane.tsx | 1 + assets/src/components/v2/triptych/version.tsx | 1 + .../src/components/v2/triptych/viewport.tsx | 0 assets/src/hooks/v2/use_api_response.tsx | 3 +- assets/src/util/util.tsx | 14 +++ .../v2/candidate_generator/triptych.ex | 1 + .../controllers/v2/screen_api_controller.ex | 4 +- lib/screens_web/router.ex | 1 + priv/{preview.png => dup_preview.png} | Bin priv/{template.json => dup_template.json} | 2 +- priv/triptych_preview.png | Bin 0 -> 466841 bytes priv/triptych_template.json | 10 +++ 18 files changed, 190 insertions(+), 13 deletions(-) create mode 100644 assets/css/v2/triptych/viewport.scss create mode 100644 assets/src/components/v2/triptych/README.md create mode 100644 assets/src/components/v2/triptych/pane.tsx create mode 100644 assets/src/components/v2/triptych/version.tsx create mode 100644 assets/src/components/v2/triptych/viewport.tsx rename priv/{preview.png => dup_preview.png} (100%) rename priv/{template.json => dup_template.json} (81%) create mode 100644 priv/triptych_preview.png create mode 100644 priv/triptych_template.json diff --git a/README.md b/README.md index 0601bd420..338396a8d 100644 --- a/README.md +++ b/README.md @@ -14,6 +14,7 @@ Some examples of the various client apps, as of January 2023: | [Multimodal LCD][solari sample] | used at high-traffic transfer stations served by many routes/modes | | [Pre-fare duo LCD][pre_fare sample] | posted outside of fare gates at rapid transit stations; content is split across two portrait-oriented 1080p physical displays | | ["Digital Urban Panel" LCD][dup sample] | content appears in rotation with ads on screens posted outside rapid transit station entrances | +| Triptych trio LCD | (:construction: WIP!) posted across the tracks from platforms at major stations; content is split across three portrait-oriented 1080p physical displays and appears in rotation with ads | and more to come! @@ -34,11 +35,11 @@ On almost all of our screen types, we use a common "framework" to fet Check out [ARCHITECTURE.md](/ARCHITECTURE.md) for an overview of the application architecture, as well as links to further more detailed documentation. -## Packaging the DUP app +## Packaging the DUP and Triptych apps -The DUP screens require the client app to be packaged into a single HTML file rather than dynamically served from our Phoenix server. +The DUP and Triptych screens require the client app to be packaged into a single HTML file rather than dynamically served from our Phoenix server. -You can find instructions on the packaging process [here](assets/src/components/v2/dup/README.md). +You can find instructions on the DUP packaging process [here](assets/src/components/v2/dup/README.md), the triptych packaging process [here](assets/src/components/v2/triptych/README.md). ## Version upgrade guide diff --git a/assets/css/v2/triptych/viewport.scss b/assets/css/v2/triptych/viewport.scss new file mode 100644 index 000000000..7e292404b --- /dev/null +++ b/assets/css/v2/triptych/viewport.scss @@ -0,0 +1,31 @@ +.triptych-screen-viewport { + width: 1080px; + height: 1920px; + overflow: hidden; + position: relative; + margin-left: auto; + margin-right: auto; + + .triptych-shifter { + width: 3240px; + height: 1920px; + overflow: hidden; + position: absolute; + } + + .triptych-shifter--left { + left: 0; + } + + .triptych-shifter--middle { + left: -1080px; + } + + .triptych-shifter--right { + right: 0px; + } +} + +.triptych-screen-viewport--all { + width: 3240px; +} diff --git a/assets/src/apps/v2/triptych.tsx b/assets/src/apps/v2/triptych.tsx index 693cb9427..033ac497d 100644 --- a/assets/src/apps/v2/triptych.tsx +++ b/assets/src/apps/v2/triptych.tsx @@ -15,13 +15,18 @@ import { MappingContext } from "Components/v2/widget"; import NormalScreen from "Components/v2/bus_shelter/normal_screen"; import NormalBody from "Components/v2/bus_shelter/normal_body"; +import MultiScreenPage from "Components/v2/multi_screen_page"; +import Viewport from "Components/v2/triptych/viewport"; +// Remove on go-live. import Placeholder from "Components/v2/placeholder"; import SimulationScreenPage from "Components/v2/simulation_screen_page"; import PageLoadNoData from "Components/v2/lcd/page_load_no_data"; import NoData from "Components/v2/lcd/no_data"; +import useOutfrontPlayerName from "Hooks/use_outfront_player_name"; + const TYPE_TO_COMPONENT = { screen_normal: NormalScreen, body_normal: NormalBody, @@ -55,20 +60,43 @@ const responseMapper: ResponseMapper = (apiResponse) => { }; const App = (): JSX.Element => { + const playerName = useOutfrontPlayerName(); + + if (playerName !== null) { + const id = `TRI-${playerName.trim()}`; + return ( + + + + + + + + ); + } + return ( + + + - + - + + + diff --git a/assets/src/components/dup/README.md b/assets/src/components/dup/README.md index 6b0b74e87..22dec22ca 100644 --- a/assets/src/components/dup/README.md +++ b/assets/src/components/dup/README.md @@ -40,8 +40,10 @@ for ROTATION_INDEX in {0..2}; do echo "export const ROTATION_INDEX = ${ROTATION_INDEX};" > ../../assets/src/components/dup/rotation_index.tsx npm --prefix ../../assets run deploy - cp -r css/dup.css js/polyfills.js js/dup.js ../inter_font_face.css ../fonts ../template.json ../preview.png . - zip -r dup-app-${ROTATION_INDEX}.zip dup.css polyfills.js dup.js inter_font_face.css fonts images dup-app.html template.json preview.png + cp -r css/dup.css js/polyfills.js js/dup.js ../inter_font_face.css ../fonts ../dup_preview.png . + cp ../dup_template.json ./template.json + sed -i "" "s/DUP APP ./DUP APP ${ROTATION_INDEX}/" template.json + zip -r dup-app-${ROTATION_INDEX}.zip dup.css polyfills.js dup.js inter_font_face.css fonts images dup-app.html template.json dup_preview.png done ``` - Commit the version bump on a branch, push it, and create a PR to mark the deploy. diff --git a/assets/src/components/v2/dup/README.md b/assets/src/components/v2/dup/README.md index 2b4c6cd39..671b2586f 100644 --- a/assets/src/components/v2/dup/README.md +++ b/assets/src/components/v2/dup/README.md @@ -33,14 +33,16 @@ - In assets/webpack.config.js, change `publicPath` in the font config to have value `'fonts/'`. - **Only if you are packaging for local testing** - replace `const playerName = useOutfrontPlayerName();` in assets/src/apps/v2/dup.tsx with `const playerName = "BRW-DUP-005";` (or any other player name from one of the DUP screen IDs (`DUP-${playerName}`)). This data is provided by Outfront's "wrapper" app that runs on the real DUP screens, but we need to set it ourselves during testing. Think of it as a sort of frontend environment variable. - - replace `apiPath = "https://screens.mbta.com" + apiPath;` in assets/src/hooks/v2/use_api_response.tsx with `apiPath = "http://localhost:4000" + apiPath;`. + - replace `apiPath = "https://screens.mbta.com...";` in assets/src/hooks/v2/use_api_response.tsx with `apiPath = "http://localhost:4000...";`. - `cd` to priv/static and run the following: ```sh for ROTATION_INDEX in {0..2}; do echo "export const ROTATION_INDEX = ${ROTATION_INDEX};" > ../../assets/src/components/v2/dup/rotation_index.tsx npm --prefix ../../assets run deploy - cp -r css/dup_v2.css js/polyfills.js js/dup_v2.js ../inter_font_face.css ../fonts ../template.json ../preview.png . - zip -r dup-app-${ROTATION_INDEX}.zip dup_v2.css polyfills.js dup_v2.js inter_font_face.css fonts images dup-app.html template.json preview.png + cp -r css/dup_v2.css js/polyfills.js js/dup_v2.js ../inter_font_face.css ../fonts ../dup_preview.png . + cp ../dup_template.json ./template.json + sed -i "" "s/DUP APP ./DUP APP ${ROTATION_INDEX}/" template.json + zip -r dup-app-${ROTATION_INDEX}.zip dup_v2.css polyfills.js dup_v2.js inter_font_face.css fonts images dup-app.html template.json dup_preview.png done ``` - Commit the version bump on a branch, push it, and create a PR to mark the deploy. diff --git a/assets/src/components/v2/triptych/README.md b/assets/src/components/v2/triptych/README.md new file mode 100644 index 000000000..b68fd5931 --- /dev/null +++ b/assets/src/components/v2/triptych/README.md @@ -0,0 +1,82 @@ +# Triptych app packaging + +- Ensure [Corsica](https://hexdocs.pm/corsica/Corsica.html) is used on the server to allow CORS requests (ideally limited to just the triptych-relevant routes). It should already be configured at [this line](/lib/screens_web/controllers/v2/screen_api_controller.ex#L9) in the API controller--if it is, you don't need to do anything for this step. +- Double check that any behavior specific to the triptych screen environment happens inside of an `isOFM()` check. This includes: + - `buildApiPath` in use_api_response.tsx should return a full URL for the API path: prefix `apiPath` string with "https://screens.mbta.com". + - `imagePath` in util.tsx should return relative paths (no leading `/`). +- Create priv/static/triptych-app.html if it doesn’t already exist. Copy paste the following contents in: + + ```html + + + + + + + Screens + + + + +
+ + + + + ``` + +- Set the version string in assets/src/components/v2/triptych/version.tsx to `current_year.current_month.current_day.1`. +- In assets/webpack.config.js, change `publicPath` in the font config to have value `'fonts/'`. +- **Only if you are packaging for local testing** + - replace `const playerName = useOutfrontPlayerName();` in assets/src/apps/v2/triptych.tsx with `const playerName = "[TODO: PUT VALID TRIPTYCH PLAYERNAME HERE]";` (or any other player name from one of the triptych screen IDs (`TRI-${playerName}`)). This data is provided by Outfront's "wrapper" app that runs on the real triptych screens, but we need to set it ourselves during testing. Think of it as a sort of frontend environment variable. + - replace `apiPath = "https://screens.mbta.com" + apiPath;` in assets/src/hooks/v2/use_api_response.tsx with `apiPath = "http://localhost:4000" + apiPath;`. +- `cd` to priv/static and run the following: + ```sh + for PANE in left middle right; do + echo "export const PANE = \"${PANE}\";" > ../../assets/src/components/v2/triptych/pane.tsx + npm --prefix ../../assets run deploy + cp -r css/triptych_v2.css js/polyfills.js js/triptych_v2.js ../inter_font_face.css ../fonts ../triptych_preview.png . + cp ../triptych_template.json ./template.json + sed -i "" -E "s/TRIPTYCH APP [[:alpha:]]+/TRIPTYCH APP $(echo $ROTATION_INDEX | tr 'a-z' 'A-Z')/" template.json + zip -r triptych-app-${ROTATION_INDEX}.zip triptych_v2.css polyfills.js triptych_v2.js inter_font_face.css fonts images triptych-app.html template.json triptych_preview.png + done + ``` +- Commit the version bump on a branch, push it, and create a PR to mark the deploy. + +## Debugging + +To assist with debugging on the triptych screens, you can paste this at the module scope in triptych.tsx to have console logs +show up on the screen: + +```js +const dEl = document.createElement("div"); +dEl.id = "debug"; +document.body.appendChild(dEl); +// save the original console.log function +const old_logger = console.log; +// grab html element for adding console.log output +const html_logger = document.getElementById("debug"); +// replace console.log function with our own function +console.log = function (...msgs) { + // first call old logger for console output + old_logger.call(this, arguments); + + // convert object args to strings and join them together + const text = msgs + .map((msg) => { + if (typeof msg == "object") { + return JSON && JSON.stringify ? JSON.stringify(msg) : msg; + } else { + return msg; + } + }) + .join(" "); + + // add the log to the html element. + html_logger.innerHTML += "
" + text + "
"; +}; +``` diff --git a/assets/src/components/v2/triptych/pane.tsx b/assets/src/components/v2/triptych/pane.tsx new file mode 100644 index 000000000..ab442d3df --- /dev/null +++ b/assets/src/components/v2/triptych/pane.tsx @@ -0,0 +1 @@ +export const TRIPTYCH_PANE = "left"; diff --git a/assets/src/components/v2/triptych/version.tsx b/assets/src/components/v2/triptych/version.tsx new file mode 100644 index 000000000..332e19b5e --- /dev/null +++ b/assets/src/components/v2/triptych/version.tsx @@ -0,0 +1 @@ +export const TRIPTYCH_VERSION = "23.8.10.1"; diff --git a/assets/src/components/v2/triptych/viewport.tsx b/assets/src/components/v2/triptych/viewport.tsx new file mode 100644 index 000000000..e69de29bb diff --git a/assets/src/hooks/v2/use_api_response.tsx b/assets/src/hooks/v2/use_api_response.tsx index ff35be0f7..2f64c6713 100644 --- a/assets/src/hooks/v2/use_api_response.tsx +++ b/assets/src/hooks/v2/use_api_response.tsx @@ -5,6 +5,7 @@ import { getDataset, getDatasetValue } from "Util/dataset"; import { getScreenSide, isOFM, isRealScreen } from "Util/util"; import * as SentryLogger from "Util/sentry"; import { ROTATION_INDEX } from "Components/v2/dup/rotation_index"; +import { TRIPTYCH_PANE } from "Components/v2/triptych/pane"; const MINUTE_IN_MS = 60_000; @@ -164,7 +165,7 @@ const useBaseApiResponse = ({ let apiPath = `/v2/api/screen/${id}${routePart}?last_refresh=${lastRefresh}${isRealScreenParam}${screenSideParam}${requestorParam}`; if (isOFM()) { - apiPath = `https://screens.mbta.com${apiPath}&rotation_index=${ROTATION_INDEX}`; + apiPath = `https://screens.mbta.com${apiPath}&rotation_index=${ROTATION_INDEX}&triptych_pane=${TRIPTYCH_PANE}`; } if (screenIdsWithOffsetMap) { diff --git a/assets/src/util/util.tsx b/assets/src/util/util.tsx index f136899f8..80f80f79c 100644 --- a/assets/src/util/util.tsx +++ b/assets/src/util/util.tsx @@ -1,4 +1,5 @@ import { ROTATION_INDEX } from "Components/v2/dup/rotation_index"; +import { TRIPTYCH_PANE } from "Components/v2/triptych/pane"; import moment from "moment"; import "moment-timezone"; import { getDatasetValue } from "Util/dataset"; @@ -44,6 +45,11 @@ const isScreenSide = (value: any): value is ScreenSide => { return value === "left" || value === "right"; }; +type TriptychPane = "left" | "middle" | "right"; +const isTriptychPane = (value: any): value is TriptychPane => { + return value === "left" || value === "middle" || value === "right"; +}; + /** * For screen types that are split across two separate displays (pre-fare), * this gets the value of the data attribute dictating which side to show. @@ -67,4 +73,12 @@ export const getRotationIndex = () => { return isRotationIndex(rotationIndex) ? rotationIndex : null; }; +export const getTriptychPane = (): TriptychPane | null => { + const pane = isOFM() + ? TRIPTYCH_PANE + : getDatasetValue("pane"); + + return isTriptychPane(pane) ? pane : null; +} + export const firstWord = (str: string): string => str.split(" ")[0]; diff --git a/lib/screens/v2/candidate_generator/triptych.ex b/lib/screens/v2/candidate_generator/triptych.ex index 4a593afb2..4152a7eca 100644 --- a/lib/screens/v2/candidate_generator/triptych.ex +++ b/lib/screens/v2/candidate_generator/triptych.ex @@ -35,6 +35,7 @@ defmodule Screens.V2.CandidateGenerator.Triptych do defp placeholder_instances do [ + %Placeholder{color: :green, slot_names: [:header]}, %Placeholder{color: :blue, slot_names: [:main_content]} ] end diff --git a/lib/screens_web/controllers/v2/screen_api_controller.ex b/lib/screens_web/controllers/v2/screen_api_controller.ex index 4b65ee1d2..85f89a538 100644 --- a/lib/screens_web/controllers/v2/screen_api_controller.ex +++ b/lib/screens_web/controllers/v2/screen_api_controller.ex @@ -6,7 +6,7 @@ defmodule ScreensWeb.V2.ScreenApiController do alias Screens.V2.ScreenData plug(:check_config) - plug Corsica, [origins: "*"] when action == :show_dup + plug Corsica, [origins: "*"] when action in [:show_dup, :show_triptych] defp check_config(conn, _) do if State.ok?() do @@ -81,6 +81,8 @@ defmodule ScreensWeb.V2.ScreenApiController do def show_dup(conn, params), do: show(conn, params) + def show_triptych(conn, params), do: show(conn, params) + def simulation(conn, %{"id" => screen_id, "last_refresh" => last_refresh} = params) do Screens.LogScreenData.log_data_request( screen_id, diff --git a/lib/screens_web/router.ex b/lib/screens_web/router.ex index 9a397f978..3b8e0b1e9 100644 --- a/lib/screens_web/router.ex +++ b/lib/screens_web/router.ex @@ -87,6 +87,7 @@ defmodule ScreensWeb.Router do get "/:id", ScreenApiController, :show get "/:id/simulation", ScreenApiController, :simulation get "/:id/dup", ScreenApiController, :show_dup + get "/:id/triptych", ScreenApiController, :show_triptych post "/log_frontend_error", ScreenApiController, :log_frontend_error end diff --git a/priv/preview.png b/priv/dup_preview.png similarity index 100% rename from priv/preview.png rename to priv/dup_preview.png diff --git a/priv/template.json b/priv/dup_template.json similarity index 81% rename from priv/template.json rename to priv/dup_template.json index d41423963..6fb924ffb 100644 --- a/priv/template.json +++ b/priv/dup_template.json @@ -2,7 +2,7 @@ "templates": [ { "displayName": "MBTA - DUP APP 0", - "preview": "preview.png", + "preview": "dup_preview.png", "indexTemplate": "dup-app.html", "previewOrientation": "landscape" } diff --git a/priv/triptych_preview.png b/priv/triptych_preview.png new file mode 100644 index 0000000000000000000000000000000000000000..12d6103fe1bb2a9af591afc1e0d1071da5753fd6 GIT binary patch literal 466841 zcmeI5e~?|}dEd`nU7;%>RoAEjg&(8q63`4Vu+y;#1|_-_+bu*!>sn=qJd%-c3vzL+ z(L|%Fq~b1~@GA;&#$`-WKwD?POFJUfpiBTEYh#3PXbTJwK_@C`_+ip27L_TLDp&pq5ZaP>c)HRJRdQ54O(@}GS1pGMKI z3`EiFsCH6(=ilG7W;BW#(Uo7keCfYh@pu1y`>ejp|9%~n*=7lVuUt-bk%zqxICuV0MUxckkckFV;n zUguG@saIF-8F>A3<2<+fqkR`OhTfRj^HWy(wzOJT?f=gGm8X^^Zdrpi$}hG?AE)WF?7l; ztqnRCx*zJg70u>9KU$4?XM{C~l{hrNJ#HF~J^Fw5K*Ys0(dU-v-I zthx8J`oJGAszg2OrD+XftJS*e3`4DKi$k+z=enMoVqMRig+H5GUHY>nQP0pc%t34z zOOI04d0~VGYZg+<8fYoyahQry78(Lp9)}}sEN>~dJ{*P)L~|Mm z%9S;x+>NHK`wT-DCrmqtl(G`XpA176?+mOgT2_>Dal%l_B4IkSE0nU#8YaJFXj)8u zG5Hn33alJPBQP|uvIY?ZHUisahnY}dWf!vibd@=XR&UE!D|}}!8*bx#Hv(JcE2S($ zgTPkQ*zOw}OOMGf9YmvG=UY;e0GRxeq2UXU`bg9~bji804x;UF%RI#Sl|4VY(9Hr+ z)|7Q{Hf=trX9`S-Sz2H!GMq7Mh{rd}tidHlRJPXqhNwj&y}-~ zHwz$f*f*N6Q(iEKI;fp2*eUFk&kpz*ehy5NjZd*trITio@iUxUL@I2u%nLlTHZ^c^ zX?Vpm>-4g5JQm7}WGobLt?LQ62ClJCx;`9w!pS;DaPo=K$CiGG-Zhh(4k$z6iSW{( ziSR;rA-r^bI6Ogk=@=orjyvIX<~6tdVRjaP6RFZvz+r_H=ZG*3pop;2l?OlT^n#y{ zJAQUB3MZFF1~}OzQ?3f&WDS~da_P#0lXZH*$vHF{Yu^Lcz_p(PWYFHnB52dLyOO+S z!u4juv!3o5pbcJ8*QGfgxCX90L;=@67Ql7NLS6sv-K(SMx_P%9ykYsgFMO-|{U?up z)(>pt#bC4f^hpK4p2hsmoABSgRO6T{Pvd%>mvct%OwT?ivzyW2&GDPEKA0Y9h3{4{ zBV@I@^rn0qa(BjFI{QfPS9BTI>%2>P_L>J=pmGm%yY$#;^?}DXN0Cf%w}N@>xoO-K zHrXXpz9MW%!zO17I6(|5+0|{n@~Px}!6kQWl{fL>dc*Jk?Za*sfDKQ!H-9l%Hk?h) z?`?92O?G-OBW)33h%lcTM&*v3%8RP*9qg2@Cp@#(DsL zl%f4adB&FH#P`ftsKhYkFKqD2CYR=T)HUjwJ~k&(MJIAtsM7F?h0>DG#_?DvER@e7 zER>H0777cMcTUM28@ytnN^?9GDrKQw`H$P5ce4N?bphQ#w?F&?x)Y$zUjW@cQBv4~ zozfU$OB(D{$Vn*)%@n7WLcnz%rIS0h%0p@E^fC@4ptMtL7$OB>W0>-0ih3!Ma9 z1J{WNqIa!9nBH~Lsm@>6;PwAkU4P}<7ff-p07Le1Rs%S~<;Aj@u?NM$1u^tga- zpu1aacxF7a=Op;q$0BeG3=oV%ChZ?#rvi2jW5u!KM-_Ep^x7;vh@HYtC39tr0_l39 z#hJy90F-T(2e{VhW#o9^8n_0oqa+0K0=NdQ1CMEhS5Bm~L$`4}9WOdwK8NUd`B(&K zM8|6!a%%qwxDL!!7%MCk7Ai;$IGLlyZb5vk!t4IQhkveS0fKY{x{K4X6C$7+=niTH z=q|1mPNa(C(oP-h6n4sI2QALf8Tq($+_eo};bb@&PS&8>EigDagDwH^z{xs&7`iVF zubfEf^fGb$rX80(REb(E=k4CSZsl9UpIQ8iBeyp%XbhdQXk>#ts&U84`&KlYZ*T02 z^k?{}2pLr>bI;Idk{l9ji(izrW#|3?Xp$dgE^O=Viq|_h<-P@Se8R1IAb#`Kfd!FH zT(^R`;Z&~=Yz;_O{YlO98$&N1*?x8JCmYvelVzT<$tnx&6R}jXiXoLe4zU2&j%m`Q z3MUuGr7cB(YXvpHwa%Iv!b@pM)U{Lbd5MU+cJ42eJy@uYi@V{w%T~BqfPD5ens{bi zTX^Qu${6U@=>>EH-Q5DiPHAC`ox)D}?0}Qu)yd9%X*@ZEwhF`w$wHBv1Mqiz8kofg$G;%*Pg?f+$=rS0bB#u z-l5ah6dwIpo)`o;|Ox zQxS0=uxVXyRlMHe%5!t%16{%#ieHCzXilWj#I3+4yUovMaC(z}w0hTDZWh2rFP8#r zxC{+dt*Vi#&evY|4$!UZ3Fx*$w~QfhEkgrb1J@3gQQA&d=Pd+ETe=(URL&pxJi;@3 z_v>(`a|cd_lYMHWJ-Cbv7AjRk9%kwdER@We8Wsu*6-YWb*#mD1PvB&YA-1H!LWP`^ zw$@lEER@d*3*e`(J;;XzeX z)xLJ=eg#Uv5nsD#u~Q**VW$}I^PrJtIP4U5DgaTNGGN5cHz!hB;{w;E zD-R2$)5`|`sB6@<&mrL2$0F^tstgcFk~e>+3Aonj1zZbTi@KJD2VCoVLS3V-X@=x0WChK&xB@LULRCxA+N_KUduY4*wMBa>W zv{fE(t<%d0|8R1?GP`%+WX+5SuhNx=@Y3mJgMWlqR&OO%4dJEh6XAvM3R(+Bp#)q5 z*WRQUlLmE-x`vY{a9eZ1uA^Uavj7ci%ap37s;TM;T-8*vbU%1zJhK<_v^ae%0!!GT zH(Hz$M65yy&#cpn7N>X9DLIL{PK6_+E8tq!6L1Y&2h&qFcx8Z~bkYFVz%|Vfnjr!9 zCdZ3#a%p(QLTSlo<9Iq=p@m3wIhYdB^~sb-0^|7$Tjimyb$T&CP$U@QOarcgYft%~ zWXJ3EMb}i_EI_(9i3syMk`fTH@v~6@RZUg<9Qq`yno8y?twL&8k1=U+(&D7WNsF@x zMrd)C_PD5PuZ&RFG)9VGhQ^47nN~y}TStdJcD{X`+_5z;=ws{j06XOt zYN7u1`H!CMW&wgDg)tk%hEKi?XOpWnl(v>aoGt5`M``EKV@w=ySW!A@P}(SMly;Pa zKwccLN;?OgsA~8?Sy^KN*PKJ{c9s;g)JptDVIOi{bYjL>1iIi@` z*ph|;0(m$JTm#pE<)W>r)4`~QfotH}Gr2KouuxbiEL4<)KweA$3$?!g_rB(40W@30 z`8-Hf+pBtT>-uLt7e)OupE~^Lg`3X&{H<56_+@syv1=O5ag8dKxo3DRmLFzY{L-u~ zJNM^3Y3`0~yjhjUsb!K)j_4>fpK+om=fUXf}p5GXH z@yPb8dq3H@-UW@JQx=VEkUql|^u86%=Gz6{Y5=^LM4e!A9 zI7DFMP&iqq7o6DYTXO~Bh4AuX1L5Uk5l~LsA>xzq$zhK%3$K6rz(4qNHw#b< zDeyD=3_nYNbw$%;fIuQG{4CQ4ewJBNV-zT4?y#EzC&S5bvIJE)naRyObTaRyL6gIZ zkY3$%bGMh0^~?@l0tAl#HJq%|%Xs*Kg_2?z3nkee3njA#3neiLxR#-@`flJ_79Ma7 zTzeIXx<*|G=dwmM%y_QE5XN(ZbP7p`Qz(zRVO-i)9Zr_@G-jcu{@_=B=w<<0@5x{G z?GG9w#o5M}(@XO`1Ae~DGQp&et(_}MJCiKoF$i!CTz5-{?w7_8qdlYhl@-{DCqZdT z@yua`%$gJXu;Ti6?_M26*Uh`_;0?>?ec@Zx?>~9;vl^os&7ryVfz_kSdk=clYQwLl zPpS)9uyJaA;LiE`7E0Oal*F2g8bfOrRU(<K06Rz2(JJ{ZH){LD@rE~jgb?tG2&4dPM);gFE-gbG&b4CBJ1c8UZpvd zW{6HNn~=jN1J^!>fNLKM;F_VntV+ix>-uo9f|GTOPC!n+{O3DgaI*lmZ)(sOp)ukc z^c+_BSOoObMqBvVaZZ7Tw?Qp^?9v__xCX90L;=@67Qi(v&eHITox)Br?yXnB$vIfmh)hCa6f4Lom;e+Oqv>7Tdk{pH|qVS=aC()*0ZB0NBXaK5~R~fjD9xV{eS%- z`p(G{YQx)(?m1@b)laSs@0!-XB=?|fvhR(5aqrG3vLWQ`>mHDQLb7sW z=#6PQ9?7O`i8p2cckVxC>#@nn4rDLbWL>9Jvc`xLxRU?ZH-3M;n*|sj%z*CF2?TU! z5j(*vpgRdg?gHp8U3rKw4emDOj?$La0XwD5P3I5@J5>x|w#q|z6-Te_u#L(c;f3%@ zFbcR%LXo=wu9Lo9{sOobD1y31UHh{HI9WIaER+aLvjr#Ts5lY5aB`lClRI!SoZKxP zd~yOAxeGX1P!F6e0s|)tVC_&3oSZOl{sK-GsWZWS^6WqP?r*wTfcQ&N96RP{G}%`p zpHA=%XOnYh?O`K_O%~b!rCk~sIFU-QG<(4)kVYjqxpd{h$sxz3i_fOq@yvo3@yzLT zN?Subb2=U=B(qf>!YdWDR6LDp9pNQw6yb&N^2q^Qr{Kj0rVT;BbvhnNs8H9aYes?e zE5^EK)mSJjlm`lg7sAW)DZ(pJ6Zs2-mrs$ zQC&uLgGAv(Dh0zS#xk}ff$lWIg!Kh<3%B z&+n@qm|9(WU_qo4*FE!&_!r-pc1iEoYt`!mTYH+`_&<+veq-pxBipa;{bb{M7c_=W zSv0aCz@y?++P9+Fe0yVGRQ%K6;|yNgY@V~R)%zW4QKd5X4BzQ+5zF1#7WWFb?A)Ki zh2meft!I8O9h)p;&L-#5)JtY#Ktu8S$6&2w<_IBaj@P&iqqml6IE zUXnr)UXmFRUNUQHSg62;rIZ114O|C3Asfe|u2VJS{avGpy4LlCx<*|GO+up@rWqn7 zAI*>;okI0z{_mUt*TA)xKc9ky`o`iN4L1wm>KqXkDk|?>sOsXlv=f-dh^wBQ#`+Yh z+IrZ= zZs#9LTPp*UHcGoobDS*~>!5am0No!GbOYD%0U1ggr5zLkoD3(!$y&2^M+)F%IN2*U zTAZah6rZdy2TqGog+w~b+t-EG

{oC%PN(7 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 tk Date: Fri, 11 Aug 2023 12:50:42 -0400 Subject: [PATCH 03/30] Working triptych app with viewport shifter --- assets/css/triptych_v2.scss | 5 ++-- assets/css/v2/triptych/body/normal.scss | 9 ------ assets/css/v2/triptych/no_data.scss | 11 +++++++ assets/css/v2/triptych/screen/normal.scss | 9 +++--- assets/css/v2/triptych/screen/takeover.scss | 16 ++++++++++ assets/css/v2/triptych/screen_container.scss | 11 ++++++- assets/src/apps/v2/triptych.tsx | 17 ++++++----- assets/src/components/dup/README.md | 5 ++-- assets/src/components/v2/dup/README.md | 4 +-- assets/src/components/v2/screen_container.tsx | 4 +-- assets/src/components/v2/triptych/README.md | 12 ++++---- assets/src/components/v2/triptych/no_data.tsx | 27 +++++++++++++++++ .../components/v2/triptych/normal_screen.tsx | 26 +++++++++++++++++ .../v2/triptych/page_load_no_data.tsx | 20 +++++++++++++ .../v2/triptych/takeover_screen.tsx | 21 ++++++++++++++ .../src/components/v2/triptych/viewport.tsx | 29 +++++++++++++++++++ assets/src/hooks/v2/use_api_response.tsx | 12 ++++++-- assets/src/util/util.tsx | 15 ++++++---- lib/screens/log_screen_data.ex | 9 +++++- lib/screens/util.ex | 2 ++ .../v2/candidate_generator/triptych.ex | 5 ++-- .../controllers/v2/screen_api_controller.ex | 10 +++---- .../controllers/v2/screen_controller.ex | 12 +++++++- lib/screens_web/router.ex | 3 +- .../templates/v2/screen/index.html.eex | 3 ++ 25 files changed, 240 insertions(+), 57 deletions(-) delete mode 100644 assets/css/v2/triptych/body/normal.scss create mode 100644 assets/css/v2/triptych/no_data.scss create mode 100644 assets/css/v2/triptych/screen/takeover.scss create mode 100644 assets/src/components/v2/triptych/no_data.tsx create mode 100644 assets/src/components/v2/triptych/normal_screen.tsx create mode 100644 assets/src/components/v2/triptych/page_load_no_data.tsx create mode 100644 assets/src/components/v2/triptych/takeover_screen.tsx diff --git a/assets/css/triptych_v2.scss b/assets/css/triptych_v2.scss index dadedd203..557d82811 100644 --- a/assets/css/triptych_v2.scss +++ b/assets/css/triptych_v2.scss @@ -3,14 +3,15 @@ @import "fonts/helvetica_font_face"; @import "v2/common/widget"; +@import "v2/triptych/viewport"; @import "v2/triptych/screen_container"; @import "v2/triptych/screen/normal"; - -@import "v2/triptych/body/normal"; +@import "v2/triptych/screen/takeover"; @import "v2/lcd_common_styles/page_load_no_data"; @import "v2/lcd_common_styles/no_data"; +@import "v2/triptych/no_data"; @import "v2/placeholder"; diff --git a/assets/css/v2/triptych/body/normal.scss b/assets/css/v2/triptych/body/normal.scss deleted file mode 100644 index 4642aa625..000000000 --- a/assets/css/v2/triptych/body/normal.scss +++ /dev/null @@ -1,9 +0,0 @@ -.body-normal__main-content { - position: absolute; - top: 0px; - left: 0px; - width: 1080px; - height: 1648px; - z-index: 1; - overflow: hidden; -} diff --git a/assets/css/v2/triptych/no_data.scss b/assets/css/v2/triptych/no_data.scss new file mode 100644 index 000000000..2f3d8a74d --- /dev/null +++ b/assets/css/v2/triptych/no_data.scss @@ -0,0 +1,11 @@ +.no-data-middle { + position: absolute; + top: 0px; + left: 1080px; +} + +.no-data-right { + position: absolute; + top: 0px; + left: 2160px; +} diff --git a/assets/css/v2/triptych/screen/normal.scss b/assets/css/v2/triptych/screen/normal.scss index b8ada9753..7ddc72155 100644 --- a/assets/css/v2/triptych/screen/normal.scss +++ b/assets/css/v2/triptych/screen/normal.scss @@ -1,5 +1,6 @@ .screen-normal { - width: 1080px; + width: 3240px; + height: 1920px; margin-left: auto; margin-right: auto; position: relative; @@ -9,16 +10,16 @@ position: absolute; top: 0px; left: 0px; - width: 1080px; + width: 3240px; height: 272px; overflow: hidden; } -.screen-normal__body { +.screen-normal__main-content { position: absolute; top: 272px; left: 0px; - width: 1080px; + width: 3240px; height: 1648px; overflow: hidden; } diff --git a/assets/css/v2/triptych/screen/takeover.scss b/assets/css/v2/triptych/screen/takeover.scss new file mode 100644 index 000000000..b8151623c --- /dev/null +++ b/assets/css/v2/triptych/screen/takeover.scss @@ -0,0 +1,16 @@ +.screen-takeover { + width: 3240px; + height: 1920px; + margin-left: auto; + margin-right: auto; + position: relative; +} + +.screen-takeover__full-screen { + position: absolute; + top: 0px; + left: 0px; + width: 3240px; + height: 1920px; + overflow: hidden; +} diff --git a/assets/css/v2/triptych/screen_container.scss b/assets/css/v2/triptych/screen_container.scss index a050035a6..ce6e2959b 100644 --- a/assets/css/v2/triptych/screen_container.scss +++ b/assets/css/v2/triptych/screen_container.scss @@ -1,7 +1,16 @@ .screen-container { position: relative; - width: 1080px; + width: 3240px; height: 1920px; margin: 0px auto; overflow: hidden; } + +.screen-container-blink { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + background-color: black; +} diff --git a/assets/src/apps/v2/triptych.tsx b/assets/src/apps/v2/triptych.tsx index 033ac497d..b972e3009 100644 --- a/assets/src/apps/v2/triptych.tsx +++ b/assets/src/apps/v2/triptych.tsx @@ -5,31 +5,32 @@ require("../../../css/triptych_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 { ResponseMapper, ResponseMapperContext, LOADING_LAYOUT, } from "Components/v2/screen_container"; -import { MappingContext } from "Components/v2/widget"; -import NormalScreen from "Components/v2/bus_shelter/normal_screen"; -import NormalBody from "Components/v2/bus_shelter/normal_body"; import MultiScreenPage from "Components/v2/multi_screen_page"; -import Viewport from "Components/v2/triptych/viewport"; +import SimulationScreenPage from "Components/v2/simulation_screen_page"; // Remove on go-live. import Placeholder from "Components/v2/placeholder"; -import SimulationScreenPage from "Components/v2/simulation_screen_page"; -import PageLoadNoData from "Components/v2/lcd/page_load_no_data"; -import NoData from "Components/v2/lcd/no_data"; +import Viewport from "Components/v2/triptych/viewport"; +import NormalScreen from "Components/v2/triptych/normal_screen"; +import TakeoverScreen from "Components/v2/triptych/takeover_screen"; +import PageLoadNoData from "Components/v2/triptych/page_load_no_data"; +import NoData from "Components/v2/triptych/no_data"; import useOutfrontPlayerName from "Hooks/use_outfront_player_name"; const TYPE_TO_COMPONENT = { screen_normal: NormalScreen, - body_normal: NormalBody, + screen_takeover: TakeoverScreen, placeholder: Placeholder, page_load_no_data: PageLoadNoData, no_data: NoData, diff --git a/assets/src/components/dup/README.md b/assets/src/components/dup/README.md index 22dec22ca..daae3f428 100644 --- a/assets/src/components/dup/README.md +++ b/assets/src/components/dup/README.md @@ -16,7 +16,6 @@ Screens - @@ -40,10 +39,10 @@ for ROTATION_INDEX in {0..2}; do echo "export const ROTATION_INDEX = ${ROTATION_INDEX};" > ../../assets/src/components/dup/rotation_index.tsx npm --prefix ../../assets run deploy - cp -r css/dup.css js/polyfills.js js/dup.js ../inter_font_face.css ../fonts ../dup_preview.png . + cp -r css/dup.css js/polyfills.js js/dup.js ../dup_preview.png . cp ../dup_template.json ./template.json sed -i "" "s/DUP APP ./DUP APP ${ROTATION_INDEX}/" template.json - zip -r dup-app-${ROTATION_INDEX}.zip dup.css polyfills.js dup.js inter_font_face.css fonts images dup-app.html template.json dup_preview.png + zip -r dup-app-${ROTATION_INDEX}.zip dup.css polyfills.js dup.js fonts images dup-app.html template.json dup_preview.png done ``` - Commit the version bump on a branch, push it, and create a PR to mark the deploy. diff --git a/assets/src/components/v2/dup/README.md b/assets/src/components/v2/dup/README.md index 671b2586f..d464da7a5 100644 --- a/assets/src/components/v2/dup/README.md +++ b/assets/src/components/v2/dup/README.md @@ -39,10 +39,10 @@ for ROTATION_INDEX in {0..2}; do echo "export const ROTATION_INDEX = ${ROTATION_INDEX};" > ../../assets/src/components/v2/dup/rotation_index.tsx npm --prefix ../../assets run deploy - cp -r css/dup_v2.css js/polyfills.js js/dup_v2.js ../inter_font_face.css ../fonts ../dup_preview.png . + cp -r css/dup_v2.css js/polyfills.js js/dup_v2.js ../dup_preview.png . cp ../dup_template.json ./template.json sed -i "" "s/DUP APP ./DUP APP ${ROTATION_INDEX}/" template.json - zip -r dup-app-${ROTATION_INDEX}.zip dup_v2.css polyfills.js dup_v2.js inter_font_face.css fonts images dup-app.html template.json dup_preview.png + zip -r dup-app-${ROTATION_INDEX}.zip dup_v2.css polyfills.js dup_v2.js fonts images dup-app.html template.json dup_preview.png done ``` - Commit the version bump on a branch, push it, and create a PR to mark the deploy. diff --git a/assets/src/components/v2/screen_container.tsx b/assets/src/components/v2/screen_container.tsx index f7f29a736..4caea3b9d 100644 --- a/assets/src/components/v2/screen_container.tsx +++ b/assets/src/components/v2/screen_container.tsx @@ -9,7 +9,7 @@ import React, { import useApiResponse, { ApiResponse, SimulationApiResponse, - useDupApiResponse, + useOFMApiResponse, } from "Hooks/v2/use_api_response"; import WidgetTreeErrorBoundary from "Components/v2/widget_tree_error_boundary"; import Widget, { WidgetData } from "Components/v2/widget"; @@ -109,7 +109,7 @@ const ScreenContainer = ({ id }) => { const blinkConfig = useContext(BlinkConfigContext); const audioConfig = useContext(AudioConfigContext); const [showBlink, setShowBlink] = useState(false); - const hook = isOFM() ? useDupApiResponse : useApiResponse; + const hook = isOFM() ? useOFMApiResponse : useApiResponse; const { apiResponse, requestCount, lastSuccess } = hook({ id }); diff --git a/assets/src/components/v2/triptych/README.md b/assets/src/components/v2/triptych/README.md index b68fd5931..419faa8ea 100644 --- a/assets/src/components/v2/triptych/README.md +++ b/assets/src/components/v2/triptych/README.md @@ -32,17 +32,17 @@ - Set the version string in assets/src/components/v2/triptych/version.tsx to `current_year.current_month.current_day.1`. - In assets/webpack.config.js, change `publicPath` in the font config to have value `'fonts/'`. - **Only if you are packaging for local testing** - - replace `const playerName = useOutfrontPlayerName();` in assets/src/apps/v2/triptych.tsx with `const playerName = "[TODO: PUT VALID TRIPTYCH PLAYERNAME HERE]";` (or any other player name from one of the triptych screen IDs (`TRI-${playerName}`)). This data is provided by Outfront's "wrapper" app that runs on the real triptych screens, but we need to set it ourselves during testing. Think of it as a sort of frontend environment variable. - - replace `apiPath = "https://screens.mbta.com" + apiPath;` in assets/src/hooks/v2/use_api_response.tsx with `apiPath = "http://localhost:4000" + apiPath;`. + - replace `const playerName = useOutfrontPlayerName();` in assets/src/apps/v2/triptych.tsx with `const playerName = "BKB-TRI-001";` (or any other player name from one of the triptych screen IDs (`TRI-${playerName}`)). This data is provided by Outfront's "wrapper" app that runs on the real triptych screens, but we need to set it ourselves during testing. Think of it as a sort of frontend environment variable. + - replace `apiPath = "https://screens.mbta.com...";` in assets/src/hooks/v2/use_api_response.tsx with `apiPath = "http://localhost:4000...";`. - `cd` to priv/static and run the following: ```sh for PANE in left middle right; do - echo "export const PANE = \"${PANE}\";" > ../../assets/src/components/v2/triptych/pane.tsx + echo "export const TRIPTYCH_PANE = \"${PANE}\";" > ../../assets/src/components/v2/triptych/pane.tsx npm --prefix ../../assets run deploy - cp -r css/triptych_v2.css js/polyfills.js js/triptych_v2.js ../inter_font_face.css ../fonts ../triptych_preview.png . + cp -r css/triptych_v2.css js/polyfills.js js/triptych_v2.js ../triptych_preview.png . cp ../triptych_template.json ./template.json - sed -i "" -E "s/TRIPTYCH APP [[:alpha:]]+/TRIPTYCH APP $(echo $ROTATION_INDEX | tr 'a-z' 'A-Z')/" template.json - zip -r triptych-app-${ROTATION_INDEX}.zip triptych_v2.css polyfills.js triptych_v2.js inter_font_face.css fonts images triptych-app.html template.json triptych_preview.png + sed -i "" -E "s/TRIPTYCH APP [[:alpha:]]+/TRIPTYCH APP $(echo $PANE | tr 'a-z' 'A-Z')/" template.json + zip -r triptych-app-${PANE}.zip triptych_v2.css polyfills.js triptych_v2.js fonts images triptych-app.html template.json triptych_preview.png done ``` - Commit the version bump on a branch, push it, and create a PR to mark the deploy. diff --git a/assets/src/components/v2/triptych/no_data.tsx b/assets/src/components/v2/triptych/no_data.tsx new file mode 100644 index 000000000..08b2ed281 --- /dev/null +++ b/assets/src/components/v2/triptych/no_data.tsx @@ -0,0 +1,27 @@ +import React, { ComponentType } from "react"; +import LcdNoData from "Components/v2/lcd/no_data"; + +interface Props { + show_alternatives: boolean; +} + +const NoData: ComponentType = ({ + show_alternatives: showAlternatives, +}) => { + // TODO: We likely want to show something different from the usual when we fail to fetch data for this screen type. + return ( + <> +

+ +
+
+ +
+
+ +
+ + ); +}; + +export default NoData; diff --git a/assets/src/components/v2/triptych/normal_screen.tsx b/assets/src/components/v2/triptych/normal_screen.tsx new file mode 100644 index 000000000..2eabec0a7 --- /dev/null +++ b/assets/src/components/v2/triptych/normal_screen.tsx @@ -0,0 +1,26 @@ +import React from "react"; + +import Widget, { WidgetData } from "Components/v2/widget"; + +interface Props { + header: WidgetData; + main_content: WidgetData; +} + +const NormalScreen: React.ComponentType = ({ + header, + main_content: mainContent, +}) => { + return ( +
+
+ +
+
+ +
+
+ ); +}; + +export default NormalScreen; diff --git a/assets/src/components/v2/triptych/page_load_no_data.tsx b/assets/src/components/v2/triptych/page_load_no_data.tsx new file mode 100644 index 000000000..770eab5ed --- /dev/null +++ b/assets/src/components/v2/triptych/page_load_no_data.tsx @@ -0,0 +1,20 @@ +import React, { ComponentType } from "react"; +import LcdPageLoadNoData from "Components/v2/lcd/page_load_no_data"; + +const PageLoadNoData: ComponentType = () => { + return ( + <> +
+ +
+
+ +
+
+ +
+ + ); +}; + +export default PageLoadNoData; diff --git a/assets/src/components/v2/triptych/takeover_screen.tsx b/assets/src/components/v2/triptych/takeover_screen.tsx new file mode 100644 index 000000000..29e28be28 --- /dev/null +++ b/assets/src/components/v2/triptych/takeover_screen.tsx @@ -0,0 +1,21 @@ +import React from "react"; + +import Widget, { WidgetData } from "Components/v2/widget"; + +interface Props { + full_screen: WidgetData; +} + +const TakeoverScreen: React.ComponentType = ({ + full_screen: fullScreen, +}) => { + return ( +
+
+ +
+
+ ); +}; + +export default TakeoverScreen; diff --git a/assets/src/components/v2/triptych/viewport.tsx b/assets/src/components/v2/triptych/viewport.tsx index e69de29bb..23d7946f6 100644 --- a/assets/src/components/v2/triptych/viewport.tsx +++ b/assets/src/components/v2/triptych/viewport.tsx @@ -0,0 +1,29 @@ +import React from "react"; + +import { getTriptychPane } from "Util/util"; + +/** + * Shifts one of the three triptych panes into view + * based on a `data-triptych-pane` data attribute on the #app div. + * If the param is missing, this will show the full + * screen content (3240px x 1920px). + */ +const Viewport: React.ComponentType<{}> = ({ children }) => { + let viewportClassName = "triptych-screen-viewport"; + let shifterClassName = "triptych-shifter"; + + const pane = getTriptychPane(); + if (pane != null) { + shifterClassName += ` triptych-shifter--${pane}`; + } else { + viewportClassName += " triptych-screen-viewport--all"; + } + + return ( +
+
{children}
+
+ ); +}; + +export default Viewport; diff --git a/assets/src/hooks/v2/use_api_response.tsx b/assets/src/hooks/v2/use_api_response.tsx index 2f64c6713..9263fa3a9 100644 --- a/assets/src/hooks/v2/use_api_response.tsx +++ b/assets/src/hooks/v2/use_api_response.tsx @@ -236,13 +236,19 @@ const useSimulationApiResponse = ({ id }) => responseHandler: rawResponseToSimulationApiResponse, }); -const useDupApiResponse = ({ id }) => +// For OFM apps--DUP, triptych--we need to request a different +// route that's more permissive of CORS, since these clients are loaded from a local html file +// (and thus their data requests to our server are cross-origin). +// +// Besides the CORS stuff, this different route runs exactly the same backend logic as the normal one +// used by `useApiResponse`. +const useOFMApiResponse = ({ id }) => useBaseApiResponse({ id, - routePart: "/dup", + routePart: "/ofm", responseHandler: rawResponseToApiResponse, }); export default useApiResponse; export { ApiResponse, SimulationApiResponse }; -export { useSimulationApiResponse, useDupApiResponse }; +export { useSimulationApiResponse, useOFMApiResponse }; diff --git a/assets/src/util/util.tsx b/assets/src/util/util.tsx index 80f80f79c..c45cd1dc2 100644 --- a/assets/src/util/util.tsx +++ b/assets/src/util/util.tsx @@ -28,6 +28,8 @@ export const formatTimeString = (timeString: string) => /** * Returns true if this client is running on an Outfront Media screen. * (A DUP or a triptych.) + * + * Use this for OFM-specific logic that is common to both the DUP and triptych apps. */ export const isOFM = () => location.href.startsWith("file:"); @@ -45,6 +47,11 @@ const isScreenSide = (value: any): value is ScreenSide => { return value === "left" || value === "right"; }; +type RotationIndex = "0" | "1" | "2"; +const isRotationIndex = (value: any): value is RotationIndex => { + return value === "0" || value === "1" || value === "2"; +}; + type TriptychPane = "left" | "middle" | "right"; const isTriptychPane = (value: any): value is TriptychPane => { return value === "left" || value === "middle" || value === "right"; @@ -61,11 +68,7 @@ export const getScreenSide = (): ScreenSide | null => { return isScreenSide(screenSide) ? screenSide : null; }; -const isRotationIndex = (value: string | undefined) => { - return value === "0" || value === "1" || value === "2"; -}; - -export const getRotationIndex = () => { +export const getRotationIndex = (): RotationIndex | null => { const rotationIndex = isOFM() ? ROTATION_INDEX.toString() : getDatasetValue("rotationIndex"); @@ -76,7 +79,7 @@ export const getRotationIndex = () => { export const getTriptychPane = (): TriptychPane | null => { const pane = isOFM() ? TRIPTYCH_PANE - : getDatasetValue("pane"); + : getDatasetValue("triptychPane"); return isTriptychPane(pane) ? pane : null; } diff --git a/lib/screens/log_screen_data.ex b/lib/screens/log_screen_data.ex index 4b78d964e..c86c9c1ca 100644 --- a/lib/screens/log_screen_data.ex +++ b/lib/screens/log_screen_data.ex @@ -19,7 +19,8 @@ defmodule Screens.LogScreenData do is_screen, requestor, screen_side \\ nil, - rotation_index \\ nil + rotation_index \\ nil, + triptych_pane \\ nil ) do if is_screen or not is_nil(requestor) do data = @@ -31,6 +32,7 @@ defmodule Screens.LogScreenData do |> insert_screen_side(screen_side) |> insert_requestor(requestor) |> insert_dup_rotation_index(rotation_index) + |> insert_triptych_pane(triptych_pane) log_message("[screen data request]", data) end @@ -140,4 +142,9 @@ defmodule Screens.LogScreenData do defp insert_dup_rotation_index(data, rotation_index), do: Map.put(data, :page_number, rotation_index) + + defp insert_triptych_pane(data, nil), do: data + + defp insert_triptych_pane(data, triptych_pane), + do: Map.put(data, :triptych_pane, triptych_pane) end diff --git a/lib/screens/util.ex b/lib/screens/util.ex index 158f6a3b1..375a73889 100644 --- a/lib/screens/util.ex +++ b/lib/screens/util.ex @@ -167,6 +167,8 @@ defmodule Screens.Util do def outdated?("DUP-" <> _, _), do: false + def outdated?("TRI-" <> _, _), do: false + def outdated?(screen_id, client_refresh_timestamp) do {:ok, client_refresh_time, _} = DateTime.from_iso8601(client_refresh_timestamp) refresh_if_loaded_before_time = State.refresh_if_loaded_before(screen_id) diff --git a/lib/screens/v2/candidate_generator/triptych.ex b/lib/screens/v2/candidate_generator/triptych.ex index 4152a7eca..53e987438 100644 --- a/lib/screens/v2/candidate_generator/triptych.ex +++ b/lib/screens/v2/candidate_generator/triptych.ex @@ -12,8 +12,9 @@ defmodule Screens.V2.CandidateGenerator.Triptych do def screen_template do {:screen, %{ - normal: [:header, :main_content], - takeover: [:full_screen] + screen_normal: [:header, :main_content], + screen_takeover: [:full_screen] + # TODO: design requested the ability to display three images each covering one pane, we need a separate layout variation for that }} |> Builder.build_template() end diff --git a/lib/screens_web/controllers/v2/screen_api_controller.ex b/lib/screens_web/controllers/v2/screen_api_controller.ex index 85f89a538..c1bd9aef3 100644 --- a/lib/screens_web/controllers/v2/screen_api_controller.ex +++ b/lib/screens_web/controllers/v2/screen_api_controller.ex @@ -6,7 +6,7 @@ defmodule ScreensWeb.V2.ScreenApiController do alias Screens.V2.ScreenData plug(:check_config) - plug Corsica, [origins: "*"] when action in [:show_dup, :show_triptych] + plug Corsica, [origins: "*"] when action == :show_ofm defp check_config(conn, _) do if State.ok?() do @@ -22,6 +22,7 @@ defmodule ScreensWeb.V2.ScreenApiController do is_screen = ScreensWeb.UserAgent.is_screen_conn?(conn, screen_id) screen_side = params["screen_side"] rotation_index = params["rotation_index"] + triptych_pane = params["pane"] Screens.LogScreenData.log_data_request( screen_id, @@ -29,7 +30,8 @@ defmodule ScreensWeb.V2.ScreenApiController do is_screen, params["requestor"], screen_side, - rotation_index + rotation_index, + triptych_pane ) cond do @@ -79,9 +81,7 @@ defmodule ScreensWeb.V2.ScreenApiController do end end - def show_dup(conn, params), do: show(conn, params) - - def show_triptych(conn, params), do: show(conn, params) + def show_ofm(conn, params), do: show(conn, params) def simulation(conn, %{"id" => screen_id, "last_refresh" => last_refresh} = params) do Screens.LogScreenData.log_data_request( diff --git a/lib/screens_web/controllers/v2/screen_controller.ex b/lib/screens_web/controllers/v2/screen_controller.ex index 6a20c7244..76b4b99d7 100644 --- a/lib/screens_web/controllers/v2/screen_controller.ex +++ b/lib/screens_web/controllers/v2/screen_controller.ex @@ -5,7 +5,7 @@ defmodule ScreensWeb.V2.ScreenController do alias Screens.V2.ScreenData.Parameters @default_app_id :bus_eink - @recognized_app_ids ~w[bus_eink_v2 bus_shelter_v2 dup_v2 gl_eink_v2 solari_v2 solari_large_v2 pre_fare_v2]a + @recognized_app_ids ~w[bus_eink_v2 bus_shelter_v2 dup_v2 gl_eink_v2 solari_v2 solari_large_v2 pre_fare_v2 triptych_v2]a @app_id_strings Enum.map(@recognized_app_ids, &Atom.to_string/1) plug(:check_config) @@ -54,6 +54,15 @@ defmodule ScreensWeb.V2.ScreenController do end end + defp triptych_pane(params) do + case params["pane"] do + "left" -> "left" + "middle" -> "middle" + "right" -> "right" + _ -> nil + end + end + def index(conn, %{"id" => app_id}) when app_id in @app_id_strings do app_id = String.to_existing_atom(app_id) @@ -95,6 +104,7 @@ defmodule ScreensWeb.V2.ScreenController do |> assign(:requestor, params["requestor"]) |> assign(:disable_sentry, params["disable_sentry"]) |> assign(:rotation_index, rotation_index(params)) + |> assign(:triptych_pane, triptych_pane(params)) |> put_view(ScreensWeb.V2.ScreenView) |> render("index.html") diff --git a/lib/screens_web/router.ex b/lib/screens_web/router.ex index 3b8e0b1e9..f10711d64 100644 --- a/lib/screens_web/router.ex +++ b/lib/screens_web/router.ex @@ -86,8 +86,7 @@ defmodule ScreensWeb.Router do get "/:id", ScreenApiController, :show get "/:id/simulation", ScreenApiController, :simulation - get "/:id/dup", ScreenApiController, :show_dup - get "/:id/triptych", ScreenApiController, :show_triptych + get "/:id/ofm", ScreenApiController, :show_ofm post "/log_frontend_error", ScreenApiController, :log_frontend_error end diff --git a/lib/screens_web/templates/v2/screen/index.html.eex b/lib/screens_web/templates/v2/screen/index.html.eex index e7d40afee..fd5de454f 100644 --- a/lib/screens_web/templates/v2/screen/index.html.eex +++ b/lib/screens_web/templates/v2/screen/index.html.eex @@ -16,6 +16,9 @@ <%= if not is_nil(@rotation_index) do %> data-rotation-index="<%= @rotation_index %>" <% end %> + <%= if not is_nil(@triptych_pane) do %> + data-triptych-pane="<%= @triptych_pane %>" + <% end %> <%= if assigns[:screenplay_fullstory_org_id] do %> data-screenplay-fullstory-org-id="<%= assigns[:screenplay_fullstory_org_id] %>" <% end %> From 89b2d5b2b824bb7f02fb4bae4fef353cda7daeb4 Mon Sep 17 00:00:00 2001 From: Jon Zimbel Date: Fri, 11 Aug 2023 12:56:42 -0400 Subject: [PATCH 04/30] Remove TODO--made an asana task for it --- lib/screens/v2/candidate_generator/triptych.ex | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/screens/v2/candidate_generator/triptych.ex b/lib/screens/v2/candidate_generator/triptych.ex index 53e987438..9afa2289e 100644 --- a/lib/screens/v2/candidate_generator/triptych.ex +++ b/lib/screens/v2/candidate_generator/triptych.ex @@ -14,7 +14,6 @@ defmodule Screens.V2.CandidateGenerator.Triptych do %{ screen_normal: [:header, :main_content], screen_takeover: [:full_screen] - # TODO: design requested the ability to display three images each covering one pane, we need a separate layout variation for that }} |> Builder.build_template() end From 83dbcfb9560f67477557b7c3da0601005155ba8a Mon Sep 17 00:00:00 2001 From: Jon Zimbel Date: Fri, 11 Aug 2023 12:59:23 -0400 Subject: [PATCH 05/30] Use matching param for triptych pane in data requests --- assets/src/hooks/v2/use_api_response.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/assets/src/hooks/v2/use_api_response.tsx b/assets/src/hooks/v2/use_api_response.tsx index 9263fa3a9..a16bc0817 100644 --- a/assets/src/hooks/v2/use_api_response.tsx +++ b/assets/src/hooks/v2/use_api_response.tsx @@ -165,7 +165,7 @@ const useBaseApiResponse = ({ let apiPath = `/v2/api/screen/${id}${routePart}?last_refresh=${lastRefresh}${isRealScreenParam}${screenSideParam}${requestorParam}`; if (isOFM()) { - apiPath = `https://screens.mbta.com${apiPath}&rotation_index=${ROTATION_INDEX}&triptych_pane=${TRIPTYCH_PANE}`; + apiPath = `http://localhost:4000${apiPath}&rotation_index=${ROTATION_INDEX}&pane=${TRIPTYCH_PANE}`; } if (screenIdsWithOffsetMap) { From 7182494ed9e4ec251934d2aedb471e6688b993eb Mon Sep 17 00:00:00 2001 From: Jon Zimbel Date: Fri, 11 Aug 2023 16:18:03 -0400 Subject: [PATCH 06/30] Cleanup and fix accidentally committed test code --- assets/css/v2/triptych/screen_container.scss | 9 --------- assets/src/components/v2/dup/README.md | 1 + assets/src/components/v2/triptych/README.md | 1 + assets/src/components/v2/triptych/version.tsx | 2 +- assets/src/hooks/v2/use_api_response.tsx | 9 +++++++-- assets/src/util/util.tsx | 14 ++++++++++++++ 6 files changed, 24 insertions(+), 12 deletions(-) diff --git a/assets/css/v2/triptych/screen_container.scss b/assets/css/v2/triptych/screen_container.scss index ce6e2959b..8e74132f6 100644 --- a/assets/css/v2/triptych/screen_container.scss +++ b/assets/css/v2/triptych/screen_container.scss @@ -5,12 +5,3 @@ margin: 0px auto; overflow: hidden; } - -.screen-container-blink { - position: absolute; - top: 0; - left: 0; - width: 100%; - height: 100%; - background-color: black; -} diff --git a/assets/src/components/v2/dup/README.md b/assets/src/components/v2/dup/README.md index d464da7a5..1564e7f62 100644 --- a/assets/src/components/v2/dup/README.md +++ b/assets/src/components/v2/dup/README.md @@ -45,6 +45,7 @@ zip -r dup-app-${ROTATION_INDEX}.zip dup_v2.css polyfills.js dup_v2.js fonts images dup-app.html template.json dup_preview.png done ``` +- On completion, the packaged client apps will be saved at `priv/static/dup-app-(0|1|2).zip`. - Commit the version bump on a branch, push it, and create a PR to mark the deploy. ## Debugging diff --git a/assets/src/components/v2/triptych/README.md b/assets/src/components/v2/triptych/README.md index 419faa8ea..5b7ecdeca 100644 --- a/assets/src/components/v2/triptych/README.md +++ b/assets/src/components/v2/triptych/README.md @@ -45,6 +45,7 @@ zip -r triptych-app-${PANE}.zip triptych_v2.css polyfills.js triptych_v2.js fonts images triptych-app.html template.json triptych_preview.png done ``` +- On completion, the packaged client apps will be saved at `priv/static/triptych-app-(left|middle|right).zip`. - Commit the version bump on a branch, push it, and create a PR to mark the deploy. ## Debugging diff --git a/assets/src/components/v2/triptych/version.tsx b/assets/src/components/v2/triptych/version.tsx index 332e19b5e..f3b01315a 100644 --- a/assets/src/components/v2/triptych/version.tsx +++ b/assets/src/components/v2/triptych/version.tsx @@ -1 +1 @@ -export const TRIPTYCH_VERSION = "23.8.10.1"; +export const TRIPTYCH_VERSION = "23.8.11.1"; diff --git a/assets/src/hooks/v2/use_api_response.tsx b/assets/src/hooks/v2/use_api_response.tsx index a16bc0817..f3fc09dec 100644 --- a/assets/src/hooks/v2/use_api_response.tsx +++ b/assets/src/hooks/v2/use_api_response.tsx @@ -2,7 +2,7 @@ import { WidgetData } from "Components/v2/widget"; import useDriftlessInterval from "Hooks/use_driftless_interval"; import React, { useEffect, useState } from "react"; import { getDataset, getDatasetValue } from "Util/dataset"; -import { getScreenSide, isOFM, isRealScreen } from "Util/util"; +import { getScreenSide, isDUP, isOFM, isRealScreen, isTriptych } from "Util/util"; import * as SentryLogger from "Util/sentry"; import { ROTATION_INDEX } from "Components/v2/dup/rotation_index"; import { TRIPTYCH_PANE } from "Components/v2/triptych/pane"; @@ -165,7 +165,12 @@ const useBaseApiResponse = ({ let apiPath = `/v2/api/screen/${id}${routePart}?last_refresh=${lastRefresh}${isRealScreenParam}${screenSideParam}${requestorParam}`; if (isOFM()) { - apiPath = `http://localhost:4000${apiPath}&rotation_index=${ROTATION_INDEX}&pane=${TRIPTYCH_PANE}`; + apiPath = `https://screens.mbta.com${apiPath}`; + if (isDUP()) { + apiPath = `${apiPath}&rotation_index=${ROTATION_INDEX}`; + } else if (isTriptych()) { + apiPath = `${apiPath}&pane=${TRIPTYCH_PANE}`; + } } if (screenIdsWithOffsetMap) { diff --git a/assets/src/util/util.tsx b/assets/src/util/util.tsx index c45cd1dc2..93901c71b 100644 --- a/assets/src/util/util.tsx +++ b/assets/src/util/util.tsx @@ -33,6 +33,20 @@ export const formatTimeString = (timeString: string) => */ export const isOFM = () => location.href.startsWith("file:"); +/** + * Returns true if this client is running on a DUP screen. + * + * Use this for DUP-specific logic. + */ +export const isDUP = () => /^file:.*dup-app.*/.test(location.href); + +/** + * Returns true if this client is running on a triptych screen. + * + * Use this for triptych-specific logic. + */ +export const isTriptych = () => /^file:.*triptych-app.*/.test(location.href); + export const imagePath = (fileName: string): string => isOFM() ? `images/${fileName}` : `/images/${fileName}`; From 5ece363adc720eed215dbbdb7540f7095fd654ed Mon Sep 17 00:00:00 2001 From: Jon Zimbel Date: Wed, 23 Aug 2023 13:54:51 -0400 Subject: [PATCH 07/30] Working triptych frontend with new route + cleaned up OFM utilities --- .gitignore | 1 + assets/css/dup_v2.scss | 3 + assets/css/triptych_v2.scss | 3 + assets/css/v2/dup/debug.scss | 26 +-- assets/css/v2/outfront_common/debug.scss | 23 +++ assets/css/v2/triptych/debug.scss | 8 + assets/src/apps/dup.tsx | 4 +- assets/src/apps/v2/dup.tsx | 8 +- assets/src/apps/v2/triptych.tsx | 10 +- assets/src/components/dup/dup_screen_page.tsx | 8 +- .../src/components/dup/screen_container.tsx | 6 +- assets/src/components/v2/dup/README.md | 40 +++- assets/src/components/v2/dup/no_data.tsx | 4 +- .../src/components/v2/dup/normal_header.tsx | 4 +- .../components/v2/dup/page_load_no_data.tsx | 8 +- assets/src/components/v2/dup/viewport.tsx | 2 +- assets/src/components/v2/screen_container.tsx | 22 ++- assets/src/components/v2/triptych/README.md | 46 +++-- assets/src/components/v2/triptych/pane.tsx | 1 - .../src/components/v2/triptych/viewport.tsx | 2 +- assets/src/hooks/outfront.tsx | 9 + assets/src/hooks/use_api_response.tsx | 3 +- assets/src/hooks/use_outfront_player_name.tsx | 39 ---- assets/src/hooks/use_outfront_station.tsx | 39 ---- assets/src/hooks/v2/use_api_response.tsx | 55 ++++-- assets/src/util/outfront.tsx | 174 ++++++++++++++++++ assets/src/util/sentry.tsx | 3 +- assets/src/util/util.tsx | 51 +---- config/config.exs | 2 + config/dev.exs | 4 +- config/test.exs | 1 + docs/devcontainer_development.md | 1 + docs/local_development.md | 4 +- lib/screens/application.ex | 5 +- lib/screens/triptych_player.ex | 17 ++ lib/screens/triptych_player/state.ex | 65 +++++++ .../triptych_player/state/local_fetch.ex | 20 ++ lib/screens/triptych_player/state/s3_fetch.ex | 53 ++++++ .../triptych_player/state/supervisor.ex | 17 ++ .../controllers/v2/screen_api_controller.ex | 14 +- lib/screens_web/router.ex | 3 +- priv/triptych_template.json | 2 +- 42 files changed, 577 insertions(+), 233 deletions(-) create mode 100644 assets/css/v2/outfront_common/debug.scss create mode 100644 assets/css/v2/triptych/debug.scss delete mode 100644 assets/src/components/v2/triptych/pane.tsx create mode 100644 assets/src/hooks/outfront.tsx delete mode 100644 assets/src/hooks/use_outfront_player_name.tsx delete mode 100644 assets/src/hooks/use_outfront_station.tsx create mode 100644 assets/src/util/outfront.tsx create mode 100644 lib/screens/triptych_player.ex create mode 100644 lib/screens/triptych_player/state.ex create mode 100644 lib/screens/triptych_player/state/local_fetch.ex create mode 100644 lib/screens/triptych_player/state/s3_fetch.ex create mode 100644 lib/screens/triptych_player/state/supervisor.ex diff --git a/.gitignore b/.gitignore index 5c2e8ccb1..9bb59c079 100644 --- a/.gitignore +++ b/.gitignore @@ -36,6 +36,7 @@ npm-debug.log # Local config files /priv/local.json /priv/signs_ui_config.json +/priv/triptych_player_to_screen_id.json # local environment variables /priv/local.env diff --git a/assets/css/dup_v2.scss b/assets/css/dup_v2.scss index 35c634731..0f8a12dc9 100644 --- a/assets/css/dup_v2.scss +++ b/assets/css/dup_v2.scss @@ -31,7 +31,10 @@ @import "v2/multi_screen_page"; @import "v2/dup/simulation"; + +@import "v2/outfront_common/debug"; @import "v2/dup/debug"; + @import "v2/dup/empty_state"; body { diff --git a/assets/css/triptych_v2.scss b/assets/css/triptych_v2.scss index 557d82811..39aaa65ee 100644 --- a/assets/css/triptych_v2.scss +++ b/assets/css/triptych_v2.scss @@ -15,6 +15,9 @@ @import "v2/placeholder"; +@import "v2/outfront_common/debug"; +@import "v2/triptych/debug"; + body { margin: 0px; } diff --git a/assets/css/v2/dup/debug.scss b/assets/css/v2/dup/debug.scss index a53d6e060..a0e6fd1c2 100644 --- a/assets/css/v2/dup/debug.scss +++ b/assets/css/v2/dup/debug.scss @@ -1,24 +1,8 @@ -#debug { - position: absolute; - bottom: 0; - right: 0; - width: 100%; - background: rgba(0, 0, 0, 0.5); - color: #fff; - display: flex; - flex-wrap: wrap; - flex-direction: column; - height: 100%; - font-size: 1.5em; - z-index: 10000; +#debug.dup { + width: 1920px; + height: 1080px; } -#debug p { - position: relative; - z-index: 9999; - font-size: 1.5em; - bottom: unset; - left: unset; - letter-spacing: normal; - font-family: Arial; +#debug .line { + width: 640px; } diff --git a/assets/css/v2/outfront_common/debug.scss b/assets/css/v2/outfront_common/debug.scss new file mode 100644 index 000000000..c44dad01d --- /dev/null +++ b/assets/css/v2/outfront_common/debug.scss @@ -0,0 +1,23 @@ +#debug { + position: absolute; + top: 0; + left: 0; + z-index: 10000; + overflow-y: hidden; + overflow-x: hidden; + + background: rgba(0, 0, 0, 0.5); + + display: flex; + flex-flow: column wrap; + + color: #fff; + font-size: 1.5em; + font-family: monospace; +} + +#debug .line { + box-sizing: border-box; + padding-left: 8px; + overflow-wrap: break-word; +} diff --git a/assets/css/v2/triptych/debug.scss b/assets/css/v2/triptych/debug.scss new file mode 100644 index 000000000..ea8b78ea8 --- /dev/null +++ b/assets/css/v2/triptych/debug.scss @@ -0,0 +1,8 @@ +#debug.triptych { + width: 1080px; + height: 1920px; +} + +#debug .line { + width: 540px; +} diff --git a/assets/src/apps/dup.tsx b/assets/src/apps/dup.tsx index 12b115963..2ff5fce77 100644 --- a/assets/src/apps/dup.tsx +++ b/assets/src/apps/dup.tsx @@ -17,10 +17,10 @@ import { MultiRotationPage, SimulationPage, } from "Components/dup/dup_screen_page"; -import { isOFM } from "Util/util"; +import { isDUP } from "Util/outfront"; const App = (): JSX.Element => { - if (isOFM()) { + if (isDUP()) { return ; } else { return ( diff --git a/assets/src/apps/v2/dup.tsx b/assets/src/apps/v2/dup.tsx index 06f4ddbd3..16ff2251d 100644 --- a/assets/src/apps/v2/dup.tsx +++ b/assets/src/apps/v2/dup.tsx @@ -37,7 +37,8 @@ import { import PageLoadNoData from "Components/v2/dup/page_load_no_data"; import NoData from "Components/v2/dup/no_data"; import OvernightDepartures from "Components/v2/dup/overnight_departures"; -import useOutfrontPlayerName from "Hooks/use_outfront_player_name"; +import { usePlayerName } from "Hooks/outfront"; +import { isDUP } from "Util/outfront"; const TYPE_TO_COMPONENT = { screen_normal: NormalScreen, @@ -122,9 +123,8 @@ const responseMapper: ResponseMapper = (apiResponse) => { }; const App = (): JSX.Element => { - const playerName = useOutfrontPlayerName(); - - if (playerName !== null) { + if (isDUP()) { + const playerName = usePlayerName()!; const id = `DUP-${playerName.trim()}`; return ( diff --git a/assets/src/apps/v2/triptych.tsx b/assets/src/apps/v2/triptych.tsx index b972e3009..263ef3a92 100644 --- a/assets/src/apps/v2/triptych.tsx +++ b/assets/src/apps/v2/triptych.tsx @@ -26,7 +26,8 @@ import TakeoverScreen from "Components/v2/triptych/takeover_screen"; import PageLoadNoData from "Components/v2/triptych/page_load_no_data"; import NoData from "Components/v2/triptych/no_data"; -import useOutfrontPlayerName from "Hooks/use_outfront_player_name"; +import { usePlayerName } from "Hooks/outfront"; +import { isTriptych } from "Util/outfront"; const TYPE_TO_COMPONENT = { screen_normal: NormalScreen, @@ -61,15 +62,14 @@ const responseMapper: ResponseMapper = (apiResponse) => { }; const App = (): JSX.Element => { - const playerName = useOutfrontPlayerName(); + if (isTriptych()) { + const playerName = usePlayerName()!; - if (playerName !== null) { - const id = `TRI-${playerName.trim()}`; return ( - + diff --git a/assets/src/components/dup/dup_screen_page.tsx b/assets/src/components/dup/dup_screen_page.tsx index c06946193..c0697d8a9 100644 --- a/assets/src/components/dup/dup_screen_page.tsx +++ b/assets/src/components/dup/dup_screen_page.tsx @@ -1,10 +1,10 @@ import React from "react"; import { useParams } from "react-router-dom"; -import useOutfrontStation from "Hooks/use_outfront_station"; import { ROTATION_INDEX } from "./rotation_index"; import { NoDataLayout } from "Components/dup/screen_container"; -import { isOFM } from "Util/util"; +import { isDUP } from "Util/outfront"; +import { useStationName } from "Hooks/outfront"; import { fetchDatasetValue } from "Util/dataset"; import { DUP_SIMULATION_REFRESH_MS } from "Constants"; @@ -13,7 +13,7 @@ const DupScreenPage = ({ }: { screenContainer: React.ComponentType; }): JSX.Element => { - const station = useOutfrontStation(); + const station = useStationName(); if (station !== null) { const id = `DUP-${station.replace(/\s/g, "")}`; @@ -37,7 +37,7 @@ const ScreenPage = ({ }: { screenContainer: React.ComponentType; }): JSX.Element => - isOFM() ? ( + isDUP() ? ( ) : ( diff --git a/assets/src/components/dup/screen_container.tsx b/assets/src/components/dup/screen_container.tsx index b04844bfb..6a0b924b1 100644 --- a/assets/src/components/dup/screen_container.tsx +++ b/assets/src/components/dup/screen_container.tsx @@ -6,11 +6,11 @@ import PartialAlerts from "Components/dup/partial_alert"; import FreeText from "Components/dup/free_text"; import useApiResponse from "Hooks/use_api_response"; -import useOutfrontStation from "Hooks/use_outfront_station"; import useCurrentPage from "Hooks/use_current_dup_page"; import { formatTimeString, classWithModifier, imagePath } from "Util/util"; import Loading from "Components/v2/bundled_svg/loading"; +import { useStationName } from "Hooks/outfront"; const LinkArrow = ({ width, color }) => { const height = 40; @@ -66,7 +66,7 @@ const REPLACEMENTS = { }; const NoDataLayout = ({ code }: { code?: string }): JSX.Element => { - let stationName = useOutfrontStation() || "Transit information"; + let stationName = useStationName(); stationName = REPLACEMENTS[stationName] || stationName; return ( @@ -94,7 +94,7 @@ const NoDataLayout = ({ code }: { code?: string }): JSX.Element => { }; const LoadingLayout = (): JSX.Element => { - let stationName = useOutfrontStation() || "Transit information"; + let stationName = useStationName(); stationName = REPLACEMENTS[stationName] || stationName; return ( diff --git a/assets/src/components/v2/dup/README.md b/assets/src/components/v2/dup/README.md index 1564e7f62..ba0911949 100644 --- a/assets/src/components/v2/dup/README.md +++ b/assets/src/components/v2/dup/README.md @@ -1,7 +1,7 @@ # DUP app packaging v2 - Ensure [Corsica](https://hexdocs.pm/corsica/Corsica.html) is used on the server to allow CORS requests (ideally limited to just the DUP-relevant routes). It should already be configured at [this line](/lib/screens_web/controllers/v2/screen_api_controller.ex#L9) in the API controller--if it is, you don't need to do anything for this step. -- Double check that any behavior specific to the DUP screen environment happens inside of an `isOFM()` check. This includes: +- Double check that any behavior specific to the DUP screen environment happens inside of an `isDUP()` or `isOFM()` check. This includes: - `buildApiPath` in use_api_response.tsx should return a full URL for the API path: prefix `apiPath` string with "https://screens.mbta.com". - `imagePath` in util.tsx should return relative paths (no leading `/`). - Create priv/static/dup-app.html if it doesn’t already exist. Copy paste the following contents in: @@ -32,16 +32,25 @@ - Set the version string in assets/src/components/v2/dup/version.tsx to `current_year.current_month.current_day.1`. - In assets/webpack.config.js, change `publicPath` in the font config to have value `'fonts/'`. - **Only if you are packaging for local testing** - - replace `const playerName = useOutfrontPlayerName();` in assets/src/apps/v2/dup.tsx with `const playerName = "BRW-DUP-005";` (or any other player name from one of the DUP screen IDs (`DUP-${playerName}`)). This data is provided by Outfront's "wrapper" app that runs on the real DUP screens, but we need to set it ourselves during testing. Think of it as a sort of frontend environment variable. - - replace `apiPath = "https://screens.mbta.com...";` in assets/src/hooks/v2/use_api_response.tsx with `apiPath = "http://localhost:4000...";`. + - add the following to the top of assets/src/apps/v2/dup.tsx, filling in the string values: + ```ts + import { __TEST_setFakeMRAID__ } from "Util/outfront"; + __TEST_setFakeMRAID__({ + playerName: "", + station: "" + }); + ``` + This sets up a fake MRAID object that emulates the real one available to the client when running on Outfront screens. + The MRAID object gives our client info about which screen it's running on. + - replace the definition of `getOutfrontAbsolutePath` in assets/src/hooks/v2/use_api_response.tsx with `const getOutfrontAbsolutePath = () => isOFM() ? "http://localhost:4000" : "";`. - `cd` to priv/static and run the following: ```sh for ROTATION_INDEX in {0..2}; do - echo "export const ROTATION_INDEX = ${ROTATION_INDEX};" > ../../assets/src/components/v2/dup/rotation_index.tsx - npm --prefix ../../assets run deploy - cp -r css/dup_v2.css js/polyfills.js js/dup_v2.js ../dup_preview.png . - cp ../dup_template.json ./template.json - sed -i "" "s/DUP APP ./DUP APP ${ROTATION_INDEX}/" template.json + echo "export const ROTATION_INDEX = ${ROTATION_INDEX};" > ../../assets/src/components/v2/dup/rotation_index.tsx && \ + npm --prefix ../../assets run deploy && \ + cp -r css/dup_v2.css js/polyfills.js js/dup_v2.js ../dup_preview.png . && \ + cp ../dup_template.json ./template.json && \ + sed -i "" "s/DUP APP ./DUP APP ${ROTATION_INDEX}/" template.json && \ zip -r dup-app-${ROTATION_INDEX}.zip dup_v2.css polyfills.js dup_v2.js fonts images dup-app.html template.json dup_preview.png done ``` @@ -54,8 +63,21 @@ To assist with debugging on the DUP screens, you can paste this at the module sc show up on the screen: ```js +const Counter = (() => { + let n = 0; + + return { + next() { + let cur = n; + n = (n + 1) % 100; + return `${cur.toString().padStart(2, "0")}`; + } + }; +})(); + const dEl = document.createElement("div"); dEl.id = "debug"; +dEl.className = "dup"; document.body.appendChild(dEl); // save the original console.log function const old_logger = console.log; @@ -78,6 +100,6 @@ console.log = function (...msgs) { .join(" "); // add the log to the html element. - html_logger.innerHTML += "
" + text + "
"; + html_logger.innerHTML = `
${Counter.next()} ${text}
${html_logger.innerHTML}`; }; ``` diff --git a/assets/src/components/v2/dup/no_data.tsx b/assets/src/components/v2/dup/no_data.tsx index 0bcc28474..20a07cf9d 100644 --- a/assets/src/components/v2/dup/no_data.tsx +++ b/assets/src/components/v2/dup/no_data.tsx @@ -1,8 +1,8 @@ -import useOutfrontStation from "Hooks/use_outfront_station"; import React, { ComponentType } from "react"; import { imagePath } from "Util/util"; import LinkArrow from "../bundled_svg/link_arrow"; import NormalHeader from "./normal_header"; +import { useStationName } from "Hooks/outfront"; // Fix station name tags without rider-facing names export const REPLACEMENTS = { @@ -15,7 +15,7 @@ interface Props { } const NoData: ComponentType = ({ include_header }) => { - let stationName = useOutfrontStation() || "Transit information"; + let stationName = useStationName() || "Transit information"; stationName = REPLACEMENTS[stationName] || stationName; return ( diff --git a/assets/src/components/v2/dup/normal_header.tsx b/assets/src/components/v2/dup/normal_header.tsx index 55f64cd3e..953657306 100644 --- a/assets/src/components/v2/dup/normal_header.tsx +++ b/assets/src/components/v2/dup/normal_header.tsx @@ -2,7 +2,7 @@ import React from "react"; import DefaultNormalHeader, { Icon } from "Components/v2/normal_header"; import { DUP_VERSION } from "Components/v2/dup/version"; -import useOutfrontPlayerName from "Hooks/use_outfront_player_name"; +import { usePlayerName } from "Hooks/outfront"; interface NormalHeaderProps { text: string; @@ -19,7 +19,7 @@ const NormalHeader = ({ accentPattern, code, }: NormalHeaderProps) => { - const playerName = useOutfrontPlayerName(); + const playerName = usePlayerName(); let version = DUP_VERSION; if (playerName) { version = `${version}-${playerName}`; diff --git a/assets/src/components/v2/dup/page_load_no_data.tsx b/assets/src/components/v2/dup/page_load_no_data.tsx index 4487726e2..bca4119c6 100644 --- a/assets/src/components/v2/dup/page_load_no_data.tsx +++ b/assets/src/components/v2/dup/page_load_no_data.tsx @@ -1,14 +1,14 @@ -import useOutfrontStation from "Hooks/use_outfront_station"; import React, { ComponentType } from "react"; import LinkArrow from "../bundled_svg/link_arrow"; import Loading from "../../../../static/images/svgr_bundled/loading.svg"; import NormalHeader from "./normal_header"; import { REPLACEMENTS } from "./no_data"; +import { useStationName } from "Hooks/outfront"; const PageLoadNoData: ComponentType = () => { - let stationName = useOutfrontStation() || "Transit information"; + let stationName = useStationName() || "Transit information"; stationName = REPLACEMENTS[stationName] || stationName; - + return (
@@ -31,4 +31,4 @@ const PageLoadNoData: ComponentType = () => { ); }; -export default PageLoadNoData; \ No newline at end of file +export default PageLoadNoData; diff --git a/assets/src/components/v2/dup/viewport.tsx b/assets/src/components/v2/dup/viewport.tsx index ababc1460..95c9ae3b9 100644 --- a/assets/src/components/v2/dup/viewport.tsx +++ b/assets/src/components/v2/dup/viewport.tsx @@ -1,6 +1,6 @@ import React from "react"; -import { getRotationIndex } from "Util/util"; +import { getRotationIndex } from "Util/outfront"; /** * Shifts one of the three rotations into view diff --git a/assets/src/components/v2/screen_container.tsx b/assets/src/components/v2/screen_container.tsx index 4caea3b9d..33e4456a7 100644 --- a/assets/src/components/v2/screen_container.tsx +++ b/assets/src/components/v2/screen_container.tsx @@ -9,12 +9,13 @@ import React, { import useApiResponse, { ApiResponse, SimulationApiResponse, - useOFMApiResponse, + useDUPApiResponse, + useTriptychApiResponse, } from "Hooks/v2/use_api_response"; import WidgetTreeErrorBoundary from "Components/v2/widget_tree_error_boundary"; import Widget, { WidgetData } from "Components/v2/widget"; import useAudioReadout from "Hooks/v2/use_audio_readout"; -import { isOFM } from "Util/util"; +import { isDUP, isOFM, isTriptych } from "Util/outfront"; type ResponseMapper = ( apiResponse: ApiResponse @@ -95,23 +96,34 @@ const ScreenLayout: ComponentType = ({ const responseMapper = useContext(ResponseMapperContext); const ErrorBoundaryOrFragment = isOFM() ? Fragment : WidgetTreeErrorBoundary; + const widgetData = responseMapper(apiResponse); + return (
- {apiResponse && } + {apiResponse && } {showBlink &&
}
); }; +const getApiResponseHook = () => { + if (isDUP()) { + return useDUPApiResponse; + } else if (isTriptych()) { + return useTriptychApiResponse; + } else { + return useApiResponse; + } +}; + const ScreenContainer = ({ id }) => { const blinkConfig = useContext(BlinkConfigContext); const audioConfig = useContext(AudioConfigContext); const [showBlink, setShowBlink] = useState(false); - const hook = isOFM() ? useOFMApiResponse : useApiResponse; - const { apiResponse, requestCount, lastSuccess } = hook({ id }); + const { apiResponse, requestCount, lastSuccess } = getApiResponseHook()({ id }); useAudioReadout({ id, config: audioConfig }); diff --git a/assets/src/components/v2/triptych/README.md b/assets/src/components/v2/triptych/README.md index 5b7ecdeca..158aac909 100644 --- a/assets/src/components/v2/triptych/README.md +++ b/assets/src/components/v2/triptych/README.md @@ -1,7 +1,7 @@ # Triptych app packaging - Ensure [Corsica](https://hexdocs.pm/corsica/Corsica.html) is used on the server to allow CORS requests (ideally limited to just the triptych-relevant routes). It should already be configured at [this line](/lib/screens_web/controllers/v2/screen_api_controller.ex#L9) in the API controller--if it is, you don't need to do anything for this step. -- Double check that any behavior specific to the triptych screen environment happens inside of an `isOFM()` check. This includes: +- Double check that any behavior specific to the triptych screen environment happens inside of an `isTriptych()` or `isOFM()` check. This includes: - `buildApiPath` in use_api_response.tsx should return a full URL for the API path: prefix `apiPath` string with "https://screens.mbta.com". - `imagePath` in util.tsx should return relative paths (no leading `/`). - Create priv/static/triptych-app.html if it doesn’t already exist. Copy paste the following contents in: @@ -32,20 +32,27 @@ - Set the version string in assets/src/components/v2/triptych/version.tsx to `current_year.current_month.current_day.1`. - In assets/webpack.config.js, change `publicPath` in the font config to have value `'fonts/'`. - **Only if you are packaging for local testing** - - replace `const playerName = useOutfrontPlayerName();` in assets/src/apps/v2/triptych.tsx with `const playerName = "BKB-TRI-001";` (or any other player name from one of the triptych screen IDs (`TRI-${playerName}`)). This data is provided by Outfront's "wrapper" app that runs on the real triptych screens, but we need to set it ourselves during testing. Think of it as a sort of frontend environment variable. - - replace `apiPath = "https://screens.mbta.com...";` in assets/src/hooks/v2/use_api_response.tsx with `apiPath = "http://localhost:4000...";`. + - add the following to the top of assets/src/apps/v2/triptych.tsx, filling in the string values: + ```ts + import { __TEST_setFakeMRAID__ } from "Util/outfront"; + __TEST_setFakeMRAID__({ + playerName: "
", + station: "", + triptychPane: "" + }); + ``` + This sets up a fake MRAID object that emulates the real one available to the client when running on Outfront screens. + The MRAID object gives our client info about which screen it's running on. + - replace the definition of `getOutfrontAbsolutePath` in assets/src/hooks/v2/use_api_response.tsx with `const getOutfrontAbsolutePath = () => isOFM() ? "http://localhost:4000" : "";`. + - make sure priv/triptych_player_to_screen_id.json mirrors mbta-ctd-config/screens/triptych_player_to_screen_id-prod.json, or at least contains a mapping for the `playerName` that you hardcoded two steps ago. - `cd` to priv/static and run the following: ```sh - for PANE in left middle right; do - echo "export const TRIPTYCH_PANE = \"${PANE}\";" > ../../assets/src/components/v2/triptych/pane.tsx - npm --prefix ../../assets run deploy - cp -r css/triptych_v2.css js/polyfills.js js/triptych_v2.js ../triptych_preview.png . - cp ../triptych_template.json ./template.json - sed -i "" -E "s/TRIPTYCH APP [[:alpha:]]+/TRIPTYCH APP $(echo $PANE | tr 'a-z' 'A-Z')/" template.json - zip -r triptych-app-${PANE}.zip triptych_v2.css polyfills.js triptych_v2.js fonts images triptych-app.html template.json triptych_preview.png - done + npm --prefix ../../assets run deploy && \ + cp -r css/triptych_v2.css js/polyfills.js js/triptych_v2.js ../triptych_preview.png . && \ + cp ../triptych_template.json ./template.json && \ + zip -r triptych-app.zip triptych_v2.css polyfills.js triptych_v2.js fonts images triptych-app.html template.json triptych_preview.png ``` -- On completion, the packaged client apps will be saved at `priv/static/triptych-app-(left|middle|right).zip`. +- On completion, the packaged client app will be saved at `priv/static/triptych-app.zip`. - Commit the version bump on a branch, push it, and create a PR to mark the deploy. ## Debugging @@ -54,8 +61,21 @@ To assist with debugging on the triptych screens, you can paste this at the modu show up on the screen: ```js +const Counter = (() => { + let n = 0; + + return { + next() { + let cur = n; + n = (n + 1) % 100; + return `${cur.toString().padStart(2, "0")}`; + } + }; +})(); + const dEl = document.createElement("div"); dEl.id = "debug"; +dEl.className = "triptych"; document.body.appendChild(dEl); // save the original console.log function const old_logger = console.log; @@ -78,6 +98,6 @@ console.log = function (...msgs) { .join(" "); // add the log to the html element. - html_logger.innerHTML += "
" + text + "
"; + html_logger.innerHTML = `
${Counter.next()} ${text}
${html_logger.innerHTML}`; }; ``` diff --git a/assets/src/components/v2/triptych/pane.tsx b/assets/src/components/v2/triptych/pane.tsx deleted file mode 100644 index ab442d3df..000000000 --- a/assets/src/components/v2/triptych/pane.tsx +++ /dev/null @@ -1 +0,0 @@ -export const TRIPTYCH_PANE = "left"; diff --git a/assets/src/components/v2/triptych/viewport.tsx b/assets/src/components/v2/triptych/viewport.tsx index 23d7946f6..cec2966a2 100644 --- a/assets/src/components/v2/triptych/viewport.tsx +++ b/assets/src/components/v2/triptych/viewport.tsx @@ -1,6 +1,6 @@ import React from "react"; -import { getTriptychPane } from "Util/util"; +import { getTriptychPane } from "Util/outfront"; /** * Shifts one of the three triptych panes into view diff --git a/assets/src/hooks/outfront.tsx b/assets/src/hooks/outfront.tsx new file mode 100644 index 000000000..c9bd42b86 --- /dev/null +++ b/assets/src/hooks/outfront.tsx @@ -0,0 +1,9 @@ +import { useMemo } from "react"; +import { getMRAID, getPlayerName, getStationName, getTriptychPane } from "Util/outfront"; +import type { TriptychPane } from "Util/outfront"; + +export const usePlayerName = (): string | null => useMemo(getPlayerName, [getMRAID()]); + +export const useTriptychPane = (): TriptychPane | null => useMemo(getTriptychPane, [getMRAID()]); + +export const useStationName = (): string | null => useMemo(getStationName, [getMRAID()]); diff --git a/assets/src/hooks/use_api_response.tsx b/assets/src/hooks/use_api_response.tsx index 137d77a63..9488c4351 100644 --- a/assets/src/hooks/use_api_response.tsx +++ b/assets/src/hooks/use_api_response.tsx @@ -1,5 +1,6 @@ import { useEffect, useState } from "react"; -import { isOFM, isRealScreen } from "Util/util"; +import { isOFM } from "Util/outfront"; +import { isRealScreen } from "Util/util"; import useInterval from "Hooks/use_interval"; import { getDatasetValue } from "Util/dataset"; import * as SentryLogger from "Util/sentry"; diff --git a/assets/src/hooks/use_outfront_player_name.tsx b/assets/src/hooks/use_outfront_player_name.tsx deleted file mode 100644 index d635f566d..000000000 --- a/assets/src/hooks/use_outfront_player_name.tsx +++ /dev/null @@ -1,39 +0,0 @@ -import { useEffect, useState } from "react"; - -const useOutfrontTags = () => { - const [tags, setTags] = useState(null); - - let mraid; - - try { - mraid = parent?.parent?.mraid; - } catch (_) {} - - if (mraid) { - useEffect(() => { - if (parent?.parent?.mraid ?? false) { - try { - const rawTags = parent.parent.mraid.getTags(); - setTags(JSON.parse(rawTags).tags); - } catch (err) { - setTags(null); - } - } - }, [parent?.parent?.mraid]); - } - - return tags; -}; - -const useOutfrontPlayerName = () => { - const tags = useOutfrontTags(); - if (tags !== null) { - const playerName = - tags.find(({ name }) => name === "player_name")?.value?.[0] ?? null; - return playerName; - } else { - return null; - } -}; - -export default useOutfrontPlayerName; diff --git a/assets/src/hooks/use_outfront_station.tsx b/assets/src/hooks/use_outfront_station.tsx deleted file mode 100644 index 7c8bd5272..000000000 --- a/assets/src/hooks/use_outfront_station.tsx +++ /dev/null @@ -1,39 +0,0 @@ -import { useEffect, useState } from "react"; - -const useOutfrontTags = () => { - const [tags, setTags] = useState(null); - - let mraid; - - try { - mraid = parent?.parent?.mraid; - } catch (_) {} - - if (mraid) { - useEffect(() => { - if (parent?.parent?.mraid ?? false) { - try { - const rawTags = parent.parent.mraid.getTags(); - setTags(JSON.parse(rawTags).tags); - } catch (err) { - setTags(null); - } - } - }, [parent?.parent?.mraid]); - } - - return tags; -}; - -const useOutfrontStation = () => { - const tags = useOutfrontTags(); - if (tags !== null) { - const station = - tags.find(({ name }) => name === "Station")?.value?.[0] ?? null; - return station; - } else { - return null; - } -}; - -export default useOutfrontStation; diff --git a/assets/src/hooks/v2/use_api_response.tsx b/assets/src/hooks/v2/use_api_response.tsx index f3fc09dec..8ec245e59 100644 --- a/assets/src/hooks/v2/use_api_response.tsx +++ b/assets/src/hooks/v2/use_api_response.tsx @@ -2,10 +2,10 @@ import { WidgetData } from "Components/v2/widget"; import useDriftlessInterval from "Hooks/use_driftless_interval"; import React, { useEffect, useState } from "react"; import { getDataset, getDatasetValue } from "Util/dataset"; -import { getScreenSide, isDUP, isOFM, isRealScreen, isTriptych } from "Util/util"; +import { isDUP, isOFM, isTriptych, getTriptychPane } from "Util/outfront"; +import { getScreenSide, isRealScreen } from "Util/util"; import * as SentryLogger from "Util/sentry"; import { ROTATION_INDEX } from "Components/v2/dup/rotation_index"; -import { TRIPTYCH_PANE } from "Components/v2/triptych/pane"; const MINUTE_IN_MS = 60_000; @@ -119,7 +119,6 @@ const getScreenSideParam = () => { }; const getRequestorParam = () => { - // Adding this to v2 because we will eventually widgetize DUPs. if (isOFM()) return `&requestor=real_screen`; let requestor = getDatasetValue("requestor"); @@ -130,6 +129,23 @@ const getRequestorParam = () => { return requestor ? `&requestor=${requestor}` : ""; }; +const getLoggingParams = () => { + if (isDUP()) { + return `&rotation_index=${ROTATION_INDEX}`; + } + + if (isTriptych()) { + const triptychPane = getTriptychPane(); + if (triptychPane != null) { + return `&pane=${triptychPane}`; + } + } + + return ""; +}; + +const getOutfrontAbsolutePath = () => isOFM() ? "https://screens.mbta.com" : ""; + interface UseApiResponseArgs { id: string; failureModeElapsedMs?: number; @@ -151,6 +167,8 @@ const useBaseApiResponse = ({ const isRealScreenParam = getIsRealScreenParam(); const screenSideParam = getScreenSideParam(); const requestorParam = getRequestorParam(); + const loggingParams = getLoggingParams(); + const outfrontAbsolutePath = getOutfrontAbsolutePath(); const [apiResponse, setApiResponse] = useState(LOADING_RESPONSE); const [requestCount, setRequestCount] = useState(0); const [lastSuccess, setLastSuccess] = useState(null); @@ -162,16 +180,7 @@ const useBaseApiResponse = ({ } = getDataset(); const refreshMs = parseInt(refreshRate, 10) * 1000; let refreshRateOffsetMs = parseInt(refreshRateOffset, 10) * 1000; - let apiPath = `/v2/api/screen/${id}${routePart}?last_refresh=${lastRefresh}${isRealScreenParam}${screenSideParam}${requestorParam}`; - - if (isOFM()) { - apiPath = `https://screens.mbta.com${apiPath}`; - if (isDUP()) { - apiPath = `${apiPath}&rotation_index=${ROTATION_INDEX}`; - } else if (isTriptych()) { - apiPath = `${apiPath}&pane=${TRIPTYCH_PANE}`; - } - } + const apiPath = `${outfrontAbsolutePath}/v2/api/screen/${id}${routePart}?last_refresh=${lastRefresh}${isRealScreenParam}${screenSideParam}${requestorParam}${loggingParams}`; if (screenIdsWithOffsetMap) { const screens = JSON.parse(screenIdsWithOffsetMap); @@ -245,15 +254,25 @@ const useSimulationApiResponse = ({ id }) => // route that's more permissive of CORS, since these clients are loaded from a local html file // (and thus their data requests to our server are cross-origin). // -// Besides the CORS stuff, this different route runs exactly the same backend logic as the normal one -// used by `useApiResponse`. -const useOFMApiResponse = ({ id }) => +// The /dup endpoint only has the CORS stuff, and otherwise runs exactly the same backend logic as +// the normal one used by `useApiResponse`. +// +// The /triptych endpoint has the CORS stuff, plus an additional step that maps the player name of +// the individual triptych pane to a screen ID representing the collective trio. +const useDUPApiResponse = ({ id }) => + useBaseApiResponse({ + id, + routePart: "/dup", + responseHandler: rawResponseToApiResponse, + }); + +const useTriptychApiResponse = ({ id }) => useBaseApiResponse({ id, - routePart: "/ofm", + routePart: "/triptych", responseHandler: rawResponseToApiResponse, }); export default useApiResponse; export { ApiResponse, SimulationApiResponse }; -export { useSimulationApiResponse, useOFMApiResponse }; +export { useSimulationApiResponse, useDUPApiResponse, useTriptychApiResponse }; diff --git a/assets/src/util/outfront.tsx b/assets/src/util/outfront.tsx new file mode 100644 index 000000000..3a77fb052 --- /dev/null +++ b/assets/src/util/outfront.tsx @@ -0,0 +1,174 @@ +import { ROTATION_INDEX } from "Components/v2/dup/rotation_index"; +import { getDatasetValue } from "Util/dataset"; + +/** + * Returns true if this client is running on an Outfront Media screen. + * (A DUP or a triptych.) + * + * Use this for OFM-specific logic that is common to both the DUP and triptych apps. + */ +export const isOFM = () => location.href.startsWith("file:"); + +/** + * Returns true if this client is running on a DUP screen. + * + * Use this for DUP-specific logic. + */ +export const isDUP = () => /^file:.*dup-app.*/.test(location.href); + +/** + * Returns true if this client is running on a triptych screen. + * + * Use this for triptych-specific logic. + */ +export const isTriptych = () => /^file:.*triptych-app.*/.test(location.href); + +type RotationIndex = "0" | "1" | "2"; +const isRotationIndex = (value: any): value is RotationIndex => { + return value === "0" || value === "1" || value === "2"; +}; + +export type TriptychPane = "left" | "middle" | "right"; +const isTriptychPane = (value: any): value is TriptychPane => { + return value === "left" || value === "middle" || value === "right"; +}; + +export const getRotationIndex = (): RotationIndex | null => { + const rotationIndex = isOFM() + ? ROTATION_INDEX.toString() + : getDatasetValue("rotationIndex"); + + return isRotationIndex(rotationIndex) ? rotationIndex : null; +}; + +export const getPlayerName = (): string | null => { + let playerName = null; + + const mraid = getMRAID(); + if (mraid) { + try { + const deviceInfoJSON = mraid.getDeviceInfo(); + const deviceInfo = JSON.parse(deviceInfoJSON); + playerName = deviceInfo.deviceName; + } catch (err) { } + } + + return playerName; +}; + +/** + * Determines which of the 3 panes of a triptych we're running on (left, middle, or right). + * + * If we're running on a real triptych screen, we determine the pane from the `Array_configuration` tag. + * If we're running in a browser, we determine the pane from the `data-triptych-pane` attribute on the #app div. + * + * Returns null if we fail to determine the pane for any reason. + */ +export const getTriptychPane = (): TriptychPane | null => { + const pane = isTriptych() ? getTriptychPaneFromTags() : getDatasetValue("triptychPane"); + + return isTriptychPane(pane) ? pane : null; +}; + +const getTriptychPaneFromTags = () => { + let pane = null; + + const tags = getTags(); + if (tags !== null) { + const arrayConfiguration = + tags.find(({ name }) => name === "Array_configuration")?.value?.[0] ?? null; + console.log("getTriptychPane: arrayConfiguration is", arrayConfiguration); + pane = arrayConfigurationToTriptychPane(arrayConfiguration); + } + + return pane; +}; + +export const getStationName = (): string | null => { + let station = null; + + const tags = getTags(); + if (tags != null) { + return tags.find(({ name }) => name === "Station")?.value?.[0] ?? null; + } + + return station; +}; + +const getTags = (): OFMTag[] | null => { + const mraid = getMRAID(); + + let tags = null; + if (mraid) { + try { + tags = JSON.parse(mraid.getTags()).tags as OFMTag[]; + } catch (err) { } + } + + return tags; +}; + +const arrayConfigurationToTriptychPane = (arrayConfiguration: string | null): TriptychPane | null => { + switch (arrayConfiguration) { + case "Triple-Left": + return "left"; + case "Triple-Middle": + return "middle"; + case "Triple-Right": + return "right"; + default: + return null; + } +}; + +const triptychPaneToArrayConfiguration = (pane: TriptychPane): string => { + return `Triple-${pane[0].toUpperCase().concat(pane.slice(1))}`; +}; + +interface OFMWindow extends Window { + mraid?: MRAID; +} + +interface MRAID { + getTags(): string; + getDeviceInfo(): string; +} + +interface OFMTag { + name: string; + value: [any]; +} + +export const getMRAID = (): MRAID | false => { + console.log("getMRAID called"); + console.log("getMRAID: returning", parent?.parent?.mraid); + return (parent?.parent as OFMWindow)?.mraid ?? false; +}; + +/** + * For use in test DUP/triptych packages only! Sets a fake MRAID object on `window` so that we can test OFM client packages + * as if they are running on real OFM screens. + */ +export const __TEST_setFakeMRAID__ = (options: { playerName: string; station: string; triptychPane?: TriptychPane }) => { + const { playerName, station, triptychPane } = options; + + let tags: OFMTag[] = [{ name: "Station", value: [station] }]; + if (triptychPane) { + tags.push({ name: "Array_configuration", value: [triptychPaneToArrayConfiguration(triptychPane)] }); + } + const tagsJSON = JSON.stringify({ tags }); + + const deviceInfoJSON = JSON.stringify({ deviceName: playerName }); + + const mraid = { + getTags() { return tagsJSON; }, + getDeviceInfo() { return deviceInfoJSON; } + }; + + // Be noisy about it so that we don't accidentally ship a package that calls this function. + alert(`Setting fake MRAID object for testing purposes: ${JSON.stringify(options)}`); + + // Since `window.parent.parent.parent...` returns itself if the window does not have a parent, we can just set the mraid object + // on the current window, and the code that reads `window.parent.parent.mraid` will still access it correctly. + (window as OFMWindow).mraid = mraid; +}; diff --git a/assets/src/util/sentry.tsx b/assets/src/util/sentry.tsx index d650a5b9d..a55796b89 100644 --- a/assets/src/util/sentry.tsx +++ b/assets/src/util/sentry.tsx @@ -1,4 +1,5 @@ -import { isOFM, isRealScreen } from "Util/util"; +import { isOFM } from "Util/outfront"; +import { isRealScreen } from "Util/util"; import { getDataset } from "Util/dataset"; // Previously tried @sentry/react and @sentry/browser as the SDK, but the QtWeb browser on e-inks could not // use them. Raven is an older stable SDK that better works with older browsers. diff --git a/assets/src/util/util.tsx b/assets/src/util/util.tsx index 93901c71b..4f8292925 100644 --- a/assets/src/util/util.tsx +++ b/assets/src/util/util.tsx @@ -1,8 +1,7 @@ -import { ROTATION_INDEX } from "Components/v2/dup/rotation_index"; -import { TRIPTYCH_PANE } from "Components/v2/triptych/pane"; import moment from "moment"; import "moment-timezone"; import { getDatasetValue } from "Util/dataset"; +import { isOFM } from "Util/outfront"; export const classWithModifier = (baseClass, modifier) => { if (!modifier) { @@ -25,28 +24,6 @@ export const classWithModifiers = (baseClass, modifiers) => { export const formatTimeString = (timeString: string) => moment(timeString).tz("America/New_York").format("h:mm"); -/** - * Returns true if this client is running on an Outfront Media screen. - * (A DUP or a triptych.) - * - * Use this for OFM-specific logic that is common to both the DUP and triptych apps. - */ -export const isOFM = () => location.href.startsWith("file:"); - -/** - * Returns true if this client is running on a DUP screen. - * - * Use this for DUP-specific logic. - */ -export const isDUP = () => /^file:.*dup-app.*/.test(location.href); - -/** - * Returns true if this client is running on a triptych screen. - * - * Use this for triptych-specific logic. - */ -export const isTriptych = () => /^file:.*triptych-app.*/.test(location.href); - export const imagePath = (fileName: string): string => isOFM() ? `images/${fileName}` : `/images/${fileName}`; @@ -61,16 +38,6 @@ const isScreenSide = (value: any): value is ScreenSide => { return value === "left" || value === "right"; }; -type RotationIndex = "0" | "1" | "2"; -const isRotationIndex = (value: any): value is RotationIndex => { - return value === "0" || value === "1" || value === "2"; -}; - -type TriptychPane = "left" | "middle" | "right"; -const isTriptychPane = (value: any): value is TriptychPane => { - return value === "left" || value === "middle" || value === "right"; -}; - /** * For screen types that are split across two separate displays (pre-fare), * this gets the value of the data attribute dictating which side to show. @@ -82,20 +49,4 @@ export const getScreenSide = (): ScreenSide | null => { return isScreenSide(screenSide) ? screenSide : null; }; -export const getRotationIndex = (): RotationIndex | null => { - const rotationIndex = isOFM() - ? ROTATION_INDEX.toString() - : getDatasetValue("rotationIndex"); - - return isRotationIndex(rotationIndex) ? rotationIndex : null; -}; - -export const getTriptychPane = (): TriptychPane | null => { - const pane = isOFM() - ? TRIPTYCH_PANE - : getDatasetValue("triptychPane"); - - return isTriptychPane(pane) ? pane : null; -} - export const firstWord = (str: string): string => str.split(" ")[0]; diff --git a/config/config.exs b/config/config.exs index 005912255..4170aad10 100644 --- a/config/config.exs +++ b/config/config.exs @@ -71,6 +71,8 @@ config :screens, audio_psa_s3_directory: "/screens/audio_assets/psa/", signs_ui_s3_path: "config.json", signs_ui_config_fetcher: Screens.SignsUiConfig.State.S3Fetch, + triptych_player_s3_bucket: "mbta-ctd-config", + triptych_player_fetcher: Screens.TriptychPlayer.State.S3Fetch, last_deploy_fetcher: Screens.Util.LastDeploy.S3Fetch, default_api_v3_url: "https://api-v3.mbta.com/", blue_bikes_api_client: Screens.BlueBikes.Client, diff --git a/config/dev.exs b/config/dev.exs index 7e282f035..1c68867bb 100644 --- a/config/dev.exs +++ b/config/dev.exs @@ -25,10 +25,12 @@ config :screens, default_api_v3_url: System.get_env("API_V3_URL", "https://api-v3.mbta.com/"), api_v3_key: System.get_env("API_V3_KEY"), config_fetcher: Screens.Config.State.LocalFetch, + triptych_player_fetcher: Screens.TriptychPlayer.State.LocalFetch, last_deploy_fetcher: Screens.Util.LastDeploy.LocalFetch, local_config_file_spec: {:priv, "local.json"}, local_signs_ui_config_file_spec: {:priv, "signs_ui_config.json"}, - signs_ui_config_fetcher: Screens.SignsUiConfig.State.LocalFetch + signs_ui_config_fetcher: Screens.SignsUiConfig.State.LocalFetch, + local_triptych_player_file_spec: {:priv, "triptych_player_to_screen_id.json"} config :screens, ScreensWeb.AuthManager, secret_key: "secret key" diff --git a/config/test.exs b/config/test.exs index 178c105d0..25b2fb312 100644 --- a/config/test.exs +++ b/config/test.exs @@ -11,6 +11,7 @@ config :screens, last_deploy_fetcher: Screens.Util.LastDeploy.LocalFetch, local_config_file_spec: {:test, "config.json"}, local_signs_ui_config_file_spec: {:test, "signs_ui_config.json"}, + local_triptych_player_file_spec: {:test, "triptych_player_to_screen_id.json"}, signs_ui_config_fetcher: Screens.SignsUiConfig.State.LocalFetch, # This will help us write testable functions. # Functions that request external data cause flaky tests, so to stop us from writing tests that execute API requests, diff --git a/docs/devcontainer_development.md b/docs/devcontainer_development.md index 84863990f..3dff4d873 100644 --- a/docs/devcontainer_development.md +++ b/docs/devcontainer_development.md @@ -5,6 +5,7 @@ Some steps call for opening VS Code's "command palette"—you can do that with t ## Access and keys 1. Get access to our S3 bucket from DevOps and/or a teammate. 1. Save the JSON found at mbta-ctd-config/screens/screens-prod.json as `priv/local.json` to supply your local server with screen configurations. + 1. Save the JSON found at mbta-ctd-config/screens/triptych_player_to_screen_id-prod.json as `priv/triptych_player_to_screen_id.json` to supply your local server with a mapping from triptych player name to screen ID. You can also just put `{}` into that file, if you don't expect to be testing the packaged triptych client anytime soon. 1. Save the JSON found at mbta-signs/config.json as `priv/signs_ui_config.json` to supply your local server with route headway information. 1. Visit [AWS security credentials](https://console.aws.amazon.com/iam/home#/security_credentials) and create an access key if you don't already have it. Keep the tab with this info open; you'll use it shortly. 1. Sign up for a [V3 API key](https://api-v3.mbta.com/). Keep the tab with this info open; you'll use it shortly. diff --git a/docs/local_development.md b/docs/local_development.md index 7f9252201..28ca5c5ea 100644 --- a/docs/local_development.md +++ b/docs/local_development.md @@ -42,7 +42,9 @@ 1. Install Elixir dependencies with `mix deps.get` 1. Install Node.js dependencies with `npm install --prefix assets` -1. Get access to our S3 bucket from DevOps and/or a teammate and save the JSON found at mbta-ctd-config/screens/screens-prod.json as `priv/local.json` to supply your local server with config values. You will also need to grab the signs_ui_config JSON and save it at `priv/signs_ui_config.json`. +1. Get access to our S3 bucket from DevOps and/or a teammate and save the JSON found at mbta-ctd-config/screens/screens-prod.json as `priv/local.json` to supply your local server with config values. + 1. You will also need to grab the signs_ui_config JSON and save it at `priv/signs_ui_config.json`. + 1. You will also need to create `priv/triptych_player_to_screen_id.json`. If you expect to be testing the packaged triptych client sometime soon, copy the contents of mbta-ctd-config/screens/triptych_player_to_screen_id-prod.json into it. Otherwise, its contents can just be an empty object, `{}`. 1. Visit [AWS security credentials](https://console.aws.amazon.com/iam/home#/security_credentials) and create an access key if you don't already have it. Save the access key ID and secret access key as environment variables: ```sh diff --git a/lib/screens/application.ex b/lib/screens/application.ex index 050d1ca7d..b8d8b4526 100644 --- a/lib/screens/application.ex +++ b/lib/screens/application.ex @@ -18,6 +18,7 @@ defmodule Screens.Application do # {Screens.Worker, arg}, Screens.Config.State.Supervisor, Screens.SignsUiConfig.State.Supervisor, + Screens.TriptychPlayer.State.Supervisor, :hackney_pool.child_spec(:ex_aws_pool, []), :hackney_pool.child_spec(:blue_bikes_pool, []), :hackney_pool.child_spec(:api_v3_pool, max_connections: 100), @@ -30,9 +31,9 @@ defmodule Screens.Application do # ScreensByAlert server process Screens.ScreensByAlert, # Task supervisor for ScreensByAlert self-refresh jobs - {Task.Supervisor, name: Screens.ScreensByAlert.SelfRefreshRunner.TaskSupervisor}, + {Task.Supervisor, name: Screens.ScreensByAlert.SelfRefreshRunner.TaskSupervisor} # ScreensByAlert self-refresh job runner - {Screens.ScreensByAlert.SelfRefreshRunner, name: Screens.ScreensByAlert.SelfRefreshRunner} + # {Screens.ScreensByAlert.SelfRefreshRunner, name: Screens.ScreensByAlert.SelfRefreshRunner} ] # See https://hexdocs.pm/elixir/Supervisor.html diff --git a/lib/screens/triptych_player.ex b/lib/screens/triptych_player.ex new file mode 100644 index 000000000..59aa2cab0 --- /dev/null +++ b/lib/screens/triptych_player.ex @@ -0,0 +1,17 @@ +defmodule Screens.TriptychPlayer do + @moduledoc """ + Provides access to a mapping from triptych player name + (a unique ID for an individual pane of a triptych trio, provided by OFM) + to screen ID + (our ID for the collective triptych screen comprising 3 panes). + + The mapping is cached in a GenServer which checks for updates to the source JSON + file every 15 seconds. + """ + + alias Screens.TriptychPlayer.State + + defdelegate fetch_screen_id_for_player(player_name), to: State + + defdelegate fetch_player_names_for_screen_id(player_name), to: State +end diff --git a/lib/screens/triptych_player/state.ex b/lib/screens/triptych_player/state.ex new file mode 100644 index 000000000..95e732914 --- /dev/null +++ b/lib/screens/triptych_player/state.ex @@ -0,0 +1,65 @@ +defmodule Screens.TriptychPlayer.State do + alias Screens.ConfigCache.State.Fetch + alias Screens.Config + + @fetcher Application.compile_env(:screens, :triptych_player_fetcher) + @last_deploy_fetcher Application.compile_env(:screens, :last_deploy_fetcher) + + @type t :: + %__MODULE__{ + config: mapping, + retry_count: non_neg_integer(), + version_id: Fetch.version_id(), + last_deploy_timestamp: DateTime.t() | nil + } + | :error + + @type mapping :: %{ofm_player_name => Config.screen_id()} + + @type ofm_player_name :: String.t() + + @enforce_keys [:config] + defstruct config: nil, + retry_count: 0, + version_id: nil, + last_deploy_timestamp: nil + + use Screens.ConfigCache.State, + config_module: __MODULE__, + fetch_config_fn: &@fetcher.fetch_config/1, + refresh_ms: 15 * 1000, + fetch_failure_error_threshold_minutes: 0, + fetch_last_deploy_fn: &@last_deploy_fetcher.get_last_deploy_time/0 + + @spec fetch_screen_id_for_player(GenServer.server(), ofm_player_name) :: + {:ok, Config.screen_id()} | :error + def fetch_screen_id_for_player(pid \\ __MODULE__, player_name) do + GenServer.call(pid, {:lookup, player_name}) + end + + @spec fetch_player_names_for_screen_id(GenServer.server(), Config.screen_id()) :: + {:ok, nonempty_list(ofm_player_name)} | :error + def fetch_player_names_for_screen_id(pid \\ __MODULE__, screen_id) do + GenServer.call(pid, {:reverse_lookup, screen_id}) + end + + ### + + @impl true + def handle_call({:lookup, player_name}, _from, %__MODULE__{config: mapping} = state) do + {:reply, Map.fetch(mapping, player_name), state} + end + + def handle_call({:reverse_lookup, screen_id}, _from, %__MODULE__{config: mapping} = state) do + player_names = for {player_name, ^screen_id} <- mapping, uniq: true, do: player_name + + case player_names do + [] -> {:reply, :error, state} + l -> {:reply, {:ok, l}, state} + end + end + + def handle_call(_, _from, :error) do + {:reply, :error, :error} + end +end diff --git a/lib/screens/triptych_player/state/local_fetch.ex b/lib/screens/triptych_player/state/local_fetch.ex new file mode 100644 index 000000000..b3ee73cb2 --- /dev/null +++ b/lib/screens/triptych_player/state/local_fetch.ex @@ -0,0 +1,20 @@ +defmodule Screens.TriptychPlayer.State.LocalFetch do + @behaviour Screens.ConfigCache.State.Fetch + + @impl true + def fetch_config(current_version) do + with {:ok, file_contents} <- File.read(local_config_path()), + {:ok, decoded} <- Jason.decode(file_contents) do + {:ok, decoded, current_version} + else + _ -> :error + end + end + + defp local_config_path do + case Application.get_env(:screens, :local_triptych_player_file_spec) do + {:priv, file_name} -> Path.join(:code.priv_dir(:screens), file_name) + {:test, file_name} -> Path.join(~w[#{File.cwd!()} test fixtures #{file_name}]) + end + end +end diff --git a/lib/screens/triptych_player/state/s3_fetch.ex b/lib/screens/triptych_player/state/s3_fetch.ex new file mode 100644 index 000000000..9c9fe9eff --- /dev/null +++ b/lib/screens/triptych_player/state/s3_fetch.ex @@ -0,0 +1,53 @@ +defmodule Screens.TriptychPlayer.State.S3Fetch do + @moduledoc false + + @behaviour Screens.ConfigCache.State.Fetch + + require Logger + + @impl true + def fetch_config(current_version) do + with {:ok, body, new_version} <- get_from_s3(current_version), + {:ok, decoded} <- Jason.decode(body) do + {:ok, decoded, new_version} + else + :unchanged -> :unchanged + _ -> :error + end + end + + def get_from_s3(current_version \\ nil) do + bucket = Application.get_env(:screens, :triptych_player_s3_bucket) + path = path_for_environment() + + opts = + case current_version do + nil -> [] + _ -> [if_none_match: current_version] + end + + get_operation = ExAws.S3.get_object(bucket, path, opts) + + case ExAws.request(get_operation) do + {:ok, %{status_code: 304}} -> + :unchanged + + {:ok, %{body: body, headers: headers, status_code: 200}} -> + etag = + headers + |> Enum.into(%{}) + |> Map.get("ETag") + + {:ok, body, etag} + + {:error, err} -> + _ = Logger.info("s3_triptych_player_config_fetch_error #{inspect(err)}") + :error + end + end + + defp path_for_environment do + "screens-" <> env = Application.get_env(:screens, :environment_name, "screens-prod") + "screens/triptych-player-#{env}.json" + end +end diff --git a/lib/screens/triptych_player/state/supervisor.ex b/lib/screens/triptych_player/state/supervisor.ex new file mode 100644 index 000000000..65a902393 --- /dev/null +++ b/lib/screens/triptych_player/state/supervisor.ex @@ -0,0 +1,17 @@ +defmodule Screens.TriptychPlayer.State.Supervisor do + @moduledoc false + + use Supervisor + + def start_link([]) do + Supervisor.start_link(__MODULE__, :ok, name: __MODULE__) + end + + def init(:ok) do + children = [ + {Screens.TriptychPlayer.State, name: Screens.TriptychPlayer.State} + ] + + Supervisor.init(children, strategy: :one_for_one) + end +end diff --git a/lib/screens_web/controllers/v2/screen_api_controller.ex b/lib/screens_web/controllers/v2/screen_api_controller.ex index c1bd9aef3..b30fc61f0 100644 --- a/lib/screens_web/controllers/v2/screen_api_controller.ex +++ b/lib/screens_web/controllers/v2/screen_api_controller.ex @@ -6,7 +6,7 @@ defmodule ScreensWeb.V2.ScreenApiController do alias Screens.V2.ScreenData plug(:check_config) - plug Corsica, [origins: "*"] when action == :show_ofm + plug Corsica, [origins: "*"] when action in [:show_dup, :show_triptych] defp check_config(conn, _) do if State.ok?() do @@ -81,7 +81,17 @@ defmodule ScreensWeb.V2.ScreenApiController do end end - def show_ofm(conn, params), do: show(conn, params) + def show_dup(conn, params), do: show(conn, params) + + def show_triptych(conn, %{"player_name" => player_name} = params) do + case Screens.TriptychPlayer.fetch_screen_id_for_player(player_name) do + {:ok, screen_id} -> show(conn, Map.put(params, "id", screen_id)) + # Reuse the logic + logging in show/2 for nonexistent IDs. + # This will log a data request for triptych_player_name:#{player_name} and + # Return a 404 response. + :error -> show(conn, Map.put(params, "id", "triptych_player_name:#{player_name}")) + end + end def simulation(conn, %{"id" => screen_id, "last_refresh" => last_refresh} = params) do Screens.LogScreenData.log_data_request( diff --git a/lib/screens_web/router.ex b/lib/screens_web/router.ex index f10711d64..ccde3c4a0 100644 --- a/lib/screens_web/router.ex +++ b/lib/screens_web/router.ex @@ -86,7 +86,8 @@ defmodule ScreensWeb.Router do get "/:id", ScreenApiController, :show get "/:id/simulation", ScreenApiController, :simulation - get "/:id/ofm", ScreenApiController, :show_ofm + get "/:id/dup", ScreenApiController, :show_dup + get "/:player_name/triptych", ScreenApiController, :show_triptych post "/log_frontend_error", ScreenApiController, :log_frontend_error end diff --git a/priv/triptych_template.json b/priv/triptych_template.json index 7ac867407..31848ca78 100644 --- a/priv/triptych_template.json +++ b/priv/triptych_template.json @@ -1,7 +1,7 @@ { "templates": [ { - "displayName": "MBTA - TRIPTYCH APP LEFT", + "displayName": "MBTA - TRIPTYCH APP", "preview": "triptych_preview.png", "indexTemplate": "triptych-app.html", "previewOrientation": "landscape" From 5f3f98522475471291ebc505c6a72a0c52296da2 Mon Sep 17 00:00:00 2001 From: Jon Zimbel Date: Wed, 23 Aug 2023 14:11:42 -0400 Subject: [PATCH 08/30] Address credo complaints --- lib/screens/triptych_player/state.ex | 4 +++- lib/screens/triptych_player/state/local_fetch.ex | 2 ++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/screens/triptych_player/state.ex b/lib/screens/triptych_player/state.ex index 95e732914..a4fd993e2 100644 --- a/lib/screens/triptych_player/state.ex +++ b/lib/screens/triptych_player/state.ex @@ -1,6 +1,8 @@ defmodule Screens.TriptychPlayer.State do - alias Screens.ConfigCache.State.Fetch + @moduledoc false + alias Screens.Config + alias Screens.ConfigCache.State.Fetch @fetcher Application.compile_env(:screens, :triptych_player_fetcher) @last_deploy_fetcher Application.compile_env(:screens, :last_deploy_fetcher) diff --git a/lib/screens/triptych_player/state/local_fetch.ex b/lib/screens/triptych_player/state/local_fetch.ex index b3ee73cb2..857f32979 100644 --- a/lib/screens/triptych_player/state/local_fetch.ex +++ b/lib/screens/triptych_player/state/local_fetch.ex @@ -1,4 +1,6 @@ defmodule Screens.TriptychPlayer.State.LocalFetch do + @moduledoc false + @behaviour Screens.ConfigCache.State.Fetch @impl true From fa3f629d82a63cf0a8f6100124289de75b34fdc9 Mon Sep 17 00:00:00 2001 From: Jon Zimbel Date: Wed, 23 Aug 2023 16:24:14 -0400 Subject: [PATCH 09/30] Use default simulation component for triptychs --- assets/src/apps/v2/triptych.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/assets/src/apps/v2/triptych.tsx b/assets/src/apps/v2/triptych.tsx index 263ef3a92..bd58fc740 100644 --- a/assets/src/apps/v2/triptych.tsx +++ b/assets/src/apps/v2/triptych.tsx @@ -88,7 +88,7 @@ const App = (): JSX.Element => { - + From 90f03205ce54b8a715df413d3def0ec66c774c13 Mon Sep 17 00:00:00 2001 From: Jon Zimbel Date: Thu, 24 Aug 2023 10:29:18 -0400 Subject: [PATCH 10/30] Add triptych player name mapping for tests --- test/fixtures/triptych_player_to_screen_id.json | 1 + 1 file changed, 1 insertion(+) create mode 100644 test/fixtures/triptych_player_to_screen_id.json diff --git a/test/fixtures/triptych_player_to_screen_id.json b/test/fixtures/triptych_player_to_screen_id.json new file mode 100644 index 000000000..0967ef424 --- /dev/null +++ b/test/fixtures/triptych_player_to_screen_id.json @@ -0,0 +1 @@ +{} From 7f7744f3d16d70862385bb57d5eac8525a3c82ce Mon Sep 17 00:00:00 2001 From: Jon Zimbel Date: Thu, 24 Aug 2023 10:29:47 -0400 Subject: [PATCH 11/30] fix: Tweak config so vscode can consistently resolve aliased import paths --- assets/tsconfig.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/assets/tsconfig.json b/assets/tsconfig.json index c7b5d30c7..23ed2fc1f 100644 --- a/assets/tsconfig.json +++ b/assets/tsconfig.json @@ -45,10 +45,10 @@ "moduleResolution": "node" /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */, "baseUrl": "./src", /* Base directory to resolve non-absolute module names. */ "paths": { - "Components": ["/components"], - "Hooks": ["/hooks"], - "Util": ["/util"], - "Constants": ["/constants"] + "Components/*": ["components/*"], + "Hooks/*": ["hooks/*"], + "Util/*": ["util/*"], + "Constants/*": ["constants/*"] }, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */ // "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */ // "typeRoots": [], /* List of folders to include type definitions from. */ From 10653e623212e3b02c27427ff25b447b5a1ed85a Mon Sep 17 00:00:00 2001 From: Jon Zimbel Date: Fri, 25 Aug 2023 11:32:40 -0400 Subject: [PATCH 12/30] Remove a few console.logs that I missed --- assets/src/util/outfront.tsx | 3 --- 1 file changed, 3 deletions(-) diff --git a/assets/src/util/outfront.tsx b/assets/src/util/outfront.tsx index 3a77fb052..99ed0df92 100644 --- a/assets/src/util/outfront.tsx +++ b/assets/src/util/outfront.tsx @@ -77,7 +77,6 @@ const getTriptychPaneFromTags = () => { if (tags !== null) { const arrayConfiguration = tags.find(({ name }) => name === "Array_configuration")?.value?.[0] ?? null; - console.log("getTriptychPane: arrayConfiguration is", arrayConfiguration); pane = arrayConfigurationToTriptychPane(arrayConfiguration); } @@ -140,8 +139,6 @@ interface OFMTag { } export const getMRAID = (): MRAID | false => { - console.log("getMRAID called"); - console.log("getMRAID: returning", parent?.parent?.mraid); return (parent?.parent as OFMWindow)?.mraid ?? false; }; From 6de37c2fbc278884932d2c1536c397f34e475809 Mon Sep 17 00:00:00 2001 From: Jon Zimbel Date: Fri, 25 Aug 2023 12:15:45 -0400 Subject: [PATCH 13/30] Unclutter useBaseApiResponse and memoize logic that computes API url --- assets/src/hooks/v2/use_api_response.tsx | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/assets/src/hooks/v2/use_api_response.tsx b/assets/src/hooks/v2/use_api_response.tsx index 8ec245e59..6f6a1a7ca 100644 --- a/assets/src/hooks/v2/use_api_response.tsx +++ b/assets/src/hooks/v2/use_api_response.tsx @@ -1,6 +1,6 @@ import { WidgetData } from "Components/v2/widget"; import useDriftlessInterval from "Hooks/use_driftless_interval"; -import React, { useEffect, useState } from "react"; +import React, { useEffect, useMemo, useState } from "react"; import { getDataset, getDatasetValue } from "Util/dataset"; import { isDUP, isOFM, isTriptych, getTriptychPane } from "Util/outfront"; import { getScreenSide, isRealScreen } from "Util/util"; @@ -146,6 +146,17 @@ const getLoggingParams = () => { const getOutfrontAbsolutePath = () => isOFM() ? "https://screens.mbta.com" : ""; +const getApiPath = (id: string, routePart: string) => { + const outfrontAbsolutePath = getOutfrontAbsolutePath(); + const lastRefresh = getDatasetValue("lastRefresh"); + const isRealScreenParam = getIsRealScreenParam(); + const screenSideParam = getScreenSideParam(); + const requestorParam = getRequestorParam(); + const loggingParams = getLoggingParams(); + + return `${outfrontAbsolutePath}/v2/api/screen/${id}${routePart}?last_refresh=${lastRefresh}${isRealScreenParam}${screenSideParam}${requestorParam}${loggingParams}`; +}; + interface UseApiResponseArgs { id: string; failureModeElapsedMs?: number; @@ -164,23 +175,17 @@ const useBaseApiResponse = ({ routePart = "", responseHandler = rawResponseToApiResponse, }: UseApiResponseArgs): UseApiResponseReturn => { - const isRealScreenParam = getIsRealScreenParam(); - const screenSideParam = getScreenSideParam(); - const requestorParam = getRequestorParam(); - const loggingParams = getLoggingParams(); - const outfrontAbsolutePath = getOutfrontAbsolutePath(); const [apiResponse, setApiResponse] = useState(LOADING_RESPONSE); const [requestCount, setRequestCount] = useState(0); const [lastSuccess, setLastSuccess] = useState(null); const { - lastRefresh, refreshRate, refreshRateOffset, screenIdsWithOffsetMap, } = getDataset(); const refreshMs = parseInt(refreshRate, 10) * 1000; let refreshRateOffsetMs = parseInt(refreshRateOffset, 10) * 1000; - const apiPath = `${outfrontAbsolutePath}/v2/api/screen/${id}${routePart}?last_refresh=${lastRefresh}${isRealScreenParam}${screenSideParam}${requestorParam}${loggingParams}`; + const apiPath = useMemo(() => getApiPath(id, routePart), [id, routePart]); if (screenIdsWithOffsetMap) { const screens = JSON.parse(screenIdsWithOffsetMap); From a28d258975e3a80fa7126e2ad14db3f46f1692f4 Mon Sep 17 00:00:00 2001 From: Jon Zimbel Date: Fri, 25 Aug 2023 16:35:25 -0400 Subject: [PATCH 14/30] Add some missing config for the test env :doh: --- config/test.exs | 1 + 1 file changed, 1 insertion(+) diff --git a/config/test.exs b/config/test.exs index 25b2fb312..5faa07b33 100644 --- a/config/test.exs +++ b/config/test.exs @@ -13,6 +13,7 @@ config :screens, local_signs_ui_config_file_spec: {:test, "signs_ui_config.json"}, local_triptych_player_file_spec: {:test, "triptych_player_to_screen_id.json"}, signs_ui_config_fetcher: Screens.SignsUiConfig.State.LocalFetch, + triptych_player_fetcher: Screens.TriptychPlayer.State.LocalFetch, # This will help us write testable functions. # Functions that request external data cause flaky tests, so to stop us from writing tests that execute API requests, # we pass a non-string as the default URL (causing tests to break.) From 88e55115164c4f940208d7e736b7a96eb216d20d Mon Sep 17 00:00:00 2001 From: Jon Zimbel Date: Fri, 25 Aug 2023 16:40:30 -0400 Subject: [PATCH 15/30] A no-op change in lib/, because the last commit didn't trigger CI for some reason?? --- lib/screens/config/v2/triptych.ex | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/screens/config/v2/triptych.ex b/lib/screens/config/v2/triptych.ex index 2ac2944d3..20ee7ebbc 100644 --- a/lib/screens/config/v2/triptych.ex +++ b/lib/screens/config/v2/triptych.ex @@ -1,7 +1,8 @@ defmodule Screens.Config.V2.Triptych do @moduledoc false - alias Screens.Config.V2.{EvergreenContentItem, OLCrowding} + alias Screens.Config.V2.EvergreenContentItem + alias Screens.Config.V2.OLCrowding @type t :: %__MODULE__{ ol_crowding: OLCrowding.t(), From 7db3eae90f72e9d863e62935b9fdfae99bbac5d9 Mon Sep 17 00:00:00 2001 From: Jon Zimbel Date: Fri, 25 Aug 2023 16:43:45 -0400 Subject: [PATCH 16/30] Revert "A no-op change in lib/, because the last commit didn't trigger CI for some reason??" This reverts commit 88e55115164c4f940208d7e736b7a96eb216d20d. --- lib/screens/config/v2/triptych.ex | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/screens/config/v2/triptych.ex b/lib/screens/config/v2/triptych.ex index 20ee7ebbc..2ac2944d3 100644 --- a/lib/screens/config/v2/triptych.ex +++ b/lib/screens/config/v2/triptych.ex @@ -1,8 +1,7 @@ defmodule Screens.Config.V2.Triptych do @moduledoc false - alias Screens.Config.V2.EvergreenContentItem - alias Screens.Config.V2.OLCrowding + alias Screens.Config.V2.{EvergreenContentItem, OLCrowding} @type t :: %__MODULE__{ ol_crowding: OLCrowding.t(), From b1064f1aae3771f21e138e9e3208118438012006 Mon Sep 17 00:00:00 2001 From: Jon Zimbel Date: Mon, 28 Aug 2023 09:11:49 -0400 Subject: [PATCH 17/30] Revert "Revert "A no-op change in lib/, because the last commit didn't trigger CI for some reason??"" This reverts commit 7db3eae90f72e9d863e62935b9fdfae99bbac5d9. --- lib/screens/config/v2/triptych.ex | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/screens/config/v2/triptych.ex b/lib/screens/config/v2/triptych.ex index 2ac2944d3..20ee7ebbc 100644 --- a/lib/screens/config/v2/triptych.ex +++ b/lib/screens/config/v2/triptych.ex @@ -1,7 +1,8 @@ defmodule Screens.Config.V2.Triptych do @moduledoc false - alias Screens.Config.V2.{EvergreenContentItem, OLCrowding} + alias Screens.Config.V2.EvergreenContentItem + alias Screens.Config.V2.OLCrowding @type t :: %__MODULE__{ ol_crowding: OLCrowding.t(), From 16051402ace5ec3627329695f37067ab02c9453a Mon Sep 17 00:00:00 2001 From: Jon Zimbel Date: Mon, 28 Aug 2023 16:12:50 -0400 Subject: [PATCH 18/30] Uncomment accidentally commented line of code --- lib/screens/application.ex | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/screens/application.ex b/lib/screens/application.ex index b8d8b4526..4a5f17c9c 100644 --- a/lib/screens/application.ex +++ b/lib/screens/application.ex @@ -31,9 +31,9 @@ defmodule Screens.Application do # ScreensByAlert server process Screens.ScreensByAlert, # Task supervisor for ScreensByAlert self-refresh jobs - {Task.Supervisor, name: Screens.ScreensByAlert.SelfRefreshRunner.TaskSupervisor} + {Task.Supervisor, name: Screens.ScreensByAlert.SelfRefreshRunner.TaskSupervisor}, # ScreensByAlert self-refresh job runner - # {Screens.ScreensByAlert.SelfRefreshRunner, name: Screens.ScreensByAlert.SelfRefreshRunner} + {Screens.ScreensByAlert.SelfRefreshRunner, name: Screens.ScreensByAlert.SelfRefreshRunner} ] # See https://hexdocs.pm/elixir/Supervisor.html From 284a5ca6b50796cecdd6744269dc38a2af334a9a Mon Sep 17 00:00:00 2001 From: Jon Zimbel Date: Mon, 28 Aug 2023 16:25:16 -0400 Subject: [PATCH 19/30] Revert `isDUP` name to `isDup` --- assets/src/apps/dup.tsx | 4 ++-- assets/src/components/dup/dup_screen_page.tsx | 4 ++-- assets/src/components/v2/screen_container.tsx | 4 ++-- assets/src/hooks/v2/use_api_response.tsx | 4 ++-- assets/src/util/outfront.tsx | 2 +- 5 files changed, 9 insertions(+), 9 deletions(-) diff --git a/assets/src/apps/dup.tsx b/assets/src/apps/dup.tsx index 2ff5fce77..ef49787d1 100644 --- a/assets/src/apps/dup.tsx +++ b/assets/src/apps/dup.tsx @@ -17,10 +17,10 @@ import { MultiRotationPage, SimulationPage, } from "Components/dup/dup_screen_page"; -import { isDUP } from "Util/outfront"; +import { isDup } from "Util/outfront"; const App = (): JSX.Element => { - if (isDUP()) { + if (isDup()) { return ; } else { return ( diff --git a/assets/src/components/dup/dup_screen_page.tsx b/assets/src/components/dup/dup_screen_page.tsx index c0697d8a9..ce7bd5089 100644 --- a/assets/src/components/dup/dup_screen_page.tsx +++ b/assets/src/components/dup/dup_screen_page.tsx @@ -3,7 +3,7 @@ import { useParams } from "react-router-dom"; import { ROTATION_INDEX } from "./rotation_index"; import { NoDataLayout } from "Components/dup/screen_container"; -import { isDUP } from "Util/outfront"; +import { isDup } from "Util/outfront"; import { useStationName } from "Hooks/outfront"; import { fetchDatasetValue } from "Util/dataset"; import { DUP_SIMULATION_REFRESH_MS } from "Constants"; @@ -37,7 +37,7 @@ const ScreenPage = ({ }: { screenContainer: React.ComponentType; }): JSX.Element => - isDUP() ? ( + isDup() ? ( ) : ( diff --git a/assets/src/components/v2/screen_container.tsx b/assets/src/components/v2/screen_container.tsx index 33e4456a7..b85ae45ba 100644 --- a/assets/src/components/v2/screen_container.tsx +++ b/assets/src/components/v2/screen_container.tsx @@ -15,7 +15,7 @@ import useApiResponse, { import WidgetTreeErrorBoundary from "Components/v2/widget_tree_error_boundary"; import Widget, { WidgetData } from "Components/v2/widget"; import useAudioReadout from "Hooks/v2/use_audio_readout"; -import { isDUP, isOFM, isTriptych } from "Util/outfront"; +import { isDup, isOFM, isTriptych } from "Util/outfront"; type ResponseMapper = ( apiResponse: ApiResponse @@ -109,7 +109,7 @@ const ScreenLayout: ComponentType = ({ }; const getApiResponseHook = () => { - if (isDUP()) { + if (isDup()) { return useDUPApiResponse; } else if (isTriptych()) { return useTriptychApiResponse; diff --git a/assets/src/hooks/v2/use_api_response.tsx b/assets/src/hooks/v2/use_api_response.tsx index 6f6a1a7ca..df1e93d49 100644 --- a/assets/src/hooks/v2/use_api_response.tsx +++ b/assets/src/hooks/v2/use_api_response.tsx @@ -2,7 +2,7 @@ import { WidgetData } from "Components/v2/widget"; import useDriftlessInterval from "Hooks/use_driftless_interval"; import React, { useEffect, useMemo, useState } from "react"; import { getDataset, getDatasetValue } from "Util/dataset"; -import { isDUP, isOFM, isTriptych, getTriptychPane } from "Util/outfront"; +import { isDup, isOFM, isTriptych, getTriptychPane } from "Util/outfront"; import { getScreenSide, isRealScreen } from "Util/util"; import * as SentryLogger from "Util/sentry"; import { ROTATION_INDEX } from "Components/v2/dup/rotation_index"; @@ -130,7 +130,7 @@ const getRequestorParam = () => { }; const getLoggingParams = () => { - if (isDUP()) { + if (isDup()) { return `&rotation_index=${ROTATION_INDEX}`; } diff --git a/assets/src/util/outfront.tsx b/assets/src/util/outfront.tsx index 99ed0df92..7d31b0f8e 100644 --- a/assets/src/util/outfront.tsx +++ b/assets/src/util/outfront.tsx @@ -14,7 +14,7 @@ export const isOFM = () => location.href.startsWith("file:"); * * Use this for DUP-specific logic. */ -export const isDUP = () => /^file:.*dup-app.*/.test(location.href); +export const isDup = () => /^file:.*dup-app.*/.test(location.href); /** * Returns true if this client is running on a triptych screen. From 4189456593a5f889141e34184b768bd8df8b6886 Mon Sep 17 00:00:00 2001 From: Jon Zimbel Date: Mon, 28 Aug 2023 16:28:30 -0400 Subject: [PATCH 20/30] Rename a few more isDUPs that the vscode refactor tool missed --- assets/src/apps/v2/dup.tsx | 4 ++-- assets/src/components/v2/dup/README.md | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/assets/src/apps/v2/dup.tsx b/assets/src/apps/v2/dup.tsx index 16ff2251d..90b78834d 100644 --- a/assets/src/apps/v2/dup.tsx +++ b/assets/src/apps/v2/dup.tsx @@ -38,7 +38,7 @@ import PageLoadNoData from "Components/v2/dup/page_load_no_data"; import NoData from "Components/v2/dup/no_data"; import OvernightDepartures from "Components/v2/dup/overnight_departures"; import { usePlayerName } from "Hooks/outfront"; -import { isDUP } from "Util/outfront"; +import { isDup } from "Util/outfront"; const TYPE_TO_COMPONENT = { screen_normal: NormalScreen, @@ -123,7 +123,7 @@ const responseMapper: ResponseMapper = (apiResponse) => { }; const App = (): JSX.Element => { - if (isDUP()) { + if (isDup()) { const playerName = usePlayerName()!; const id = `DUP-${playerName.trim()}`; return ( diff --git a/assets/src/components/v2/dup/README.md b/assets/src/components/v2/dup/README.md index ba0911949..f35d6a8c7 100644 --- a/assets/src/components/v2/dup/README.md +++ b/assets/src/components/v2/dup/README.md @@ -1,7 +1,7 @@ # DUP app packaging v2 - Ensure [Corsica](https://hexdocs.pm/corsica/Corsica.html) is used on the server to allow CORS requests (ideally limited to just the DUP-relevant routes). It should already be configured at [this line](/lib/screens_web/controllers/v2/screen_api_controller.ex#L9) in the API controller--if it is, you don't need to do anything for this step. -- Double check that any behavior specific to the DUP screen environment happens inside of an `isDUP()` or `isOFM()` check. This includes: +- Double check that any behavior specific to the DUP screen environment happens inside of an `isDup()` or `isOFM()` check. This includes: - `buildApiPath` in use_api_response.tsx should return a full URL for the API path: prefix `apiPath` string with "https://screens.mbta.com". - `imagePath` in util.tsx should return relative paths (no leading `/`). - Create priv/static/dup-app.html if it doesn’t already exist. Copy paste the following contents in: From a0171c090c586626cd717ab909a2a004584a874f Mon Sep 17 00:00:00 2001 From: Jon Zimbel Date: Tue, 29 Aug 2023 13:27:15 -0400 Subject: [PATCH 21/30] Run prettier --- assets/src/components/v2/screen_container.tsx | 12 +++--- assets/src/hooks/outfront.tsx | 16 +++++-- assets/src/hooks/use_api_response.tsx | 6 +-- assets/src/hooks/v2/use_api_response.tsx | 14 +++---- assets/src/util/outfront.tsx | 42 ++++++++++++++----- 5 files changed, 59 insertions(+), 31 deletions(-) diff --git a/assets/src/components/v2/screen_container.tsx b/assets/src/components/v2/screen_container.tsx index b85ae45ba..1765b4a59 100644 --- a/assets/src/components/v2/screen_container.tsx +++ b/assets/src/components/v2/screen_container.tsx @@ -18,7 +18,7 @@ import useAudioReadout from "Hooks/v2/use_audio_readout"; import { isDup, isOFM, isTriptych } from "Util/outfront"; type ResponseMapper = ( - apiResponse: ApiResponse + apiResponse: ApiResponse, ) => WidgetData | SimulationApiResponse; const defaultResponseMapper: ResponseMapper = (apiResponse) => { @@ -42,7 +42,7 @@ const LOADING_LAYOUT = { }; const ResponseMapperContext = createContext( - defaultResponseMapper + defaultResponseMapper, ); /* "Blink" info @@ -68,7 +68,7 @@ interface BlinkConfig { const defaultBlinkConfig = null; const BlinkConfigContext = createContext( - defaultBlinkConfig + defaultBlinkConfig, ); interface AudioConfig { @@ -79,7 +79,7 @@ interface AudioConfig { const defaultAudioConfig = null; const AudioConfigContext = createContext( - defaultAudioConfig + defaultAudioConfig, ); const LastFetchContext = createContext(null); @@ -123,7 +123,9 @@ const ScreenContainer = ({ id }) => { const audioConfig = useContext(AudioConfigContext); const [showBlink, setShowBlink] = useState(false); - const { apiResponse, requestCount, lastSuccess } = getApiResponseHook()({ id }); + const { apiResponse, requestCount, lastSuccess } = getApiResponseHook()({ + id, + }); useAudioReadout({ id, config: audioConfig }); diff --git a/assets/src/hooks/outfront.tsx b/assets/src/hooks/outfront.tsx index c9bd42b86..b2989d082 100644 --- a/assets/src/hooks/outfront.tsx +++ b/assets/src/hooks/outfront.tsx @@ -1,9 +1,17 @@ import { useMemo } from "react"; -import { getMRAID, getPlayerName, getStationName, getTriptychPane } from "Util/outfront"; +import { + getMRAID, + getPlayerName, + getStationName, + getTriptychPane, +} from "Util/outfront"; import type { TriptychPane } from "Util/outfront"; -export const usePlayerName = (): string | null => useMemo(getPlayerName, [getMRAID()]); +export const usePlayerName = (): string | null => + useMemo(getPlayerName, [getMRAID()]); -export const useTriptychPane = (): TriptychPane | null => useMemo(getTriptychPane, [getMRAID()]); +export const useTriptychPane = (): TriptychPane | null => + useMemo(getTriptychPane, [getMRAID()]); -export const useStationName = (): string | null => useMemo(getStationName, [getMRAID()]); +export const useStationName = (): string | null => + useMemo(getStationName, [getMRAID()]); diff --git a/assets/src/hooks/use_api_response.tsx b/assets/src/hooks/use_api_response.tsx index 9488c4351..e21d5ecd6 100644 --- a/assets/src/hooks/use_api_response.tsx +++ b/assets/src/hooks/use_api_response.tsx @@ -14,7 +14,7 @@ const doFailureBuffer = ( lastSuccess: number | null, failureModeElapsedMs: number, setApiResponse: React.Dispatch>, - apiResponse: object = FAILURE_RESPONSE + apiResponse: object = FAILURE_RESPONSE, ) => { if (lastSuccess == null) { // We haven't had a successful request since initial page load. @@ -71,7 +71,7 @@ const useApiResponse = ({ failureModeElapsedMs = MINUTE_IN_MS, }: UseApiResponseArgs) => { const [apiResponse, setApiResponse] = useState( - LOADING_RESPONSE + LOADING_RESPONSE, ); const [lastSuccess, setLastSuccess] = useState(null); const lastRefresh = getDatasetValue("lastRefresh"); @@ -112,7 +112,7 @@ const useApiResponse = ({ lastSuccess, failureModeElapsedMs, setApiResponse, - json + json, ); } } catch (err) { diff --git a/assets/src/hooks/v2/use_api_response.tsx b/assets/src/hooks/v2/use_api_response.tsx index df1e93d49..79f38b0d9 100644 --- a/assets/src/hooks/v2/use_api_response.tsx +++ b/assets/src/hooks/v2/use_api_response.tsx @@ -81,7 +81,7 @@ const rawResponseToSimulationApiResponse = ({ const doFailureBuffer = ( lastSuccess: number | null, setApiResponse: React.Dispatch>, - apiResponse: ApiResponse = FAILURE_RESPONSE + apiResponse: ApiResponse = FAILURE_RESPONSE, ) => { if (lastSuccess == null) { // We haven't had a successful request since initial page load. @@ -144,7 +144,8 @@ const getLoggingParams = () => { return ""; }; -const getOutfrontAbsolutePath = () => isOFM() ? "https://screens.mbta.com" : ""; +const getOutfrontAbsolutePath = () => + isOFM() ? "https://screens.mbta.com" : ""; const getApiPath = (id: string, routePart: string) => { const outfrontAbsolutePath = getOutfrontAbsolutePath(); @@ -178,11 +179,8 @@ const useBaseApiResponse = ({ const [apiResponse, setApiResponse] = useState(LOADING_RESPONSE); const [requestCount, setRequestCount] = useState(0); const [lastSuccess, setLastSuccess] = useState(null); - const { - refreshRate, - refreshRateOffset, - screenIdsWithOffsetMap, - } = getDataset(); + const { refreshRate, refreshRateOffset, screenIdsWithOffsetMap } = + getDataset(); const refreshMs = parseInt(refreshRate, 10) * 1000; let refreshRateOffsetMs = parseInt(refreshRateOffset, 10) * 1000; const apiPath = useMemo(() => getApiPath(id, routePart), [id, routePart]); @@ -235,7 +233,7 @@ const useBaseApiResponse = ({ fetchData(); }, refreshMs, - refreshRateOffsetMs + refreshRateOffsetMs, ); return { apiResponse, requestCount, lastSuccess }; diff --git a/assets/src/util/outfront.tsx b/assets/src/util/outfront.tsx index 7d31b0f8e..c63e6dced 100644 --- a/assets/src/util/outfront.tsx +++ b/assets/src/util/outfront.tsx @@ -50,7 +50,7 @@ export const getPlayerName = (): string | null => { const deviceInfoJSON = mraid.getDeviceInfo(); const deviceInfo = JSON.parse(deviceInfoJSON); playerName = deviceInfo.deviceName; - } catch (err) { } + } catch (err) {} } return playerName; @@ -58,14 +58,16 @@ export const getPlayerName = (): string | null => { /** * Determines which of the 3 panes of a triptych we're running on (left, middle, or right). - * + * * If we're running on a real triptych screen, we determine the pane from the `Array_configuration` tag. * If we're running in a browser, we determine the pane from the `data-triptych-pane` attribute on the #app div. * * Returns null if we fail to determine the pane for any reason. */ export const getTriptychPane = (): TriptychPane | null => { - const pane = isTriptych() ? getTriptychPaneFromTags() : getDatasetValue("triptychPane"); + const pane = isTriptych() + ? getTriptychPaneFromTags() + : getDatasetValue("triptychPane"); return isTriptychPane(pane) ? pane : null; }; @@ -76,7 +78,8 @@ const getTriptychPaneFromTags = () => { const tags = getTags(); if (tags !== null) { const arrayConfiguration = - tags.find(({ name }) => name === "Array_configuration")?.value?.[0] ?? null; + tags.find(({ name }) => name === "Array_configuration")?.value?.[0] ?? + null; pane = arrayConfigurationToTriptychPane(arrayConfiguration); } @@ -101,13 +104,15 @@ const getTags = (): OFMTag[] | null => { if (mraid) { try { tags = JSON.parse(mraid.getTags()).tags as OFMTag[]; - } catch (err) { } + } catch (err) {} } return tags; }; -const arrayConfigurationToTriptychPane = (arrayConfiguration: string | null): TriptychPane | null => { +const arrayConfigurationToTriptychPane = ( + arrayConfiguration: string | null, +): TriptychPane | null => { switch (arrayConfiguration) { case "Triple-Left": return "left"; @@ -146,24 +151,39 @@ export const getMRAID = (): MRAID | false => { * For use in test DUP/triptych packages only! Sets a fake MRAID object on `window` so that we can test OFM client packages * as if they are running on real OFM screens. */ -export const __TEST_setFakeMRAID__ = (options: { playerName: string; station: string; triptychPane?: TriptychPane }) => { +export const __TEST_setFakeMRAID__ = (options: { + playerName: string; + station: string; + triptychPane?: TriptychPane; +}) => { const { playerName, station, triptychPane } = options; let tags: OFMTag[] = [{ name: "Station", value: [station] }]; if (triptychPane) { - tags.push({ name: "Array_configuration", value: [triptychPaneToArrayConfiguration(triptychPane)] }); + tags.push({ + name: "Array_configuration", + value: [triptychPaneToArrayConfiguration(triptychPane)], + }); } const tagsJSON = JSON.stringify({ tags }); const deviceInfoJSON = JSON.stringify({ deviceName: playerName }); const mraid = { - getTags() { return tagsJSON; }, - getDeviceInfo() { return deviceInfoJSON; } + getTags() { + return tagsJSON; + }, + getDeviceInfo() { + return deviceInfoJSON; + }, }; // Be noisy about it so that we don't accidentally ship a package that calls this function. - alert(`Setting fake MRAID object for testing purposes: ${JSON.stringify(options)}`); + alert( + `Setting fake MRAID object for testing purposes: ${JSON.stringify( + options, + )}`, + ); // Since `window.parent.parent.parent...` returns itself if the window does not have a parent, we can just set the mraid object // on the current window, and the code that reads `window.parent.parent.mraid` will still access it correctly. From f7759095d8943d235d9c268841dfb292fad17d20 Mon Sep 17 00:00:00 2001 From: Jon Zimbel Date: Tue, 29 Aug 2023 15:45:11 -0400 Subject: [PATCH 22/30] Rename split-screen slots to left_, middle_, right_pane for consistency --- lib/screens/v2/candidate_generator/triptych.ex | 2 +- test/screens/v2/candidate_generator/triptych_test.exs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/screens/v2/candidate_generator/triptych.ex b/lib/screens/v2/candidate_generator/triptych.ex index c432acdb0..6bee17ba0 100644 --- a/lib/screens/v2/candidate_generator/triptych.ex +++ b/lib/screens/v2/candidate_generator/triptych.ex @@ -14,7 +14,7 @@ defmodule Screens.V2.CandidateGenerator.Triptych do {:screen, %{ screen_normal: [:full_screen], - screen_split: [:first_pane, :second_pane, :third_pane] + screen_split: [:left_pane, :middle_pane, :right_pane] }} |> Builder.build_template() end diff --git a/test/screens/v2/candidate_generator/triptych_test.exs b/test/screens/v2/candidate_generator/triptych_test.exs index 356712dff..94f003ac1 100644 --- a/test/screens/v2/candidate_generator/triptych_test.exs +++ b/test/screens/v2/candidate_generator/triptych_test.exs @@ -29,7 +29,7 @@ defmodule Screens.V2.CandidateGenerator.TriptychTest do assert {:screen, %{ screen_normal: [:full_screen], - screen_split: [:first_pane, :second_pane, :third_pane] + screen_split: [:left_pane, :middle_pane, :right_pane] }} == Triptych.screen_template() end end From e669c59541f8e82639ff3b9f97e33efa9d4812d4 Mon Sep 17 00:00:00 2001 From: Jon Zimbel Date: Tue, 29 Aug 2023 16:05:41 -0400 Subject: [PATCH 23/30] "Closed" -> "Car closed" per designs (plus some tweaks to avoid prettier making a mess of the existing code) --- assets/src/components/v2/train_crowding.tsx | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/assets/src/components/v2/train_crowding.tsx b/assets/src/components/v2/train_crowding.tsx index 681bb4b0f..0c27dcd9f 100644 --- a/assets/src/components/v2/train_crowding.tsx +++ b/assets/src/components/v2/train_crowding.tsx @@ -48,13 +48,12 @@ type OccupancyStatus = | "crowded" | "closed"; +type CarOrientation = FrontCarDirection | "middle"; const lookupCarComponent = ( occupancyStatus: OccupancyStatus, - frontCarDirection: FrontCarDirection | "middle", + carOrientation: CarOrientation, ) => { - const lookupKey: `${ - | FrontCarDirection - | "middle"}/${OccupancyStatus}` = `${frontCarDirection}/${occupancyStatus}`; + const lookupKey: `${CarOrientation}/${OccupancyStatus}` = `${carOrientation}/${occupancyStatus}`; switch (lookupKey) { case "left/not_crowded": @@ -119,7 +118,7 @@ const TrainCrowding: React.ComponentType = ({ arrowSlot = platformPosition + 1; } else { if (platformPosition == 25) { - arrowSlot = platformPosition - 1 + arrowSlot = platformPosition - 1; } else arrowSlot = platformPosition; } @@ -201,8 +200,8 @@ const TrainCrowding: React.ComponentType = ({
{crowding.includes("closed") ? ( <> - {" "} - Closed + Car + closed ) : ( crowding.includes("no_data") && ( From e157570e664041ced58660d6e1a78670129e1d86 Mon Sep 17 00:00:00 2001 From: Jon Zimbel Date: Wed, 30 Aug 2023 09:27:16 -0400 Subject: [PATCH 24/30] Add packaging README section on communicating with Outfront --- assets/src/components/v2/dup/README.md | 7 +++++++ assets/src/components/v2/triptych/README.md | 10 ++++++++++ 2 files changed, 17 insertions(+) diff --git a/assets/src/components/v2/dup/README.md b/assets/src/components/v2/dup/README.md index f35d6a8c7..de7ec4413 100644 --- a/assets/src/components/v2/dup/README.md +++ b/assets/src/components/v2/dup/README.md @@ -57,6 +57,13 @@ - On completion, the packaged client apps will be saved at `priv/static/dup-app-(0|1|2).zip`. - Commit the version bump on a branch, push it, and create a PR to mark the deploy. +## Working with Outfront + +Once you've created the client app packages, you'll need to send them to Outfront to test and deploy. + +Ask a Screens team member for the email of our contact at Outfront. +In your message, be sure to specify a player name (or "Liveboard name") that they should set on the test screen. + ## Debugging To assist with debugging on the DUP screens, you can paste this at the module scope in dup.tsx to have console logs diff --git a/assets/src/components/v2/triptych/README.md b/assets/src/components/v2/triptych/README.md index 158aac909..432d212f1 100644 --- a/assets/src/components/v2/triptych/README.md +++ b/assets/src/components/v2/triptych/README.md @@ -55,6 +55,16 @@ - On completion, the packaged client app will be saved at `priv/static/triptych-app.zip`. - Commit the version bump on a branch, push it, and create a PR to mark the deploy. +## Working with Outfront + +Once you've created the client app package, you'll need to send it to Outfront for them to test and deploy it. + +Ask a Screens team member for the email of our contact at Outfront. +In your message, be sure to specify: +- a player name (or "Liveboard name"), and +- a triptych pane (or `Array_configuration`--value should be of the form "Triple-(Left|Middle|Right)") +that they should set on the test screen. + ## Debugging To assist with debugging on the triptych screens, you can paste this at the module scope in triptych.tsx to have console logs From f9959103c6561ab2cee8dc39e08c396fbb820cfd Mon Sep 17 00:00:00 2001 From: Jon Zimbel Date: Wed, 30 Aug 2023 10:36:06 -0400 Subject: [PATCH 25/30] Document `platform_position` value --- lib/screens/config/v2/train_crowding.ex | 4 +++- lib/screens/v2/widget_instance/train_crowding.ex | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/lib/screens/config/v2/train_crowding.ex b/lib/screens/config/v2/train_crowding.ex index cfb22313a..9adb05030 100644 --- a/lib/screens/config/v2/train_crowding.ex +++ b/lib/screens/config/v2/train_crowding.ex @@ -5,7 +5,9 @@ defmodule Screens.Config.V2.TrainCrowding do station_id: String.t(), route_id: String.t(), direction_id: 0 | 1, - platform_position: pos_integer(), + # Describes where the "you are here" arrow should be positioned. + # 1: leftmost, 25: rightmost + platform_position: 1..25, front_car_direction: :left | :right, enabled: boolean() } diff --git a/lib/screens/v2/widget_instance/train_crowding.ex b/lib/screens/v2/widget_instance/train_crowding.ex index d40ff6b3b..d0bc6fac8 100644 --- a/lib/screens/v2/widget_instance/train_crowding.ex +++ b/lib/screens/v2/widget_instance/train_crowding.ex @@ -20,7 +20,9 @@ defmodule Screens.V2.WidgetInstance.TrainCrowding do @type widget_data :: %{ destination: String.t(), crowding: list(crowding_level), - platform_position: number, + # Describes where the "you are here" arrow should be positioned. + # 1: leftmost, 25: rightmost + platform_position: 1..25, front_car_direction: :left | :right, now: String.t() } From e9c6a03557c15084353bf1960ccb3fdb918958e8 Mon Sep 17 00:00:00 2001 From: Jon Zimbel Date: Wed, 30 Aug 2023 10:53:23 -0400 Subject: [PATCH 26/30] Adjust no-data/disabled templates to match new slot names --- assets/src/apps/v2/triptych.tsx | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/assets/src/apps/v2/triptych.tsx b/assets/src/apps/v2/triptych.tsx index 887ed7635..a7f57ad39 100644 --- a/assets/src/apps/v2/triptych.tsx +++ b/assets/src/apps/v2/triptych.tsx @@ -13,7 +13,6 @@ import { MappingContext } from "Components/v2/widget"; import { ResponseMapper, ResponseMapperContext, - LOADING_LAYOUT, } from "Components/v2/screen_container"; import ScreenPage from "Components/v2/screen_page"; @@ -37,12 +36,19 @@ const TYPE_TO_COMPONENT = { train_crowding: TrainCrowding, }; +const LOADING_LAYOUT = { + full_screen: { + type: "page_load_no_data", + }, + type: "screen_normal", +}; + const DISABLED_LAYOUT = { full_screen: { type: "no_data", show_alternatives: true, }, - type: "screen_takeover", + type: "screen_normal", }; const FAILURE_LAYOUT = DISABLED_LAYOUT; From 72059da291c26913fa04774c9bf223e450d0841d Mon Sep 17 00:00:00 2001 From: Jon Zimbel Date: Wed, 30 Aug 2023 11:32:04 -0400 Subject: [PATCH 27/30] Finish documenting OFM util functions and hooks --- assets/src/hooks/outfront.tsx | 9 +++++++++ assets/src/util/outfront.tsx | 12 ++++++++++++ 2 files changed, 21 insertions(+) diff --git a/assets/src/hooks/outfront.tsx b/assets/src/hooks/outfront.tsx index b2989d082..710de4255 100644 --- a/assets/src/hooks/outfront.tsx +++ b/assets/src/hooks/outfront.tsx @@ -7,11 +7,20 @@ import { } from "Util/outfront"; import type { TriptychPane } from "Util/outfront"; +/** + * Returns the player name, or null if we fail to determine it for any reason. + */ export const usePlayerName = (): string | null => useMemo(getPlayerName, [getMRAID()]); +/** + * Returns the triptych pane, or null if we fail to determine it for any reason. + */ export const useTriptychPane = (): TriptychPane | null => useMemo(getTriptychPane, [getMRAID()]); +/** + * Returns the station name, or null if we fail to determine it for any reason. + */ export const useStationName = (): string | null => useMemo(getStationName, [getMRAID()]); diff --git a/assets/src/util/outfront.tsx b/assets/src/util/outfront.tsx index c63e6dced..189e96034 100644 --- a/assets/src/util/outfront.tsx +++ b/assets/src/util/outfront.tsx @@ -41,6 +41,13 @@ export const getRotationIndex = (): RotationIndex | null => { return isRotationIndex(rotationIndex) ? rotationIndex : null; }; +/** + * Gets Outfront's unique name/ID for the media player we're running on. + * + * For DUPs, this is just a single ID. For triptychs, each of the 3 panes has its own player name. + * + * Returns null if we fail to determine the player name for any reason. + */ export const getPlayerName = (): string | null => { let playerName = null; @@ -86,6 +93,11 @@ const getTriptychPaneFromTags = () => { return pane; }; +/** + * Gets name of the station (e.g. "Back Bay") from the `Station` tag. + * + * Returns null if we fail to determine the station name for any reason. + */ export const getStationName = (): string | null => { let station = null; From 1005d8b60e9e1ecf909888ffe2ea7bbffdf92d09 Mon Sep 17 00:00:00 2001 From: Jon Zimbel Date: Wed, 30 Aug 2023 11:34:02 -0400 Subject: [PATCH 28/30] Reorder lines for consistency with surrounding functions --- assets/src/util/outfront.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/assets/src/util/outfront.tsx b/assets/src/util/outfront.tsx index 189e96034..a6e016d71 100644 --- a/assets/src/util/outfront.tsx +++ b/assets/src/util/outfront.tsx @@ -110,9 +110,9 @@ export const getStationName = (): string | null => { }; const getTags = (): OFMTag[] | null => { - const mraid = getMRAID(); - let tags = null; + + const mraid = getMRAID(); if (mraid) { try { tags = JSON.parse(mraid.getTags()).tags as OFMTag[]; From 9e768380a27e52abcbec35cd5d7e32b370e8bc04 Mon Sep 17 00:00:00 2001 From: Jon Zimbel Date: Wed, 30 Aug 2023 13:04:29 -0400 Subject: [PATCH 29/30] Use getMRAID in useCurrentPage + update fake MRAID with new fields for that --- assets/src/hooks/use_current_dup_page.tsx | 9 ++---- assets/src/util/outfront.tsx | 35 +++++++++++++++++++++-- 2 files changed, 35 insertions(+), 9 deletions(-) diff --git a/assets/src/hooks/use_current_dup_page.tsx b/assets/src/hooks/use_current_dup_page.tsx index 86903dc80..67265f68e 100644 --- a/assets/src/hooks/use_current_dup_page.tsx +++ b/assets/src/hooks/use_current_dup_page.tsx @@ -1,16 +1,13 @@ import { useEffect, useState } from "react"; +import { getMRAID } from "Util/outfront"; const useCurrentPage = () => { const [page, setPage] = useState(0); const [paging, setPaging] = useState(false); - let mraid; - - try { - mraid = parent?.parent?.mraid; - } catch (_) {} - useEffect(() => { + const mraid = getMRAID(); + if (mraid) { const layoutID = mraid.requestInit(); mraid.addEventListener( diff --git a/assets/src/util/outfront.tsx b/assets/src/util/outfront.tsx index a6e016d71..8048f9ed0 100644 --- a/assets/src/util/outfront.tsx +++ b/assets/src/util/outfront.tsx @@ -57,7 +57,7 @@ export const getPlayerName = (): string | null => { const deviceInfoJSON = mraid.getDeviceInfo(); const deviceInfo = JSON.parse(deviceInfoJSON); playerName = deviceInfo.deviceName; - } catch (err) {} + } catch (err) { } } return playerName; @@ -116,7 +116,7 @@ const getTags = (): OFMTag[] | null => { if (mraid) { try { tags = JSON.parse(mraid.getTags()).tags as OFMTag[]; - } catch (err) {} + } catch (err) { } } return tags; @@ -148,8 +148,16 @@ interface OFMWindow extends Window { interface MRAID { getTags(): string; getDeviceInfo(): string; + + // The below fields/methods are used by logic that runs when the app is foregrounded + requestInit(): LayoutID; + addEventListener(eventID: EventID, callback: () => void, layoutID: LayoutID): void; + EVENTS: { ONSCREEN: EventID } } +type LayoutID = any; +type EventID = any; + interface OFMTag { name: string; value: [any]; @@ -181,7 +189,8 @@ export const __TEST_setFakeMRAID__ = (options: { const deviceInfoJSON = JSON.stringify({ deviceName: playerName }); - const mraid = { + const mraid: MRAID = { + ...BASE_MRAID, getTags() { return tagsJSON; }, @@ -201,3 +210,23 @@ export const __TEST_setFakeMRAID__ = (options: { // on the current window, and the code that reads `window.parent.parent.mraid` will still access it correctly. (window as OFMWindow).mraid = mraid; }; + +const BASE_MRAID: Pick = { + // Stubbed methods/fields for foreground detection logic + EVENTS: { ONSCREEN: "fakeOnscreenEvent" }, + requestInit() { + return "fakeLayoutID"; + }, + addEventListener(eventID, callback, layoutID) { + if (eventID == "fakeOnscreenEvent" && layoutID == "fakeLayoutID") { + console.log("FakeMRAID: Setting fake ONSCREEN event to fire in 3 seconds"); + + setTimeout(() => { + console.log("FakeMRAID: Firing fake ONSCREEN event"); + callback(); + }, 2000); + } else { + throw new Error("FakeMRAID: Stubbed addEventListener method expected eventID of 'fakeOnscreenEvent' and layoutID of 'fakeLayoutID'"); + } + } +} From f266e7c5fbb4f43d43648a8f86814d46fb24a330 Mon Sep 17 00:00:00 2001 From: Jon Zimbel Date: Wed, 30 Aug 2023 13:05:06 -0400 Subject: [PATCH 30/30] Prettier --- assets/src/hooks/use_current_dup_page.tsx | 2 +- assets/src/util/outfront.tsx | 24 +++++++++++++++-------- 2 files changed, 17 insertions(+), 9 deletions(-) diff --git a/assets/src/hooks/use_current_dup_page.tsx b/assets/src/hooks/use_current_dup_page.tsx index 67265f68e..ea1070fbe 100644 --- a/assets/src/hooks/use_current_dup_page.tsx +++ b/assets/src/hooks/use_current_dup_page.tsx @@ -13,7 +13,7 @@ const useCurrentPage = () => { mraid.addEventListener( mraid.EVENTS.ONSCREEN, () => setPaging(true), - layoutID + layoutID, ); } else { setPaging(true); diff --git a/assets/src/util/outfront.tsx b/assets/src/util/outfront.tsx index 8048f9ed0..fffd40bad 100644 --- a/assets/src/util/outfront.tsx +++ b/assets/src/util/outfront.tsx @@ -57,7 +57,7 @@ export const getPlayerName = (): string | null => { const deviceInfoJSON = mraid.getDeviceInfo(); const deviceInfo = JSON.parse(deviceInfoJSON); playerName = deviceInfo.deviceName; - } catch (err) { } + } catch (err) {} } return playerName; @@ -116,7 +116,7 @@ const getTags = (): OFMTag[] | null => { if (mraid) { try { tags = JSON.parse(mraid.getTags()).tags as OFMTag[]; - } catch (err) { } + } catch (err) {} } return tags; @@ -151,8 +151,12 @@ interface MRAID { // The below fields/methods are used by logic that runs when the app is foregrounded requestInit(): LayoutID; - addEventListener(eventID: EventID, callback: () => void, layoutID: LayoutID): void; - EVENTS: { ONSCREEN: EventID } + addEventListener( + eventID: EventID, + callback: () => void, + layoutID: LayoutID, + ): void; + EVENTS: { ONSCREEN: EventID }; } type LayoutID = any; @@ -219,14 +223,18 @@ const BASE_MRAID: Pick = { }, addEventListener(eventID, callback, layoutID) { if (eventID == "fakeOnscreenEvent" && layoutID == "fakeLayoutID") { - console.log("FakeMRAID: Setting fake ONSCREEN event to fire in 3 seconds"); + console.log( + "FakeMRAID: Setting fake ONSCREEN event to fire in 3 seconds", + ); setTimeout(() => { console.log("FakeMRAID: Firing fake ONSCREEN event"); callback(); }, 2000); } else { - throw new Error("FakeMRAID: Stubbed addEventListener method expected eventID of 'fakeOnscreenEvent' and layoutID of 'fakeLayoutID'"); + throw new Error( + "FakeMRAID: Stubbed addEventListener method expected eventID of 'fakeOnscreenEvent' and layoutID of 'fakeLayoutID'", + ); } - } -} + }, +};