stop details and track info
This commit is contained in:
parent
3e89c654ec
commit
58a19efe14
@ -41,11 +41,15 @@
|
||||
<span class="departure__estimated">{{ departure.estimated.format('HH:mm') }}</span>
|
||||
</div>
|
||||
|
||||
<stop :stop="departure.stop" class="departure__stop">
|
||||
<template slot="actions">
|
||||
<fa :icon="['fal', 'sign']" fixed-width class="mr-1"/>
|
||||
</template>
|
||||
</stop>
|
||||
<div class="departure__stop">
|
||||
<fa :icon="['fal', 'sign']" fixed-width class="mr-1"/>
|
||||
<stop :stop="departure.stop"></stop>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<div class="alert alert-info" v-if="stops.length === 0">
|
||||
<fa :icon="['fal', 'info-circle']"></fa>
|
||||
Wybierz przystanki korzystając z wyszukiwarki poniżej, aby zobaczyć listę odjazdów.
|
||||
</div>
|
||||
</div>
|
@ -20,15 +20,12 @@
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<ul class="stop-group__stops list-unstyled">
|
||||
<li v-for="stop in group" :key="stop.id">
|
||||
<stop :stop="stop">
|
||||
<template slot="actions">
|
||||
<button @click="select(stop, $event)" class="btn btn-action">
|
||||
<fa :icon="['fal', 'check']" />
|
||||
</button>
|
||||
</template>
|
||||
</stop>
|
||||
<ul class="stop-group__stops list-underlined">
|
||||
<li v-for="stop in group" :key="stop.id" class="d-flex">
|
||||
<button @click="select(stop, $event)" class="btn btn-action">
|
||||
<fa :icon="['fal', 'check']" />
|
||||
</button>
|
||||
<stop :stop="stop" class="flex-grow-1"></stop>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
6
resources/components/fold.html
Normal file
6
resources/components/fold.html
Normal file
@ -0,0 +1,6 @@
|
||||
<div class="collapse">
|
||||
<lazy v-if="lazy" :activate="visible">
|
||||
<slot></slot>
|
||||
</lazy>
|
||||
<slot v-else></slot>
|
||||
</div>
|
3
resources/components/lazy.html
Normal file
3
resources/components/lazy.html
Normal file
@ -0,0 +1,3 @@
|
||||
<keep-alive>
|
||||
<slot v-if="visible"></slot>
|
||||
</keep-alive>
|
@ -2,14 +2,11 @@
|
||||
<departures :stops="stops"/>
|
||||
|
||||
<ul class="picker__stops list-underlined">
|
||||
<li v-for="stop in stops" :key="stop.id">
|
||||
<stop :stop="stop">
|
||||
<template slot="actions">
|
||||
<button @click="remove(stop)" class="btn btn-action">
|
||||
<fa :icon="['fal', 'times']" />
|
||||
</button>
|
||||
</template>
|
||||
</stop>
|
||||
<li v-for="stop in stops" :key="stop.id" class="d-flex align-items-center">
|
||||
<button @click="remove(stop)" class="btn btn-action">
|
||||
<fa :icon="['fal', 'times']" />
|
||||
</button>
|
||||
<stop :stop="stop" class="flex-grow-1"></stop>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
|
4
resources/components/popper.html
Normal file
4
resources/components/popper.html
Normal file
@ -0,0 +1,4 @@
|
||||
<div class="popper" :class="{ 'has-arrow': arrow, 'd-none': !visible }">
|
||||
<div class="popper-arrow" ref="arrow" v-if="arrow"></div>
|
||||
<slot />
|
||||
</div>
|
43
resources/components/stop-details.html
Normal file
43
resources/components/stop-details.html
Normal file
@ -0,0 +1,43 @@
|
||||
<div v-if="ready">
|
||||
<div class="row">
|
||||
<div class="col-md">
|
||||
<strong>Linie:</strong>
|
||||
<ul class="stop__lines list-unstyled list-inline">
|
||||
<li v-for="line in lines" class="list-inline-item">
|
||||
<fa :icon="['fac', line.type]" fixed-width></fa>
|
||||
|
||||
<span class="badge badge-dark">
|
||||
<fa :icon="['fas', 'walking']" fixed-width v-if="line.fast"/>
|
||||
<fa :icon="['fal', 'moon']" fixed-width v-if="line.night"/>
|
||||
|
||||
{{ line.symbol }}
|
||||
</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="col-md">
|
||||
<strong>Trasy:</strong>
|
||||
<ul class="stop__tracks list-underlined">
|
||||
<li v-for="{ track, order } in tracks" class="track">
|
||||
<div class="track__line">
|
||||
<fa :icon="['fac', track.line.type]" fixed-width></fa>
|
||||
<span class="badge badge-dark">
|
||||
<fa :icon="['fas', 'walking']" fixed-width v-if="track.line.fast"/>
|
||||
<fa :icon="['fal', 'moon']" fixed-width v-if="track.line.night"/>
|
||||
|
||||
{{ track.line.symbol }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="track__description">
|
||||
{{ track.description }}
|
||||
</div>
|
||||
<span class="badge badge-pill badge-light track__order">#{{ order }}</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else class="text-center">
|
||||
<fa icon="spinner-third" pulse></fa>
|
||||
</div>
|
@ -1,19 +1,22 @@
|
||||
<div class="stop">
|
||||
<div class="stop__actions">
|
||||
<slot name="actions"/>
|
||||
</div>
|
||||
|
||||
<span class="stop__name">{{ stop.name }}</span>
|
||||
<span class="stop__description badge badge-dark" v-if="stop.variant">{{ stop.variant }}</span>
|
||||
|
||||
<slot/>
|
||||
|
||||
<div class="stop__actions flex-space-left">
|
||||
<button class="btn btn-action">
|
||||
<fa :icon="['fal', 'info-circle']"/>
|
||||
</button>
|
||||
<slot name="actions">
|
||||
<button class="btn btn-action" ref="action-info" @click="details = !details">
|
||||
<fa :icon="['fal', details ? 'chevron-circle-up' : 'info-circle']"/>
|
||||
</button>
|
||||
|
||||
<button class="btn btn-action">
|
||||
<fa :icon="['fal', 'map-marked-alt']"/>
|
||||
</button>
|
||||
<button class="btn btn-action" ref="action-map">
|
||||
<fa :icon="['fal', 'map-marked-alt']"/>
|
||||
</button>
|
||||
</slot>
|
||||
</div>
|
||||
|
||||
<fold :visible="details" class="stop__details" lazy>
|
||||
<stop-details :stop="stop"></stop-details>
|
||||
</fold>
|
||||
</div>
|
@ -1,7 +1,7 @@
|
||||
.list-underlined {
|
||||
@extend .list-unstyled;
|
||||
|
||||
li {
|
||||
> li {
|
||||
border-bottom: 1px solid $text-muted;
|
||||
}
|
||||
}
|
||||
@ -29,3 +29,12 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.collapse {
|
||||
padding-bottom: .5rem;
|
||||
}
|
||||
|
||||
%flex {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
@ -8,7 +8,12 @@
|
||||
}
|
||||
|
||||
.departure__stop {
|
||||
@extend %flex;
|
||||
width: 100%;
|
||||
|
||||
.stop {
|
||||
flex: 1 1 auto;
|
||||
}
|
||||
}
|
||||
|
||||
.departure__time {
|
||||
|
@ -1,8 +1,23 @@
|
||||
.line {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
@extend %flex;
|
||||
|
||||
.line__symbol {
|
||||
min-width: 4rem;
|
||||
}
|
||||
}
|
||||
|
||||
.track {
|
||||
@extend %flex;
|
||||
flex-shrink: 0;
|
||||
flex-grow: 0;
|
||||
|
||||
.track__line {
|
||||
width: 6rem;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.track__description {
|
||||
flex: 1 1 auto;
|
||||
white-space: normal;
|
||||
}
|
||||
}
|
108
resources/styles/_popper.scss
Normal file
108
resources/styles/_popper.scss
Normal file
@ -0,0 +1,108 @@
|
||||
@mixin triangle-border($color, $orientation, $size) {
|
||||
border-width:
|
||||
if($orientation == top, 0, $size)
|
||||
if($orientation == right, 0, $size)
|
||||
if($orientation == bottom, 0, $size)
|
||||
if($orientation == left, 0, $size)
|
||||
;
|
||||
|
||||
border-color:
|
||||
if($orientation == bottom, $color, transparent)
|
||||
if($orientation == left, $color, transparent)
|
||||
if($orientation == top, $color, transparent)
|
||||
if($orientation == right, $color, transparent)
|
||||
;
|
||||
|
||||
#{$orientation}: -$size;
|
||||
@if ($orientation == top) or ($orientation == bottom) {
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
} @else {
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
}
|
||||
}
|
||||
|
||||
@mixin triangle($orientation, $size, $color, $border: none) {
|
||||
background: $color;
|
||||
|
||||
&::after {
|
||||
width: 0;
|
||||
height: 0;
|
||||
border-style: solid;
|
||||
content: "";
|
||||
position: absolute;
|
||||
|
||||
@include triangle-border($color, $orientation, $size);
|
||||
}
|
||||
|
||||
@if $border != none {
|
||||
&::before {
|
||||
width: 0;
|
||||
height: 0;
|
||||
border-style: solid;
|
||||
content: "";
|
||||
position: absolute;
|
||||
|
||||
@include triangle-border($border, $orientation, $size + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@mixin triangle-top($size, $color, $border: none) { @include triangle(top, $size, $color, $border); }
|
||||
@mixin triangle-bottom($size, $color, $border: none) { @include triangle(bottom, $size, $color, $border); }
|
||||
@mixin triangle-left($size, $color, $border: none) { @include triangle(left, $size, $color, $border); }
|
||||
@mixin triangle-right($size, $color, $border: none) { @include triangle(right, $size, $color, $border); }
|
||||
|
||||
.popper {
|
||||
$arrow-base: 10px;
|
||||
$arrow-color: white;
|
||||
$arrow-border: black;
|
||||
|
||||
$popper-padding: .5rem;
|
||||
|
||||
padding: $popper-padding;
|
||||
background: white;
|
||||
border: 1px solid black;
|
||||
z-index: 1000;
|
||||
position: relative;
|
||||
|
||||
max-width: 500px;
|
||||
min-width: 200px;
|
||||
|
||||
border-radius: 2px;
|
||||
|
||||
.popper-arrow {
|
||||
position: absolute;
|
||||
width: 0;
|
||||
height: 0;
|
||||
}
|
||||
|
||||
@mixin placement($placement) {
|
||||
$opposite: (
|
||||
left: right,
|
||||
right: left,
|
||||
top: bottom,
|
||||
bottom: top
|
||||
);
|
||||
|
||||
&[x-placement^="#{$placement}"] {
|
||||
margin-#{map-get($opposite, $placement)}: $arrow-base;
|
||||
|
||||
.popper-arrow {
|
||||
#{map-get($opposite, $placement)}: 0;
|
||||
@include triangle(map-get($opposite, $placement), $arrow-base, $arrow-color, $arrow-border);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.has-arrow {
|
||||
@include placement("left");
|
||||
@include placement("right");
|
||||
@include placement("top");
|
||||
@include placement("bottom");
|
||||
}
|
||||
|
||||
animation: ease-in fade-in 150ms
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
.stop, .stop-group__header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
@extend %flex;
|
||||
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.stop__name {
|
||||
@ -14,6 +15,9 @@
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.stop__details {
|
||||
flex-basis: 100%;
|
||||
}
|
||||
|
||||
.stop-group__name {
|
||||
font-size: $font-size-base;
|
||||
|
@ -15,3 +15,4 @@ $custom-control-indicator-active-bg: $dark;
|
||||
@import "departure";
|
||||
@import "line";
|
||||
@import "controls";
|
||||
@import "popper";
|
@ -1,4 +1,3 @@
|
||||
/// <reference path="types/popper.js.d.ts"/>
|
||||
/// <reference path="types/webpack.d.ts"/>
|
||||
|
||||
import '../styles/main.scss'
|
||||
@ -6,13 +5,13 @@ import '../styles/main.scss'
|
||||
import './font-awesome'
|
||||
import './filters'
|
||||
|
||||
import * as Popper from 'popper.js';
|
||||
import Popper from 'popper.js';
|
||||
import * as $ from "jquery";
|
||||
|
||||
import * as components from './components';
|
||||
|
||||
window['$'] = window['jQuery'] = $;
|
||||
window['popper'] = Popper;
|
||||
window['Popper'] = Popper;
|
||||
|
||||
// dependencies
|
||||
import 'bootstrap'
|
||||
|
@ -7,7 +7,7 @@ import moment = require("moment");
|
||||
import { Jsonified } from "../utils";
|
||||
import { debounce } from "../decorators";
|
||||
|
||||
@Component({template})
|
||||
@Component({ template })
|
||||
export class Departures extends Vue {
|
||||
private _intervalId: number;
|
||||
|
||||
|
@ -1,2 +1,4 @@
|
||||
export * from './utils'
|
||||
export * from './picker'
|
||||
export * from './departures'
|
||||
export * from './departures'
|
||||
export * from './stop'
|
@ -70,12 +70,5 @@ export class FinderComponent extends Vue {
|
||||
}
|
||||
}
|
||||
|
||||
@Component({ template: stop })
|
||||
export class StopComponent extends Vue {
|
||||
@Prop(Object)
|
||||
public stop: Stop;
|
||||
}
|
||||
|
||||
Vue.component('StopPicker', PickerComponent);
|
||||
Vue.component('StopFinder', FinderComponent);
|
||||
Vue.component('Stop', StopComponent);
|
||||
|
50
resources/ts/components/stop.ts
Normal file
50
resources/ts/components/stop.ts
Normal file
@ -0,0 +1,50 @@
|
||||
import { Component, Prop } from "vue-property-decorator";
|
||||
import { Line, Stop, Track } from "../model";
|
||||
import Vue from 'vue';
|
||||
|
||||
import template = require('../../components/stop.html');
|
||||
import details = require('../../components/stop-details.html');
|
||||
import urls from "../urls";
|
||||
|
||||
@Component({ template: details })
|
||||
class StopDetailsComponent extends Vue {
|
||||
@Prop(Object)
|
||||
public stop: Stop;
|
||||
|
||||
private ready: boolean = false;
|
||||
|
||||
tracks: { order: number, track: Track }[] = [];
|
||||
|
||||
get types() {
|
||||
return this.tracks.map(t => t.track.line.type).filter((value, index, array) => {
|
||||
return array.indexOf(value) === index;
|
||||
});
|
||||
}
|
||||
|
||||
get lines(): Line[] {
|
||||
return this.tracks.map(t => t.track.line).reduce((lines, line: Line) => {
|
||||
return Object.assign(lines, { [line.symbol]: line });
|
||||
}, {} as any);
|
||||
}
|
||||
|
||||
async mounted() {
|
||||
const response = await fetch(urls.prepare(urls.stops.tracks, { id: this.stop.id }));
|
||||
|
||||
if (response.ok) {
|
||||
this.tracks = await response.json();
|
||||
}
|
||||
|
||||
this.ready = true;
|
||||
}
|
||||
}
|
||||
|
||||
@Component({ template })
|
||||
export class StopComponent extends Vue {
|
||||
@Prop(Object)
|
||||
public stop: Stop;
|
||||
|
||||
details: boolean = false;
|
||||
}
|
||||
|
||||
Vue.component('Stop', StopComponent);
|
||||
Vue.component('StopDetails', StopDetailsComponent);
|
72
resources/ts/components/utils.ts
Normal file
72
resources/ts/components/utils.ts
Normal file
@ -0,0 +1,72 @@
|
||||
import Vue from 'vue';
|
||||
import { Component, Prop, Watch } from "vue-property-decorator";
|
||||
import Popper, { Placement } from "popper.js";
|
||||
import * as $ from 'jquery';
|
||||
|
||||
import popper = require("../../components/popper.html");
|
||||
import lazy = require("../../components/lazy.html");
|
||||
import collapse = require("../../components/fold.html");
|
||||
|
||||
@Component({ template: popper })
|
||||
export class PopperComponent extends Vue {
|
||||
@Prop(String)
|
||||
public reference: string;
|
||||
|
||||
@Prop({ type: String, default: "auto" })
|
||||
public placement: Placement;
|
||||
|
||||
@Prop(Boolean)
|
||||
public arrow: boolean;
|
||||
|
||||
@Prop({ type: Boolean, default: false })
|
||||
public visible: boolean;
|
||||
|
||||
private _popper;
|
||||
|
||||
mounted() {
|
||||
const reference = this.$parent.$refs[this.reference] as HTMLElement;
|
||||
|
||||
this._popper = new Popper(reference, this.$el, {
|
||||
placement: this.placement,
|
||||
modifiers: {
|
||||
arrow: { enabled: this.arrow, element: this.$refs['arrow'] as Element }
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Watch('visible')
|
||||
private onVisibilityUpdate() {
|
||||
this._popper.update();
|
||||
}
|
||||
}
|
||||
|
||||
@Component({ template: collapse })
|
||||
export class FoldComponent extends Vue {
|
||||
@Prop(Boolean)
|
||||
public visible: boolean;
|
||||
|
||||
@Prop(Boolean)
|
||||
public lazy: boolean;
|
||||
|
||||
@Watch('visible')
|
||||
private onVisibilityChange(value) {
|
||||
const action = () => $(this.$el).collapse(value ? 'show' : 'hide');
|
||||
setTimeout(action);
|
||||
}
|
||||
}
|
||||
|
||||
@Component({ template: lazy })
|
||||
export class LazyComponent extends Vue {
|
||||
@Prop(Boolean)
|
||||
public activate: boolean;
|
||||
protected visible: boolean = false;
|
||||
|
||||
@Watch('activate')
|
||||
private onVisibilityChange(value, old) {
|
||||
this.visible = value || old;
|
||||
}
|
||||
}
|
||||
|
||||
Vue.component('Popper', PopperComponent);
|
||||
Vue.component('Fold', FoldComponent);
|
||||
Vue.component('Lazy', LazyComponent);
|
@ -1,4 +1,8 @@
|
||||
import { signed } from "./utils";
|
||||
import Vue from 'vue';
|
||||
|
||||
Vue.filter('signed', signed);
|
||||
Vue.filter('signed', signed);
|
||||
Vue.directive('hover', (el, binding) => {
|
||||
el.addEventListener('mouseenter', binding.value);
|
||||
el.addEventListener('mouseleave', binding.value);
|
||||
});
|
@ -6,4 +6,10 @@ export interface Line {
|
||||
type: LineType;
|
||||
night: boolean;
|
||||
fast: boolean;
|
||||
}
|
||||
|
||||
export interface Track {
|
||||
id: string;
|
||||
description: string;
|
||||
line: Line;
|
||||
}
|
131
resources/ts/types/popper.js.d.ts
vendored
131
resources/ts/types/popper.js.d.ts
vendored
@ -1,131 +0,0 @@
|
||||
declare class Popper {
|
||||
static modifiers: (Popper.BaseModifier & { name: string })[];
|
||||
static placements: Popper.Placement[];
|
||||
static Defaults: Popper.PopperOptions;
|
||||
|
||||
options: Popper.PopperOptions;
|
||||
|
||||
constructor(reference: Element | Popper.ReferenceObject, popper: Element, options?: Popper.PopperOptions);
|
||||
|
||||
destroy(): void;
|
||||
update(): void;
|
||||
scheduleUpdate(): void;
|
||||
enableEventListeners(): void;
|
||||
disableEventListeners(): void;
|
||||
}
|
||||
|
||||
declare namespace Popper {
|
||||
export type Position = 'top' | 'right' | 'bottom' | 'left';
|
||||
|
||||
export type Placement = 'auto-start'
|
||||
| 'auto'
|
||||
| 'auto-end'
|
||||
| 'top-start'
|
||||
| 'top'
|
||||
| 'top-end'
|
||||
| 'right-start'
|
||||
| 'right'
|
||||
| 'right-end'
|
||||
| 'bottom-end'
|
||||
| 'bottom'
|
||||
| 'bottom-start'
|
||||
| 'left-end'
|
||||
| 'left'
|
||||
| 'left-start';
|
||||
|
||||
export type Boundary = 'scrollParent' | 'viewport' | 'window';
|
||||
|
||||
export type Behavior = 'flip' | 'clockwise' | 'counterclockwise';
|
||||
|
||||
export type ModifierFn = (data: Data, options: Object) => Data;
|
||||
|
||||
export interface BaseModifier {
|
||||
order?: number;
|
||||
enabled?: boolean;
|
||||
fn?: ModifierFn;
|
||||
}
|
||||
|
||||
export interface Modifiers {
|
||||
shift?: BaseModifier;
|
||||
offset?: BaseModifier & {
|
||||
offset?: number | string,
|
||||
};
|
||||
preventOverflow?: BaseModifier & {
|
||||
priority?: Position[],
|
||||
padding?: number,
|
||||
boundariesElement?: Boundary | Element,
|
||||
escapeWithReference?: boolean
|
||||
};
|
||||
keepTogether?: BaseModifier;
|
||||
arrow?: BaseModifier & {
|
||||
element?: string | Element,
|
||||
};
|
||||
flip?: BaseModifier & {
|
||||
behavior?: Behavior | Position[],
|
||||
padding?: number,
|
||||
boundariesElement?: Boundary | Element,
|
||||
};
|
||||
inner?: BaseModifier;
|
||||
hide?: BaseModifier;
|
||||
applyStyle?: BaseModifier & {
|
||||
onLoad?: Function,
|
||||
gpuAcceleration?: boolean,
|
||||
};
|
||||
computeStyle?: BaseModifier & {
|
||||
gpuAcceleration?: boolean;
|
||||
x?: 'bottom' | 'top',
|
||||
y?: 'left' | 'right'
|
||||
};
|
||||
|
||||
[name: string]: (BaseModifier & Record<string, any>) | undefined;
|
||||
}
|
||||
|
||||
export interface Offset {
|
||||
top: number;
|
||||
left: number;
|
||||
width: number;
|
||||
height: number;
|
||||
}
|
||||
|
||||
export interface Data {
|
||||
instance: Popper;
|
||||
placement: Placement;
|
||||
originalPlacement: Placement;
|
||||
flipped: boolean;
|
||||
hide: boolean;
|
||||
arrowElement: Element;
|
||||
styles: CSSStyleDeclaration;
|
||||
boundaries: Object;
|
||||
offsets: {
|
||||
popper: Offset,
|
||||
reference: Offset,
|
||||
arrow: {
|
||||
top: number,
|
||||
left: number,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export interface PopperOptions {
|
||||
placement?: Placement;
|
||||
positionFixed?: boolean;
|
||||
eventsEnabled?: boolean;
|
||||
modifiers?: Modifiers;
|
||||
removeOnDestroy?: boolean;
|
||||
|
||||
onCreate?(data: Data): void;
|
||||
|
||||
onUpdate?(data: Data): void;
|
||||
}
|
||||
|
||||
export interface ReferenceObject {
|
||||
clientHeight: number;
|
||||
clientWidth: number;
|
||||
|
||||
getBoundingClientRect(): ClientRect;
|
||||
}
|
||||
}
|
||||
|
||||
declare module "popper.js" {
|
||||
export = Popper;
|
||||
}
|
@ -52,7 +52,8 @@ export default {
|
||||
stops: {
|
||||
all: `${base}/stops`,
|
||||
search: `${base}/stops/search`,
|
||||
get: `${base}/stops/{id}`
|
||||
get: `${base}/stops/{id}`,
|
||||
tracks: `${base}/stops/{id}/tracks`
|
||||
},
|
||||
prepare: (url: string, params: UrlParams = { }) => prepare(url, Object.assign({}, { provider: window['app'].provider }, params))
|
||||
}
|
@ -7,6 +7,7 @@ type Simplify<T, K = any> = string |
|
||||
|
||||
export type Jsonified<T> = { [K in keyof T]: Simplify<T[K]> }
|
||||
export type Optionalify<T> = { [K in keyof T]?: T[K] }
|
||||
export type Dictionary<T> = { [key: string]: T };
|
||||
|
||||
export type Index = string | symbol | number;
|
||||
|
||||
|
@ -4,7 +4,10 @@
|
||||
namespace App\Controller\Api\v1;
|
||||
|
||||
use App\Controller\Controller;
|
||||
use App\Model\Stop;
|
||||
use App\Provider\StopRepository;
|
||||
use App\Provider\TrackRepository;
|
||||
use App\Service\Proxy\ReferenceFactory;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\Routing\Annotation\Route;
|
||||
|
||||
@ -47,4 +50,16 @@ class StopsController extends Controller
|
||||
{
|
||||
return $this->json($stops->getById($id));
|
||||
}
|
||||
|
||||
/**
|
||||
* @Route("/{id}/tracks", methods={"GET"})
|
||||
*/
|
||||
public function tracks(ReferenceFactory $reference, TrackRepository $tracks, $id)
|
||||
{
|
||||
$stop = $reference->get(Stop::class, $id);
|
||||
|
||||
return $this->json($tracks->getByStop($stop)->map(function ($tuple) {
|
||||
return array_combine(['track', 'order'], $tuple);
|
||||
}));
|
||||
}
|
||||
}
|
@ -45,7 +45,7 @@ class TrackEntity implements Entity, Fillable
|
||||
/**
|
||||
* Stops in track
|
||||
* @var Collection
|
||||
* @ORM\OneToMany(targetEntity=StopInTrack::class, mappedBy="track", cascade={"persist"})
|
||||
* @ORM\OneToMany(targetEntity=StopInTrack::class, fetch="EXTRA_LAZY", mappedBy="track", cascade={"persist"})
|
||||
* @ORM\OrderBy({"order": "ASC"})
|
||||
*/
|
||||
private $stopsInTrack;
|
||||
@ -104,9 +104,4 @@ class TrackEntity implements Entity, Fillable
|
||||
{
|
||||
$this->stopsInTrack = new ArrayCollection($stopsInTrack);
|
||||
}
|
||||
|
||||
public function getStops()
|
||||
{
|
||||
return $this->getStopsInTrack()->map(t\property('stop'));
|
||||
}
|
||||
}
|
@ -4,6 +4,7 @@ namespace App\Provider\Database;
|
||||
|
||||
use App\Entity\Entity;
|
||||
use App\Entity\ProviderEntity;
|
||||
use App\Model\Referable;
|
||||
use App\Service\EntityConverter;
|
||||
use App\Service\IdUtils;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
@ -48,4 +49,11 @@ class DatabaseRepository
|
||||
{
|
||||
return $this->converter->convert($entity);
|
||||
}
|
||||
|
||||
protected function reference($class, Referable $referable)
|
||||
{
|
||||
$id = $this->id->generate($this->provider, $referable->getId());
|
||||
|
||||
return $this->em->getReference($class, $id);
|
||||
}
|
||||
}
|
61
src/Provider/Database/GenericTrackRepository.php
Normal file
61
src/Provider/Database/GenericTrackRepository.php
Normal file
@ -0,0 +1,61 @@
|
||||
<?php
|
||||
|
||||
namespace App\Provider\Database;
|
||||
|
||||
use App\Entity\LineEntity;
|
||||
use App\Entity\StopEntity;
|
||||
use App\Entity\StopInTrack;
|
||||
use App\Entity\TrackEntity;
|
||||
use App\Model\Track;
|
||||
use App\Provider\TrackRepository;
|
||||
use Tightenco\Collect\Support\Collection;
|
||||
use Kadet\Functional as f;
|
||||
|
||||
class GenericTrackRepository extends DatabaseRepository implements TrackRepository
|
||||
{
|
||||
public function getAll(): Collection
|
||||
{
|
||||
$tracks = $this->em->getRepository(TrackEntity::class)->findAll();
|
||||
|
||||
return collect($tracks)->map(f\ref([$this, 'convert']));
|
||||
}
|
||||
|
||||
public function getById($id): Track
|
||||
{
|
||||
// TODO: Implement getById() method.
|
||||
}
|
||||
|
||||
public function getManyById($ids): Collection
|
||||
{
|
||||
// TODO: Implement getManyById() method.
|
||||
}
|
||||
|
||||
public function getByStop($stop): Collection
|
||||
{
|
||||
$tracks = $this->em->createQueryBuilder()
|
||||
->from(StopInTrack::class, 'st')
|
||||
->join('st.track', 't')
|
||||
->where('st.stop = :stop')
|
||||
->select(['st', 't'])
|
||||
->getQuery()
|
||||
->execute(['stop' => $this->reference(StopEntity::class, $stop)]);
|
||||
|
||||
return collect($tracks)->map(function (StopInTrack $entity) {
|
||||
return [ $this->convert($entity->getTrack()), $entity->getOrder() ];
|
||||
});
|
||||
}
|
||||
|
||||
public function getByLine($line): Collection
|
||||
{
|
||||
$tracks = $this->em->createQueryBuilder()
|
||||
->from(StopInTrack::class, 'st')
|
||||
->join('st.track', 't')
|
||||
->join('t.stops', 's')
|
||||
->where('st.line = :line')
|
||||
->select(['st', 't', 's'])
|
||||
->getQuery()
|
||||
->execute(['stop' => $this->reference(LineEntity::class, $line)]);
|
||||
|
||||
return collect($tracks)->map(f\ref([$this, 'convert']));
|
||||
}
|
||||
}
|
@ -8,6 +8,7 @@ interface Provider
|
||||
public function getLineRepository(): LineRepository;
|
||||
public function getStopRepository(): StopRepository;
|
||||
public function getMessageRepository(): MessageRepository;
|
||||
public function getTrackRepository(): TrackRepository;
|
||||
|
||||
public function getName();
|
||||
public function getIdentifier();
|
||||
|
17
src/Provider/TrackRepository.php
Normal file
17
src/Provider/TrackRepository.php
Normal file
@ -0,0 +1,17 @@
|
||||
<?php
|
||||
|
||||
namespace App\Provider;
|
||||
|
||||
use App\Model\Track;
|
||||
use Tightenco\Collect\Support\Collection;
|
||||
|
||||
interface TrackRepository
|
||||
{
|
||||
public function getAll(): Collection;
|
||||
|
||||
public function getById($id): Track;
|
||||
public function getManyById($ids): Collection;
|
||||
|
||||
public function getByStop($stop): Collection;
|
||||
public function getByLine($line): Collection;
|
||||
}
|
@ -159,8 +159,8 @@ class ZtmGdanskDataUpdateSubscriber implements EventSubscriberInterface
|
||||
$this->logger->info(sprintf('Saving %d tracks from ZTM Gdańsk', count($stops)));
|
||||
|
||||
return collect($tracks)->map(function ($track) use ($provider, $stops) {
|
||||
$track = TrackEntity::createFromArray([
|
||||
'id' => $track['id'],
|
||||
$entity = TrackEntity::createFromArray([
|
||||
'id' => $this->ids->generate($provider, $track['id']),
|
||||
'line' => $this->em->getReference(
|
||||
LineEntity::class,
|
||||
$this->ids->generate($provider, $track['routeId'])
|
||||
@ -169,20 +169,20 @@ class ZtmGdanskDataUpdateSubscriber implements EventSubscriberInterface
|
||||
'provider' => $provider,
|
||||
]);
|
||||
|
||||
$stops = $stops->get($track->getId())->map(function ($stop) use ($track, $provider) {
|
||||
$stops = $stops->get($track['id'])->map(function ($stop) use ($entity, $provider) {
|
||||
return StopInTrack::createFromArray([
|
||||
'stop' => $this->em->getReference(
|
||||
StopEntity::class,
|
||||
$this->ids->generate($provider, $stop['stopId'])
|
||||
),
|
||||
'track' => $track,
|
||||
'track' => $entity,
|
||||
'order' => $stop['stopSequence'],
|
||||
]);
|
||||
});
|
||||
|
||||
$track->setStopsInTrack($stops->all());
|
||||
$entity->setStopsInTrack($stops->all());
|
||||
|
||||
return $track;
|
||||
return $entity;
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -6,6 +6,7 @@ namespace App\Provider;
|
||||
use App\Entity\ProviderEntity;
|
||||
use App\Provider\Database\GenericLineRepository;
|
||||
use App\Provider\Database\GenericStopRepository;
|
||||
use App\Provider\Database\GenericTrackRepository;
|
||||
use App\Provider\ZtmGdansk\{ZtmGdanskDepartureRepository, ZtmGdanskMessageRepository};
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
|
||||
@ -14,6 +15,7 @@ class ZtmGdanskProvider implements Provider
|
||||
private $lines;
|
||||
private $departures;
|
||||
private $stops;
|
||||
private $tracks;
|
||||
private $messages;
|
||||
|
||||
public function getName()
|
||||
@ -30,17 +32,20 @@ class ZtmGdanskProvider implements Provider
|
||||
EntityManagerInterface $em,
|
||||
GenericLineRepository $lines,
|
||||
GenericStopRepository $stops,
|
||||
GenericTrackRepository $tracks,
|
||||
ZtmGdanskMessageRepository $messages
|
||||
) {
|
||||
$provider = $em->getReference(ProviderEntity::class, $this->getIdentifier());
|
||||
|
||||
$lines = $lines->withProvider($provider);
|
||||
$stops = $stops->withProvider($provider);
|
||||
$lines = $lines->withProvider($provider);
|
||||
$stops = $stops->withProvider($provider);
|
||||
$tracks = $tracks->withProvider($provider);
|
||||
|
||||
$this->lines = $lines;
|
||||
$this->departures = new ZtmGdanskDepartureRepository($lines);
|
||||
$this->stops = $stops;
|
||||
$this->messages = $messages;
|
||||
$this->tracks = $tracks;
|
||||
}
|
||||
|
||||
public function getDepartureRepository(): DepartureRepository
|
||||
@ -62,4 +67,9 @@ class ZtmGdanskProvider implements Provider
|
||||
{
|
||||
return $this->messages;
|
||||
}
|
||||
|
||||
public function getTrackRepository(): TrackRepository
|
||||
{
|
||||
return $this->tracks;
|
||||
}
|
||||
}
|
@ -11,10 +11,11 @@ use App\Model\Line;
|
||||
use App\Model\Operator;
|
||||
use App\Model\Stop;
|
||||
use App\Model\Track;
|
||||
use App\Service\Proxy\ReferenceObjectFactory;
|
||||
use App\Service\Proxy\ReferenceFactory;
|
||||
use Doctrine\ORM\PersistentCollection;
|
||||
use Doctrine\ORM\Proxy\Proxy;
|
||||
use Kadet\Functional as f;
|
||||
use Kadet\Functional\Transforms as t;
|
||||
use const Kadet\Functional\_;
|
||||
|
||||
final class EntityConverter
|
||||
@ -22,7 +23,7 @@ final class EntityConverter
|
||||
private $id;
|
||||
private $reference;
|
||||
|
||||
public function __construct(IdUtils $id, ReferenceObjectFactory $reference)
|
||||
public function __construct(IdUtils $id, ReferenceFactory $reference)
|
||||
{
|
||||
$this->id = $id;
|
||||
$this->reference = $reference;
|
||||
@ -66,7 +67,7 @@ final class EntityConverter
|
||||
'operator' => $convert($entity->getOperator()),
|
||||
'night' => $entity->isNight(),
|
||||
'fast' => $entity->isFast(),
|
||||
'tracks' => $this->collection($entity->getTracks(), $convert),
|
||||
'tracks' => $this->collection($entity->getTracks())->map($convert),
|
||||
]);
|
||||
break;
|
||||
|
||||
@ -74,7 +75,9 @@ final class EntityConverter
|
||||
$result->fill([
|
||||
'variant' => $entity->getVariant(),
|
||||
'description' => $entity->getDescription(),
|
||||
'stops' => $this->collection($entity->getStops(), $convert),
|
||||
'stops' => $this->collection($entity->getStopsInTrack())
|
||||
->map(t\property('stop'))
|
||||
->map($convert),
|
||||
'line' => $convert($entity->getLine()),
|
||||
]);
|
||||
break;
|
||||
@ -89,6 +92,7 @@ final class EntityConverter
|
||||
$entity->getLongitude(),
|
||||
],
|
||||
]);
|
||||
break;
|
||||
}
|
||||
|
||||
return $result;
|
||||
@ -106,10 +110,10 @@ final class EntityConverter
|
||||
return $entity->getId();
|
||||
}
|
||||
|
||||
private function collection(PersistentCollection $collection, $converter)
|
||||
private function collection($collection)
|
||||
{
|
||||
if ($collection->isInitialized()) {
|
||||
return collect($collection)->map($converter);
|
||||
if (!$collection instanceof PersistentCollection || $collection->isInitialized()) {
|
||||
return collect($collection);
|
||||
}
|
||||
|
||||
return collect();
|
||||
|
@ -26,7 +26,7 @@ class JustReferenceNormalizer implements NormalizerInterface
|
||||
*/
|
||||
public function normalize($object, $format = null, array $context = [])
|
||||
{
|
||||
return [ 'id' => $object->getId() ];
|
||||
return [ 'id' => $object->getId(), 'href' => '#' ];
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -5,7 +5,7 @@ namespace App\Service\Proxy;
|
||||
use ProxyManager\Factory\AbstractBaseFactory;
|
||||
use ProxyManager\ProxyGenerator\ProxyGeneratorInterface;
|
||||
|
||||
class ReferenceObjectFactory extends AbstractBaseFactory
|
||||
class ReferenceFactory extends AbstractBaseFactory
|
||||
{
|
||||
public function get($class, $id)
|
||||
{
|
@ -9,6 +9,7 @@ use App\Provider\DepartureRepository;
|
||||
use App\Provider\LineRepository;
|
||||
use App\Provider\MessageRepository;
|
||||
use App\Provider\StopRepository;
|
||||
use App\Provider\TrackRepository;
|
||||
use const Kadet\Functional\_;
|
||||
use function Kadet\Functional\any;
|
||||
use function Kadet\Functional\curry;
|
||||
@ -59,6 +60,10 @@ class RepositoryParameterConverter implements ParamConverterInterface
|
||||
$request->attributes->set($configuration->getName(), $provider->getMessageRepository());
|
||||
break;
|
||||
|
||||
case is_a($class, TrackRepository::class, true):
|
||||
$request->attributes->set($configuration->getName(), $provider->getTrackRepository());
|
||||
break;
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
@ -75,7 +80,8 @@ class RepositoryParameterConverter implements ParamConverterInterface
|
||||
StopRepository::class,
|
||||
LineRepository::class,
|
||||
DepartureRepository::class,
|
||||
MessageRepository::class
|
||||
MessageRepository::class,
|
||||
TrackRepository::class,
|
||||
]));
|
||||
|
||||
return $supports($configuration->getClass());
|
||||
|
@ -1,5 +1,6 @@
|
||||
{% extends 'base.html.twig' %}
|
||||
{% block title "#{parent()} - #{provider.name}" %}
|
||||
|
||||
{% block body %}
|
||||
<stop-picker></stop-picker>
|
||||
{% endblock %}
|
||||
|
Loading…
Reference in New Issue
Block a user