feature: on session
This commit is contained in:
parent
0271d06e49
commit
ce697dc51f
69
src/components/InSessionNow.vue
Normal file
69
src/components/InSessionNow.vue
Normal file
@ -0,0 +1,69 @@
|
||||
<template>
|
||||
<section class="">
|
||||
<div class="solari-container">
|
||||
<SolariBoard :headers="headers" :loading="isLoading" :config="colsConfig" :data="list" :size="18" />
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
.solari-container {
|
||||
width: 485px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
import { getFlightplansNow } from '../data/http/listRequest.js';
|
||||
import 'vue-loading-overlay/dist/css/index.css';
|
||||
import SolariBoard from '../components/SolariBoard.vue';
|
||||
import { hoursFromSeconds } from '../helpers/time-helpers.js';
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
isLoading: true,
|
||||
list: [],
|
||||
board: undefined,
|
||||
test: [['LTS016T', 'SKSM-SKBO', '']],
|
||||
headers: ['Callsign', 'Ruta', 'E.T.A'],
|
||||
colsConfig: [
|
||||
{
|
||||
align: 'left',
|
||||
size: 8
|
||||
},
|
||||
{
|
||||
align: 'right',
|
||||
size: 9
|
||||
},
|
||||
{
|
||||
align: 'right',
|
||||
size: 5
|
||||
},
|
||||
],
|
||||
}
|
||||
},
|
||||
async mounted() {
|
||||
await this.loadData();
|
||||
},
|
||||
methods: {
|
||||
async loadData() {
|
||||
this.isLoading = true;
|
||||
let data = await getFlightplansNow();
|
||||
this.isLoading = false;
|
||||
const rows = data.reduce((acc, d) => {
|
||||
const eta = hoursFromSeconds(d.eta);
|
||||
const row = [ d.callsign, `${d.departure.icao}-${d.arrival.icao}`, `${eta.h}:${eta.m}`];
|
||||
acc.push(row);
|
||||
return acc;
|
||||
}, []);
|
||||
|
||||
// this.list = rows;
|
||||
this.list = this.test;
|
||||
}
|
||||
},
|
||||
components: {
|
||||
SolariBoard
|
||||
}
|
||||
}
|
||||
</script>
|
191
src/components/SolariBoard.vue
Normal file
191
src/components/SolariBoard.vue
Normal file
@ -0,0 +1,191 @@
|
||||
<template>
|
||||
<div class="board">
|
||||
<h1 v-if="title">{{ title }}</h1>
|
||||
<div class="board-content" :style="getGridStyle">
|
||||
|
||||
<div class="board-headers" :class="{'no-headers': !(headers && headers.length) }" v-for="(header, i) in headers" :key="i">
|
||||
<h2>{{ header }}</h2>
|
||||
</div>
|
||||
<div class="board-item" v-for="(j) in this.total" :key="j" :class="getItemClasses(j)">
|
||||
<SolariBoardRow :ref="`line_${j - 1}`"
|
||||
:textToShow="getValue(j - 1)"
|
||||
:loops="getConfigValue(j - 1, 'loop')"
|
||||
:size="getConfigValue(j - 1, 'size')"
|
||||
:delay="getConfigValue(j - 1, 'delay')"
|
||||
:align="getConfigValue(j - 1, 'align')"></SolariBoardRow>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import SolariBoardRow from './SolariBoardRow.vue';
|
||||
import _isNil from 'lodash/isNil';
|
||||
import _fill from 'lodash/fill';
|
||||
|
||||
export default {
|
||||
props: {
|
||||
title: String,
|
||||
data: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
},
|
||||
delay: {
|
||||
type: Number,
|
||||
default: () => 200
|
||||
},
|
||||
size: {
|
||||
type: Number,
|
||||
default: () => 25
|
||||
},
|
||||
loops: {
|
||||
type: Number,
|
||||
default: () => 0
|
||||
},
|
||||
align: {
|
||||
type: String,
|
||||
default: () => 'left'
|
||||
},
|
||||
loading: Boolean,
|
||||
config: Object,
|
||||
headers: Array,
|
||||
rows: {
|
||||
type: Number,
|
||||
default: 4
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
values: []
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
|
||||
if (Array.isArray(this.data)) {
|
||||
this.setData(this.data);
|
||||
}
|
||||
|
||||
},
|
||||
computed: {
|
||||
getGridStyle() {
|
||||
let size;
|
||||
if (this.headers) {
|
||||
size = this.headers && this.headers.length;
|
||||
} else {
|
||||
size = this.data && this.data[0] ? this.data[0].length : 1;
|
||||
}
|
||||
return `grid-template-columns: repeat(${size}, auto)`;
|
||||
},
|
||||
cols() {
|
||||
if (Array.isArray(this.config)) {
|
||||
return this.config.length;
|
||||
}
|
||||
return 0;
|
||||
},
|
||||
total() {
|
||||
return this.cols * this.rows;
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
setData(data) {
|
||||
const plainData = (data || []).reduce((acc, row) => {
|
||||
console.log('row :>> ', row);
|
||||
acc = acc.concat(row);
|
||||
return acc;
|
||||
}, []);
|
||||
console.log('plainData :>> ', plainData);
|
||||
console.log('this.total :>> ', this.total);
|
||||
this.values = _fill(Array(this.total), ' ').map((d, i) => plainData[i] || d);
|
||||
console.log('this.values :>> ', this.values);
|
||||
|
||||
},
|
||||
mapRow(r, i) {
|
||||
r.loops = (_isNil(r.loops)) ? this.loops : r.loops;
|
||||
r.delay = ((_isNil(r.delay)) ? this.delay : r.delay) * i;
|
||||
r.size = (_isNil(r.size)) ? this.size : r.size;
|
||||
r.align = (_isNil(r.align)) ? this.align : r.align;
|
||||
return r;
|
||||
},
|
||||
getConfigValue(index, key) {
|
||||
return this.config[(index % this.cols)][key];
|
||||
},
|
||||
getValue(index) {
|
||||
return this.values[index];
|
||||
},
|
||||
getItemClasses(index) {
|
||||
const classes = [];
|
||||
if (index % this.cols === 0) {
|
||||
classes.push('last-col');
|
||||
}
|
||||
if (index > ((this.rows - 1) * this.cols)) {
|
||||
classes.push('last-row');
|
||||
}
|
||||
return classes.join(' ');
|
||||
}
|
||||
},
|
||||
components: {
|
||||
SolariBoardRow,
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.board {
|
||||
min-height: 100px;
|
||||
margin: 0 auto;
|
||||
text-align: center;
|
||||
}
|
||||
.board-content {
|
||||
display: inline-grid;
|
||||
width: 100%
|
||||
}
|
||||
.board-headers{
|
||||
margin-bottom: 4px;
|
||||
&.no-headers {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
.board-item {
|
||||
display: inline-block;
|
||||
height: 2rem;
|
||||
line-height: 1;
|
||||
margin-bottom: 2px;
|
||||
margin-right: 8px;
|
||||
color: #222222;
|
||||
|
||||
&.last-col {
|
||||
margin-right: 0;
|
||||
}
|
||||
&.last-row {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
h1 {
|
||||
margin: 0;
|
||||
text-align: center;
|
||||
color: #eff1ed;
|
||||
text-transform: uppercase;
|
||||
font-size: 80%;
|
||||
font-weight: 400;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
h2 {
|
||||
margin: 0;
|
||||
text-align: center;
|
||||
color: #eff1ed;
|
||||
text-transform: uppercase;
|
||||
font-size: 60%;
|
||||
font-weight: 600;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.board {
|
||||
display: inline-block;
|
||||
background-color: #333;
|
||||
padding: 8px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
</style>
|
156
src/components/SolariBoardRow.vue
Normal file
156
src/components/SolariBoardRow.vue
Normal file
@ -0,0 +1,156 @@
|
||||
<template>
|
||||
<div class="board-line">
|
||||
<div class="board-letter" :ref="'letter' + $index" :class="{'animating': isAnimating[$index]}" v-for="(char, $index) in charsBack" :key="$index">{{ char }}</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import url('https://fonts.googleapis.com/css2?family=Inconsolata&display=swap');
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.board-line {
|
||||
display: inline-flex;
|
||||
font-family: 'Inconsolata', monospace;
|
||||
}
|
||||
.board-letter {
|
||||
display: inline-block;
|
||||
padding: 2px;
|
||||
margin-right: 2px;
|
||||
border-radius: 3px;
|
||||
border: 1px solid #222;
|
||||
background-color: #222;
|
||||
// color: #EEB868;
|
||||
// color: #717744;
|
||||
color: #bcbd8b;
|
||||
font-size: 20px;
|
||||
height: 26px;
|
||||
width: 16px;
|
||||
position: relative;
|
||||
line-height: 1;
|
||||
|
||||
&::after {
|
||||
content:' ';
|
||||
display: inline-block;
|
||||
border-bottom: 1px solid #ededed;
|
||||
opacity: 0.2;
|
||||
width: 100%;
|
||||
height: 1px;
|
||||
top: 49%;
|
||||
left: 0;
|
||||
position: absolute;
|
||||
box-shadow: 6px 4px 8px;
|
||||
}
|
||||
}
|
||||
|
||||
.board-letter.animating {
|
||||
animation: squeeze 0.075s ease-in-out infinite;
|
||||
}
|
||||
@keyframes squeeze {
|
||||
50% {
|
||||
transform: scaleY(0);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
import _isInteger from 'lodash/isInteger';
|
||||
import _delay from 'lodash/delay';
|
||||
|
||||
export default {
|
||||
props: {
|
||||
textToShow: {
|
||||
type: String,
|
||||
default: () => ''
|
||||
},
|
||||
delay: {
|
||||
type: Number,
|
||||
default: () => 0
|
||||
},
|
||||
size: {
|
||||
type: Number,
|
||||
},
|
||||
loops: {
|
||||
type: Number,
|
||||
default: () => 0
|
||||
},
|
||||
align: {
|
||||
type: String,
|
||||
default: 'left'
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
charsAll: ' ABCDEFGHIJKLMNÑOPQRSTUVWXYZ0123456789-:.>*',
|
||||
charsBack: [],
|
||||
isAnimating: [],
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
chars() {
|
||||
const text = this.getText(this.textToShow);
|
||||
const size = _isInteger(this.size) ? this.size : text.length;
|
||||
const chars = Array.apply(null, Array(size)).map(() => ' ');
|
||||
const offset = (this.align === 'left') ? 0 : chars.length - text.length;
|
||||
|
||||
for (let index = 0; index < text.length; index++) {
|
||||
chars[index + offset] = text.charAt(index);
|
||||
}
|
||||
return chars;
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
chars: {
|
||||
handler(newValue) {
|
||||
this.charsBack = Array.apply(null, Array(newValue.length)).map(() => ' ');
|
||||
if (_isInteger(this.delay) && this.delay > 0) {
|
||||
_delay(this.startAnimation, this.delay);
|
||||
} else {
|
||||
this.startAnimation();
|
||||
}
|
||||
},
|
||||
immediate: true
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
getNextIndex(index) {
|
||||
return (index >= this.charsAll.length - 1) ? 0 : index + 1;
|
||||
},
|
||||
getLetterIndex(letter) {
|
||||
return this.charsAll.indexOf(letter);
|
||||
},
|
||||
getText(text) {
|
||||
return (text.length > this.size) ? text.substring(0, this.size) : text;
|
||||
},
|
||||
async startAnimation() {
|
||||
this.charsBack = this.charsBack.map(() => ' ');
|
||||
this.isAnimating = [];
|
||||
for (let index = 0; index < this.chars.length; index++) {
|
||||
const char = this.chars[index];
|
||||
this.animateLetter(index, char);
|
||||
await new Promise((resolve) => setTimeout(resolve, 100))
|
||||
}
|
||||
},
|
||||
animateLetter(index, letter) {
|
||||
let showIndex = 0;
|
||||
this.charsBack[index] = this.charsAll[showIndex];
|
||||
this.isAnimating[index] = true;
|
||||
let loop = 0;
|
||||
const letterIndex = this.getLetterIndex(letter);
|
||||
|
||||
const interval = setInterval(() => {
|
||||
showIndex = this.getNextIndex(showIndex);
|
||||
this.charsBack[index] = this.charsAll.charAt(showIndex);
|
||||
if (showIndex === 0) {
|
||||
loop++;
|
||||
}
|
||||
if (loop >= this.loops && letterIndex === showIndex) {
|
||||
clearInterval(interval);
|
||||
this.isAnimating[index] = false;
|
||||
}
|
||||
}, 50);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
75
src/components/TableAcars.vue
Normal file
75
src/components/TableAcars.vue
Normal file
@ -0,0 +1,75 @@
|
||||
<template>
|
||||
<loading v-model:active="isLoading"
|
||||
:can-cancel="false"
|
||||
:is-full-page="fullPage"/>
|
||||
<section class="section">
|
||||
<h1 class="title">Pilotos activos con mas de 200 horas en ACARS</h1>
|
||||
<table class="table is-striped is-hoverable is-fullwidth">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="has-text-centered">VID</th>
|
||||
<th class="">Nombre</th>
|
||||
<th class="has-text-centered">Total Vuelos</th>
|
||||
<th class="has-text-centered">Horas totales</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="item in whitelist" :key="item.vid">
|
||||
<td class="has-text-centered">
|
||||
{{ item.vid }}
|
||||
</td>
|
||||
<td>
|
||||
{{ item.name }}
|
||||
</td>
|
||||
<td class="has-text-centered">
|
||||
{{ item.flights }}
|
||||
</td>
|
||||
<td class="has-text-centered">
|
||||
<FormatTime :value="item.flightTime" />
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<style lang="sass">
|
||||
|
||||
</style>
|
||||
|
||||
<script>
|
||||
import moment from 'moment';
|
||||
import { getWhitelist } from '../data/http/listRequest.js';
|
||||
import FormatTime from './FormatTime.vue';
|
||||
import Loading from 'vue-loading-overlay';
|
||||
import 'vue-loading-overlay/dist/css/index.css';
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
isLoading: true,
|
||||
whitelist: [],
|
||||
}
|
||||
},
|
||||
async mounted() {
|
||||
await this.loadData();
|
||||
},
|
||||
methods: {
|
||||
async loadData() {
|
||||
this.isLoading = true;
|
||||
this.whitelist = await getWhitelist();
|
||||
this.isLoading = false;
|
||||
},
|
||||
dateTime(value) {
|
||||
return moment(value).fromNow(); //.format('YYYY-MM-DD HH:mm');
|
||||
},
|
||||
flagIcon(code) {
|
||||
return new URL(`/public/flags/${code}.png`, import.meta.url).href
|
||||
}
|
||||
},
|
||||
components: {
|
||||
FormatTime,
|
||||
Loading,
|
||||
}
|
||||
}
|
||||
</script>
|
135
src/components/TableIvao.vue
Normal file
135
src/components/TableIvao.vue
Normal file
@ -0,0 +1,135 @@
|
||||
<template>
|
||||
<!-- <loading v-model:active="isLoading"
|
||||
:can-cancel="false"
|
||||
:is-full-page="fullPage"/> -->
|
||||
<section class="section">
|
||||
<h1 class="title">Status mensual IVAO</h1>
|
||||
<table class="table is-striped is-hoverable is-fullwidth" v-if="!$isMobile()">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="has-text-centered">VID</th>
|
||||
<th class="">Nombre</th>
|
||||
<th class="has-text-centered">Horas en session</th>
|
||||
<th class="has-text-centered">Horas en el aire</th>
|
||||
<th class="has-text-centered">Último vuelo</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="item in list" :key="item.vid">
|
||||
<td class="has-text-centered">
|
||||
{{ item.vid }}
|
||||
</td>
|
||||
<td>
|
||||
{{ item.name }} ({{ item.division }} <img v-if="item.division" :src="flagIcon(item.division.toLowerCase())" :alt="item.division" :title="item.division" @error="fallbackIcon" />)
|
||||
</td>
|
||||
<td class="has-text-centered">
|
||||
<FormatTime :value="item.sessionsTime / 60" />
|
||||
</td>
|
||||
<td class="has-text-centered">
|
||||
<FormatTime :value="item.time / 60" />
|
||||
</td>
|
||||
<td class="">
|
||||
<div class="is-size-6">
|
||||
{{ item.lastFlight.departureId }} - {{ item.lastFlight.arrivalId }} <span class="is-size-7 ml-1 has-text-grey-light">{{ dateTime(item.lastFlightDate) }}</span>
|
||||
</div>
|
||||
<div class="field is-grouped is-grouped-multiline">
|
||||
<div class="control">
|
||||
<div class="tags has-addons">
|
||||
<span class="tag is-dark">Callsign</span>
|
||||
<span class="tag">{{ item.lastCallsign }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="control">
|
||||
<div class="tags has-addons">
|
||||
<span class="tag is-dark">Aircraft</span>
|
||||
<span class="tag">{{ item.lastFlight.aircraftId }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<div v-else>
|
||||
<div class="box is-shadowless mb-1" v-for="item in list" :key="item.vid">
|
||||
<p class="has-text-weight-bold is-size-7 is-uppercase">{{ item.name }} ({{ item.division }} <img v-if="item.division" :src="flagIcon(item.division.toLowerCase())" :alt="item.division" :title="item.division" />)</p>
|
||||
<p class="has-text-weight-light is-size-7">VID: {{ item.vid }}</p>
|
||||
<p class="has-text-weight-light is-size-7">Horas en el aire : <FormatTime :value="item.time / 60" /></p>
|
||||
<p class="has-text-weight-light is-size-7">Horas en session : <FormatTime :value="item.sessionsTime / 60" /></p>
|
||||
|
||||
<h5 class="mt-2 has-text-weight-bold is-size-7 is-uppercase">Último vuelo</h5>
|
||||
<div class="is-size-7">
|
||||
{{ item.lastFlight.departureId }} - {{ item.lastFlight.arrivalId }} <span class="is-size-7 ml-1 has-text-grey-light">{{ dateTime(item.lastFlightDate) }}</span>
|
||||
</div>
|
||||
<div class="mt-1 field is-grouped is-grouped-multiline">
|
||||
<div class="control">
|
||||
<div class="tags has-addons">
|
||||
<span class="tag is-dark">Callsign</span>
|
||||
<span class="tag">{{ item.lastCallsign }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="control">
|
||||
<div class="tags has-addons">
|
||||
<span class="tag is-dark">Aircraft</span>
|
||||
<span class="tag">{{ item.lastFlight.aircraftId }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
.box {
|
||||
border: 1px solid hsl(0, 0%, 86%);
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
import moment from 'moment';
|
||||
import { getMonthlyList } from '../data/http/listRequest.js';
|
||||
import FormatTime from './FormatTime.vue';
|
||||
import Loading from 'vue-loading-overlay';
|
||||
import 'vue-loading-overlay/dist/css/index.css';
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
isLoading: true,
|
||||
list: [],
|
||||
}
|
||||
},
|
||||
async mounted() {
|
||||
await this.loadData();
|
||||
},
|
||||
methods: {
|
||||
async loadData() {
|
||||
this.isLoading = true;
|
||||
let data = await getMonthlyList();
|
||||
this.list = data.sort((a, b) => b.time - a.time).map(d => {
|
||||
const val = d.time / 60;
|
||||
const hours = Number.isNaN(val) ? 0 : Math.floor(val / 60);
|
||||
const minutes = String(Number.isNaN(val) ? 0 :Math.round((val % 60)) + 100).substring(1);
|
||||
d._formattedTime = `${hours}:${minutes}`;
|
||||
this.isLoading = false;
|
||||
return d;
|
||||
});
|
||||
},
|
||||
dateTime(value) {
|
||||
return moment(value).fromNow(); //.format('YYYY-MM-DD HH:mm');
|
||||
},
|
||||
flagIcon(code) {
|
||||
return new URL(`/src/assets/images/flags/${code}.png`, import.meta.url).href
|
||||
},
|
||||
fallbackIcon(e) {
|
||||
e.target.style.display = 'none';
|
||||
|
||||
}
|
||||
},
|
||||
components: {
|
||||
FormatTime,
|
||||
}
|
||||
}
|
||||
</script>
|
@ -11,4 +11,16 @@ export async function getWhitelist() {
|
||||
import.meta.env;
|
||||
const response = await request(`${VITE_API_BASE}${VITE_API_PATH_WHITELIST}`);
|
||||
return response;
|
||||
}
|
||||
export async function getInSessionNow() {
|
||||
const { VITE_API_BASE, VITE_API_PATH_NOW_SESSIONS } =
|
||||
import.meta.env;
|
||||
const response = await request(`${VITE_API_BASE}${VITE_API_PATH_NOW_SESSIONS}`);
|
||||
return response;
|
||||
}
|
||||
export async function getFlightplansNow() {
|
||||
const { VITE_API_BASE, VITE_API_PATH_NOW_FLIGHTPLANS } =
|
||||
import.meta.env;
|
||||
const response = await request(`${VITE_API_BASE}${VITE_API_PATH_NOW_FLIGHTPLANS}`);
|
||||
return response;
|
||||
}
|
244
src/helpers/fakeData.js
Normal file
244
src/helpers/fakeData.js
Normal file
@ -0,0 +1,244 @@
|
||||
export const onSessionFakeData = [{
|
||||
'time': 34031,
|
||||
'id': 51004653,
|
||||
'callsign': 'GIA88',
|
||||
'userId': 446688,
|
||||
'connectionType': 'PILOT',
|
||||
'serverId': 'WS',
|
||||
'createdAt': '2023-01-13T01:35:25.000Z',
|
||||
'pilotSession': {
|
||||
'simulatorId': 'P3D',
|
||||
'textureId': 355
|
||||
},
|
||||
'lastTrack': {
|
||||
'altitude': 36202,
|
||||
'altitudeDifference': -200,
|
||||
'arrivalDistance': 2307.286865,
|
||||
'departureDistance': 4086.708252,
|
||||
'groundSpeed': 439,
|
||||
'heading': 287,
|
||||
'latitude': 28.503233,
|
||||
'longitude': 45.723396,
|
||||
'onGround': false,
|
||||
'state': 'En Route',
|
||||
'timestamp': '2023-01-13T11:02:27.000Z',
|
||||
'transponder': 2000,
|
||||
'transponderMode': 'N',
|
||||
'time': 34023
|
||||
},
|
||||
'flightPlan': {
|
||||
'id': 54507818,
|
||||
'arrivalId': 'EHAM',
|
||||
'departureId': 'WIII',
|
||||
'aircraftId': 'B77W',
|
||||
'aircraft': {
|
||||
'icaoCode': 'B77W',
|
||||
'model': '777-300ER',
|
||||
'wakeTurbulence': 'H',
|
||||
'isMilitary': false,
|
||||
'description': 'LandPlane'
|
||||
},
|
||||
'departure': {
|
||||
'icao': 'WIII',
|
||||
'iata': 'CGK',
|
||||
'name': 'Soekarno Hatta',
|
||||
'countryId': 'ID',
|
||||
'longitude': 106.6611111111,
|
||||
'latitude': -6.1236111111,
|
||||
'military': false,
|
||||
'city': 'Jakarta'
|
||||
},
|
||||
'arrival': {
|
||||
'icao': 'EHAM',
|
||||
'iata': 'AMS',
|
||||
'name': 'Schiphol',
|
||||
'countryId': 'NL',
|
||||
'longitude': 4.7641666667,
|
||||
'latitude': 52.3080555556,
|
||||
'military': false,
|
||||
'city': 'Amsterdam'
|
||||
}
|
||||
},
|
||||
'softwareType': {
|
||||
'id': 'altitude/win',
|
||||
'name': 'Altitude/win'
|
||||
},
|
||||
'user': {
|
||||
'id': 446688,
|
||||
'divisionId': 'ID',
|
||||
'firstName': null,
|
||||
'lastName': null,
|
||||
'rating': {
|
||||
'pilotRatingId': 4,
|
||||
'pilotRating': {
|
||||
'id': 4,
|
||||
'name': 'Advanced Flight Student',
|
||||
'shortName': 'FS3',
|
||||
'description': 'Rating requires at least 25 hours online as a pilot<br>and a successful theoretical IvAp test<br>'
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
'time': 33149,
|
||||
'id': 51004722,
|
||||
'callsign': 'DLH122',
|
||||
'userId': 671530,
|
||||
'connectionType': 'PILOT',
|
||||
'serverId': 'WS',
|
||||
'createdAt': '2023-01-13T01:50:07.000Z',
|
||||
'pilotSession': {
|
||||
'simulatorId': 'X-Plane11',
|
||||
'textureId': 1827
|
||||
},
|
||||
'lastTrack': {
|
||||
'altitude': 34635,
|
||||
'altitudeDifference': 329,
|
||||
'arrivalDistance': 1223.80835,
|
||||
'departureDistance': 3178.239502,
|
||||
'groundSpeed': 463,
|
||||
'heading': 128,
|
||||
'latitude': 61.01244,
|
||||
'longitude': -22.375532,
|
||||
'onGround': false,
|
||||
'state': 'En Route',
|
||||
'timestamp': '2023-01-13T11:02:32.000Z',
|
||||
'transponder': 2000,
|
||||
'transponderMode': 'N',
|
||||
'time': 33146
|
||||
},
|
||||
'flightPlan': {
|
||||
'id': 54507919,
|
||||
'arrivalId': 'EDDF',
|
||||
'departureId': 'CYVR',
|
||||
'aircraftId': 'A359',
|
||||
'aircraft': {
|
||||
'icaoCode': 'A359',
|
||||
'model': 'A350-900 XWB',
|
||||
'wakeTurbulence': 'H',
|
||||
'isMilitary': false,
|
||||
'description': 'LandPlane'
|
||||
},
|
||||
'departure': {
|
||||
'icao': 'CYVR',
|
||||
'iata': 'YVR',
|
||||
'name': 'Vancouver',
|
||||
'countryId': 'CA',
|
||||
'longitude': -123.1839694444,
|
||||
'latitude': 49.1946972222,
|
||||
'military': false,
|
||||
'city': 'Vancouver'
|
||||
},
|
||||
'arrival': {
|
||||
'icao': 'EDDF',
|
||||
'iata': 'FRA',
|
||||
'name': 'Frankfurt',
|
||||
'countryId': 'DE',
|
||||
'longitude': 8.5704555556,
|
||||
'latitude': 50.0333055556,
|
||||
'military': false,
|
||||
'city': 'Frankfurt/Main'
|
||||
}
|
||||
},
|
||||
'softwareType': {
|
||||
'id': 'altitude/win',
|
||||
'name': 'Altitude/win'
|
||||
},
|
||||
'user': {
|
||||
'id': 671530,
|
||||
'divisionId': 'BR',
|
||||
'firstName': null,
|
||||
'lastName': null,
|
||||
'rating': {
|
||||
'pilotRatingId': 4,
|
||||
'pilotRating': {
|
||||
'id': 4,
|
||||
'name': 'Advanced Flight Student',
|
||||
'shortName': 'FS3',
|
||||
'description': 'Rating requires at least 25 hours online as a pilot<br>and a successful theoretical IvAp test<br>'
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
'time': 30300,
|
||||
'id': 51004897,
|
||||
'callsign': 'ABS9912',
|
||||
'userId': 697657,
|
||||
'connectionType': 'PILOT',
|
||||
'serverId': 'WS',
|
||||
'createdAt': '2023-01-13T02:37:36.000Z',
|
||||
'pilotSession': {
|
||||
'simulatorId': 'X-Plane11',
|
||||
'textureId': 67
|
||||
},
|
||||
'lastTrack': {
|
||||
'altitude': 34959,
|
||||
'altitudeDifference': 33,
|
||||
'arrivalDistance': 1464.623535,
|
||||
'departureDistance': 3891.043701,
|
||||
'groundSpeed': 495,
|
||||
'heading': 134,
|
||||
'latitude': -7.272876,
|
||||
'longitude': -65.504532,
|
||||
'onGround': false,
|
||||
'state': 'En Route',
|
||||
'timestamp': '2023-01-13T11:02:27.000Z',
|
||||
'transponder': 7320,
|
||||
'transponderMode': 'N',
|
||||
'time': 30292
|
||||
},
|
||||
'flightPlan': {
|
||||
'id': 54508190,
|
||||
'arrivalId': 'SBGR',
|
||||
'departureId': 'KLAX',
|
||||
'aircraftId': 'A346',
|
||||
'aircraft': {
|
||||
'icaoCode': 'A346',
|
||||
'model': 'A340-600',
|
||||
'wakeTurbulence': 'H',
|
||||
'isMilitary': false,
|
||||
'description': 'LandPlane'
|
||||
},
|
||||
'departure': {
|
||||
'icao': 'KLAX',
|
||||
'iata': 'LAX',
|
||||
'name': 'Los Angeles International',
|
||||
'countryId': 'US',
|
||||
'longitude': -118.40805,
|
||||
'latitude': 33.9424972222,
|
||||
'military': false,
|
||||
'city': 'Los Angeles'
|
||||
},
|
||||
'arrival': {
|
||||
'icao': 'SBGR',
|
||||
'iata': 'GRU',
|
||||
'name': 'São Paulo/Guarulhos / Governador André Franco Montoro Intl',
|
||||
'countryId': 'BR',
|
||||
'longitude': -46.4730555556,
|
||||
'latitude': -23.4355555556,
|
||||
'military': true,
|
||||
'city': 'Guarulhos'
|
||||
}
|
||||
},
|
||||
'softwareType': {
|
||||
'id': 'altitude/win',
|
||||
'name': 'Altitude/win'
|
||||
},
|
||||
'user': {
|
||||
'id': 697657,
|
||||
'divisionId': 'BR',
|
||||
'firstName': null,
|
||||
'lastName': null,
|
||||
'rating': {
|
||||
'pilotRatingId': 4,
|
||||
'pilotRating': {
|
||||
'id': 4,
|
||||
'name': 'Advanced Flight Student',
|
||||
'shortName': 'FS3',
|
||||
'description': 'Rating requires at least 25 hours online as a pilot<br>and a successful theoretical IvAp test<br>'
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
];
|
12
src/helpers/time-helpers.js
Normal file
12
src/helpers/time-helpers.js
Normal file
@ -0,0 +1,12 @@
|
||||
function format(num) {
|
||||
return String(100 + num).substring(1);
|
||||
}
|
||||
|
||||
export function hoursFromSeconds(seconds) {
|
||||
const h = format(Math.floor(seconds / 3600));
|
||||
const m = format(Math.round(seconds % 3600 * 60));
|
||||
return {
|
||||
h,
|
||||
m
|
||||
};
|
||||
}
|
@ -1,23 +1,29 @@
|
||||
import { createRouter, createWebHistory } from "vue-router";
|
||||
import HomeView from "../views/HomeView.vue";
|
||||
import { createRouter, createWebHistory } from 'vue-router';
|
||||
import TableAcars from '../components/TableAcars.vue';
|
||||
import IvaoView from '../views/IvaoView.vue';
|
||||
|
||||
const router = createRouter({
|
||||
history: createWebHistory(import.meta.env.BASE_URL),
|
||||
routes: [
|
||||
{
|
||||
path: "/",
|
||||
name: "home",
|
||||
component: HomeView,
|
||||
history: createWebHistory(
|
||||
import.meta.env.BASE_URL),
|
||||
routes: [{
|
||||
path: '/',
|
||||
name: 'home',
|
||||
component: IvaoView,
|
||||
},
|
||||
{
|
||||
path: "/about",
|
||||
name: "about",
|
||||
// route level code-splitting
|
||||
// this generates a separate chunk (About.[hash].js) for this route
|
||||
// which is lazy-loaded when the route is visited.
|
||||
component: () => import("../views/AboutView.vue"),
|
||||
path: '/acars',
|
||||
name: 'acars',
|
||||
component: TableAcars,
|
||||
},
|
||||
// {
|
||||
// path: '/about',
|
||||
// name: 'about',
|
||||
// // route level code-splitting
|
||||
// // this generates a separate chunk (About.[hash].js) for this route
|
||||
// // which is lazy-loaded when the route is visited.
|
||||
// component: () => import('../views/AboutView.vue'),
|
||||
// },
|
||||
],
|
||||
});
|
||||
|
||||
export default router;
|
||||
export default router;
|
11
src/views/IvaoView.vue
Normal file
11
src/views/IvaoView.vue
Normal file
@ -0,0 +1,11 @@
|
||||
<script setup>
|
||||
import TableIvao from '../components/TableIvao.vue';
|
||||
import InSessionNow from '../components/InSessionNow.vue';
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<main>
|
||||
<InSessionNow v-if="!$isMobile()" />
|
||||
<TableIvao />
|
||||
</main>
|
||||
</template>
|
Loading…
x
Reference in New Issue
Block a user