diff --git a/resources/components/popper.html b/resources/components/popper.html
index 82cfe63..ed83d7a 100644
--- a/resources/components/popper.html
+++ b/resources/components/popper.html
@@ -1,4 +1,4 @@
-
+
diff --git a/resources/styles/_animations.scss b/resources/styles/_animations.scss
new file mode 100644
index 0000000..fc4a7d0
--- /dev/null
+++ b/resources/styles/_animations.scss
@@ -0,0 +1,38 @@
+@mixin vue-animation($name, $animation: .5s ease-in-out) {
+ .#{$name}-enter-active {
+ animation: #{$name}-in $animation;
+ animation-fill-mode: backwards;
+ }
+
+ .#{$name}-leave-active {
+ animation: #{$name}-in $animation reverse;
+ animation-fill-mode: forwards;
+ }
+
+ @keyframes #{$name}-in {
+ @content
+ }
+}
+
+@include vue-animation(fade, 250ms ease-in-out) {
+ 0% {
+ opacity: 0
+ }
+ 100% {
+ opacity: 1
+ }
+}
+
+.transition-box {
+ @include clearfix;
+
+ > * {
+ width: 100%;
+ float: left;
+ min-height: 2px;
+
+ &:not(:first-child) {
+ margin-left: -100%;
+ }
+ }
+}
\ No newline at end of file
diff --git a/resources/styles/_common.scss b/resources/styles/_common.scss
index ba1d4b6..29b9570 100644
--- a/resources/styles/_common.scss
+++ b/resources/styles/_common.scss
@@ -87,7 +87,7 @@
background: none;
}
- .btn {
+ > .btn {
margin-top: -.5rem;
margin-bottom: -.5rem;
}
@@ -97,3 +97,15 @@
svg.svg-inline--fa {
transform: rotate(360deg)
}
+
+.btn-unstyled {
+ padding: 0;
+ margin: 0;
+ background: none;
+ border: none;
+ display: block;
+}
+
+.icon {
+ padding: .5rem 0.75rem;
+}
\ No newline at end of file
diff --git a/resources/styles/_controls.scss b/resources/styles/_controls.scss
index 3e8a722..818cfae 100644
--- a/resources/styles/_controls.scss
+++ b/resources/styles/_controls.scss
@@ -4,11 +4,17 @@
color: black;
+ &:hover, &:active, &:focus {
+ text-decoration: none;
+ }
+
&:focus {
outline: 2px solid rgba($blue, .2);
}
}
+ display: inline-block;
+
&.btn-outline-action {
@extend .btn-outline-dark;
}
diff --git a/resources/styles/_popper.scss b/resources/styles/_popper.scss
index db193e8..1168ad0 100644
--- a/resources/styles/_popper.scss
+++ b/resources/styles/_popper.scss
@@ -113,6 +113,4 @@
@include placement("top");
@include placement("bottom");
}
-
- animation: ease-in fade-in 150ms
}
diff --git a/resources/styles/main.scss b/resources/styles/main.scss
index 94929f9..54184b5 100644
--- a/resources/styles/main.scss
+++ b/resources/styles/main.scss
@@ -43,6 +43,7 @@ $container-max-widths: map-merge($container-max-widths, ( xl: 1320px ));
@import "line";
@import "controls";
@import "popper";
+@import "animations";
body {
min-height: 100vh;
diff --git a/resources/ts/app.ts b/resources/ts/app.ts
index 5db619f..0f157a4 100644
--- a/resources/ts/app.ts
+++ b/resources/ts/app.ts
@@ -25,11 +25,14 @@ Vue.use(Vuex);
import('bootstrap'),
]);
+ // here goes "public" API
+ window['czydojade'] = Object.assign({
+ state: {}
+ }, window['czydojade'], {
+ components,
+ application: new components.Application({ el: '#app' })
+ });
+
store.dispatch('messages/update');
store.dispatch('load', window['czydojade'].state);
-
- // here goes "public" API
- window['czydojade'] = Object.assign({}, window['czydojade'], {
- components, application: new components.Application({ el: '#app' })
- });
})();
diff --git a/resources/ts/components/app.ts b/resources/ts/components/app.ts
index e664444..0a215dc 100644
--- a/resources/ts/components/app.ts
+++ b/resources/ts/components/app.ts
@@ -4,6 +4,7 @@ import { Component, Watch } from "vue-property-decorator";
import { Mutation, Action } from 'vuex-class'
import { ObtainPayload } from "../store/departures";
import { Stop } from "../model";
+import { PopperComponent } from "./utils";
@Component({ store })
export class Application extends Vue {
@@ -11,9 +12,11 @@ export class Application extends Vue {
messages: true
};
- private settings = {
+ private visibility = {
messages: false,
- departures: false
+ departures: false,
+ save: false,
+ picker: 'search'
};
private autorefresh = {
@@ -55,23 +58,19 @@ export class Application extends Vue {
this.$el.classList.remove('not-ready');
}
- @Action('messages/update') updateMessages: () => void;
+ @Action('messages/update') updateMessages: () => void;
@Action('departures/update') updateDepartures: (payload: ObtainPayload) => void;
@Mutation add: (stops: Stop[]) => void;
@Mutation remove: (stop: Stop) => void;
@Mutation clear: () => void;
- save() {
- this.$store.dispatch('save').then(x => console.log(x));
- }
-
@Watch('stops')
onStopUpdate(this: any, stops) {
this.updateDepartures({ stops });
}
- @Watch('settings', { immediate: true, deep: true })
+ @Watch('autorefresh', { immediate: true, deep: true })
onAutorefreshUpdate(settings) {
if (this.intervals.messages) {
clearInterval(this.intervals.messages);
diff --git a/resources/ts/components/favourites.ts b/resources/ts/components/favourites.ts
new file mode 100644
index 0000000..b904681
--- /dev/null
+++ b/resources/ts/components/favourites.ts
@@ -0,0 +1,39 @@
+import Vue from 'vue'
+import { Component } from 'vue-property-decorator'
+import { namespace } from "vuex-class";
+import { Favourite } from "../store/favourites";
+import { SavedState } from "../store/root";
+
+const { State, Mutation } = namespace('favourites');
+
+@Component({ template: require('../../components/favourites.html' )})
+export class FavouritesComponent extends Vue {
+ @State favourites: Favourite[];
+ @Mutation remove: (fav: Favourite) => void;
+
+ choose(favourite: Favourite) {
+ this.$store.dispatch('load', favourite.state);
+ }
+}
+
+@Component({ template: require('../../components/favourites/save.html' )})
+export class FavouritesAdderComponent extends Vue {
+ private name = "";
+
+ @Mutation add: (fav: Favourite) => void;
+
+ async save() {
+ const state = await this.$store.dispatch('save') as SavedState;
+ const name = this.name;
+
+ const favourite: Favourite = { name, state };
+
+ this.add(favourite);
+ this.name = '';
+
+ this.$emit('saved', favourite);
+ }
+}
+
+Vue.component('Favourites', FavouritesComponent);
+Vue.component('FavouritesAdder', FavouritesAdderComponent);
diff --git a/resources/ts/components/index.ts b/resources/ts/components/index.ts
index ce46728..c7258d3 100644
--- a/resources/ts/components/index.ts
+++ b/resources/ts/components/index.ts
@@ -5,4 +5,5 @@ export * from './departures'
export * from './stop'
export * from './messages'
export * from './map'
-export * from './app'
\ No newline at end of file
+export * from './app'
+export * from './favourites'
diff --git a/resources/ts/components/utils.ts b/resources/ts/components/utils.ts
index 3fed9f1..a074847 100644
--- a/resources/ts/components/utils.ts
+++ b/resources/ts/components/utils.ts
@@ -20,11 +20,12 @@ export class PopperComponent extends Vue {
public lazy: boolean;
public hovered: boolean = false;
+ public focused: boolean = false;
private _popper;
get show() {
- return this.visible || this.hovered;
+ return this.visible || this.hovered || this.focused;
}
mounted() {
diff --git a/resources/ts/filters.ts b/resources/ts/filters.ts
index 8ca0110..44c5ae1 100644
--- a/resources/ts/filters.ts
+++ b/resources/ts/filters.ts
@@ -30,7 +30,7 @@ Vue.directive('hover', {
el.addEventListener('click', activate);
el.addEventListener('keydown', keyboard);
el.addEventListener('mouseleave', deactivate);
- el.addEventListener('focusout', deactivate);
+ // el.addEventListener('focusout', deactivate);
},
unbind(el, binding) {
if (typeof binding['events'] !== 'undefined') {
@@ -40,7 +40,7 @@ Vue.directive('hover', {
el.removeEventListener('click', activate);
el.removeEventListener('keydown', keyboard);
el.removeEventListener('mouseleave', deactivate);
- el.removeEventListener('focusout', deactivate);
+ // el.removeEventListener('focusout', deactivate);
}
}
});
diff --git a/resources/ts/store/favourites.ts b/resources/ts/store/favourites.ts
new file mode 100644
index 0000000..51d711c
--- /dev/null
+++ b/resources/ts/store/favourites.ts
@@ -0,0 +1,37 @@
+import { RootState, SavedState } from "./root";
+import { Module, Plugin, Store } from "vuex";
+import * as utils from "../utils";
+
+export interface Favourite {
+ name: string;
+ state: SavedState;
+}
+
+export interface FavouritesState {
+ favourites: Favourite[];
+}
+
+const favourites: Module = {
+ namespaced: true,
+ state: {
+ favourites: []
+ },
+ mutations: {
+ add(state, favourite: Favourite) {
+ state.favourites.push(favourite);
+ },
+ remove(state, favourite: Favourite) {
+ state.favourites = state.favourites.filter(f => f != favourite);
+ }
+ }
+};
+
+export const localStorageSaver = (path: string, key: string): Plugin => (store: Store) => {
+ utils.set(store.state, path, JSON.parse(window.localStorage.getItem(key) || '[]'));
+
+ store.subscribe((mutation, state) => {
+ window.localStorage.setItem(key, JSON.stringify(utils.get(state, path)));
+ })
+};
+
+export default favourites;
\ No newline at end of file
diff --git a/resources/ts/store/index.ts b/resources/ts/store/index.ts
index f629d16..07ffcc8 100644
--- a/resources/ts/store/index.ts
+++ b/resources/ts/store/index.ts
@@ -1,10 +1,15 @@
import Vuex from 'vuex';
import messages from './messages';
-import departures from './departures';
+import departures from './departures'
+import favourites, { localStorageSaver } from './favourites'
+
import { state, mutations, actions } from "./root";
export default new Vuex.Store({
state, mutations, actions,
- modules: { messages, departures }
+ modules: { messages, departures, favourites },
+ plugins: [
+ localStorageSaver('favourites.favourites', 'favourites'),
+ ]
})
\ No newline at end of file
diff --git a/resources/ts/store/root.ts b/resources/ts/store/root.ts
index a549eeb..2a108ad 100644
--- a/resources/ts/store/root.ts
+++ b/resources/ts/store/root.ts
@@ -17,9 +17,10 @@ export const state: RootState = {
};
export const mutations: MutationTree = {
- add: (state, stops) => state.stops = [...state.stops, ...ensureArray(stops)],
- remove: (state, stop) => state.stops = state.stops.filter(s => s != stop),
- clear: (state) => state.stops = [],
+ add: (state, stops) => state.stops = [...state.stops, ...ensureArray(stops)],
+ replace: (state, stops) => state.stops = stops,
+ remove: (state, stop) => state.stops = state.stops.filter(s => s != stop),
+ clear: (state) => state.stops = [],
};
export const actions: ActionTree = {
@@ -28,7 +29,7 @@ export const actions: ActionTree = {
const response = await fetch(urls.prepare(urls.stops.all, { id: stops }));
if (response.ok) {
- commit('updateStops', await response.json());
+ commit('replace', await response.json());
}
}
},
diff --git a/src/Controller/MainController.php b/src/Controller/MainController.php
index fa34056..583322f 100644
--- a/src/Controller/MainController.php
+++ b/src/Controller/MainController.php
@@ -5,6 +5,7 @@ namespace App\Controller;
use App\Provider\Provider;
use App\Service\ProviderResolver;
+use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Routing\Annotation\Route;
class MainController extends Controller
@@ -20,9 +21,15 @@ class MainController extends Controller
/**
* @Route("/{provider}", name="app")
*/
- public function app(Provider $provider)
+ public function app(Provider $provider, Request $request)
{
- return $this->render('app.html.twig', ['provider' => $provider]);
+ $state = json_decode($request->query->get('state', '{}'), true) ?: [];
+ $state = array_merge([
+ 'version' => 1,
+ 'stops' => []
+ ], $state);
+
+ return $this->render('app.html.twig', compact('state', 'provider'));
}
/**
diff --git a/src/Service/VersionExtension.php b/src/Service/VersionExtension.php
new file mode 100644
index 0000000..d60c84c
--- /dev/null
+++ b/src/Service/VersionExtension.php
@@ -0,0 +1,18 @@
+
Komunikaty {{ '{{ messages.count }}' }}
-
@@ -113,6 +147,9 @@
{% endblock %}
diff --git a/templates/base.html.twig b/templates/base.html.twig
index 2247e5a..9b6be8b 100644
--- a/templates/base.html.twig
+++ b/templates/base.html.twig
@@ -26,6 +26,10 @@
{% block javascripts %}{% endblock %}
-