Compare commits
3 Commits
395c837dd1
...
8b9ba7f81b
Author | SHA1 | Date |
---|---|---|
Vegard Berg | 8b9ba7f81b | |
Vegard Berg | 52c38966c4 | |
Vegard Berg | bd2be948eb |
|
@ -11,6 +11,7 @@
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@sveltejs/vite-plugin-svelte": "^2.4.3",
|
"@sveltejs/vite-plugin-svelte": "^2.4.3",
|
||||||
|
"@sveltejs/vite-plugin-svelte-inspector": "^1.0.4",
|
||||||
"@tsconfig/svelte": "^5.0.0",
|
"@tsconfig/svelte": "^5.0.0",
|
||||||
"@vitejs/plugin-basic-ssl": "^1.0.1",
|
"@vitejs/plugin-basic-ssl": "^1.0.1",
|
||||||
"svelte": "^4.1.2",
|
"svelte": "^4.1.2",
|
||||||
|
@ -21,6 +22,7 @@
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@urql/svelte": "^4.0.4",
|
"@urql/svelte": "^4.0.4",
|
||||||
|
"geojson": "^0.5.0",
|
||||||
"graphql": "^16.8.1",
|
"graphql": "^16.8.1",
|
||||||
"graphql-ws": "^5.14.1",
|
"graphql-ws": "^5.14.1",
|
||||||
"subscriptions-transport-ws": "^0.11.0",
|
"subscriptions-transport-ws": "^0.11.0",
|
||||||
|
|
|
@ -3,14 +3,13 @@
|
||||||
import { Client, setContextClient, cacheExchange, fetchExchange, subscriptionExchange } from '@urql/svelte';
|
import { Client, setContextClient, cacheExchange, fetchExchange, subscriptionExchange } from '@urql/svelte';
|
||||||
import { createClient as createWsClient } from 'graphql-ws';
|
import { createClient as createWsClient } from 'graphql-ws';
|
||||||
import { SubscriptionClient } from 'subscriptions-transport-ws';
|
import { SubscriptionClient } from 'subscriptions-transport-ws';
|
||||||
|
import { createJourneyPlannerClientContext, createVehiclesClientContext } from './lib/entur';
|
||||||
|
|
||||||
let subscriptionClient = new SubscriptionClient('wss://api.entur.io/realtime/v1/vehicles/subscriptions', { reconnect: true})
|
createVehiclesClientContext();
|
||||||
|
createJourneyPlannerClientContext();
|
||||||
|
|
||||||
/*
|
/*
|
||||||
let wsClient = createWsClient({
|
let subscriptionClient = new SubscriptionClient('wss://api.entur.io/realtime/v1/vehicles/subscriptions', { reconnect: true})
|
||||||
url: 'wss://api.entur.io/realtime/v1/vehicles/subscriptions'
|
|
||||||
})
|
|
||||||
*/
|
|
||||||
|
|
||||||
let client = new Client({
|
let client = new Client({
|
||||||
url: 'https://api.entur.io/realtime/v1/vehicles/graphql',
|
url: 'https://api.entur.io/realtime/v1/vehicles/graphql',
|
||||||
|
@ -20,21 +19,13 @@
|
||||||
subscriptionExchange({
|
subscriptionExchange({
|
||||||
forwardSubscription(request) {
|
forwardSubscription(request) {
|
||||||
return subscriptionClient.request(request);
|
return subscriptionClient.request(request);
|
||||||
/*
|
|
||||||
const input = { ...request, query: request.query || "" };
|
|
||||||
return {
|
|
||||||
subscribe(sink) {
|
|
||||||
const unsubscribe = wsClient.subscribe(input, sink);
|
|
||||||
return { unsubscribe };
|
|
||||||
}
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
|
|
||||||
setContextClient(client)
|
setContextClient(client)
|
||||||
|
*/
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<main>
|
<main>
|
||||||
|
|
|
@ -3,10 +3,6 @@
|
||||||
line-height: 1.5;
|
line-height: 1.5;
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
|
|
||||||
color-scheme: light dark;
|
|
||||||
color: rgba(255, 255, 255, 0.87);
|
|
||||||
background-color: #242424;
|
|
||||||
|
|
||||||
font-synthesis: none;
|
font-synthesis: none;
|
||||||
text-rendering: optimizeLegibility;
|
text-rendering: optimizeLegibility;
|
||||||
-webkit-font-smoothing: antialiased;
|
-webkit-font-smoothing: antialiased;
|
||||||
|
|
|
@ -0,0 +1,73 @@
|
||||||
|
<script lang="ts">
|
||||||
|
import { GeoJSON, LineLayer, MarkerLayer } from "svelte-maplibre";
|
||||||
|
import { getJourneyPlannerClientContext, quaysToGeoJSON } from "./entur";
|
||||||
|
import { gql, queryStore } from "@urql/svelte";
|
||||||
|
import type { GeoJSON as IGeoJSON } from "geojson";
|
||||||
|
|
||||||
|
export let lineRef: string | null = null;
|
||||||
|
console.log(lineRef + "hi")
|
||||||
|
|
||||||
|
$: queryResult = queryStore({
|
||||||
|
client: getJourneyPlannerClientContext(),
|
||||||
|
query: gql`
|
||||||
|
query ($line: ID!) {
|
||||||
|
line(id: $line) {
|
||||||
|
publicCode
|
||||||
|
name
|
||||||
|
journeyPatterns {
|
||||||
|
directionType
|
||||||
|
quays {
|
||||||
|
longitude
|
||||||
|
latitude
|
||||||
|
name
|
||||||
|
publicCode
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
variables: { line: lineRef }
|
||||||
|
})
|
||||||
|
|
||||||
|
let data: IGeoJSON;
|
||||||
|
|
||||||
|
$: if ($queryResult.data) {
|
||||||
|
data = quaysToGeoJSON(...$queryResult.data.line.journeyPatterns.map((x: any) => x.quays)) as IGeoJSON
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
h1 {
|
||||||
|
z-index: 1000;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
code {
|
||||||
|
z-index: 100;
|
||||||
|
position: absolute;
|
||||||
|
bottom: 0.5em;
|
||||||
|
left: 5em;
|
||||||
|
right: 5em;
|
||||||
|
max-height: 10em;
|
||||||
|
overflow-y: scroll;
|
||||||
|
background-color: white;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
{#if lineRef != null}
|
||||||
|
<GeoJSON data={data}>
|
||||||
|
<LineLayer
|
||||||
|
|
||||||
|
layout={{ 'line-cap': 'round', 'line-join': 'round' }}
|
||||||
|
paint={{
|
||||||
|
'line-width': 5,
|
||||||
|
'line-dasharray': [5, 2],
|
||||||
|
'line-color': '#008800',
|
||||||
|
'line-opacity': 0.8,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<MarkerLayer applyToClusters={false} let:feature>
|
||||||
|
<span>{feature.properties?.name ?? "N/A"}</span>
|
||||||
|
</MarkerLayer>
|
||||||
|
</GeoJSON>
|
||||||
|
{/if}
|
|
@ -1,18 +1,34 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { Control, ControlButton, ControlGroup, MapLibre, Marker, NavigationControl, Popup, mapContext } from 'svelte-maplibre';
|
import { Control, ControlButton, ControlGroup, LineLayer, MapLibre, Marker, NavigationControl, Popup, mapContext, GeoJSON, MarkerLayer } from 'svelte-maplibre';
|
||||||
import VehicleMarker from "./VehicleMarker.svelte"
|
import VehicleMarker from "./VehicleMarker.svelte"
|
||||||
import {writable} from 'svelte/store';
|
import {writable, type Writable} from 'svelte/store';
|
||||||
import { queryStore, gql, getContextClient, subscriptionStore } from '@urql/svelte';
|
import { queryStore, gql, getContextClient, subscriptionStore } from '@urql/svelte';
|
||||||
|
import { getJourneyPlannerClientContext, getVehiclesClientContext, quaysToGeoJSON } from './entur';
|
||||||
|
import { buses, selectedLine } from './store';
|
||||||
|
import LineMarker from './LineMarker.svelte';
|
||||||
|
import type { GeoJSON as IGeoJSON } from 'geojson';
|
||||||
|
|
||||||
|
interface Vehicle {
|
||||||
|
vehicleId: string;
|
||||||
|
location: {longitude: number, latitude: number};
|
||||||
|
bearing: number;
|
||||||
|
line: {lineRef: string, lineName: string, publicCode: string};
|
||||||
|
originName: string;
|
||||||
|
destinationName: string;
|
||||||
|
occupancy: string;
|
||||||
|
delay: number;
|
||||||
|
vehicleStatus: string;
|
||||||
|
}
|
||||||
|
|
||||||
$: bearing = 0.0;
|
$: bearing = 0.0;
|
||||||
setInterval(() => bearing = 0, 100)
|
setInterval(() => bearing = 0, 100)
|
||||||
|
|
||||||
$: iconSize = 30;
|
$: iconSize = 35;
|
||||||
|
|
||||||
let dataStore = writable({});
|
let dataStore: Writable<Record<string, Vehicle>> = writable({});
|
||||||
|
|
||||||
let vehiclesInit = queryStore({
|
let vehiclesInit = queryStore({
|
||||||
client: getContextClient(),
|
client: getVehiclesClientContext(),
|
||||||
query: gql`
|
query: gql`
|
||||||
query {
|
query {
|
||||||
vehicles(codespaceId:"SKY") {
|
vehicles(codespaceId:"SKY") {
|
||||||
|
@ -41,7 +57,7 @@ import { queryStore, gql, getContextClient, subscriptionStore } from '@urql/svel
|
||||||
|
|
||||||
vehiclesInit.subscribe((v) => {
|
vehiclesInit.subscribe((v) => {
|
||||||
if (v.data) {
|
if (v.data) {
|
||||||
for (const veh of v.data.vehicles.filter(x => ["AT_ORIGIN", "IN_PROGRESS", "OFF_ROUTE"].includes(x.vehicleStatus))) {
|
for (const veh of v.data.vehicles.filter((x: Vehicle) => ["AT_ORIGIN", "IN_PROGRESS", "OFF_ROUTE"].includes(x.vehicleStatus))) {
|
||||||
dataStore.update((d: any) => {
|
dataStore.update((d: any) => {
|
||||||
d[veh.vehicleId] = veh;
|
d[veh.vehicleId] = veh;
|
||||||
return d;
|
return d;
|
||||||
|
@ -51,10 +67,10 @@ vehiclesInit.subscribe((v) => {
|
||||||
})
|
})
|
||||||
|
|
||||||
const vehicles = subscriptionStore({
|
const vehicles = subscriptionStore({
|
||||||
client: getContextClient(),
|
client: getVehiclesClientContext(),
|
||||||
query: gql`
|
query: gql`
|
||||||
subscription {
|
subscription {
|
||||||
vehicles(codespaceId:"SKY") {
|
vehicles(codespaceId: "SKY") {
|
||||||
vehicleId
|
vehicleId
|
||||||
line {
|
line {
|
||||||
lineRef
|
lineRef
|
||||||
|
@ -85,10 +101,44 @@ vehiclesInit.subscribe((v) => {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
$: queryResult = queryStore({
|
||||||
|
client: getJourneyPlannerClientContext(),
|
||||||
|
query: gql`
|
||||||
|
query ($line: ID!) {
|
||||||
|
line(id: $line) {
|
||||||
|
publicCode
|
||||||
|
name
|
||||||
|
journeyPatterns {
|
||||||
|
directionType
|
||||||
|
quays {
|
||||||
|
longitude
|
||||||
|
latitude
|
||||||
|
name
|
||||||
|
publicCode
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
variables: { line: $selectedLine }
|
||||||
|
})
|
||||||
|
|
||||||
|
let data: IGeoJSON;
|
||||||
|
|
||||||
|
$: if ($queryResult.data) {
|
||||||
|
data = quaysToGeoJSON(...$queryResult.data.line.journeyPatterns.map((x: any) => x.quays)) as IGeoJSON
|
||||||
|
}
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
|
div.quay {
|
||||||
|
z-index: -1;
|
||||||
|
padding: 0.1em;
|
||||||
|
background-color: #baddad;
|
||||||
|
border: 1px dotted #333;
|
||||||
|
border-radius: 5px;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
|
@ -112,14 +162,34 @@ vehiclesInit.subscribe((v) => {
|
||||||
<ControlButton on:click={() => iconSize = 20}>S</ControlButton>
|
<ControlButton on:click={() => iconSize = 20}>S</ControlButton>
|
||||||
</ControlGroup>
|
</ControlGroup>
|
||||||
</Control>
|
</Control>
|
||||||
|
<Control position="bottom-right">
|
||||||
|
<ControlGroup>
|
||||||
|
<ControlButton>{$buses}</ControlButton>
|
||||||
|
</ControlGroup>
|
||||||
|
</Control>
|
||||||
|
|
||||||
|
{#if $selectedLine != null}
|
||||||
|
<p id="foo">
|
||||||
|
<GeoJSON {data}>
|
||||||
|
<LineLayer
|
||||||
|
paint={{'line-width': 5, 'line-color': 'orange'}}
|
||||||
|
beforeLayerType={(l) => {console.log(l.type); return false}}
|
||||||
|
/>
|
||||||
|
<MarkerLayer let:feature>
|
||||||
|
<div class="quay">{feature.properties?.name ?? ""}</div>
|
||||||
|
</MarkerLayer>
|
||||||
|
</GeoJSON>
|
||||||
|
</p>
|
||||||
|
{/if}
|
||||||
|
|
||||||
{#if $vehicles}
|
{#if $vehicles}
|
||||||
{#each Object.entries($dataStore) as [id, vehicle]}
|
{#each Object.entries($dataStore) as [id, vehicle]}
|
||||||
<VehicleMarker
|
<VehicleMarker
|
||||||
lngLat={[vehicle.location.longitude, vehicle.location.latitude]}
|
lngLat={[vehicle.location.longitude, vehicle.location.latitude]}
|
||||||
|
lineRef={vehicle.line.lineRef}
|
||||||
lineNumber={vehicle.line.publicCode}
|
lineNumber={vehicle.line.publicCode}
|
||||||
lineName={vehicle.line.lineName}
|
lineName={vehicle.line.lineName}
|
||||||
bearing={vehicle.bearing + map?.getBearing() % 360}
|
bearing={vehicle.bearing}
|
||||||
origin={vehicle.originName}
|
origin={vehicle.originName}
|
||||||
destination={vehicle.destinationName}
|
destination={vehicle.destinationName}
|
||||||
iconSize={iconSize}
|
iconSize={iconSize}
|
||||||
|
|
|
@ -1,7 +1,12 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { Marker, Popup } from 'svelte-maplibre';
|
import { Marker, Popup } from 'svelte-maplibre';
|
||||||
|
import Delay from './micro/Delay.svelte';
|
||||||
|
import Bus from './micro/Bus.svelte';
|
||||||
|
import { buses, selectedLine } from './store';
|
||||||
|
import { onDestroy } from 'svelte';
|
||||||
|
|
||||||
export let lngLat: [number, number];
|
export let lngLat: [number, number];
|
||||||
|
export let lineRef: string | null = null;
|
||||||
export let lineNumber: string;
|
export let lineNumber: string;
|
||||||
export let lineName: string = "N/A";
|
export let lineName: string = "N/A";
|
||||||
export let bearing: number = 0.0;
|
export let bearing: number = 0.0;
|
||||||
|
@ -11,34 +16,43 @@ export let iconSize: number = 30;
|
||||||
export let delay: number = 0;
|
export let delay: number = 0;
|
||||||
export let occupancy: string = "Unknown";
|
export let occupancy: string = "Unknown";
|
||||||
|
|
||||||
console.log("lineNumber: %s", lineNumber)
|
$buses += 1
|
||||||
|
onDestroy(() => {
|
||||||
|
$buses -= 1
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
const clickHandler = () => {
|
||||||
|
if ($selectedLine == lineRef)
|
||||||
|
selectedLine.set(null)
|
||||||
|
else
|
||||||
|
selectedLine.set(lineRef)
|
||||||
|
console.log("click\t%s", $selectedLine)
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
<style>
|
<style>
|
||||||
svg text {
|
|
||||||
font-weight: 600;
|
div.popover-content {
|
||||||
font-size: 1.25rem;
|
|
||||||
color: black;
|
color: black;
|
||||||
background-color: white;;
|
background-color: white;
|
||||||
}
|
}
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<Marker {lngLat}>
|
<Marker {lngLat} on:click={clickHandler}>
|
||||||
<svg width={iconSize} height={iconSize} viewBox="0 0 50 50">
|
<Bus {iconSize} {bearing} face={lineNumber} />
|
||||||
<g transform="rotate({bearing}, 25, 25)">
|
|
||||||
<circle cx="25" cy="25" r="24" stroke="black" stroke-width="3" fill="red" />
|
|
||||||
<circle cx="25" cy="5" r="5" stroke="black" stroke-width="1.5" fill="white" />
|
|
||||||
</g>
|
|
||||||
<text x="25" y="32.5" text-anchor="middle">{lineNumber ?? "🚍"}</text>
|
|
||||||
</svg>
|
|
||||||
|
|
||||||
|
|
||||||
<Popup openOn="hover" offset={[0, -10]}>
|
<Popup openOn="hover" offset={[0, -10]}>
|
||||||
|
<div class="popover-content">
|
||||||
<h4>{lineNumber}: {lineName}</h4>
|
<h4>{lineNumber}: {lineName}</h4>
|
||||||
<p>Going from <i>{origin}</i> to <i>{destination}</i></p>
|
<p>
|
||||||
<ul>
|
Going from <i>{origin}</i> to <i>{destination}</i>
|
||||||
<li>Occupancy: {occupancy}</li>
|
<br />
|
||||||
<li>Delay: {delay}</li>
|
<Delay {delay} />
|
||||||
</ul>
|
<br />
|
||||||
|
{occupancy}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
</Popup>
|
</Popup>
|
||||||
</Marker>
|
</Marker>
|
|
@ -0,0 +1,83 @@
|
||||||
|
import { Client, cacheExchange, fetchExchange, subscriptionExchange } from "@urql/svelte";
|
||||||
|
import { SubscriptionClient } from "subscriptions-transport-ws";
|
||||||
|
import { setContext, getContext } from "svelte";
|
||||||
|
|
||||||
|
|
||||||
|
export function createVehiclesClientContext() {
|
||||||
|
let subscriptionClient = new SubscriptionClient('wss://api.entur.io/realtime/v1/vehicles/subscriptions', { reconnect: true})
|
||||||
|
|
||||||
|
let client = new Client({
|
||||||
|
url: 'https://api.entur.io/realtime/v1/vehicles/graphql',
|
||||||
|
exchanges: [
|
||||||
|
fetchExchange,
|
||||||
|
cacheExchange,
|
||||||
|
subscriptionExchange({
|
||||||
|
forwardSubscription(request) {
|
||||||
|
return subscriptionClient.request(request);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
],
|
||||||
|
})
|
||||||
|
|
||||||
|
setContext("entur-graphql-vehiclepositions-client", client)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getVehiclesClientContext(): Client {
|
||||||
|
return getContext("entur-graphql-vehiclepositions-client")
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createJourneyPlannerClientContext() {
|
||||||
|
let client = new Client({
|
||||||
|
url: 'https://api.entur.io/journey-planner/v3/graphql',
|
||||||
|
exchanges: [
|
||||||
|
fetchExchange,
|
||||||
|
cacheExchange,
|
||||||
|
]
|
||||||
|
})
|
||||||
|
|
||||||
|
setContext("entur-graphql-journeyplannet-client", client)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getJourneyPlannerClientContext(): Client {
|
||||||
|
return getContext("entur-graphql-journeyplannet-client")
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export interface Quay {
|
||||||
|
longitude: number;
|
||||||
|
latitude: number;
|
||||||
|
name: string;
|
||||||
|
publicCode: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function quaysToGeoJSON(...quayCollections: Array<Array<Quay>>) {
|
||||||
|
const multilineString = {
|
||||||
|
type: "Feature",
|
||||||
|
properties: {},
|
||||||
|
geometry: {
|
||||||
|
type: "MultiLineString",
|
||||||
|
coordinates: quayCollections.map((quays => quays.map(quay => [quay.longitude, quay.latitude]))),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
const features = quayCollections.map(quays =>
|
||||||
|
quays.map(quay => {
|
||||||
|
return {
|
||||||
|
type: "Feature",
|
||||||
|
properties: {
|
||||||
|
name: quay.name + (quay.publicCode ? (" " + quay.publicCode) : "")
|
||||||
|
},
|
||||||
|
geometry: {
|
||||||
|
type: "Point",
|
||||||
|
coordinates: [quay.longitude, quay.latitude]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
|
||||||
|
return {
|
||||||
|
type: "FeatureCollection",
|
||||||
|
features: [
|
||||||
|
multilineString,
|
||||||
|
...features.flat(1)
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,28 @@
|
||||||
|
<script lang="ts">
|
||||||
|
export let iconSize = 35;
|
||||||
|
export let bearing = 0;
|
||||||
|
export let face: string = "";
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
svg {
|
||||||
|
z-index: -1000;
|
||||||
|
}
|
||||||
|
text {
|
||||||
|
font-weight: 600;
|
||||||
|
font-size: 1.25rem;
|
||||||
|
color: darkgray;
|
||||||
|
background-color: white;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<svg width={iconSize * 1.2} height={iconSize * 1.2} viewBox="-10 -10 70 70">
|
||||||
|
<g transform="rotate({bearing}, 25, 25)">
|
||||||
|
<!--
|
||||||
|
<circle cx="25" cy="25" r="24" stroke="black" stroke-width="3" fill="red" />
|
||||||
|
<circle cx="25" cy="5" r="5" stroke="black" stroke-width="1.5" fill="white" />
|
||||||
|
-->
|
||||||
|
<path d="M 25 0 l -25 10 v 40 l 25 -10 l 25 10 v -40 l -25 -10" fill="green" stroke="black" stroke-width="3" />
|
||||||
|
</g>
|
||||||
|
<text x="25" y="32.5" text-anchor="middle" fill="white" stroke="black" stroke-width="0.5">{face ?? "🚍"}</text>
|
||||||
|
</svg>
|
|
@ -0,0 +1,22 @@
|
||||||
|
<script lang="ts">
|
||||||
|
/** The delay in seconds.*/
|
||||||
|
export let delay: number;
|
||||||
|
|
||||||
|
let delay_min = Math.round(delay / 60);
|
||||||
|
let delay_abs = Math.abs(delay_min);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
The bus is
|
||||||
|
{#if delay_min == 1}
|
||||||
|
<span>one minute delayed.</span>
|
||||||
|
{:else if delay_min > 1}
|
||||||
|
<span>{delay_abs} minutes delayed.</span>
|
||||||
|
{:else if delay_min == -1}
|
||||||
|
<span>one minute ahead of schedule.</span>
|
||||||
|
{:else if delay_min < -1}
|
||||||
|
<span>{delay_abs} minutes ahead of schedule.</span>
|
||||||
|
{:else}
|
||||||
|
<span>on time.</span>
|
||||||
|
{/if}
|
||||||
|
</div>
|
|
@ -0,0 +1,5 @@
|
||||||
|
import { writable, type Writable } from "svelte/store";
|
||||||
|
|
||||||
|
export const buses = writable(0);
|
||||||
|
|
||||||
|
export const selectedLine: Writable<string|null> = writable(null);
|
|
@ -4,4 +4,11 @@ export default {
|
||||||
// Consult https://svelte.dev/docs#compile-time-svelte-preprocess
|
// Consult https://svelte.dev/docs#compile-time-svelte-preprocess
|
||||||
// for more information about preprocessors
|
// for more information about preprocessors
|
||||||
preprocess: vitePreprocess(),
|
preprocess: vitePreprocess(),
|
||||||
|
vitePlugin: {
|
||||||
|
inspector: {
|
||||||
|
toggleKeyCombo: 'meta-shift',
|
||||||
|
showToggleButton: 'always',
|
||||||
|
toggleButtonPos: 'bottom-right'
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,12 +1,18 @@
|
||||||
import { defineConfig } from 'vite'
|
import { defineConfig } from 'vite'
|
||||||
import { svelte } from '@sveltejs/vite-plugin-svelte'
|
import { svelte } from '@sveltejs/vite-plugin-svelte'
|
||||||
import basicSsl from '@vitejs/plugin-basic-ssl'
|
import basicSsl from '@vitejs/plugin-basic-ssl'
|
||||||
|
import { svelteInspector } from '@sveltejs/vite-plugin-svelte-inspector'
|
||||||
|
|
||||||
// https://vitejs.dev/config/
|
// https://vitejs.dev/config/
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
plugins: [
|
plugins: [
|
||||||
svelte(),
|
svelte(),
|
||||||
//basicSsl(),
|
//basicSsl(),
|
||||||
|
/*svelteInspector({
|
||||||
|
showToggleButton: 'always',
|
||||||
|
toggleButtonPos: 'top-right',
|
||||||
|
openKey: 'meta-shift'
|
||||||
|
})*/
|
||||||
],
|
],
|
||||||
base: "",
|
base: "",
|
||||||
})
|
})
|
||||||
|
|
|
@ -232,7 +232,7 @@
|
||||||
|
|
||||||
"@sveltejs/vite-plugin-svelte-inspector@^1.0.4":
|
"@sveltejs/vite-plugin-svelte-inspector@^1.0.4":
|
||||||
version "1.0.4"
|
version "1.0.4"
|
||||||
resolved "https://registry.npmjs.org/@sveltejs/vite-plugin-svelte-inspector/-/vite-plugin-svelte-inspector-1.0.4.tgz"
|
resolved "https://registry.yarnpkg.com/@sveltejs/vite-plugin-svelte-inspector/-/vite-plugin-svelte-inspector-1.0.4.tgz#c99fcb73aaa845a3e2c0563409aeb3ee0b863add"
|
||||||
integrity sha512-zjiuZ3yydBtwpF3bj0kQNV0YXe+iKE545QGZVTaylW3eAzFr+pJ/cwK8lZEaRp4JtaJXhD5DyWAV4AxLh6DgaQ==
|
integrity sha512-zjiuZ3yydBtwpF3bj0kQNV0YXe+iKE545QGZVTaylW3eAzFr+pJ/cwK8lZEaRp4JtaJXhD5DyWAV4AxLh6DgaQ==
|
||||||
dependencies:
|
dependencies:
|
||||||
debug "^4.3.4"
|
debug "^4.3.4"
|
||||||
|
@ -594,6 +594,11 @@ geojson-vt@^3.2.1:
|
||||||
resolved "https://registry.npmjs.org/geojson-vt/-/geojson-vt-3.2.1.tgz"
|
resolved "https://registry.npmjs.org/geojson-vt/-/geojson-vt-3.2.1.tgz"
|
||||||
integrity sha512-EvGQQi/zPrDA6zr6BnJD/YhwAkBP8nnJ9emh3EnHQKVMfg/MRVtPbMYdgVy/IaEmn4UfagD2a6fafPDL5hbtwg==
|
integrity sha512-EvGQQi/zPrDA6zr6BnJD/YhwAkBP8nnJ9emh3EnHQKVMfg/MRVtPbMYdgVy/IaEmn4UfagD2a6fafPDL5hbtwg==
|
||||||
|
|
||||||
|
geojson@^0.5.0:
|
||||||
|
version "0.5.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/geojson/-/geojson-0.5.0.tgz#3cd6c96399be65b56ee55596116fe9191ce701c0"
|
||||||
|
integrity sha512-/Bx5lEn+qRF4TfQ5aLu6NH+UKtvIv7Lhc487y/c8BdludrCTpiWf9wyI0RTyqg49MFefIAvFDuEi5Dfd/zgNxQ==
|
||||||
|
|
||||||
get-stream@^6.0.1:
|
get-stream@^6.0.1:
|
||||||
version "6.0.1"
|
version "6.0.1"
|
||||||
resolved "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz"
|
resolved "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz"
|
||||||
|
|
Loading…
Reference in New Issue