X-Git-Url: https://gitweb.michael.orlitzky.com/?a=blobdiff_plain;f=index.html.in;h=ce22f37a7eb472ed16ccb76893392dc82b182713;hb=HEAD;hp=e14b87df82b4899df299a1d2bb4c9bb54c803ea0;hpb=5e410c090440052303ffe0524746c6e11345c89b;p=charm-bypass.git diff --git a/index.html.in b/index.html.in index e14b87d..ce22f37 100644 --- a/index.html.in +++ b/index.html.in @@ -8,10 +8,15 @@ href="data:image/svg+xml;base64,@FAVICON@" /> - CharmBypass: it's transit equity y'all + CharmBypass: got that transit equity @@ -183,15 +198,7 @@ -

Zone:

+

+ Zone: +

Zone 2
- @@ -269,6 +278,11 @@
+ +

+ + On the MTA's PDF schedule for your route +

@@ -280,7 +294,7 @@ Daily security code (optional): Origin:

- - +
- +
- +
- +
- + +
+
+ +
+ +
+
+ + +
+
+ - +
- +
- +

Destination:

- +
- +
- +
- +
- +
- + id="destination_FRC" + value="FRC" /> + +
+
+ +
+ +
+
+ + +
+
+ - +
- +
+

OK

+ - +
@@ -506,7 +567,7 @@ const t = document.getElementById("ticket"); const sn = document.getElementById("servicename"); - if (params.get("servicename") == "Commuter Bus") { + if (params.get("servicename") === "Commuter Bus") { /* The top of the background is initially at y=246.859, and * we scale it by a factor of 1.12 to y=276.482 for a change * of 29.623. So after we scale it, we translate it upwards @@ -523,7 +584,7 @@ * tickets overlayed in inkscape */ sn.setAttribute("transform", "translate(0 64.28)"); } - else if (params.get("servicename") == "MARC Train") { + else if (params.get("servicename") === "MARC Train") { /* insane tricks are explained above */ tbg.setAttribute("transform", "translate(0 -72.378) scale(1 1.2932)"); @@ -625,7 +686,7 @@ /* BaltimoreLink and MARC Train */ let minutes = 90; const params = new URLSearchParams(document.location.search); - if (params.get("servicename") == "Commuter Bus") { + if (params.get("servicename") === "Commuter Bus") { /* Commuter bus tickets are only valid for ten minutes */ minutes = 10; } @@ -662,7 +723,7 @@ function swap_day_night() { const sky = document.getElementById("sky"); - if (sky.getAttribute("class") == "night") { + if (sky.getAttribute("class") === "night") { sky.setAttribute("fill", "#efb02f"); sky.setAttribute("class", "day"); } @@ -680,23 +741,51 @@ * Compute the zone (string) for the given origin/destination pair. * * If we don't know it or if you chose in invalid pair (destination - * not on the same line as your origin?) then the empty string is - * returned. + * not on the same line as your origin?) then null is returned. */ function compute_marc_zone(src, dest) { - /* Sorted on the first component, then the second */ + /* Sorted on the first component, then the second. + * + * Key: + * + * $6.00 => 1 + * $7.00 => 2 + * $8.00 => 3 + * $9.00 => 4 + */ const zone_map = { BAL_BWE: 2, BAL_BWI: 1, + BAL_HAE: 1, + BAL_NCR: 3, BAL_SEB: 3, BAL_WAS: 4, BAL_WBL: 1, - BCA_CPK_: 3, - BCA_WAS_: 4, - BWI_BWE: 1, + BCA_CPK: 3, + BCA_WAS: 4, + BWE_BWI: 1, + BWE_HAE: 1, + BWE_NCR: 1, + BWE_SEB: 1, + BWE_WAS: 2, + BWE_WBL: 2, + BWI_HAE: 1, + BWI_NCR: 2, + BWI_SEB: 2, BWI_WAS: 3, - BWI_WBL: 1 + BWI_WBL: 1, + FRC_WAS: 4, + HAE_NCR: 2, + HAE_SEB: 2, + HAE_WAS: 3, + HAE_WBL: 1, + NCR_SEB: 1, + NCR_WAS: 1, + NCR_WBL: 3, + SEB_WAS: 1, + SEB_WBL: 3, + WAS_WBL: 4 }; /* Forward direction key for zone_map */ @@ -729,7 +818,7 @@ case 4: return "Four Zone"; default: - return ""; + return null; } } @@ -749,7 +838,8 @@ const dest = params.get("destination"); if (src && dest) { - /* MARC Train */ + /* MARC Train. We can assume that compute_marc_zone() doesn't + * return null because that's part of our form validation. */ const zone = compute_marc_zone(src, dest); set_zone(zone); } @@ -804,6 +894,78 @@ menu.style.display = "none"; } + /** + * Determine if the user agent is mobile Firefox. + */ + function ua_is_mobile_ff() { + const ua = navigator.userAgent.toLowerCase(); + return (ua.includes("firefox") && ua.includes("mobile")); + } + + /** + * Validate the MARC form's origin/destination. + * + * We don't want the user to be able to choose a pair of stops that + * aren't actually connected by the same MARC line. If we don't have + * zone information for the (origin,destination) pair, that indicates + * that it's probably not a valid choice; otherwise I would have + * filled in the information from the CharmPass app already. + * + * All browsers except mobile Firefox let us call setCustomValidity() + * to provide an error message that is displayed if the user tries + * to submit invalid choices. But amazingly, mobile Firefox does not: + * + * https://bugzilla.mozilla.org/show_bug.cgi?id=1510450 + * + * Instead we have to work around it (in that one browser) by + * showing/hiding a paragraph that we fill with the errors. + */ + function validate_origin_destination(event) { + const origins = document.getElementsByName("origin"); + const destinations = document.getElementsByName("destination"); + const mfe = document.getElementById("marc-form-errors"); + const marcsubmit = document.getElementById("marc-submit"); + + if (ua_is_mobile_ff()) { + /* Even though this is only for one browser, empty paragraphs + * are handled inconsistently and should be avoided as a rule. + * So, we make it say "OK" before we hide it. */ + mfe.textContent = "OK"; + mfe.style.visibility = "hidden"; + marcsubmit.disabled = false; + } + + let src = null; + let dest = null; + origins.forEach((x) => { if (x.checked) src = x; }) + destinations.forEach((x) => { + if (x.checked) dest = x; + + /* clear all errors before possibly setting one */ + x.setCustomValidity(''); + }) + + if (src.value === dest.value) { + let err = "Origin and destination are the same"; + dest.setCustomValidity(err); + + if (ua_is_mobile_ff()) { + mfe.textContent = err; + mfe.style.visibility = "visible"; + marcsubmit.disabled = true; + } + } + else if (compute_marc_zone(src.value, dest.value) === null) { + let err = "Origin and destination are on different lines"; + dest.setCustomValidity(err); + + if (ua_is_mobile_ff()) { + mfe.textContent = err; + mfe.style.visibility = "visible"; + marcsubmit.disabled = true; + } + } + } /*****************************************************/ /* Add event handlers for all of the functions above */ @@ -848,6 +1010,33 @@ /* Swap colors when the screen is tapped */ document.body.addEventListener("click", swap_day_night); } + else { + /* If we haven't submitted the form yet, set up change handlers + * for the origin/destination radio buttons that validate that + * the origin and destination are on the same line. */ + document.getElementsByName("origin").forEach( + (x) => x.addEventListener("change", validate_origin_destination) + ); + document.getElementsByName("destination").forEach( + (x) => x.addEventListener("change", validate_origin_destination) + ); + + /* Also do it when the page loads, because firefox likes to + * remember your selection even after the page reloads. */ + window.addEventListener("load", validate_origin_destination); + + /* Finally, we have to babysit mobile Firefox, who doesn't + * support HTML5 form validation going into 2024. Turn on + * the little form errors paragraph so we can toggle its + * visibility (and make it display the error) when the user + * makes an invalid selection. */ + window.addEventListener("load", () => { + if (ua_is_mobile_ff()) { + const mfe = document.getElementById("marc-form-errors"); + mfe.style.display = "block"; + } + }); + }