better responsiveness

This commit is contained in:
Kacper Donat 2018-09-16 17:21:12 +02:00
parent 8c36299596
commit b6948ef0f1
12 changed files with 150 additions and 48 deletions

View File

@ -1,5 +1,5 @@
<div class="fold"> <div class="fold" :aria-expanded="visible ? 'true' : 'false'">
<div class="fold__inner" ref="inner"> <div class="fold__inner" ref="inner" :tabindex="visible ? false : -1">
<lazy v-if="lazy" :activate="visible"> <lazy v-if="lazy" :activate="visible">
<slot></slot> <slot></slot>
</lazy> </lazy>

View File

@ -1,4 +1,4 @@
<span class="line__symbol flex" :class="[`line--${line.type}`]"> <span class="line__symbol flex" :class="{ [`line--${line.type}`]: true, 'line--night': line.night, 'line--fast': line.fast }">
<span class="flex align-items-stretch"> <span class="flex align-items-stretch">
<span class="icon"> <span class="icon">
<fa :icon="['fac', line.type]" fixed-width></fa> <fa :icon="['fac', line.type]" fixed-width></fa>

View File

@ -1,6 +1,15 @@
<ul class="messages list-unstyled"> <ul class="messages list-unstyled">
<li class="messages__message alert" :class="`alert-${type(message)}`" v-for="message in messages"> <li class="message alert" :class="`alert-${type(message)}`" v-for="message in messages">
<fa :icon="icon(message)" fixed-width></fa> <fa :icon="icon(message)" fixed-width></fa>
{{ message.message }} {{ message.message }}
<div class="message__info">
<small class="message__date">
Komunikat ważny od
{{ message.validFrom.format('HH:mm') }}
do
{{ message.validTo.format('HH:mm') }}
</small>
</div>
</li> </li>
</ul> </ul>

View File

@ -1,37 +1,35 @@
<div v-if="ready"> <div v-if="ready" class="stop__details" v-responsive>
<div class="row"> <section>
<div class="col-md"> <strong>Linie:</strong>
<strong>Linie:</strong> <ul class="stop__lines list-unstyled list-inline">
<ul class="stop__lines list-unstyled list-inline"> <li v-for="line in lines" class="list-inline-item mb-2">
<li v-for="line in lines" class="list-inline-item mb-2"> <line-symbol :line="line"></line-symbol>
<line-symbol :line="line"></line-symbol> </li>
</li> </ul>
</ul>
<strong>Na mapie:</strong> <strong>Trasy:</strong>
<div style="height: 350px"> <ul class="stop__tracks list-underlined">
<l-map :center="stop.location" :zoom=17> <li v-for="{ track, order } in tracks" class="track">
<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> <div class="track__line">
<l-marker :lat-lng="stop.location"></l-marker> <line-symbol :line="track.line"></line-symbol>
</l-map> </div>
</div> <div class="track__description">
</div> {{ track.description }}
</div>
<span class="badge badge-pill badge-light track__order">#{{ order }}</span>
</li>
</ul>
</section>
<div class="col-md"> <section>
<strong>Trasy:</strong> <strong>Na mapie:</strong>
<ul class="stop__tracks list-underlined"> <div style="height: 350px" tabindex="-1">
<li v-for="{ track, order } in tracks" class="track"> <l-map :center="stop.location" :zoom=17>
<div class="track__line"> <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>
<line-symbol :line="track.line"></line-symbol> <l-marker :lat-lng="stop.location"></l-marker>
</div> </l-map>
<div class="track__description">
{{ track.description }}
</div>
<span class="badge badge-pill badge-light track__order">#{{ order }}</span>
</li>
</ul>
</div> </div>
</div> </section>
</div> </div>
<div v-else class="text-center"> <div v-else class="text-center">
<fa icon="spinner-third" pulse></fa> <fa icon="spinner-third" pulse></fa>

View File

@ -16,7 +16,7 @@
</slot> </slot>
</div> </div>
<fold :visible="details" class="stop__details" lazy> <fold :visible="details" class="stop__details-fold" lazy>
<stop-details :stop="stop"></stop-details> <stop-details :stop="stop"></stop-details>
</fold> </fold>

View File

@ -3,6 +3,10 @@
@extend .btn-link; @extend .btn-link;
color: black; color: black;
&:focus {
outline: 2px solid rgba($blue, .2);
}
} }
&.btn-outline-action { &.btn-outline-action {

View File

@ -8,9 +8,25 @@
padding: 0 .2rem; padding: 0 .2rem;
} }
&.line--#{$type} .icon { &.line--#{$type} {
background-color: $color; .icon {
color: color-yiq($color); background-color: $color;
color: color-yiq($color);
}
.badge {
background-color: transparent;
color: $color;
border: 1px solid $color;
}
}
&.line--night {
.badge {
background-color: $dark;
color: $white;
border: none;
}
} }
} }
} }

View File

@ -15,7 +15,7 @@
display: flex; display: flex;
} }
.stop__details { .stop__details-fold {
flex-basis: 100%; flex-basis: 100%;
.fold__inner { padding-bottom: .75rem; } .fold__inner { padding-bottom: .75rem; }
} }
@ -29,4 +29,21 @@
.stop__tracks .line__symbol .badge { .stop__tracks .line__symbol .badge {
flex-grow: 1; flex-grow: 1;
}
.stop__details {
display: flex;
flex-wrap: wrap;
margin: 0 (-$grid-gutter-width/2);
section {
flex: 0 0 100%;
padding: 0 $grid-gutter-width/2;
}
&.size-lg {
section {
flex: 0 0 50%;
}
}
} }

View File

@ -15,6 +15,11 @@ $line-types: (
'unknown': $dark 'unknown': $dark
); );
$default-spacing: .5rem;
$alert-margin-bottom: $default-spacing;
$headings-margin-bottom: $default-spacing;
@import "~bootstrap/scss/bootstrap"; @import "~bootstrap/scss/bootstrap";
@import "common"; @import "common";

View File

@ -4,6 +4,8 @@ import { Message } from "../model/message";
import urls from "../urls"; import urls from "../urls";
import { faInfoCircle, faExclamationTriangle, faQuestionCircle } from "@fortawesome/pro-light-svg-icons"; import { faInfoCircle, faExclamationTriangle, faQuestionCircle } from "@fortawesome/pro-light-svg-icons";
import { Jsonified } from "../utils";
import moment = require("moment");
@Component({ template: require("../../components/messages.html") }) @Component({ template: require("../../components/messages.html") })
export class MessagesComponent extends Vue { export class MessagesComponent extends Vue {
@ -18,7 +20,15 @@ export class MessagesComponent extends Vue {
const response = await fetch(urls.prepare(urls.messages)); const response = await fetch(urls.prepare(urls.messages));
if (response.ok) { if (response.ok) {
this.messages = await response.json(); const messages = (await response.json()) as Jsonified<Message>[];
this.messages = messages.map(m => {
const message = m as Message;
message.validFrom = moment(m.validFrom);
message.validTo = moment(m.validTo);
return message;
});
} }
this.$emit('update', this.messages); this.$emit('update', this.messages);

View File

@ -1,11 +1,11 @@
export interface Decorator<TArgs extends any[], FArgs extends any[], TRet, FRet> { export interface Decorator<TArgs extends any[], FArgs extends any[], TRet extends any, FRet extends any> {
decorate(f: (...farg: FArgs) => FRet, ...args: TArgs): (...farg: FArgs) => TRet; decorate(f: (...farg: FArgs) => any, ...args: TArgs): (...farg: FArgs) => TRet;
(...args: TArgs): (target, name: string | symbol, descriptor: TypedPropertyDescriptor<(...farg: FArgs) => FRet>) => void; (...args: TArgs): (target, name: string | symbol, descriptor: TypedPropertyDescriptor<(...farg: FArgs) => FRet>) => void;
} }
export function decorator<TArgs extends any[], FArgs extends any[], TRet, FRet> export function decorator<TArgs extends any[], FArgs extends any[], TRet extends any, FRet extends any>
(decorate: (f: (...fargs: FArgs) => FRet, ...args: TArgs) => (...fargs: FArgs) => TRet) (decorate: (f: (...farg: FArgs) => FRet, ...args: TArgs) => (...farg: FArgs) => TRet)
: Decorator<TArgs, FArgs, TRet, FRet> { : Decorator<TArgs, FArgs, TRet, FRet> {
const factory = function (this: Decorator<TArgs, FArgs, TRet, FRet>, ...args: TArgs) { const factory = function (this: Decorator<TArgs, FArgs, TRet, FRet>, ...args: TArgs) {
@ -43,3 +43,11 @@ export const debounce = decorator(function (decorated, time: number, max: number
}, time); }, time);
} }
}); });
export const condition = decorator(function <Args extends any[], Ret extends any>(decorated: (...args: Args) => Ret, predicate: (...args: Args) => boolean) {
return function (this: any, ...args: Args) {
if (predicate(...args)) {
return decorated(...args);
}
}
});

View File

@ -1,5 +1,6 @@
import { signed } from "./utils"; import { signed } from "./utils";
import Vue from 'vue'; import Vue from 'vue';
import { condition } from "./decorators";
Vue.filter('signed', signed); Vue.filter('signed', signed);
@ -18,6 +19,40 @@ Vue.directive('hover', (el, binding, node) => {
} }
}; };
el.addEventListener('mouseenter', e => update(true, e)); const activate = event => update(true, event);
el.addEventListener('mouseleave', e => update(false, e)); const deactivate = event => update(false, event);
});
el.addEventListener('mouseenter', activate);
el.addEventListener('click', activate);
el.addEventListener('keydown', condition.decorate(deactivate, e => e.keyCode == 27));
el.addEventListener('mouseleave', deactivate);
el.addEventListener('blur', deactivate);
});
Vue.directive('responsive', (el, binding) => {
const breakpoints = typeof binding.value === 'object' ? binding.value : {
'xs': 0,
'sm': 576,
'md': 768,
'lg': 1024,
'xl': 1200,
};
const resize = () => {
const width = el.scrollWidth;
el.classList.remove(...Object.keys(breakpoints).map(breakpoint => `size-${breakpoint}`));
for (let [ breakpoint, size ] of Object.entries(breakpoints)) {
if (width < size) {
break;
}
el.classList.add(`size-${breakpoint}`);
}
};
resize();
if (!binding.modifiers['once']) {
window.addEventListener('resize', resize);
}
});