Exctract trip to separate component

This commit is contained in:
Kacper Donat 2020-01-24 21:52:13 +01:00
parent c00e038fba
commit e49a70c71a
16 changed files with 125 additions and 38 deletions

View File

@ -41,6 +41,7 @@
"imagemin-webpack-plugin": "^2.3.0", "imagemin-webpack-plugin": "^2.3.0",
"mini-css-extract-plugin": "^0.4.2", "mini-css-extract-plugin": "^0.4.2",
"portal-vue": "^2.1.7", "portal-vue": "^2.1.7",
"vue-dragscroll": "^1.10.2",
"vue2-leaflet": "^1.0.2", "vue2-leaflet": "^1.0.2",
"vuex": "^3.0.1", "vuex": "^3.0.1",
"vuex-class": "^0.3.1", "vuex-class": "^0.3.1",

View File

@ -34,16 +34,7 @@
</div> </div>
</div> </div>
<fold :visible="showTrip"> <fold :visible="showTrip">
<div v-if="trip != null" class="trip" :class="[ `trip--${departure.line.type}` ]"> <trip :schedule="trip.schedule" v-if="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"> <div v-else class="text-center">
<fa icon="spinner-third" pulse></fa> <fa icon="spinner-third" pulse></fa>
</div> </div>

View File

@ -0,0 +1,13 @@
<div class="trip">
<ol class="trip__stops" v-dragscroll.x>
<li v-for="stop in schedule" class="trip__stop">
<div class="trip__marker"/>
<div class="trip__description">
<stop :stop="stop.stop"/>
</div>
<div class="trip__departure">
{{ stop.departure.format('HH:mm') }}
</div>
</li>
</ol>
</div>

View File

@ -0,0 +1,6 @@
.drag-scroll {
cursor : grab;
&:active {
cursor : grabbing;
}
}

View File

@ -4,7 +4,8 @@ $description-rotation: 60deg;
$description-width: 250px; $description-width: 250px;
$trip-stop-marker-size: .9rem; $trip-stop-marker-size: .9rem;
$trip-stop-line-width: .2rem; $trip-stop-marker-spacing: .75rem;
$trip-line-width: .2rem;
.trip { .trip {
display: flex; display: flex;
@ -18,45 +19,49 @@ $trip-stop-line-width: .2rem;
display: flex; display: flex;
list-style: none; list-style: none;
overflow-x: auto; overflow-x: auto;
@include no-scrollbars;
@extend .drag-scroll;
} }
.trip-stop { .trip__stop {
width: 2.5rem; width: 2.5rem;
position: relative; position: relative;
flex-shrink: 0; flex-shrink: 0;
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
flex-direction: column;
} }
.trip-stop:first-child { .trip__stop:first-child {
.trip-stop__marker::before { .trip__marker::before {
content: none; content: none;
} }
} }
.trip-stop:last-child { .trip__stop:last-child {
.trip-stop__marker::after { .trip__marker::after {
content: none; content: none;
} }
} }
.trip-stop__marker { .trip__marker {
width: $trip-stop-marker-size; width: $trip-stop-marker-size;
height: $trip-stop-marker-size; height: $trip-stop-marker-size;
border: $dark $trip-stop-line-width solid; border: $dark $trip-line-width solid;
border-radius: 100%; border-radius: 100%;
background: white; background: white;
margin: .75rem 0; margin: $trip-stop-marker-spacing 0;
&::before, &::after { &::before, &::after {
content: ""; content: "";
display: block; display: block;
height: $trip-stop-line-width; height: $trip-line-width;
background: $dark; background: $dark;
width: 50%; width: 50%;
position: absolute; position: absolute;
top: 50%; top: $trip-stop-marker-spacing + ($trip-stop-marker-size) / 2;
transform: translateY(-50%); transform: translateY(-50%);
z-index: -1; z-index: -1;
} }
@ -72,7 +77,7 @@ $trip-stop-line-width: .2rem;
@each $type, $color in $line-types { @each $type, $color in $line-types {
.trip--#{$type} { .trip--#{$type} {
.trip-stop__marker { .trip__marker {
border-color: $color; border-color: $color;
&::before, &::after { &::before, &::after {
@ -82,7 +87,7 @@ $trip-stop-line-width: .2rem;
} }
} }
.trip-stop__description { .trip__description {
display: flex; display: flex;
transform: rotate(-$description-rotation) translateX(.75rem); transform: rotate(-$description-rotation) translateX(.75rem);
transform-origin: 0 50%; transform-origin: 0 50%;
@ -95,3 +100,10 @@ $trip-stop-line-width: .2rem;
width: max-content; width: max-content;
} }
} }
.trip__departure {
font-size: $small-font-size;
font-weight: bold;
margin-bottom: .5rem;
}

View File

@ -42,6 +42,16 @@ $grid-gutter-width: $spacer * 2;
} }
} }
@mixin no-scrollbars {
scrollbar-width: none; /* Firefox */
-ms-overflow-style: none; /* Internet Explorer 10+ */
&::-webkit-scrollbar { /* WebKit */
width: 0;
height: 0;
}
}
@import "common"; @import "common";
@import "stop"; @import "stop";
@import "departure"; @import "departure";
@ -52,6 +62,7 @@ $grid-gutter-width: $spacer * 2;
@import "form"; @import "form";
@import "fabourites"; @import "fabourites";
@import "trip"; @import "trip";
@import "dragscroll";
body { body {
min-height: 100vh; min-height: 100vh;

View File

@ -14,10 +14,12 @@ window['Popper'] = Popper;
import Vue from "vue"; import Vue from "vue";
import Vuex from 'vuex'; import Vuex from 'vuex';
import PortalVue from 'portal-vue'; import PortalVue from 'portal-vue';
import VueDragscroll from 'vue-dragscroll';
import { Workbox } from "workbox-window"; import { Workbox } from "workbox-window";
Vue.use(Vuex); Vue.use(Vuex);
Vue.use(PortalVue); Vue.use(PortalVue);
Vue.use(VueDragscroll);
// async dependencies // async dependencies
(async function () { (async function () {

View File

@ -5,6 +5,8 @@ import { namespace } from 'vuex-class';
import store from '../store' import store from '../store'
import { Trip } from "../model/trip"; import { Trip } from "../model/trip";
import urls from "../urls"; import urls from "../urls";
import { Jsonified } from "../utils";
import * as moment from "moment";
const { State } = namespace('departures'); const { State } = namespace('departures');
@ -19,9 +21,20 @@ export class DeparturesComponent extends Vue {
@Component({ template: require("../../components/departures/departure.html") }) @Component({ template: require("../../components/departures/departure.html") })
export class DepartureComponent extends Vue { export class DepartureComponent extends Vue {
@Prop(Object) departure: Departure; @Prop(Object) departure: Departure;
scheduledTrip: Trip = null;
showTrip: boolean = false; showTrip: boolean = false;
trip: Trip = null;
processTrip(trip: Jsonified<Trip>): Trip {
return {
...trip,
schedule: trip.schedule.map(s => ({
...s,
arrival: moment.parseZone(s.arrival),
departure: moment.parseZone(s.departure),
}))
};
};
get timeDiffers() { get timeDiffers() {
const departure = this.departure; const departure = this.departure;
@ -42,9 +55,21 @@ export class DepartureComponent extends Vue {
const response = await fetch(urls.prepare(urls.trip, { id: this.departure.trip.id })); const response = await fetch(urls.prepare(urls.trip, { id: this.departure.trip.id }));
if (response.ok) { if (response.ok) {
this.trip = await response.json(); this.scheduledTrip = this.processTrip(await response.json());
} }
} }
get trip() {
const trip = this.scheduledTrip;
return trip && {
...trip,
schedule: trip.schedule.map(stop => ({
...stop,
arrival: stop.arrival.clone().add(this.departure.delay, 'seconds'),
departure: stop.departure.clone().add(this.departure.delay, 'seconds'),
}))
};
}
} }
Vue.component('Departures', DeparturesComponent); Vue.component('Departures', DeparturesComponent);

View File

@ -7,3 +7,4 @@ export * from './messages'
export * from './map' export * from './map'
export * from './app' export * from './app'
export * from './favourites' export * from './favourites'
export * from './trip'

View File

@ -0,0 +1,11 @@
import Vue from "vue";
import Component from "vue-class-component";
import { Prop } from "vue-property-decorator";
import { ScheduledStop } from "../model/trip";
@Component({ template: require("../../components/trip.html") })
export class TripComponent extends Vue {
@Prop(Array) public schedule: ScheduledStop[];
}
Vue.component('Trip', TripComponent);

View File

@ -2,7 +2,10 @@ export interface Stop {
id: any; id: any;
name: string; name: string;
description?: string; description?: string;
location?: [ number, number ]; location?: {
lat: number,
lng: number,
};
onDemand?: boolean; onDemand?: boolean;
variant?: string; variant?: string;
} }

View File

@ -1,6 +1,6 @@
import { Module } from "vuex"; import { Module } from "vuex";
import { RootState } from "./root"; import { RootState } from "./root";
import { Departure, Stop } from "../model"; import { Departure, Line, Stop } from "../model";
import * as moment from 'moment' import * as moment from 'moment'
import common, { CommonState } from './common' import common, { CommonState } from './common'
import urls from "../urls"; import urls from "../urls";
@ -43,12 +43,12 @@ export const departures: Module<DeparturesState, RootState> = {
} }
const departures = await response.json() as Jsonified<Departure>[]; const departures = await response.json() as Jsonified<Departure>[];
commit('update', departures.map(departure => { commit('update', departures.map((departure): Departure => ({
departure.scheduled = moment.parseZone(departure.scheduled); ...departure,
departure.estimated = departure.estimated && moment.parseZone(departure.estimated); line: departure.line as Line,
scheduled: moment.parseZone(departure.scheduled),
return departure as Departure; estimated: departure.estimated && moment.parseZone(departure.estimated),
})); })));
} }
} }
}; };

View File

@ -1,9 +1,13 @@
type Simplify<T, K = any> = string | import { Moment } from "moment";
type Simplify<T> = string |
T extends string ? string : T extends string ? string :
T extends number ? number : T extends number ? number :
T extends boolean ? boolean : T extends boolean ? boolean :
T extends Array<K> ? Array<K> : T extends Moment ? string :
T extends Object ? Object : any; T extends Array<infer K> ? Array<Simplify<K>> :
T extends (infer K)[] ? Simplify<K>[] :
T extends Object ? Jsonified<T> : any;
export type Jsonified<T> = { [K in keyof T]: Simplify<T[K]> } export type Jsonified<T> = { [K in keyof T]: Simplify<T[K]> }
export type Optionalify<T> = { [K in keyof T]?: T[K] } export type Optionalify<T> = { [K in keyof T]?: T[K] }

View File

@ -8,7 +8,7 @@ use Tightenco\Collect\Support\Collection;
interface ScheduleRepository interface ScheduleRepository
{ {
const DEFAULT_DEPARTURES_COUNT = 8; const DEFAULT_DEPARTURES_COUNT = 16;
public function getDeparturesForStop( public function getDeparturesForStop(
Stop $stop, Stop $stop,

View File

@ -46,7 +46,9 @@ class ZtmGdanskDepartureRepository implements DepartureRepository
$first = $real->map(t\getter('scheduled'))->min() ?? $now; $first = $real->map(t\getter('scheduled'))->min() ?? $now;
$scheduled = $this->getScheduledDepartures($stop, $first); $scheduled = $this->getScheduledDepartures($stop, $first);
return $this->pair($scheduled, $real); return $this->pair($scheduled, $real)->filter(function (Departure $departure) use ($now) {
return $departure->getDeparture() > $now;
});
} }
private function getRealDepartures(Stop $stop) private function getRealDepartures(Stop $stop)

View File

@ -6197,6 +6197,11 @@ vue-class-component@^6.2.0:
resolved "https://registry.yarnpkg.com/vue-class-component/-/vue-class-component-6.3.2.tgz#e6037e84d1df2af3bde4f455e50ca1b9eec02be6" resolved "https://registry.yarnpkg.com/vue-class-component/-/vue-class-component-6.3.2.tgz#e6037e84d1df2af3bde4f455e50ca1b9eec02be6"
integrity sha512-cH208IoM+jgZyEf/g7mnFyofwPDJTM/QvBNhYMjqGB8fCsRyTf68rH2ISw/G20tJv+5mIThQ3upKwoL4jLTr1A== integrity sha512-cH208IoM+jgZyEf/g7mnFyofwPDJTM/QvBNhYMjqGB8fCsRyTf68rH2ISw/G20tJv+5mIThQ3upKwoL4jLTr1A==
vue-dragscroll@^1.10.2:
version "1.10.2"
resolved "https://registry.yarnpkg.com/vue-dragscroll/-/vue-dragscroll-1.10.2.tgz#34ace3c6aa7a39edc157cac5e9ea1d5b31b1119c"
integrity sha512-fGVw8KP3ggbp49csa1Tbj2my0YuNmZ1zxYYge4QWIypGNHVwd9hResy/v6QF5HxY0a+qd2EBteeBpxtJxFMp5A==
vue-property-decorator@^7.0.0: vue-property-decorator@^7.0.0:
version "7.3.0" version "7.3.0"
resolved "https://registry.yarnpkg.com/vue-property-decorator/-/vue-property-decorator-7.3.0.tgz#d50d67f0b0f1c814f9f2fba36d6eeccbcc62dbb6" resolved "https://registry.yarnpkg.com/vue-property-decorator/-/vue-property-decorator-7.3.0.tgz#d50d67f0b0f1c814f9f2fba36d6eeccbcc62dbb6"