diff --git a/resources/components/picker/stop.html b/resources/components/picker/stop.html
index d18b9d1..1245508 100644
--- a/resources/components/picker/stop.html
+++ b/resources/components/picker/stop.html
@@ -1,8 +1,6 @@
-
-
diff --git a/resources/components/popper.html b/resources/components/popper.html
index ed83d7a..abb20fa 100644
--- a/resources/components/popper.html
+++ b/resources/components/popper.html
@@ -1,7 +1,4 @@
-
+
\ No newline at end of file
+
+
diff --git a/resources/components/stop/map.html b/resources/components/stop/map.html
index 7c80252..28051dc 100644
--- a/resources/components/stop/map.html
+++ b/resources/components/stop/map.html
@@ -1,5 +1,7 @@
-
-
-
-
+
+
+
+
+
+
diff --git a/resources/styles/_animations.scss b/resources/styles/_animations.scss
index fc4a7d0..8fa563a 100644
--- a/resources/styles/_animations.scss
+++ b/resources/styles/_animations.scss
@@ -35,4 +35,4 @@
margin-left: -100%;
}
}
-}
\ No newline at end of file
+}
diff --git a/resources/styles/_common.scss b/resources/styles/_common.scss
index 9e8c0ce..e58e558 100644
--- a/resources/styles/_common.scss
+++ b/resources/styles/_common.scss
@@ -109,3 +109,7 @@ svg.svg-inline--fa {
.icon {
padding: .5rem 0.75rem;
}
+
+.invalid-feedback p {
+ margin-bottom: 0;
+}
diff --git a/resources/styles/_fabourites.scss b/resources/styles/_fabourites.scss
new file mode 100644
index 0000000..0e7609a
--- /dev/null
+++ b/resources/styles/_fabourites.scss
@@ -0,0 +1,5 @@
+@include media-breakpoint-up('sm') {
+ .favourite-add-form {
+ width: 250px;
+ }
+}
diff --git a/resources/styles/_form.scss b/resources/styles/_form.scss
new file mode 100644
index 0000000..37df9bb
--- /dev/null
+++ b/resources/styles/_form.scss
@@ -0,0 +1,7 @@
+label {
+ font-weight: bold;
+ font-size: .8rem;
+ margin-bottom: 0;
+ margin-top: -0.2rem;
+ display: block;
+}
diff --git a/resources/styles/_popper.scss b/resources/styles/_popper.scss
index 1168ad0..8a1110a 100644
--- a/resources/styles/_popper.scss
+++ b/resources/styles/_popper.scss
@@ -114,3 +114,10 @@
@include placement("bottom");
}
}
+
+@include media-breakpoint-down('sm') {
+ .popper {
+ margin-left: $spacer;
+ margin-right: $spacer;
+ }
+}
diff --git a/resources/styles/main.scss b/resources/styles/main.scss
index 16ac380..653bc87 100644
--- a/resources/styles/main.scss
+++ b/resources/styles/main.scss
@@ -10,7 +10,6 @@ $primary: #005ea8;
$custom-control-indicator-checked-bg: $dark;
$custom-control-indicator-active-bg: $dark;
-
$line-types: (
'trolleybus': #419517,
'tram': #cd2e12,
@@ -27,6 +26,7 @@ $headings-margin-bottom: $default-spacing;
$container-max-widths: map-merge($container-max-widths, ( xl: 1320px ));
$link-color: #005ea8;
+$grid-gutter-width: $spacer * 2;
@import "~bootstrap/scss/bootstrap";
@@ -49,6 +49,8 @@ $link-color: #005ea8;
@import "controls";
@import "popper";
@import "animations";
+@import "form";
+@import "fabourites";
body {
min-height: 100vh;
diff --git a/resources/ts/app.ts b/resources/ts/app.ts
index 2adc0ca..bb4087c 100644
--- a/resources/ts/app.ts
+++ b/resources/ts/app.ts
@@ -13,9 +13,11 @@ window['Popper'] = Popper;
// dependencies
import Vue from "vue";
import Vuex from 'vuex';
+import PortalVue from 'portal-vue';
import { Workbox } from "workbox-window";
Vue.use(Vuex);
+Vue.use(PortalVue);
// async dependencies
(async function () {
diff --git a/resources/ts/components/favourites.ts b/resources/ts/components/favourites.ts
index b904681..68f9aaa 100644
--- a/resources/ts/components/favourites.ts
+++ b/resources/ts/components/favourites.ts
@@ -19,6 +19,7 @@ export class FavouritesComponent extends Vue {
@Component({ template: require('../../components/favourites/save.html' )})
export class FavouritesAdderComponent extends Vue {
private name = "";
+ private errors = { name: [] };
@Mutation add: (fav: Favourite) => void;
@@ -28,10 +29,28 @@ export class FavouritesAdderComponent extends Vue {
const favourite: Favourite = { name, state };
- this.add(favourite);
- this.name = '';
+ if (this.validate(favourite)) {
+ this.add(favourite);
+ this.name = '';
- this.$emit('saved', favourite);
+ this.$emit('saved', favourite);
+ }
+ }
+
+ private validate(favourite: Favourite) {
+ let errors = { name: [] };
+
+ if (favourite.name.length == 0) {
+ errors.name.push("Musisz podać nazwę.");
+ }
+
+ if (this.$store.state.favourites.favourites.filter(other => other.name == favourite.name).length > 0) {
+ errors.name.push("Istnieje już zapisana grupa przystanków o takiej nazwie.");
+ }
+
+ this.errors = errors;
+
+ return Object.entries(errors).map(a => a[1]).reduce((acc, cur) => [ ...acc, ...cur ]).length == 0;
}
}
diff --git a/resources/ts/components/picker.ts b/resources/ts/components/picker.ts
index 394541d..f11b5a1 100644
--- a/resources/ts/components/picker.ts
+++ b/resources/ts/components/picker.ts
@@ -13,6 +13,11 @@ export class PickerStopComponent extends Vue {
details: boolean = false;
map: boolean = false;
+ inMap: boolean = false;
+
+ get showMap() {
+ return this.inMap || this.map;
+ }
}
@Component({
diff --git a/resources/ts/components/utils.ts b/resources/ts/components/utils.ts
index a3f216e..a2e12b6 100644
--- a/resources/ts/components/utils.ts
+++ b/resources/ts/components/utils.ts
@@ -1,35 +1,37 @@
import Vue from 'vue';
import { Component, Prop, Watch } from "vue-property-decorator";
import Popper, { Placement } from "popper.js";
+import { Portal } from "portal-vue";
-@Component({ template: require("../../components/popper.html") })
+@Component({
+ template: require("../../components/popper.html")
+})
export class PopperComponent extends Vue {
@Prop(String)
public reference: string;
+ @Prop(Object)
+ public refs: string;
+
@Prop({ type: String, default: "auto" })
public placement: Placement;
@Prop(Boolean)
public arrow: boolean;
- @Prop({ type: Boolean, default: false })
- public visible: boolean;
-
- @Prop(Boolean)
- public lazy: boolean;
-
- public hovered: boolean = false;
- public focused: boolean = false;
-
+ private _event;
private _popper;
- get show() {
- return this.visible || this.hovered || this.focused;
+ focusOut(event: MouseEvent) {
+ if (this.$el.contains(event.target as Node)) {
+ return;
+ }
+
+ this.$emit('leave', event);
}
mounted() {
- const reference = this.$parent.$refs[this.reference] as HTMLElement;
+ const reference = this.refsSource[this.reference] as HTMLElement;
this._popper = new Popper(reference, this.$el, {
placement: this.placement,
@@ -42,8 +44,6 @@ export class PopperComponent extends Vue {
if (window.innerWidth < 560) {
data.instance.options.placement = 'bottom';
data.styles.transform = `translate3d(0, ${data.offsets.popper.top}px, 0)`;
- data.styles.width = '100%';
- data.styles.margin = '0';
data.styles.right = '0';
data.styles.left = '0';
data.styles.width = 'auto';
@@ -56,13 +56,20 @@ export class PopperComponent extends Vue {
}
});
- this.$nextTick(() => this._popper.update())
+ this.$nextTick(() => {
+ this._popper.update();
+ document.addEventListener('click', this._event = this.focusOut.bind(this), { capture: true });
+ });
}
updated() {
this._popper.update();
}
+ get listeners() {
+ return { ...this.$listeners, focusout: this.focusOut }
+ }
+
@Watch('visible')
private onVisibilityUpdate() {
this._popper.update();
@@ -71,6 +78,19 @@ export class PopperComponent extends Vue {
beforeDestroy() {
this._popper.destroy();
+ this._event && document.removeEventListener('click', this._event, { capture: true });
+ }
+
+ get refsSource() {
+ if (this.refs) {
+ return this.refs;
+ }
+
+ if (this.$parent.$options.name == 'portalTarget') {
+ return this.$parent.$parent.$refs;
+ }
+
+ return this.$parent.$refs
}
}
diff --git a/resources/ts/filters.ts b/resources/ts/filters.ts
index 44c5ae1..3a0e040 100644
--- a/resources/ts/filters.ts
+++ b/resources/ts/filters.ts
@@ -45,6 +45,20 @@ Vue.directive('hover', {
}
});
+Vue.directive('autofocus', {
+ inserted(el, binding) {
+ if (binding.value !== undefined) {
+ const value = binding.value;
+
+ if ((typeof value === "boolean" && !value) || (typeof value === "function" && !value(el))) {
+ return;
+ }
+ }
+
+ el.focus();
+ }
+});
+
Vue.directive('responsive', {
inserted(el, binding) {
const breakpoints = typeof binding.value === 'object' ? binding.value : {
diff --git a/src/Provider/ZtmGdansk/ZtmGdanskDepartureRepository.php b/src/Provider/ZtmGdansk/ZtmGdanskDepartureRepository.php
index 4908aad..e6faedf 100644
--- a/src/Provider/ZtmGdansk/ZtmGdanskDepartureRepository.php
+++ b/src/Provider/ZtmGdansk/ZtmGdanskDepartureRepository.php
@@ -51,7 +51,13 @@ class ZtmGdanskDepartureRepository implements DepartureRepository
private function getRealDepartures(Stop $stop)
{
- $estimates = json_decode(file_get_contents(static::ESTIMATES_URL . "?stopId=" . $stop->getId()), true)['delay'];
+ try {
+ $estimates = file_get_contents(static::ESTIMATES_URL . "?stopId=" . $stop->getId());
+ $estimates = json_decode($estimates, true)['delay'];
+ } catch (\Error $e) {
+ return collect();
+ }
+
$estimates = collect($estimates);
$lines = $estimates->map(function ($delay) {
diff --git a/templates/app.html.twig b/templates/app.html.twig
index 6d6061c..32753f6 100644
--- a/templates/app.html.twig
+++ b/templates/app.html.twig
@@ -11,7 +11,7 @@
Komunikaty
{{ '{{ messages.count }}' }}
-