new layout
This commit is contained in:
parent
1c7a1e8669
commit
f8c3f77c83
@ -1,21 +1,4 @@
|
|||||||
<div class="departures">
|
<div class="departures" v-responsive>
|
||||||
<div class="departures__actions">
|
|
||||||
<div class="departures__auto-refresh">
|
|
||||||
<div class="custom-control custom-checkbox">
|
|
||||||
<input type="checkbox" class="custom-control-input" v-model="autoRefresh" :id="`autorefresh_${_uid}`">
|
|
||||||
<label class="custom-control-label" :for="`autorefresh_${_uid}`">auto odświeżanie:</label>
|
|
||||||
</div>
|
|
||||||
<div v-if="autoRefresh" class="d-flex align-items-center">
|
|
||||||
<input v-model="interval" class="form-control form-control-sm mx-1"/>
|
|
||||||
s
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<button @click="update" class="flex-space-left btn btn-sm btn-outline-action">
|
|
||||||
<fa :icon="['far', 'sync']" /> odśwież
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<ul class="departures__list list-underlined">
|
<ul class="departures__list list-underlined">
|
||||||
<li class="departure" v-for="departure in departures">
|
<li class="departure" v-for="departure in departures">
|
||||||
<div class="departure__line">
|
<div class="departure__line">
|
||||||
@ -35,7 +18,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="departure__stop">
|
<div class="departure__stop">
|
||||||
<fa :icon="['fal', 'sign']" fixed-width class="mr-1"/>
|
<fa :icon="['fal', 'sign']" fixed-width class="mr-1"></fa>
|
||||||
<stop :stop="departure.stop"></stop>
|
<stop :stop="departure.stop"></stop>
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
|
@ -10,7 +10,7 @@
|
|||||||
<h3 class="stop-group__name">{{ name }}</h3>
|
<h3 class="stop-group__name">{{ name }}</h3>
|
||||||
|
|
||||||
<div class="actions flex-space-left">
|
<div class="actions flex-space-left">
|
||||||
<button class="btn btn-action" @click="group.forEach(select)">
|
<button class="btn btn-action" @click="select(group)">
|
||||||
<fa :icon="['fal', 'check-double']"></fa>
|
<fa :icon="['fal', 'check-double']"></fa>
|
||||||
wybierz wszystkie
|
wybierz wszystkie
|
||||||
</button>
|
</button>
|
||||||
|
@ -1,6 +1,4 @@
|
|||||||
<div class="picker">
|
<div class="picker">
|
||||||
<departures :stops="stops"/>
|
|
||||||
|
|
||||||
<ul class="picker__stops list-underlined">
|
<ul class="picker__stops list-underlined">
|
||||||
<li v-for="stop in stops" :key="stop.id" class="d-flex align-items-center">
|
<li v-for="stop in stops" :key="stop.id" class="d-flex align-items-center">
|
||||||
<button @click="remove(stop)" class="btn btn-action">
|
<button @click="remove(stop)" class="btn btn-action">
|
||||||
|
@ -11,7 +11,7 @@
|
|||||||
</button>
|
</button>
|
||||||
|
|
||||||
<button class="btn btn-action" ref="action-map" v-hover:map>
|
<button class="btn btn-action" ref="action-map" v-hover:map>
|
||||||
<fa :icon="['fal', 'map-marked-alt']"/>
|
<fa :icon="['fal', 'map-marker-alt']"/>
|
||||||
</button>
|
</button>
|
||||||
</slot>
|
</slot>
|
||||||
</div>
|
</div>
|
||||||
@ -20,7 +20,7 @@
|
|||||||
<stop-details :stop="stop"></stop-details>
|
<stop-details :stop="stop"></stop-details>
|
||||||
</fold>
|
</fold>
|
||||||
|
|
||||||
<popper reference="action-map" :visible="map" arrow placement="left-start">
|
<popper reference="action-map" :visible="map" arrow>
|
||||||
<div style="height: 300px; width: 500px">
|
<div style="height: 300px; width: 500px">
|
||||||
<l-map :center="stop.location" :zoom=17 :options="{ zoomControl: false, dragging: false }">
|
<l-map :center="stop.location" :zoom=17 :options="{ zoomControl: false, dragging: false }">
|
||||||
<l-tile-layer url="http://{s}.tile.osm.org/{z}/{x}/{y}.png" attribution='© <a href="http://osm.org/copyright">OpenStreetMap</a> contributors'></l-tile-layer>
|
<l-tile-layer url="http://{s}.tile.osm.org/{z}/{x}/{y}.png" attribution='© <a href="http://osm.org/copyright">OpenStreetMap</a> contributors'></l-tile-layer>
|
||||||
|
@ -54,7 +54,14 @@
|
|||||||
padding-top: .5rem;
|
padding-top: .5rem;
|
||||||
padding-bottom: .5rem;
|
padding-bottom: .5rem;
|
||||||
|
|
||||||
|
line-height: $btn-line-height;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
background: none;
|
background: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.btn {
|
||||||
|
margin-top: -.5rem;
|
||||||
|
margin-bottom: -.5rem;
|
||||||
|
}
|
||||||
}
|
}
|
@ -7,7 +7,7 @@
|
|||||||
flex: 3 0;
|
flex: 3 0;
|
||||||
|
|
||||||
.line__symbol {
|
.line__symbol {
|
||||||
min-width: 5rem;
|
min-width: 6rem;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -28,6 +28,28 @@
|
|||||||
text-decoration: line-through;
|
text-decoration: line-through;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
.departures {
|
||||||
|
&.size-md {
|
||||||
|
.departure__time {
|
||||||
|
order: 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.departure__stop {
|
||||||
|
flex: 2 0;
|
||||||
|
width: auto;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.size-sm {
|
||||||
|
.departure__time {
|
||||||
|
margin-left: auto;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.departures__actions {
|
.departures__actions {
|
||||||
@ -41,20 +63,3 @@
|
|||||||
width: auto;
|
width: auto;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@include media-breakpoint-up(lg) {
|
|
||||||
.departure__time {
|
|
||||||
order: 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
.departure__stop {
|
|
||||||
flex: 2 0;
|
|
||||||
width: auto;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@include media-breakpoint-up(sm) {
|
|
||||||
.departure__time {
|
|
||||||
margin-left: auto;
|
|
||||||
}
|
|
||||||
}
|
|
@ -20,6 +20,8 @@ $default-spacing: .5rem;
|
|||||||
$alert-margin-bottom: $default-spacing;
|
$alert-margin-bottom: $default-spacing;
|
||||||
$headings-margin-bottom: $default-spacing;
|
$headings-margin-bottom: $default-spacing;
|
||||||
|
|
||||||
|
$container-max-widths: map-merge($container-max-widths, ( xl: 1320px ));
|
||||||
|
|
||||||
@import "~bootstrap/scss/bootstrap";
|
@import "~bootstrap/scss/bootstrap";
|
||||||
|
|
||||||
@import "common";
|
@import "common";
|
||||||
|
@ -12,6 +12,7 @@ window['$'] = window['jQuery'] = $;
|
|||||||
window['Popper'] = Popper;
|
window['Popper'] = Popper;
|
||||||
|
|
||||||
// dependencies
|
// dependencies
|
||||||
|
import './font-awesome';
|
||||||
import 'bootstrap'
|
import 'bootstrap'
|
||||||
import { Vue } from "vue-property-decorator";
|
import { Vue } from "vue-property-decorator";
|
||||||
|
|
||||||
@ -19,7 +20,6 @@ import './filters'
|
|||||||
|
|
||||||
// async dependencies
|
// async dependencies
|
||||||
(async function () {
|
(async function () {
|
||||||
import ('./font-awesome');
|
|
||||||
})();
|
})();
|
||||||
|
|
||||||
// here goes "public" API
|
// here goes "public" API
|
||||||
@ -30,9 +30,13 @@ window['czydojade'] = {
|
|||||||
window['app'] = new Vue({
|
window['app'] = new Vue({
|
||||||
el: '#app',
|
el: '#app',
|
||||||
data: {
|
data: {
|
||||||
|
stops: [],
|
||||||
messages: {
|
messages: {
|
||||||
count: 0,
|
count: 0,
|
||||||
visible: true
|
visible: true
|
||||||
|
},
|
||||||
|
departures: {
|
||||||
|
state: ''
|
||||||
}
|
}
|
||||||
}, methods: {
|
}, methods: {
|
||||||
handleMessagesUpdate(messages) {
|
handleMessagesUpdate(messages) {
|
||||||
|
@ -3,23 +3,31 @@ 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 urls from '../urls';
|
||||||
import * as moment from "moment";
|
import * as moment from "moment";
|
||||||
import { Jsonified } from "../utils";
|
import { FetchingState, Jsonified } from "../utils";
|
||||||
import { debounce } from "../decorators";
|
import { debounce, Notify } from "../decorators";
|
||||||
|
|
||||||
@Component({ template: require("../../components/departures.html") })
|
@Component({ template: require("../../components/departures.html") })
|
||||||
export class Departures extends Vue {
|
export class Departures extends Vue {
|
||||||
private _intervalId: number;
|
private _intervalId: number;
|
||||||
|
|
||||||
autoRefresh: boolean = false;
|
|
||||||
departures: Departure[] = [];
|
departures: Departure[] = [];
|
||||||
interval: number = 20;
|
|
||||||
|
|
||||||
@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')
|
@Watch('stops')
|
||||||
@debounce(300)
|
@debounce(300)
|
||||||
async update() {
|
async update() {
|
||||||
|
this.state = 'fetching';
|
||||||
const response = await fetch(urls.prepare(urls.departures, {
|
const response = await fetch(urls.prepare(urls.departures, {
|
||||||
stop: this.stops.map(stop => stop.id),
|
stop: this.stops.map(stop => stop.id),
|
||||||
}));
|
}));
|
||||||
@ -33,6 +41,10 @@ export class Departures extends Vue {
|
|||||||
|
|
||||||
return departure as Departure;
|
return departure as Departure;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
this.state = 'ready';
|
||||||
|
} else {
|
||||||
|
this.state = 'error';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2,30 +2,29 @@ import Component from "vue-class-component";
|
|||||||
import Vue from "vue";
|
import Vue from "vue";
|
||||||
import { Stop, StopGroup, StopGroups } from "../model";
|
import { Stop, StopGroup, StopGroups } from "../model";
|
||||||
import { Prop, Watch } from "vue-property-decorator";
|
import { Prop, Watch } from "vue-property-decorator";
|
||||||
import { filter, map } from "../utils";
|
import { ensureArray, FetchingState, filter, map } from "../utils";
|
||||||
import { debounce } from "../decorators";
|
import { debounce } from "../decorators";
|
||||||
import urls from '../urls';
|
import urls from '../urls';
|
||||||
|
|
||||||
@Component({ template: require("../../components/picker.html") })
|
@Component({ template: require("../../components/picker.html") })
|
||||||
export class PickerComponent extends Vue {
|
export class PickerComponent extends Vue {
|
||||||
protected stops?: Stop[] = [];
|
@Prop({ default: () => [], type: Array })
|
||||||
|
protected stops?: Stop[];
|
||||||
|
|
||||||
private remove(stop: Stop) {
|
private remove(stop: Stop) {
|
||||||
this.stops = this.stops.filter(s => s != stop);
|
this.$emit('update:stops', this.stops.filter(s => s != stop));
|
||||||
}
|
}
|
||||||
|
|
||||||
private add(stop: Stop) {
|
private add(stop: Stop|Stop[]) {
|
||||||
this.stops.push(stop);
|
this.$emit('update:stops', [...this.stops, ...ensureArray(stop)]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type FinderState = 'fetching' | 'ready' | 'error';
|
|
||||||
|
|
||||||
@Component({ template: require('../../components/finder.html') })
|
@Component({ template: require('../../components/finder.html') })
|
||||||
export class FinderComponent extends Vue {
|
export class FinderComponent extends Vue {
|
||||||
protected found?: StopGroups = {};
|
protected found?: StopGroups = {};
|
||||||
|
|
||||||
public state: FinderState = 'ready';
|
public state: FetchingState = 'ready';
|
||||||
public filter: string = "";
|
public filter: string = "";
|
||||||
|
|
||||||
@Prop({default: [], type: Array})
|
@Prop({default: [], type: Array})
|
||||||
|
@ -1,3 +1,6 @@
|
|||||||
|
import Vue from 'vue';
|
||||||
|
import getOwnPropertyDescriptor = Reflect.getOwnPropertyDescriptor;
|
||||||
|
|
||||||
export interface Decorator<TArgs extends any[], FArgs extends any[], TRet extends any, FRet extends any> {
|
export interface Decorator<TArgs extends any[], FArgs extends any[], TRet extends any, FRet extends any> {
|
||||||
decorate(f: (...farg: FArgs) => any, ...args: TArgs): (...farg: FArgs) => TRet;
|
decorate(f: (...farg: FArgs) => any, ...args: TArgs): (...farg: FArgs) => TRet;
|
||||||
|
|
||||||
@ -51,3 +54,24 @@ export const condition = decorator(function <Args extends any[], Ret extends any
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// decorators.js
|
||||||
|
import { createDecorator } from 'vue-class-component'
|
||||||
|
|
||||||
|
export const Notify = (name?: string) => createDecorator((options, key) => {
|
||||||
|
const symbol = Symbol(key);
|
||||||
|
|
||||||
|
if (typeof options.computed === 'undefined') {
|
||||||
|
options.computed = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
options.computed[key] = {
|
||||||
|
get: function (this: Vue) {
|
||||||
|
return this[symbol];
|
||||||
|
},
|
||||||
|
set: function (this: Vue, value: any) {
|
||||||
|
this[symbol] = value;
|
||||||
|
this.$emit(name ? name : `update:${key}`, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
@ -7,8 +7,10 @@ import { fas } from "@fortawesome/pro-solid-svg-icons";
|
|||||||
import { fal } from "@fortawesome/pro-light-svg-icons";
|
import { fal } from "@fortawesome/pro-light-svg-icons";
|
||||||
import { fac } from "./icons";
|
import { fac } from "./icons";
|
||||||
|
|
||||||
import { FontAwesomeIcon } from '@fortawesome/vue-fontawesome'
|
import { FontAwesomeIcon, FontAwesomeLayers, FontAwesomeLayersText } from '@fortawesome/vue-fontawesome'
|
||||||
|
|
||||||
library.add(far, fas, fal, fac);
|
library.add(far, fas, fal, fac);
|
||||||
|
|
||||||
Vue.component('fa', FontAwesomeIcon);
|
Vue.component('fa', FontAwesomeIcon);
|
||||||
|
Vue.component('fa-layers', FontAwesomeLayers);
|
||||||
|
Vue.component('fa-text', FontAwesomeLayersText);
|
||||||
|
@ -10,6 +10,7 @@ export type Optionalify<T> = { [K in keyof T]?: T[K] }
|
|||||||
export type Dictionary<T> = { [key: string]: T };
|
export type Dictionary<T> = { [key: string]: T };
|
||||||
|
|
||||||
export type Index = string | symbol | number;
|
export type Index = string | symbol | number;
|
||||||
|
export type FetchingState = 'fetching' | 'ready' | 'error' | 'not-initialized';
|
||||||
|
|
||||||
export function map<T extends {}, KT extends keyof T, R extends { [KR in keyof T] }>(source: T, mapper: (value: T[KT], key: KT) => R[KT]): R {
|
export function map<T extends {}, KT extends keyof T, R extends { [KR in keyof T] }>(source: T, mapper: (value: T[KT], key: KT) => R[KT]): R {
|
||||||
const result: R = {} as R;
|
const result: R = {} as R;
|
||||||
@ -36,3 +37,7 @@ export function filter<T, KT extends keyof T>(source: T, filter: (value: T[KT],
|
|||||||
export function signed(number: number): string {
|
export function signed(number: number): string {
|
||||||
return number > 0 ? `+${number}` : number.toString();
|
return number > 0 ? `+${number}` : number.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function ensureArray<T>(x: T[]|T): T[] {
|
||||||
|
return x instanceof Array ? x : [ x ];
|
||||||
|
}
|
@ -2,6 +2,17 @@
|
|||||||
{% block title "#{parent()} - #{provider.name}" %}
|
{% block title "#{parent()} - #{provider.name}" %}
|
||||||
|
|
||||||
{% block body %}
|
{% block body %}
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-4">
|
||||||
|
<section class="picker">
|
||||||
|
<h2 class="section__title">
|
||||||
|
<fa :icon="['fal', 'sign']" fixed-width></fa>
|
||||||
|
Przystanki
|
||||||
|
</h2>
|
||||||
|
<stop-picker :stops.sync="stops"></stop-picker>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-8">
|
||||||
<section class="messages" v-show="messages.count > 0">
|
<section class="messages" v-show="messages.count > 0">
|
||||||
<h2 class="section__title flex">
|
<h2 class="section__title flex">
|
||||||
<fa :icon="['fal', 'bullhorn']" fixed-width class="mr-2"></fa>
|
<fa :icon="['fal', 'bullhorn']" fixed-width class="mr-2"></fa>
|
||||||
@ -14,7 +25,21 @@
|
|||||||
<messages @update="handleMessagesUpdate"></messages>
|
<messages @update="handleMessagesUpdate"></messages>
|
||||||
</fold>
|
</fold>
|
||||||
</section>
|
</section>
|
||||||
<stop-picker></stop-picker>
|
|
||||||
|
<section>
|
||||||
|
<h2 class="section__title flex">
|
||||||
|
<fa :icon="['fal', 'clock']" fixed-width class="mr-1"></fa>
|
||||||
|
Odjazdy
|
||||||
|
<button class="btn btn-action flex-space-left" @click="$refs.departures.update()">
|
||||||
|
<fa-layers>
|
||||||
|
<fa :icon="['fal', 'sync']" :spin="departures.state === 'fetching'"></fa>
|
||||||
|
</fa-layers>
|
||||||
|
</button>
|
||||||
|
</h2>
|
||||||
|
<departures :stops="stops" ref="departures" @update:state="departures.state = $event"></departures>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block javascripts %}
|
{% block javascripts %}
|
||||||
|
@ -8,7 +8,7 @@
|
|||||||
<title>{% block title %}Czy dojadę?{% endblock %}</title>
|
<title>{% block title %}Czy dojadę?{% endblock %}</title>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<main role="main" class="container" id="app">
|
<main role="main" class="container mt-5" id="app">
|
||||||
{% block body %}{% endblock %}
|
{% block body %}{% endblock %}
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user