From 5a34bf7ff362beff8c175e87f65eb8e7401216d9 Mon Sep 17 00:00:00 2001 From: Christophe TARET Date: Tue, 4 Jun 2024 10:54:34 +0200 Subject: [PATCH] Add an optimizer library to score tracks (#203) The optimizer lib is a thin wrapper around igc-xc-score --- .eslintrc.json | 3 +- .verdaccio/config.yml | 28 + CONTRIBUTING.md | 51 ++ README.md | 4 +- apps/airspaces/src/app/create-geojson.ts | 2 +- apps/airspaces/src/app/parser/openaip.ts | 3 +- apps/airspaces/src/app/parser/openair.ts | 3 +- apps/airspaces/src/app/parser/parser.ts | 3 +- apps/airspaces/src/app/upload-tiles-diff.ts | 3 +- apps/fetcher/src/app/elevation/arcgis.ts | 2 +- apps/fetcher/src/app/elevation/elevation.ts | 3 +- apps/fetcher/src/app/redis.ts | 9 +- apps/fetcher/src/app/state/serialize.ts | 3 +- apps/fetcher/src/app/state/state.ts | 2 +- apps/fetcher/src/app/state/sync.test.ts | 6 +- apps/fetcher/src/app/state/sync.ts | 5 +- apps/fetcher/src/app/trackers/flymaster.ts | 9 +- apps/fetcher/src/app/trackers/flyme.ts | 9 +- apps/fetcher/src/app/trackers/inreach.ts | 8 +- .../src/app/trackers/live-track.test.ts | 3 +- apps/fetcher/src/app/trackers/live-track.ts | 3 +- apps/fetcher/src/app/trackers/ogn-client.ts | 6 +- apps/fetcher/src/app/trackers/ogn-push.ts | 5 +- apps/fetcher/src/app/trackers/ogn.ts | 13 +- apps/fetcher/src/app/trackers/refresh.ts | 8 +- apps/fetcher/src/app/trackers/skylines.ts | 9 +- apps/fetcher/src/app/trackers/spot.test.ts | 2 +- apps/fetcher/src/app/trackers/spot.ts | 9 +- apps/fetcher/src/app/trackers/tracker.ts | 5 +- .../fetcher/src/app/trackers/xcontest.test.ts | 3 +- apps/fetcher/src/app/trackers/xcontest.ts | 9 +- apps/fetcher/src/app/trackers/zoleo.test.ts | 2 +- apps/fetcher/src/app/trackers/zoleo.ts | 14 +- apps/fetcher/src/app/ufos/aviant.ts | 9 +- apps/fetcher/src/app/ufos/refresh.ts | 6 +- apps/fetcher/src/app/ufos/ufo.ts | 5 +- apps/fetcher/src/fetcher.ts | 4 +- apps/fxc-front/project.json | 1 + .../src/app/components/2d/airspace-element.ts | 6 +- .../src/app/components/2d/controls-element.ts | 10 +- .../src/app/components/2d/line-element.ts | 8 +- .../src/app/components/2d/map-element.ts | 13 +- .../src/app/components/2d/marker-element.ts | 8 +- .../src/app/components/2d/path-element.ts | 101 ++- .../src/app/components/2d/planner-element.ts | 10 +- .../src/app/components/2d/skyways-element.ts | 8 +- .../src/app/components/2d/tracking-element.ts | 12 +- .../app/components/3d/airspace3d-element.ts | 9 +- .../app/components/3d/controls3d-element.ts | 10 +- .../src/app/components/3d/line3d-element.ts | 8 +- .../src/app/components/3d/map3d-element.ts | 14 +- .../src/app/components/3d/marker3d-element.ts | 8 +- .../app/components/3d/skyways3d-element.ts | 8 +- .../app/components/3d/tracking3d-element.ts | 22 +- .../src/app/components/chart-element.ts | 9 +- .../src/app/components/dashboard-element.ts | 3 +- .../src/app/components/loader-element.ts | 6 +- .../src/app/components/menu-element.ts | 3 +- .../src/app/components/name-element.ts | 5 +- .../src/app/components/ui/about-modal.ts | 6 +- .../src/app/components/ui/google-btn.ts | 3 +- .../src/app/components/ui/live-modal.ts | 11 +- .../src/app/components/ui/main-menu.ts | 26 +- .../src/app/components/ui/pref-modal.ts | 10 +- .../src/app/components/ui/share-modal.ts | 8 +- .../src/app/components/ui/supporter-modal.ts | 3 +- .../src/app/components/ui/track-modal.ts | 9 +- .../src/app/components/ui/waypoint-modal.ts | 5 +- apps/fxc-front/src/app/gm/closing-sector.ts | 17 +- apps/fxc-front/src/app/gm/fai-sectors.ts | 2 +- apps/fxc-front/src/app/logic/airspaces.ts | 7 +- apps/fxc-front/src/app/logic/elevation.ts | 2 +- .../src/app/logic/live-track-popup.ts | 3 +- apps/fxc-front/src/app/logic/live-track.ts | 2 +- apps/fxc-front/src/app/logic/messages.ts | 2 +- apps/fxc-front/src/app/logic/score/league.ts | 7 - .../src/app/logic/score/league/czech.ts | 68 -- .../src/app/logic/score/league/frcfd.ts | 25 - .../src/app/logic/score/league/leagues.ts | 64 +- .../src/app/logic/score/league/leonardo.ts | 21 - .../src/app/logic/score/league/ukxcl.ts | 153 ---- .../src/app/logic/score/league/wxc.ts | 20 - .../src/app/logic/score/league/xcontest.ts | 66 -- apps/fxc-front/src/app/logic/score/measure.ts | 153 ---- apps/fxc-front/src/app/logic/score/scorer.ts | 139 +--- .../fxc-front/src/app/pages/admin-elements.ts | 3 +- apps/fxc-front/src/app/pages/admin.ts | 3 +- apps/fxc-front/src/app/pages/archives.ts | 6 +- .../fxc-front/src/app/pages/privacy-policy.ts | 3 +- apps/fxc-front/src/app/pages/settings.ts | 5 +- apps/fxc-front/src/app/pages/terms.ts | 2 +- .../fxc-front/src/app/redux/airspace-slice.ts | 3 +- apps/fxc-front/src/app/redux/app-slice.ts | 3 +- apps/fxc-front/src/app/redux/arcgis-slice.ts | 3 +- apps/fxc-front/src/app/redux/browser-slice.ts | 3 +- .../src/app/redux/live-track-slice.ts | 16 +- .../fxc-front/src/app/redux/location-slice.ts | 5 +- apps/fxc-front/src/app/redux/planner-slice.ts | 12 +- apps/fxc-front/src/app/redux/selectors.ts | 8 +- apps/fxc-front/src/app/redux/skyways-slice.ts | 5 +- apps/fxc-front/src/app/redux/store.ts | 5 +- apps/fxc-front/src/app/redux/track-slice.ts | 7 +- apps/fxc-front/src/app/redux/units-slice.ts | 6 +- apps/fxc-front/src/app/workers/optimizer.ts | 26 + apps/fxc-front/src/app/workers/track.ts | 3 +- apps/fxc-front/src/env.d.ts | 2 +- apps/fxc-front/src/flyxc.ts | 10 +- apps/fxc-front/vite.config.ts | 2 + apps/fxc-server/src/app/parser/geojson.ts | 2 +- apps/fxc-server/src/app/parser/igc.ts | 2 +- apps/fxc-server/src/app/parser/kml.ts | 2 +- apps/fxc-server/src/app/parser/parser.ts | 4 +- apps/fxc-server/src/app/parser/trk.ts | 2 +- apps/fxc-server/src/app/routes/admin.ts | 5 +- apps/fxc-server/src/app/routes/live-track.ts | 8 +- apps/fxc-server/src/app/routes/session.ts | 2 +- apps/fxc-server/src/app/routes/supporters.ts | 5 +- apps/fxc-server/src/app/routes/track.ts | 7 +- apps/fxc-server/src/app/routes/waypoints.ts | 3 +- apps/fxc-server/src/app/routes/zoleo.ts | 7 +- .../fxc-server/src/app/waypoints/waypoints.ts | 3 +- apps/misc/src/app/email_inreach.ts | 3 +- apps/misc/src/app/list_flymaster.ts | 2 +- apps/misc/src/app/list_trackers.ts | 4 +- apps/misc/src/app/list_tracks.ts | 5 +- apps/misc/src/app/migrate.ts | 8 +- apps/proxy/src/main.ts | 3 +- apps/run/src/app/airspace.ts | 7 +- apps/run/src/app/altitude.ts | 6 +- apps/run/src/app/process.ts | 5 +- apps/run/src/main.ts | 3 +- docker/.env | 2 + docker/docker-compose.yml | 24 + libs/common-node/src/lib/datastore.ts | 3 +- libs/common-node/src/lib/live-track-entity.ts | 5 +- libs/common-node/src/lib/redis.ts | 3 +- libs/common-node/src/lib/track-entity.ts | 6 +- libs/common-node/src/lib/validators.ts | 12 +- libs/common/src/lib/airspaces.ts | 4 +- libs/common/src/lib/aprs.test.ts | 3 +- libs/common/src/lib/distance.ts | 2 +- libs/common/src/lib/live-track-entity.ts | 2 +- libs/common/src/lib/live-track.ts | 3 +- libs/common/src/lib/models.ts | 15 +- libs/common/src/lib/proj.ts | 2 +- libs/optimizer/.babelrc | 3 + libs/optimizer/.eslintrc.json | 25 + libs/optimizer/README.md | 19 + libs/optimizer/jest.config.ts | 11 + libs/optimizer/package-lock.json | 97 +++ libs/optimizer/package.json | 12 + libs/optimizer/project.json | 36 + libs/optimizer/src/index.ts | 2 + libs/optimizer/src/lib/api.ts | 26 + libs/optimizer/src/lib/optimizer.spec.ts | 303 ++++++++ libs/optimizer/src/lib/optimizer.ts | 266 +++++++ libs/optimizer/src/lib/scoring-rules.ts | 98 +++ .../src/lib/utils/create-segments.ts | 31 + libs/optimizer/src/lib/utils/merge-tracks.ts | 22 + libs/optimizer/tsconfig.json | 22 + libs/optimizer/tsconfig.lib.json | 10 + libs/optimizer/tsconfig.spec.json | 9 + libs/vaadin-dom/src/lib/Binder.ts | 6 +- libs/vaadin-dom/src/lib/Field.ts | 9 +- libs/vaadin-nodom/src/lib/BinderNode.ts | 3 +- libs/vaadin-nodom/src/lib/NoDomBinder.ts | 13 +- libs/vaadin-nodom/src/lib/Validation.ts | 3 +- nx.json | 5 + package-lock.json | 680 ++++++++++++++---- package.json | 21 +- tsconfig.base.json | 1 + 171 files changed, 2253 insertions(+), 1190 deletions(-) create mode 100644 .verdaccio/config.yml create mode 100644 CONTRIBUTING.md delete mode 100644 apps/fxc-front/src/app/logic/score/league.ts delete mode 100644 apps/fxc-front/src/app/logic/score/league/czech.ts delete mode 100644 apps/fxc-front/src/app/logic/score/league/frcfd.ts delete mode 100644 apps/fxc-front/src/app/logic/score/league/leonardo.ts delete mode 100644 apps/fxc-front/src/app/logic/score/league/ukxcl.ts delete mode 100644 apps/fxc-front/src/app/logic/score/league/wxc.ts delete mode 100644 apps/fxc-front/src/app/logic/score/league/xcontest.ts delete mode 100644 apps/fxc-front/src/app/logic/score/measure.ts create mode 100644 apps/fxc-front/src/app/workers/optimizer.ts create mode 100644 docker/.env create mode 100644 docker/docker-compose.yml create mode 100644 libs/optimizer/.babelrc create mode 100644 libs/optimizer/.eslintrc.json create mode 100644 libs/optimizer/README.md create mode 100644 libs/optimizer/jest.config.ts create mode 100644 libs/optimizer/package-lock.json create mode 100644 libs/optimizer/package.json create mode 100644 libs/optimizer/project.json create mode 100644 libs/optimizer/src/index.ts create mode 100644 libs/optimizer/src/lib/api.ts create mode 100644 libs/optimizer/src/lib/optimizer.spec.ts create mode 100644 libs/optimizer/src/lib/optimizer.ts create mode 100644 libs/optimizer/src/lib/scoring-rules.ts create mode 100644 libs/optimizer/src/lib/utils/create-segments.ts create mode 100644 libs/optimizer/src/lib/utils/merge-tracks.ts create mode 100644 libs/optimizer/tsconfig.json create mode 100644 libs/optimizer/tsconfig.lib.json create mode 100644 libs/optimizer/tsconfig.spec.json diff --git a/.eslintrc.json b/.eslintrc.json index 67e3c191..b9c2deb0 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -19,7 +19,8 @@ ] } ], - "require-node-import-prefix/no-empty-import-prefix": "error" + "require-node-import-prefix/no-empty-import-prefix": "error", + "@typescript-eslint/consistent-type-imports": "error" } }, { diff --git a/.verdaccio/config.yml b/.verdaccio/config.yml new file mode 100644 index 00000000..a007fe82 --- /dev/null +++ b/.verdaccio/config.yml @@ -0,0 +1,28 @@ +# path to a directory with all packages +storage: ../tmp/local-registry/storage + +# a list of other known repositories we can talk to +uplinks: + npmjs: + url: https://registry.npmjs.org/ + maxage: 60m + +packages: + '**': + # give all users (including non-authenticated users) full access + # because it is a local registry + access: $all + publish: $all + unpublish: $all + + # if package is not available locally, proxy requests to npm registry + proxy: npmjs + +# log settings +logs: + type: stdout + format: pretty + level: warn + +publish: + allow_offline: true # set offline to true to allow publish offline diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 00000000..54a0fd9d --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,51 @@ +# How to contribute. + +## Required tools + +- node.js +- npm +- on mac-os, you have to install xcode command line developer tools (run xcode-select --install) +- gcloud +- docker +- the IDE of your choice + +## Project setup + +- run `npm install` +- add default key definitions + - `cp apps/fxc-front/src/app/keys.ts.dist apps/fxc-front/src/app/keys.ts` + - `cp libs/common/src/lib/keys.ts.dist libs/common/src/lib/keys.ts` + +### Simplistic configuration + +**redis server** + +- `cd docker; docker compose up -d redis` + +**pubsub** + +- `cd docker; docker compose up -d pubsub` + +**datastore** + +For the moment, it does not work with docker compose. But if you install the cloud-datastore-emulator, you will have a working configuration. + +**_Installation_** + +- `gcloud components install cloud-datastore-emulator` + +**_run the data store:_** + +- `gcloud beta emulators datastore start --data-dir=MY_DATA_DIR` + +**_before npm run dev:_** + +- open another shell. +- define the required environment variables:`eval $(gcloud beta emulators datastore --data-dir=MY_DATA_DIR env-init)` +- you can then run the application locally in this shell with `npm run dev` + +## Helpful commands + +`npx nx check` runs the build, lint, and test targets for all the projects. Nice to use before uploading a PR. + +`nx affected:test --all --parallel --maxParallel 10 --watch` will run the tests affected by your code changes. diff --git a/README.md b/README.md index 04c5e0c9..1efd8337 100644 --- a/README.md +++ b/README.md @@ -27,8 +27,6 @@ - Ufo 3d model by [pawinc](https://sketchfab.com/demarerik), - the many open source libraries, projects, and data sources used by FlyXc. -✨ _This workspace has been generated by [Nx, a Smart, fast and extensible build system.](https://nx.dev)_ ✨ - ## Support flyxc You can support the development of flyxc via the Buy me a Coffee platform @@ -36,3 +34,5 @@ You can support the development of flyxc via the Buy me a Coffee platform Buy Me A Coffee Any contribution is greatly appreciated! + +✨ _This workspace has been generated by [Nx, a Smart, fast and extensible build system.](https://nx.dev)_ ✨ diff --git a/apps/airspaces/src/app/create-geojson.ts b/apps/airspaces/src/app/create-geojson.ts index c129d455..1db41f64 100755 --- a/apps/airspaces/src/app/create-geojson.ts +++ b/apps/airspaces/src/app/create-geojson.ts @@ -6,7 +6,7 @@ import { readFileSync, writeFileSync } from 'node:fs'; import { join, resolve } from 'node:path'; import * as oaip from './parser/openaip'; import * as oair from './parser/openair'; -import { Airspace } from './parser/parser'; +import type { Airspace } from './parser/parser'; const GeoJSON = require('geojson'); // eslint-disable-line @typescript-eslint/no-var-requires // Filter out airspaces above: diff --git a/apps/airspaces/src/app/parser/openaip.ts b/apps/airspaces/src/app/parser/openaip.ts index 75afd9a8..700dce5d 100644 --- a/apps/airspaces/src/app/parser/openaip.ts +++ b/apps/airspaces/src/app/parser/openaip.ts @@ -1,5 +1,6 @@ import { Activity, Class, Type } from '@flyxc/common'; -import { Airspace, METER_PER_FEET, roundCoords } from './parser'; +import type { Airspace } from './parser'; +import { METER_PER_FEET, roundCoords } from './parser'; enum Unit { Meter = 0, diff --git a/apps/airspaces/src/app/parser/openair.ts b/apps/airspaces/src/app/parser/openair.ts index 42af2a1c..7d24624c 100644 --- a/apps/airspaces/src/app/parser/openair.ts +++ b/apps/airspaces/src/app/parser/openair.ts @@ -4,7 +4,8 @@ import { Activity, Class, Type, decodeClass } from '@flyxc/common'; import { computeDestinationPoint, getDistance, getGreatCircleBearing, sexagesimalToDecimal } from 'geolib'; -import { Airspace, METER_PER_FEET, roundCoords } from './parser'; +import type { Airspace } from './parser'; +import { METER_PER_FEET, roundCoords } from './parser'; const enum Direction { Clockwise = 1, diff --git a/apps/airspaces/src/app/parser/parser.ts b/apps/airspaces/src/app/parser/parser.ts index 8925d7a6..4d47ed6c 100644 --- a/apps/airspaces/src/app/parser/parser.ts +++ b/apps/airspaces/src/app/parser/parser.ts @@ -1,4 +1,5 @@ -import { AirspaceTyped, round } from '@flyxc/common'; +import type { AirspaceTyped } from '@flyxc/common'; +import { round } from '@flyxc/common'; export interface Airspace extends AirspaceTyped { polygon: [number, number][][]; diff --git a/apps/airspaces/src/app/upload-tiles-diff.ts b/apps/airspaces/src/app/upload-tiles-diff.ts index c5219d35..27a3c481 100644 --- a/apps/airspaces/src/app/upload-tiles-diff.ts +++ b/apps/airspaces/src/app/upload-tiles-diff.ts @@ -1,7 +1,8 @@ // Unzip tiles to Google Cloud Storage. import { parallelTasksWithTimeout } from '@flyxc/common'; -import { Bucket, Storage } from '@google-cloud/storage'; +import type { Bucket } from '@google-cloud/storage'; +import { Storage } from '@google-cloud/storage'; import { program } from 'commander'; import { existsSync, readFileSync } from 'node:fs'; import path, { join, resolve } from 'node:path'; diff --git a/apps/fetcher/src/app/elevation/arcgis.ts b/apps/fetcher/src/app/elevation/arcgis.ts index 816c21ad..91b5f70e 100644 --- a/apps/fetcher/src/app/elevation/arcgis.ts +++ b/apps/fetcher/src/app/elevation/arcgis.ts @@ -1,4 +1,4 @@ -import { LatLon } from '@flyxc/common'; +import type { LatLon } from '@flyxc/common'; // URL of the elevation service. // diff --git a/apps/fetcher/src/app/elevation/elevation.ts b/apps/fetcher/src/app/elevation/elevation.ts index d0dc86fc..0d89cd79 100644 --- a/apps/fetcher/src/app/elevation/elevation.ts +++ b/apps/fetcher/src/app/elevation/elevation.ts @@ -2,7 +2,8 @@ // // We mostly care about the AGL of the last fix. -import { fetchResponse, formatReqError, LatLon, protos } from '@flyxc/common'; +import type { LatLon, protos } from '@flyxc/common'; +import { fetchResponse, formatReqError } from '@flyxc/common'; import { getElevationUrl, parseElevationResponse } from './arcgis'; export interface ElevationUpdates { diff --git a/apps/fetcher/src/app/redis.ts b/apps/fetcher/src/app/redis.ts index 34492574..ad118261 100644 --- a/apps/fetcher/src/app/redis.ts +++ b/apps/fetcher/src/app/redis.ts @@ -1,13 +1,14 @@ import { Keys, protos, trackerNames } from '@flyxc/common'; import { pushListCap } from '@flyxc/common-node'; -import { Datastore } from '@google-cloud/datastore'; -import { ChainableCommander, Redis } from 'ioredis'; +import type { Datastore } from '@google-cloud/datastore'; +import type { ChainableCommander, Redis } from 'ioredis'; import * as nos from 'node-os-utils'; import * as zlib from 'node:zlib'; -import { ElevationUpdates } from './elevation/elevation'; +import type { ElevationUpdates } from './elevation/elevation'; import { exportToStorage } from './state/serialize'; import { BUCKET_NAME, EXPORT_FILE_SEC, PERIODIC_STATE_PATH } from './state/state'; -import { SyncStatus, syncFromDatastore } from './state/sync'; +import type { SyncStatus } from './state/sync'; +import { syncFromDatastore } from './state/sync'; // Logs for syncs. export function addSyncLogs(pipeline: ChainableCommander, status: SyncStatus, timeSec: number) { diff --git a/apps/fetcher/src/app/state/serialize.ts b/apps/fetcher/src/app/state/serialize.ts index 7b3a9c22..5640c65b 100644 --- a/apps/fetcher/src/app/state/serialize.ts +++ b/apps/fetcher/src/app/state/serialize.ts @@ -1,5 +1,6 @@ import { protos } from '@flyxc/common'; -import { File, Storage } from '@google-cloud/storage'; +import type { File } from '@google-cloud/storage'; +import { Storage } from '@google-cloud/storage'; import { isBefore, lightFormat, parse, sub } from 'date-fns'; import * as zlib from 'node:zlib'; diff --git a/apps/fetcher/src/app/state/state.ts b/apps/fetcher/src/app/state/state.ts index db902934..5b162481 100644 --- a/apps/fetcher/src/app/state/state.ts +++ b/apps/fetcher/src/app/state/state.ts @@ -1,4 +1,4 @@ -import { protos } from '@flyxc/common'; +import type { protos } from '@flyxc/common'; import { environment } from '../../environments/environment.prod'; import { importFromStorage } from './serialize'; diff --git a/apps/fetcher/src/app/state/sync.test.ts b/apps/fetcher/src/app/state/sync.test.ts index 1da3e992..c4353ba0 100644 --- a/apps/fetcher/src/app/state/sync.test.ts +++ b/apps/fetcher/src/app/state/sync.test.ts @@ -1,5 +1,7 @@ -import { LiveTrackEntity, protos, TrackerEntity, TrackerNames } from '@flyxc/common'; -import { Datastore, Key } from '@google-cloud/datastore'; +import type { LiveTrackEntity, TrackerEntity, TrackerNames } from '@flyxc/common'; +import { protos } from '@flyxc/common'; +import type { Key } from '@google-cloud/datastore'; +import { Datastore } from '@google-cloud/datastore'; import { createInitState } from './state'; import { syncLiveTrack } from './sync'; diff --git a/apps/fetcher/src/app/state/sync.ts b/apps/fetcher/src/app/state/sync.ts index 1548d0fb..223c60bb 100644 --- a/apps/fetcher/src/app/state/sync.ts +++ b/apps/fetcher/src/app/state/sync.ts @@ -1,7 +1,6 @@ +import type { LiveTrackEntity, TrackerNames } from '@flyxc/common'; import { LIVE_REFRESH_SEC, - LiveTrackEntity, - TrackerNames, protos, trackerNames, trackerValidators, @@ -10,7 +9,7 @@ import { } from '@flyxc/common'; import { LIVE_TRACK_TABLE } from '@flyxc/common-node'; import { Datastore } from '@google-cloud/datastore'; -import { RunQueryResponse } from '@google-cloud/datastore/build/src/query'; +import type { RunQueryResponse } from '@google-cloud/datastore/build/src/query'; import { FULL_SYNC_SEC, PARTIAL_SYNC_SEC } from './state'; const BATCH_SIZE = 50; diff --git a/apps/fetcher/src/app/trackers/flymaster.ts b/apps/fetcher/src/app/trackers/flymaster.ts index 3412490d..b983b8dd 100644 --- a/apps/fetcher/src/app/trackers/flymaster.ts +++ b/apps/fetcher/src/app/trackers/flymaster.ts @@ -2,19 +2,20 @@ // // https://lt.flymaster.net/wlb/? +import type { protos, TrackerNames } from '@flyxc/common'; import { fetchResponse, formatReqError, LIVE_MINIMAL_INTERVAL_SEC, - protos, removeBeforeFromLiveTrack, simplifyLiveTrack, - TrackerNames, validateFlymasterAccount, SecretKeys, } from '@flyxc/common'; -import { LivePoint, makeLiveTrack } from './live-track'; -import { TrackerFetcher, TrackerUpdates } from './tracker'; +import type { LivePoint } from './live-track'; +import { makeLiveTrack } from './live-track'; +import type { TrackerUpdates } from './tracker'; +import { TrackerFetcher } from './tracker'; // Latency before a fix is available (usually ~4min). const FLYMASTER_LATENCY_SEC = 5 * 60; diff --git a/apps/fetcher/src/app/trackers/flyme.ts b/apps/fetcher/src/app/trackers/flyme.ts index 7fc55d60..138de5ea 100644 --- a/apps/fetcher/src/app/trackers/flyme.ts +++ b/apps/fetcher/src/app/trackers/flyme.ts @@ -2,9 +2,12 @@ // // http://xcglobe.com/flyme/ -import { fetchResponse, formatReqError, protos, SecretKeys, TrackerNames, validateFlymeAccount } from '@flyxc/common'; -import { LivePoint, makeLiveTrack } from './live-track'; -import { TrackerFetcher, TrackerUpdates } from './tracker'; +import type { protos, TrackerNames } from '@flyxc/common'; +import { fetchResponse, formatReqError, SecretKeys, validateFlymeAccount } from '@flyxc/common'; +import type { LivePoint } from './live-track'; +import { makeLiveTrack } from './live-track'; +import type { TrackerUpdates } from './tracker'; +import { TrackerFetcher } from './tracker'; export class FlymeFetcher extends TrackerFetcher { protected getTrackerName(): TrackerNames { diff --git a/apps/fetcher/src/app/trackers/inreach.ts b/apps/fetcher/src/app/trackers/inreach.ts index b5be496a..6df8a1ed 100644 --- a/apps/fetcher/src/app/trackers/inreach.ts +++ b/apps/fetcher/src/app/trackers/inreach.ts @@ -4,6 +4,7 @@ // - https://support.garmin.com/en-US/?faq=tdlDCyo1fJ5UxjUbA9rMY8 (offline) // - https://web.archive.org/web/20230328084014/https://support.garmin.com/en-US/?faq=tdlDCyo1fJ5UxjUbA9rMY8 +import type { TrackerNames } from '@flyxc/common'; import { fetchResponse, formatReqError, @@ -14,14 +15,15 @@ import { protos, SecretKeys, simplifyLiveTrack, - TrackerNames, validateInreachAccount, } from '@flyxc/common'; import { pushListCap } from '@flyxc/common-node'; import { DOMParser } from '@xmldom/xmldom'; -import { LivePoint, makeLiveTrack } from './live-track'; +import type { LivePoint } from './live-track'; +import { makeLiveTrack } from './live-track'; import { Proxies } from './proxies'; -import { TrackerFetcher, TrackerUpdates } from './tracker'; +import type { TrackerUpdates } from './tracker'; +import { TrackerFetcher } from './tracker'; // Local state let useProxyUntilS = 0; diff --git a/apps/fetcher/src/app/trackers/live-track.test.ts b/apps/fetcher/src/app/trackers/live-track.test.ts index 979b10c4..43a2bc66 100644 --- a/apps/fetcher/src/app/trackers/live-track.test.ts +++ b/apps/fetcher/src/app/trackers/live-track.test.ts @@ -1,6 +1,7 @@ import { getTrackerName, isEmergencyFix, isLowBatFix, isValidFix, LiveTrackFlag, trackerIdByName } from '@flyxc/common'; import { computeDestinationPoint } from 'geolib'; -import { LivePoint, makeLiveTrack } from './live-track'; +import type { LivePoint } from './live-track'; +import { makeLiveTrack } from './live-track'; describe('makeLiveTrack', () => { it('should order the points in chronological order', () => { diff --git a/apps/fetcher/src/app/trackers/live-track.ts b/apps/fetcher/src/app/trackers/live-track.ts index a4671b52..fb55041d 100644 --- a/apps/fetcher/src/app/trackers/live-track.ts +++ b/apps/fetcher/src/app/trackers/live-track.ts @@ -1,4 +1,5 @@ -import { getTrackerFlags as getLiveTrackFlags, protos, round, TrackerNames, UfoFleetNames } from '@flyxc/common'; +import type { TrackerNames, UfoFleetNames } from '@flyxc/common'; +import { getTrackerFlags as getLiveTrackFlags, protos, round } from '@flyxc/common'; import { getDistance } from 'geolib'; export interface LivePoint { diff --git a/apps/fetcher/src/app/trackers/ogn-client.ts b/apps/fetcher/src/app/trackers/ogn-client.ts index 278a53cb..3dc8a9a3 100644 --- a/apps/fetcher/src/app/trackers/ogn-client.ts +++ b/apps/fetcher/src/app/trackers/ogn-client.ts @@ -1,8 +1,10 @@ // Client for the OGN APRS server -import { AprsPosition, parseAprsPosition } from '@flyxc/common'; +import type { AprsPosition } from '@flyxc/common'; +import { parseAprsPosition } from '@flyxc/common'; import { Socket } from 'node:net'; -import readline, { Interface } from 'node:readline'; +import type { Interface } from 'node:readline'; +import readline from 'node:readline'; const VERSION = '1.0'; const OGN_FAST_ID_REGEXP = /\bid[0-9a-z]{2}(?[0-9a-z]{6})\b/i; diff --git a/apps/fetcher/src/app/trackers/ogn-push.ts b/apps/fetcher/src/app/trackers/ogn-push.ts index 2e4af919..dd8f719b 100644 --- a/apps/fetcher/src/app/trackers/ogn-push.ts +++ b/apps/fetcher/src/app/trackers/ogn-push.ts @@ -1,6 +1,7 @@ -import { AprsPosition, findIndexes, generateAprsPosition, getFixSpeed, getTrackerName, protos } from '@flyxc/common'; +import type { AprsPosition, protos } from '@flyxc/common'; +import { findIndexes, generateAprsPosition, getFixSpeed, getTrackerName } from '@flyxc/common'; import { getRhumbLineBearing } from 'geolib'; -import { OgnClient } from './ogn-client'; +import type { OgnClient } from './ogn-client'; // Don't push obsolete fixes. const RECENT_FIX_AGE_SEC = 10 * 60; diff --git a/apps/fetcher/src/app/trackers/ogn.ts b/apps/fetcher/src/app/trackers/ogn.ts index b2677447..66e3bf76 100644 --- a/apps/fetcher/src/app/trackers/ogn.ts +++ b/apps/fetcher/src/app/trackers/ogn.ts @@ -2,12 +2,15 @@ // // See http://wiki.glidernet.org/. -import { protos, TrackerNames, validateOgnAccount } from '@flyxc/common'; -import { ChainableCommander } from 'ioredis'; -import { LivePoint, makeLiveTrack } from './live-track'; -import { OgnClient } from './ogn-client'; +import type { protos, TrackerNames } from '@flyxc/common'; +import { validateOgnAccount } from '@flyxc/common'; +import type { ChainableCommander } from 'ioredis'; +import type { LivePoint } from './live-track'; +import { makeLiveTrack } from './live-track'; +import type { OgnClient } from './ogn-client'; import { OgnPusher } from './ogn-push'; -import { TrackerFetcher, TrackerUpdates } from './tracker'; +import type { TrackerUpdates } from './tracker'; +import { TrackerFetcher } from './tracker'; // Push positions to OGN. let ognPusher: OgnPusher | undefined; diff --git a/apps/fetcher/src/app/trackers/refresh.ts b/apps/fetcher/src/app/trackers/refresh.ts index def740f9..527e17e5 100644 --- a/apps/fetcher/src/app/trackers/refresh.ts +++ b/apps/fetcher/src/app/trackers/refresh.ts @@ -1,3 +1,4 @@ +import type { protos } from '@flyxc/common'; import { Keys, LIVE_AGE_OLD_SEC, @@ -7,13 +8,12 @@ import { LIVE_TRACKER_RETENTION_SEC, SecretKeys, mergeLiveTracks, - protos, removeBeforeFromLiveTrack, simplifyLiveTrack, } from '@flyxc/common'; import { pushListCap } from '@flyxc/common-node'; -import { Datastore } from '@google-cloud/datastore'; -import { ChainableCommander, Redis } from 'ioredis'; +import type { Datastore } from '@google-cloud/datastore'; +import type { ChainableCommander, Redis } from 'ioredis'; import { patchLastFixAGL } from '../elevation/elevation'; import { addElevationLogs } from '../redis'; import { FlymasterFetcher } from './flymaster'; @@ -23,7 +23,7 @@ import { OgnFetcher } from './ogn'; import { OGN_HOST, OGN_PORT, OgnClient } from './ogn-client'; import { SkylinesFetcher } from './skylines'; import { SpotFetcher } from './spot'; -import { TrackerUpdates } from './tracker'; +import type { TrackerUpdates } from './tracker'; import { XcontestFetcher } from './xcontest'; import { ZoleoFetcher } from './zoleo'; diff --git a/apps/fetcher/src/app/trackers/skylines.ts b/apps/fetcher/src/app/trackers/skylines.ts index 79be5dcb..b7205780 100644 --- a/apps/fetcher/src/app/trackers/skylines.ts +++ b/apps/fetcher/src/app/trackers/skylines.ts @@ -2,19 +2,20 @@ // // See https://github.com/skylines-project/skylines. +import type { protos, TrackerNames } from '@flyxc/common'; import { decodeDeltas, fetchResponse, formatReqError, LIVE_MINIMAL_INTERVAL_SEC, - protos, removeBeforeFromLiveTrack, simplifyLiveTrack, - TrackerNames, validateSkylinesAccount, } from '@flyxc/common'; -import { LivePoint, makeLiveTrack } from './live-track'; -import { TrackerFetcher, TrackerUpdates } from './tracker'; +import type { LivePoint } from './live-track'; +import { makeLiveTrack } from './live-track'; +import type { TrackerUpdates } from './tracker'; +import { TrackerFetcher } from './tracker'; const SECONDS_IN_DAY = 60 * 60 * 24; diff --git a/apps/fetcher/src/app/trackers/spot.test.ts b/apps/fetcher/src/app/trackers/spot.test.ts index ca68bf86..97ab967e 100644 --- a/apps/fetcher/src/app/trackers/spot.test.ts +++ b/apps/fetcher/src/app/trackers/spot.test.ts @@ -2,7 +2,7 @@ const spot2Feed = require('./fixtures/spot2.txt'); const spot3Feed = require('./fixtures/spot3.txt'); -import { LivePoint } from './live-track'; +import type { LivePoint } from './live-track'; import { parse } from './spot'; describe('Parse JSON feed', () => { diff --git a/apps/fetcher/src/app/trackers/spot.ts b/apps/fetcher/src/app/trackers/spot.ts index 18244eba..60def744 100644 --- a/apps/fetcher/src/app/trackers/spot.ts +++ b/apps/fetcher/src/app/trackers/spot.ts @@ -2,17 +2,18 @@ // // See https://www.findmespot.com/en-us/support/spot-trace/get-help/general/spot-api-support. +import type { protos, TrackerNames } from '@flyxc/common'; import { fetchResponse, formatReqError, LIVE_MINIMAL_INTERVAL_SEC, - protos, simplifyLiveTrack, - TrackerNames, validateSpotAccount, } from '@flyxc/common'; -import { LivePoint, makeLiveTrack } from './live-track'; -import { TrackerFetcher, TrackerUpdates } from './tracker'; +import type { LivePoint } from './live-track'; +import { makeLiveTrack } from './live-track'; +import type { TrackerUpdates } from './tracker'; +import { TrackerFetcher } from './tracker'; export class SpotFetcher extends TrackerFetcher { protected getTrackerName(): TrackerNames { diff --git a/apps/fetcher/src/app/trackers/tracker.ts b/apps/fetcher/src/app/trackers/tracker.ts index dc0b6ed9..928f415b 100644 --- a/apps/fetcher/src/app/trackers/tracker.ts +++ b/apps/fetcher/src/app/trackers/tracker.ts @@ -1,7 +1,8 @@ // Base class for fetching tracker updates. -import { LIVE_REFRESH_SEC, LIVE_TRACKER_RETENTION_SEC, protos, TrackerNames } from '@flyxc/common'; -import { ChainableCommander } from 'ioredis'; +import type { protos, TrackerNames } from '@flyxc/common'; +import { LIVE_REFRESH_SEC, LIVE_TRACKER_RETENTION_SEC } from '@flyxc/common'; +import type { ChainableCommander } from 'ioredis'; // Updates for a tick of a tracker type (InReach, Spot, ...). export interface TrackerUpdates { diff --git a/apps/fetcher/src/app/trackers/xcontest.test.ts b/apps/fetcher/src/app/trackers/xcontest.test.ts index c07989b9..bbf53511 100644 --- a/apps/fetcher/src/app/trackers/xcontest.test.ts +++ b/apps/fetcher/src/app/trackers/xcontest.test.ts @@ -2,7 +2,8 @@ const xcontestUsers = require('./fixtures/xcontest-live-users.json'); const xcontestTrack = require('./fixtures/xcontest-live-track.json'); -import { XContestFlight, parseLiveTrack, parseLiveUsers } from './xcontest'; +import type { XContestFlight } from './xcontest'; +import { parseLiveTrack, parseLiveUsers } from './xcontest'; describe('Parse XContest json', () => { test('it should parse users', () => { diff --git a/apps/fetcher/src/app/trackers/xcontest.ts b/apps/fetcher/src/app/trackers/xcontest.ts index 6ba0643c..bd107b7d 100644 --- a/apps/fetcher/src/app/trackers/xcontest.ts +++ b/apps/fetcher/src/app/trackers/xcontest.ts @@ -2,18 +2,19 @@ // // https://live.xcontest.org/ +import type { protos, TrackerNames } from '@flyxc/common'; import { fetchResponse, formatReqError, LIVE_TRACKER_RETENTION_SEC, parallelTasksWithTimeout, - protos, SecretKeys, - TrackerNames, validateXContestAccount, } from '@flyxc/common'; -import { LivePoint, makeLiveTrack } from './live-track'; -import { TrackerFetcher, TrackerUpdates } from './tracker'; +import type { LivePoint } from './live-track'; +import { makeLiveTrack } from './live-track'; +import type { TrackerUpdates } from './tracker'; +import { TrackerFetcher } from './tracker'; // duration to fetch const FETCH_MS = 4 * 60 * 1000; diff --git a/apps/fetcher/src/app/trackers/zoleo.test.ts b/apps/fetcher/src/app/trackers/zoleo.test.ts index 48d20b45..648d5311 100644 --- a/apps/fetcher/src/app/trackers/zoleo.test.ts +++ b/apps/fetcher/src/app/trackers/zoleo.test.ts @@ -1,4 +1,4 @@ -import { ZoleoMessage } from '@flyxc/common-node'; +import type { ZoleoMessage } from '@flyxc/common-node'; import { parse } from './zoleo'; describe('parse', () => { diff --git a/apps/fetcher/src/app/trackers/zoleo.ts b/apps/fetcher/src/app/trackers/zoleo.ts index 26817498..0e2369a0 100644 --- a/apps/fetcher/src/app/trackers/zoleo.ts +++ b/apps/fetcher/src/app/trackers/zoleo.ts @@ -1,9 +1,13 @@ -import { Keys, TrackerNames, protos, validateZoleoAccount } from '@flyxc/common'; -import { LIVE_TRACK_TABLE, ZoleoMessage } from '@flyxc/common-node'; +import type { TrackerNames, protos } from '@flyxc/common'; +import { Keys, validateZoleoAccount } from '@flyxc/common'; +import type { ZoleoMessage } from '@flyxc/common-node'; +import { LIVE_TRACK_TABLE } from '@flyxc/common-node'; import { Datastore } from '@google-cloud/datastore'; -import { ChainableCommander, Redis } from 'ioredis'; -import { LivePoint, makeLiveTrack } from './live-track'; -import { TrackerFetcher, TrackerUpdates } from './tracker'; +import type { ChainableCommander, Redis } from 'ioredis'; +import type { LivePoint } from './live-track'; +import { makeLiveTrack } from './live-track'; +import type { TrackerUpdates } from './tracker'; +import { TrackerFetcher } from './tracker'; export class ZoleoFetcher extends TrackerFetcher { constructor( diff --git a/apps/fetcher/src/app/ufos/aviant.ts b/apps/fetcher/src/app/ufos/aviant.ts index 63827f68..a358fc0a 100644 --- a/apps/fetcher/src/app/ufos/aviant.ts +++ b/apps/fetcher/src/app/ufos/aviant.ts @@ -1,8 +1,11 @@ // https://www.aviant.no/ -import { fetchResponse, formatReqError, SecretKeys, UfoFleetNames } from '@flyxc/common'; -import { LivePoint, makeLiveTrack } from '../trackers/live-track'; -import { UfoFleetFetcher, UfoFleetUpdates } from './ufo'; +import type { UfoFleetNames } from '@flyxc/common'; +import { fetchResponse, formatReqError, SecretKeys } from '@flyxc/common'; +import type { LivePoint } from '../trackers/live-track'; +import { makeLiveTrack } from '../trackers/live-track'; +import type { UfoFleetUpdates } from './ufo'; +import { UfoFleetFetcher } from './ufo'; export class AviantFetcher extends UfoFleetFetcher { protected getFleetName(): UfoFleetNames { diff --git a/apps/fetcher/src/app/ufos/refresh.ts b/apps/fetcher/src/app/ufos/refresh.ts index 681cc03b..b1ccc820 100644 --- a/apps/fetcher/src/app/ufos/refresh.ts +++ b/apps/fetcher/src/app/ufos/refresh.ts @@ -1,17 +1,17 @@ +import type { protos } from '@flyxc/common'; import { Keys, LIVE_FETCH_TIMEOUT_SEC, LIVE_MINIMAL_INTERVAL_SEC, LIVE_UFO_RETENTION_SEC, mergeLiveTracks, - protos, removeBeforeFromLiveTrack, simplifyLiveTrack, } from '@flyxc/common'; import { pushListCap } from '@flyxc/common-node'; -import { ChainableCommander } from 'ioredis'; +import type { ChainableCommander } from 'ioredis'; import { AviantFetcher } from './aviant'; -import { UfoFleetUpdates } from './ufo'; +import type { UfoFleetUpdates } from './ufo'; export async function resfreshUfoFleets(pipeline: ChainableCommander, state: protos.FetcherState) { const fetchers = [new AviantFetcher(state, pipeline)]; diff --git a/apps/fetcher/src/app/ufos/ufo.ts b/apps/fetcher/src/app/ufos/ufo.ts index b0c04a8d..c1655dff 100644 --- a/apps/fetcher/src/app/ufos/ufo.ts +++ b/apps/fetcher/src/app/ufos/ufo.ts @@ -1,5 +1,6 @@ -import { LIVE_REFRESH_SEC, protos, UfoFleetNames } from '@flyxc/common'; -import { ChainableCommander } from 'ioredis'; +import type { UfoFleetNames } from '@flyxc/common'; +import { LIVE_REFRESH_SEC, protos } from '@flyxc/common'; +import type { ChainableCommander } from 'ioredis'; // Updates for a tick of a tracker type (InReach, Spot, ...). export interface UfoFleetUpdates { diff --git a/apps/fetcher/src/fetcher.ts b/apps/fetcher/src/fetcher.ts index cb7b8f6c..44689ebc 100644 --- a/apps/fetcher/src/fetcher.ts +++ b/apps/fetcher/src/fetcher.ts @@ -10,9 +10,9 @@ import { removeDeviceFromLiveTrack, } from '@flyxc/common'; import { getDatastore, getRedisClient, pushListCap } from '@flyxc/common-node'; -import { Datastore } from '@google-cloud/datastore'; +import type { Datastore } from '@google-cloud/datastore'; import { program } from 'commander'; -import { ChainableCommander } from 'ioredis'; +import type { ChainableCommander } from 'ioredis'; import process from 'node:process'; import { addExportLogs, addHostInfo, addStateLogs, addSyncLogs, HandleCommand } from './app/redis'; import { createStateArchive, exportToStorage } from './app/state/serialize'; diff --git a/apps/fxc-front/project.json b/apps/fxc-front/project.json index 601ae14d..02618c12 100644 --- a/apps/fxc-front/project.json +++ b/apps/fxc-front/project.json @@ -7,6 +7,7 @@ "targets": { "build": { "executor": "@nx/vite:build", + "dependsOn": ["optimizer"], "outputs": ["{options.outputPath}"], "defaultConfiguration": "production", "options": { diff --git a/apps/fxc-front/src/app/components/2d/airspace-element.ts b/apps/fxc-front/src/app/components/2d/airspace-element.ts index 669e3155..9ba87a25 100644 --- a/apps/fxc-front/src/app/components/2d/airspace-element.ts +++ b/apps/fxc-front/src/app/components/2d/airspace-element.ts @@ -1,11 +1,13 @@ import * as common from '@flyxc/common'; -import { LitElement, PropertyValues } from 'lit'; +import type { PropertyValues } from 'lit'; +import { LitElement } from 'lit'; import { customElement, property, state } from 'lit/decorators.js'; import { connect } from 'pwa-helpers'; import { AspMapType, AspZoomMapType, getAirspaceList } from '../../logic/airspaces'; import * as sel from '../../redux/selectors'; -import { RootState, store } from '../../redux/store'; +import type { RootState } from '../../redux/store'; +import { store } from '../../redux/store'; @customElement('airspace-element') export class AirspaceElement extends connect(store)(LitElement) { diff --git a/apps/fxc-front/src/app/components/2d/controls-element.ts b/apps/fxc-front/src/app/components/2d/controls-element.ts index e0d23187..28df905e 100644 --- a/apps/fxc-front/src/app/components/2d/controls-element.ts +++ b/apps/fxc-front/src/app/components/2d/controls-element.ts @@ -6,14 +6,16 @@ import './path-element'; import './skyways-element'; import './tracking-element'; -import * as common from '@flyxc/common'; -import { css, CSSResult, html, LitElement, TemplateResult } from 'lit'; +import type * as common from '@flyxc/common'; +import type { CSSResult, TemplateResult } from 'lit'; +import { css, html, LitElement } from 'lit'; import { customElement, property, state } from 'lit/decorators.js'; import { when } from 'lit/directives/when.js'; import { connect } from 'pwa-helpers'; -import * as units from '../../logic/units'; +import type * as units from '../../logic/units'; import * as sel from '../../redux/selectors'; -import { RootState, store } from '../../redux/store'; +import type { RootState } from '../../redux/store'; +import { store } from '../../redux/store'; @customElement('controls-element') export class ControlsElement extends connect(store)(LitElement) { diff --git a/apps/fxc-front/src/app/components/2d/line-element.ts b/apps/fxc-front/src/app/components/2d/line-element.ts index 88e27c4e..a8109832 100644 --- a/apps/fxc-front/src/app/components/2d/line-element.ts +++ b/apps/fxc-front/src/app/components/2d/line-element.ts @@ -1,10 +1,12 @@ -import * as common from '@flyxc/common'; -import { LitElement, PropertyValues } from 'lit'; +import type * as common from '@flyxc/common'; +import type { PropertyValues } from 'lit'; +import { LitElement } from 'lit'; import { customElement, property, state } from 'lit/decorators.js'; import { connect } from 'pwa-helpers'; import * as sel from '../../redux/selectors'; -import { RootState, store } from '../../redux/store'; +import type { RootState } from '../../redux/store'; +import { store } from '../../redux/store'; const INACTIVE_OPACITY = 0.5; diff --git a/apps/fxc-front/src/app/components/2d/map-element.ts b/apps/fxc-front/src/app/components/2d/map-element.ts index 20486e19..56936ee8 100644 --- a/apps/fxc-front/src/app/components/2d/map-element.ts +++ b/apps/fxc-front/src/app/components/2d/map-element.ts @@ -1,10 +1,12 @@ -import { findClosestFix, LatLon, LatLonAlt, pixelCoordinates, RuntimeTrack } from '@flyxc/common'; +import type { LatLon, LatLonAlt, RuntimeTrack } from '@flyxc/common'; +import { findClosestFix, pixelCoordinates } from '@flyxc/common'; import { Loader } from '@googlemaps/js-api-loader'; -import { html, LitElement, PropertyValues, svg, TemplateResult } from 'lit'; +import type { PropertyValues, TemplateResult } from 'lit'; +import { html, LitElement, svg } from 'lit'; import { customElement, property, state } from 'lit/decorators.js'; import { repeat } from 'lit/directives/repeat.js'; import { when } from 'lit/directives/when.js'; -import { UnsubscribeHandle } from 'micro-typed-events'; +import type { UnsubscribeHandle } from 'micro-typed-events'; import { connect } from 'pwa-helpers'; import simplify from 'simplify-path'; import { getApiKeyAndHost } from '../../apikey'; @@ -13,7 +15,8 @@ import { setApiLoading, setTimeSec } from '../../redux/app-slice'; import { setCurrentLocation, setCurrentZoom } from '../../redux/location-slice'; import { setIsFreeDrawing, setRoute } from '../../redux/planner-slice'; import * as sel from '../../redux/selectors'; -import { RootState, store } from '../../redux/store'; +import type { RootState } from '../../redux/store'; +import { store } from '../../redux/store'; import { setCurrentTrackId } from '../../redux/track-slice'; import { ControlsElement } from './controls-element'; import { LineElement } from './line-element'; @@ -329,7 +332,7 @@ export class MapElement extends connect(store)(LitElement) { this.pointerEventId = undefined; this.freeDrawPath = ''; store.dispatch(setIsFreeDrawing(false)); - this.pathPoints = simplify(this.pathPoints, 30); + this.pathPoints = simplify(this.pathPoints, 15); let encodedRoute = ''; if (this.pathPoints.length >= 2 && this.map) { const proj = this.map.getProjection() as google.maps.Projection; diff --git a/apps/fxc-front/src/app/components/2d/marker-element.ts b/apps/fxc-front/src/app/components/2d/marker-element.ts index b901fa31..2e1b8f3e 100644 --- a/apps/fxc-front/src/app/components/2d/marker-element.ts +++ b/apps/fxc-front/src/app/components/2d/marker-element.ts @@ -1,10 +1,12 @@ -import * as common from '@flyxc/common'; -import { LitElement, PropertyValues, html, nothing } from 'lit'; +import type * as common from '@flyxc/common'; +import type { PropertyValues } from 'lit'; +import { LitElement, html, nothing } from 'lit'; import { customElement, property, state } from 'lit/decorators.js'; import { connect } from 'pwa-helpers'; import * as units from '../../logic/units'; import * as sel from '../../redux/selectors'; -import { RootState, store } from '../../redux/store'; +import type { RootState } from '../../redux/store'; +import { store } from '../../redux/store'; import { setCurrentTrackId } from '../../redux/track-slice'; import './adv-marker-element'; diff --git a/apps/fxc-front/src/app/components/2d/path-element.ts b/apps/fxc-front/src/app/components/2d/path-element.ts index 4ad48940..8eef9498 100644 --- a/apps/fxc-front/src/app/components/2d/path-element.ts +++ b/apps/fxc-front/src/app/components/2d/path-element.ts @@ -1,8 +1,9 @@ import '../ui/share-modal'; import '../ui/waypoint-modal'; -import { LatLon } from '@flyxc/common'; -import { LitElement, PropertyValues } from 'lit'; +import type { LatLon } from '@flyxc/common'; +import type { PropertyValues } from 'lit'; +import { LitElement } from 'lit'; import { customElement, property, state } from 'lit/decorators.js'; import { connect } from 'pwa-helpers'; @@ -13,12 +14,17 @@ import { FaiSectors } from '../../gm/fai-sectors'; import { addAltitude } from '../../logic/elevation'; import { getCurrentUrl, pushCurrentState } from '../../logic/history'; import { drawRoute } from '../../logic/messages'; -import { LEAGUES } from '../../logic/score/league/leagues'; -import { Measure } from '../../logic/score/measure'; -import { CircuitType, Score } from '../../logic/score/scorer'; +import { Score } from '../../logic/score/scorer'; import { setDistance, setEnabled, setRoute, setScore } from '../../redux/planner-slice'; -import { RootState, store } from '../../redux/store'; -import { PlannerElement } from './planner-element'; +import type { RootState } from '../../redux/store'; +import { store } from '../../redux/store'; +import type { PlannerElement } from './planner-element'; +import { getScoringRuleName } from '../../logic/score/league/leagues'; +import type { LeagueCode } from '../../logic/score/league/leagues'; +import ScoringWorker from '../../workers/optimizer?worker'; +import type { Request as WorkerRequest, Response as WorkerResponse } from '../../workers/optimizer'; +import { CircuitType } from '@flyxc/optimizer/lib/api'; +import type { ScoringResult } from '@flyxc/optimizer/lib/optimizer'; // Route color by circuit type. const ROUTE_STROKE_COLORS = { @@ -44,7 +50,7 @@ export class PathElement extends connect(store)(LitElement) { @state() private enabled = false; @state() - private league = 'xc'; + private league: LeagueCode = 'xc'; @state() private encodedRoute = ''; @state() @@ -60,6 +66,8 @@ export class PathElement extends connect(store)(LitElement) { private closingSector?: ClosingSector; private faiSectors?: FaiSectors; private plannerElement?: PlannerElement; + private scoringRequestId = 0; + private scoringWorker?: Worker; stateChanged(state: RootState): void { this.league = state.planner.league; @@ -191,33 +199,53 @@ export class PathElement extends connect(store)(LitElement) { // Optimize the route and draw the optimize lines and sectors. private optimize(): void { - if (!this.line || this.line.getPath().getLength() < 2) { + const { line } = this; + if (!line || line.getPath().getLength() < 2 || this.doNotSyncState) { return; } - const line = this.line; - store.dispatch(setDistance(google.maps.geometry.spherical.computeLength(line.getPath()))); const points = this.getPathPoints(); - const measure = new Measure(points); - const scores = LEAGUES[this.league].score(measure); - scores.sort((score1, score2) => score2.points - score1.points); - const score = scores[0]; + if (!this.scoringWorker) { + this.scoringWorker = new ScoringWorker(); + this.scoringWorker.onmessage = (msg: MessageEvent) => { + if (msg.data.id == this.scoringRequestId) { + this.optimizerCallback(msg.data.response); + } + }; + } + + const request: WorkerRequest = { + request: { + track: { + points: points.map((point, i) => ({ ...point, alt: 0, timeSec: i * 60 })), + }, + ruleName: getScoringRuleName(this.league), + }, + id: ++this.scoringRequestId, + }; + + this.scoringWorker.postMessage(request); + } + + private optimizerCallback(result: ScoringResult): void { + const score = this.toScore(result); + store.dispatch(setDistance(score.distanceM)); store.dispatch(setScore(score)); - let optimizedPath = score.indexes.map((index) => new google.maps.LatLng(points[index].lat, points[index].lon)); - if (score.circuit == CircuitType.FlatTriangle || score.circuit == CircuitType.FaiTriangle) { - optimizedPath = [optimizedPath[1], optimizedPath[2], optimizedPath[3], optimizedPath[1]]; - } else if (score.circuit == CircuitType.OutAndReturn) { - optimizedPath = [optimizedPath[1], optimizedPath[2]]; - } + const path = [result.startPoint, ...result.turnpoints, result.endPoint] + .filter((p) => p != null) + .map((p) => ({ lat: p!.lat, lng: p!.lon })); - if (!this.optimizedLine) { - this, (this.optimizedLine = new google.maps.Polyline()); + if (result.circuit != CircuitType.OpenDistance) { + path.push(path[0]); } + + this.optimizedLine ??= new google.maps.Polyline(); + this.optimizedLine.setOptions({ map: this.map, - path: optimizedPath, + path, strokeColor: ROUTE_STROKE_COLORS[score.circuit], strokeOpacity: 0.8, strokeWeight: 3, @@ -229,10 +257,9 @@ export class PathElement extends connect(store)(LitElement) { this.closingSector.addListener('rightclick', (e) => this.appendToPath(e.latLng)); } - if (score.closingRadius) { - const center = points[score.indexes[0]]; - this.closingSector.center = center; - this.closingSector.radius = score.closingRadius; + if (result.closingPoints) { + this.closingSector.center = result.closingPoints.in; + this.closingSector.radius = score.closingRadiusM; this.closingSector.update(); this.closingSector.setMap(this.map); } else { @@ -244,8 +271,7 @@ export class PathElement extends connect(store)(LitElement) { this.faiSectors.addListeners('rightclick', (e) => this.appendToPath(e.latLng)); } if (score.circuit == CircuitType.FlatTriangle || score.circuit == CircuitType.FaiTriangle) { - const faiPoints = score.indexes.slice(1, 4).map((i) => points[i]); - this.faiSectors.update(faiPoints); + this.faiSectors.update(result.turnpoints); this.faiSectors.setMap(this.map); } else { this.faiSectors.setMap(null); @@ -254,12 +280,23 @@ export class PathElement extends connect(store)(LitElement) { this.postScoreToHost(score); } + private toScore(result: ScoringResult): Score { + return new Score({ + circuit: result.circuit, + distanceM: result.lengthKm * 1000, + multiplier: result.multiplier, + closingRadiusM: (result.closingRadiusM ?? 0) * 1000, + indexes: result.solutionIndices, + points: result.score, + }); + } + // Sends a message to the iframe host with the changes. private postScoreToHost(score: Score) { let kms = ''; let circuit = ''; - if (score.distance && window.parent) { - kms = (score.distance / 1000).toFixed(1); + if (score.distanceM && window.parent) { + kms = (score.distanceM / 1000).toFixed(1); circuit = CIRCUIT_SHORT_NAME[score.circuit]; if (score.circuit == CircuitType.OpenDistance) { circuit += score.indexes.length - 2; diff --git a/apps/fxc-front/src/app/components/2d/planner-element.ts b/apps/fxc-front/src/app/components/2d/planner-element.ts index 270c5a8e..255ac8ed 100644 --- a/apps/fxc-front/src/app/components/2d/planner-element.ts +++ b/apps/fxc-front/src/app/components/2d/planner-element.ts @@ -1,13 +1,15 @@ -import { css, CSSResult, html, LitElement, TemplateResult } from 'lit'; +import type { CSSResult, TemplateResult } from 'lit'; +import { css, html, LitElement } from 'lit'; import { customElement, state } from 'lit/decorators.js'; import { unsafeHTML } from 'lit/directives/unsafe-html.js'; import { when } from 'lit/directives/when.js'; import { connect } from 'pwa-helpers'; -import { Score } from '../../logic/score/scorer'; +import type { Score } from '../../logic/score/scorer'; import * as units from '../../logic/units'; import { decrementSpeed, incrementSpeed, setSpeed } from '../../redux/planner-slice'; -import { RootState, store } from '../../redux/store'; +import type { RootState } from '../../redux/store'; +import { store } from '../../redux/store'; const ICON_MINUS = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAkAAAAJAQMAAADaX5RTAAAABlBMVEX///9xe4e/5menAAAAE0lEQVQImWP438DQAEP7kNj/GwCK4wo9HA2mvgAAAABJRU5ErkJggg=='; @@ -139,7 +141,7 @@ export class PlannerElement extends connect(store)(LitElement) {
${this.score.circuit}
- ${unsafeHTML(units.formatUnit(this.score.distance / 1000, this.units.distance, undefined, 'unit'))} + ${unsafeHTML(units.formatUnit(this.score.distanceM / 1000, this.units.distance, undefined, 'unit'))}
diff --git a/apps/fxc-front/src/app/components/2d/skyways-element.ts b/apps/fxc-front/src/app/components/2d/skyways-element.ts index 4108731d..1a47d6e4 100644 --- a/apps/fxc-front/src/app/components/2d/skyways-element.ts +++ b/apps/fxc-front/src/app/components/2d/skyways-element.ts @@ -1,9 +1,11 @@ -import { html, LitElement, PropertyValues } from 'lit'; +import type { PropertyValues } from 'lit'; +import { html, LitElement } from 'lit'; import { customElement, property, state } from 'lit/decorators.js'; -import { TemplateResult } from 'lit/html.js'; +import type { TemplateResult } from 'lit/html.js'; import { connect } from 'pwa-helpers'; import * as skyways from '../../redux/skyways-slice'; -import { RootState, store } from '../../redux/store'; +import type { RootState } from '../../redux/store'; +import { store } from '../../redux/store'; import { GMAP_MAX_ZOOM_LEVEL } from './map-element'; import { WMTSInterpolatingOverlayElement } from './wmts-overlay'; diff --git a/apps/fxc-front/src/app/components/2d/tracking-element.ts b/apps/fxc-front/src/app/components/2d/tracking-element.ts index ad18e977..2380f2c1 100644 --- a/apps/fxc-front/src/app/components/2d/tracking-element.ts +++ b/apps/fxc-front/src/app/components/2d/tracking-element.ts @@ -1,12 +1,16 @@ -import { LitElement, PropertyValues } from 'lit'; +import type { PropertyValues } from 'lit'; +import { LitElement } from 'lit'; import { customElement, property, state } from 'lit/decorators.js'; import { connect } from 'pwa-helpers'; -import { FixType, LiveLineProperties, LivePointProperties } from '../../logic/live-track'; +import type { LiveLineProperties, LivePointProperties } from '../../logic/live-track'; +import { FixType } from '../../logic/live-track'; import { popupContent } from '../../logic/live-track-popup'; -import { Units, formatDurationMin, formatUnit } from '../../logic/units'; +import type { Units } from '../../logic/units'; +import { formatDurationMin, formatUnit } from '../../logic/units'; import { setCurrentLiveId } from '../../redux/live-track-slice'; import * as sel from '../../redux/selectors'; -import { RootState, store } from '../../redux/store'; +import type { RootState } from '../../redux/store'; +import { store } from '../../redux/store'; import { getUniqueContrastColor } from '../../styles/track'; // Anchors and label origins for markers. diff --git a/apps/fxc-front/src/app/components/3d/airspace3d-element.ts b/apps/fxc-front/src/app/components/3d/airspace3d-element.ts index beb9eb00..ad0968ef 100644 --- a/apps/fxc-front/src/app/components/3d/airspace3d-element.ts +++ b/apps/fxc-front/src/app/components/3d/airspace3d-element.ts @@ -3,9 +3,10 @@ import { customElement, property, state } from 'lit/decorators.js'; import { connect } from 'pwa-helpers'; import VectorTileLayer from '@arcgis/core/layers/VectorTileLayer'; -import Map from '@arcgis/core/Map'; +import type Map from '@arcgis/core/Map'; -import SceneView from '@arcgis/core/views/SceneView'; +import type SceneView from '@arcgis/core/views/SceneView'; +import type { LatLon } from '@flyxc/common'; import { ASP_COLOR_DANGER, ASP_COLOR_OTHER, @@ -13,12 +14,12 @@ import { ASP_COLOR_RESTRICTED, Class, getAirspaceTilesUrlTemplate, - LatLon, MAX_AIRSPACE_TILE_ZOOM, Type, } from '@flyxc/common'; import { getAirspaceList } from '../../logic/airspaces'; -import { RootState, store } from '../../redux/store'; +import type { RootState } from '../../redux/store'; +import { store } from '../../redux/store'; @customElement('airspace3d-element') export class Airspace3dElement extends connect(store)(LitElement) { diff --git a/apps/fxc-front/src/app/components/3d/controls3d-element.ts b/apps/fxc-front/src/app/components/3d/controls3d-element.ts index effe1236..dbbe77ab 100644 --- a/apps/fxc-front/src/app/components/3d/controls3d-element.ts +++ b/apps/fxc-front/src/app/components/3d/controls3d-element.ts @@ -2,15 +2,17 @@ import '../dashboard-element'; import '../menu-element'; import '../name-element'; -import * as common from '@flyxc/common'; -import { css, CSSResult, html, LitElement, TemplateResult } from 'lit'; +import type * as common from '@flyxc/common'; +import type { CSSResult, TemplateResult } from 'lit'; +import { css, html, LitElement } from 'lit'; import { customElement, state } from 'lit/decorators.js'; import { when } from 'lit/directives/when.js'; import { connect } from 'pwa-helpers'; -import * as units from '../../logic/units'; +import type * as units from '../../logic/units'; import * as sel from '../../redux/selectors'; -import { RootState, store } from '../../redux/store'; +import type { RootState } from '../../redux/store'; +import { store } from '../../redux/store'; @customElement('controls3d-element') export class Controls3dElement extends connect(store)(LitElement) { diff --git a/apps/fxc-front/src/app/components/3d/line3d-element.ts b/apps/fxc-front/src/app/components/3d/line3d-element.ts index 49b49915..701e3cad 100644 --- a/apps/fxc-front/src/app/components/3d/line3d-element.ts +++ b/apps/fxc-front/src/app/components/3d/line3d-element.ts @@ -1,14 +1,16 @@ import * as common from '@flyxc/common'; -import { LitElement, PropertyValues } from 'lit'; +import type { PropertyValues } from 'lit'; +import { LitElement } from 'lit'; import { customElement, property, state } from 'lit/decorators.js'; import { connect } from 'pwa-helpers'; import Color from '@arcgis/core/Color'; import Graphic from '@arcgis/core/Graphic'; -import GraphicsLayer from '@arcgis/core/layers/GraphicsLayer'; +import type GraphicsLayer from '@arcgis/core/layers/GraphicsLayer'; import * as sel from '../../redux/selectors'; -import { RootState, store } from '../../redux/store'; +import type { RootState } from '../../redux/store'; +import { store } from '../../redux/store'; const INACTIVE_ALPHA = 0.7; diff --git a/apps/fxc-front/src/app/components/3d/map3d-element.ts b/apps/fxc-front/src/app/components/3d/map3d-element.ts index 63938a32..28567304 100644 --- a/apps/fxc-front/src/app/components/3d/map3d-element.ts +++ b/apps/fxc-front/src/app/components/3d/map3d-element.ts @@ -20,23 +20,25 @@ import VirtualLighting from '@arcgis/core/views/3d/environment/VirtualLighting'; import SceneView from '@arcgis/core/views/SceneView'; import Popup from '@arcgis/core/widgets/Popup'; import IntegratedMesh3DTilesLayer from '@arcgis/core/layers/IntegratedMesh3DTilesLayer.js'; -import { LatLon, LatLonAlt, RuntimeTrack } from '@flyxc/common'; +import type { LatLon, LatLonAlt, RuntimeTrack } from '@flyxc/common'; import { alertController } from '@ionic/core/components'; -import { LitElement, PropertyValues, TemplateResult, html } from 'lit'; +import type { PropertyValues, TemplateResult } from 'lit'; +import { LitElement, html } from 'lit'; import { customElement, state } from 'lit/decorators.js'; import { repeat } from 'lit/directives/repeat.js'; import { when } from 'lit/directives/when.js'; -import { UnsubscribeHandle } from 'micro-typed-events'; +import type { UnsubscribeHandle } from 'micro-typed-events'; import { connect } from 'pwa-helpers'; import * as msg from '../../logic/messages'; import { setApiLoading, setTimeSec, setView3d } from '../../redux/app-slice'; import { setCurrentLiveId } from '../../redux/live-track-slice'; import { setCurrentLocation, setCurrentZoom } from '../../redux/location-slice'; import * as sel from '../../redux/selectors'; -import { RootState, store } from '../../redux/store'; +import type { RootState } from '../../redux/store'; +import { store } from '../../redux/store'; import { setCurrentTrackId } from '../../redux/track-slice'; -import { Airspace3dElement } from './airspace3d-element'; -import { Skyways3dElement } from './skyways3d-element'; +import type { Airspace3dElement } from './airspace3d-element'; +import type { Skyways3dElement } from './skyways3d-element'; import { getApiKeyAndHost } from '../../apikey'; @customElement('map3d-element') diff --git a/apps/fxc-front/src/app/components/3d/marker3d-element.ts b/apps/fxc-front/src/app/components/3d/marker3d-element.ts index 8449f4d0..cca51048 100644 --- a/apps/fxc-front/src/app/components/3d/marker3d-element.ts +++ b/apps/fxc-front/src/app/components/3d/marker3d-element.ts @@ -1,13 +1,15 @@ import * as common from '@flyxc/common'; -import { LitElement, PropertyValues } from 'lit'; +import type { PropertyValues } from 'lit'; +import { LitElement } from 'lit'; import { customElement, property, state } from 'lit/decorators.js'; import { connect } from 'pwa-helpers'; import Graphic from '@arcgis/core/Graphic'; -import GraphicsLayer from '@arcgis/core/layers/GraphicsLayer'; +import type GraphicsLayer from '@arcgis/core/layers/GraphicsLayer'; import * as sel from '../../redux/selectors'; -import { RootState, store } from '../../redux/store'; +import type { RootState } from '../../redux/store'; +import { store } from '../../redux/store'; const MARKER_HEIGHT = 30; diff --git a/apps/fxc-front/src/app/components/3d/skyways3d-element.ts b/apps/fxc-front/src/app/components/3d/skyways3d-element.ts index b08484a0..592a9ee5 100644 --- a/apps/fxc-front/src/app/components/3d/skyways3d-element.ts +++ b/apps/fxc-front/src/app/components/3d/skyways3d-element.ts @@ -1,13 +1,15 @@ -import { LitElement, PropertyValues } from 'lit'; +import type { PropertyValues } from 'lit'; +import { LitElement } from 'lit'; import { customElement, property, state } from 'lit/decorators.js'; import { connect } from 'pwa-helpers'; -import Map from '@arcgis/core/Map'; +import type Map from '@arcgis/core/Map'; import WebTileLayer from '@arcgis/core/layers/WebTileLayer'; import TileInfo from '@arcgis/core/layers/support/TileInfo'; import * as skyways from '../../redux/skyways-slice'; -import { RootState, store } from '../../redux/store'; +import type { RootState } from '../../redux/store'; +import { store } from '../../redux/store'; @customElement('skyways3d-element') export class Skyways3dElement extends connect(store)(LitElement) { diff --git a/apps/fxc-front/src/app/components/3d/tracking3d-element.ts b/apps/fxc-front/src/app/components/3d/tracking3d-element.ts index 4f1f2eb4..144c754a 100644 --- a/apps/fxc-front/src/app/components/3d/tracking3d-element.ts +++ b/apps/fxc-front/src/app/components/3d/tracking3d-element.ts @@ -1,23 +1,27 @@ -import { protos } from '@flyxc/common'; -import { LitElement, PropertyValues } from 'lit'; +import type { protos } from '@flyxc/common'; +import type { PropertyValues } from 'lit'; +import { LitElement } from 'lit'; import { customElement, property, state } from 'lit/decorators.js'; -import { UnsubscribeHandle } from 'micro-typed-events'; +import type { UnsubscribeHandle } from 'micro-typed-events'; import { connect } from 'pwa-helpers'; import Color from '@arcgis/core/Color'; import Point from '@arcgis/core/geometry/Point'; import Graphic from '@arcgis/core/Graphic'; -import GraphicsLayer from '@arcgis/core/layers/GraphicsLayer'; -import ElevationSampler from '@arcgis/core/layers/support/ElevationSampler'; -import SceneView from '@arcgis/core/views/SceneView'; +import type GraphicsLayer from '@arcgis/core/layers/GraphicsLayer'; +import type ElevationSampler from '@arcgis/core/layers/support/ElevationSampler'; +import type SceneView from '@arcgis/core/views/SceneView'; -import { FixType, LiveLineProperties, LivePointProperties } from '../../logic/live-track'; +import type { LiveLineProperties, LivePointProperties } from '../../logic/live-track'; +import { FixType } from '../../logic/live-track'; import { popupContent } from '../../logic/live-track-popup'; import * as msg from '../../logic/messages'; -import { formatDurationMin, Units } from '../../logic/units'; +import type { Units } from '../../logic/units'; +import { formatDurationMin } from '../../logic/units'; import { liveTrackSelectors, setCurrentLiveId } from '../../redux/live-track-slice'; import * as sel from '../../redux/selectors'; -import { RootState, store } from '../../redux/store'; +import type { RootState } from '../../redux/store'; +import { store } from '../../redux/store'; import { getUniqueContrastColor } from '../../styles/track'; // A track is considered recent if ended less than timeout ago. diff --git a/apps/fxc-front/src/app/components/chart-element.ts b/apps/fxc-front/src/app/components/chart-element.ts index 16c75f9d..f61f4538 100644 --- a/apps/fxc-front/src/app/components/chart-element.ts +++ b/apps/fxc-front/src/app/components/chart-element.ts @@ -1,6 +1,8 @@ -import { Class, Flags, isAirspaceVisible, RuntimeTrack, sampleAt, Type } from '@flyxc/common'; +import type { Class, RuntimeTrack, Type } from '@flyxc/common'; +import { Flags, isAirspaceVisible, sampleAt } from '@flyxc/common'; import { ticks } from 'd3-array'; -import { css, CSSResult, html, LitElement, PropertyValues, svg, SVGTemplateResult, TemplateResult } from 'lit'; +import type { CSSResult, PropertyValues, SVGTemplateResult, TemplateResult } from 'lit'; +import { css, html, LitElement, svg } from 'lit'; import { customElement, query, state } from 'lit/decorators.js'; import { guard } from 'lit/directives/guard.js'; import { connect } from 'pwa-helpers'; @@ -8,7 +10,8 @@ import { connect } from 'pwa-helpers'; import * as units from '../logic/units'; import { ChartYAxis, setChartYAxis } from '../redux/app-slice'; import * as sel from '../redux/selectors'; -import { RootState, store } from '../redux/store'; +import type { RootState } from '../redux/store'; +import { store } from '../redux/store'; const MIN_SPEED_FACTOR = 16; const MAX_SPEED_FACTOR = 4096; diff --git a/apps/fxc-front/src/app/components/dashboard-element.ts b/apps/fxc-front/src/app/components/dashboard-element.ts index aba064e2..2ca46999 100644 --- a/apps/fxc-front/src/app/components/dashboard-element.ts +++ b/apps/fxc-front/src/app/components/dashboard-element.ts @@ -1,7 +1,8 @@ import './ui/pref-modal'; import * as common from '@flyxc/common'; -import { css, CSSResult, html, LitElement, TemplateResult } from 'lit'; +import type { CSSResult, TemplateResult } from 'lit'; +import { css, html, LitElement } from 'lit'; import { customElement, property } from 'lit/decorators.js'; import { modalController } from '@ionic/core/components'; diff --git a/apps/fxc-front/src/app/components/loader-element.ts b/apps/fxc-front/src/app/components/loader-element.ts index 85ee7fc0..64020491 100644 --- a/apps/fxc-front/src/app/components/loader-element.ts +++ b/apps/fxc-front/src/app/components/loader-element.ts @@ -1,7 +1,9 @@ -import { css, CSSResult, html, LitElement, PropertyValues, TemplateResult } from 'lit'; +import type { CSSResult, PropertyValues, TemplateResult } from 'lit'; +import { css, html, LitElement } from 'lit'; import { customElement, property, state } from 'lit/decorators.js'; import { connect } from 'pwa-helpers'; -import { RootState, store } from '../redux/store'; +import type { RootState } from '../redux/store'; +import { store } from '../redux/store'; @customElement('loader-element') export class LoaderElement extends connect(store)(LitElement) { diff --git a/apps/fxc-front/src/app/components/menu-element.ts b/apps/fxc-front/src/app/components/menu-element.ts index 430b2a10..eeedb574 100644 --- a/apps/fxc-front/src/app/components/menu-element.ts +++ b/apps/fxc-front/src/app/components/menu-element.ts @@ -1,4 +1,5 @@ -import { CSSResult, html, LitElement, TemplateResult } from 'lit'; +import type { CSSResult, TemplateResult } from 'lit'; +import { html, LitElement } from 'lit'; import { customElement } from 'lit/decorators.js'; import { controlStyle } from '../styles/control-style'; diff --git a/apps/fxc-front/src/app/components/name-element.ts b/apps/fxc-front/src/app/components/name-element.ts index 5d02a3aa..6d5388bb 100644 --- a/apps/fxc-front/src/app/components/name-element.ts +++ b/apps/fxc-front/src/app/components/name-element.ts @@ -1,7 +1,8 @@ import './ui/track-modal'; -import * as common from '@flyxc/common'; -import { CSSResult, html, LitElement, TemplateResult } from 'lit'; +import type * as common from '@flyxc/common'; +import type { CSSResult, TemplateResult } from 'lit'; +import { html, LitElement } from 'lit'; import { customElement, property } from 'lit/decorators.js'; import { modalController } from '@ionic/core/components'; diff --git a/apps/fxc-front/src/app/components/ui/about-modal.ts b/apps/fxc-front/src/app/components/ui/about-modal.ts index bac06c40..65ede08f 100644 --- a/apps/fxc-front/src/app/components/ui/about-modal.ts +++ b/apps/fxc-front/src/app/components/ui/about-modal.ts @@ -1,4 +1,5 @@ -import { html, LitElement, TemplateResult } from 'lit'; +import type { TemplateResult } from 'lit'; +import { html, LitElement } from 'lit'; import { customElement } from 'lit/decorators.js'; import { modalController } from '@ionic/core/components'; @@ -20,7 +21,8 @@ export class AboutModal extends LitElement { Momtchil Momtchev, Stanislav Ošmera, Tristan Horn, - Sylvain Pasutto + Sylvain Pasutto, + flyingtof

diff --git a/apps/fxc-front/src/app/components/ui/google-btn.ts b/apps/fxc-front/src/app/components/ui/google-btn.ts index 40783182..885773c4 100644 --- a/apps/fxc-front/src/app/components/ui/google-btn.ts +++ b/apps/fxc-front/src/app/components/ui/google-btn.ts @@ -1,4 +1,5 @@ -import { css, CSSResult, html, LitElement, TemplateResult } from 'lit'; +import type { CSSResult, TemplateResult } from 'lit'; +import { css, html, LitElement } from 'lit'; import { customElement, property } from 'lit/decorators.js'; import { when } from 'lit/directives/when.js'; diff --git a/apps/fxc-front/src/app/components/ui/live-modal.ts b/apps/fxc-front/src/app/components/ui/live-modal.ts index eeb7e5ef..ce372b3a 100644 --- a/apps/fxc-front/src/app/components/ui/live-modal.ts +++ b/apps/fxc-front/src/app/components/ui/live-modal.ts @@ -1,15 +1,18 @@ -import * as common from '@flyxc/common'; +import type * as common from '@flyxc/common'; import { modalController, toastController } from '@ionic/core/components'; import { getDistance } from 'geolib'; -import { LitElement, TemplateResult, html } from 'lit'; +import type { TemplateResult } from 'lit'; +import { LitElement, html } from 'lit'; import { customElement, state } from 'lit/decorators.js'; import { when } from 'lit/directives/when.js'; import { connect } from 'pwa-helpers'; import * as msg from '../../logic/messages'; import * as units from '../../logic/units'; -import { LivePilot, getLivePilots, setCenterOnLocation, setCurrentLiveId } from '../../redux/live-track-slice'; +import type { LivePilot } from '../../redux/live-track-slice'; +import { getLivePilots, setCenterOnLocation, setCurrentLiveId } from '../../redux/live-track-slice'; import { setCurrentLocation } from '../../redux/location-slice'; -import { RootState, store } from '../../redux/store'; +import type { RootState } from '../../redux/store'; +import { store } from '../../redux/store'; import { getUniqueContrastColor } from '../../styles/track'; import { maybeHideSidePane } from '../../../flyxc'; diff --git a/apps/fxc-front/src/app/components/ui/main-menu.ts b/apps/fxc-front/src/app/components/ui/main-menu.ts index 84581e98..da706e5c 100644 --- a/apps/fxc-front/src/app/components/ui/main-menu.ts +++ b/apps/fxc-front/src/app/components/ui/main-menu.ts @@ -1,21 +1,24 @@ import { Class, Type, getClassName, getTypeName } from '@flyxc/common'; -import { SearchbarCustomEvent, ToggleCustomEvent, modalController, toastController } from '@ionic/core/components'; -import { LitElement, TemplateResult, html } from 'lit'; +import type { SearchbarCustomEvent, ToggleCustomEvent } from '@ionic/core/components'; +import { modalController, toastController } from '@ionic/core/components'; +import type { TemplateResult } from 'lit'; +import { LitElement, html } from 'lit'; import { customElement, state } from 'lit/decorators.js'; import { map } from 'lit/directives/map.js'; import { when } from 'lit/directives/when.js'; -import { UnsubscribeHandle } from 'micro-typed-events'; +import type { UnsubscribeHandle } from 'micro-typed-events'; import { connect } from 'pwa-helpers'; import { requestCurrentPosition } from '../../logic/geolocation'; import { ParamNames, addUrlParamValues, pushCurrentState } from '../../logic/history'; import * as msg from '../../logic/messages'; import { uploadTracks } from '../../logic/track'; -import { DistanceUnit, formatUnit } from '../../logic/units'; +import type { DistanceUnit } from '../../logic/units'; +import { formatUnit } from '../../logic/units'; import * as airspaces from '../../redux/airspace-slice'; import { setApiLoading } from '../../redux/app-slice'; import * as arcgis from '../../redux/arcgis-slice'; +import type { LivePilot } from '../../redux/live-track-slice'; import { - LivePilot, getLivePilots, setDisplayLabels as setDisplayLiveLabels, setFetchMillis, @@ -26,7 +29,8 @@ import { import { setEnabled } from '../../redux/planner-slice'; import * as sel from '../../redux/selectors'; import * as skyways from '../../redux/skyways-slice'; -import { RootState, store } from '../../redux/store'; +import type { RootState } from '../../redux/store'; +import { store } from '../../redux/store'; import { setDisplayLabels, setLockOnPilot } from '../../redux/track-slice'; import './about-modal'; import './live-modal'; @@ -632,16 +636,16 @@ export class TrackItems extends connect(store)(LitElement) { private async handleUpload(e: Event & { target: HTMLInputElement }): Promise { if (e.target) { - const el = e.target; - if (el.files?.length) { + const input = e.target; + if (input.files?.length) { const files: File[] = []; - for (let i = 0; i < el.files.length; i++) { - files.push(el.files[i]); + for (let i = 0; i < input.files.length; i++) { + files.push(input.files[i]); } const ids = await uploadTracks(files); pushCurrentState(); addUrlParamValues(ParamNames.groupId, ids); - el.value = ''; + input.value = ''; } } } diff --git a/apps/fxc-front/src/app/components/ui/pref-modal.ts b/apps/fxc-front/src/app/components/ui/pref-modal.ts index fa3e1db0..35097d7c 100644 --- a/apps/fxc-front/src/app/components/ui/pref-modal.ts +++ b/apps/fxc-front/src/app/components/ui/pref-modal.ts @@ -1,14 +1,16 @@ -import { html, LitElement, TemplateResult } from 'lit'; +import type { TemplateResult } from 'lit'; +import { html, LitElement } from 'lit'; import { customElement, state } from 'lit/decorators.js'; import { connect } from 'pwa-helpers'; import { modalController } from '@ionic/core/components'; -import { LEAGUES } from '../../logic/score/league/leagues'; import * as units from '../../logic/units'; import { setLeague } from '../../redux/planner-slice'; -import { RootState, store } from '../../redux/store'; +import type { RootState } from '../../redux/store'; +import { store } from '../../redux/store'; import { setAltitudeUnit, setDistanceUnit, setSpeedUnit, setVarioUnit } from '../../redux/units-slice'; +import { LEAGUE_CODES, LEAGUES } from '../../logic/score/league/leagues'; @customElement('pref-modal') export class PrefModal extends connect(store)(LitElement) { @@ -21,7 +23,7 @@ export class PrefModal extends connect(store)(LitElement) { constructor() { super(); - Object.getOwnPropertyNames(LEAGUES).forEach((value) => { + LEAGUE_CODES.forEach((value) => { this.leagues.push({ value, name: LEAGUES[value].name }); }); this.leagues.sort((a, b) => (a < b ? -1 : 1)); diff --git a/apps/fxc-front/src/app/components/ui/share-modal.ts b/apps/fxc-front/src/app/components/ui/share-modal.ts index aad6140f..e275d065 100644 --- a/apps/fxc-front/src/app/components/ui/share-modal.ts +++ b/apps/fxc-front/src/app/components/ui/share-modal.ts @@ -1,9 +1,11 @@ -import { encodeFloats, encodeSignedIntegers, LatLonAlt } from '@flyxc/common'; -import { html, LitElement, PropertyValues, TemplateResult } from 'lit'; +import type { LatLonAlt } from '@flyxc/common'; +import { encodeFloats, encodeSignedIntegers } from '@flyxc/common'; +import type { PropertyValues, TemplateResult } from 'lit'; +import { html, LitElement } from 'lit'; import { customElement, property, state } from 'lit/decorators.js'; import { toDataURL } from 'qrcode/lib/browser'; -import { SegmentCustomEvent } from '@ionic/core'; +import type { SegmentCustomEvent } from '@ionic/core'; import { modalController, toastController } from '@ionic/core/components'; @customElement('share-modal') diff --git a/apps/fxc-front/src/app/components/ui/supporter-modal.ts b/apps/fxc-front/src/app/components/ui/supporter-modal.ts index c8ccddd2..8ca64728 100644 --- a/apps/fxc-front/src/app/components/ui/supporter-modal.ts +++ b/apps/fxc-front/src/app/components/ui/supporter-modal.ts @@ -1,4 +1,5 @@ -import { html, LitElement, TemplateResult } from 'lit'; +import type { TemplateResult } from 'lit'; +import { html, LitElement } from 'lit'; import { customElement } from 'lit/decorators.js'; import { modalController } from '@ionic/core/components'; diff --git a/apps/fxc-front/src/app/components/ui/track-modal.ts b/apps/fxc-front/src/app/components/ui/track-modal.ts index 7d2f812d..1a454182 100644 --- a/apps/fxc-front/src/app/components/ui/track-modal.ts +++ b/apps/fxc-front/src/app/components/ui/track-modal.ts @@ -1,5 +1,7 @@ -import { extractGroupId, RuntimeTrack } from '@flyxc/common'; -import { html, LitElement, TemplateResult } from 'lit'; +import type { RuntimeTrack } from '@flyxc/common'; +import { extractGroupId } from '@flyxc/common'; +import type { TemplateResult } from 'lit'; +import { html, LitElement } from 'lit'; import { customElement, state } from 'lit/decorators.js'; import { connect } from 'pwa-helpers'; @@ -9,7 +11,8 @@ import { pushCurrentState } from '../../logic/history'; import * as msg from '../../logic/messages'; import * as app from '../../redux/app-slice'; import * as sel from '../../redux/selectors'; -import { RootState, store } from '../../redux/store'; +import type { RootState } from '../../redux/store'; +import { store } from '../../redux/store'; import * as trackSlice from '../../redux/track-slice'; import { maybeHideSidePane } from '../../../flyxc'; diff --git a/apps/fxc-front/src/app/components/ui/waypoint-modal.ts b/apps/fxc-front/src/app/components/ui/waypoint-modal.ts index 0aaba1f6..727d9de8 100644 --- a/apps/fxc-front/src/app/components/ui/waypoint-modal.ts +++ b/apps/fxc-front/src/app/components/ui/waypoint-modal.ts @@ -1,5 +1,6 @@ -import { LatLonAlt } from '@flyxc/common'; -import { html, LitElement, TemplateResult } from 'lit'; +import type { LatLonAlt } from '@flyxc/common'; +import type { TemplateResult } from 'lit'; +import { html, LitElement } from 'lit'; import { customElement, property, state } from 'lit/decorators.js'; import { modalController } from '@ionic/core/components'; diff --git a/apps/fxc-front/src/app/gm/closing-sector.ts b/apps/fxc-front/src/app/gm/closing-sector.ts index 799a050d..08c92f7c 100644 --- a/apps/fxc-front/src/app/gm/closing-sector.ts +++ b/apps/fxc-front/src/app/gm/closing-sector.ts @@ -1,12 +1,10 @@ -import { computeDestinationPoint } from 'geolib'; - export class ClosingSector { center: { lat: number; lon: number } | null = null; radius: number | null = null; - poly: google.maps.Polygon; + circle: google.maps.Circle; constructor() { - this.poly = new google.maps.Polygon({ + this.circle = new google.maps.Circle({ fillColor: '#ffff00', fillOpacity: 0.3, strokeColor: '#ffff00', @@ -17,21 +15,18 @@ export class ClosingSector { } setMap(map?: google.maps.Map | null): void { - this.poly.setMap(map ?? null); + this.circle.setMap(map ?? null); } addListener(name: string, handler: (...args: any[]) => void): google.maps.MapsEventListener { - return this.poly.addListener(name, handler); + return this.circle.addListener(name, handler); } update(): void { if (this.center == null || this.radius == null) { return; } - const path: { latitude: number; longitude: number }[] = [{ latitude: this.center.lat, longitude: this.center.lon }]; - for (let i = 0; i <= 360; i += 10) { - path.push(computeDestinationPoint(this.center, this.radius, i)); - } - this.poly.setPath(path.map(({ latitude, longitude }) => new google.maps.LatLng(latitude, longitude))); + this.circle.setCenter({ lat: this.center.lat, lng: this.center.lon }); + this.circle.setRadius(this.radius); } } diff --git a/apps/fxc-front/src/app/gm/fai-sectors.ts b/apps/fxc-front/src/app/gm/fai-sectors.ts index af611ca8..bfcfeca2 100644 --- a/apps/fxc-front/src/app/gm/fai-sectors.ts +++ b/apps/fxc-front/src/app/gm/fai-sectors.ts @@ -1,6 +1,6 @@ import { getDistance, getGreatCircleBearing, toDeg, toRad } from 'geolib'; -import { LatLon } from '@flyxc/common'; +import type { LatLon } from '@flyxc/common'; const SECTOR_COLORS = ['#f00', '#0f0', '#00f']; diff --git a/apps/fxc-front/src/app/logic/airspaces.ts b/apps/fxc-front/src/app/logic/airspaces.ts index 12c8c125..5d48e593 100644 --- a/apps/fxc-front/src/app/logic/airspaces.ts +++ b/apps/fxc-front/src/app/logic/airspaces.ts @@ -1,12 +1,7 @@ +import type { AirspaceString, AirspaceTyped, Class, LatLon, Point, Type } from '@flyxc/common'; import { AIRSPACE_TILE_SIZE, - AirspaceString, - AirspaceTyped, - Class, - LatLon, MAX_AIRSPACE_TILE_ZOOM, - Point, - Type, getAirspaceCategory, getAirspaceColor, getAirspaceTileUrl, diff --git a/apps/fxc-front/src/app/logic/elevation.ts b/apps/fxc-front/src/app/logic/elevation.ts index 4f1f10c7..ce883dcb 100644 --- a/apps/fxc-front/src/app/logic/elevation.ts +++ b/apps/fxc-front/src/app/logic/elevation.ts @@ -1,4 +1,4 @@ -import { LatLon, LatLonAlt } from '@flyxc/common'; +import type { LatLon, LatLonAlt } from '@flyxc/common'; // Adds the ground elevation to the list of points. // diff --git a/apps/fxc-front/src/app/logic/live-track-popup.ts b/apps/fxc-front/src/app/logic/live-track-popup.ts index 103006e5..6e4e34b5 100644 --- a/apps/fxc-front/src/app/logic/live-track-popup.ts +++ b/apps/fxc-front/src/app/logic/live-track-popup.ts @@ -2,7 +2,8 @@ import { getFixMessage, getTrackerDisplayName, isEmergencyFix, isLowBatFix, isVa import { liveTrackSelectors } from '../redux/live-track-slice'; import { store } from '../redux/store'; -import { formatUnit, Units } from './units'; +import type { Units } from './units'; +import { formatUnit } from './units'; // Generates the content of the live tracking popup. export function popupContent( diff --git a/apps/fxc-front/src/app/logic/live-track.ts b/apps/fxc-front/src/app/logic/live-track.ts index 2a0ca68f..08e4bcb9 100644 --- a/apps/fxc-front/src/app/logic/live-track.ts +++ b/apps/fxc-front/src/app/logic/live-track.ts @@ -1,3 +1,4 @@ +import type { protos } from '@flyxc/common'; import { differentialDecodeLiveTrack, getFixMessage, @@ -7,7 +8,6 @@ import { isUfo, LIVE_MINIMAL_INTERVAL_SEC, mergeLiveTracks, - protos, simplifyLiveTrack, } from '@flyxc/common'; import { getRhumbLineBearing } from 'geolib'; diff --git a/apps/fxc-front/src/app/logic/messages.ts b/apps/fxc-front/src/app/logic/messages.ts index df1493d2..710e7b32 100644 --- a/apps/fxc-front/src/app/logic/messages.ts +++ b/apps/fxc-front/src/app/logic/messages.ts @@ -1,6 +1,6 @@ import type Graphic from '@arcgis/core/Graphic'; import type SceneView from '@arcgis/core/views/SceneView'; -import { LatLon, LatLonAlt } from '@flyxc/common'; +import type { LatLon, LatLonAlt } from '@flyxc/common'; import { createEvents } from 'micro-typed-events'; // Zoom the map in the given direction (typically mouse wheel). diff --git a/apps/fxc-front/src/app/logic/score/league.ts b/apps/fxc-front/src/app/logic/score/league.ts deleted file mode 100644 index b13c7777..00000000 --- a/apps/fxc-front/src/app/logic/score/league.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { Measure } from './measure'; -import { Score } from './scorer'; - -export abstract class League { - abstract name: string; - abstract score(measure: Measure): Score[]; -} diff --git a/apps/fxc-front/src/app/logic/score/league/czech.ts b/apps/fxc-front/src/app/logic/score/league/czech.ts deleted file mode 100644 index 53a9350d..00000000 --- a/apps/fxc-front/src/app/logic/score/league/czech.ts +++ /dev/null @@ -1,68 +0,0 @@ -import { Measure } from '../measure'; -import { Score, scoreCircuits, scoreOpenDistance, scoreTriangles } from '../scorer'; - -abstract class CZXCBase { - score(measure: Measure): Score[] { - return [ - ...scoreOpenDistance(measure, 3, this.openDistanceMultiplier), - ...scoreCircuits(measure, null, this.scoreTriangles), - ]; - } - protected scoreTriangles = (measure: Measure, distances: number[], indexes: number[]): Score[] => - scoreTriangles( - this.flatTriangleMultiplier(), - this.faiTriangleMultiplier(), - 0.05, - false, - true, - measure, - distances, - indexes, - ); - - protected abstract faiTriangleMultiplier(): number; - protected abstract flatTriangleMultiplier(): number; - protected abstract openDistanceMultiplier(): number; -} - -export class CzechLocal extends CZXCBase { - name = 'Czech (ČPP local)'; - - protected faiTriangleMultiplier(): number { - return 2.2; - } - protected flatTriangleMultiplier(): number { - return 1.8; - } - protected openDistanceMultiplier(): number { - return 1; - } -} - -export class CzechEurope extends CZXCBase { - name = 'Czech (ČPP Europe)'; - - protected faiTriangleMultiplier(): number { - return 1.4; - } - protected flatTriangleMultiplier(): number { - return 1.2; - } - protected openDistanceMultiplier(): number { - return 1; - } -} - -export class CzechOutEurope extends CZXCBase { - name = 'Czech (ČPP outside Europe)'; - - protected faiTriangleMultiplier(): number { - return 1.4; - } - protected flatTriangleMultiplier(): number { - return 1.2; - } - protected openDistanceMultiplier(): number { - return 0.8; - } -} diff --git a/apps/fxc-front/src/app/logic/score/league/frcfd.ts b/apps/fxc-front/src/app/logic/score/league/frcfd.ts deleted file mode 100644 index 993281c5..00000000 --- a/apps/fxc-front/src/app/logic/score/league/frcfd.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { League } from '../league'; -import { Measure } from '../measure'; -import { Score, scoreCircuits, scoreOpenDistance, scoreTriangles } from '../scorer'; - -export class FrCfd extends League { - name = 'France (CFD)'; - - score(measure: Measure): Score[] { - return [ - ...scoreOpenDistance(measure, 3, this.openDistanceMultiplier), - ...scoreCircuits(measure, null, this.scoreTriangleAbsolute), - ...scoreCircuits(measure, null, this.scoreTriangleRelative), - ]; - } - - protected scoreTriangleRelative = (measure: Measure, distances: number[], indexes: number[]): Score[] => - scoreTriangles(1.2, 1.4, 0.05, false, true, measure, distances, indexes); - - protected scoreTriangleAbsolute = (measure: Measure, distances: number[], indexes: number[]): Score[] => - scoreTriangles(1.2, 1.4, 3000, true, false, measure, distances, indexes); - - protected openDistanceMultiplier(distance: number): number { - return distance < 15000 ? 0 : 1; - } -} diff --git a/apps/fxc-front/src/app/logic/score/league/leagues.ts b/apps/fxc-front/src/app/logic/score/league/leagues.ts index 19fab934..09eb48ad 100644 --- a/apps/fxc-front/src/app/logic/score/league/leagues.ts +++ b/apps/fxc-front/src/app/logic/score/league/leagues.ts @@ -1,22 +1,46 @@ -import { League } from '../league'; -import { CzechEurope, CzechLocal, CzechOutEurope } from './czech'; -import { FrCfd } from './frcfd'; -import { Leonardo } from './leonardo'; -import { UKXCLClub, UKXCLInternational, UKXCLNational } from './ukxcl'; -import { WXC } from './wxc'; -import { NorwayLeague, XContest, XContestPPG } from './xcontest'; +import type { ScoringRuleName } from '@flyxc/optimizer/lib/api'; -export const LEAGUES: { [name: string]: League } = { - czl: new CzechLocal(), - cze: new CzechEurope(), - czo: new CzechOutEurope(), - fr: new FrCfd(), - leo: new Leonardo(), - nor: new NorwayLeague(), - ukc: new UKXCLClub(), - uki: new UKXCLInternational(), - ukn: new UKXCLNational(), - xc: new XContest(), - xcppg: new XContestPPG(), - wxc: new WXC(), +export const LEAGUE_CODES = [ + 'czl', + 'cze', + 'czo', + 'fr', + 'leo', + 'nor', + 'ukc', + 'uki', + 'ukn', + 'xc', + 'xcppg', + 'wxc', +] as const; + +export type LeagueCode = (typeof LEAGUE_CODES)[number]; + +interface LeagueDetails { + name: string; + ruleName: ScoringRuleName; +} + +export const LEAGUES: Readonly> = { + czl: { name: 'Czech (ČPP local)', ruleName: 'CzechLocal' }, + cze: { name: 'Czech (ČPP Europe)', ruleName: 'CzechEurope' }, + czo: { name: 'Czech (ČPP outside Europe)', ruleName: 'CzechOutsideEurope' }, + fr: { name: 'France (CFD)', ruleName: 'FFVL' }, + leo: { name: 'Leonardo', ruleName: 'Leonardo' }, + nor: { name: 'Norway (Distanseligaen)', ruleName: 'Norway' }, + ukc: { name: 'UK (XC League, Club)', ruleName: 'UKClub' }, + uki: { name: 'UK (XC League, International)', ruleName: 'UKInternational' }, + ukn: { name: 'UK (XC League, National)', ruleName: 'UKNational' }, + xc: { name: 'XContest', ruleName: 'XContest' }, + xcppg: { name: 'XContest PPG', ruleName: 'XContestPPG' }, + wxc: { name: 'World XC Online Contest', ruleName: 'WorldXC' }, }; + +export function getScoringRuleName(leagueCode: LeagueCode): ScoringRuleName { + const ruleName = LEAGUES[leagueCode]?.ruleName; + if (ruleName == null) { + throw new Error('Unkown league code "${leagueCode}"'); + } + return ruleName; +} diff --git a/apps/fxc-front/src/app/logic/score/league/leonardo.ts b/apps/fxc-front/src/app/logic/score/league/leonardo.ts deleted file mode 100644 index 00a65dec..00000000 --- a/apps/fxc-front/src/app/logic/score/league/leonardo.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { League } from '../league'; -import { Measure } from '../measure'; -import { Score, scoreCircuits, scoreOpenDistance, scoreTriangles } from '../scorer'; - -export class Leonardo extends League { - name = 'Leonardo'; - - score(measure: Measure): Score[] { - return [ - ...scoreOpenDistance(measure, 3, this.openDistanceMultiplier), - ...scoreCircuits(measure, null, this.scoreTriangles), - ]; - } - - protected scoreTriangles = (measure: Measure, distances: number[], indexes: number[]): Score[] => - scoreTriangles(1.75, 2, 0.2, false, true, measure, distances, indexes); - - protected openDistanceMultiplier(): number { - return 1.5; - } -} diff --git a/apps/fxc-front/src/app/logic/score/league/ukxcl.ts b/apps/fxc-front/src/app/logic/score/league/ukxcl.ts deleted file mode 100644 index 662bb27c..00000000 --- a/apps/fxc-front/src/app/logic/score/league/ukxcl.ts +++ /dev/null @@ -1,153 +0,0 @@ -import { Measure } from '../measure'; -import { CircuitType, Score, scoreCircuits, scoreOpenDistance } from '../scorer'; - -function scoreOutAndReturn(multiplierFn: (d: number) => number, measure: Measure): Score[] { - let maxIndexes; - let maxDistance = 0; - let maxScoringDistance = 0; - const len = measure.points.length; - for (let i = 0; i < len - 1; i++) { - for (let tp1 = i; tp1 < len - 1; tp1++) { - for (let tp2 = tp1 + 1; tp2 < len; tp2++) { - const distance = measure.getDistance(tp1, tp2); - for (let l = tp2; l < len; l++) { - const gap = measure.getDistance(i, l); - if (gap <= 800) { - const scoringDistance = 2 * distance - gap; - if (scoringDistance > maxScoringDistance) { - maxIndexes = [i, tp1, tp2, l]; - maxDistance = distance; - maxScoringDistance = scoringDistance; - } - } - } - } - } - } - return maxIndexes == null - ? [] - : [ - new Score({ - distance: maxScoringDistance, - indexes: maxIndexes, - multiplier: multiplierFn(2 * maxDistance), - circuit: CircuitType.OutAndReturn, - closingRadius: 800, - }), - ]; -} - -function scoreTriangles( - faiMultiplierFn: (d: number) => number, - flatMultiplierFn: (d: number) => number, - measure: Measure, - distances: number[], - indexes: number[], -): Score[] { - const firstIndex = indexes[0]; - const lastIndex = indexes[2]; - const len = measure.points.length; - const distance = distances[0] + distances[1] + distances[2]; - const shortestLegFraction = Math.min(...distances) / distance; - const longestLegFraction = Math.max(...distances) / distance; - let multiplier; - let circuit; - if (shortestLegFraction >= 0.28) { - multiplier = faiMultiplierFn(distance); - circuit = CircuitType.FaiTriangle; - } else if (shortestLegFraction >= 0.15 && longestLegFraction <= 0.45) { - multiplier = flatMultiplierFn(distance); - circuit = CircuitType.FlatTriangle; - } else { - return []; - } - let minGap = 800; - let minIndexes; - for (let i = firstIndex; i >= 0; --i) { - for (let j = lastIndex; j < len; ++j) { - const gap = measure.getDistance(i, j); - if (gap < minGap) { - minGap = gap; - minIndexes = [i, ...indexes, j]; - } - } - } - return minIndexes == null - ? [] - : [ - new Score({ - distance: distance - minGap, - indexes: minIndexes, - multiplier, - circuit, - closingRadius: 800, - }), - ]; -} - -abstract class UKXCLBase { - score(measure: Measure): Score[] { - return [ - ...scoreOpenDistance(measure, 3, this.openDistanceMultiplier), - ...scoreCircuits(measure, this.scoreOutAndReturn, this.scoreTriangles), - ]; - } - - protected abstract outAndReturnFlatTriangleMultiplier(distance: number): number; - protected abstract faiTriangleMultiplier(distance: number): number; - protected abstract openDistanceMultiplier(distance: number): number; - - protected scoreOutAndReturn = (measure: Measure): Score[] => - scoreOutAndReturn(this.outAndReturnFlatTriangleMultiplier, measure); - - protected scoreTriangles = (measure: Measure, distances: number[], indexes: number[]): Score[] => - scoreTriangles(this.faiTriangleMultiplier, this.outAndReturnFlatTriangleMultiplier, measure, distances, indexes); -} - -export class UKXCLClub extends UKXCLBase { - name = 'UK (XC League, Club)'; - - protected outAndReturnFlatTriangleMultiplier(distance: number): number { - return distance < 5000 ? 0 : distance < 15000 ? 1.2 : distance < 35000 ? 1.3 : 1.7; - } - - protected faiTriangleMultiplier(distance: number): number { - return distance < 5000 ? 0 : distance < 15000 ? 1.5 : distance < 35000 ? 1.7 : 2.0; - } - - protected openDistanceMultiplier(distance: number): number { - return distance < 5000 ? 0 : 1; - } -} - -export class UKXCLInternational extends UKXCLBase { - name = 'UK (XC League, International)'; - - protected outAndReturnFlatTriangleMultiplier(distance: number): number { - return distance < 35000 ? 0 : 1.2; - } - - protected faiTriangleMultiplier(distance: number): number { - return distance < 25000 ? 0 : 1.5; - } - - protected openDistanceMultiplier(distance: number): number { - return distance < 10000 ? 0 : 1; - } -} - -export class UKXCLNational extends UKXCLBase { - name = 'UK (XC League, National)'; - - protected outAndReturnFlatTriangleMultiplier(distance: number): number { - return distance < 15000 ? 0 : distance < 35000 ? 1.3 : 1.7; - } - - protected faiTriangleMultiplier(distance: number): number { - return distance < 15000 ? 0 : distance < 25000 ? 1.7 : 2.0; - } - - protected openDistanceMultiplier(distance: number): number { - return distance < 10000 ? 0 : 1; - } -} diff --git a/apps/fxc-front/src/app/logic/score/league/wxc.ts b/apps/fxc-front/src/app/logic/score/league/wxc.ts deleted file mode 100644 index a3671cde..00000000 --- a/apps/fxc-front/src/app/logic/score/league/wxc.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { Measure } from '../measure'; -import { Score, scoreCircuits, scoreOpenDistance, scoreTriangles } from '../scorer'; - -export class WXC { - name = 'World XC Online Contest'; - - score(measure: Measure): Score[] { - return [ - ...scoreOpenDistance(measure, 3, this.openDistanceMultiplier), - ...scoreCircuits(measure, null, this.scoreTriangles), - ]; - } - - protected scoreTriangles = (measure: Measure, distances: number[], indexes: number[]): Score[] => - scoreTriangles(1.75, 2, 0.2, false, true, measure, distances, indexes); - - protected openDistanceMultiplier(): number { - return 1; - } -} diff --git a/apps/fxc-front/src/app/logic/score/league/xcontest.ts b/apps/fxc-front/src/app/logic/score/league/xcontest.ts deleted file mode 100644 index 8e5ee30f..00000000 --- a/apps/fxc-front/src/app/logic/score/league/xcontest.ts +++ /dev/null @@ -1,66 +0,0 @@ -import { Measure } from '../measure'; -import { Score, scoreCircuits, ScoreFunction, scoreOpenDistance, scoreTriangles } from '../scorer'; - -// https://www.xcontest.org/world/en/rules/ -export class XContest { - name = 'XContest'; - - score(measure: Measure): Score[] { - return [ - ...scoreOpenDistance(measure, 3, this.openDistanceMultiplier), - ...scoreCircuits(measure, null, this.scoreTriangles1), - ...scoreCircuits(measure, null, this.scoreTriangles2), - ]; - } - - protected scoreTriangles1: ScoreFunction = (measure: Measure, distances: number[], indexes: number[]): Score[] => - scoreTriangles(1.4, 1.6, 0.05, false, true, measure, distances, indexes); - - protected scoreTriangles2 = (measure: Measure, distances: number[], indexes: number[]): Score[] => - scoreTriangles(1.2, 1.4, 0.2, false, true, measure, distances, indexes); - - protected openDistanceMultiplier(): number { - return 1; - } -} - -// https://paramotors.xcontest.org/world/en/rules/ -export class XContestPPG { - name = 'XContest PPG'; - - score(measure: Measure): Score[] { - return [ - ...scoreOpenDistance(measure, 3, this.openDistanceMultiplier), - ...scoreCircuits(measure, null, this.scoreTriangles1), - ]; - } - - protected scoreTriangles1: ScoreFunction = (measure: Measure, distances: number[], indexes: number[]): Score[] => - scoreTriangles(2.0, 4.0, 800, true, true, measure, distances, indexes); - - protected openDistanceMultiplier(): number { - return 1; - } -} - -export class NorwayLeague { - name = 'Norway (Distanseligaen)'; - - score(measure: Measure): Score[] { - return [ - ...scoreOpenDistance(measure, 3, this.openDistanceMultiplier), - ...scoreCircuits(measure, null, this.scoreTriangles1), - ...scoreCircuits(measure, null, this.scoreTriangles2), - ]; - } - - protected scoreTriangles1: ScoreFunction = (measure: Measure, distances: number[], indexes: number[]): Score[] => - scoreTriangles(1.7, 2.4, 0.05, false, true, measure, distances, indexes); - - protected scoreTriangles2 = (measure: Measure, distances: number[], indexes: number[]): Score[] => - scoreTriangles(1.5, 2.2, 0.2, false, true, measure, distances, indexes); - - protected openDistanceMultiplier(): number { - return 1; - } -} diff --git a/apps/fxc-front/src/app/logic/score/measure.ts b/apps/fxc-front/src/app/logic/score/measure.ts deleted file mode 100644 index 541c8a98..00000000 --- a/apps/fxc-front/src/app/logic/score/measure.ts +++ /dev/null @@ -1,153 +0,0 @@ -// Optimization code adapted from -// https://github.com/twpayne/maxxc/blob/7c22a46536ec7299ec91d135fea10b5b5925032c/track.c - -import { LatLon } from '@flyxc/common'; -import { getDistance } from 'geolib'; - -export interface PointReference { - index: number; - distance: number; -} - -export class Measure { - private maxDelta = 0; - private len: number; - private distanceCache: { [idxA: number]: { [idxB: number]: number } } = {}; - private farthestBefore: PointReference[] = []; - private farthestAfter: PointReference[] = []; - - constructor(public points: LatLon[]) { - this.len = points.length; - for (let i = 0; i < this.len - 1; i++) { - this.maxDelta = Math.max(this.maxDelta, getDistance(points[i], points[i + 1])); - } - this.farthestBefore.push({ index: 0, distance: 0 }); - for (let i = 1; i < this.len; i++) { - this.farthestBefore.push(this.getFarthestFrom(i, 0, i, 0)); - } - for (let i = 0; i < this.len - 1; i++) { - this.farthestAfter.push(this.getFarthestFrom(i, i, this.len, 0)); - } - this.farthestAfter.push({ index: this.len - 1, distance: 0 }); - } - - getDistance(indexA: number, indexB: number): number { - if (indexA === indexB) { - return 0; - } - if (indexB < indexA) { - [indexA, indexB] = [indexB, indexA]; - } - if (!(indexA in this.distanceCache)) { - this.distanceCache[indexA] = {}; - } - if (!(indexB in this.distanceCache[indexA])) { - this.distanceCache[indexA][indexB] = getDistance(this.points[indexA], this.points[indexB]); - } - return this.distanceCache[indexA][indexB]; - } - - getFarthestFrom(ref: number, startIndex: number, stopIndex: number, bound: number): PointReference { - let index = -1; - let distance; - for (let i = startIndex; i < stopIndex; ) { - distance = this.getDistance(ref, i); - if (distance >= bound) { - index = i; - bound = distance; - i++; - } else { - i = this.fastForward(i, bound - distance); - } - } - return { index, distance: bound }; - } - - computeOpenDistance0(): { distance: number; indexes: number[] } { - const indexes = [-1, -1]; - let bound = -1; - for (let start = 0; start < this.len - 1; start++) { - const finish = this.getFarthestFrom(start, start + 1, this.len, bound); - if (finish.index > -1) { - indexes[0] = start; - indexes[1] = finish.index; - bound = finish.distance; - } - } - return { distance: bound, indexes }; - } - - computeOpenDistance1(): { distance: number; indexes: number[] } { - const indexes = [-1, -1, -1]; - let bound = -1; - for (let tp1 = 1; tp1 < this.len - 1; ) { - const total = this.farthestBefore[tp1].distance + this.farthestAfter[tp1].distance; - if (total > bound) { - indexes[0] = this.farthestBefore[tp1].index; - indexes[1] = tp1; - indexes[2] = this.farthestAfter[tp1].index; - bound = total; - tp1++; - } else { - tp1 = this.fastForward(tp1, (bound - total) / 2); - } - } - return { distance: bound, indexes }; - } - - computeOpenDistance2(): { distance: number; indexes: number[] } { - const indexes = [-1, -1, -1, -1]; - let bound = -1; - for (let tp1 = 1; tp1 < this.len - 2; tp1++) { - const leg1 = this.farthestBefore[tp1].distance; - for (let tp2 = tp1 + 1; tp2 < this.len - 1; ) { - const distance = leg1 + this.getDistance(tp1, tp2) + this.farthestAfter[tp2].distance; - if (distance > bound) { - indexes[0] = this.farthestBefore[tp1].index; - indexes[1] = tp1; - indexes[2] = tp2; - indexes[3] = this.farthestAfter[tp2].index; - bound = distance; - tp2++; - } else { - tp2 = this.fastForward(tp2, (bound - distance) / 2); - } - } - } - return { distance: bound, indexes }; - } - - computeOpenDistance3(): { distance: number; indexes: number[] } { - const indexes = [-1, -1, -1, -1, -1]; - let bound = -1; - for (let tp1 = 1; tp1 < this.len - 3; tp1++) { - const leg1 = this.farthestBefore[tp1].distance; - for (let tp2 = tp1 + 1; tp2 < this.len - 2; tp2++) { - const leg2 = this.getDistance(tp1, tp2); - for (let tp3 = tp2 + 1; tp3 < this.len - 1; ) { - const distance = leg1 + leg2 + this.getDistance(tp2, tp3) + this.farthestAfter[tp3].distance; - if (distance > bound) { - indexes[0] = this.farthestBefore[tp1].index; - indexes[1] = tp1; - indexes[2] = tp2; - indexes[3] = tp3; - indexes[4] = this.farthestAfter[tp3].index; - bound = distance; - tp3++; - } else { - tp3 = this.fastForward(tp3, (bound - distance) / 2); - } - } - } - } - return { distance: bound, indexes }; - } - - private fastForward(index: number, distance: number): number { - if (this.maxDelta == 0) { - return index + 1; - } - const step = Math.floor(distance / this.maxDelta); - return index + Math.max(step, 1); - } -} diff --git a/apps/fxc-front/src/app/logic/score/scorer.ts b/apps/fxc-front/src/app/logic/score/scorer.ts index 89269756..7fb6fb9a 100644 --- a/apps/fxc-front/src/app/logic/score/scorer.ts +++ b/apps/fxc-front/src/app/logic/score/scorer.ts @@ -1,140 +1,19 @@ -import { Measure } from './measure'; - -export type ScoreFunction = (measure: Measure, distances: number[], indexes: number[]) => Score[]; - -export enum CircuitType { - OpenDistance = 'Open distance', - FlatTriangle = 'Flat triangle', - FaiTriangle = 'Fai triangle', - OutAndReturn = 'Out and return', -} +import { CircuitType } from '@flyxc/optimizer/lib/api'; export class Score { - distance: number; + distanceM: number; indexes: number[]; multiplier: number; circuit: CircuitType; - closingRadius: number | null; + closingRadiusM: number; points: number; constructor(score: Partial) { - this.distance = score.distance || 0; - this.indexes = score.indexes || []; - this.multiplier = score.multiplier || 1; - this.circuit = score.circuit || CircuitType.OpenDistance; - this.closingRadius = score.closingRadius || null; - this.points = (this.distance * this.multiplier) / 1000; - } -} - -export function scoreOpenDistance( - measure: Measure, - maxTurnpoints: number, - multiplierFn: (d: number) => number, -): Score[] { - const len = measure.points.length; - let result: any; - if (maxTurnpoints == 0 || len == 2) { - result = measure.computeOpenDistance0(); - } else if (maxTurnpoints == 1 || len == 3) { - result = measure.computeOpenDistance1(); - } else if (maxTurnpoints == 2 || len == 4) { - result = measure.computeOpenDistance2(); - } else if (maxTurnpoints == 3 || len == 5) { - result = measure.computeOpenDistance3(); - } - - return result - ? [ - new Score({ - distance: result.distance, - indexes: result.indexes, - multiplier: multiplierFn(result.distance), - circuit: CircuitType.OpenDistance, - }), - ] - : []; -} - -export function scoreCircuits( - measure: Measure, - scoreOutAndReturn: ScoreFunction | null, - scoreTriangles: ScoreFunction | null, -): Score[] { - const len = measure.points.length; - const distances = [0, 0, 0]; - const scores: Score[] = []; - for (let i = 0; i < len - 2; i++) { - for (let j = i + 1; j < len - 1; j++) { - distances[0] = measure.getDistance(i, j); - if (scoreOutAndReturn) { - const score = scoreOutAndReturn(measure, distances, [i, j]); - if (score) { - scores.push(...score); - } - } - if (scoreTriangles) { - for (let k = j + 1; k < len; k++) { - distances[1] = measure.getDistance(j, k); - distances[2] = measure.getDistance(k, i); - const score = scoreTriangles(measure, distances, [i, j, k]); - if (score) { - scores.push(...score); - } - } - } - } - } - return scores; -} - -export function scoreTriangles( - flatMul: number | undefined, - faiMul: number | undefined, - tolerance: number, - absolute: boolean, - penalize: boolean, - measure: Measure, - distances: number[], - indexes: number[], -): Score[] { - const firstIdx = indexes[0]; - const lastIdx = indexes[2]; - const len = measure.points.length; - const distance = distances[0] + distances[1] + distances[2]; - const closingRadius = absolute ? tolerance : tolerance * distance; - let minGap = closingRadius; - let minIndexes: null | number[] = null; - for (let i = firstIdx; i >= 0; i--) { - for (let j = lastIdx; j < len; j++) { - const gap = measure.getDistance(i, j); - if (gap <= minGap) { - minGap = gap; - minIndexes = [i, ...indexes, j]; - } - } - } - if (!minIndexes) { - return []; - } - let multiplier: number; - let circuit: CircuitType; - if (faiMul && Math.min(...distances) / distance >= 0.28) { - multiplier = faiMul; - circuit = CircuitType.FaiTriangle; - } else if (flatMul) { - multiplier = flatMul; - circuit = CircuitType.FlatTriangle; - } else { - return []; + this.distanceM = score.distanceM ?? 0; + this.indexes = score.indexes ?? []; + this.multiplier = score.multiplier ?? 1; + this.circuit = score.circuit ?? CircuitType.OpenDistance; + this.closingRadiusM = score.closingRadiusM ?? 0; + this.points = score.points ?? 0; } - return [ - new Score({ - distance: penalize ? distance - minGap : distance, - indexes: minIndexes, - multiplier, - circuit, - closingRadius, - }), - ]; } diff --git a/apps/fxc-front/src/app/pages/admin-elements.ts b/apps/fxc-front/src/app/pages/admin-elements.ts index 9eb48eef..f9f3e367 100644 --- a/apps/fxc-front/src/app/pages/admin-elements.ts +++ b/apps/fxc-front/src/app/pages/admin-elements.ts @@ -1,5 +1,6 @@ import * as common from '@flyxc/common'; -import { Binder, CheckedFieldStrategy, field, VaadinFieldStrategy } from '@vaadin/dom'; +import type { Binder } from '@vaadin/dom'; +import { CheckedFieldStrategy, field, VaadinFieldStrategy } from '@vaadin/dom'; import * as lit from 'lit'; import { customElement, property, query } from 'lit/decorators.js'; import { when } from 'lit/directives/when.js'; diff --git a/apps/fxc-front/src/app/pages/admin.ts b/apps/fxc-front/src/app/pages/admin.ts index 199b7303..9707e0b4 100644 --- a/apps/fxc-front/src/app/pages/admin.ts +++ b/apps/fxc-front/src/app/pages/admin.ts @@ -1,6 +1,7 @@ import '@alenaksu/json-viewer'; import * as common from '@flyxc/common'; -import { html, LitElement, PropertyValues, TemplateResult } from 'lit'; +import type { PropertyValues, TemplateResult } from 'lit'; +import { html, LitElement } from 'lit'; import { customElement, property, state } from 'lit/decorators.js'; import { unsafeHTML } from 'lit/directives/unsafe-html.js'; import { when } from 'lit/directives/when.js'; diff --git a/apps/fxc-front/src/app/pages/archives.ts b/apps/fxc-front/src/app/pages/archives.ts index 5de7e753..1f2d1f5e 100644 --- a/apps/fxc-front/src/app/pages/archives.ts +++ b/apps/fxc-front/src/app/pages/archives.ts @@ -1,5 +1,7 @@ -import { createPopper, Instance } from '@popperjs/core'; -import { html, LitElement, TemplateResult } from 'lit'; +import type { Instance } from '@popperjs/core'; +import { createPopper } from '@popperjs/core'; +import type { TemplateResult } from 'lit'; +import { html, LitElement } from 'lit'; import { customElement, state } from 'lit/decorators.js'; import { when } from 'lit/directives/when.js'; import { getApiKeyAndHost } from '../apikey'; diff --git a/apps/fxc-front/src/app/pages/privacy-policy.ts b/apps/fxc-front/src/app/pages/privacy-policy.ts index 96de7afd..f4e6709a 100644 --- a/apps/fxc-front/src/app/pages/privacy-policy.ts +++ b/apps/fxc-front/src/app/pages/privacy-policy.ts @@ -1,4 +1,5 @@ -import { html, LitElement, TemplateResult } from 'lit'; +import type { TemplateResult } from 'lit'; +import { html, LitElement } from 'lit'; import { customElement } from 'lit/decorators.js'; @customElement('privacy-policy-page') diff --git a/apps/fxc-front/src/app/pages/settings.ts b/apps/fxc-front/src/app/pages/settings.ts index a278daa9..25fc89c9 100644 --- a/apps/fxc-front/src/app/pages/settings.ts +++ b/apps/fxc-front/src/app/pages/settings.ts @@ -2,13 +2,14 @@ import { AccountFormModel, fetchResponse } from '@flyxc/common'; import { alertController } from '@ionic/core/components'; import { Binder, field } from '@vaadin/dom'; -import { LitElement, TemplateResult, html } from 'lit'; +import type { TemplateResult } from 'lit'; +import { LitElement, html } from 'lit'; import { customElement, queryAll, state } from 'lit/decorators.js'; import { when } from 'lit/directives/when.js'; import { setFetchMillis } from '../redux/live-track-slice'; import '../components/ui/google-btn'; import './admin-elements'; -import { TrackerPanel } from './admin-elements'; +import type { TrackerPanel } from './admin-elements'; @customElement('settings-page') export class SettingsPage extends LitElement { diff --git a/apps/fxc-front/src/app/pages/terms.ts b/apps/fxc-front/src/app/pages/terms.ts index 8064c728..7a5410b8 100644 --- a/apps/fxc-front/src/app/pages/terms.ts +++ b/apps/fxc-front/src/app/pages/terms.ts @@ -1,4 +1,4 @@ -import { html, LitElement, TemplateResult } from 'lit'; +import { html, LitElement, type TemplateResult } from 'lit'; import { customElement } from 'lit/decorators.js'; @customElement('terms-page') diff --git a/apps/fxc-front/src/app/redux/airspace-slice.ts b/apps/fxc-front/src/app/redux/airspace-slice.ts index 53d5fcfb..45a39515 100644 --- a/apps/fxc-front/src/app/redux/airspace-slice.ts +++ b/apps/fxc-front/src/app/redux/airspace-slice.ts @@ -1,5 +1,6 @@ import { Class, Type } from '@flyxc/common'; -import { PayloadAction, createSlice } from '@reduxjs/toolkit'; +import type { PayloadAction } from '@reduxjs/toolkit'; +import { createSlice } from '@reduxjs/toolkit'; type AirspaceState = { maxAltitude: number; diff --git a/apps/fxc-front/src/app/redux/app-slice.ts b/apps/fxc-front/src/app/redux/app-slice.ts index 49e1eb13..ae900a7d 100644 --- a/apps/fxc-front/src/app/redux/app-slice.ts +++ b/apps/fxc-front/src/app/redux/app-slice.ts @@ -1,4 +1,5 @@ -import { createSlice, PayloadAction, Store } from '@reduxjs/toolkit'; +import type { PayloadAction, Store } from '@reduxjs/toolkit'; +import { createSlice } from '@reduxjs/toolkit'; export const UPDATE_APP_TIME_EVERY_MIN = 10; diff --git a/apps/fxc-front/src/app/redux/arcgis-slice.ts b/apps/fxc-front/src/app/redux/arcgis-slice.ts index fc1e4fb8..c05063cc 100644 --- a/apps/fxc-front/src/app/redux/arcgis-slice.ts +++ b/apps/fxc-front/src/app/redux/arcgis-slice.ts @@ -1,4 +1,5 @@ -import { createSlice, PayloadAction } from '@reduxjs/toolkit'; +import type { PayloadAction } from '@reduxjs/toolkit'; +import { createSlice } from '@reduxjs/toolkit'; type ArcgisState = { // Altitude exaggeration multiplier for 3d. diff --git a/apps/fxc-front/src/app/redux/browser-slice.ts b/apps/fxc-front/src/app/redux/browser-slice.ts index 50e46d30..8c820486 100644 --- a/apps/fxc-front/src/app/redux/browser-slice.ts +++ b/apps/fxc-front/src/app/redux/browser-slice.ts @@ -1,5 +1,6 @@ import { getHostName } from '@flyxc/common'; -import { createSlice, PayloadAction } from '@reduxjs/toolkit'; +import type { PayloadAction } from '@reduxjs/toolkit'; +import { createSlice } from '@reduxjs/toolkit'; import { store } from './store'; type BrowserState = { diff --git a/apps/fxc-front/src/app/redux/live-track-slice.ts b/apps/fxc-front/src/app/redux/live-track-slice.ts index a2f7baf7..a0ef4a05 100644 --- a/apps/fxc-front/src/app/redux/live-track-slice.ts +++ b/apps/fxc-front/src/app/redux/live-track-slice.ts @@ -1,18 +1,14 @@ -import { getLastMessage, isEmergencyTrack, LatLonAlt, protos } from '@flyxc/common'; +import type { LatLonAlt, protos } from '@flyxc/common'; +import { getLastMessage, isEmergencyTrack } from '@flyxc/common'; -import { - createAsyncThunk, - createEntityAdapter, - createSelector, - createSlice, - EntityState, - PayloadAction, -} from '@reduxjs/toolkit'; +import type { EntityState, PayloadAction } from '@reduxjs/toolkit'; +import { createAsyncThunk, createEntityAdapter, createSelector, createSlice } from '@reduxjs/toolkit'; import type { Response } from '../workers/live-track'; import LiveTrackWorker from '../workers/live-track?worker'; import { isMobile } from './browser-slice'; -import { RootState, store } from './store'; +import type { RootState } from './store'; +import { store } from './store'; // Refresh live tracks every. const REFRESH_INTERVAL_SEC = isMobile() ? 2 * 60 : 60; diff --git a/apps/fxc-front/src/app/redux/location-slice.ts b/apps/fxc-front/src/app/redux/location-slice.ts index 52a578dc..96cba684 100644 --- a/apps/fxc-front/src/app/redux/location-slice.ts +++ b/apps/fxc-front/src/app/redux/location-slice.ts @@ -1,6 +1,7 @@ -import { LatLon } from '@flyxc/common'; +import type { LatLon } from '@flyxc/common'; -import { createSlice, PayloadAction } from '@reduxjs/toolkit'; +import type { PayloadAction } from '@reduxjs/toolkit'; +import { createSlice } from '@reduxjs/toolkit'; type State = { // Current location and zoom level. diff --git a/apps/fxc-front/src/app/redux/planner-slice.ts b/apps/fxc-front/src/app/redux/planner-slice.ts index 5b3a4a85..36c28dec 100644 --- a/apps/fxc-front/src/app/redux/planner-slice.ts +++ b/apps/fxc-front/src/app/redux/planner-slice.ts @@ -1,13 +1,15 @@ -import { createSlice, PayloadAction } from '@reduxjs/toolkit'; +import type { PayloadAction } from '@reduxjs/toolkit'; +import { createSlice } from '@reduxjs/toolkit'; import { deleteUrlParam, getUrlParamValues, ParamNames, setUrlParamValue } from '../logic/history'; -import { Score } from '../logic/score/scorer'; +import type { Score } from '../logic/score/scorer'; +import type { LeagueCode } from '../logic/score/league/leagues'; export type PlannerState = { score?: Score; speed: number; distance: number; - league: string; + league: LeagueCode; enabled: boolean; // Encoded route. route: string; @@ -21,7 +23,7 @@ const initialState: PlannerState = { score: undefined, speed: Number(getUrlParamValues(ParamNames.speed)[0] ?? 20), distance: 0, - league: getUrlParamValues(ParamNames.league)[0] ?? localStorage.getItem('league') ?? 'xc', + league: (getUrlParamValues(ParamNames.league)[0] ?? localStorage.getItem('league') ?? 'xc') as LeagueCode, enabled, route, isFreeDrawing: false, @@ -41,7 +43,7 @@ const plannerSlice = createSlice({ state.speed = Math.max(1, action.payload); setUrlParamValue(ParamNames.speed, state.speed.toFixed(1)); }, - setLeague: (state, action: PayloadAction) => { + setLeague: (state, action: PayloadAction) => { setUrlParamValue(ParamNames.league, action.payload); localStorage.setItem('league', action.payload); state.league = action.payload; diff --git a/apps/fxc-front/src/app/redux/selectors.ts b/apps/fxc-front/src/app/redux/selectors.ts index dc42220c..8e02c7d3 100644 --- a/apps/fxc-front/src/app/redux/selectors.ts +++ b/apps/fxc-front/src/app/redux/selectors.ts @@ -1,9 +1,11 @@ -import { extractGroupId, LatLon, LatLonAlt, RuntimeTrack, sampleAt } from '@flyxc/common'; +import type { LatLon, LatLonAlt, RuntimeTrack } from '@flyxc/common'; +import { extractGroupId, sampleAt } from '@flyxc/common'; import { createSelector } from 'reselect'; -import { DistanceUnit, Units } from '../logic/units'; +import type { Units } from '../logic/units'; +import { DistanceUnit } from '../logic/units'; import { getUniqueColor } from '../styles/track'; -import { RootState } from './store'; +import type { RootState } from './store'; import { trackAdapterSelector } from './track-slice'; export const units = (state: RootState): Units => state.units; diff --git a/apps/fxc-front/src/app/redux/skyways-slice.ts b/apps/fxc-front/src/app/redux/skyways-slice.ts index f2f9ed92..a75e694e 100644 --- a/apps/fxc-front/src/app/redux/skyways-slice.ts +++ b/apps/fxc-front/src/app/redux/skyways-slice.ts @@ -2,8 +2,9 @@ // // See https://thermal.kk7.ch -import { createSelector, createSlice, PayloadAction } from '@reduxjs/toolkit'; -import { RootState } from './store'; +import type { PayloadAction } from '@reduxjs/toolkit'; +import { createSelector, createSlice } from '@reduxjs/toolkit'; +import type { RootState } from './store'; const TILE_URL = 'https://thermal.kk7.ch/tiles/{layer}/{z}/{x}/{y}.png?src={domain}'.replace( '{domain}', diff --git a/apps/fxc-front/src/app/redux/store.ts b/apps/fxc-front/src/app/redux/store.ts index 649522ed..9a93b35d 100644 --- a/apps/fxc-front/src/app/redux/store.ts +++ b/apps/fxc-front/src/app/redux/store.ts @@ -1,6 +1,7 @@ -import { ThunkAction } from 'redux-thunk'; +import type { ThunkAction } from 'redux-thunk'; -import { Action, DevToolsEnhancerOptions, combineReducers, configureStore } from '@reduxjs/toolkit'; +import type { Action, DevToolsEnhancerOptions } from '@reduxjs/toolkit'; +import { combineReducers, configureStore } from '@reduxjs/toolkit'; import * as airspace from './airspace-slice'; import * as app from './app-slice'; diff --git a/apps/fxc-front/src/app/redux/track-slice.ts b/apps/fxc-front/src/app/redux/track-slice.ts index ca946901..d6efc3df 100644 --- a/apps/fxc-front/src/app/redux/track-slice.ts +++ b/apps/fxc-front/src/app/redux/track-slice.ts @@ -1,3 +1,4 @@ +import type { RuntimeTrack } from '@flyxc/common'; import { addAirspaces, addGroundAltitude, @@ -5,16 +6,16 @@ import { createTrackId, extractGroupId, protos, - RuntimeTrack, } from '@flyxc/common'; -import { createAsyncThunk, createEntityAdapter, createSlice, EntityState, PayloadAction } from '@reduxjs/toolkit'; +import type { EntityState, PayloadAction } from '@reduxjs/toolkit'; +import { createAsyncThunk, createEntityAdapter, createSlice } from '@reduxjs/toolkit'; import { addUrlParamValue, deleteUrlParamValue, ParamNames } from '../logic/history'; import * as msg from '../logic/messages'; import type { Response } from '../workers/track'; import TrackWorker from '../workers/track?worker'; import { setTimeSec } from './app-slice'; import { setEnabled, setRoute } from './planner-slice'; -import { AppDispatch, AppThunk, RootState } from './store'; +import type { AppDispatch, AppThunk, RootState } from './store'; const FETCH_EVERY_SECONDS = 15; export const FETCH_FOR_MINUTES = 3; diff --git a/apps/fxc-front/src/app/redux/units-slice.ts b/apps/fxc-front/src/app/redux/units-slice.ts index 9a87a540..78237a4a 100644 --- a/apps/fxc-front/src/app/redux/units-slice.ts +++ b/apps/fxc-front/src/app/redux/units-slice.ts @@ -1,6 +1,8 @@ -import { createSlice, PayloadAction } from '@reduxjs/toolkit'; +import type { PayloadAction } from '@reduxjs/toolkit'; +import { createSlice } from '@reduxjs/toolkit'; -import { DistanceUnit, SpeedUnit, Units } from '../logic/units'; +import type { Units } from '../logic/units'; +import { DistanceUnit, SpeedUnit } from '../logic/units'; const initialState: Units = { distance: (localStorage.getItem('unit.distance') ?? DistanceUnit.Kilometers) as DistanceUnit, diff --git a/apps/fxc-front/src/app/workers/optimizer.ts b/apps/fxc-front/src/app/workers/optimizer.ts new file mode 100644 index 00000000..dcec1eb6 --- /dev/null +++ b/apps/fxc-front/src/app/workers/optimizer.ts @@ -0,0 +1,26 @@ +import type { ScoringRequest, ScoringResult } from '@flyxc/optimizer/lib/optimizer'; +import { getOptimizer } from '@flyxc/optimizer/lib/optimizer'; + +export interface Request { + request: ScoringRequest; + id?: number; +} + +export interface Response { + response: ScoringResult; + id?: number; +} + +addEventListener('message', (event: MessageEvent) => { + const { request, id } = event.data; + const optimizer = getOptimizer(request); + let result: IteratorResult; + do { + result = optimizer.next(); + } while (!result.done); + + postMessage({ + response: result.value, + id, + } satisfies Response); +}); diff --git a/apps/fxc-front/src/app/workers/track.ts b/apps/fxc-front/src/app/workers/track.ts index fdfb379c..995fa327 100644 --- a/apps/fxc-front/src/app/workers/track.ts +++ b/apps/fxc-front/src/app/workers/track.ts @@ -1,7 +1,8 @@ // Filter the altitude using a median filter. // Compute the heading. -import { computeVerticalSpeed, RuntimeTrack } from '@flyxc/common'; +import type { RuntimeTrack } from '@flyxc/common'; +import { computeVerticalSpeed } from '@flyxc/common'; import { getRhumbLineBearing } from 'geolib'; import createMedianFilter from 'moving-median'; diff --git a/apps/fxc-front/src/env.d.ts b/apps/fxc-front/src/env.d.ts index 1f62738a..5ad68dc8 100644 --- a/apps/fxc-front/src/env.d.ts +++ b/apps/fxc-front/src/env.d.ts @@ -1,7 +1,7 @@ /// /// -import { AirspaceServer } from '@flyxc/common'; +import type { AirspaceServer } from '@flyxc/common'; interface ImportMetaEnv { VITE_AIRSPACE_SERVER: AirspaceServer; diff --git a/apps/fxc-front/src/flyxc.ts b/apps/fxc-front/src/flyxc.ts index bd9e4b35..0c2b7dba 100644 --- a/apps/fxc-front/src/flyxc.ts +++ b/apps/fxc-front/src/flyxc.ts @@ -11,14 +11,15 @@ import './app/components/chart-element'; import './app/components/loader-element'; import './app/components/ui/main-menu'; -import { LatLonAlt } from '@flyxc/common'; -import { html, LitElement, TemplateResult } from 'lit'; +import type { LatLonAlt } from '@flyxc/common'; +import type { TemplateResult } from 'lit'; +import { html, LitElement } from 'lit'; import { customElement, state } from 'lit/decorators.js'; import { classMap } from 'lit/directives/class-map.js'; import { when } from 'lit/directives/when.js'; import { connect } from 'pwa-helpers'; -import { NavigationHookResult } from '@ionic/core/dist/types/components/route/route-interface'; +import type { NavigationHookResult } from '@ionic/core/dist/types/components/route/route-interface'; import { ionicInit } from './app/components/ui/ionic'; import { requestCurrentPosition } from './app/logic/geolocation'; @@ -36,7 +37,8 @@ import { downloadTracksByGroupIds, downloadTracksByUrls, uploadTracks } from './ import * as app from './app/redux/app-slice'; import * as planner from './app/redux/planner-slice'; import * as sel from './app/redux/selectors'; -import { RootState, store } from './app/redux/store'; +import type { RootState } from './app/redux/store'; +import { store } from './app/redux/store'; import * as track from './app/redux/track-slice'; export const SHOW_SPLIT_PANE_WHEN = `(min-width: 992px)`; diff --git a/apps/fxc-front/vite.config.ts b/apps/fxc-front/vite.config.ts index 488ba723..0ef2fcbd 100644 --- a/apps/fxc-front/vite.config.ts +++ b/apps/fxc-front/vite.config.ts @@ -93,6 +93,8 @@ export default defineConfig({ define: { __BUILD_TIMESTAMP__: JSON.stringify(format(new Date(), 'yyyyMMdd.HHmm')), __AIRSPACE_DATE__: JSON.stringify(getAirspaceDate()), + // TODO(vicb): check how to remove this (igc-xc-score) + global: {}, }, }); diff --git a/apps/fxc-server/src/app/parser/geojson.ts b/apps/fxc-server/src/app/parser/geojson.ts index e1b04d1c..e16ff6f9 100644 --- a/apps/fxc-server/src/app/parser/geojson.ts +++ b/apps/fxc-server/src/app/parser/geojson.ts @@ -1,4 +1,4 @@ -import { protos } from '@flyxc/common'; +import type { protos } from '@flyxc/common'; // Parse a GeoJson track to a proto message. export function parseGeoJson(geojson: any): protos.Track[] { diff --git a/apps/fxc-server/src/app/parser/igc.ts b/apps/fxc-server/src/app/parser/igc.ts index 61db5b40..771578f4 100644 --- a/apps/fxc-server/src/app/parser/igc.ts +++ b/apps/fxc-server/src/app/parser/igc.ts @@ -1,4 +1,4 @@ -import { protos } from '@flyxc/common'; +import type { protos } from '@flyxc/common'; import IGCParser from 'igc-parser'; export function parse(content: string): protos.Track[] { diff --git a/apps/fxc-server/src/app/parser/kml.ts b/apps/fxc-server/src/app/parser/kml.ts index 69a7ac84..2df0efcf 100644 --- a/apps/fxc-server/src/app/parser/kml.ts +++ b/apps/fxc-server/src/app/parser/kml.ts @@ -1,4 +1,4 @@ -import { protos } from '@flyxc/common'; +import type { protos } from '@flyxc/common'; import * as toGeoJSON from '@tmcw/togeojson'; import { DOMParser } from '@xmldom/xmldom'; diff --git a/apps/fxc-server/src/app/parser/parser.ts b/apps/fxc-server/src/app/parser/parser.ts index 746562e5..bafcfa1b 100644 --- a/apps/fxc-server/src/app/parser/parser.ts +++ b/apps/fxc-server/src/app/parser/parser.ts @@ -1,12 +1,12 @@ import { diffEncodeTrack, fetchResponse, formatReqError, protos } from '@flyxc/common'; +import type { TrackEntity } from '@flyxc/common-node'; import { retrieveMetaTrackGroupByHash, retrieveMetaTrackGroupByUrl, saveTrack, - TrackEntity, queueTrackPostProcessing, } from '@flyxc/common-node'; -import { Datastore } from '@google-cloud/datastore'; +import type { Datastore } from '@google-cloud/datastore'; import * as crypto from 'node:crypto'; import { parse as parseGpx, parseRoute as parseGpxRoute } from './gpx'; import { parse as parseIgc } from './igc'; diff --git a/apps/fxc-server/src/app/parser/trk.ts b/apps/fxc-server/src/app/parser/trk.ts index dfff584c..421f37c8 100644 --- a/apps/fxc-server/src/app/parser/trk.ts +++ b/apps/fxc-server/src/app/parser/trk.ts @@ -1,4 +1,4 @@ -import { protos } from '@flyxc/common'; +import type { protos } from '@flyxc/common'; const months: { [key: string]: number } = { JAN: 0, diff --git a/apps/fxc-server/src/app/routes/admin.ts b/apps/fxc-server/src/app/routes/admin.ts index ef297ad3..d09f5a05 100644 --- a/apps/fxc-server/src/app/routes/admin.ts +++ b/apps/fxc-server/src/app/routes/admin.ts @@ -1,8 +1,9 @@ import csurf from '@dr.pogodin/csurf'; import { AccountFormModel, Keys, trackerNames, ufoFleetNames } from '@flyxc/common'; import { TRACK_TABLE, retrieveLiveTrackById, retrieveRecentTracks } from '@flyxc/common-node'; -import { NextFunction, Request, Response, Router } from 'express'; -import { Redis } from 'ioredis'; +import type { NextFunction, Request, Response } from 'express'; +import { Router } from 'express'; +import type { Redis } from 'ioredis'; import zlib from 'node:zlib'; import { Datastore } from '@google-cloud/datastore'; diff --git a/apps/fxc-server/src/app/routes/live-track.ts b/apps/fxc-server/src/app/routes/live-track.ts index cf4f84be..8c4f31a7 100644 --- a/apps/fxc-server/src/app/routes/live-track.ts +++ b/apps/fxc-server/src/app/routes/live-track.ts @@ -1,11 +1,10 @@ import csurf from '@dr.pogodin/csurf'; +import type { AccountModel, LiveTrackEntity } from '@flyxc/common'; import { AccountFormModel, - AccountModel, LONG_INCREMENTAL_UPDATE_SEC, SHORT_INCREMENTAL_UPDATE_SEC, Keys, - LiveTrackEntity, protos, SecretKeys, } from '@flyxc/common'; @@ -19,8 +18,9 @@ import { } from '@flyxc/common-node'; import { Datastore } from '@google-cloud/datastore'; import { NoDomBinder } from '@vaadin/nodom'; -import { Request, Response, Router } from 'express'; -import { Redis } from 'ioredis'; +import type { Request, Response } from 'express'; +import { Router } from 'express'; +import type { Redis } from 'ioredis'; import { getUserInfo, isLoggedIn, logout } from './session'; // Store the token in the session. diff --git a/apps/fxc-server/src/app/routes/session.ts b/apps/fxc-server/src/app/routes/session.ts index 81fda9c5..55c9a270 100644 --- a/apps/fxc-server/src/app/routes/session.ts +++ b/apps/fxc-server/src/app/routes/session.ts @@ -1,5 +1,5 @@ import { SecretKeys } from '@flyxc/common'; -import { Request } from 'express'; +import type { Request } from 'express'; const adminEmails = SecretKeys.ADMINS.split(','); diff --git a/apps/fxc-server/src/app/routes/supporters.ts b/apps/fxc-server/src/app/routes/supporters.ts index 423c0017..b06e046d 100644 --- a/apps/fxc-server/src/app/routes/supporters.ts +++ b/apps/fxc-server/src/app/routes/supporters.ts @@ -1,6 +1,7 @@ import { Keys } from '@flyxc/common'; -import { Request, Response, Router } from 'express'; -import { Redis } from 'ioredis'; +import type { Request, Response } from 'express'; +import { Router } from 'express'; +import type { Redis } from 'ioredis'; export function getSupportersRouter(redis: Redis): Router { const router = Router(); diff --git a/apps/fxc-server/src/app/routes/track.ts b/apps/fxc-server/src/app/routes/track.ts index e78b2613..fd165a90 100644 --- a/apps/fxc-server/src/app/routes/track.ts +++ b/apps/fxc-server/src/app/routes/track.ts @@ -4,9 +4,10 @@ import { retrieveMetaTrackGroupByUrl, retrieveMetaTrackGroupsByIds, } from '@flyxc/common-node'; -import { Datastore } from '@google-cloud/datastore'; -import { Request, Response, Router } from 'express'; -import { UploadedFile } from 'express-fileupload'; +import type { Datastore } from '@google-cloud/datastore'; +import type { Request, Response } from 'express'; +import { Router } from 'express'; +import type { UploadedFile } from 'express-fileupload'; import { parse, parseFromUrl, parseRoute } from '../parser/parser'; diff --git a/apps/fxc-server/src/app/routes/waypoints.ts b/apps/fxc-server/src/app/routes/waypoints.ts index 6b2418a8..50553971 100644 --- a/apps/fxc-server/src/app/routes/waypoints.ts +++ b/apps/fxc-server/src/app/routes/waypoints.ts @@ -1,4 +1,5 @@ -import { Request, Response, Router } from 'express'; +import type { Request, Response } from 'express'; +import { Router } from 'express'; import { encode } from '../waypoints/waypoints'; export function getWaypointRouter(): Router { diff --git a/apps/fxc-server/src/app/routes/zoleo.ts b/apps/fxc-server/src/app/routes/zoleo.ts index ddbf2a98..8e1a17f3 100644 --- a/apps/fxc-server/src/app/routes/zoleo.ts +++ b/apps/fxc-server/src/app/routes/zoleo.ts @@ -1,17 +1,18 @@ import { Keys, SecretKeys, fetchResponse, round } from '@flyxc/common'; +import type { ZoleoMessage } from '@flyxc/common-node'; import { LIVE_TRACK_TABLE, ZOLEO_MAX_MSG, ZOLEO_MAX_MSG_SIZE, - ZoleoMessage, getDatastore, pushListCap, retrieveLiveTrackByGoogleId, } from '@flyxc/common-node'; import { Datastore } from '@google-cloud/datastore'; -import { Request, Response, Router } from 'express'; +import type { Request, Response } from 'express'; +import { Router } from 'express'; import basicAuth from 'express-basic-auth'; -import Redis from 'ioredis'; +import type Redis from 'ioredis'; import { pathGet } from 'object-standard-path'; import { getUserInfo, isLoggedIn } from './session'; diff --git a/apps/fxc-server/src/app/waypoints/waypoints.ts b/apps/fxc-server/src/app/waypoints/waypoints.ts index b5ce6f15..5ae5e8a7 100644 --- a/apps/fxc-server/src/app/waypoints/waypoints.ts +++ b/apps/fxc-server/src/app/waypoints/waypoints.ts @@ -2,7 +2,8 @@ const { buildGPX, BaseBuilder } = require('gpx-builder'); const builder = require('xmlbuilder'); -import { LatLonAlt, round } from '@flyxc/common'; +import type { LatLonAlt } from '@flyxc/common'; +import { round } from '@flyxc/common'; const printf = require('printf'); export function encode( diff --git a/apps/misc/src/app/email_inreach.ts b/apps/misc/src/app/email_inreach.ts index a0350ce1..92a083a5 100644 --- a/apps/misc/src/app/email_inreach.ts +++ b/apps/misc/src/app/email_inreach.ts @@ -1,6 +1,7 @@ import { readFileSync } from 'node:fs'; import { setTimeout } from 'node:timers/promises'; -import { LiveTrackEntity, SecretKeys, trackerNames } from '@flyxc/common'; +import type { LiveTrackEntity } from '@flyxc/common'; +import { SecretKeys, trackerNames } from '@flyxc/common'; import { MailerSend, EmailParams, Sender, Recipient } from 'mailersend'; (async () => { diff --git a/apps/misc/src/app/list_flymaster.ts b/apps/misc/src/app/list_flymaster.ts index 89f25acf..12a31932 100644 --- a/apps/misc/src/app/list_flymaster.ts +++ b/apps/misc/src/app/list_flymaster.ts @@ -1,5 +1,5 @@ import { readFileSync } from 'node:fs'; -import { LiveTrackEntity } from '@flyxc/common'; +import type { LiveTrackEntity } from '@flyxc/common'; const trackers = JSON.parse(readFileSync(`${__dirname}/assets/trackers.json`, 'utf-8')) as LiveTrackEntity[]; diff --git a/apps/misc/src/app/list_trackers.ts b/apps/misc/src/app/list_trackers.ts index b548c1f3..c6dd55d6 100644 --- a/apps/misc/src/app/list_trackers.ts +++ b/apps/misc/src/app/list_trackers.ts @@ -1,7 +1,7 @@ import { LIVE_TRACK_TABLE } from '@flyxc/common-node'; -import { LiveTrackEntity } from '@flyxc/common'; +import type { LiveTrackEntity } from '@flyxc/common'; import { Datastore } from '@google-cloud/datastore'; -import { RunQueryResponse } from '@google-cloud/datastore/build/src/query'; +import type { RunQueryResponse } from '@google-cloud/datastore/build/src/query'; import { writeFileSync } from 'node:fs'; const datastore = new Datastore(); diff --git a/apps/misc/src/app/list_tracks.ts b/apps/misc/src/app/list_tracks.ts index 3f890be6..bd16f14f 100644 --- a/apps/misc/src/app/list_tracks.ts +++ b/apps/misc/src/app/list_tracks.ts @@ -1,6 +1,7 @@ -import { TrackEntity, TRACK_TABLE } from '@flyxc/common-node'; +import type { TrackEntity } from '@flyxc/common-node'; +import { TRACK_TABLE } from '@flyxc/common-node'; import { Datastore } from '@google-cloud/datastore'; -import { RunQueryResponse } from '@google-cloud/datastore/build/src/query'; +import type { RunQueryResponse } from '@google-cloud/datastore/build/src/query'; import { writeFileSync } from 'node:fs'; const datastore = new Datastore(); diff --git a/apps/misc/src/app/migrate.ts b/apps/misc/src/app/migrate.ts index 2336cb60..a0e4aa2f 100644 --- a/apps/misc/src/app/migrate.ts +++ b/apps/misc/src/app/migrate.ts @@ -1,7 +1,9 @@ -import { diffEncodeAirspaces, LiveTrackEntity, protos, trackerNames } from '@flyxc/common'; -import { getDatastore, retrieveTrackById, TrackEntity, updateUnzippedTracks } from '@flyxc/common-node'; +import type { LiveTrackEntity } from '@flyxc/common'; +import { diffEncodeAirspaces, protos, trackerNames } from '@flyxc/common'; +import type { TrackEntity } from '@flyxc/common-node'; +import { getDatastore, retrieveTrackById, updateUnzippedTracks } from '@flyxc/common-node'; import { Datastore } from '@google-cloud/datastore'; -import { RunQueryResponse } from '@google-cloud/datastore/build/src/query'; +import type { RunQueryResponse } from '@google-cloud/datastore/build/src/query'; const datastore = getDatastore(); diff --git a/apps/proxy/src/main.ts b/apps/proxy/src/main.ts index e0a8208c..ffd40e5b 100644 --- a/apps/proxy/src/main.ts +++ b/apps/proxy/src/main.ts @@ -1,5 +1,6 @@ import { fetchResponse, protos, SecretKeys } from '@flyxc/common'; -import express, { Request, Response } from 'express'; +import type { Request, Response } from 'express'; +import express from 'express'; const app = express().use(express.raw()); diff --git a/apps/run/src/app/airspace.ts b/apps/run/src/app/airspace.ts index 9f6f7a2e..6ee8e587 100644 --- a/apps/run/src/app/airspace.ts +++ b/apps/run/src/app/airspace.ts @@ -1,8 +1,7 @@ +import type { AirspaceString, AirspaceTyped, Point } from '@flyxc/common'; import { AIRSPACE_TILE_SIZE, AirspaceColorCategory, - AirspaceString, - AirspaceTyped, fetchResponse, Flags, getAirspaceColorCategory, @@ -10,13 +9,13 @@ import { isInFeature, MAX_AIRSPACE_TILE_ZOOM, pixelCoordinates, - Point, protos, toTypedAirspace, } from '@flyxc/common'; import async from 'async'; import { VectorTile } from 'mapbox-vector-tile'; -import { LRU, lru } from 'tiny-lru'; +import type { LRU } from 'tiny-lru'; +import { lru } from 'tiny-lru'; // Look MARGIN_METER above max altitude. const MARGIN_METER = 200; diff --git a/apps/run/src/app/altitude.ts b/apps/run/src/app/altitude.ts index 480537bf..60cf2316 100644 --- a/apps/run/src/app/altitude.ts +++ b/apps/run/src/app/altitude.ts @@ -7,10 +7,12 @@ // - https://observablehq.com/@benjaminortizulloa/mapzen-dem, // - https://www.mapzen.com/blog/terrain-tile-service/ -import { fetchResponse, pixelCoordinates, protos } from '@flyxc/common'; +import type { protos } from '@flyxc/common'; +import { fetchResponse, pixelCoordinates } from '@flyxc/common'; import async from 'async'; import lodepng from 'lodepng'; -import { LRU, lru } from 'tiny-lru'; +import type { LRU } from 'tiny-lru'; +import { lru } from 'tiny-lru'; // Zoom level for the altitude tiles. const ZOOM_LEVEL = 10; diff --git a/apps/run/src/app/process.ts b/apps/run/src/app/process.ts index e3091097..2b00f374 100644 --- a/apps/run/src/app/process.ts +++ b/apps/run/src/app/process.ts @@ -6,8 +6,9 @@ import { protos, SecretKeys, } from '@flyxc/common'; -import { retrieveTrackById, saveTrack, TrackEntity } from '@flyxc/common-node'; -import { Datastore } from '@google-cloud/datastore'; +import type { TrackEntity } from '@flyxc/common-node'; +import { retrieveTrackById, saveTrack } from '@flyxc/common-node'; +import type { Datastore } from '@google-cloud/datastore'; import async from 'async'; import * as polyline from 'google-polyline'; import simplify from 'simplify-path'; diff --git a/apps/run/src/main.ts b/apps/run/src/main.ts index b813bedf..bc3a4033 100644 --- a/apps/run/src/main.ts +++ b/apps/run/src/main.ts @@ -1,5 +1,6 @@ import { getDatastore } from '@flyxc/common-node'; -import express, { Request, Response } from 'express'; +import type { Request, Response } from 'express'; +import express from 'express'; import { postProcessTrack } from './app/process'; const app = express().use(express.json()); diff --git a/docker/.env b/docker/.env new file mode 100644 index 00000000..ba9867c0 --- /dev/null +++ b/docker/.env @@ -0,0 +1,2 @@ +# docker compose env file +COMPOSE_PROJECT_NAME=flyxc diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml new file mode 100644 index 00000000..52b629c9 --- /dev/null +++ b/docker/docker-compose.yml @@ -0,0 +1,24 @@ +# This file permits to launch docker images of services required for this app +# +services: + # launch it with ' docker compose up -d redis' + redis: + image: redis:latest + ports: + - 6378:6379 + + # launch it with ' docker compose up -d pubsub' + pubsub: + image: gcr.io/google.com/cloudsdktool/google-cloud-cli:latest + ports: + - 8085:8085 + command: gcloud beta emulators pubsub start + + # could not manage to make it work (port issue?) + # instead you can use the emulator on your workstation + # by launching "gcloud beta emulators datastore start" + datastore: + image: gcr.io/google.com/cloudsdktool/google-cloud-cli:latest + command: gcloud beta emulators datastore start --project flyxc + ports: + - 8081:8081 diff --git a/libs/common-node/src/lib/datastore.ts b/libs/common-node/src/lib/datastore.ts index c437dc01..001548e7 100644 --- a/libs/common-node/src/lib/datastore.ts +++ b/libs/common-node/src/lib/datastore.ts @@ -1,4 +1,5 @@ -import { Datastore, Key } from '@google-cloud/datastore'; +import type { Key } from '@google-cloud/datastore'; +import { Datastore } from '@google-cloud/datastore'; let datastore: Datastore; diff --git a/libs/common-node/src/lib/live-track-entity.ts b/libs/common-node/src/lib/live-track-entity.ts index 7952257a..e5cf17cb 100644 --- a/libs/common-node/src/lib/live-track-entity.ts +++ b/libs/common-node/src/lib/live-track-entity.ts @@ -1,5 +1,6 @@ -import { AccountModel, LiveTrackEntity, trackerNames } from '@flyxc/common'; -import { Datastore } from '@google-cloud/datastore'; +import type { AccountModel, LiveTrackEntity } from '@flyxc/common'; +import { trackerNames } from '@flyxc/common'; +import type { Datastore } from '@google-cloud/datastore'; export const LIVE_TRACK_TABLE = 'LiveTrack'; export const NAME_MAX_LENGTH = 30; diff --git a/libs/common-node/src/lib/redis.ts b/libs/common-node/src/lib/redis.ts index 033c935f..a8510a86 100644 --- a/libs/common-node/src/lib/redis.ts +++ b/libs/common-node/src/lib/redis.ts @@ -1,4 +1,5 @@ -import Redis, { ChainableCommander } from 'ioredis'; +import type { ChainableCommander } from 'ioredis'; +import Redis from 'ioredis'; import { SecretKeys } from '@flyxc/common'; diff --git a/libs/common-node/src/lib/track-entity.ts b/libs/common-node/src/lib/track-entity.ts index 658b702a..a2c239aa 100644 --- a/libs/common-node/src/lib/track-entity.ts +++ b/libs/common-node/src/lib/track-entity.ts @@ -1,5 +1,7 @@ -import { getHostName, protos } from '@flyxc/common'; -import { Datastore, Key } from '@google-cloud/datastore'; +import type { protos } from '@flyxc/common'; +import { getHostName } from '@flyxc/common'; +import type { Key } from '@google-cloud/datastore'; +import { Datastore } from '@google-cloud/datastore'; import * as zlib from 'node:zlib'; export const TRACK_TABLE = 'Track'; diff --git a/libs/common-node/src/lib/validators.ts b/libs/common-node/src/lib/validators.ts index 289a06f6..c88b943f 100644 --- a/libs/common-node/src/lib/validators.ts +++ b/libs/common-node/src/lib/validators.ts @@ -1,12 +1,6 @@ -import { - fetchResponse, - SecretKeys, - TrackerEntity, - TrackerModel, - validateInreachAccount, - validateSkylinesAccount, -} from '@flyxc/common'; -import { Validator } from '@vaadin/nodom'; +import type { TrackerEntity, TrackerModel } from '@flyxc/common'; +import { fetchResponse, SecretKeys, validateInreachAccount, validateSkylinesAccount } from '@flyxc/common'; +import type { Validator } from '@vaadin/nodom'; // Makes sure the account is not password protected. export class InreachValidator implements Validator { diff --git a/libs/common/src/lib/airspaces.ts b/libs/common/src/lib/airspaces.ts index 1ce318bf..22ae6689 100644 --- a/libs/common/src/lib/airspaces.ts +++ b/libs/common/src/lib/airspaces.ts @@ -1,6 +1,6 @@ -import { Feature } from 'mapbox-vector-tile'; +import type { Feature } from 'mapbox-vector-tile'; -import { Point } from './runtime-track'; +import type { Point } from './runtime-track'; // Maximium zoom level for the airspaces. export const MAX_AIRSPACE_TILE_ZOOM = 10; diff --git a/libs/common/src/lib/aprs.test.ts b/libs/common/src/lib/aprs.test.ts index b91df234..9f2de22a 100644 --- a/libs/common/src/lib/aprs.test.ts +++ b/libs/common/src/lib/aprs.test.ts @@ -1,4 +1,5 @@ -import { AprsPosition, generateAprsPosition, parseAprsPosition } from './aprs'; +import type { AprsPosition } from './aprs'; +import { generateAprsPosition, parseAprsPosition } from './aprs'; describe('parseAprsPosition', () => { it('return null if malformed position', () => { diff --git a/libs/common/src/lib/distance.ts b/libs/common/src/lib/distance.ts index 185f3acf..2d94bf4c 100644 --- a/libs/common/src/lib/distance.ts +++ b/libs/common/src/lib/distance.ts @@ -1,6 +1,6 @@ import { getDistance } from 'geolib'; -import { RuntimeTrack } from './runtime-track'; +import type { RuntimeTrack } from './runtime-track'; // Finds the closest fix to {lat, lon} across all the tracks. export function findClosestFix( diff --git a/libs/common/src/lib/live-track-entity.ts b/libs/common/src/lib/live-track-entity.ts index bf21b5cf..7efc9a85 100644 --- a/libs/common/src/lib/live-track-entity.ts +++ b/libs/common/src/lib/live-track-entity.ts @@ -1,4 +1,4 @@ -import { Datastore, Key } from '@google-cloud/datastore'; +import type { Datastore, Key } from '@google-cloud/datastore'; export interface TrackerEntity { enabled: boolean; diff --git a/libs/common/src/lib/live-track.ts b/libs/common/src/lib/live-track.ts index ed229b70..124401c0 100644 --- a/libs/common/src/lib/live-track.ts +++ b/libs/common/src/lib/live-track.ts @@ -1,4 +1,5 @@ -import { LiveDifferentialTrack, LiveExtra, LiveTrack } from '../protos/live-track'; +import type { LiveDifferentialTrack, LiveExtra } from '../protos/live-track'; +import { LiveTrack } from '../protos/live-track'; import { diffDecodeArray, diffEncodeArray32bit, findIndexes } from './math'; import { deepCopy } from './util'; diff --git a/libs/common/src/lib/models.ts b/libs/common/src/lib/models.ts index 6c21b06b..5cc8df7b 100644 --- a/libs/common/src/lib/models.ts +++ b/libs/common/src/lib/models.ts @@ -1,16 +1,9 @@ // Models and validation for user data. -import { - BooleanModel, - NotBlank, - NotNull, - ObjectModel, - Size, - StringModel, - Validator, - _getPropertyModel, -} from '@vaadin/nodom'; -import { TrackerNames, trackerNames } from './live-track'; +import type { Validator } from '@vaadin/nodom'; +import { BooleanModel, NotBlank, NotNull, ObjectModel, Size, StringModel, _getPropertyModel } from '@vaadin/nodom'; +import type { TrackerNames } from './live-track'; +import { trackerNames } from './live-track'; import type { LiveTrackEntity, TrackerEntity } from './live-track-entity'; // Client side model for an account. diff --git a/libs/common/src/lib/proj.ts b/libs/common/src/lib/proj.ts index 6111d1b6..92620141 100644 --- a/libs/common/src/lib/proj.ts +++ b/libs/common/src/lib/proj.ts @@ -1,5 +1,5 @@ import SphericalMercator from '@mapbox/sphericalmercator'; -import { LatLon, Point } from './runtime-track'; +import type { LatLon, Point } from './runtime-track'; const mercatorBySize = new Map(); diff --git a/libs/optimizer/.babelrc b/libs/optimizer/.babelrc new file mode 100644 index 00000000..f2f38067 --- /dev/null +++ b/libs/optimizer/.babelrc @@ -0,0 +1,3 @@ +{ + "presets": ["@nx/js/babel"] +} diff --git a/libs/optimizer/.eslintrc.json b/libs/optimizer/.eslintrc.json new file mode 100644 index 00000000..adbe7ae2 --- /dev/null +++ b/libs/optimizer/.eslintrc.json @@ -0,0 +1,25 @@ +{ + "extends": ["../../.eslintrc.json"], + "ignorePatterns": ["!**/*"], + "overrides": [ + { + "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], + "rules": {} + }, + { + "files": ["*.ts", "*.tsx"], + "rules": {} + }, + { + "files": ["*.js", "*.jsx"], + "rules": {} + }, + { + "files": ["*.json"], + "parser": "jsonc-eslint-parser", + "rules": { + "@nx/dependency-checks": "error" + } + } + ] +} diff --git a/libs/optimizer/README.md b/libs/optimizer/README.md new file mode 100644 index 00000000..c27b709f --- /dev/null +++ b/libs/optimizer/README.md @@ -0,0 +1,19 @@ +# optimizer + +## Description + +This library is a thin wrapper around [igc-xc-score](https://github.com/mmomtchev/igc-xc-score) by Momtchil Momtchev. + +It is used to score flight tracks. + +## Usage + +The `getOptimizer` generator function computes score of a track. + +The supported league rules are exposed in `scoringRuleNames`. + +## Running unit tests + +Run `nx test optimizer`. + +This library was generated with [Nx](https://nx.dev). diff --git a/libs/optimizer/jest.config.ts b/libs/optimizer/jest.config.ts new file mode 100644 index 00000000..b187e307 --- /dev/null +++ b/libs/optimizer/jest.config.ts @@ -0,0 +1,11 @@ +/* eslint-disable */ +export default { + displayName: 'optimizer', + preset: '../../jest.preset.js', + testEnvironment: 'node', + transform: { + '^.+\\.[tj]s$': ['ts-jest', { tsconfig: '/tsconfig.spec.json' }], + }, + moduleFileExtensions: ['ts', 'js', 'html'], + coverageDirectory: '../../coverage/libs/optimizer', +}; diff --git a/libs/optimizer/package-lock.json b/libs/optimizer/package-lock.json new file mode 100644 index 00000000..793a3932 --- /dev/null +++ b/libs/optimizer/package-lock.json @@ -0,0 +1,97 @@ +{ + "name": "@flyxc/optimizer", + "version": "0.0.1", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "@flyxc/optimizer", + "version": "0.0.1", + "dependencies": { + "igc-parser": "^1.1.0", + "igc-xc-score": "^1.7.0", + "tslib": "^2.3.0" + } + }, + "node_modules/collections": { + "version": "5.1.13", + "resolved": "https://registry.npmjs.org/collections/-/collections-5.1.13.tgz", + "integrity": "sha512-SCb6Qd+d3Z02corWQ7/mqXiXeeTdHvkP6TeFSYfGYdCFp1WrjSNZ3j6y8Y3T/7osGEe0iOcU2g1d346l99m4Lg==", + "dependencies": { + "weak-map": "~1.0.x" + } + }, + "node_modules/flatbush": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/flatbush/-/flatbush-4.4.0.tgz", + "integrity": "sha512-cf6G+sfy/+/FLH7Ls1URQ5GCRlXgwgqUZiEsMNrMZqb3Us3EkKmzUlKbnyoBy/4wI4oLJ+8cyCQoKJIVm92Fmg==", + "dependencies": { + "flatqueue": "^2.0.3" + } + }, + "node_modules/flatqueue": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/flatqueue/-/flatqueue-2.0.3.tgz", + "integrity": "sha512-RZCWZNkmxzUOh8jqEcEGZCycb3B8KAfpPwg3H//cURasunYxsg1eIvE+QDSjX+ZPHTIVfINfK1aLTrVKKO0i4g==", + "engines": { + "node": ">= 12.17.0" + } + }, + "node_modules/flight-recorder-manufacturers": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/flight-recorder-manufacturers/-/flight-recorder-manufacturers-2.0.0.tgz", + "integrity": "sha512-FhSY7XslPkhYGdBBVRLLExoKqiLWbKuxPEx3O3a443hp8VOrcvE+ibibigVplQbaNipVG3pSPTZPTFJPIfcO8Q==", + "engines": { + "node": ">=12" + } + }, + "node_modules/igc-parser": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/igc-parser/-/igc-parser-1.1.0.tgz", + "integrity": "sha512-CJGWiCcCCxNSI3Pt0zbllHNekEp2NOVCgDysg5/7FuW/uXUUDiW+AcZWSwRwvtRQxnYf+Sn7mQmYKLWQfVSATA==", + "dependencies": { + "flight-recorder-manufacturers": "^2.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/igc-xc-score": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/igc-xc-score/-/igc-xc-score-1.7.0.tgz", + "integrity": "sha512-49UQM9mbXjTo2mmeRYIq0vszShw8bg8TKhTU2XGwBCmSk0ojJFACajeZKSB0dT8u3F6p+7ap1oRv17g22Xh3PA==", + "dependencies": { + "collections": "^5.1.13", + "flatbush": "^4.0.0", + "igc-parser": "^1.1.0", + "rbush": "^3.0.1" + }, + "bin": { + "igc-xc-score": "dist/igc-xc-score.cjs" + } + }, + "node_modules/quickselect": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/quickselect/-/quickselect-2.0.0.tgz", + "integrity": "sha512-RKJ22hX8mHe3Y6wH/N3wCM6BWtjaxIyyUIkpHOvfFnxdI4yD4tBXEBKSbriGujF6jnSVkJrffuo6vxACiSSxIw==" + }, + "node_modules/rbush": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/rbush/-/rbush-3.0.1.tgz", + "integrity": "sha512-XRaVO0YecOpEuIvbhbpTrZgoiI6xBlz6hnlr6EHhd+0x9ase6EmeN+hdwwUaJvLcsFFQ8iWVF1GAK1yB0BWi0w==", + "dependencies": { + "quickselect": "^2.0.0" + } + }, + "node_modules/tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + }, + "node_modules/weak-map": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/weak-map/-/weak-map-1.0.8.tgz", + "integrity": "sha512-lNR9aAefbGPpHO7AEnY0hCFjz1eTkWCXYvkTRrTHs9qv8zJp+SkVYpzfLIFXQQiG3tVvbNFQgVg2bQS8YGgxyw==" + } + } +} diff --git a/libs/optimizer/package.json b/libs/optimizer/package.json new file mode 100644 index 00000000..2bd2093c --- /dev/null +++ b/libs/optimizer/package.json @@ -0,0 +1,12 @@ +{ + "name": "@flyxc/optimizer", + "version": "0.0.1", + "dependencies": { + "igc-parser": "^1.1.0", + "igc-xc-score": "^1.7.0", + "tslib": "^2.3.0" + }, + "type": "module", + "main": "./src/index.js", + "typings": "./src/index.d.ts" +} diff --git a/libs/optimizer/project.json b/libs/optimizer/project.json new file mode 100644 index 00000000..ca268d7b --- /dev/null +++ b/libs/optimizer/project.json @@ -0,0 +1,36 @@ +{ + "name": "optimizer", + "$schema": "../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "libs/optimizer/src", + "projectType": "library", + "targets": { + "build": { + "executor": "@nx/js:tsc", + "outputs": ["{options.outputPath}"], + "options": { + "outputPath": "dist/libs/optimizer", + "main": "libs/optimizer/src/index.ts", + "additionalEntryPoints": [ + "libs/optimizer/src/lib/api.ts", + "libs/optimizer/src/lib/scoring-rules.ts", + "libs/optimizer/src/lib/optimizer.ts" + ], + "generateExportsField": true, + "tsConfig": "libs/optimizer/tsconfig.lib.json", + "assets": ["libs/optimizer/*.md"] + } + }, + "lint": { + "executor": "@nx/eslint:lint", + "outputs": ["{options.outputFile}"] + }, + "test": { + "executor": "@nx/jest:jest", + "outputs": ["{workspaceRoot}/coverage/{projectRoot}"], + "options": { + "jestConfig": "libs/optimizer/jest.config.ts" + } + } + }, + "tags": [] +} diff --git a/libs/optimizer/src/index.ts b/libs/optimizer/src/index.ts new file mode 100644 index 00000000..7496a27e --- /dev/null +++ b/libs/optimizer/src/index.ts @@ -0,0 +1,2 @@ +// Do not export from here as igc-xc-score pollute the global namespace +// See https://github.com/mmomtchev/igc-xc-score/issues/234 diff --git a/libs/optimizer/src/lib/api.ts b/libs/optimizer/src/lib/api.ts new file mode 100644 index 00000000..f0d8fb8f --- /dev/null +++ b/libs/optimizer/src/lib/api.ts @@ -0,0 +1,26 @@ +// Do not depend on igc-xc-score that pollutes the global namespace +// See https://github.com/mmomtchev/igc-xc-score/issues/234 + +export const scoringRuleNames = [ + 'CzechLocal', + 'CzechEurope', + 'CzechOutsideEurope', + 'FFVL', + 'Leonardo', + 'Norway', + 'UKClub', + 'UKInternational', + 'UKNational', + 'XContest', + 'XContestPPG', + 'WorldXC', +] as const; + +export enum CircuitType { + OpenDistance = 'Open distance', + FlatTriangle = 'Flat triangle', + FaiTriangle = 'Fai triangle', + OutAndReturn = 'Out and return', +} + +export type ScoringRuleName = (typeof scoringRuleNames)[number]; diff --git a/libs/optimizer/src/lib/optimizer.spec.ts b/libs/optimizer/src/lib/optimizer.spec.ts new file mode 100644 index 00000000..aae888c5 --- /dev/null +++ b/libs/optimizer/src/lib/optimizer.spec.ts @@ -0,0 +1,303 @@ +import type { ScoringRequest, ScoringResult } from './optimizer'; +import { getOptimizer } from './optimizer'; +import { computeDestinationPoint, getGreatCircleBearing, getPreciseDistance } from 'geolib'; +import { createSegments } from './utils/create-segments'; +import { mergeTracks } from './utils/merge-tracks'; +import { CircuitType, scoringRuleNames, type ScoringRuleName } from './api'; + +describe('optimizer', () => { + scoringRuleNames.forEach((ruleName) => { + describe(`${ruleName} rules`, () => { + it('Scores an empty request with a score of 0', () => { + const request = { + track: { points: [] }, + ruleName, + }; + const result = optimize(request); + expect(result).toMatchObject({ + score: 0, + lengthKm: 0, + multiplier: 0, + optimal: true, + }); + }); + + [1, 10].forEach((segmentsPerBranch) => { + describe(`given a free distanceKm request (${segmentsPerBranch} segments/branch)`, () => { + const start = { lat: 45, lon: 5 }; + const end = { lat: 45, lon: 6 }; + + const request = { + track: createSegments({ ...start, alt: 0, timeSec: 0 }, { ...end, alt: 0, timeSec: 60 }, segmentsPerBranch), + ruleName, + }; + + const multiplier = getFreeDistanceMultiplier(ruleName); + const distanceKm = getPreciseDistance(start, end) / 1000; + expect(optimize(request)).toMatchObject({ + score: expect.closeTo(distanceKm * multiplier, 1), + lengthKm: expect.closeTo(distanceKm, 1), + multiplier, + circuit: CircuitType.OpenDistance, + optimal: true, + }); + }); + + describe(`given a free distanceKm with 1 turnpoints request (${segmentsPerBranch} segment/branch)`, () => { + const start = { lat: 45, lon: 5 }; + const tp = { lat: 45, lon: 6 }; + const end = { lat: 46, lon: 6 }; + + const request = { + track: mergeTracks( + createSegments({ ...start, alt: 0, timeSec: 0 }, { ...tp, alt: 0, timeSec: 60 }, segmentsPerBranch), + createSegments({ ...tp, alt: 0, timeSec: 60 }, { ...end, alt: 0, timeSec: 120 }, segmentsPerBranch), + ), + ruleName, + }; + + const distanceKm = (getPreciseDistance(start, tp) + getPreciseDistance(tp, end)) / 1000; + const multiplier = getFreeDistanceMultiplier(ruleName); + expect(optimize(request)).toMatchObject({ + score: expect.closeTo(distanceKm * multiplier, 1), + lengthKm: expect.closeTo(distanceKm, 1), + multiplier, + circuit: CircuitType.OpenDistance, + optimal: true, + }); + }); + + describe(`given a free distanceKm with 2 turnpoints request (${segmentsPerBranch} segment/branch)`, () => { + const start = { lat: 45, lon: 5 }; + const tp1 = { lat: 45, lon: 6 }; + const tp2 = { lat: 46, lon: 6 }; + const end = { lat: 46, lon: 5 }; + + const request = { + track: mergeTracks( + createSegments({ ...start, alt: 0, timeSec: 0 }, { ...tp1, alt: 0, timeSec: 60 }, segmentsPerBranch), + createSegments({ ...tp1, alt: 0, timeSec: 60 }, { ...tp2, alt: 0, timeSec: 120 }, segmentsPerBranch), + createSegments({ ...tp2, alt: 0, timeSec: 120 }, { ...end, alt: 0, timeSec: 180 }, segmentsPerBranch), + ), + ruleName, + }; + + const distanceKm = + (getPreciseDistance(start, tp1) + getPreciseDistance(tp1, tp2) + getPreciseDistance(tp2, end)) / 1000; + const multiplier = getFreeDistanceMultiplier(ruleName); + expect(optimize(request)).toMatchObject({ + score: expect.closeTo(distanceKm * multiplier, 1), + lengthKm: expect.closeTo(distanceKm, 1), + multiplier, + circuit: CircuitType.OpenDistance, + optimal: true, + }); + }); + + describe(`given a free distanceKm with 3 turnpoints request (${segmentsPerBranch} segment/branch)`, () => { + const start = { lat: 45, lon: 5 }; + const tp1 = { lat: 45, lon: 6 }; + const tp2 = { lat: 46, lon: 6 }; + const tp3 = { lat: 46, lon: 5 }; + const end = { lat: 47, lon: 5 }; + + const request = { + track: mergeTracks( + createSegments({ ...start, alt: 0, timeSec: 0 }, { ...tp1, alt: 0, timeSec: 60 }, segmentsPerBranch), + createSegments({ ...tp1, alt: 0, timeSec: 60 }, { ...tp2, alt: 0, timeSec: 120 }, segmentsPerBranch), + createSegments({ ...tp2, alt: 0, timeSec: 120 }, { ...tp3, alt: 0, timeSec: 180 }, segmentsPerBranch), + createSegments({ ...tp3, alt: 0, timeSec: 180 }, { ...end, alt: 0, timeSec: 240 }, segmentsPerBranch), + ), + ruleName, + }; + + const distanceKm = + (getPreciseDistance(start, tp1) + + getPreciseDistance(tp1, tp2) + + getPreciseDistance(tp2, tp3) + + getPreciseDistance(tp3, end)) / + 1000; + const multiplier = getFreeDistanceMultiplier(ruleName); + expect(optimize(request)).toMatchObject({ + score: expect.closeTo(distanceKm * multiplier, 1), + lengthKm: expect.closeTo(distanceKm, 1), + multiplier, + circuit: CircuitType.OpenDistance, + optimal: true, + }); + }); + + describe(`given a closed flat triangle request (${segmentsPerBranch} segment/branch)`, () => { + const tp1 = { lat: 45, lon: 5 }; + const tp2 = { lat: 45, lon: 6 }; + const tp3 = { lat: 45.2, lon: 6 }; + + const request = { + track: mergeTracks( + createSegments({ ...tp1, alt: 0, timeSec: 0 }, { ...tp2, alt: 0, timeSec: 60 }, segmentsPerBranch), + createSegments({ ...tp2, alt: 0, timeSec: 60 }, { ...tp3, alt: 0, timeSec: 120 }, segmentsPerBranch), + createSegments({ ...tp3, alt: 0, timeSec: 120 }, { ...tp1, alt: 0, timeSec: 180 }, segmentsPerBranch), + ), + ruleName, + }; + + const distanceKm = + (getPreciseDistance(tp1, tp2) + getPreciseDistance(tp2, tp3) + getPreciseDistance(tp3, tp1)) / 1000; + const multiplier = getFlatTriangleMultiplier(ruleName); + expect(optimize(request)).toMatchObject({ + score: expect.closeTo(distanceKm * multiplier, 1), + lengthKm: expect.closeTo(distanceKm, 1), + multiplier, + circuit: CircuitType.FlatTriangle, + optimal: true, + }); + }); + + describe(`given a closed FAI triangle request (${segmentsPerBranch} segment/branch)`, () => { + const tp1 = { lat: 45, lon: 5 }; + const tp2 = { lat: 45, lon: 6 }; + + const distance1 = getPreciseDistance(tp1, tp2); + const bearing = getGreatCircleBearing(tp1, tp2); + // Build an equilateral triangle + const tp3GeoLib = computeDestinationPoint(tp1, distance1, bearing + 60); + const tp3 = { lat: tp3GeoLib.latitude, lon: tp3GeoLib.longitude }; + + const request = { + track: mergeTracks( + createSegments({ ...tp1, alt: 0, timeSec: 0 }, { ...tp2, alt: 0, timeSec: 60 }, segmentsPerBranch), + createSegments({ ...tp2, alt: 0, timeSec: 60 }, { ...tp3, alt: 0, timeSec: 120 }, segmentsPerBranch), + createSegments({ ...tp3, alt: 0, timeSec: 120 }, { ...tp1, alt: 0, timeSec: 180 }, segmentsPerBranch), + ), + ruleName, + }; + + const distanceKm = + (getPreciseDistance(tp1, tp2) + getPreciseDistance(tp2, tp3) + getPreciseDistance(tp3, tp1)) / 1000; + const multiplier = getFaiTriangleMultiplier(ruleName); + expect(optimize(request)).toMatchObject({ + score: expect.closeTo(distanceKm * multiplier, 1), + lengthKm: expect.closeTo(distanceKm, 1), + multiplier, + circuit: CircuitType.FaiTriangle, + optimal: true, + }); + }); + }); + }); + }); + + it('given a closed FAI triangle request (10 segments/branch) with minimal loop allowed', () => { + const tp1 = { lat: 45, lon: 5 }; + const tp2 = { lat: 45, lon: 6 }; + + const distance1 = getPreciseDistance(tp1, tp2); + const bearing = getGreatCircleBearing(tp1, tp2); + // Build an equilateral triangle + const tp3GeoLib = computeDestinationPoint(tp1, distance1, bearing + 60); + const tp3 = { lat: tp3GeoLib.latitude, lon: tp3GeoLib.longitude }; + + const request: ScoringRequest = { + track: mergeTracks( + createSegments({ ...tp1, alt: 0, timeSec: 0 }, { ...tp2, alt: 0, timeSec: 60 }, 10), + createSegments({ ...tp2, alt: 0, timeSec: 60 }, { ...tp3, alt: 0, timeSec: 120 }, 10), + createSegments({ ...tp3, alt: 0, timeSec: 120 }, { ...tp1, alt: 0, timeSec: 180 }, 10), + ), + ruleName: 'FFVL', + maxNumCycles: 1, + }; + + const distanceKm = + (getPreciseDistance(tp1, tp2) + getPreciseDistance(tp2, tp3) + getPreciseDistance(tp3, tp1)) / 1000; + const multiplier = getFaiTriangleMultiplier('FFVL'); + expect(optimize(request)).toMatchObject({ + score: expect.closeTo(distanceKm * multiplier, 1), + lengthKm: expect.closeTo(distanceKm, 1), + multiplier, + circuit: CircuitType.FaiTriangle, + optimal: true, + }); + }); +}); + +function optimize(request: ScoringRequest): ScoringResult { + const optimizer = getOptimizer(request); + let result: IteratorResult; + do { + result = optimizer.next(); + } while (!result.done); + + expect(result.value.optimal).toBe(true); + + return result.value; +} + +// TODO: refactor to extract from the rules +function getFreeDistanceMultiplier(scoringRules: ScoringRuleName) { + switch (scoringRules) { + case 'CzechLocal': + case 'CzechEurope': + case 'FFVL': + case 'Norway': + case 'UKClub': + case 'UKInternational': + case 'UKNational': + case 'XContest': + case 'XContestPPG': + case 'WorldXC': + return 1; + case 'CzechOutsideEurope': + return 0.8; + case 'Leonardo': + return 1.5; + } +} + +// TODO: refactor to extract from the rules +function getFlatTriangleMultiplier(scoringRules: ScoringRuleName) { + switch (scoringRules) { + case 'CzechEurope': + case 'CzechOutsideEurope': + case 'FFVL': + case 'UKInternational': + return 1.2; + case 'Leonardo': + case 'WorldXC': + return 1.75; + case 'XContest': + return 1.4; + case 'Norway': + case 'UKClub': + case 'UKNational': + return 1.7; + case 'CzechLocal': + return 1.8; + case 'XContestPPG': + return 2; + } +} + +// TODO: refactor to extract from the rules +function getFaiTriangleMultiplier(scoringRules: ScoringRuleName) { + switch (scoringRules) { + case 'CzechEurope': + case 'CzechOutsideEurope': + case 'FFVL': + return 1.4; + case 'Leonardo': + case 'UKClub': + case 'UKNational': + case 'WorldXC': + return 2; + case 'XContest': + return 1.6; + case 'CzechLocal': + return 2.2; + case 'Norway': + return 2.4; + case 'UKInternational': + return 1.5; + case 'XContestPPG': + return 4; + } +} diff --git a/libs/optimizer/src/lib/optimizer.ts b/libs/optimizer/src/lib/optimizer.ts new file mode 100644 index 00000000..ae471491 --- /dev/null +++ b/libs/optimizer/src/lib/optimizer.ts @@ -0,0 +1,266 @@ +import type { Point, Solution } from 'igc-xc-score'; +import { solver } from 'igc-xc-score'; +import type { BRecord, IGCFile } from 'igc-parser'; +import type { ScoringRuleName } from './api'; +import { scoringRules } from './scoring-rules'; +import { CircuitType } from './api'; + +// Minimum number of points in an igc-xc-score track. +// See this issue https://github.com/mmomtchev/igc-xc-score/issues/231 +const MIN_IGC_XC_SCORE_POINTS = 5; + +export interface LatLon { + lat: number; + lon: number; +} + +export interface LatLonAltTime extends LatLon { + alt: number; + timeSec: number; +} + +export interface ScoringTrack { + points: LatLonAltTime[]; +} + +export interface ScoringRequest { + track: ScoringTrack; + ruleName: ScoringRuleName; + /** Maximum cycle duration. Unbounded when not provided */ + maxCycleDurationMs?: number; + /** Maximum number of cycles. Unbounded when not provided */ + maxNumCycles?: number; +} + +export interface Leg { + name: string; + lengthKm: number; + start: LatLon; + end: LatLon; +} +export interface ScoringResult { + // The score for the track in the given league + score: number; + // The length of the optimized track in kms + lengthKm: number; + // Multiplier for computing score. score = lengthKm * multiplier + multiplier: number; + circuit?: CircuitType; + closingRadiusM?: number; + // Indices of solutions points in the request + solutionIndices: number[]; + // Whether the result is optimal. + //If not the optimizer can be cycled again to get a more accurate solution. + optimal: boolean; + startPoint?: LatLon; + endPoint?: LatLon; + legs: Leg[]; + turnpoints: LatLon[]; + closingPoints?: { + in: LatLon; + out: LatLon; + }; +} + +/** + * Returns an iterative optimizer computing the score for the flight. + * + * At each iteration, the score should be a better solutions. + * + * @see README.md + * + * @param request Contains the tracks and options. + * @param ruleName The ScoringRules to use. + * @returns an Iterator of OptimizationResult + */ +export function* getOptimizer(request: ScoringRequest): Iterator { + if (request.track.points.length === 0) { + return { + score: 0, + lengthKm: 0, + multiplier: 0, + solutionIndices: [], + optimal: true, + legs: [], + turnpoints: [], + }; + } + const { track } = request; + const flight = toIgcFile(appendPointsIfNeeded(track, MIN_IGC_XC_SCORE_POINTS)); + const solverScoringRules = scoringRules.get(request.ruleName); + if (solverScoringRules == null) { + throw new Error(`Unknown scoring rule: ${request.ruleName}`); + } + const options = toSolverOptions(request); + const solutionIterator = solver(flight, solverScoringRules, options); + while (true) { + const solution = solutionIterator.next(); + if (solution.done) { + return toOptimizationResult(solution.value, track); + } + yield toOptimizationResult(solution.value, track); + } +} + +/** + * Append points if the track is too short for igc-xc-score. + * + * The points are close enough to the last point to not affect the score. + */ +function appendPointsIfNeeded(track: ScoringTrack, minValidLength: number) { + track = structuredClone(track); + + if (track.points.length >= minValidLength) { + return track; + } + + while (track.points.length < minValidLength) { + const lastPoint = track.points.at(-1)!; + track.points.push({ + ...lastPoint, + lat: lastPoint.lat + 0.000001, + timeSec: lastPoint.timeSec + 60, + }); + } + + return track; +} + +/** + * create an igc file from a track + * @param track the source track + */ +function toIgcFile(track: ScoringTrack): IGCFile { + if (track.points.length == 0) { + throw new Error('Empty track'); + } + + const fixes = track.points.map((point): BRecord => { + const timeMilliSec = point.timeSec * 1000; + return { + timestamp: timeMilliSec, + time: new Date(timeMilliSec).toISOString(), + latitude: point.lat, + longitude: point.lon, + valid: true, + pressureAltitude: null, + gpsAltitude: point.alt, + extensions: {}, + fixAccuracy: null, + enl: null, + }; + }); + + // Only fill out the fields required by igc-xc-score. + return { + date: new Date(track.points[0].timeSec * 1000).toISOString(), + fixes, + } as any; +} + +function toSolverOptions(request: ScoringRequest) { + // TODO: upstream the type to igc-xc-score + return { + maxcycle: request.maxCycleDurationMs, + maxloop: request.maxNumCycles, + }; +} + +function toOptimizationResult(solution: Solution, track: ScoringTrack): ScoringResult { + return { + score: solution.score ?? 0, + lengthKm: solution.scoreInfo?.distance ?? 0, + multiplier: solution.opt.scoring.multiplier, + circuit: toCircuitType(solution.opt.scoring.code), + closingRadiusM: getClosingRadiusM(solution), + solutionIndices: getSolutionIndices(solution, track), + optimal: solution.optimal || false, + startPoint: solution.scoreInfo?.ep?.start ? pointToLatTon(solution.scoreInfo?.ep?.start) : undefined, + endPoint: solution.scoreInfo?.ep?.finish ? pointToLatTon(solution.scoreInfo?.ep?.finish) : undefined, + legs: (solution.scoreInfo?.legs ?? []).map(({ name, d, start, finish }) => ({ + name, + lengthKm: d, + start: pointToLatTon(start), + end: pointToLatTon(finish), + })), + turnpoints: (solution.scoreInfo?.tp ?? []).map((point) => pointToLatTon(point)), + closingPoints: solution.scoreInfo?.cp + ? { + in: pointToLatTon(solution.scoreInfo.cp.in), + out: pointToLatTon(solution.scoreInfo.cp.out), + } + : undefined, + }; +} + +function pointToLatTon(point: Point): LatLon { + return { lat: point.y, lon: point.x }; +} + +function getClosingRadiusM(solution: Solution): number | undefined { + const closingDistance = solution.scoreInfo?.cp?.d; + + if (closingDistance == null) { + return undefined; + } + + // TODO: remove cast when https://github.com/mmomtchev/igc-xc-score/pull/233 is merged + const closingDistanceFixed = (solution.opt.scoring as any)?.closingDistanceFixed; + + if (closingDistanceFixed != null && closingDistance < closingDistanceFixed) { + return closingDistanceFixed * 1000; + } + + // TODO: remove cast when https://github.com/mmomtchev/igc-xc-score/pull/233 is merged + const closingDistanceRelativeRatio = (solution.opt.scoring as any)?.closingDistanceRelative; + const closingDistanceRelative = + solution.scoreInfo?.distance != null && closingDistanceRelativeRatio != null + ? closingDistanceRelativeRatio * solution.scoreInfo.distance + : undefined; + + if (closingDistanceRelative != null && closingDistance < closingDistanceRelative) { + return closingDistanceRelative; + } + + return undefined; +} + +const circuitMapping: Readonly> = { + od: CircuitType.OpenDistance, + tri: CircuitType.FlatTriangle, + fai: CircuitType.FaiTriangle, + oar: CircuitType.OutAndReturn, +}; + +function toCircuitType(code: string): CircuitType { + const type = circuitMapping[code]; + if (type == null) { + throw new Error(`Unknown type "${code}"`); + } + return type; +} + +/** + * Return the indices of the solution in the input track. + * + * It contains (when applicable): + * - the starting point + * - the 'in' closing point + * - the turn points + * - the 'out' closing point + * - the end point + * */ +function getSolutionIndices(solution: Solution, inputTrack: ScoringTrack): number[] { + return ( + [ + solution.scoreInfo?.ep?.start.r, + solution.scoreInfo?.cp?.in.r, + ...(solution.scoreInfo?.tp ?? []).map((turnPoint) => turnPoint.r), + solution.scoreInfo?.cp?.out.r, + solution.scoreInfo?.ep?.finish.r, + ] + .filter((index) => index != null) + // Map added dummy points back to the last point of the input track. + .map((index) => Math.min(index!, inputTrack.points.length - 1)) + ); +} diff --git a/libs/optimizer/src/lib/scoring-rules.ts b/libs/optimizer/src/lib/scoring-rules.ts new file mode 100644 index 00000000..eb33d4df --- /dev/null +++ b/libs/optimizer/src/lib/scoring-rules.ts @@ -0,0 +1,98 @@ +import * as igcXcScore from 'igc-xc-score'; +import type { ScoringRuleName } from './api'; + +// TODO: Export the rules from igc-xc-score +const scoringBaseModel = igcXcScore.scoringRules['XContest']; +const openDistance = scoringBaseModel[0]; +const freeTriangle = scoringBaseModel[1]; +const faiTriangle = scoringBaseModel[2]; +const outAndReturn = igcXcScore.scoringRules['FAI-OAR'][0]; + +const czechLocalRule = [ + { ...openDistance, multiplier: 1 }, + { ...freeTriangle, multiplier: 1.8 }, + { ...faiTriangle, multiplier: 2.2 }, +]; + +const czechEuropeRule = [ + { ...openDistance, multiplier: 1 }, + { ...freeTriangle, multiplier: 1.2 }, + { ...faiTriangle, multiplier: 1.4 }, +]; + +const czechOutEuropeRule = [ + { ...openDistance, multiplier: 0.8 }, + { ...freeTriangle, multiplier: 1.2 }, + { ...faiTriangle, multiplier: 1.4 }, +]; + +const leonardoRule = [ + { ...openDistance, multiplier: 1.5 }, + { ...freeTriangle, multiplier: 1.75, closingDistanceRelative: 0.2 }, + { ...faiTriangle, multiplier: 2, closingDistanceRelative: 0.2 }, +]; + +const norwayRule = [ + { ...openDistance, multiplier: 1 }, + { ...freeTriangle, multiplier: 1.7, closingDistanceRelative: 0.05 }, + { ...freeTriangle, multiplier: 1.5, closingDistanceRelative: 0.2 }, + { ...faiTriangle, multiplier: 2.4, closingDistanceRelative: 0.05 }, + { ...faiTriangle, multiplier: 2.2, closingDistanceRelative: 0.2 }, +]; + +const ukXclClubRule = [ + { ...openDistance, multiplier: 1, minDistance: 5 }, + { ...outAndReturn, multiplier: 1.2, minDistance: 5 }, + { ...outAndReturn, multiplier: 1.3, minDistance: 15 }, + { ...outAndReturn, multiplier: 1.7, minDistance: 35 }, + { ...freeTriangle, multiplier: 1.2, minDistance: 5 }, + { ...freeTriangle, multiplier: 1.3, minDistance: 15 }, + { ...freeTriangle, multiplier: 1.7, minDistance: 35 }, + { ...faiTriangle, multiplier: 1.5, minDistance: 5 }, + { ...faiTriangle, multiplier: 1.7, minDistance: 15 }, + { ...faiTriangle, multiplier: 2, minDistance: 35 }, +]; + +const ukXclInternationalRule = [ + { ...openDistance, multiplier: 1, minDistance: 10 }, + { ...outAndReturn, multiplier: 1.2, minDistance: 35 }, + { ...freeTriangle, multiplier: 1.2, minDistance: 35 }, + { ...faiTriangle, multiplier: 1.5, minDistance: 25 }, +]; + +const ukXclNationalRule = [ + { ...openDistance, multiplier: 1, minDistance: 10 }, + { ...outAndReturn, multiplier: 1.3, minDistance: 15 }, + { ...outAndReturn, multiplier: 1.7, minDistance: 35 }, + { ...freeTriangle, multiplier: 1.3, minDistance: 15 }, + { ...freeTriangle, multiplier: 1.7, minDistance: 35 }, + { ...faiTriangle, multiplier: 1.7, minDistance: 15 }, + { ...faiTriangle, multiplier: 2, minDistance: 25 }, +]; + +const xContestPpgRule = [ + { ...openDistance, multiplier: 1 }, + { ...freeTriangle, multiplier: 2, closingDistanceFixed: 0.8 }, + { ...faiTriangle, multiplier: 4, closingDistanceFixed: 0.8 }, +]; + +const wxcRule = [ + { ...openDistance, multiplier: 1 }, + { ...freeTriangle, multiplier: 1.75, closingDistanceRelative: 0.2 }, + { ...faiTriangle, multiplier: 2, closingDistanceFixed: 0.2 }, +]; + +export const scoringRules: Map = new Map([ + ['CzechEurope', czechEuropeRule], + ['CzechLocal', czechLocalRule], + ['CzechOutsideEurope', czechOutEuropeRule], + ['FFVL', igcXcScore.scoringRules['FFVL']], + ['Leonardo', leonardoRule], + ['Norway', norwayRule], + ['UKClub', ukXclClubRule], + ['UKInternational', ukXclInternationalRule], + ['UKNational', ukXclNationalRule], + ['XContest', igcXcScore.scoringRules['XContest']], + ['XContestPPG', xContestPpgRule], + ['WorldXC', wxcRule], +]); diff --git a/libs/optimizer/src/lib/utils/create-segments.ts b/libs/optimizer/src/lib/utils/create-segments.ts new file mode 100644 index 00000000..e65071ad --- /dev/null +++ b/libs/optimizer/src/lib/utils/create-segments.ts @@ -0,0 +1,31 @@ +import type { LatLonAltTime, ScoringTrack } from '../optimizer'; + +/** + * Creates a track with segments. + * + * Points are computed by a linear interpolation + * + * @returns a ScoringTrack of nbSegments + */ +export function createSegments(startPoint: LatLonAltTime, endPoint: LatLonAltTime, nbSegments: number): ScoringTrack { + const points: LatLonAltTime[] = []; + + points.push(startPoint); + + const deltaLat = (endPoint.lat - startPoint.lat) / nbSegments; + const deltaLon = (endPoint.lon - startPoint.lon) / nbSegments; + const deltaAlt = (endPoint.alt - startPoint.alt) / nbSegments; + const deltaTimeSec = (endPoint.timeSec - startPoint.timeSec) / nbSegments; + for (let index = 1; index < nbSegments; index++) { + points.push({ + lat: startPoint.lat + deltaLat * index, + lon: startPoint.lon + deltaLon * index, + alt: Math.round(startPoint.alt + deltaAlt * index), + timeSec: Math.round(startPoint.timeSec + deltaTimeSec * index), + }); + } + + points.push(endPoint); + + return { points }; +} diff --git a/libs/optimizer/src/lib/utils/merge-tracks.ts b/libs/optimizer/src/lib/utils/merge-tracks.ts new file mode 100644 index 00000000..eb65929b --- /dev/null +++ b/libs/optimizer/src/lib/utils/merge-tracks.ts @@ -0,0 +1,22 @@ +import type { LatLonAltTime, ScoringTrack } from '../optimizer'; + +/** + * Merge multiple tracks. + * + * Collapse end and start point of consecutive tracks when they match. + * + * @param tracks the tracks to concatenate + * @returns a track + */ +export function mergeTracks(...tracks: ScoringTrack[]): ScoringTrack { + const points: LatLonAltTime[] = []; + + for (const track of tracks) { + if (track.points.length == 0) { + continue; + } + const collapse = points.at(-1)?.lat === track.points[0].lat && points.at(-1)?.lon === track.points[0].lon; + points.push(...(collapse ? track.points.slice(1) : track.points)); + } + return { points }; +} diff --git a/libs/optimizer/tsconfig.json b/libs/optimizer/tsconfig.json new file mode 100644 index 00000000..a5384dc1 --- /dev/null +++ b/libs/optimizer/tsconfig.json @@ -0,0 +1,22 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "module": "ES2022", + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitOverride": true, + "noPropertyAccessFromIndexSignature": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true + }, + "files": [], + "include": ["src"], + "references": [ + { + "path": "./tsconfig.lib.json" + }, + { + "path": "./tsconfig.spec.json" + } + ] +} diff --git a/libs/optimizer/tsconfig.lib.json b/libs/optimizer/tsconfig.lib.json new file mode 100644 index 00000000..33eca2c2 --- /dev/null +++ b/libs/optimizer/tsconfig.lib.json @@ -0,0 +1,10 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "include": ["src/**/*.ts"], + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"] +} diff --git a/libs/optimizer/tsconfig.spec.json b/libs/optimizer/tsconfig.spec.json new file mode 100644 index 00000000..f6d8ffcc --- /dev/null +++ b/libs/optimizer/tsconfig.spec.json @@ -0,0 +1,9 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "../../dist/out-tsc", + "module": "commonjs", + "types": ["jest", "node"] + }, + "include": ["jest.config.ts", "src/**/*.test.ts", "src/**/*.spec.ts", "src/**/*.d.ts"] +} diff --git a/libs/vaadin-dom/src/lib/Binder.ts b/libs/vaadin-dom/src/lib/Binder.ts index fb5f2838..9be078db 100644 --- a/libs/vaadin-dom/src/lib/Binder.ts +++ b/libs/vaadin-dom/src/lib/Binder.ts @@ -1,5 +1,7 @@ -import { AbstractModel, BinderConfiguration, ModelConstructor, NoDomBinder, _onChange } from '@vaadin/nodom'; -import { FieldStrategy, getDefaultFieldStrategy } from './Field'; +import type { AbstractModel, BinderConfiguration, ModelConstructor } from '@vaadin/nodom'; +import { NoDomBinder, _onChange } from '@vaadin/nodom'; +import type { FieldStrategy } from './Field'; +import { getDefaultFieldStrategy } from './Field'; export class Binder> extends NoDomBinder { /** diff --git a/libs/vaadin-dom/src/lib/Field.ts b/libs/vaadin-dom/src/lib/Field.ts index ecb5c7e0..11003174 100644 --- a/libs/vaadin-dom/src/lib/Field.ts +++ b/libs/vaadin-dom/src/lib/Field.ts @@ -1,6 +1,9 @@ -import { AbstractModel, getBinderNode, _fromString } from '@vaadin/nodom'; -import { ElementPart, noChange, nothing, PropertyPart } from 'lit'; -import { directive, Directive, DirectiveParameters, PartInfo, PartType } from 'lit/directive.js'; +import type { AbstractModel } from '@vaadin/nodom'; +import { getBinderNode, _fromString } from '@vaadin/nodom'; +import type { ElementPart, PropertyPart } from 'lit'; +import { noChange, nothing } from 'lit'; +import type { DirectiveParameters, PartInfo } from 'lit/directive.js'; +import { directive, Directive, PartType } from 'lit/directive.js'; import type { Binder } from './Binder'; diff --git a/libs/vaadin-nodom/src/lib/BinderNode.ts b/libs/vaadin-nodom/src/lib/BinderNode.ts index e6221c2d..f01b1606 100644 --- a/libs/vaadin-nodom/src/lib/BinderNode.ts +++ b/libs/vaadin-nodom/src/lib/BinderNode.ts @@ -16,6 +16,7 @@ // TODO: Fix dependency cycle import type { NoDomBinder } from './NoDomBinder'; +import type { ModelConstructor, ModelValue } from './Models'; import { _binderNode, _ItemModel, @@ -25,8 +26,6 @@ import { AbstractModel, ArrayModel, getBinderNode, - ModelConstructor, - ModelValue, ObjectModel, } from './Models'; diff --git a/libs/vaadin-nodom/src/lib/NoDomBinder.ts b/libs/vaadin-nodom/src/lib/NoDomBinder.ts index 58ef2030..59f78e22 100644 --- a/libs/vaadin-nodom/src/lib/NoDomBinder.ts +++ b/libs/vaadin-nodom/src/lib/NoDomBinder.ts @@ -1,13 +1,8 @@ import { BinderNode } from './BinderNode'; -import { _parent, AbstractModel, ModelConstructor } from './Models'; -import { - InterpolateMessageCallback, - runValidator, - ServerValidator, - ValidationError, - Validator, - ValueError, -} from './Validation'; +import type { AbstractModel, ModelConstructor } from './Models'; +import { _parent } from './Models'; +import type { InterpolateMessageCallback, Validator, ValueError } from './Validation'; +import { runValidator, ServerValidator, ValidationError } from './Validation'; const _submitting = Symbol('submitting'); const _defaultValue = Symbol('defaultValue'); diff --git a/libs/vaadin-nodom/src/lib/Validation.ts b/libs/vaadin-nodom/src/lib/Validation.ts index 1dc9fdc6..f8778d4b 100644 --- a/libs/vaadin-nodom/src/lib/Validation.ts +++ b/libs/vaadin-nodom/src/lib/Validation.ts @@ -1,7 +1,8 @@ // TODO: Fix dependency cycle import type { BinderNode } from './BinderNode'; -import { AbstractModel, getBinderNode, NumberModel } from './Models'; +import type { AbstractModel } from './Models'; +import { getBinderNode, NumberModel } from './Models'; import type { NoDomBinder } from './NoDomBinder'; import { Required } from './Validators'; diff --git a/nx.json b/nx.json index d0b8901f..ef2b15ef 100644 --- a/nx.json +++ b/nx.json @@ -22,6 +22,11 @@ "@nx/eslint:lint": { "inputs": ["default", "{workspaceRoot}/.eslintrc.json"], "cache": true + }, + "@nx/js:tsc": { + "cache": true, + "dependsOn": ["^build"], + "inputs": ["production", "^production"] } }, "namedInputs": { diff --git a/package-lock.json b/package-lock.json index 1d8b0c9d..8a739d2c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,7 +14,7 @@ "@dr.pogodin/csurf": "^1.13.0", "@google-cloud/compute": "^4.7.0", "@google-cloud/datastore": "^9.0.0", - "@google-cloud/pubsub": "^4.4.0", + "@google-cloud/pubsub": "^4.4.1", "@google-cloud/storage": "^7.11.1", "@googlemaps/js-api-loader": "^1.16.6", "@ionic/core": "^8.2.0", @@ -22,9 +22,9 @@ "@popperjs/core": "^2.11.8", "@protobuf-ts/runtime": "^2.9.4", "@reduxjs/toolkit": "^2.2.5", - "@stencil/core": "^4.18.2", - "@swc-node/register": "1.8.0", - "@swc/core": "^1.3.95", + "@stencil/core": "^4.18.3", + "@swc-node/register": "~1.9.1", + "@swc/core": "~1.5.7", "@tmcw/togeojson": "^5.8.1", "@types/mapbox__sphericalmercator": "^1.2.3", "@vivaxy/png": "^1.3.0", @@ -42,11 +42,12 @@ "express-session": "^1.18.0", "geojson": "^0.5.0", "geolib": "^3.3.4", - "glob": "^10.3.15", + "glob": "^10.4.1", "google-polyline": "^1.0.3", "gpx-builder": "^5.3.0", "grant": "^5.4.22", "igc-parser": "^1.1.0", + "igc-xc-score": "^1.7.0", "ioredis": "^5.4.1", "lit": "^3.1.3", "lodepng": "^2.2.0", @@ -71,7 +72,7 @@ }, "devDependencies": { "@esri/arcgis-rest-geocoding": "^4.0.2", - "@esri/arcgis-rest-request": "^4.2.1", + "@esri/arcgis-rest-request": "^4.2.2", "@nx-tools/nx-container": "^6.0.1", "@nx/cypress": "19.1.1", "@nx/eslint": "19.1.1", @@ -85,6 +86,7 @@ "@nx/webpack": "19.1.1", "@nx/workspace": "19.1.1", "@protobuf-ts/plugin": "^2.9.4", + "@swc/helpers": "~0.5.2", "@types/compression": "^1.7.5", "@types/csurf": "^1.11.5", "@types/d3-array": "^3.2.1", @@ -92,7 +94,7 @@ "@types/express-fileupload": "^1.5.0", "@types/express-session": "^1.18.0", "@types/google.maps": "^3.55.9", - "@types/gtag.js": "^0.0.19", + "@types/gtag.js": "^0.0.20", "@types/ioredis": "^4.28.10", "@types/jest": "29.4.0", "@types/js-yaml": "^4.0.5", @@ -114,14 +116,15 @@ "jest": "^29.4.1", "jest-bench": "^29.4", "jest-environment-jsdom": "29.6.4", + "jest-environment-node": "^29.4.1", "jsdom": "~22.1.0", "mailersend": "^2.2.0", "nx": "19.1.1", "prettier": "^2.6.2", "rollup-plugin-minify-html-literals": "^1.2.6", "rollup-plugin-visualizer": "^5.12.0", - "servez": "^2.1.4", - "ts-jest": "^29.1.0", + "servez": "^2.1.6", + "ts-jest": "^29.1.4", "ts-node": "^10.9.2", "typescript": "5.4.5", "vite": "~5.0.0", @@ -2718,9 +2721,9 @@ } }, "node_modules/@eslint-community/regexpp": { - "version": "4.10.0", - "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.10.0.tgz", - "integrity": "sha512-Cu96Sd2By9mCNTx2iyKOmq10v22jUVQv0lQnlGNy16oE9589yE+QADPbrMGCkA51cKZSg3Pu/aTJVTGfL/qjUA==", + "version": "4.10.1", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.10.1.tgz", + "integrity": "sha512-Zm2NGpWELsQAD1xsJzGQpYfvICSsFkEpU0jxBjfdC6uNEWXcHnfs9hScFWtXVDVl+rBQJGrl4g1vcKIejpH9dA==", "dev": true, "engines": { "node": "^12.0.0 || ^14.0.0 || >=16.0.0" @@ -2891,9 +2894,9 @@ } }, "node_modules/@esri/arcgis-rest-request": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/@esri/arcgis-rest-request/-/arcgis-rest-request-4.2.1.tgz", - "integrity": "sha512-MK8ZrKqJuPDhYDxm9kcDYDtGfWVIMn8ldw6xHthvPhp/6/ZbZZ9B5MwL92Nmdyl2Q1hngiXxCbetW62uLUHj9g==", + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@esri/arcgis-rest-request/-/arcgis-rest-request-4.2.2.tgz", + "integrity": "sha512-SWcNhzhW6+bmDgkeTFVC9zgnPSqXSOueX5hVxWZztV2xM1Ag/B6kSBDoP92lRXJ6kcDT8JuNs4wXxUKZfonXNg==", "dev": true, "dependencies": { "@esri/arcgis-rest-fetch": "^4.0.0", @@ -3043,9 +3046,9 @@ } }, "node_modules/@google-cloud/pubsub": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/@google-cloud/pubsub/-/pubsub-4.4.0.tgz", - "integrity": "sha512-1eiiAZUFhxcOqKPVwZarc3ghXuhoc3S7z5BgNrxqdirJ/MYr3IjQVTA7Lq2dAAsDuWms1LBN897rbnEGW9PpfA==", + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/@google-cloud/pubsub/-/pubsub-4.4.1.tgz", + "integrity": "sha512-OsNsadtM+/EhHijc8pIgf+GaqWIZIaizhq6p2mzK+lB7BRziV66ByMDO6EEtyqcdmqYh6MbQ39RZMEQocZsxPQ==", "dependencies": { "@google-cloud/paginator": "^5.0.0", "@google-cloud/precise-date": "^4.0.0", @@ -3056,7 +3059,7 @@ "arrify": "^2.0.0", "extend": "^3.0.2", "google-auth-library": "^9.3.0", - "google-gax": "^4.3.1", + "google-gax": "^4.3.3", "heap-js": "^2.2.0", "is-stream-ended": "^0.1.4", "lodash.snakecase": "^4.1.1", @@ -3763,12 +3766,13 @@ } }, "node_modules/@nrwl/devkit": { - "version": "19.1.1", - "resolved": "https://registry.npmjs.org/@nrwl/devkit/-/devkit-19.1.1.tgz", - "integrity": "sha512-CrbEy4zBRPPV8gGtwpSgfxJUElXRxEGvvxQlrhoCKmzH7v9407jFjXpzYOipwa9u65a7raCCtsSKYuRdecRglQ==", + "version": "19.1.2", + "resolved": "https://registry.npmjs.org/@nrwl/devkit/-/devkit-19.1.2.tgz", + "integrity": "sha512-vWI+OrTICE9Yw6C/jIwxybnvavI9dnQJ7tpzFbLcSVfFAGdFtYJGCLVe40IkWcvUfELoVmzpXtKP/sPy0D7J9w==", "dev": true, + "peer": true, "dependencies": { - "@nx/devkit": "19.1.1" + "@nx/devkit": "19.1.2" } }, "node_modules/@nrwl/eslint-plugin-nx": { @@ -3959,7 +3963,16 @@ } } }, - "node_modules/@nx/devkit": { + "node_modules/@nx/cypress/node_modules/@nrwl/devkit": { + "version": "19.1.1", + "resolved": "https://registry.npmjs.org/@nrwl/devkit/-/devkit-19.1.1.tgz", + "integrity": "sha512-CrbEy4zBRPPV8gGtwpSgfxJUElXRxEGvvxQlrhoCKmzH7v9407jFjXpzYOipwa9u65a7raCCtsSKYuRdecRglQ==", + "dev": true, + "dependencies": { + "@nx/devkit": "19.1.1" + } + }, + "node_modules/@nx/cypress/node_modules/@nx/devkit": { "version": "19.1.1", "resolved": "https://registry.npmjs.org/@nx/devkit/-/devkit-19.1.1.tgz", "integrity": "sha512-YMt5vFaNMeIKgBwQ3RIFQG7AoYOksd8vNxwunirN95q/70HMIoJQsnRCMT45jVd9D/GIWASgY8QsGTMJfcO0qQ==", @@ -3979,6 +3992,27 @@ "nx": ">= 17 <= 20" } }, + "node_modules/@nx/devkit": { + "version": "19.1.2", + "resolved": "https://registry.npmjs.org/@nx/devkit/-/devkit-19.1.2.tgz", + "integrity": "sha512-oHYZzfmvogPh7z8pf1RjW7eJaS05VZ1Ts/axlWerzQauWT7aoeyCaxa0D9q3ThnUuDt1PqKjwJi5jmCihBT2Sw==", + "dev": true, + "peer": true, + "dependencies": { + "@nrwl/devkit": "19.1.2", + "ejs": "^3.1.7", + "enquirer": "~2.3.6", + "ignore": "^5.0.4", + "minimatch": "9.0.3", + "semver": "^7.5.3", + "tmp": "~0.2.1", + "tslib": "^2.3.0", + "yargs-parser": "21.1.1" + }, + "peerDependencies": { + "nx": ">= 17 <= 20" + } + }, "node_modules/@nx/eslint": { "version": "19.1.1", "resolved": "https://registry.npmjs.org/@nx/eslint/-/eslint-19.1.1.tgz", @@ -4029,6 +4063,64 @@ } } }, + "node_modules/@nx/eslint-plugin/node_modules/@nrwl/devkit": { + "version": "19.1.1", + "resolved": "https://registry.npmjs.org/@nrwl/devkit/-/devkit-19.1.1.tgz", + "integrity": "sha512-CrbEy4zBRPPV8gGtwpSgfxJUElXRxEGvvxQlrhoCKmzH7v9407jFjXpzYOipwa9u65a7raCCtsSKYuRdecRglQ==", + "dev": true, + "dependencies": { + "@nx/devkit": "19.1.1" + } + }, + "node_modules/@nx/eslint-plugin/node_modules/@nx/devkit": { + "version": "19.1.1", + "resolved": "https://registry.npmjs.org/@nx/devkit/-/devkit-19.1.1.tgz", + "integrity": "sha512-YMt5vFaNMeIKgBwQ3RIFQG7AoYOksd8vNxwunirN95q/70HMIoJQsnRCMT45jVd9D/GIWASgY8QsGTMJfcO0qQ==", + "dev": true, + "dependencies": { + "@nrwl/devkit": "19.1.1", + "ejs": "^3.1.7", + "enquirer": "~2.3.6", + "ignore": "^5.0.4", + "minimatch": "9.0.3", + "semver": "^7.5.3", + "tmp": "~0.2.1", + "tslib": "^2.3.0", + "yargs-parser": "21.1.1" + }, + "peerDependencies": { + "nx": ">= 17 <= 20" + } + }, + "node_modules/@nx/eslint/node_modules/@nrwl/devkit": { + "version": "19.1.1", + "resolved": "https://registry.npmjs.org/@nrwl/devkit/-/devkit-19.1.1.tgz", + "integrity": "sha512-CrbEy4zBRPPV8gGtwpSgfxJUElXRxEGvvxQlrhoCKmzH7v9407jFjXpzYOipwa9u65a7raCCtsSKYuRdecRglQ==", + "dev": true, + "dependencies": { + "@nx/devkit": "19.1.1" + } + }, + "node_modules/@nx/eslint/node_modules/@nx/devkit": { + "version": "19.1.1", + "resolved": "https://registry.npmjs.org/@nx/devkit/-/devkit-19.1.1.tgz", + "integrity": "sha512-YMt5vFaNMeIKgBwQ3RIFQG7AoYOksd8vNxwunirN95q/70HMIoJQsnRCMT45jVd9D/GIWASgY8QsGTMJfcO0qQ==", + "dev": true, + "dependencies": { + "@nrwl/devkit": "19.1.1", + "ejs": "^3.1.7", + "enquirer": "~2.3.6", + "ignore": "^5.0.4", + "minimatch": "9.0.3", + "semver": "^7.5.3", + "tmp": "~0.2.1", + "tslib": "^2.3.0", + "yargs-parser": "21.1.1" + }, + "peerDependencies": { + "nx": ">= 17 <= 20" + } + }, "node_modules/@nx/express": { "version": "19.1.1", "resolved": "https://registry.npmjs.org/@nx/express/-/express-19.1.1.tgz", @@ -4049,6 +4141,35 @@ } } }, + "node_modules/@nx/express/node_modules/@nrwl/devkit": { + "version": "19.1.1", + "resolved": "https://registry.npmjs.org/@nrwl/devkit/-/devkit-19.1.1.tgz", + "integrity": "sha512-CrbEy4zBRPPV8gGtwpSgfxJUElXRxEGvvxQlrhoCKmzH7v9407jFjXpzYOipwa9u65a7raCCtsSKYuRdecRglQ==", + "dev": true, + "dependencies": { + "@nx/devkit": "19.1.1" + } + }, + "node_modules/@nx/express/node_modules/@nx/devkit": { + "version": "19.1.1", + "resolved": "https://registry.npmjs.org/@nx/devkit/-/devkit-19.1.1.tgz", + "integrity": "sha512-YMt5vFaNMeIKgBwQ3RIFQG7AoYOksd8vNxwunirN95q/70HMIoJQsnRCMT45jVd9D/GIWASgY8QsGTMJfcO0qQ==", + "dev": true, + "dependencies": { + "@nrwl/devkit": "19.1.1", + "ejs": "^3.1.7", + "enquirer": "~2.3.6", + "ignore": "^5.0.4", + "minimatch": "9.0.3", + "semver": "^7.5.3", + "tmp": "~0.2.1", + "tslib": "^2.3.0", + "yargs-parser": "21.1.1" + }, + "peerDependencies": { + "nx": ">= 17 <= 20" + } + }, "node_modules/@nx/jest": { "version": "19.1.1", "resolved": "https://registry.npmjs.org/@nx/jest/-/jest-19.1.1.tgz", @@ -4072,6 +4193,35 @@ "yargs-parser": "21.1.1" } }, + "node_modules/@nx/jest/node_modules/@nrwl/devkit": { + "version": "19.1.1", + "resolved": "https://registry.npmjs.org/@nrwl/devkit/-/devkit-19.1.1.tgz", + "integrity": "sha512-CrbEy4zBRPPV8gGtwpSgfxJUElXRxEGvvxQlrhoCKmzH7v9407jFjXpzYOipwa9u65a7raCCtsSKYuRdecRglQ==", + "dev": true, + "dependencies": { + "@nx/devkit": "19.1.1" + } + }, + "node_modules/@nx/jest/node_modules/@nx/devkit": { + "version": "19.1.1", + "resolved": "https://registry.npmjs.org/@nx/devkit/-/devkit-19.1.1.tgz", + "integrity": "sha512-YMt5vFaNMeIKgBwQ3RIFQG7AoYOksd8vNxwunirN95q/70HMIoJQsnRCMT45jVd9D/GIWASgY8QsGTMJfcO0qQ==", + "dev": true, + "dependencies": { + "@nrwl/devkit": "19.1.1", + "ejs": "^3.1.7", + "enquirer": "~2.3.6", + "ignore": "^5.0.4", + "minimatch": "9.0.3", + "semver": "^7.5.3", + "tmp": "~0.2.1", + "tslib": "^2.3.0", + "yargs-parser": "21.1.1" + }, + "peerDependencies": { + "nx": ">= 17 <= 20" + } + }, "node_modules/@nx/js": { "version": "19.1.1", "resolved": "https://registry.npmjs.org/@nx/js/-/js-19.1.1.tgz", @@ -4117,6 +4267,35 @@ } } }, + "node_modules/@nx/js/node_modules/@nrwl/devkit": { + "version": "19.1.1", + "resolved": "https://registry.npmjs.org/@nrwl/devkit/-/devkit-19.1.1.tgz", + "integrity": "sha512-CrbEy4zBRPPV8gGtwpSgfxJUElXRxEGvvxQlrhoCKmzH7v9407jFjXpzYOipwa9u65a7raCCtsSKYuRdecRglQ==", + "dev": true, + "dependencies": { + "@nx/devkit": "19.1.1" + } + }, + "node_modules/@nx/js/node_modules/@nx/devkit": { + "version": "19.1.1", + "resolved": "https://registry.npmjs.org/@nx/devkit/-/devkit-19.1.1.tgz", + "integrity": "sha512-YMt5vFaNMeIKgBwQ3RIFQG7AoYOksd8vNxwunirN95q/70HMIoJQsnRCMT45jVd9D/GIWASgY8QsGTMJfcO0qQ==", + "dev": true, + "dependencies": { + "@nrwl/devkit": "19.1.1", + "ejs": "^3.1.7", + "enquirer": "~2.3.6", + "ignore": "^5.0.4", + "minimatch": "9.0.3", + "semver": "^7.5.3", + "tmp": "~0.2.1", + "tslib": "^2.3.0", + "yargs-parser": "21.1.1" + }, + "peerDependencies": { + "nx": ">= 17 <= 20" + } + }, "node_modules/@nx/js/node_modules/ts-node": { "version": "10.9.1", "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.1.tgz", @@ -4183,6 +4362,35 @@ "tslib": "^2.3.0" } }, + "node_modules/@nx/node/node_modules/@nrwl/devkit": { + "version": "19.1.1", + "resolved": "https://registry.npmjs.org/@nrwl/devkit/-/devkit-19.1.1.tgz", + "integrity": "sha512-CrbEy4zBRPPV8gGtwpSgfxJUElXRxEGvvxQlrhoCKmzH7v9407jFjXpzYOipwa9u65a7raCCtsSKYuRdecRglQ==", + "dev": true, + "dependencies": { + "@nx/devkit": "19.1.1" + } + }, + "node_modules/@nx/node/node_modules/@nx/devkit": { + "version": "19.1.1", + "resolved": "https://registry.npmjs.org/@nx/devkit/-/devkit-19.1.1.tgz", + "integrity": "sha512-YMt5vFaNMeIKgBwQ3RIFQG7AoYOksd8vNxwunirN95q/70HMIoJQsnRCMT45jVd9D/GIWASgY8QsGTMJfcO0qQ==", + "dev": true, + "dependencies": { + "@nrwl/devkit": "19.1.1", + "ejs": "^3.1.7", + "enquirer": "~2.3.6", + "ignore": "^5.0.4", + "minimatch": "9.0.3", + "semver": "^7.5.3", + "tmp": "~0.2.1", + "tslib": "^2.3.0", + "yargs-parser": "21.1.1" + }, + "peerDependencies": { + "nx": ">= 17 <= 20" + } + }, "node_modules/@nx/nx-darwin-arm64": { "version": "19.1.1", "resolved": "https://registry.npmjs.org/@nx/nx-darwin-arm64/-/nx-darwin-arm64-19.1.1.tgz", @@ -4362,6 +4570,35 @@ "vitest": "^1.3.1" } }, + "node_modules/@nx/vite/node_modules/@nrwl/devkit": { + "version": "19.1.1", + "resolved": "https://registry.npmjs.org/@nrwl/devkit/-/devkit-19.1.1.tgz", + "integrity": "sha512-CrbEy4zBRPPV8gGtwpSgfxJUElXRxEGvvxQlrhoCKmzH7v9407jFjXpzYOipwa9u65a7raCCtsSKYuRdecRglQ==", + "dev": true, + "dependencies": { + "@nx/devkit": "19.1.1" + } + }, + "node_modules/@nx/vite/node_modules/@nx/devkit": { + "version": "19.1.1", + "resolved": "https://registry.npmjs.org/@nx/devkit/-/devkit-19.1.1.tgz", + "integrity": "sha512-YMt5vFaNMeIKgBwQ3RIFQG7AoYOksd8vNxwunirN95q/70HMIoJQsnRCMT45jVd9D/GIWASgY8QsGTMJfcO0qQ==", + "dev": true, + "dependencies": { + "@nrwl/devkit": "19.1.1", + "ejs": "^3.1.7", + "enquirer": "~2.3.6", + "ignore": "^5.0.4", + "minimatch": "9.0.3", + "semver": "^7.5.3", + "tmp": "~0.2.1", + "tslib": "^2.3.0", + "yargs-parser": "21.1.1" + }, + "peerDependencies": { + "nx": ">= 17 <= 20" + } + }, "node_modules/@nx/web": { "version": "19.1.1", "resolved": "https://registry.npmjs.org/@nx/web/-/web-19.1.1.tgz", @@ -4377,6 +4614,35 @@ "tslib": "^2.3.0" } }, + "node_modules/@nx/web/node_modules/@nrwl/devkit": { + "version": "19.1.1", + "resolved": "https://registry.npmjs.org/@nrwl/devkit/-/devkit-19.1.1.tgz", + "integrity": "sha512-CrbEy4zBRPPV8gGtwpSgfxJUElXRxEGvvxQlrhoCKmzH7v9407jFjXpzYOipwa9u65a7raCCtsSKYuRdecRglQ==", + "dev": true, + "dependencies": { + "@nx/devkit": "19.1.1" + } + }, + "node_modules/@nx/web/node_modules/@nx/devkit": { + "version": "19.1.1", + "resolved": "https://registry.npmjs.org/@nx/devkit/-/devkit-19.1.1.tgz", + "integrity": "sha512-YMt5vFaNMeIKgBwQ3RIFQG7AoYOksd8vNxwunirN95q/70HMIoJQsnRCMT45jVd9D/GIWASgY8QsGTMJfcO0qQ==", + "dev": true, + "dependencies": { + "@nrwl/devkit": "19.1.1", + "ejs": "^3.1.7", + "enquirer": "~2.3.6", + "ignore": "^5.0.4", + "minimatch": "9.0.3", + "semver": "^7.5.3", + "tmp": "~0.2.1", + "tslib": "^2.3.0", + "yargs-parser": "21.1.1" + }, + "peerDependencies": { + "nx": ">= 17 <= 20" + } + }, "node_modules/@nx/webpack": { "version": "19.1.1", "resolved": "https://registry.npmjs.org/@nx/webpack/-/webpack-19.1.1.tgz", @@ -4422,6 +4688,35 @@ "webpack-subresource-integrity": "^5.1.0" } }, + "node_modules/@nx/webpack/node_modules/@nrwl/devkit": { + "version": "19.1.1", + "resolved": "https://registry.npmjs.org/@nrwl/devkit/-/devkit-19.1.1.tgz", + "integrity": "sha512-CrbEy4zBRPPV8gGtwpSgfxJUElXRxEGvvxQlrhoCKmzH7v9407jFjXpzYOipwa9u65a7raCCtsSKYuRdecRglQ==", + "dev": true, + "dependencies": { + "@nx/devkit": "19.1.1" + } + }, + "node_modules/@nx/webpack/node_modules/@nx/devkit": { + "version": "19.1.1", + "resolved": "https://registry.npmjs.org/@nx/devkit/-/devkit-19.1.1.tgz", + "integrity": "sha512-YMt5vFaNMeIKgBwQ3RIFQG7AoYOksd8vNxwunirN95q/70HMIoJQsnRCMT45jVd9D/GIWASgY8QsGTMJfcO0qQ==", + "dev": true, + "dependencies": { + "@nrwl/devkit": "19.1.1", + "ejs": "^3.1.7", + "enquirer": "~2.3.6", + "ignore": "^5.0.4", + "minimatch": "9.0.3", + "semver": "^7.5.3", + "tmp": "~0.2.1", + "tslib": "^2.3.0", + "yargs-parser": "21.1.1" + }, + "peerDependencies": { + "nx": ">= 17 <= 20" + } + }, "node_modules/@nx/workspace": { "version": "19.1.1", "resolved": "https://registry.npmjs.org/@nx/workspace/-/workspace-19.1.1.tgz", @@ -4437,6 +4732,35 @@ "yargs-parser": "21.1.1" } }, + "node_modules/@nx/workspace/node_modules/@nrwl/devkit": { + "version": "19.1.1", + "resolved": "https://registry.npmjs.org/@nrwl/devkit/-/devkit-19.1.1.tgz", + "integrity": "sha512-CrbEy4zBRPPV8gGtwpSgfxJUElXRxEGvvxQlrhoCKmzH7v9407jFjXpzYOipwa9u65a7raCCtsSKYuRdecRglQ==", + "dev": true, + "dependencies": { + "@nx/devkit": "19.1.1" + } + }, + "node_modules/@nx/workspace/node_modules/@nx/devkit": { + "version": "19.1.1", + "resolved": "https://registry.npmjs.org/@nx/devkit/-/devkit-19.1.1.tgz", + "integrity": "sha512-YMt5vFaNMeIKgBwQ3RIFQG7AoYOksd8vNxwunirN95q/70HMIoJQsnRCMT45jVd9D/GIWASgY8QsGTMJfcO0qQ==", + "dev": true, + "dependencies": { + "@nrwl/devkit": "19.1.1", + "ejs": "^3.1.7", + "enquirer": "~2.3.6", + "ignore": "^5.0.4", + "minimatch": "9.0.3", + "semver": "^7.5.3", + "tmp": "~0.2.1", + "tslib": "^2.3.0", + "yargs-parser": "21.1.1" + }, + "peerDependencies": { + "nx": ">= 17 <= 20" + } + }, "node_modules/@octokit/auth-token": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/@octokit/auth-token/-/auth-token-4.0.0.tgz", @@ -5146,12 +5470,12 @@ } }, "node_modules/@swc-node/register": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/@swc-node/register/-/register-1.8.0.tgz", - "integrity": "sha512-8K3589HoBSmVmrEVrtr4K5sWEithpGDzcFGic81OW0A9sZY38IV5EGRODQWCk0SBDyLhaF+pid120vJAtsHo1A==", + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/@swc-node/register/-/register-1.9.1.tgz", + "integrity": "sha512-z//TBXJdRWXoISCXlQmVz+NMm8Qm/UvcfKiGC0tSJdfeVYf5EZkGqvk2OiRH4SIJ6OGFfS9T0YrvA2pDKzWtPA==", "dependencies": { - "@swc-node/core": "^1.12.0", - "@swc-node/sourcemap-support": "^0.4.0", + "@swc-node/core": "^1.13.1", + "@swc-node/sourcemap-support": "^0.5.0", "colorette": "^2.0.20", "debug": "^4.3.4", "pirates": "^4.0.6", @@ -5162,14 +5486,14 @@ "url": "https://github.com/sponsors/Brooooooklyn" }, "peerDependencies": { - "@swc/core": ">= 1.3", + "@swc/core": ">= 1.4.13", "typescript": ">= 4.3" } }, "node_modules/@swc-node/sourcemap-support": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/@swc-node/sourcemap-support/-/sourcemap-support-0.4.0.tgz", - "integrity": "sha512-weuRmYTO+4yOtHtPZHXlPdA1dJJJp3QOoZAFZ6uZidu992F2X5v1fQdnb26xs1o3Ex/e2sYhRyY5R6NGNuoATQ==", + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/@swc-node/sourcemap-support/-/sourcemap-support-0.5.0.tgz", + "integrity": "sha512-fbhjL5G0YvFoWwNhWleuBUfotiX+USiA9oJqu9STFw+Hb0Cgnddn+HVS/K5fI45mn92e8V+cHD2jgFjk4w2T9Q==", "dependencies": { "source-map-support": "^0.5.21", "tslib": "^2.6.2" @@ -5185,9 +5509,9 @@ } }, "node_modules/@swc/core": { - "version": "1.5.22", - "resolved": "https://registry.npmjs.org/@swc/core/-/core-1.5.22.tgz", - "integrity": "sha512-YxvaMFS0Uq9gHVjJh0aKBB868lcBExWVWoZWSCqcoE95S1to9LZiN5WOOlwxSjajN26YqwSU+Ndsg+bgy9VZeg==", + "version": "1.5.24", + "resolved": "https://registry.npmjs.org/@swc/core/-/core-1.5.24.tgz", + "integrity": "sha512-Eph9zvO4xvqWZGVzTdtdEJ0Vqf0VIML/o/e4Qd2RLOqtfgnlRi7avmMu5C0oqciJ0tk+hqdUKVUZ4JPoPaiGvQ==", "hasInstallScript": true, "dependencies": { "@swc/counter": "^0.1.3", @@ -5201,16 +5525,16 @@ "url": "https://opencollective.com/swc" }, "optionalDependencies": { - "@swc/core-darwin-arm64": "1.5.22", - "@swc/core-darwin-x64": "1.5.22", - "@swc/core-linux-arm-gnueabihf": "1.5.22", - "@swc/core-linux-arm64-gnu": "1.5.22", - "@swc/core-linux-arm64-musl": "1.5.22", - "@swc/core-linux-x64-gnu": "1.5.22", - "@swc/core-linux-x64-musl": "1.5.22", - "@swc/core-win32-arm64-msvc": "1.5.22", - "@swc/core-win32-ia32-msvc": "1.5.22", - "@swc/core-win32-x64-msvc": "1.5.22" + "@swc/core-darwin-arm64": "1.5.24", + "@swc/core-darwin-x64": "1.5.24", + "@swc/core-linux-arm-gnueabihf": "1.5.24", + "@swc/core-linux-arm64-gnu": "1.5.24", + "@swc/core-linux-arm64-musl": "1.5.24", + "@swc/core-linux-x64-gnu": "1.5.24", + "@swc/core-linux-x64-musl": "1.5.24", + "@swc/core-win32-arm64-msvc": "1.5.24", + "@swc/core-win32-ia32-msvc": "1.5.24", + "@swc/core-win32-x64-msvc": "1.5.24" }, "peerDependencies": { "@swc/helpers": "*" @@ -5222,9 +5546,9 @@ } }, "node_modules/@swc/core-darwin-arm64": { - "version": "1.5.22", - "resolved": "https://registry.npmjs.org/@swc/core-darwin-arm64/-/core-darwin-arm64-1.5.22.tgz", - "integrity": "sha512-aqiflbmzv8joY/fNpCBe7/YJupoWQHz5dX1YfU3D8Kii5VNZeqsQEXDUEEAFZUwnSce1aoGguX/O/DIxC+1Rlg==", + "version": "1.5.24", + "resolved": "https://registry.npmjs.org/@swc/core-darwin-arm64/-/core-darwin-arm64-1.5.24.tgz", + "integrity": "sha512-M7oLOcC0sw+UTyAuL/9uyB9GeO4ZpaBbH76JSH6g1m0/yg7LYJZGRmplhDmwVSDAR5Fq4Sjoi1CksmmGkgihGA==", "cpu": [ "arm64" ], @@ -5237,9 +5561,9 @@ } }, "node_modules/@swc/core-darwin-x64": { - "version": "1.5.22", - "resolved": "https://registry.npmjs.org/@swc/core-darwin-x64/-/core-darwin-x64-1.5.22.tgz", - "integrity": "sha512-U0yddnMZxuZEd2P4RJIcbgu7b9/w0jo0e6I4iL4+MFN1J3IBxPi666xsBDdnsTDGKYHwv+sgFrwcK2gJIL/n6Q==", + "version": "1.5.24", + "resolved": "https://registry.npmjs.org/@swc/core-darwin-x64/-/core-darwin-x64-1.5.24.tgz", + "integrity": "sha512-MfcFjGGYognpSBSos2pYUNYJSmqEhuw5ceGr6qAdME7ddbjGXliza4W6FggsM+JnWwpqa31+e7/R+GetW4WkaQ==", "cpu": [ "x64" ], @@ -5252,9 +5576,9 @@ } }, "node_modules/@swc/core-linux-arm-gnueabihf": { - "version": "1.5.22", - "resolved": "https://registry.npmjs.org/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.5.22.tgz", - "integrity": "sha512-gXyPgla7w4QgvTx10XXNAECU+bdJtY8/6jz0IbU6NMiHtCC8NO6my1io5wuDUeUE5KefACpIWmKsKV2ts/uNNA==", + "version": "1.5.24", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.5.24.tgz", + "integrity": "sha512-amI2pwtcWV3E/m/nf+AQtn1LWDzKLZyjCmWd3ms7QjEueWYrY8cU1Y4Wp7wNNsxIoPOi8zek1Uj2wwFD/pttNQ==", "cpu": [ "arm" ], @@ -5267,9 +5591,9 @@ } }, "node_modules/@swc/core-linux-arm64-gnu": { - "version": "1.5.22", - "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.5.22.tgz", - "integrity": "sha512-5UaMgE2onZETZ23xOl2fuql4MtU6HMbAISGTkIKu2U44J/4sINRi7VYiEWdc8ZN/xPcCKpvbqmpZZejf4T4Rtw==", + "version": "1.5.24", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.5.24.tgz", + "integrity": "sha512-sTSvmqMmgT1ynH/nP75Pc51s+iT4crZagHBiDOf5cq+kudUYjda9lWMs7xkXB/TUKFHPCRK0HGunl8bkwiIbuw==", "cpu": [ "arm64" ], @@ -5282,9 +5606,9 @@ } }, "node_modules/@swc/core-linux-arm64-musl": { - "version": "1.5.22", - "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.5.22.tgz", - "integrity": "sha512-t/Rj/afBx5+7cfCtBGmx9V55ckqHh031jglfgJtaAOPRXygz30tavPCVdaOXyJPxjZ2QszYPK43CTOsA4QI+pg==", + "version": "1.5.24", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.5.24.tgz", + "integrity": "sha512-vd2/hfOBGbrX21FxsFdXCUaffjkHvlZkeE2UMRajdXifwv79jqOHIJg3jXG1F3ZrhCghCzirFts4tAZgcG8XWg==", "cpu": [ "arm64" ], @@ -5297,9 +5621,9 @@ } }, "node_modules/@swc/core-linux-x64-gnu": { - "version": "1.5.22", - "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.5.22.tgz", - "integrity": "sha512-OYn6FZd02iiJOIet6SzvVHsIzpB06biKNJvPPSYbQHZBb2BQbqw6zGwhkrHh8+tFfzL0W0RVOHzp1dJwAgAvlQ==", + "version": "1.5.24", + "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.5.24.tgz", + "integrity": "sha512-Zrdzi7NqzQxm2BvAG5KyOSBEggQ7ayrxh599AqqevJmsUXJ8o2nMiWQOBvgCGp7ye+Biz3pvZn1EnRzAp+TpUg==", "cpu": [ "x64" ], @@ -5312,9 +5636,9 @@ } }, "node_modules/@swc/core-linux-x64-musl": { - "version": "1.5.22", - "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.5.22.tgz", - "integrity": "sha512-DOmY/kTAITzObfXZif7+NzhEb6xrwQriVhogAFjM2//M6QQX3XYU6EazKyBU97FG6jAnU8Xo0CsQ11NVO+P+dA==", + "version": "1.5.24", + "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.5.24.tgz", + "integrity": "sha512-1F8z9NRi52jdZQCGc5sflwYSctL6omxiVmIFVp8TC9nngjQKc00TtX/JC2Eo2HwvgupkFVl5YQJidAck9YtmJw==", "cpu": [ "x64" ], @@ -5327,9 +5651,9 @@ } }, "node_modules/@swc/core-win32-arm64-msvc": { - "version": "1.5.22", - "resolved": "https://registry.npmjs.org/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.5.22.tgz", - "integrity": "sha512-m+z0a0Kxc27CgMheBfch71vSHAaVu3WOtuTevinRy+kWYIXRAEtCIQK1F7U7Lj06VuTONAtol6ZQb/aywvJiDg==", + "version": "1.5.24", + "resolved": "https://registry.npmjs.org/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.5.24.tgz", + "integrity": "sha512-cKpP7KvS6Xr0jFSTBXY53HZX/YfomK5EMQYpCVDOvfsZeYHN20sQSKXfpVLvA/q2igVt1zzy1XJcOhpJcgiKLg==", "cpu": [ "arm64" ], @@ -5342,9 +5666,9 @@ } }, "node_modules/@swc/core-win32-ia32-msvc": { - "version": "1.5.22", - "resolved": "https://registry.npmjs.org/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.5.22.tgz", - "integrity": "sha512-96ipznXxSv8jzd22po3++UK4TjKwNb38f2RQjTTYyzry4omuI0fmzBLNVBdEOtw/jSQqouwLHvnezywhXwIk/A==", + "version": "1.5.24", + "resolved": "https://registry.npmjs.org/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.5.24.tgz", + "integrity": "sha512-IoPWfi0iwqjZuf7gE223+B97/ZwkKbu7qL5KzGP7g3hJrGSKAvv7eC5Y9r2iKKtLKyv5R/T6Ho0kFR/usi7rHw==", "cpu": [ "ia32" ], @@ -5357,9 +5681,9 @@ } }, "node_modules/@swc/core-win32-x64-msvc": { - "version": "1.5.22", - "resolved": "https://registry.npmjs.org/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.5.22.tgz", - "integrity": "sha512-53pNt9zuD5x8HGjeF4LeVxpZGqkviqfF+idnEJ9alT0aEKaSkxf7iL+RnPsq4lXMIkREE9cCoG0uDoXGwl14dA==", + "version": "1.5.24", + "resolved": "https://registry.npmjs.org/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.5.24.tgz", + "integrity": "sha512-zHgF2k1uVJL8KIW+PnVz1To4a3Cz9THbh2z2lbehaF/gKHugH4c3djBozU4das1v35KOqf5jWIEviBLql2wDLQ==", "cpu": [ "x64" ], @@ -5663,9 +5987,9 @@ } }, "node_modules/@types/express-serve-static-core": { - "version": "4.19.2", - "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.19.2.tgz", - "integrity": "sha512-dPSEQElyVJ97BuGduAqQjpBocZWAs0GR94z+ptL7JXQJeJdHw2WBG3EWdFrK36b8Q6j8P4cXOMhgUoi0IIfIsg==", + "version": "4.19.3", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.19.3.tgz", + "integrity": "sha512-KOzM7MhcBFlmnlr/fzISFF5vGWVSvN6fTd4T+ExOt08bA/dA5kpSzY52nMsI1KDFmUREpJelPYyuslLRSjjgCg==", "dev": true, "dependencies": { "@types/node": "*", @@ -5704,9 +6028,9 @@ } }, "node_modules/@types/gtag.js": { - "version": "0.0.19", - "resolved": "https://registry.npmjs.org/@types/gtag.js/-/gtag.js-0.0.19.tgz", - "integrity": "sha512-KHoDzrf9rSd0mooKN576PjExpdk/XRrNu4RQnmigsScSTSidwyOUe9kDrHz9UPKjiBrx2QEsSkexbJSgS0j72w==", + "version": "0.0.20", + "resolved": "https://registry.npmjs.org/@types/gtag.js/-/gtag.js-0.0.20.tgz", + "integrity": "sha512-wwAbk3SA2QeU67unN7zPxjEHmPmlXwZXZvQEpbEUQuMCRGgKyE1m6XDuTUA9b6pCGb/GqJmdfMOY5LuDjJSbbg==", "dev": true }, "node_modules/@types/html-minifier": { @@ -6045,16 +6369,16 @@ } }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "7.11.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.11.0.tgz", - "integrity": "sha512-P+qEahbgeHW4JQ/87FuItjBj8O3MYv5gELDzr8QaQ7fsll1gSMTYb6j87MYyxwf3DtD7uGFB9ShwgmCJB5KmaQ==", + "version": "7.12.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.12.0.tgz", + "integrity": "sha512-7F91fcbuDf/d3S8o21+r3ZncGIke/+eWk0EpO21LXhDfLahriZF9CGj4fbAetEjlaBdjdSm9a6VeXbpbT6Z40Q==", "dev": true, "dependencies": { "@eslint-community/regexpp": "^4.10.0", - "@typescript-eslint/scope-manager": "7.11.0", - "@typescript-eslint/type-utils": "7.11.0", - "@typescript-eslint/utils": "7.11.0", - "@typescript-eslint/visitor-keys": "7.11.0", + "@typescript-eslint/scope-manager": "7.12.0", + "@typescript-eslint/type-utils": "7.12.0", + "@typescript-eslint/utils": "7.12.0", + "@typescript-eslint/visitor-keys": "7.12.0", "graphemer": "^1.4.0", "ignore": "^5.3.1", "natural-compare": "^1.4.0", @@ -6078,15 +6402,15 @@ } }, "node_modules/@typescript-eslint/parser": { - "version": "7.11.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-7.11.0.tgz", - "integrity": "sha512-yimw99teuaXVWsBcPO1Ais02kwJ1jmNA1KxE7ng0aT7ndr1pT1wqj0OJnsYVGKKlc4QJai86l/025L6z8CljOg==", + "version": "7.12.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-7.12.0.tgz", + "integrity": "sha512-dm/J2UDY3oV3TKius2OUZIFHsomQmpHtsV0FTh1WO8EKgHLQ1QCADUqscPgTpU+ih1e21FQSRjXckHn3txn6kQ==", "dev": true, "dependencies": { - "@typescript-eslint/scope-manager": "7.11.0", - "@typescript-eslint/types": "7.11.0", - "@typescript-eslint/typescript-estree": "7.11.0", - "@typescript-eslint/visitor-keys": "7.11.0", + "@typescript-eslint/scope-manager": "7.12.0", + "@typescript-eslint/types": "7.12.0", + "@typescript-eslint/typescript-estree": "7.12.0", + "@typescript-eslint/visitor-keys": "7.12.0", "debug": "^4.3.4" }, "engines": { @@ -6106,13 +6430,13 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "7.11.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.11.0.tgz", - "integrity": "sha512-27tGdVEiutD4POirLZX4YzT180vevUURJl4wJGmm6TrQoiYwuxTIY98PBp6L2oN+JQxzE0URvYlzJaBHIekXAw==", + "version": "7.12.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.12.0.tgz", + "integrity": "sha512-itF1pTnN6F3unPak+kutH9raIkL3lhH1YRPGgt7QQOh43DQKVJXmWkpb+vpc/TiDHs6RSd9CTbDsc/Y+Ygq7kg==", "dev": true, "dependencies": { - "@typescript-eslint/types": "7.11.0", - "@typescript-eslint/visitor-keys": "7.11.0" + "@typescript-eslint/types": "7.12.0", + "@typescript-eslint/visitor-keys": "7.12.0" }, "engines": { "node": "^18.18.0 || >=20.0.0" @@ -6123,13 +6447,13 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "7.11.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-7.11.0.tgz", - "integrity": "sha512-WmppUEgYy+y1NTseNMJ6mCFxt03/7jTOy08bcg7bxJJdsM4nuhnchyBbE8vryveaJUf62noH7LodPSo5Z0WUCg==", + "version": "7.12.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-7.12.0.tgz", + "integrity": "sha512-lib96tyRtMhLxwauDWUp/uW3FMhLA6D0rJ8T7HmH7x23Gk1Gwwu8UZ94NMXBvOELn6flSPiBrCKlehkiXyaqwA==", "dev": true, "dependencies": { - "@typescript-eslint/typescript-estree": "7.11.0", - "@typescript-eslint/utils": "7.11.0", + "@typescript-eslint/typescript-estree": "7.12.0", + "@typescript-eslint/utils": "7.12.0", "debug": "^4.3.4", "ts-api-utils": "^1.3.0" }, @@ -6150,9 +6474,9 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "7.11.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.11.0.tgz", - "integrity": "sha512-MPEsDRZTyCiXkD4vd3zywDCifi7tatc4K37KqTprCvaXptP7Xlpdw0NR2hRJTetG5TxbWDB79Ys4kLmHliEo/w==", + "version": "7.12.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.12.0.tgz", + "integrity": "sha512-o+0Te6eWp2ppKY3mLCU+YA9pVJxhUJE15FV7kxuD9jgwIAa+w/ycGJBMrYDTpVGUM/tgpa9SeMOugSabWFq7bg==", "dev": true, "engines": { "node": "^18.18.0 || >=20.0.0" @@ -6163,13 +6487,13 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "7.11.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.11.0.tgz", - "integrity": "sha512-cxkhZ2C/iyi3/6U9EPc5y+a6csqHItndvN/CzbNXTNrsC3/ASoYQZEt9uMaEp+xFNjasqQyszp5TumAVKKvJeQ==", + "version": "7.12.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.12.0.tgz", + "integrity": "sha512-5bwqLsWBULv1h6pn7cMW5dXX/Y2amRqLaKqsASVwbBHMZSnHqE/HN4vT4fE0aFsiwxYvr98kqOWh1a8ZKXalCQ==", "dev": true, "dependencies": { - "@typescript-eslint/types": "7.11.0", - "@typescript-eslint/visitor-keys": "7.11.0", + "@typescript-eslint/types": "7.12.0", + "@typescript-eslint/visitor-keys": "7.12.0", "debug": "^4.3.4", "globby": "^11.1.0", "is-glob": "^4.0.3", @@ -6206,15 +6530,15 @@ } }, "node_modules/@typescript-eslint/utils": { - "version": "7.11.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-7.11.0.tgz", - "integrity": "sha512-xlAWwPleNRHwF37AhrZurOxA1wyXowW4PqVXZVUNCLjB48CqdPJoJWkrpH2nij9Q3Lb7rtWindtoXwxjxlKKCA==", + "version": "7.12.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-7.12.0.tgz", + "integrity": "sha512-Y6hhwxwDx41HNpjuYswYp6gDbkiZ8Hin9Bf5aJQn1bpTs3afYY4GX+MPYxma8jtoIV2GRwTM/UJm/2uGCVv+DQ==", "dev": true, "dependencies": { "@eslint-community/eslint-utils": "^4.4.0", - "@typescript-eslint/scope-manager": "7.11.0", - "@typescript-eslint/types": "7.11.0", - "@typescript-eslint/typescript-estree": "7.11.0" + "@typescript-eslint/scope-manager": "7.12.0", + "@typescript-eslint/types": "7.12.0", + "@typescript-eslint/typescript-estree": "7.12.0" }, "engines": { "node": "^18.18.0 || >=20.0.0" @@ -6228,12 +6552,12 @@ } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "7.11.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.11.0.tgz", - "integrity": "sha512-7syYk4MzjxTEk0g/w3iqtgxnFQspDJfn6QKD36xMuuhTzjcxY7F8EmBLnALjVyaOF1/bVocu3bS/2/F7rXrveQ==", + "version": "7.12.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.12.0.tgz", + "integrity": "sha512-uZk7DevrQLL3vSnfFl5bj4sL75qC9D6EdjemIdbtkuUmIheWpuiiylSY01JxJE7+zGrOWDZrp1WxOuDntvKrHQ==", "dev": true, "dependencies": { - "@typescript-eslint/types": "7.11.0", + "@typescript-eslint/types": "7.12.0", "eslint-visitor-keys": "^3.4.3" }, "engines": { @@ -6928,15 +7252,15 @@ } }, "node_modules/ajv": { - "version": "8.14.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.14.0.tgz", - "integrity": "sha512-oYs1UUtO97ZO2lJ4bwnWeQW8/zvOIQLGKcvPTsWmvc2SYgBb+upuNS5NxoLaMU4h8Ju3Nbj6Cq8mD2LQoqVKFA==", + "version": "8.15.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.15.0.tgz", + "integrity": "sha512-15BTtQUOsSrmHCy+B4VnAiJAJxJ8IFgu6fcjFQF3jQYZ78nLSQthlFg4ehp+NLIyfvFgOlxNsjKIEhydtFPVHQ==", "dev": true, "dependencies": { "fast-deep-equal": "^3.1.3", + "fast-uri": "^2.3.0", "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2", - "uri-js": "^4.4.1" + "require-from-string": "^2.0.2" }, "funding": { "type": "github", @@ -7895,9 +8219,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001625", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001625.tgz", - "integrity": "sha512-4KE9N2gcRH+HQhpeiRZXd+1niLB/XNLAhSy4z7fI8EzcbcPoAqjNInxVHTiTwWfTIV4w096XG8OtCOCQQKPv3w==", + "version": "1.0.30001627", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001627.tgz", + "integrity": "sha512-4zgNiB8nTyV/tHhwZrFs88ryjls/lHiqFhrxCW4qSTeuRByBVnPYpDInchOIySWknznucaf31Z4KYqjfbrecVw==", "dev": true, "funding": [ { @@ -8188,6 +8512,14 @@ "integrity": "sha512-lHl4d5/ONEbLlJvaJNtsF/Lz+WvB07u2ycqTYbdrq7UypDXailES4valYb2eWiJFxZlVmpGekfqoxQhzyFdT4Q==", "dev": true }, + "node_modules/collections": { + "version": "5.1.13", + "resolved": "https://registry.npmjs.org/collections/-/collections-5.1.13.tgz", + "integrity": "sha512-SCb6Qd+d3Z02corWQ7/mqXiXeeTdHvkP6TeFSYfGYdCFp1WrjSNZ3j6y8Y3T/7osGEe0iOcU2g1d346l99m4Lg==", + "dependencies": { + "weak-map": "~1.0.x" + } + }, "node_modules/color": { "version": "4.2.3", "resolved": "https://registry.npmjs.org/color/-/color-4.2.3.tgz", @@ -9091,9 +9423,9 @@ "integrity": "sha512-vjAczensTgRcqDERK0SR2XMwsF/tSvnvlv6VcF2GIhg6Sx4yOIt/irsr1RDJsKiIyBzJDpCoXiWWq28MqH2cnQ==" }, "node_modules/debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "version": "4.3.5", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.5.tgz", + "integrity": "sha512-pt0bNEmneDIvdL1Xsd9oDQ/wrQRkXDT4AUWlNZNPKvW5x/jyO9VFXkJUP07vQ2upmw5PlaITaPKc31jK13V+jg==", "dependencies": { "ms": "2.1.2" }, @@ -9551,9 +9883,9 @@ } }, "node_modules/electron-to-chromium": { - "version": "1.4.786", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.786.tgz", - "integrity": "sha512-i/A2UB0sxYViMN0M2zIotQFRIOt1jLuVXudACHBDiJ5gGuAUzf/crZxwlBTdA0O52Hy4CNtTzS7AKRAacs/08Q==", + "version": "1.4.789", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.789.tgz", + "integrity": "sha512-0VbyiaXoT++Fi2vHGo2ThOeS6X3vgRCWrjPeO2FeIAWL6ItiSJ9BqlH8LfCXe3X1IdcG+S0iLoNaxQWhfZoGzQ==", "dev": true }, "node_modules/elliptic": { @@ -10502,6 +10834,12 @@ "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", "dev": true }, + "node_modules/fast-uri": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-2.3.0.tgz", + "integrity": "sha512-eel5UKGn369gGEWOqBShmFJWfq/xSJvsgDzgLYC845GneayWvXBf0lJCBn5qTABfewy1ZDPoaR5OZCP+kssfuw==", + "dev": true + }, "node_modules/fast-xml-parser": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-4.4.0.tgz", @@ -10750,6 +11088,22 @@ "node": "^10.12.0 || >=12.0.0" } }, + "node_modules/flatbush": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/flatbush/-/flatbush-4.4.0.tgz", + "integrity": "sha512-cf6G+sfy/+/FLH7Ls1URQ5GCRlXgwgqUZiEsMNrMZqb3Us3EkKmzUlKbnyoBy/4wI4oLJ+8cyCQoKJIVm92Fmg==", + "dependencies": { + "flatqueue": "^2.0.3" + } + }, + "node_modules/flatqueue": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/flatqueue/-/flatqueue-2.0.3.tgz", + "integrity": "sha512-RZCWZNkmxzUOh8jqEcEGZCycb3B8KAfpPwg3H//cURasunYxsg1eIvE+QDSjX+ZPHTIVfINfK1aLTrVKKO0i4g==", + "engines": { + "node": ">= 12.17.0" + } + }, "node_modules/flatted": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.1.tgz", @@ -12088,6 +12442,20 @@ "node": ">=12" } }, + "node_modules/igc-xc-score": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/igc-xc-score/-/igc-xc-score-1.7.0.tgz", + "integrity": "sha512-49UQM9mbXjTo2mmeRYIq0vszShw8bg8TKhTU2XGwBCmSk0ojJFACajeZKSB0dT8u3F6p+7ap1oRv17g22Xh3PA==", + "dependencies": { + "collections": "^5.1.13", + "flatbush": "^4.0.0", + "igc-parser": "^1.1.0", + "rbush": "^3.0.1" + }, + "bin": { + "igc-xc-score": "dist/igc-xc-score.cjs" + } + }, "node_modules/ignore": { "version": "5.3.1", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.1.tgz", @@ -12645,9 +13013,9 @@ } }, "node_modules/jackspeak": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.1.2.tgz", - "integrity": "sha512-kWmLKn2tRtfYMF/BakihVVRzBKOxz4gJMiL2Rj91WnAB5TPZumSH99R/Yf1qE1u4uRimvCSJfm6hnxohXeEXjQ==", + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.2.3.tgz", + "integrity": "sha512-htOzIMPbpLid/Gq9/zaz9SfExABxqRe1sSCdxntlO/aMD6u0issZQiY25n2GKQUtJ02j7z5sfptlAOMpWWOmvw==", "dependencies": { "@isaacs/cliui": "^8.0.2" }, @@ -16717,6 +17085,11 @@ } ] }, + "node_modules/quickselect": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/quickselect/-/quickselect-2.0.0.tgz", + "integrity": "sha512-RKJ22hX8mHe3Y6wH/N3wCM6BWtjaxIyyUIkpHOvfFnxdI4yD4tBXEBKSbriGujF6jnSVkJrffuo6vxACiSSxIw==" + }, "node_modules/random-bytes": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/random-bytes/-/random-bytes-1.0.0.tgz", @@ -16764,6 +17137,14 @@ "node": ">= 0.8" } }, + "node_modules/rbush": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/rbush/-/rbush-3.0.1.tgz", + "integrity": "sha512-XRaVO0YecOpEuIvbhbpTrZgoiI6xBlz6hnlr6EHhd+0x9ase6EmeN+hdwwUaJvLcsFFQ8iWVF1GAK1yB0BWi0w==", + "dependencies": { + "quickselect": "^2.0.0" + } + }, "node_modules/react-is": { "version": "18.3.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", @@ -16992,9 +17373,9 @@ "dev": true }, "node_modules/reselect": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/reselect/-/reselect-5.1.0.tgz", - "integrity": "sha512-aw7jcGLDpSgNDyWBQLv2cedml85qd95/iszJjN988zX1t7AVRJi19d9kto5+W7oCfQ94gyo40dVbT6g2k4/kXg==" + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/reselect/-/reselect-5.1.1.tgz", + "integrity": "sha512-K/BG6eIky/SBpzfHZv/dd+9JBFiS4SWV7FIujVyJRux6e45+73RaUHXLmIR1f7WOMaQ0U1km6qwklRQxpJJY0w==" }, "node_modules/resolve": { "version": "1.22.8", @@ -17284,9 +17665,9 @@ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" }, "node_modules/sass": { - "version": "1.77.3", - "resolved": "https://registry.npmjs.org/sass/-/sass-1.77.3.tgz", - "integrity": "sha512-WJHo+jmFp0dwRuymPmIovuxHaBntcCyja5hCB0yYY9wWrViEp4kF5Cdai98P72v6FzroPuABqu+ddLMbQWmwzA==", + "version": "1.77.4", + "resolved": "https://registry.npmjs.org/sass/-/sass-1.77.4.tgz", + "integrity": "sha512-vcF3Ckow6g939GMA4PeU7b2K/9FALXk2KF9J87txdHzXbUF9XRQRwSxcAs/fGaTnJeBFd7UoV22j3lzMLdM0Pw==", "dev": true, "dependencies": { "chokidar": ">=3.0.0 <4.0.0", @@ -19974,6 +20355,11 @@ "defaults": "^1.0.3" } }, + "node_modules/weak-map": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/weak-map/-/weak-map-1.0.8.tgz", + "integrity": "sha512-lNR9aAefbGPpHO7AEnY0hCFjz1eTkWCXYvkTRrTHs9qv8zJp+SkVYpzfLIFXQQiG3tVvbNFQgVg2bQS8YGgxyw==" + }, "node_modules/web-streams-polyfill": { "version": "4.0.0-beta.3", "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-4.0.0-beta.3.tgz", diff --git a/package.json b/package.json index c52ca334..f9da67a9 100644 --- a/package.json +++ b/package.json @@ -17,7 +17,7 @@ "private": true, "devDependencies": { "@esri/arcgis-rest-geocoding": "^4.0.2", - "@esri/arcgis-rest-request": "^4.2.1", + "@esri/arcgis-rest-request": "^4.2.2", "@nx-tools/nx-container": "^6.0.1", "@nx/cypress": "19.1.1", "@nx/eslint": "19.1.1", @@ -31,6 +31,7 @@ "@nx/webpack": "19.1.1", "@nx/workspace": "19.1.1", "@protobuf-ts/plugin": "^2.9.4", + "@swc/helpers": "~0.5.2", "@types/compression": "^1.7.5", "@types/csurf": "^1.11.5", "@types/d3-array": "^3.2.1", @@ -38,7 +39,7 @@ "@types/express-fileupload": "^1.5.0", "@types/express-session": "^1.18.0", "@types/google.maps": "^3.55.9", - "@types/gtag.js": "^0.0.19", + "@types/gtag.js": "^0.0.20", "@types/ioredis": "^4.28.10", "@types/jest": "29.4.0", "@types/js-yaml": "^4.0.5", @@ -60,14 +61,15 @@ "jest": "^29.4.1", "jest-bench": "^29.4", "jest-environment-jsdom": "29.6.4", + "jest-environment-node": "^29.4.1", "jsdom": "~22.1.0", "mailersend": "^2.2.0", "nx": "19.1.1", "prettier": "^2.6.2", "rollup-plugin-minify-html-literals": "^1.2.6", "rollup-plugin-visualizer": "^5.12.0", - "servez": "^2.1.4", - "ts-jest": "^29.1.0", + "servez": "^2.1.6", + "ts-jest": "^29.1.4", "ts-node": "^10.9.2", "typescript": "5.4.5", "vite": "~5.0.0", @@ -81,7 +83,7 @@ "@dr.pogodin/csurf": "^1.13.0", "@google-cloud/compute": "^4.7.0", "@google-cloud/datastore": "^9.0.0", - "@google-cloud/pubsub": "^4.4.0", + "@google-cloud/pubsub": "^4.4.1", "@google-cloud/storage": "^7.11.1", "@googlemaps/js-api-loader": "^1.16.6", "@ionic/core": "^8.2.0", @@ -89,9 +91,9 @@ "@popperjs/core": "^2.11.8", "@protobuf-ts/runtime": "^2.9.4", "@reduxjs/toolkit": "^2.2.5", - "@stencil/core": "^4.18.2", - "@swc-node/register": "1.8.0", - "@swc/core": "^1.3.95", + "@stencil/core": "^4.18.3", + "@swc-node/register": "~1.9.1", + "@swc/core": "~1.5.7", "@tmcw/togeojson": "^5.8.1", "@types/mapbox__sphericalmercator": "^1.2.3", "@vivaxy/png": "^1.3.0", @@ -109,11 +111,12 @@ "express-session": "^1.18.0", "geojson": "^0.5.0", "geolib": "^3.3.4", - "glob": "^10.3.15", + "glob": "^10.4.1", "google-polyline": "^1.0.3", "gpx-builder": "^5.3.0", "grant": "^5.4.22", "igc-parser": "^1.1.0", + "igc-xc-score": "^1.7.0", "ioredis": "^5.4.1", "lit": "^3.1.3", "lodepng": "^2.2.0", diff --git a/tsconfig.base.json b/tsconfig.base.json index f5986db2..476cc855 100644 --- a/tsconfig.base.json +++ b/tsconfig.base.json @@ -18,6 +18,7 @@ "paths": { "@flyxc/common": ["libs/common/src/index.ts"], "@flyxc/common-node": ["libs/common-node/src/index.ts"], + "@flyxc/optimizer/*": ["libs/optimizer/src/*"], "@vaadin/dom": ["libs/vaadin-dom/src/index.ts"], "@vaadin/nodom": ["libs/vaadin-nodom/src/index.ts"] }