Add lines to destinations
This is useful when we have few different stops, with same destinations and different lines.
This commit is contained in:
parent
9ecadfd4d1
commit
66e45c8112
@ -1,12 +1,16 @@
|
||||
<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="icon">
|
||||
<fa :icon="['fac', line.type]" fixed-width/>
|
||||
</span>
|
||||
<span class="badge badge-dark flex">
|
||||
<fa :icon="['fas', 'walking']" fixed-width v-if="line.fast"/>
|
||||
<fa :icon="['fal', 'moon']" fixed-width v-if="line.night"/>
|
||||
{{ line.symbol }}
|
||||
</span>
|
||||
<slot name="icon" v-if="!simple">
|
||||
<span class="icon">
|
||||
<fa :icon="['fac', line.type]" fixed-width/>
|
||||
</span>
|
||||
</slot>
|
||||
<slot name="badge">
|
||||
<span class="badge badge-dark flex">
|
||||
<fa :icon="['fal', 'moon']" fixed-width v-if="line.night && !simple"/>
|
||||
{{ line.symbol }}
|
||||
<fa :icon="['fas', 'walking']" v-if="line.fast"/>
|
||||
</span>
|
||||
</slot>
|
||||
</span>
|
||||
</span>
|
||||
</span>
|
||||
|
@ -2,11 +2,17 @@
|
||||
<div class="d-flex">
|
||||
<slot name="primary-action" />
|
||||
<div class="overflow-hidden align-self-center">
|
||||
<stop :stop="stop" class="my-1"/>
|
||||
<stop :stop="stop" />
|
||||
<div class="stop__destinations" v-if="destinations && destinations.length > 0">
|
||||
<fa :icon="['far', 'chevron-right']" />
|
||||
<ul class="ml-1">
|
||||
<li class="stop__destination" v-for="destination in destinations" :key="destination.id">{{ destination.name }}</li>
|
||||
<li class="stop__destination destination" v-for="destination in destinations" :key="destination.stop.id">
|
||||
<ul class="destination__lines">
|
||||
<li v-for="line in destination.lines">
|
||||
<line-symbol :line="line" :key="line.symbol" simple/>
|
||||
</li>
|
||||
</ul>
|
||||
<span class="destination__name ml-1">{{ destination.stop.name }}</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -53,6 +53,16 @@
|
||||
|
||||
.stop__destination {
|
||||
@extend .favourite__stop;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.destination__line {
|
||||
@extend .line__symbol;
|
||||
}
|
||||
|
||||
.destination__lines li {
|
||||
display: inline-block;
|
||||
@include spacing;
|
||||
}
|
||||
|
||||
.finder__stop {
|
||||
|
@ -43,6 +43,13 @@ $grid-gutter-width: $spacer * 2;
|
||||
}
|
||||
}
|
||||
|
||||
@mixin spacing($spacing: .25em) {
|
||||
margin-left: $spacing;
|
||||
&:first-child {
|
||||
margin-left: 0;
|
||||
}
|
||||
}
|
||||
|
||||
@mixin no-scrollbars {
|
||||
scrollbar-width: none; /* Firefox */
|
||||
-ms-overflow-style: none; /* Internet Explorer 10+ */
|
||||
|
@ -6,6 +6,9 @@ import { Line } from "../model";
|
||||
export class LineComponent extends Vue {
|
||||
@Prop(Object)
|
||||
public line: Line;
|
||||
|
||||
@Prop(Boolean)
|
||||
public simple: boolean;
|
||||
}
|
||||
|
||||
Vue.component('LineSymbol', LineComponent);
|
||||
Vue.component('LineSymbol', LineComponent);
|
||||
|
@ -1,8 +1,8 @@
|
||||
import Component from "vue-class-component";
|
||||
import Vue from "vue";
|
||||
import { Stop, StopGroup, StopGroups } from "../model";
|
||||
import { Destination, Line, Stop, StopGroup, StopGroups } from "../model";
|
||||
import { Prop, Watch } from "vue-property-decorator";
|
||||
import { FetchingState, filter, map, unique } from "../utils";
|
||||
import { FetchingState, filter, map, match, unique } from "../utils";
|
||||
import { debounce } from "../decorators";
|
||||
import urls from '../urls';
|
||||
|
||||
@ -20,7 +20,29 @@ export class PickerStopComponent extends Vue {
|
||||
}
|
||||
|
||||
get destinations() {
|
||||
return unique(this.stop.destinations, stop => stop.name);
|
||||
const compactLines = destination => ({
|
||||
...destination,
|
||||
lines: Object.entries(groupLinesByType(destination.lines || [])).map(([type, lines]) => ({
|
||||
type: type,
|
||||
symbol: joinedSymbol(lines),
|
||||
night: lines.every(line => line.night),
|
||||
fast: lines.every(line => line.fast),
|
||||
})),
|
||||
all: destination.lines
|
||||
});
|
||||
|
||||
const groupLinesByType = (lines: Line[]) => lines.reduce<{ [kind: string]: Line[]}>((groups, line) => ({
|
||||
...groups,
|
||||
[line.type]: [ ...(groups[line.type] || []), line ]
|
||||
}), {});
|
||||
|
||||
const joinedSymbol = match<string, [Line[]]>(
|
||||
[lines => lines.length === 1, lines => lines[0].symbol],
|
||||
[lines => lines.length === 2, ([first, second]) => `${first.symbol}, ${second.symbol}`],
|
||||
[lines => lines.length > 2, ([first]) => `${first.symbol}…`],
|
||||
);
|
||||
|
||||
return unique(this.stop.destinations || [], destination => destination.stop.name).map(compactLines);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,7 +1,6 @@
|
||||
import Vue from 'vue';
|
||||
import { Component, Prop, Watch } from "vue-property-decorator";
|
||||
import Popper, { Placement } from "popper.js";
|
||||
import { Portal } from "portal-vue";
|
||||
import vueRemovedHookMixin from "vue-removed-hook-mixin";
|
||||
|
||||
@Component({
|
||||
@ -155,3 +154,9 @@ export class LazyComponent extends Vue {
|
||||
Vue.component('Popper', PopperComponent);
|
||||
Vue.component('Fold', FoldComponent);
|
||||
Vue.component('Lazy', LazyComponent);
|
||||
|
||||
// https://github.com/vuejs/vue/issues/7829
|
||||
Vue.component('Empty', {
|
||||
functional: true,
|
||||
render: (h, { data }) => h('template', data, '')
|
||||
});
|
||||
|
@ -1,3 +1,5 @@
|
||||
import { Line } from "./line";
|
||||
|
||||
export interface Stop {
|
||||
id: any;
|
||||
name: string;
|
||||
@ -8,7 +10,12 @@ export interface Stop {
|
||||
};
|
||||
onDemand?: boolean;
|
||||
variant?: string;
|
||||
destinations?: Stop[];
|
||||
destinations?: Destination[];
|
||||
}
|
||||
|
||||
export type Destination = {
|
||||
stop: Stop;
|
||||
lines: Line[]
|
||||
}
|
||||
|
||||
export type StopGroup = Stop[];
|
||||
|
@ -95,3 +95,22 @@ export function unique<T, U>(array: T[], criterion: (item: T) => U = identity) {
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
type Pattern<TResult, TArgs extends any[]> = [
|
||||
(...args: TArgs) => boolean,
|
||||
((...args: TArgs) => TResult) | TResult,
|
||||
]
|
||||
|
||||
export function match<TResult, TArgs extends any[]>(...patterns: Pattern<TResult, TArgs>[]): (...args: TArgs) => TResult {
|
||||
return (...args: TArgs) => {
|
||||
for (let [pattern, action] of patterns) {
|
||||
if (pattern(...args)) {
|
||||
return typeof action === "function" ? (action as (...args: TArgs) => TResult)(...args) : action;
|
||||
}
|
||||
}
|
||||
|
||||
throw new Error(`No pattern matches args: ${JSON.stringify(args)}`);
|
||||
}
|
||||
}
|
||||
|
||||
match.default = (...args: any[]) => true;
|
||||
|
@ -14,7 +14,7 @@ use Symfony\Component\Routing\Annotation\Route;
|
||||
class TripController extends Controller
|
||||
{
|
||||
/**
|
||||
* @Route("/{id}")
|
||||
* @Route("/{id}", methods={"GET"})
|
||||
*/
|
||||
public function one($id, TripRepository $repository)
|
||||
{
|
||||
|
52
src/Model/Destination.php
Normal file
52
src/Model/Destination.php
Normal file
@ -0,0 +1,52 @@
|
||||
<?php
|
||||
|
||||
namespace App\Model;
|
||||
|
||||
use JMS\Serializer\Annotation as Serializer;
|
||||
use Nelmio\ApiDocBundle\Annotation\Model;
|
||||
use Swagger\Annotations as SWG;
|
||||
use Tightenco\Collect\Support\Collection;
|
||||
|
||||
class Destination implements Fillable
|
||||
{
|
||||
use FillTrait;
|
||||
|
||||
/**
|
||||
* Stop associated with destination.
|
||||
* @Serializer\Type(Stop::class)
|
||||
* @var Stop
|
||||
*/
|
||||
private $stop;
|
||||
|
||||
/**
|
||||
* @Serializer\Type("Collection")
|
||||
* @SWG\Property(type="array", @SWG\Items(ref=@Model(type=Line::class, groups={"Default"})))
|
||||
* @var Line[]|Collection<Line>
|
||||
*/
|
||||
private $lines;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->lines = collect();
|
||||
}
|
||||
|
||||
public function getStop(): Stop
|
||||
{
|
||||
return $this->stop;
|
||||
}
|
||||
|
||||
public function setStop(Stop $stop): void
|
||||
{
|
||||
$this->stop = $stop;
|
||||
}
|
||||
|
||||
public function getLines(): Collection
|
||||
{
|
||||
return $this->lines;
|
||||
}
|
||||
|
||||
public function setLines(iterable $lines): void
|
||||
{
|
||||
$this->lines = collect($lines);
|
||||
}
|
||||
}
|
@ -73,9 +73,9 @@ class Stop implements Referable, Fillable
|
||||
*
|
||||
* @Serializer\Groups({"WithDestinations"})
|
||||
* @Serializer\Type("Collection")
|
||||
* @SWG\Property(type="array", @SWG\Items(ref=@Model(type=Stop::class, groups={"Default"})))
|
||||
* @SWG\Property(type="array", @SWG\Items(ref=@Model(type=Destination::class, groups={"Default"})))
|
||||
*
|
||||
* @var Collection<Stop>
|
||||
* @var Collection<Destination>
|
||||
*/
|
||||
private $destinations;
|
||||
|
||||
|
@ -4,11 +4,12 @@ namespace App\Provider\Database;
|
||||
|
||||
use App\Entity\StopEntity;
|
||||
use App\Entity\TrackEntity;
|
||||
use App\Model\Destination;
|
||||
use App\Model\Stop;
|
||||
use App\Provider\StopRepository;
|
||||
use Tightenco\Collect\Support\Collection;
|
||||
use Kadet\Functional as f;
|
||||
use Kadet\Functional\Transforms as t;
|
||||
use Tightenco\Collect\Support\Collection;
|
||||
|
||||
class GenericStopRepository extends DatabaseRepository implements StopRepository
|
||||
{
|
||||
@ -46,9 +47,10 @@ class GenericStopRepository extends DatabaseRepository implements StopRepository
|
||||
$stops = collect($query->execute([':name' => "%$name%"]));
|
||||
|
||||
$destinations = collect($this->em->createQueryBuilder()
|
||||
->select('t', 'f', 'fs', 'ts')
|
||||
->select('t', 'tl', 'f', 'fs', 'ts')
|
||||
->from(TrackEntity::class, 't')
|
||||
->join('t.stopsInTrack', 'ts')
|
||||
->join('t.line', 'tl')
|
||||
->where('ts.stop IN (:stops)')
|
||||
->join('t.final', 'f')
|
||||
->join('f.stop', 'fs')
|
||||
@ -62,9 +64,19 @@ class GenericStopRepository extends DatabaseRepository implements StopRepository
|
||||
return $grouped;
|
||||
}, collect())
|
||||
->map(function (Collection $tracks) {
|
||||
return $tracks->map(function (TrackEntity $track) {
|
||||
return $this->convert($track->getFinal()->getStop());
|
||||
})->unique()->values();
|
||||
return $tracks
|
||||
->groupBy(function (TrackEntity $track) {
|
||||
return $track->getFinal()->getStop()->getId();
|
||||
})->map(function (Collection $tracks, $id) {
|
||||
return Destination::createFromArray([
|
||||
'stop' => $this->convert($tracks->first()->getFinal()->getStop()),
|
||||
'lines' => $tracks
|
||||
->map(t\property('line'))
|
||||
->unique(t\property('id'))
|
||||
->map(f\ref([$this, 'convert']))
|
||||
->values(),
|
||||
]);
|
||||
})->values();
|
||||
});
|
||||
|
||||
return collect($stops)->map(f\ref([$this, 'convert']))->each(function (Stop $stop) use ($destinations) {
|
||||
|
Loading…
Reference in New Issue
Block a user