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 @@ 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 @@ -
- +
+
+ +
+ + +
    +
  • +
+
-
- - +
+ + - - + + +
- 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 @@ +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 @@ +
  • - - + + +
  • @@ -143,7 +146,7 @@ -
    +