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