{"version":3,"sources":["happybart.png","Stations.ts","App.tsx","serviceWorker.ts","index.tsx"],"names":["module","exports","bartStations","hashToLocation","h","substr","split","station","dir","useHashLocation","useState","window","location","hash","setLocation","onhashchange","changeLocation","useCallback","info","postData","a","url","data","fetch","method","mode","cache","credentials","headers","redirect","referrer","body","JSON","stringify","response","json","BartRow","etd","onSubmitNewBart","time","getMinutesFromETD","formattedString","bartBarWidth","submittedNewBart","setSubmittedNewBart","className","style","width","background","hexcolor","src","happybart","alt","type","onClick","disabled","allEtdsForEveryTrain","direction","console","log","bartPromise","then","resp","ok","Error","bartStationETD","root","map","train","estimate","flat","sort","b","convertStrMinutesToNumMinutes","minutes","strMinutes","parseInt","App","etds","setEtds","updateTrains","allEstimates","useEffect","callback","delay","savedCallback","useRef","current","id","setInterval","clearInterval","useInterval","formattedTimeEstimates","i","color","lineColor","key","name","onChange","v","target","value","selected","Boolean","hostname","match","ReactDOM","render","document","getElementById","navigator","serviceWorker","ready","registration","unregister"],"mappings":"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","file":"static/js/main.93933d11.chunk.js","sourcesContent":["module.exports = __webpack_public_path__ + \"static/media/happybart.6158c9a2.png\";","export type BartStation = typeof bartStations[number];\nexport const bartStations = [\n \"12th\",\n \"16th\",\n \"19th\",\n \"24th\",\n \"ashb\",\n \"antc\",\n \"balb\",\n \"bayf\",\n \"cast\",\n \"civc\",\n \"cols\",\n \"colm\",\n \"conc\",\n \"daly\",\n \"dbrk\",\n \"dubl\",\n \"deln\",\n \"plza\",\n \"embr\",\n \"frmt\",\n \"ftvl\",\n \"glen\",\n \"hayw\",\n \"lafy\",\n \"lake\",\n \"mcar\",\n \"mlbr\",\n \"mont\",\n \"nbrk\",\n \"ncon\",\n \"oakl\",\n \"orin\",\n \"pitt\",\n \"pctr\",\n \"phil\",\n \"powl\",\n \"rich\",\n \"rock\",\n \"sbrn\",\n \"sfia\",\n \"sanl\",\n \"shay\",\n \"ssan\",\n \"ucty\",\n \"warm\",\n \"wcrk\",\n \"wdub\",\n \"woak\"\n] as const;\n","import React, { useState, useEffect, useRef, useCallback } from \"react\";\nimport { BartStation, bartStations } from \"./Stations\";\nimport \"./App.css\";\n\nimport happybart from \"./happybart.png\";\n\ntype ETD = {\n length: string;\n minutes: string;\n hexcolor: string;\n};\n\ntype BartLineETD = {\n abbreviation: string;\n destination: string;\n estimate: Array;\n};\n\ntype BartStationETD = {\n root: {\n station: Array<{\n abbr: string;\n etd: Array;\n }>;\n };\n};\n\n// class AppClassStyle extends React.Component {\n// state: { estimates: Array } = { estimates: [] };\n// componentDidMount() {\n// allEstimatesForEveryTrain(\"woak\", \"s\").then(\n// (allEstimates: Array) => {\n// this.setState({ estimates: allEstimates });\n// }\n// );\n// }\n\n// shouldComponentUpdate(prevProps) {\n// if (prevProps.foo != this.props.foo) {\n// //... do something\n// }\n// }\n\n// render() {\n// console.log(\"Time estimates are\", this.state.estimates);\n\n// const formattedTimeEstimates = this.state.estimates\n// .map((time: number) => `${time} minutes`)\n// .map((formattedString: string) => {\n// return
{formattedString}
;\n// });\n\n// return (\n//
\n//
\n//

Your next Bart train is leaving in

\n// {formattedTimeEstimates}\n//
\n//
\n// );\n// }\n// }\n\nfunction useInterval(callback: () => void, delay: number) {\n const savedCallback = useRef();\n\n // Remember the latest callback.\n useEffect(() => {\n savedCallback.current = callback;\n }, [callback]);\n\n // Set up the interval.\n useEffect(() => {\n function tick() {\n savedCallback.current && savedCallback.current();\n }\n if (delay !== null) {\n let id = setInterval(tick, delay);\n return () => clearInterval(id);\n }\n }, [delay]);\n}\n\ntype LocationInfo = {\n station: BartStation;\n dir: \"s\" | \"n\";\n};\nfunction hashToLocation(h: string): LocationInfo {\n const [station, dir] = h.substr(1).split(\"/\");\n return {\n station: (station || \"woak\") as BartStation,\n dir: (dir || \"s\") as any\n };\n}\n\nfunction useHashLocation(): [\n LocationInfo,\n (info: Partial) => void\n] {\n const [location, setLocation] = useState(\n hashToLocation(window.location.hash)\n );\n window.onhashchange = () => {\n setLocation(hashToLocation(window.location.hash));\n };\n\n const changeLocation = useCallback(\n (info: Partial) => {\n info = { ...location, ...info };\n window.location.hash = `${info.station}/${info.dir}`;\n },\n [location]\n );\n\n return [location, changeLocation];\n}\n\nasync function postData(url = \"\", data = {}) {\n // Default options are marked with *\n const response = await fetch(url, {\n method: \"POST\", // *GET, POST, PUT, DELETE, etc.\n mode: \"cors\", // no-cors, *cors, same-origin\n cache: \"no-cache\", // *default, no-cache, reload, force-cache, only-if-cached\n credentials: \"same-origin\", // include, *same-origin, omit\n headers: {\n \"Content-Type\": \"application/json\"\n // 'Content-Type': 'application/x-www-form-urlencoded',\n },\n redirect: \"follow\", // manual, *follow, error\n referrer: \"no-referrer\", // no-referrer, *client\n body: JSON.stringify(data) // body data type must match \"Content-Type\" header\n });\n return await response.json(); // parses JSON response into native JavaScript objects\n}\n\nconst BartRow = ({\n etd,\n onSubmitNewBart\n}: {\n etd: ETD;\n onSubmitNewBart: (color: string) => void;\n}) => {\n const time = getMinutesFromETD(etd);\n const formattedString = `${time} minutes`;\n const bartBarWidth = `calc(100% - ${5 * time}px)`;\n const [submittedNewBart, setSubmittedNewBart] = useState(false);\n return (\n
\n {formattedString}\n
\n \n \"bart,\n
\n
\n {\n setSubmittedNewBart(true);\n try {\n await onSubmitNewBart(etd.hexcolor);\n } catch (e) {\n setSubmittedNewBart(false);\n }\n }}\n disabled={submittedNewBart}\n >\n {submittedNewBart ? \"Thanks!\" : \"New Bart!\"}\n \n \n );\n};\n\nconst App: React.FC = () => {\n const [location, changeLocation] = useHashLocation();\n const [etds, setEtds] = useState>([]);\n console.log(\"Location is\", location);\n\n const updateTrains = () => {\n allEtdsForEveryTrain(location.station || \"woak\", location.dir || \"s\").then(\n (allEstimates: Array) => {\n setEtds(allEstimates);\n }\n );\n };\n\n // initial fetch without delay!\n useEffect(updateTrains, [location]);\n\n useInterval(updateTrains, 40 * 1000);\n\n console.log(\"ETDs are\", etds);\n\n const formattedTimeEstimates = etds.map((etd: ETD, i: number) => (\n {\n await postData(\"https://marco-new-barts.builtwithdark.com/report\", {\n station: location.station,\n direction: location.dir,\n lineColor: color\n });\n }}\n key={i}\n />\n ));\n\n return (\n
\n
\n

\n Your next Bart train from\n changeLocation({ station: v.target.value as any })}\n >\n \n {bartStations.map(station => (\n \n {station}\n \n ))}\n \n going\n changeLocation({ dir: v.target.value as any })}\n >\n \n \n \n is leaving in\n

\n
{formattedTimeEstimates}
\n
\n
\n );\n};\n\n// http://api.bart.gov/api/etd.aspx?cmd=etd&dir=n&orig=WOAK&key=MW9S-E7SL-26DU-VV8V&json=y\n\n// https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API\nconst allEtdsForEveryTrain = (\n station: BartStation,\n direction: \"s\" | \"n\"\n): Promise => {\n console.log(`You want to leave from ${station} going ${direction}`);\n const bartPromise: Promise = fetch(\n `https://api.bart.gov/api/etd.aspx?cmd=etd&dir=${direction}&orig=${station}&key=MW9S-E7SL-26DU-VV8V&json=y`\n );\n console.log(bartPromise);\n\n const estimates = bartPromise\n .then((resp: Response) => {\n if (resp.ok) {\n // https://developer.mozilla.org/en-US/docs/Web/API/ReadableStream\n return resp.json();\n } else {\n throw new Error(\"I didn't get the info from bart :(\");\n }\n })\n .then((bartStationETD: BartStationETD) => {\n const etdsForStation = bartStationETD.root.station[0].etd;\n const allEtdsForEveryTrain: ETD[] = etdsForStation\n .map(train => train.estimate)\n .flat()\n .sort(\n (a, b) =>\n convertStrMinutesToNumMinutes(a.minutes) -\n convertStrMinutesToNumMinutes(b.minutes)\n );\n\n console.log(\"All etds: \", allEtdsForEveryTrain);\n return allEtdsForEveryTrain;\n });\n return estimates;\n};\n\nconst convertStrMinutesToNumMinutes = (strMinutes: string) => {\n return strMinutes === \"Leaving\" ? 0 : parseInt(strMinutes);\n};\n\nconst getMinutesFromETD = (etd: ETD) => {\n return convertStrMinutesToNumMinutes(etd.minutes);\n};\n\n// Overview\n// 1. Get the data ✅\n// 2. parse the data/format the data ✅\n// 3. Show it off!\n// ---\n// 4. Allow opt url pararmeters (http://localhost:3000/#woak/s)\n\n// https://bart.marcopolo.io/\n\n// http://qwantz.com/\n\nexport default App;\n\n// function bar() {\n// console.log(\"Hello from bar\");\n// this.foo = \"From bar\";\n\n// const arrowFn = () =>\n// console.log(\"Arrow fn\", (this && this.foo) || \"Foo is not set\");\n// arrowFn();\n\n// const nonArrowFn = function() {\n// console.log(\"Non arrow fn\", (this && this.foo) || \"Foo is not set\");\n// };\n\n// const boundNonArrowFn = nonArrowFn.bind({ foo: \"binded thing\" });\n\n// nonArrowFn();\n// nonArrowFn.call({ foo: \"SOMETHING ELSE\" });\n// boundNonArrowFn();\n// }\n\n// bar.call({});\n","// This optional code is used to register a service worker.\n// register() is not called by default.\n\n// This lets the app load faster on subsequent visits in production, and gives\n// it offline capabilities. However, it also means that developers (and users)\n// will only see deployed updates on subsequent visits to a page, after all the\n// existing tabs open on the page have been closed, since previously cached\n// resources are updated in the background.\n\n// To learn more about the benefits of this model and instructions on how to\n// opt-in, read https://bit.ly/CRA-PWA\n\nconst isLocalhost = Boolean(\n window.location.hostname === 'localhost' ||\n // [::1] is the IPv6 localhost address.\n window.location.hostname === '[::1]' ||\n // 127.0.0.1/8 is considered localhost for IPv4.\n window.location.hostname.match(\n /^127(?:\\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/\n )\n);\n\ntype Config = {\n onSuccess?: (registration: ServiceWorkerRegistration) => void;\n onUpdate?: (registration: ServiceWorkerRegistration) => void;\n};\n\nexport function register(config?: Config) {\n if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) {\n // The URL constructor is available in all browsers that support SW.\n const publicUrl = new URL(\n (process as { env: { [key: string]: string } }).env.PUBLIC_URL,\n window.location.href\n );\n if (publicUrl.origin !== window.location.origin) {\n // Our service worker won't work if PUBLIC_URL is on a different origin\n // from what our page is served on. This might happen if a CDN is used to\n // serve assets; see https://github.com/facebook/create-react-app/issues/2374\n return;\n }\n\n window.addEventListener('load', () => {\n const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`;\n\n if (isLocalhost) {\n // This is running on localhost. Let's check if a service worker still exists or not.\n checkValidServiceWorker(swUrl, config);\n\n // Add some additional logging to localhost, pointing developers to the\n // service worker/PWA documentation.\n navigator.serviceWorker.ready.then(() => {\n console.log(\n 'This web app is being served cache-first by a service ' +\n 'worker. To learn more, visit https://bit.ly/CRA-PWA'\n );\n });\n } else {\n // Is not localhost. Just register service worker\n registerValidSW(swUrl, config);\n }\n });\n }\n}\n\nfunction registerValidSW(swUrl: string, config?: Config) {\n navigator.serviceWorker\n .register(swUrl)\n .then(registration => {\n registration.onupdatefound = () => {\n const installingWorker = registration.installing;\n if (installingWorker == null) {\n return;\n }\n installingWorker.onstatechange = () => {\n if (installingWorker.state === 'installed') {\n if (navigator.serviceWorker.controller) {\n // At this point, the updated precached content has been fetched,\n // but the previous service worker will still serve the older\n // content until all client tabs are closed.\n console.log(\n 'New content is available and will be used when all ' +\n 'tabs for this page are closed. See https://bit.ly/CRA-PWA.'\n );\n\n // Execute callback\n if (config && config.onUpdate) {\n config.onUpdate(registration);\n }\n } else {\n // At this point, everything has been precached.\n // It's the perfect time to display a\n // \"Content is cached for offline use.\" message.\n console.log('Content is cached for offline use.');\n\n // Execute callback\n if (config && config.onSuccess) {\n config.onSuccess(registration);\n }\n }\n }\n };\n };\n })\n .catch(error => {\n console.error('Error during service worker registration:', error);\n });\n}\n\nfunction checkValidServiceWorker(swUrl: string, config?: Config) {\n // Check if the service worker can be found. If it can't reload the page.\n fetch(swUrl)\n .then(response => {\n // Ensure service worker exists, and that we really are getting a JS file.\n const contentType = response.headers.get('content-type');\n if (\n response.status === 404 ||\n (contentType != null && contentType.indexOf('javascript') === -1)\n ) {\n // No service worker found. Probably a different app. Reload the page.\n navigator.serviceWorker.ready.then(registration => {\n registration.unregister().then(() => {\n window.location.reload();\n });\n });\n } else {\n // Service worker found. Proceed as normal.\n registerValidSW(swUrl, config);\n }\n })\n .catch(() => {\n console.log(\n 'No internet connection found. App is running in offline mode.'\n );\n });\n}\n\nexport function unregister() {\n if ('serviceWorker' in navigator) {\n navigator.serviceWorker.ready.then(registration => {\n registration.unregister();\n });\n }\n}\n","import React from 'react';\nimport ReactDOM from 'react-dom';\nimport './index.css';\nimport App from './App';\nimport * as serviceWorker from './serviceWorker';\n\nReactDOM.render(, document.getElementById('root'));\n\n// If you want your app to work offline and load faster, you can change\n// unregister() to register() below. Note this comes with some pitfalls.\n// Learn more about service workers: https://bit.ly/CRA-PWA\nserviceWorker.unregister();\n"],"sourceRoot":""}