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.

385 lines
12 KiB

  1. <template>
  2. <div :class="['page-container', { 'nav-is-showing': isPickerVisible }]">
  3. <header>
  4. <div class="header__col">
  5. <template v-if="!isPickerVisible">
  6. <button class="btn btn--primary" @click="showPicker">
  7. tap the map or <svg
  8. xmlns="http://www.w3.org/2000/svg"
  9. class="icon icon-tabler icon-tabler-search"
  10. width="24"
  11. height="24"
  12. viewBox="0 0 24 24"
  13. stroke-width="3"
  14. stroke="#ffffff"
  15. fill="none"
  16. stroke-linecap="round"
  17. stroke-linejoin="round"
  18. >
  19. <path stroke="none" d="M0 0h24v24H0z" fill="none" />
  20. <circle cx="10" cy="10" r="7" />
  21. <line x1="21" y1="21" x2="15" y2="15" />
  22. </svg>
  23. </button>
  24. </template>
  25. <template v-if="isPickerVisible">
  26. <button class="btn btn--primary" @click="showPicker">
  27. hide search <svg
  28. xmlns="http://www.w3.org/2000/svg"
  29. class="icon icon-tabler icon-tabler-arrow-bar-to-up"
  30. width="24"
  31. height="24"
  32. viewBox="0 0 24 24"
  33. stroke-width="3"
  34. stroke="#ffffff"
  35. fill="none"
  36. stroke-linecap="round"
  37. stroke-linejoin="round"
  38. >
  39. <path stroke="none" d="M0 0h24v24H0z" fill="none" />
  40. <line x1="12" y1="10" x2="12" y2="20" />
  41. <line x1="12" y1="10" x2="16" y2="14" />
  42. <line x1="12" y1="10" x2="8" y2="14" />
  43. <line x1="4" y1="4" x2="20" y2="4" />
  44. </svg>
  45. </button>
  46. </template>
  47. </div>
  48. <div class="header__col">
  49. <Logo />
  50. <div v-if="!selectedOrig.iata && !isPickerVisible && isMapReady" class="logo-blurb">
  51. from one of <strong>{{ airports_orig.length }}</strong> <span style="color: white;">local</span> airports
  52. </div>
  53. <div v-if="selectedOrig.iata && !selectedDest.iata && airports_dest.length > 1 && !isPickerVisible && isMapReady" class="logo-blurb">
  54. to one of <strong>{{ airports_dest.length }}</strong> {{ getCompliment() }} destinations
  55. </div>
  56. </div>
  57. <div class="header__col" />
  58. </header>
  59. <!-- <pre style="position: fixed; z-index: 500; right: 0; top: 2rem; font-size: 0.6rem; color: gray;">
  60. {{ selectedOrig }}
  61. {{ selectedDest }}
  62. </pre> -->
  63. <main>
  64. <nav v-if="isMapReady" :class="['nav', { 'nav--show': isPickerVisible },{ 'nav--hide': !isPickerVisible }]">
  65. <AirportPicker
  66. :airports="airports_orig"
  67. :selected-airport="selectedOrig"
  68. leg="start here ►"
  69. @select-airport="makeOrigin"
  70. @deselect-airport="clearOrigin"
  71. />
  72. <AirportPicker
  73. v-show="selectedOrig.iata"
  74. :airports="airports_dest"
  75. :selected-airport="selectedDest"
  76. leg="land here ■"
  77. @select-airport="makeDestination"
  78. @deselect-airport="clearDestination"
  79. />
  80. </nav>
  81. <Map
  82. ref="mapComponent"
  83. :airports-orig="airports_orig"
  84. :airports-dest="airports_dest"
  85. :selected-orig="selectedOrig"
  86. :selected-dest="selectedDest"
  87. @make-origin="makeOrigin"
  88. @make-destination="makeDestination"
  89. @clear-origin="clearOrigin"
  90. @clear-destination="clearDestination"
  91. @map-ready="mapReady"
  92. />
  93. <div :class="['flyout-container',{ 'flyout-container--hide': !selectedOrig.iata || !selectedDest.iata },{ 'flyout-container--show': selectedOrig.iata && selectedDest.iata }]">
  94. <FlightsFlyout
  95. v-if="selectedOrig.iata && selectedDest.iata"
  96. :selected-orig="selectedOrig"
  97. :selected-dest="selectedDest"
  98. />
  99. </div>
  100. </main>
  101. </div>
  102. </template>
  103. <script>
  104. // import AirportPicker from '../components/AirportPicker.vue'
  105. export default {
  106. // components: { AirportPicker },
  107. data () {
  108. return {
  109. isMapReady: false,
  110. color: 'red',
  111. isSidebarVisible: false,
  112. isPickerVisible: false,
  113. airports_orig: [],
  114. airports_dest: [],
  115. airportFetch_filterFormula: 'AND(Is_Origin=1,{IsCurrent-AsOrigin}=\'Yes\')',
  116. airportFetch_fields: [
  117. 'Airport_IATA',
  118. 'Icon_Url',
  119. 'Latitude_Deg',
  120. 'Longitude_Deg',
  121. 'Municipality',
  122. 'Airport_Name',
  123. 'Type',
  124. 'Search_Field'
  125. ],
  126. airportFetch_sort: '&sort[0][field]=Airport_IATA&sort[0][direction]=asc',
  127. selectedOrig: {
  128. iata: '',
  129. lat: '',
  130. long: '',
  131. icon: '',
  132. name: '',
  133. municipality: '',
  134. type: '',
  135. search: ''
  136. },
  137. selectedDest: {
  138. iata: '',
  139. lat: '',
  140. long: '',
  141. icon: '',
  142. name: '',
  143. municipality: '',
  144. type: '',
  145. search: ''
  146. },
  147. emptyAirport: {
  148. iata: '',
  149. lat: '',
  150. long: '',
  151. icon: '',
  152. name: '',
  153. municipality: '',
  154. type: '',
  155. search: ''
  156. }
  157. }
  158. },
  159. async fetch () {
  160. let offset
  161. let data = []
  162. while (true) {
  163. const offsetParam = offset ? `&offset=${offset}` : ''
  164. const res = await fetch(`https://api.airtable.com/v0/appiQwfVZixRgRICe/Airports_IATA?filterByFormula=${this.airportFetch_filterFormula}${this.airportFetch_fields_string}${this.airportFetch_sort}${offsetParam}`, {
  165. method: 'GET',
  166. headers: {
  167. 'Content-Type': 'application/x-www-form-urlencoded',
  168. Authorization: 'Bearer keyJ2ht64ZSN57AG1'
  169. }
  170. })
  171. const json = await res.json()
  172. offset = await json.offset
  173. data = [...data, ...await json.records]
  174. await console.log(data.length)
  175. if (await !offset) { break } // Were done let's stop this thing
  176. }
  177. // console.log('Look ma! I waited!') // Won't run till the while is done
  178. this.airports_orig = data.map((record) => {
  179. return {
  180. iata: record.fields.Airport_IATA,
  181. lat: record.fields.Latitude_Deg,
  182. long: record.fields.Longitude_Deg,
  183. icon: record.fields.Icon_Url,
  184. name: record.fields.Airport_Name,
  185. municipality: record.fields.Municipality,
  186. type: record.fields.Type,
  187. search: record.fields.Search_Field
  188. }
  189. })
  190. },
  191. computed: {
  192. airportFetch_fields_string () {
  193. const vm = this
  194. const array = vm.airportFetch_fields.map((field) => {
  195. return '&fields[]=' + field
  196. })
  197. return array.join('')
  198. }
  199. },
  200. beforeCreate () {
  201. this.isMapReady = false
  202. },
  203. created () {
  204. // console.log(this.$route.path)
  205. if (this.$route && this.$route.params && this.$route.params.o) {
  206. this.fetchSingleAirport(this.$route.params.o, true)
  207. }
  208. if (this.$route && this.$route.params && this.$route.params.d) {
  209. this.fetchSingleAirport(this.$route.params.d, false)
  210. }
  211. },
  212. // watch: {
  213. // history (to, from) {
  214. // console.log(`routed from ${from} to ${to}`)
  215. // }
  216. // },
  217. methods: {
  218. mapReady () {
  219. this.isMapReady = true
  220. },
  221. getCompliment () {
  222. const arr = [
  223. 'beautiful', 'gorgeous', 'breathtaking', 'wild', 'adventurous', 'amazing', 'majestic', 'uncharted', 'unpredictable', 'pristine', 'untamed'
  224. ]
  225. return arr[Math.floor(Math.random() * arr.length)]
  226. },
  227. makeOrigin (airport) {
  228. // console.log(airport)
  229. this.selectedOrig = { ...airport }
  230. this.selectedDest = { ...this.emptyAirport }
  231. history.pushState(
  232. {},
  233. null,
  234. '/go/' + encodeURIComponent(airport.iata)
  235. )
  236. this.fetchDestinations(airport.iata)
  237. },
  238. makeDestination (airport) {
  239. this.selectedDest = { ...airport }
  240. this.isPickerVisible = false
  241. history.pushState(
  242. {},
  243. null,
  244. '/go/' + encodeURIComponent(this.selectedOrig.iata) + '/' + encodeURIComponent(airport.iata)
  245. )
  246. },
  247. clearOrigin () {
  248. this.selectedOrig = { ...this.emptyAirport }
  249. this.selectedDest = { ...this.emptyAirport }
  250. history.pushState(
  251. {},
  252. null,
  253. '/go/'
  254. )
  255. },
  256. clearDestination () {
  257. this.selectedDest = { ...this.emptyAirport }
  258. history.pushState(
  259. {},
  260. null,
  261. '/go/' + encodeURIComponent(this.selectedOrig.iata)
  262. )
  263. },
  264. async fetchDestinations (iata) {
  265. const airportFetchDestfilterFormula = `AND(Is_Destination=1,{IsCurrent-AsDest}="Yes",(FIND("${iata}", Associated_Origin_Search)>0))`
  266. let offset
  267. let data = []
  268. while (true) {
  269. const offsetParam = offset ? `&offset=${offset}` : ''
  270. const res = await fetch(`https://api.airtable.com/v0/appiQwfVZixRgRICe/Airports_IATA?filterByFormula=${airportFetchDestfilterFormula}${this.airportFetch_fields_string}${this.airportFetch_sort}${offsetParam}`, {
  271. method: 'GET',
  272. headers: {
  273. 'Content-Type': 'application/x-www-form-urlencoded',
  274. Authorization: 'Bearer keyJ2ht64ZSN57AG1'
  275. }
  276. })
  277. const json = await res.json()
  278. offset = await json.offset
  279. data = [...data, ...await json.records]
  280. // await console.log(data.length)
  281. if (await !offset) { break } // Were done let's stop this thing
  282. }
  283. // console.log('Look ma! I waited!') // Won't run till the while is done
  284. this.airports_dest = data.map((record) => {
  285. return {
  286. iata: record.fields.Airport_IATA,
  287. lat: record.fields.Latitude_Deg,
  288. long: record.fields.Longitude_Deg,
  289. icon: record.fields.Icon_Url,
  290. name: record.fields.Airport_Name,
  291. municipality: record.fields.Municipality,
  292. type: record.fields.Type,
  293. search: record.fields.Search_Field
  294. }
  295. })
  296. },
  297. async fetchSingleAirport (iata, isOrig) {
  298. const airportFetchFilterFormula =
  299. isOrig
  300. ? `AND(Is_Origin=1,{IsCurrent-AsOrigin}="Yes",Airport_IATA="${iata}")`
  301. : `AND(Is_Destination=1,{IsCurrent-AsDest}="Yes",Airport_IATA="${iata}")`
  302. const response = await fetch(`https://api.airtable.com/v0/appiQwfVZixRgRICe/Airports_IATA?filterByFormula=${airportFetchFilterFormula}${this.airportFetch_fields_string}`, {
  303. method: 'GET',
  304. headers: {
  305. 'Content-Type': 'application/x-www-form-urlencoded',
  306. Authorization: 'Bearer keyJ2ht64ZSN57AG1'
  307. }
  308. })
  309. const mapData = await response.json()
  310. const thisAirport = {
  311. iata: mapData.records[0].fields.Airport_IATA,
  312. lat: mapData.records[0].fields.Latitude_Deg,
  313. long: mapData.records[0].fields.Longitude_Deg,
  314. icon: mapData.records[0].fields.Icon_Url,
  315. name: mapData.records[0].fields.Airport_Name,
  316. municipality: mapData.records[0].fields.Municipality,
  317. type: mapData.records[0].fields.Type,
  318. search: mapData.records[0].fields.Search_Field
  319. }
  320. switch (isOrig) {
  321. case true:
  322. this.selectedOrig = { ...thisAirport }
  323. this.fetchDestinations(thisAirport.iata)
  324. break
  325. case false:
  326. this.selectedDest = { ...thisAirport }
  327. break
  328. default:
  329. break
  330. }
  331. },
  332. showPicker () {
  333. const vm = this
  334. vm.isPickerVisible = !vm.isPickerVisible
  335. setTimeout(function () {
  336. vm.$refs.mapComponent.resize()
  337. }, 500)
  338. },
  339. showSidebar () {
  340. this.isSidebarVisible = !this.isSidebarVisible
  341. }
  342. }
  343. }
  344. </script>
  345. <style scoped>
  346. main {
  347. background: black;
  348. }
  349. .logo-blurb {
  350. position: absolute;
  351. left: clamp(47% + 1rem, 51% - 2rem, 47%);
  352. font-size: clamp(10px, 1rem, 18px);
  353. color: white;
  354. top: clamp(20px + 0.25rem, 4rem + 0.25rem, 50px + 0.25rem);
  355. white-space: nowrap;
  356. }
  357. </style>