Add trip display to departures
This commit is contained in:
parent
1bdea1926d
commit
c00e038fba
@ -1,6 +1,6 @@
|
|||||||
<div class="departures" v-responsive>
|
<div class="departures" v-responsive>
|
||||||
<ul class="departures__list list-underlined">
|
<ul class="departures__list list-underlined">
|
||||||
<departure :departure="departure" :key="departure.id" v-for="departure in departures"/>
|
<departure :departure="departure" :key="departure.key" v-for="departure in departures"/>
|
||||||
</ul>
|
</ul>
|
||||||
<div class="alert alert-info" v-if="stops.length === 0">
|
<div class="alert alert-info" v-if="stops.length === 0">
|
||||||
<fa :icon="['fal', 'info-circle']"/>
|
<fa :icon="['fal', 'info-circle']"/>
|
||||||
|
@ -34,5 +34,18 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<fold :visible="showTrip">
|
<fold :visible="showTrip">
|
||||||
|
<div v-if="trip != null" class="trip" :class="[ `trip--${departure.line.type}` ]">
|
||||||
|
<ol class="trip__stops">
|
||||||
|
<li v-for="stop in trip.schedule" class="trip-stop">
|
||||||
|
<div class="trip-stop__marker"/>
|
||||||
|
<div class="trip-stop__description">
|
||||||
|
<stop :stop="stop.stop"/>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
</ol>
|
||||||
|
</div>
|
||||||
|
<div v-else class="text-center">
|
||||||
|
<fa icon="spinner-third" pulse></fa>
|
||||||
|
</div>
|
||||||
</fold>
|
</fold>
|
||||||
</li>
|
</li>
|
||||||
|
@ -6,7 +6,7 @@
|
|||||||
|
|
||||||
.stop__name {
|
.stop__name {
|
||||||
flex: 1 0;
|
flex: 1 0;
|
||||||
line-height: 1.2;
|
line-height: 1.1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.stop__variant {
|
.stop__variant {
|
||||||
|
66
resources/styles/_trigonometry.scss
Normal file
66
resources/styles/_trigonometry.scss
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
///////////////////////////////////////////////////////////
|
||||||
|
// Plain SASS Trigonometry Algorithm in Taylor Expansion //
|
||||||
|
// //
|
||||||
|
// Based on //
|
||||||
|
// http://japborst.net/posts/sass-sines-and-cosines //
|
||||||
|
///////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
$pi: 3.14159265359;
|
||||||
|
$_precision: 5;
|
||||||
|
|
||||||
|
@function pow($base, $exp) {
|
||||||
|
$value: $base;
|
||||||
|
@if $exp > 1 {
|
||||||
|
@for $i from 2 through $exp {
|
||||||
|
$value: $value * $base;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@if $exp < 1{
|
||||||
|
@for $i from 0 through -$exp {
|
||||||
|
$value: $value / $base;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@return $value;
|
||||||
|
}
|
||||||
|
|
||||||
|
@function fact($num) {
|
||||||
|
$fact: 1;
|
||||||
|
@if $num > 0{
|
||||||
|
@for $i from 1 through $num {
|
||||||
|
$fact: $fact * $i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@return $fact;
|
||||||
|
}
|
||||||
|
|
||||||
|
@function _to_unitless_rad($angle) {
|
||||||
|
@if unit($angle) == "deg" {
|
||||||
|
$angle: $angle / 180deg * $pi;
|
||||||
|
}
|
||||||
|
@if unit($angle) == "rad" {
|
||||||
|
$angle: $angle / 1rad;
|
||||||
|
}
|
||||||
|
@return $angle;
|
||||||
|
}
|
||||||
|
|
||||||
|
@function sin($angle){
|
||||||
|
$a: _to_unitless_rad($angle);
|
||||||
|
$sin: $a;
|
||||||
|
@for $n from 1 through $_precision {
|
||||||
|
$sin: $sin + (pow(-1, $n) / fact(2 * $n + 1) ) * pow($a, (2 * $n + 1));
|
||||||
|
}
|
||||||
|
@return $sin;
|
||||||
|
}
|
||||||
|
|
||||||
|
@function cos($angle){
|
||||||
|
$a: _to_unitless_rad($angle);
|
||||||
|
$cos: 1;
|
||||||
|
@for $n from 1 through $_precision {
|
||||||
|
$cos: $cos + ( pow(-1,$n) / fact(2*$n) ) * pow($a,2*$n);
|
||||||
|
}
|
||||||
|
@return $cos;
|
||||||
|
}
|
||||||
|
|
||||||
|
@function tan($angle){
|
||||||
|
@return sin($angle) / cos($angle);
|
||||||
|
}
|
97
resources/styles/_trip.scss
Normal file
97
resources/styles/_trip.scss
Normal file
@ -0,0 +1,97 @@
|
|||||||
|
@import "trigonometry";
|
||||||
|
|
||||||
|
$description-rotation: 60deg;
|
||||||
|
$description-width: 250px;
|
||||||
|
|
||||||
|
$trip-stop-marker-size: .9rem;
|
||||||
|
$trip-stop-line-width: .2rem;
|
||||||
|
|
||||||
|
.trip {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.trip__stops {
|
||||||
|
padding-top: sin($description-rotation) * $description-width;
|
||||||
|
padding-right: cos($description-rotation) * $description-width;
|
||||||
|
padding-left: 0;
|
||||||
|
display: flex;
|
||||||
|
list-style: none;
|
||||||
|
overflow-x: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.trip-stop {
|
||||||
|
width: 2.5rem;
|
||||||
|
position: relative;
|
||||||
|
flex-shrink: 0;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.trip-stop:first-child {
|
||||||
|
.trip-stop__marker::before {
|
||||||
|
content: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.trip-stop:last-child {
|
||||||
|
.trip-stop__marker::after {
|
||||||
|
content: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.trip-stop__marker {
|
||||||
|
width: $trip-stop-marker-size;
|
||||||
|
height: $trip-stop-marker-size;
|
||||||
|
border: $dark $trip-stop-line-width solid;
|
||||||
|
border-radius: 100%;
|
||||||
|
background: white;
|
||||||
|
margin: .75rem 0;
|
||||||
|
|
||||||
|
&::before, &::after {
|
||||||
|
content: "";
|
||||||
|
display: block;
|
||||||
|
height: $trip-stop-line-width;
|
||||||
|
background: $dark;
|
||||||
|
width: 50%;
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
transform: translateY(-50%);
|
||||||
|
z-index: -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
&::after {
|
||||||
|
right: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
left: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@each $type, $color in $line-types {
|
||||||
|
.trip--#{$type} {
|
||||||
|
.trip-stop__marker {
|
||||||
|
border-color: $color;
|
||||||
|
|
||||||
|
&::before, &::after {
|
||||||
|
background: $color;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.trip-stop__description {
|
||||||
|
display: flex;
|
||||||
|
transform: rotate(-$description-rotation) translateX(.75rem);
|
||||||
|
transform-origin: 0 50%;
|
||||||
|
max-width: $description-width;
|
||||||
|
position: absolute;
|
||||||
|
left: 50%;
|
||||||
|
top: 0;
|
||||||
|
|
||||||
|
.stop {
|
||||||
|
width: max-content;
|
||||||
|
}
|
||||||
|
}
|
@ -51,6 +51,7 @@ $grid-gutter-width: $spacer * 2;
|
|||||||
@import "animations";
|
@import "animations";
|
||||||
@import "form";
|
@import "form";
|
||||||
@import "fabourites";
|
@import "fabourites";
|
||||||
|
@import "trip";
|
||||||
|
|
||||||
body {
|
body {
|
||||||
min-height: 100vh;
|
min-height: 100vh;
|
||||||
|
@ -3,6 +3,8 @@ import { Departure, Stop } from "../model";
|
|||||||
import { Component, Prop, Watch } from "vue-property-decorator";
|
import { Component, Prop, Watch } from "vue-property-decorator";
|
||||||
import { namespace } from 'vuex-class';
|
import { namespace } from 'vuex-class';
|
||||||
import store from '../store'
|
import store from '../store'
|
||||||
|
import { Trip } from "../model/trip";
|
||||||
|
import urls from "../urls";
|
||||||
|
|
||||||
const { State } = namespace('departures');
|
const { State } = namespace('departures');
|
||||||
|
|
||||||
@ -19,6 +21,7 @@ export class DepartureComponent extends Vue {
|
|||||||
@Prop(Object) departure: Departure;
|
@Prop(Object) departure: Departure;
|
||||||
|
|
||||||
showTrip: boolean = false;
|
showTrip: boolean = false;
|
||||||
|
trip: Trip = null;
|
||||||
|
|
||||||
get timeDiffers() {
|
get timeDiffers() {
|
||||||
const departure = this.departure;
|
const departure = this.departure;
|
||||||
@ -29,6 +32,19 @@ export class DepartureComponent extends Vue {
|
|||||||
get time() {
|
get time() {
|
||||||
return this.departure.estimated || this.departure.scheduled;
|
return this.departure.estimated || this.departure.scheduled;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Watch('showTrip')
|
||||||
|
async downloadTrips() {
|
||||||
|
if (this.showTrip != true || this.trip != null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const response = await fetch(urls.prepare(urls.trip, { id: this.departure.trip.id }));
|
||||||
|
|
||||||
|
if (response.ok) {
|
||||||
|
this.trip = await response.json();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Vue.component('Departures', DeparturesComponent);
|
Vue.component('Departures', DeparturesComponent);
|
||||||
|
@ -1,9 +1,10 @@
|
|||||||
import { Stop } from "./stop";
|
import { Stop } from "./stop";
|
||||||
import { Line } from "./line";
|
import { Line } from "./line";
|
||||||
import { Moment } from "moment";
|
import { Moment } from "moment";
|
||||||
|
import { Identity } from "./identity";
|
||||||
|
|
||||||
export interface Departure {
|
export interface Departure {
|
||||||
id: string;
|
key: string;
|
||||||
display: string;
|
display: string;
|
||||||
estimated: Moment;
|
estimated: Moment;
|
||||||
scheduled?: Moment;
|
scheduled?: Moment;
|
||||||
@ -12,6 +13,7 @@ export interface Departure {
|
|||||||
delay: number;
|
delay: number;
|
||||||
|
|
||||||
vehicle?: Vehicle;
|
vehicle?: Vehicle;
|
||||||
|
trip?: Identity;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Vehicle {
|
export interface Vehicle {
|
||||||
|
3
resources/ts/model/identity.ts
Normal file
3
resources/ts/model/identity.ts
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
export type Identity = {
|
||||||
|
id: string;
|
||||||
|
}
|
@ -1,4 +1,5 @@
|
|||||||
export * from './stop'
|
export * from './stop'
|
||||||
export * from './departure'
|
export * from './departure'
|
||||||
export * from './line'
|
export * from './line'
|
||||||
export * from './error'
|
export * from './error'
|
||||||
|
export * from './identity'
|
||||||
|
@ -56,5 +56,6 @@ export default {
|
|||||||
get: `${base}/stops/{id}`,
|
get: `${base}/stops/{id}`,
|
||||||
tracks: `${base}/stops/{id}/tracks`
|
tracks: `${base}/stops/{id}/tracks`
|
||||||
},
|
},
|
||||||
|
trip: `${base}/trips/{id}`,
|
||||||
prepare: (url: string, params: UrlParams = { }) => prepare(url, Object.assign({}, { provider: window['data'].provider }, params))
|
prepare: (url: string, params: UrlParams = { }) => prepare(url, Object.assign({}, { provider: window['data'].provider }, params))
|
||||||
}
|
}
|
||||||
|
@ -60,7 +60,7 @@ class GenericScheduleRepository extends DatabaseRepository implements ScheduleRe
|
|||||||
$last = $entity->getTrip()->getTrack()->getStopsInTrack()->last()->getStop();
|
$last = $entity->getTrip()->getTrack()->getStopsInTrack()->last()->getStop();
|
||||||
|
|
||||||
return Departure::createFromArray([
|
return Departure::createFromArray([
|
||||||
'id' => sprintf('%s::%s', $entity->getTrip()->getId(), $entity->getDeparture()->format('H:i')),
|
'key' => sprintf('%s::%s', $this->id->of($entity->getTrip()), $entity->getDeparture()->format('H:i')),
|
||||||
'scheduled' => $entity->getDeparture(),
|
'scheduled' => $entity->getDeparture(),
|
||||||
'stop' => $stop,
|
'stop' => $stop,
|
||||||
'display' => $last->getName(),
|
'display' => $last->getName(),
|
||||||
|
@ -70,7 +70,7 @@ class ZtmGdanskDepartureRepository implements DepartureRepository
|
|||||||
$estimated = (clone $scheduled)->addSeconds($delay['delayInSeconds']);
|
$estimated = (clone $scheduled)->addSeconds($delay['delayInSeconds']);
|
||||||
|
|
||||||
return Departure::createFromArray([
|
return Departure::createFromArray([
|
||||||
'id' => sprintf('%s::%s', $delay['routeId'], $scheduled->format('H:i')),
|
'key' => sprintf('%s::%s', $delay['routeId'], $scheduled->format('H:i')),
|
||||||
'scheduled' => $scheduled,
|
'scheduled' => $scheduled,
|
||||||
'estimated' => $estimated,
|
'estimated' => $estimated,
|
||||||
'stop' => $stop,
|
'stop' => $stop,
|
||||||
|
Loading…
Reference in New Issue
Block a user