new layout
This commit is contained in:
parent
1c7a1e8669
commit
f8c3f77c83
@ -1,21 +1,4 @@
|
||||
<div class="departures">
|
||||
<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>
|
||||
|
||||
<div class="departures" v-responsive>
|
||||
<ul class="departures__list list-underlined">
|
||||
<li class="departure" v-for="departure in departures">
|
||||
<div class="departure__line">
|
||||
@ -35,7 +18,7 @@
|
||||
</div>
|
||||
|
||||
<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>
|
||||
</div>
|
||||
</li>
|
||||
|
@ -10,7 +10,7 @@
|
||||
<h3 class="stop-group__name">{{ name }}</h3>
|
||||
|
||||
<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>
|
||||
wybierz wszystkie
|
||||
</button>
|
||||
|
@ -1,6 +1,4 @@
|
||||
<div class="picker">
|
||||
<departures :stops="stops"/>
|
||||
|
||||
<ul class="picker__stops list-underlined">
|
||||
<li v-for="stop in stops" :key="stop.id" class="d-flex align-items-center">
|
||||
<button @click="remove(stop)" class="btn btn-action">
|
||||
|
@ -11,7 +11,7 @@
|
||||
</button>
|
||||
|
||||
<button class="btn btn-action" ref="action-map" v-hover:map>
|
||||
<fa :icon="['fal', 'map-marked-alt']"/>
|
||||
<fa :icon="['fal', 'map-marker-alt']"/>
|
||||
</button>
|
||||
</slot>
|
||||
</div>
|
||||
@ -20,7 +20,7 @@
|
||||
<stop-details :stop="stop"></stop-details>
|
||||
</fold>
|
||||
|
||||
<popper reference="action-map" :visible="map" arrow placement="left-start">
|
||||
<popper reference="action-map" :visible="map" arrow>
|
||||
<div style="height: 300px; width: 500px">
|
||||
<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>
|
||||
|
@ -54,7 +54,14 @@
|
||||
padding-top: .5rem;
|
||||
padding-bottom: .5rem;
|
||||
|
||||
line-height: $btn-line-height;
|
||||
|
||||
&:hover {
|
||||
background: none;
|
||||
}
|
||||
|
||||
.btn {
|
||||
margin-top: -.5rem;
|
||||
margin-bottom: -.5rem;
|
||||
}
|
||||
}
|
@ -7,7 +7,7 @@
|
||||
flex: 3 0;
|
||||
|
||||
.line__symbol {
|
||||
min-width: 5rem;
|
||||
min-width: 6rem;
|
||||
}
|
||||
}
|
||||
|
||||
@ -28,6 +28,28 @@
|
||||
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 {
|
||||
@ -41,20 +63,3 @@
|
||||
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;
|
||||
$headings-margin-bottom: $default-spacing;
|
||||
|
||||
$container-max-widths: map-merge($container-max-widths, ( xl: 1320px ));
|
||||
|
||||
@import "~bootstrap/scss/bootstrap";
|
||||
|
||||
@import "common";
|
||||
|
@ -12,6 +12,7 @@ window['$'] = window['jQuery'] = $;
|
||||
window['Popper'] = Popper;
|
||||
|
||||
// dependencies
|
||||
import './font-awesome';
|
||||
import 'bootstrap'
|
||||
import { Vue } from "vue-property-decorator";
|
||||
|
||||
@ -19,7 +20,6 @@ import './filters'
|
||||
|
||||
// async dependencies
|
||||
(async function () {
|
||||
import ('./font-awesome');
|
||||
})();
|
||||
|
||||
// here goes "public" API
|
||||
@ -30,9 +30,13 @@ window['czydojade'] = {
|
||||
window['app'] = new Vue({
|
||||
el: '#app',
|
||||
data: {
|
||||
stops: [],
|
||||
messages: {
|
||||
count: 0,
|
||||
visible: true
|
||||
},
|
||||
departures: {
|
||||
state: ''
|
||||
}
|
||||
}, methods: {
|
||||
handleMessagesUpdate(messages) {
|
||||
|
@ -3,23 +3,31 @@ import { Departure, Stop } from "../model";
|
||||
import { Component, Prop, Watch } from "vue-property-decorator";
|
||||
import urls from '../urls';
|
||||
import * as moment from "moment";
|
||||
import { Jsonified } from "../utils";
|
||||
import { debounce } from "../decorators";
|
||||
import { FetchingState, Jsonified } from "../utils";
|
||||
import { debounce, Notify } from "../decorators";
|
||||
|
||||
@Component({ template: require("../../components/departures.html") })
|
||||
export class Departures extends Vue {
|
||||
private _intervalId: number;
|
||||
|
||||
autoRefresh: boolean = false;
|
||||
departures: Departure[] = [];
|
||||
interval: number = 20;
|
||||
|
||||
@Prop(Array)
|
||||
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),
|
||||
}));
|
||||
@ -33,6 +41,10 @@ export class Departures extends Vue {
|
||||
|
||||
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 { Stop, StopGroup, StopGroups } from "../model";
|
||||
import { Prop, Watch } from "vue-property-decorator";
|
||||
import { filter, map } from "../utils";
|
||||
import { ensureArray, FetchingState, filter, map } from "../utils";
|
||||
import { debounce } from "../decorators";
|
||||
import urls from '../urls';
|
||||
|
||||
@Component({ template: require("../../components/picker.html") })
|
||||
export class PickerComponent extends Vue {
|
||||
protected stops?: Stop[] = [];
|
||||
@Prop({ default: () => [], type: Array })
|
||||
protected stops?: 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) {
|
||||
this.stops.push(stop);
|
||||
private add(stop: Stop|Stop[]) {
|
||||
this.$emit('update:stops', [...this.stops, ...ensureArray(stop)]);
|
||||
}
|
||||
}
|
||||
|
||||
type FinderState = 'fetching' | 'ready' | 'error';
|
||||
|
||||
@Component({ template: require('../../components/finder.html') })
|
||||
export class FinderComponent extends Vue {
|
||||
protected found?: StopGroups = {};
|
||||
|
||||
public state: FinderState = 'ready';
|
||||
public state: FetchingState = 'ready';
|
||||
public filter: string = "";
|
||||
|
||||
@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> {
|
||||
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 { fac } from "./icons";
|
||||
|
||||
import { FontAwesomeIcon } from '@fortawesome/vue-fontawesome'
|
||||
import { FontAwesomeIcon, FontAwesomeLayers, FontAwesomeLayersText } from '@fortawesome/vue-fontawesome'
|
||||
|
||||
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 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 {
|
||||
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 {
|
||||
return number > 0 ? `+${number}` : number.toString();
|
||||
}
|
||||
|
||||
export function ensureArray<T>(x: T[]|T): T[] {
|
||||
return x instanceof Array ? x : [ x ];
|
||||
}
|
@ -2,19 +2,44 @@
|
||||
{% block title "#{parent()} - #{provider.name}" %}
|
||||
|
||||
{% block body %}
|
||||
<section class="messages" v-show="messages.count > 0">
|
||||
<h2 class="section__title flex">
|
||||
<fa :icon="['fal', 'bullhorn']" fixed-width class="mr-2"></fa>
|
||||
Komunikaty <span class="ml-2 badge badge-pill badge-dark">{{ '{{ messages.count }}' }}</span>
|
||||
<button class="btn btn-action btn-sm flex-space-left" @click="messages.visible = !messages.visible">
|
||||
<fa :icon="['fal', messages.visible ? 'chevron-up' : 'chevron-down']" fixed-width />
|
||||
</button>
|
||||
</h2>
|
||||
<fold :visible="messages.visible">
|
||||
<messages @update="handleMessagesUpdate"></messages>
|
||||
</fold>
|
||||
</section>
|
||||
<stop-picker></stop-picker>
|
||||
<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">
|
||||
<h2 class="section__title flex">
|
||||
<fa :icon="['fal', 'bullhorn']" fixed-width class="mr-2"></fa>
|
||||
Komunikaty <span class="ml-2 badge badge-pill badge-dark">{{ '{{ messages.count }}' }}</span>
|
||||
<button class="btn btn-action btn-sm flex-space-left" @click="messages.visible = !messages.visible">
|
||||
<fa :icon="['fal', messages.visible ? 'chevron-up' : 'chevron-down']" fixed-width/>
|
||||
</button>
|
||||
</h2>
|
||||
<fold :visible="messages.visible">
|
||||
<messages @update="handleMessagesUpdate"></messages>
|
||||
</fold>
|
||||
</section>
|
||||
|
||||
<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 %}
|
||||
|
||||
{% block javascripts %}
|
||||
|
@ -8,7 +8,7 @@
|
||||
<title>{% block title %}Czy dojadę?{% endblock %}</title>
|
||||
</head>
|
||||
<body>
|
||||
<main role="main" class="container" id="app">
|
||||
<main role="main" class="container mt-5" id="app">
|
||||
{% block body %}{% endblock %}
|
||||
</main>
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user