move departures data to vuex

This commit is contained in:
Kacper Donat 2018-09-30 14:59:10 +02:00
parent f29fb2206e
commit 73134a0e01
11 changed files with 130 additions and 84 deletions

View File

@ -39,9 +39,6 @@ Vue.use(Vuex);
stops: [], stops: [],
sections: { sections: {
messages: true messages: true
},
departures: {
state: ''
} }
}, },
computed: { computed: {
@ -51,11 +48,22 @@ Vue.use(Vuex);
counts: this.$store.getters['messages/counts'], counts: this.$store.getters['messages/counts'],
state: this.$store.state.messages.state state: this.$store.state.messages.state
}; };
},
departures(this: any) {
return {
state: this.$store.state.departures.state
};
}
},
watch: {
stops(this: any, stops) {
this.updateDepartures({ stops });
} }
}, },
methods: { methods: {
...mapActions({ ...mapActions({
updateMessages: 'messages/update' updateMessages: 'messages/update',
updateDepartures: 'departures/update'
}) })
} }
}); });

View File

@ -1,65 +1,18 @@
import Vue from 'vue' import Vue from 'vue'
import { Departure, Stop } from "../model"; import { Departure, Stop } from "../model";
import { Component, Prop, Watch } from "vue-property-decorator"; import { Component, Prop, Watch } from "vue-property-decorator";
import urls from '../urls'; import { namespace } from 'vuex-class';
import * as moment from "moment"; import store from '../store'
import { FetchingState, Jsonified } from "../utils";
import { debounce, notify } from "../decorators";
@Component({ template: require("../../components/departures.html") }) const { State } = namespace('departures');
@Component({ template: require("../../components/departures.html"), store })
export class Departures extends Vue { export class Departures extends Vue {
private _intervalId: number; @State
departures: Departure[];
departures: Departure[] = [];
@Prop(Array) @Prop(Array)
stops: Stop[]; stops: Stop[];
@Prop({ default: false, type: Boolean })
autoRefresh: boolean;
@Prop({ default: 20, type: Number })
interval: number;
@notify()
state: FetchingState;
@Watch('stops')
@debounce(300)
async update() {
this.state = 'fetching';
const response = await fetch(urls.prepare(urls.departures, {
stop: this.stops.map(stop => stop.id),
}));
if (response.ok) {
const departures = await response.json() as Jsonified<Departure>[];
this.departures = departures.map(departure => {
departure.scheduled = moment.parseZone(departure.scheduled);
departure.estimated = moment.parseZone(departure.estimated);
return departure as Departure;
});
this.state = 'ready';
} else {
this.state = 'error';
}
}
@Watch('interval')
@Watch('autoRefresh')
private setupAutoRefresh() {
if (this._intervalId) {
window.clearInterval(this._intervalId);
this._intervalId = undefined;
}
if (this.autoRefresh) {
this._intervalId = window.setInterval(() => this.update(), this.interval * 1000);
}
}
} }
Vue.component('Departures', Departures); Vue.component('Departures', Departures);

View File

@ -0,0 +1,3 @@
export interface Error {
message: string;
}

View File

@ -1,3 +1,4 @@
export * from './stop' export * from './stop'
export * from './departure' export * from './departure'
export * from './line' export * from './line'
export * from './error'

View File

@ -0,0 +1,27 @@
import { FetchingState } from "../utils";
import { Moment } from "moment";
import { Module, MutationTree } from "vuex";
import { RootState } from "./root";
import * as moment from "moment";
export interface CommonState {
state: FetchingState,
lastUpdate: Moment,
error: string
}
export const state: CommonState = {
state: "not-initialized",
error: "",
lastUpdate: moment()
};
export const mutations: MutationTree<CommonState> = {
fetching: (state) => state.state = 'fetching',
error: (state, error) => {
state.state = 'error';
state.error = error;
}
};
export default { state, mutations };

View File

@ -0,0 +1,56 @@
import { Module } from "vuex";
import { RootState } from "./root";
import { Departure, Stop } from "../model";
import * as moment from 'moment'
import common, { CommonState } from './common'
import urls from "../urls";
import { Jsonified } from "../utils";
export interface DeparturesState extends CommonState {
departures: Departure[],
}
interface ObtainPayload {
stops: Stop[]
}
export const departures: Module<DeparturesState, RootState> = {
namespaced: true,
state: {
departures: [ ],
...common.state
},
mutations: {
update: (state, departures) => {
state.departures = departures;
state.lastUpdate = moment();
state.state = 'ready';
},
...common.mutations
},
actions: {
async update({ commit }, { stops }: ObtainPayload) {
commit('fetching');
const response = await fetch(urls.prepare(urls.departures, {
stop: stops.map(stop => stop.id),
}));
if (!response.ok) {
const error = await response.json() as Error;
commit('error', error.message);
return;
}
const departures = await response.json() as Jsonified<Departure>[];
commit('update', departures.map(departure => {
departure.scheduled = moment.parseZone(departure.scheduled);
departure.estimated = moment.parseZone(departure.estimated);
return departure as Departure;
}));
}
}
};
export default departures;

View File

@ -1,7 +1,8 @@
import Vuex from 'vuex'; import Vuex from 'vuex';
import { messages } from './messages'; import messages from './messages';
import departures from './departures';
export default new Vuex.Store({ export default new Vuex.Store({
modules: { messages } modules: { messages, departures }
}) })

View File

@ -1,24 +1,20 @@
import { ActionContext, Module, Store } from "vuex"; import { ActionContext, Module } from "vuex";
import { RootState } from "./root"; import { RootState } from "./root";
import { Message, MessageType } from "../model/message"; import { Message, MessageType } from "../model/message";
import common, { CommonState } from "./common";
import urls from "../urls"; import urls from "../urls";
import { FetchingState, Jsonified } from "../utils"; import { Jsonified } from "../utils";
import * as moment from 'moment'; import * as moment from 'moment';
import { Moment } from "moment";
export interface MessagesState { export interface MessagesState extends CommonState {
messages: Message[], messages: Message[]
state: FetchingState,
lastUpdate: Moment
} }
export const messages: Module<MessagesState, RootState> = { export const messages: Module<MessagesState, RootState> = {
namespaced: true, namespaced: true,
state: { state: {
messages: [], messages: [],
state: "not-initialized", ...common.state,
lastUpdate: moment()
}, },
getters: { getters: {
count: state => state.messages.length, count: state => state.messages.length,
@ -34,8 +30,7 @@ export const messages: Module<MessagesState, RootState> = {
state.lastUpdate = moment(); state.lastUpdate = moment();
state.state = 'ready'; state.state = 'ready';
}, },
fetching: (state: MessagesState) => state.state = 'fetching', ...common.mutations
error: (state: MessagesState, error) => state.state = 'error',
}, },
actions: { actions: {
async update({ commit }: ActionContext<MessagesState, RootState>) { async update({ commit }: ActionContext<MessagesState, RootState>) {
@ -44,7 +39,8 @@ export const messages: Module<MessagesState, RootState> = {
const response = await fetch(urls.prepare(urls.messages)); const response = await fetch(urls.prepare(urls.messages));
if (!response.ok) { if (!response.ok) {
commit('error', await response.json()); const error = await response.json() as Error;
commit('error', error.message);
return; return;
} }
@ -60,3 +56,5 @@ export const messages: Module<MessagesState, RootState> = {
} }
} }
}; };
export default messages;

View File

@ -3,6 +3,7 @@
namespace App\Controller\Api\v1; namespace App\Controller\Api\v1;
use App\Controller\Controller; use App\Controller\Controller;
use App\Model\Stop;
use App\Provider\TrackRepository; use App\Provider\TrackRepository;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route; use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Request;
@ -40,14 +41,16 @@ class TracksController extends Controller
private function byStop(Request $request, TrackRepository $repository) private function byStop(Request $request, TrackRepository $repository)
{ {
$stop = encapsulate($request->query->get('stop')); $stop = $request->query->get('stop');
$stop = array_map([Stop::class, 'reference'], encapsulate($stop));
return $this->json($repository->getByStop($stop)); return $this->json($repository->getByStop($stop));
} }
private function byLine(Request $request, TrackRepository $repository) private function byLine(Request $request, TrackRepository $repository)
{ {
$line = encapsulate($request->query->get('line')); $line = $request->query->get('line');
$line = array_map([Stop::class, 'reference'], encapsulate($line));
return $this->json($repository->getByLine($line)); return $this->json($repository->getByLine($line));
} }

View File

@ -35,7 +35,6 @@ class GenericTrackRepository extends DatabaseRepository implements TrackReposito
public function getByStop($stop): Collection public function getByStop($stop): Collection
{ {
$reference = f\apply(f\ref([$this, 'reference']), StopEntity::class); $reference = f\apply(f\ref([$this, 'reference']), StopEntity::class);
$stop = array_map([Stop::class, 'reference'], encapsulate($stop));
$tracks = $this->em->createQueryBuilder() $tracks = $this->em->createQueryBuilder()
->from(StopInTrack::class, 'st') ->from(StopInTrack::class, 'st')
@ -43,7 +42,7 @@ class GenericTrackRepository extends DatabaseRepository implements TrackReposito
->where('st.stop in (:stop)') ->where('st.stop in (:stop)')
->select(['st', 't']) ->select(['st', 't'])
->getQuery() ->getQuery()
->execute(['stop' => array_map($reference, $stop)]); ->execute(['stop' => array_map($reference, encapsulate($stop))]);
return collect($tracks)->map(function (StopInTrack $entity) { return collect($tracks)->map(function (StopInTrack $entity) {
return [ $this->convert($entity->getTrack()), $entity->getOrder() ]; return [ $this->convert($entity->getTrack()), $entity->getOrder() ];
@ -53,7 +52,6 @@ class GenericTrackRepository extends DatabaseRepository implements TrackReposito
public function getByLine($line): Collection public function getByLine($line): Collection
{ {
$reference = f\apply(f\ref([$this, 'reference']), LineEntity::class); $reference = f\apply(f\ref([$this, 'reference']), LineEntity::class);
$line = array_map([Stop::class, 'reference'], encapsulate($line));
$tracks = $this->em->createQueryBuilder() $tracks = $this->em->createQueryBuilder()
->from(StopInTrack::class, 'st') ->from(StopInTrack::class, 'st')
@ -62,7 +60,7 @@ class GenericTrackRepository extends DatabaseRepository implements TrackReposito
->where('st.line in (:line)') ->where('st.line in (:line)')
->select(['st', 't', 's']) ->select(['st', 't', 's'])
->getQuery() ->getQuery()
->execute(['stop' => array_map($reference, $line)]); ->execute(['stop' => array_map($reference, encapsulate($line))]);
return collect($tracks)->map(f\ref([$this, 'convert'])); return collect($tracks)->map(f\ref([$this, 'convert']));
} }

View File

@ -25,13 +25,11 @@
<h2 class="section__title flex"> <h2 class="section__title flex">
<fa :icon="['fal', 'clock']" fixed-width class="mr-1"></fa> <fa :icon="['fal', 'clock']" fixed-width class="mr-1"></fa>
Odjazdy Odjazdy
<button class="btn btn-action flex-space-left" @click="$refs.departures.update()"> <button class="btn btn-action flex-space-left" @click="updateDepartures({ stops })">
<fa-layers>
<fa :icon="['fal', 'sync']" :spin="departures.state === 'fetching'"></fa> <fa :icon="['fal', 'sync']" :spin="departures.state === 'fetching'"></fa>
</fa-layers>
</button> </button>
</h2> </h2>
<departures :stops="stops" ref="departures" @update:state="departures.state = $event"></departures> <departures :stops="stops"></departures>
{% if provider.attribution %} {% if provider.attribution %}
<div class="attribution"> <div class="attribution">
<fa :icon="['fal', 'info-circle']"></fa> <fa :icon="['fal', 'info-circle']"></fa>