From 38e6ae52e1a490cb42dcaaf132f0c95f91a8ecd1 Mon Sep 17 00:00:00 2001 From: Kacper Donat <kadet1090@gmail.com> Date: Thu, 6 Feb 2020 20:09:06 +0100 Subject: [PATCH] Add final stop info to track --- resources/components/finder.html | 13 +++-- resources/components/picker/stop.html | 34 ++++++----- resources/styles/_favourites.scss | 1 + resources/styles/_stop.scss | 14 ++++- resources/ts/model/stop.ts | 1 + src/Entity/StopInTrack.php | 22 +++++-- src/Entity/TrackEntity.php | 17 ++++-- src/Migrations/Version20200206183956.php | 57 +++++++++++++++++++ src/Model/ReferableTrait.php | 3 +- .../Database/GenericStopRepository.php | 15 ++--- .../ZtmGdanskDataUpdateSubscriber.php | 49 +++++++++------- src/Service/IterableUtils.php | 28 +++++++++ templates/app.html.twig | 15 +++-- 13 files changed, 203 insertions(+), 66 deletions(-) create mode 100644 src/Migrations/Version20200206183956.php create mode 100644 src/Service/IterableUtils.php diff --git a/resources/components/finder.html b/resources/components/finder.html index 0674533..9f26c5a 100644 --- a/resources/components/finder.html +++ b/resources/components/finder.html @@ -22,11 +22,14 @@ </div> <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 align-self-start"> - <tooltip>dodaj przystanek</tooltip> - <fa :icon="['fal', 'check']" /> - </button> - <picker-stop :stop="stop" class="flex-grow-1"></picker-stop> + <picker-stop :stop="stop" class="flex-grow-1"> + <template v-slot:primary-action> + <button @click="select(stop, $event)" class="btn btn-action align-self-start"> + <tooltip>dodaj przystanek</tooltip> + <fa :icon="['fal', 'check']" /> + </button> + </template> + </picker-stop> </li> </ul> </div> diff --git a/resources/components/picker/stop.html b/resources/components/picker/stop.html index 787a63a..1e9de69 100644 --- a/resources/components/picker/stop.html +++ b/resources/components/picker/stop.html @@ -1,19 +1,27 @@ -<div class="d-flex flex-wrap"> - <stop :stop="stop" /> +<div class="finder__stop"> + <div class="d-flex"> + <slot name="primary-action" /> + <div style="overflow: hidden"> + <stop :stop="stop" /> +<!-- <div style="white-space: nowrap">Lorem ipsum dolor sit amet, consectetur adipisicing elit. Earum, ut!</div>--> + <ul class="stop__destinations"> + <li class="stop__destination" v-for="destination in stop.destinations" :key="destination.id"><stop :stop="destination"/></li> + </ul> + </div> - <div class="stop__actions flex-space-left"> - <slot name="actions"> - <button class="btn btn-action" ref="action-info" @click="details = !details"> - <tooltip>dodatkowe informacje</tooltip> - <fa :icon="['fal', details ? 'chevron-circle-up' : 'info-circle']"/> - </button> + <div class="stop__actions flex-space-left"> + <slot name="actions"> + <button class="btn btn-action" ref="action-info" @click="details = !details"> + <tooltip>dodatkowe informacje</tooltip> + <fa :icon="['fal', details ? 'chevron-circle-up' : 'info-circle']"/> + </button> - <button class="btn btn-action" ref="action-map" v-hover:map> - <fa :icon="['fal', 'map-marker-alt']"/> - </button> - </slot> + <button class="btn btn-action" ref="action-map" v-hover:map> + <fa :icon="['fal', 'map-marker-alt']"/> + </button> + </slot> + </div> </div> - <fold :visible="details" class="stop__details-fold" lazy> <stop-details :stop="stop"/> </fold> diff --git a/resources/styles/_favourites.scss b/resources/styles/_favourites.scss index bbb50c9..27f3d79 100644 --- a/resources/styles/_favourites.scss +++ b/resources/styles/_favourites.scss @@ -11,6 +11,7 @@ font-size: $small-font-size; overflow-x: hidden; text-overflow: ellipsis; + max-width: 100%; &:last-child { margin-bottom: 0; diff --git a/resources/styles/_stop.scss b/resources/styles/_stop.scss index f8191cc..e0c48fe 100644 --- a/resources/styles/_stop.scss +++ b/resources/styles/_stop.scss @@ -5,7 +5,7 @@ } .stop__name { - flex: 1 0; + //flex: 1 0; line-height: 1.1; margin-right: .5em; } @@ -46,3 +46,15 @@ } } } + +.stop__destinations { + @extend .favourite__stops; +} + +.stop__destination { + @extend .favourite__stop; +} + +.finder__stop { + max-width: 100%; +} diff --git a/resources/ts/model/stop.ts b/resources/ts/model/stop.ts index ebb3bfb..fa6bc33 100644 --- a/resources/ts/model/stop.ts +++ b/resources/ts/model/stop.ts @@ -8,6 +8,7 @@ export interface Stop { }; onDemand?: boolean; variant?: string; + destinations?: Stop[]; } export type StopGroup = Stop[]; diff --git a/src/Entity/StopInTrack.php b/src/Entity/StopInTrack.php index 608327b..26cbd55 100644 --- a/src/Entity/StopInTrack.php +++ b/src/Entity/StopInTrack.php @@ -4,32 +4,42 @@ namespace App\Entity; use App\Model\Fillable; use App\Model\FillTrait; +use App\Model\Referable; +use App\Model\ReferableTrait; use Doctrine\ORM\Mapping as ORM; /** * @ORM\Entity - * @ORM\Table("track_stop") + * @ORM\Table("track_stop", uniqueConstraints={ + * @ORM\UniqueConstraint(name="stop_in_track_idx", columns={"stop_id", "track_id", "sequence"}) + * }) */ -class StopInTrack implements Fillable +class StopInTrack implements Fillable, Referable { - use FillTrait; + use FillTrait, ReferableEntityTrait; + + /** + * Identifier for stop coming from provider + * + * @ORM\Column(type="integer") + * @ORM\Id + * @ORM\GeneratedValue + */ + private $id; /** * @ORM\ManyToOne(targetEntity=StopEntity::class, fetch="EAGER") - * @ORM\Id */ private $stop; /** * @ORM\ManyToOne(targetEntity=TrackEntity::class, fetch="EAGER", inversedBy="stopsInTrack") - * @ORM\Id */ private $track; /** * Order in track * @var int - * @ORM\Id * @ORM\Column(name="sequence", type="integer") */ private $order; diff --git a/src/Entity/TrackEntity.php b/src/Entity/TrackEntity.php index a56fd2e..a0eaa1d 100644 --- a/src/Entity/TrackEntity.php +++ b/src/Entity/TrackEntity.php @@ -4,6 +4,7 @@ namespace App\Entity; use App\Model\Fillable; use App\Model\FillTrait; +use App\Service\IterableUtils; use Doctrine\Common\Collections\ArrayCollection; use Doctrine\Common\Collections\Collection; use Doctrine\ORM\Mapping as ORM; @@ -50,6 +51,12 @@ class TrackEntity implements Entity, Fillable */ private $stopsInTrack; + /** + * Final stop in this track. + * @var StopInTrack + * @ORM\OneToOne(targetEntity=StopInTrack::class, fetch="LAZY") + */ + private $final; /** * Track constructor. @@ -98,15 +105,17 @@ class TrackEntity implements Entity, Fillable } /** - * @param Collection $stopsInTrack + * @param iterable $stopsInTrack */ - public function setStopsInTrack(array $stopsInTrack): void + public function setStopsInTrack(iterable $stopsInTrack): void { - $this->stopsInTrack = new ArrayCollection($stopsInTrack); + $this->stopsInTrack = IterableUtils::toArrayCollection($stopsInTrack); + + $this->final = $this->stopsInTrack->last(); } public function getFinal(): StopInTrack { - return $this->getStopsInTrack()->last(); + return $this->final; } } diff --git a/src/Migrations/Version20200206183956.php b/src/Migrations/Version20200206183956.php new file mode 100644 index 0000000..d7485ab --- /dev/null +++ b/src/Migrations/Version20200206183956.php @@ -0,0 +1,57 @@ +<?php + +declare(strict_types=1); + +namespace DoctrineMigrations; + +use Doctrine\DBAL\Schema\Schema; +use Doctrine\Migrations\AbstractMigration; + +/** + * Auto-generated Migration: Please modify to your needs! + */ +final class Version20200206183956 extends AbstractMigration +{ + public function getDescription() : string + { + return ''; + } + + public function up(Schema $schema) : void + { + // this up() migration is auto-generated, please modify it to your needs + $this->abortIf($this->connection->getDatabasePlatform()->getName() !== 'sqlite', 'Migration can only be executed safely on \'sqlite\'.'); + + $this->addSql('CREATE TEMPORARY TABLE __temp__track AS SELECT id, line_id, provider_id, variant, description FROM track'); + $this->addSql('DROP TABLE track'); + $this->addSql('CREATE TABLE track (id VARCHAR(255) NOT NULL COLLATE BINARY, line_id VARCHAR(255) DEFAULT NULL COLLATE BINARY, provider_id VARCHAR(255) DEFAULT NULL COLLATE BINARY, variant VARCHAR(16) DEFAULT NULL COLLATE BINARY, description VARCHAR(256) DEFAULT NULL COLLATE BINARY, final_id INTEGER DEFAULT NULL, PRIMARY KEY(id))'); + $this->addSql('INSERT INTO track (id, line_id, provider_id, variant, description) SELECT id, line_id, provider_id, variant, description FROM __temp__track'); + $this->addSql('DROP TABLE __temp__track'); + $this->addSql('CREATE UNIQUE INDEX UNIQ_D6E3F8A613D41B2D ON track (final_id)'); + $this->addSql('CREATE TEMPORARY TABLE __temp__track_stop AS SELECT stop_id, track_id, sequence FROM track_stop'); + $this->addSql('DROP TABLE track_stop'); + $this->addSql('CREATE TABLE track_stop (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, sequence INTEGER NOT NULL, stop_id VARCHAR(255) DEFAULT NULL, track_id VARCHAR(255) DEFAULT NULL)'); + $this->addSql('INSERT INTO track_stop (stop_id, track_id, sequence) SELECT stop_id, track_id, sequence FROM __temp__track_stop'); + $this->addSql('DROP TABLE __temp__track_stop'); + $this->addSql('CREATE UNIQUE INDEX stop_in_track_idx ON track_stop (stop_id, track_id, sequence)'); + } + + public function down(Schema $schema) : void + { + // this down() migration is auto-generated, please modify it to your needs + $this->abortIf($this->connection->getDatabasePlatform()->getName() !== 'sqlite', 'Migration can only be executed safely on \'sqlite\'.'); + + $this->addSql('DROP INDEX UNIQ_D6E3F8A613D41B2D'); + $this->addSql('CREATE TEMPORARY TABLE __temp__track AS SELECT id, variant, description, line_id, provider_id FROM track'); + $this->addSql('DROP TABLE track'); + $this->addSql('CREATE TABLE track (id VARCHAR(255) NOT NULL, variant VARCHAR(16) DEFAULT NULL, description VARCHAR(256) DEFAULT NULL, line_id VARCHAR(255) DEFAULT NULL, provider_id VARCHAR(255) DEFAULT NULL, PRIMARY KEY(id))'); + $this->addSql('INSERT INTO track (id, variant, description, line_id, provider_id) SELECT id, variant, description, line_id, provider_id FROM __temp__track'); + $this->addSql('DROP TABLE __temp__track'); + $this->addSql('DROP INDEX stop_in_track_idx'); + $this->addSql('CREATE TEMPORARY TABLE __temp__track_stop AS SELECT sequence, stop_id, track_id FROM track_stop'); + $this->addSql('DROP TABLE track_stop'); + $this->addSql('CREATE TABLE track_stop (sequence INTEGER NOT NULL, stop_id VARCHAR(255) NOT NULL COLLATE BINARY, track_id VARCHAR(255) NOT NULL COLLATE BINARY, PRIMARY KEY(stop_id, track_id, sequence))'); + $this->addSql('INSERT INTO track_stop (sequence, stop_id, track_id) SELECT sequence, stop_id, track_id FROM __temp__track_stop'); + $this->addSql('DROP TABLE __temp__track_stop'); + } +} diff --git a/src/Model/ReferableTrait.php b/src/Model/ReferableTrait.php index 70280dc..bb4397f 100644 --- a/src/Model/ReferableTrait.php +++ b/src/Model/ReferableTrait.php @@ -2,6 +2,7 @@ namespace App\Model; +use Doctrine\ORM\Mapping as ORM; use JMS\Serializer\Annotation as Serializer; trait ReferableTrait @@ -38,4 +39,4 @@ trait ReferableTrait return $result; } -} \ No newline at end of file +} diff --git a/src/Provider/Database/GenericStopRepository.php b/src/Provider/Database/GenericStopRepository.php index 2d3aa9a..737b11f 100644 --- a/src/Provider/Database/GenericStopRepository.php +++ b/src/Provider/Database/GenericStopRepository.php @@ -3,10 +3,8 @@ namespace App\Provider\Database; use App\Entity\StopEntity; -use App\Entity\StopInTrack; use App\Entity\TrackEntity; use App\Model\Stop; -use App\Model\Track; use App\Provider\StopRepository; use Tightenco\Collect\Support\Collection; use Kadet\Functional as f; @@ -23,7 +21,7 @@ class GenericStopRepository extends DatabaseRepository implements StopRepository public function getById($id): ?Stop { - $id = $this->id->generate($this->provider, $id); + $id = $this->id->generate($this->provider, $id); $stop = $this->em->getRepository(StopEntity::class)->find($id); return $this->convert($stop); @@ -31,7 +29,7 @@ class GenericStopRepository extends DatabaseRepository implements StopRepository public function getManyById($ids): Collection { - $ids = collect($ids)->map(f\apply(f\ref([$this->id, 'generate']), $this->provider)); + $ids = collect($ids)->map(f\apply(f\ref([$this->id, 'generate']), $this->provider)); $stops = $this->em->getRepository(StopEntity::class)->findBy(['id' => $ids->all()]); return collect($stops)->map(f\ref([$this, 'convert'])); @@ -48,12 +46,12 @@ class GenericStopRepository extends DatabaseRepository implements StopRepository $stops = collect($query->execute([':name' => "%$name%"])); $destinations = collect($this->em->createQueryBuilder() - ->select('t', 'ts', 'ts2', 's') + ->select('t', 'f', 'fs', 'ts') ->from(TrackEntity::class, 't') ->join('t.stopsInTrack', 'ts') - ->join('t.stopsInTrack', 'ts2') - ->join('ts2.stop', 's') ->where('ts.stop IN (:stops)') + ->join('t.final', 'f') + ->join('f.stop', 'fs') ->getQuery() ->execute(['stops' => $stops->map(t\property('id'))->all()])) ->reduce(function ($grouped, TrackEntity $track) { @@ -67,8 +65,7 @@ class GenericStopRepository extends DatabaseRepository implements StopRepository return $tracks->map(function (TrackEntity $track) { return $this->convert($track->getFinal()->getStop()); })->unique()->values(); - }) - ; + }); return collect($stops)->map(f\ref([$this, 'convert']))->each(function (Stop $stop) use ($destinations) { $stop->setDestinations($destinations[$this->id->generate($this->provider, $stop->getId())]); diff --git a/src/Provider/ZtmGdansk/ZtmGdanskDataUpdateSubscriber.php b/src/Provider/ZtmGdansk/ZtmGdanskDataUpdateSubscriber.php index abf6553..146ef55 100644 --- a/src/Provider/ZtmGdansk/ZtmGdanskDataUpdateSubscriber.php +++ b/src/Provider/ZtmGdansk/ZtmGdanskDataUpdateSubscriber.php @@ -12,7 +12,6 @@ use App\Entity\TripEntity; use App\Entity\TripStopEntity; use App\Event\DataUpdateEvent; use App\Model\Line as LineModel; -use App\Model\Location; use App\Service\DataUpdater; use App\Service\IdUtils; use Carbon\Carbon; @@ -20,13 +19,10 @@ use Cerbero\JsonObjects\JsonObjects; use Doctrine\ORM\EntityManagerInterface; use Symfony\Component\Console\Helper\ProgressBar; use Psr\Log\LoggerInterface; -use Symfony\Component\Console\Output\ConsoleOutputInterface; -use Symfony\Component\Console\Output\ConsoleSectionOutput; -use Symfony\Component\Console\Output\NullOutput; use Symfony\Component\EventDispatcher\EventSubscriberInterface; use Tightenco\Collect\Support\Collection; -use function Cerbero\JsonObjects\JsonObjects; use function Kadet\Functional\ref; +use function Kadet\Functional\Transforms\property; class ZtmGdanskDataUpdateSubscriber implements EventSubscriberInterface { @@ -45,6 +41,8 @@ class ZtmGdanskDataUpdateSubscriber implements EventSubscriberInterface private $logger; private $provider; + private $stopBlacklist = []; + /** * ZtmGdanskDataUpdateSubscriber constructor. * @@ -147,6 +145,7 @@ class ZtmGdanskDataUpdateSubscriber implements EventSubscriberInterface private function getStops(ProviderEntity $provider, DataUpdateEvent $event) { + $this->stopBlacklist = []; $output = $event->getOutput(); $output->write('Obtaining stops from ZTM Gdańsk... '); @@ -157,9 +156,12 @@ class ZtmGdanskDataUpdateSubscriber implements EventSubscriberInterface $this->logger->debug(sprintf("Saving %d stops tracks from ZTM Gdańsk.", count($stops))); return collect($stops) ->filter(function ($stop) { - return $stop['nonpassenger'] !== 1 - && $stop['virtual'] !== 1 - && $stop['depot'] !== 1; + if ($stop['nonpassenger'] === 1 || $stop['virtual'] === 1 || $stop['depot'] === 1) { + $this->stopBlacklist[] = $stop['stopId']; + return false; + } + + return true; }) ->map(function ($stop) use ($provider) { $name = trim($stop['stopName'] ?? $stop['stopDesc']); @@ -178,7 +180,7 @@ class ZtmGdanskDataUpdateSubscriber implements EventSubscriberInterface ; } - public function getTracks(ProviderEntity $provider, DataUpdateEvent $event, $stops = []) + public function getTracks(ProviderEntity $provider, DataUpdateEvent $event, Collection $stops = null) { $output = $event->getOutput(); @@ -209,19 +211,24 @@ class ZtmGdanskDataUpdateSubscriber implements EventSubscriberInterface 'provider' => $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' => $entity, - // HACK! Gdynia has 0 based sequence - 'order' => $stop['stopSequence'] + (int)($stop['stopId'] > 30000), - ]); - }); + $stops = $stops->get($track['id']) + ->filter(function ($stop) { + return !in_array($stop['stopId'], $this->stopBlacklist); + }) + ->map(function ($stop) use ($entity, $provider) { + return StopInTrack::createFromArray([ + 'stop' => $this->em->getReference( + StopEntity::class, + $this->ids->generate($provider, $stop['stopId']) + ), + 'track' => $entity, + // HACK! Gdynia has 0 based sequence + 'order' => $stop['stopSequence'] + (int)($stop['stopId'] > 30000), + ]); + }) + ->sortBy(property("order")); - $entity->setStopsInTrack($stops->all()); + $entity->setStopsInTrack($stops); return $entity; }); diff --git a/src/Service/IterableUtils.php b/src/Service/IterableUtils.php new file mode 100644 index 0000000..5018060 --- /dev/null +++ b/src/Service/IterableUtils.php @@ -0,0 +1,28 @@ +<?php + +namespace App\Service; + +use Doctrine\Common\Collections\ArrayCollection; +use Tightenco\Collect\Support\Collection; + +final class IterableUtils +{ + public static function toArray(iterable $iterable): array + { + if (is_array($iterable)) { + return $iterable; + } + + return iterator_to_array($iterable); + } + + public static function toArrayCollection(iterable $iterable): ArrayCollection + { + return new ArrayCollection(static::toArray($iterable)); + } + + public static function toCollection(iterable $iterable): Collection + { + return collect($iterable); + } +} diff --git a/templates/app.html.twig b/templates/app.html.twig index d2f9802..f364419 100644 --- a/templates/app.html.twig +++ b/templates/app.html.twig @@ -101,11 +101,14 @@ <ul class="picker__stops list-underlined"> <li v-for="stop in stops" :key="stop.id" class="d-flex align-items-center"> - <button @click="remove(stop)" class="btn btn-action align-self-start"> - <tooltip>usuń przystanek</tooltip> - <fa :icon="['fal', 'times']"></fa> - </button> - <picker-stop :stop="stop" class="flex-grow-1"></picker-stop> + <picker-stop :stop="stop" class="flex-grow-1"> + <template v-slot:primary-action> + <button @click="remove(stop)" class="btn btn-action align-self-start"> + <tooltip>usuń przystanek</tooltip> + <fa :icon="['fal', 'times']"></fa> + </button> + </template> + </picker-stop> </li> </ul> @@ -143,7 +146,7 @@ </button> </template> </header> - <div class="transition-box" style="overflow: hidden;"> + <div class="transition-box"> <transition name="fade"> <stop-finder @select="add" :blacklist="stops" v-if="visibility.picker === 'search'"></stop-finder> <favourites v-else-if="visibility.picker === 'favourites'"></favourites>