new layout

This commit is contained in:
Kacper Donat 2018-09-17 22:46:02 +02:00
parent 1c7a1e8669
commit f8c3f77c83
15 changed files with 137 additions and 71 deletions

View File

@ -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>

View File

@ -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>

View File

@ -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">

View File

@ -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='&copy; <a href="http://osm.org/copyright">OpenStreetMap</a> contributors'></l-tile-layer>

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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";

View File

@ -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) {

View File

@ -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';
}
}

View File

@ -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})

View File

@ -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);
}
}
});

View File

@ -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);

View File

@ -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 ];
}

View File

@ -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 %}

View File

@ -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>