stop details and track info

This commit is contained in:
Kacper Donat 2018-09-10 21:43:13 +02:00
parent 3e89c654ec
commit 58a19efe14
38 changed files with 520 additions and 206 deletions

View File

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

View File

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

View File

@ -0,0 +1,6 @@
<div class="collapse">
<lazy v-if="lazy" :activate="visible">
<slot></slot>
</lazy>
<slot v-else></slot>
</div>

View File

@ -0,0 +1,3 @@
<keep-alive>
<slot v-if="visible"></slot>
</keep-alive>

View File

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

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

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

View File

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

View File

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

View File

@ -8,7 +8,12 @@
}
.departure__stop {
@extend %flex;
width: 100%;
.stop {
flex: 1 1 auto;
}
}
.departure__time {

View File

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

View 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
}

View File

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

View File

@ -15,3 +15,4 @@ $custom-control-indicator-active-bg: $dark;
@import "departure";
@import "line";
@import "controls";
@import "popper";

View File

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

View File

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

View File

@ -1,2 +1,4 @@
export * from './utils'
export * from './picker'
export * from './departures'
export * from './departures'
export * from './stop'

View File

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

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

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

View File

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

View File

@ -6,4 +6,10 @@ export interface Line {
type: LineType;
night: boolean;
fast: boolean;
}
export interface Track {
id: string;
description: string;
line: Line;
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View 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']));
}
}

View File

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

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

View File

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

View File

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

View File

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

View File

@ -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' => '#' ];
}
/**

View File

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

View File

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

View File

@ -1,5 +1,6 @@
{% extends 'base.html.twig' %}
{% block title "#{parent()} - #{provider.name}" %}
{% block body %}
<stop-picker></stop-picker>
{% endblock %}