From 66e45c811229673d612875e51e5112592baf4f31 Mon Sep 17 00:00:00 2001 From: Kacper Donat Date: Sat, 8 Feb 2020 18:16:04 +0100 Subject: [PATCH] Add lines to destinations This is useful when we have few different stops, with same destinations and different lines. --- resources/components/line.html | 22 ++++---- resources/components/picker/stop.html | 12 +++-- resources/styles/_stop.scss | 10 ++++ resources/styles/main.scss | 7 +++ resources/ts/components/line.ts | 5 +- resources/ts/components/picker.ts | 28 ++++++++-- resources/ts/components/utils.ts | 7 ++- resources/ts/model/stop.ts | 9 +++- resources/ts/utils.ts | 19 +++++++ src/Controller/Api/v1/TripController.php | 2 +- src/Model/Destination.php | 52 +++++++++++++++++++ src/Model/Stop.php | 4 +- .../Database/GenericStopRepository.php | 22 ++++++-- 13 files changed, 173 insertions(+), 26 deletions(-) create mode 100644 src/Model/Destination.php diff --git a/resources/components/line.html b/resources/components/line.html index e8565f2..91f1ef2 100644 --- a/resources/components/line.html +++ b/resources/components/line.html @@ -1,12 +1,16 @@ - - - - - - - {{ line.symbol }} - + + + + + + + + + {{ line.symbol }} + + + - \ No newline at end of file + diff --git a/resources/components/picker/stop.html b/resources/components/picker/stop.html index f40e869..1fa2830 100644 --- a/resources/components/picker/stop.html +++ b/resources/components/picker/stop.html @@ -2,11 +2,17 @@
- +
-
    -
  • {{ destination.name }}
  • +
  • +
      +
    • + +
    • +
    + {{ destination.stop.name }} +
diff --git a/resources/styles/_stop.scss b/resources/styles/_stop.scss index 3ac0d0c..e72315f 100644 --- a/resources/styles/_stop.scss +++ b/resources/styles/_stop.scss @@ -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 { diff --git a/resources/styles/main.scss b/resources/styles/main.scss index 9038407..73afad3 100644 --- a/resources/styles/main.scss +++ b/resources/styles/main.scss @@ -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+ */ diff --git a/resources/ts/components/line.ts b/resources/ts/components/line.ts index bce6353..86ca509 100644 --- a/resources/ts/components/line.ts +++ b/resources/ts/components/line.ts @@ -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); \ No newline at end of file +Vue.component('LineSymbol', LineComponent); diff --git a/resources/ts/components/picker.ts b/resources/ts/components/picker.ts index f74e41a..f8a8f61 100644 --- a/resources/ts/components/picker.ts +++ b/resources/ts/components/picker.ts @@ -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( + [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); } } diff --git a/resources/ts/components/utils.ts b/resources/ts/components/utils.ts index 200e083..9dc6837 100644 --- a/resources/ts/components/utils.ts +++ b/resources/ts/components/utils.ts @@ -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, '') +}); diff --git a/resources/ts/model/stop.ts b/resources/ts/model/stop.ts index fa6bc33..40e0e6b 100644 --- a/resources/ts/model/stop.ts +++ b/resources/ts/model/stop.ts @@ -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[]; diff --git a/resources/ts/utils.ts b/resources/ts/utils.ts index ecb4a18..d7d6a22 100644 --- a/resources/ts/utils.ts +++ b/resources/ts/utils.ts @@ -95,3 +95,22 @@ export function unique(array: T[], criterion: (item: T) => U = identity) { return result; } + +type Pattern = [ + (...args: TArgs) => boolean, + ((...args: TArgs) => TResult) | TResult, +] + +export function match(...patterns: Pattern[]): (...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; diff --git a/src/Controller/Api/v1/TripController.php b/src/Controller/Api/v1/TripController.php index 9f1583e..87b87cd 100644 --- a/src/Controller/Api/v1/TripController.php +++ b/src/Controller/Api/v1/TripController.php @@ -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) { diff --git a/src/Model/Destination.php b/src/Model/Destination.php new file mode 100644 index 0000000..45d22c1 --- /dev/null +++ b/src/Model/Destination.php @@ -0,0 +1,52 @@ + + */ + 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); + } +} diff --git a/src/Model/Stop.php b/src/Model/Stop.php index a37764a..0d294db 100644 --- a/src/Model/Stop.php +++ b/src/Model/Stop.php @@ -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 + * @var Collection */ private $destinations; diff --git a/src/Provider/Database/GenericStopRepository.php b/src/Provider/Database/GenericStopRepository.php index 737b11f..8dc1972 100644 --- a/src/Provider/Database/GenericStopRepository.php +++ b/src/Provider/Database/GenericStopRepository.php @@ -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) {