You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

467 lines
12 KiB

  1. <template>
  2. <div :class="['page-container', { 'nav-is-showing': isPickerVisible }]">
  3. <header>
  4. <div class="header__col">
  5. <Logo />
  6. <div v-if="!selectedOrig.iata" class="logo-blurb">
  7. from one of {{ airports_orig.length }} airports
  8. </div>
  9. <div v-if="selectedOrig.iata && !selectedDest.iata && airports_dest.length > 1" class="logo-blurb">
  10. to one of {{ airports_dest.length }} beautiful destinations
  11. </div>
  12. </div>
  13. <div class="header__col">
  14. <div v-show="!isPickerVisible">
  15. <button class="btn btn--primary" @click="showPicker">
  16. tap the map or search 🔎
  17. </button>
  18. </div>
  19. <div v-show="isPickerVisible">
  20. <button class="btn btn--primary" @click="showPicker">
  21. hide search
  22. </button>
  23. </div>
  24. </div>
  25. <div class="header__col">
  26. <button
  27. class="btn btn--primary"
  28. @click="showSidebar"
  29. >
  30. </button>
  31. </div>
  32. </header>
  33. <!-- <pre style="position: fixed; z-index: 500; right: 0; top: 2rem; font-size: 0.6rem; color: gray;">
  34. {{ selectedOrig }}
  35. {{ selectedDest }}
  36. </pre> -->
  37. <main>
  38. <nav :class="['nav', { 'nav--show': isPickerVisible },{ 'nav--hide': !isPickerVisible }]">
  39. <AirportPicker
  40. :airports="airports_orig"
  41. :selected-airport="selectedOrig"
  42. @select-airport="makeOrigin"
  43. @deselect-airport="clearOrigin"
  44. />
  45. <AirportPicker
  46. v-show="selectedOrig.iata"
  47. :airports="airports_dest"
  48. :selected-airport="selectedDest"
  49. @select-airport="makeDestination"
  50. @deselect-airport="clearDestination"
  51. />
  52. </nav>
  53. <Map
  54. :airports-orig="airports_orig"
  55. :airports-dest="airports_dest"
  56. :selected-orig="selectedOrig"
  57. :selected-dest="selectedDest"
  58. @make-origin="makeOrigin"
  59. @make-destination="makeDestination"
  60. @clear-origin="clearOrigin"
  61. @clear-destination="clearDestination"
  62. />
  63. <aside
  64. :class="['sidebar-container',{ 'sidebar-container--hide': !isSidebarVisible },{ 'sidebar-container--show': isSidebarVisible }]"
  65. >
  66. <div class="sidebar">
  67. sidebar info
  68. </div>
  69. </aside>
  70. <div :class="['flyout-container',{ 'flyout-container--hide': !selectedOrig.iata || !selectedDest.iata },{ 'flyout-container--show': selectedOrig.iata && selectedDest.iata }]">
  71. <FlightsFlyout
  72. :selected-orig="selectedOrig"
  73. :selected-dest="selectedDest"
  74. />
  75. </div>
  76. </main>
  77. </div>
  78. </template>
  79. <script>
  80. // import AirportPicker from '../components/AirportPicker.vue'
  81. export default {
  82. // components: { AirportPicker },
  83. data () {
  84. return {
  85. color: 'red',
  86. mountains: [
  87. {
  88. slug: 'sluggy',
  89. title: 'titly'
  90. }
  91. ],
  92. isSidebarVisible: false,
  93. isPickerVisible: false,
  94. airports_orig: [],
  95. airports_dest: [],
  96. airportFetch_filterFormula: 'AND(Is_Origin=1,{IsCurrent-AsOrigin}=\'Yes\')',
  97. airportFetch_fields: [
  98. 'Airport_IATA',
  99. 'Icon_Url',
  100. 'Latitude_Deg',
  101. 'Longitude_Deg',
  102. 'Municipality',
  103. 'Airport_Name',
  104. 'Type',
  105. 'Search_Field'
  106. ],
  107. airportFetch_sort: '&sort[0][field]=Airport_IATA&sort[0][direction]=asc',
  108. selectedOrig: {
  109. iata: '',
  110. lat: '',
  111. long: '',
  112. icon: '',
  113. name: '',
  114. municipality: '',
  115. type: '',
  116. search: ''
  117. },
  118. selectedDest: {
  119. iata: '',
  120. lat: '',
  121. long: '',
  122. icon: '',
  123. name: '',
  124. municipality: '',
  125. type: '',
  126. search: ''
  127. },
  128. emptyAirport: {
  129. iata: '',
  130. lat: '',
  131. long: '',
  132. icon: '',
  133. name: '',
  134. municipality: '',
  135. type: '',
  136. search: ''
  137. }
  138. }
  139. },
  140. async fetch () {
  141. this.mountains = await fetch(
  142. 'https://api.nuxtjs.dev/mountains'
  143. ).then(res => res.json())
  144. let response2 = {}
  145. let mapData2 = {}
  146. const response = await fetch(`https://api.airtable.com/v0/appiQwfVZixRgRICe/Airports_IATA?filterByFormula=${this.airportFetch_filterFormula}${this.airportFetch_fields_string}${this.airportFetch_sort}`, {
  147. method: 'GET',
  148. headers: {
  149. 'Content-Type': 'application/x-www-form-urlencoded',
  150. Authorization: 'Bearer keyJ2ht64ZSN57AG1'
  151. }
  152. })
  153. if (await response.offset) {
  154. response2 = fetch(`https://api.airtable.com/v0/appiQwfVZixRgRICe/Airports_IATA?filterByFormula=${this.airportFetch_filterFormula}&fields[]=Airport_IATA&fields[]=Icon_Url&fields[]=Latitude_Deg&fields[]=Longitude_Deg&sort[0][field]=Airport_IATA&sort[0][direction]=asc&offset=${response.offset}`, {
  155. method: 'GET',
  156. headers: {
  157. 'Content-Type': 'application/x-www-form-urlencoded',
  158. Authorization: 'Bearer keyJ2ht64ZSN57AG1'
  159. }
  160. })
  161. mapData2 = await response2.json()
  162. }
  163. const mapData = await response.json()
  164. const r1 = mapData.records
  165. const r2 = mapData2.records
  166. console.log(r2)
  167. // const mapDataTotal = [...r1, ...r2]
  168. const mapDataTotal = [...r1]
  169. this.airports_orig = mapDataTotal.map((record) => {
  170. return {
  171. iata: record.fields.Airport_IATA,
  172. lat: record.fields.Latitude_Deg,
  173. long: record.fields.Longitude_Deg,
  174. icon: record.fields.Icon_Url,
  175. name: record.fields.Airport_Name,
  176. municipality: record.fields.Municipality,
  177. type: record.fields.Type,
  178. search: record.fields.Search_Field
  179. }
  180. })
  181. },
  182. computed: {
  183. airportFetch_fields_string () {
  184. const vm = this
  185. const array = vm.airportFetch_fields.map((field) => {
  186. return '&fields[]=' + field
  187. })
  188. return array.join('')
  189. }
  190. },
  191. created () {
  192. // console.log(this.$route.path)
  193. if (this.$route && this.$route.params && this.$route.params.o) {
  194. this.fetchSingleAirport(this.$route.params.o, true)
  195. }
  196. if (this.$route && this.$route.params && this.$route.params.d) {
  197. this.fetchSingleAirport(this.$route.params.d, false)
  198. }
  199. },
  200. // watch: {
  201. // history (to, from) {
  202. // console.log(`routed from ${from} to ${to}`)
  203. // }
  204. // },
  205. methods: {
  206. makeOrigin (airport) {
  207. // console.log(airport)
  208. this.selectedOrig = { ...airport }
  209. this.selectedDest = { ...this.emptyAirport }
  210. history.pushState(
  211. {},
  212. null,
  213. '/go/' + encodeURIComponent(airport.iata)
  214. )
  215. this.fetchDestinations(airport.iata)
  216. },
  217. makeDestination (airport) {
  218. this.selectedDest = { ...airport }
  219. history.pushState(
  220. {},
  221. null,
  222. '/go/' + encodeURIComponent(this.selectedOrig.iata) + '/' + encodeURIComponent(airport.iata)
  223. )
  224. },
  225. clearOrigin () {
  226. this.selectedOrig = { ...this.emptyAirport }
  227. this.selectedDest = { ...this.emptyAirport }
  228. history.pushState(
  229. {},
  230. null,
  231. '/go/'
  232. )
  233. },
  234. clearDestination () {
  235. this.selectedDest = { ...this.emptyAirport }
  236. history.pushState(
  237. {},
  238. null,
  239. '/go/' + encodeURIComponent(this.selectedOrig.iata)
  240. )
  241. },
  242. async fetchDestinations (iata) {
  243. const airportFetchDestfilterFormula = `AND(Is_Destination=1,{IsCurrent-AsDest}="Yes",(FIND("${iata}", Associated_Origin_Search)>0))`
  244. const response = await fetch(`https://api.airtable.com/v0/appiQwfVZixRgRICe/Airports_IATA?filterByFormula=${airportFetchDestfilterFormula}${this.airportFetch_fields_string}${this.airportFetch_sort}`, {
  245. method: 'GET',
  246. headers: {
  247. 'Content-Type': 'application/x-www-form-urlencoded',
  248. Authorization: 'Bearer keyJ2ht64ZSN57AG1'
  249. }
  250. })
  251. const mapData = await response.json()
  252. // console.log('mapData.records length: ' + mapData.records.length)
  253. // console.log('this.airports_dest length (before): ' + this.airports_dest.length)
  254. this.airports_dest = mapData.records.map((record) => {
  255. return {
  256. iata: record.fields.Airport_IATA,
  257. lat: record.fields.Latitude_Deg,
  258. long: record.fields.Longitude_Deg,
  259. icon: record.fields.Icon_Url,
  260. name: record.fields.Airport_Name,
  261. municipality: record.fields.Municipality,
  262. type: record.fields.Type,
  263. search: record.fields.Search_Field
  264. }
  265. })
  266. // this.$refs.flMap.mapObject.fitBounds(this.markers.map((m) => { return [m.lat, m.lng] }))
  267. // console.log('this.airports_dest length (after)' + this.airports_dest.length)
  268. },
  269. async fetchSingleAirport (iata, isOrig) {
  270. const airportFetchFilterFormula =
  271. isOrig
  272. ? `AND(Is_Origin=1,{IsCurrent-AsOrigin}="Yes",Airport_IATA="${iata}")`
  273. : `AND(Is_Destination=1,{IsCurrent-AsDest}="Yes",Airport_IATA="${iata}")`
  274. const response = await fetch(`https://api.airtable.com/v0/appiQwfVZixRgRICe/Airports_IATA?filterByFormula=${airportFetchFilterFormula}${this.airportFetch_fields_string}`, {
  275. method: 'GET',
  276. headers: {
  277. 'Content-Type': 'application/x-www-form-urlencoded',
  278. Authorization: 'Bearer keyJ2ht64ZSN57AG1'
  279. }
  280. })
  281. const mapData = await response.json()
  282. const thisAirport = {
  283. iata: mapData.records[0].fields.Airport_IATA,
  284. lat: mapData.records[0].fields.Latitude_Deg,
  285. long: mapData.records[0].fields.Longitude_Deg,
  286. icon: mapData.records[0].fields.Icon_Url,
  287. name: mapData.records[0].fields.Airport_Name,
  288. municipality: mapData.records[0].fields.Municipality,
  289. type: mapData.records[0].fields.Type,
  290. search: mapData.records[0].fields.Search_Field
  291. }
  292. switch (isOrig) {
  293. case true:
  294. this.selectedOrig = { ...thisAirport }
  295. this.fetchDestinations(thisAirport.iata)
  296. break
  297. case false:
  298. this.selectedDest = { ...thisAirport }
  299. break
  300. default:
  301. break
  302. }
  303. },
  304. showPicker () {
  305. this.isPickerVisible = !this.isPickerVisible
  306. },
  307. showSidebar () {
  308. this.isSidebarVisible = !this.isSidebarVisible
  309. }
  310. }
  311. }
  312. </script>
  313. <style>
  314. html,
  315. body,
  316. #__nuxt,
  317. #__layout,
  318. .page-container,
  319. main {
  320. height: 100%;
  321. }
  322. header {
  323. position: fixed;
  324. top: 0;
  325. width: 100vw;
  326. z-index: 403;
  327. display: grid;
  328. grid-template-columns: 1fr 1fr 1fr;
  329. padding: 1rem;
  330. }
  331. .header__col:nth-child(2) {
  332. text-align: center;
  333. }
  334. .header__col:nth-child(3) {
  335. text-align: right;
  336. }
  337. .logo-blurb {
  338. position: relative;
  339. left: 4.5rem;
  340. color: white;
  341. top: -0.8rem;
  342. }
  343. main {
  344. display: flex;
  345. flex-direction: column;
  346. /* grid-template-rows: min-content 1fr; */
  347. }
  348. .nav {
  349. flex: 0 0 auto;
  350. /* position: fixed;
  351. top: 0;
  352. padding-top: 4rem;
  353. width: 100vw;
  354. z-index: 401; */
  355. background: white;
  356. display: flex;
  357. flex-direction: column;
  358. justify-content: flex-end;
  359. }
  360. :root {
  361. --m-s: 0.1s;
  362. --h-s: 0.3s;
  363. --d: calc(var(--h-s) - var(--m-s));
  364. }
  365. .nav--hide {
  366. transition: margin-top var(--m-s) linear var(--h-s), max-height var(--h-s) linear 0s;
  367. margin-top: 0;
  368. max-height: 0;
  369. }
  370. .nav--show {
  371. margin-top: 3.5rem;
  372. max-height: 250px;
  373. transition: margin-top var(--m-s) linear 0s, max-height var(--h-s) linear var(--m-s);
  374. }
  375. .btn--nav-open {
  376. padding: 1rem;
  377. display: block;
  378. }
  379. .flyout-container {
  380. z-index: 401;
  381. position: fixed;
  382. bottom: 0;
  383. display: flex;
  384. justify-content: center;
  385. width: 100vw;
  386. transition: all 0.3s;
  387. pointer-events: none;
  388. }
  389. .flyout-container.flyout-container--hide {
  390. transform: translateY(200px);
  391. }
  392. .flyout-container.flyout-container--show {
  393. transform: translateY(0);
  394. }
  395. .sidebar-container {
  396. z-index: 402;
  397. position: fixed;
  398. top: 0;
  399. right: 0;
  400. display: flex;
  401. justify-content: flex-start;
  402. height: 100vh;
  403. transition: all 0.3s;
  404. pointer-events: none;
  405. }
  406. .sidebar-container.sidebar-container--hide {
  407. transform: translateX(200px);
  408. }
  409. .sidebar-container.sidebar-container--show {
  410. transform: translateX(0);
  411. }
  412. .sidebar {
  413. background: white;
  414. padding: 3rem;
  415. border-radius: 3rem 0 0 3rem;
  416. pointer-events: all;
  417. }
  418. </style>