🚨 Try to make TS linter happy

This commit is contained in:
2023-01-30 22:07:20 +01:00
parent 27f895ce0c
commit 2fd6783534
10 changed files with 242 additions and 152 deletions

View File

@@ -11,6 +11,7 @@
},
"license": "MIT",
"devDependencies": {
"@types/leaflet": "^1.9.0",
"@vitejs/plugin-basic-ssl": "^1.0.1",
"eslint": "^8.32.0",
"eslint-plugin-solid": "^0.9.3",

View File

@@ -1,10 +1,8 @@
import { Component } from 'solid-js';
import { MatrixCapabilities, WidgetApi, WidgetApiToWidgetAction, CustomEvent, IVisibilityActionRequest } from 'matrix-widget-api';
import { IVisibilityActionRequest, MatrixCapabilities, WidgetApi, WidgetApiToWidgetAction } from 'matrix-widget-api';
import { HopeProvider } from "@hope-ui/solid";
import { BusinessDataProvider } from './businessData';
import { SearchProvider } from './search';
import { PassagesDisplay } from './passagesDisplay';
import { StopsManager } from './stopsManager';
@@ -28,7 +26,7 @@ const App: Component = () => {
console.log("App: widgetId:" + widgetId);
console.log("App: userId:" + userId);
const api = new WidgetApi(widgetId);
const api = new WidgetApi(widgetId != null ? widgetId : undefined);
api.requestCapability(MatrixCapabilities.AlwaysOnScreen);
api.start();
api.on("ready", function() {

View File

@@ -1,28 +1,37 @@
import { createContext, createSignal } from 'solid-js';
import { createContext, createSignal, JSX } from 'solid-js';
import { createStore } from 'solid-js/store';
import { Passages, Stops } from './types';
import { Line, Lines, Passage, Passages, Stop, Stops } from './types';
interface Store {
export interface BusinessDataStore {
getLine: (lineId: string) => Promise<Line>;
getLinePassages: (lineId: string) => Record<string, Passage[]>;
passages: () => Passages;
getLinePassages?: (lineId: string) => Passages;
addPassages?: (passages: Passages) => void;
clearPassages?: () => void;
refreshPassages: (stopId: number) => Promise<void>;
addPassages: (passages: Passages) => void;
clearPassages: () => void;
stops: () => Stops;
addStops?: (stops: Stops) => void;
getStop: (stopId: number) => Stop | undefined;
searchStopByName: (name: string) => Promise<Stops>;
};
export const BusinessDataContext = createContext<Store>();
export const BusinessDataContext = createContext<BusinessDataStore>();
export function BusinessDataProvider(props: { children: JSX.Element }) {
const [serverUrl, setServerUrl] = createSignal<string>("https://localhost:4443");
const [serverUrl] = createSignal<string>("https://localhost:4443");
const [store, setStore] = createStore({ lines: {}, passages: {}, stops: {} });
type Store = {
lines: Lines;
passages: Passages;
stops: Stops;
};
const getLine: Line = async (lineId: string) => {
const [store, setStore] = createStore<Store>({ lines: {}, passages: {}, stops: {} });
const getLine = async (lineId: string): Promise<Line> => {
let line = store.lines[lineId];
if (line === undefined) {
console.log(`${lineId} not found... fetch it from backend.`);
@@ -35,15 +44,15 @@ export function BusinessDataProvider(props: { children: JSX.Element }) {
return line;
}
const getLinePassages = (lineId: string) => {
const getLinePassages = (lineId: string): Record<string, Passage[]> => {
return store.passages[lineId];
};
const passages = () => {
const passages = (): Passages => {
return store.passages;
}
const refreshPassages = async (stopId: number) => {
const refreshPassages = async (stopId: number): Promise<void> => {
const httpOptions = { headers: { "Content-Type": "application/json" } };
console.log(`Fetching data for ${stopId}`);
const data = await fetch(`${serverUrl()}/stop/nextPassages/${stopId}`, httpOptions);
@@ -51,31 +60,30 @@ export function BusinessDataProvider(props: { children: JSX.Element }) {
addPassages(response.passages);
}
const addPassages = (passages) => {
setStore((s) => {
const addPassages = (passages: Passages): void => {
setStore('passages', passages);
});
}
const clearPassages = () => {
setStore((s) => {
const clearPassages = (): void => {
setStore((s: Store): Store => {
for (const lineId of Object.keys(s.passages)) {
setStore('passages', lineId, undefined);
}
return s;
});
}
const getStop = (stopId: int) => {
const getStop = (stopId: number): Stop | undefined => {
return store.stops[stopId];
}
const searchStopByName = async (name: string) => {
const searchStopByName = async (name: string): Promise<Stops> => {
const data = await fetch(`${serverUrl()}/stop/?name=${name}`, {
headers: { 'Content-Type': 'application/json' }
});
const stops = await data.json();
const byIdStops = {};
const byIdStops: Stops = {};
for (const stop of stops) {
byIdStops[stop.id] = stop;
setStore('stops', stop.id, stop);
@@ -85,10 +93,22 @@ export function BusinessDataProvider(props: { children: JSX.Element }) {
return (
<BusinessDataContext.Provider value={{
getLine, getLinePassages, passages, refreshPassages, clearPassages,
getStop, searchStopByName
getLine, getLinePassages, passages, refreshPassages, addPassages, clearPassages, getStop, searchStopByName
}}>
{props.children}
</BusinessDataContext.Provider>
);
}
export interface BusinessDataStore {
getLine: (lineId: string) => Promise<Line>;
getLinePassages: (lineId: string) => Record<string, Passage[]>;
passages: () => Passages;
refreshPassages: (stopId: number) => Promise<void>;
addPassages: (passages: Passages) => void;
clearPassages: () => void;
getStop: (stopId: number) => Stop | undefined;
searchStopByName: (name: string) => Promise<Stops>;
};

View File

@@ -1,28 +1,43 @@
import { Component, createEffect, createResource, createSignal, useContext } from "solid-js";
import { createEffect, createResource, createSignal, For, JSX, ParentComponent, Show, useContext, VoidComponent } from "solid-js";
import { createStore } from "solid-js/store";
import { createDateNow } from "@solid-primitives/date";
import { format } from "date-fns";
import { BusinessDataContext } from "./businessData";
import { SearchContext } from "./search";
import { BusinessDataContext, BusinessDataStore } from "./businessData";
import { SearchContext, SearchStore } from "./search";
import { PassagesPanel } from "./passagesPanel";
import { Passage, Passages } from "./types";
import { getTransportModeSrc } from "./utils";
import { PassagesPanel } from "./passagesPanel";
import styles from "./passagesDisplay.module.css";
export const PassagesDisplay: Component = () => {
export const PassagesDisplay: ParentComponent = () => {
const maxPassagePerPanel = 5;
const syncPeriodMsec = 20 * 1000;
const panelSwitchPeriodMsec = 4 * 1000;
const { passages, getLine, getLinePassages, refreshPassages, clearPassages } = useContext(BusinessDataContext);
const businessDataStore: BusinessDataStore | undefined = useContext(BusinessDataContext);
// TODO: Use props instead
const { getDisplayedStops } = useContext(SearchContext);
const searchStore: SearchStore | undefined = useContext(SearchContext);
if (businessDataStore === undefined || searchStore === undefined)
return <div />;
const { passages, getLine, getLinePassages, refreshPassages, clearPassages } = businessDataStore;
const { getDisplayedStops } = searchStore;
const [displayedPanelId, setDisplayedPanelId] = createSignal<number>(0);
const [panels, setPanels] = createStore([]);
type PositionnedPanel = {
position: number;
// TODO: Should be PassagesPanelComponent ?
panel: JSX.Element;
};
const [panels, setPanels] = createStore<PositionnedPanel[]>([]);
const [dateNow] = createDateNow(1000);
@@ -32,7 +47,7 @@ export const PassagesDisplay: Component = () => {
nextPanelId = 0;
}
setDisplayedPanelId(nextPanelId);
}, 4000);
}, panelSwitchPeriodMsec);
createEffect(() => {
console.log("######### onStopIdUpdate #########");
@@ -60,14 +75,22 @@ export const PassagesDisplay: Component = () => {
);
// TODO: Sort transport modes by weight
const Header: Component = (props) => {
const computeTransportModes = async (lineIds: Array<number>) => {
const Header: VoidComponent<{ passages: Passages, title: string }> = (props) => {
const computeTransportModes = async (lineIds: string[]): Promise<string[]> => {
const lines = await Promise.all(lineIds.map((lineId) => getLine(lineId)));
return new Set(lines.map((line) => getTransportModeSrc(line.transportMode, false)));
const urls: Set<string> = new Set();
for (const line of lines) {
const src = getTransportModeSrc(line.transportMode, false);
if (src !== undefined) {
urls.add(src);
}
}
return Array.from(urls);
}
const [linesIds, setLinesIds] = createSignal([]);
const [transportModeUrls] = createResource(linesIds, computeTransportModes);
const [linesIds, setLinesIds] = createSignal<string[]>([]);
const [transportModeUrls] = createResource<string[], string[]>(linesIds, computeTransportModes);
createEffect(() => {
setLinesIds(Object.keys(props.passages));
@@ -76,7 +99,7 @@ export const PassagesDisplay: Component = () => {
return (
<div class={styles.header}>
<Show when={transportModeUrls() !== undefined} >
<For each={Array.from(transportModeUrls())}>
<For each={transportModeUrls()}>
{(url) =>
<div class={styles.transportMode}>
<img src={url} />
@@ -85,14 +108,14 @@ export const PassagesDisplay: Component = () => {
</For>
</Show>
<div class={styles.title}>
<svg viewbox="0 0 1260 50">
<svg viewBox="0 0 1260 50">
<text x="0" y="50%" dominant-baseline="middle" font-size="50" style="fill: #ffffff">
{props.title}
</text>
</svg>
</div>
<div class={styles.clock}>
<svg viewbox="0 0 115 43">
<svg viewBox="0 0 115 43">
<text x="50%" y="55%" dominant-baseline="middle" text-anchor="middle" font-size="43" style="fill: #ffffff">
{format(dateNow(), "HH:mm")}
</text>
@@ -102,12 +125,12 @@ export const PassagesDisplay: Component = () => {
);
};
const Footer: Component = (props) => {
const Footer: VoidComponent<{ panels: PositionnedPanel[] }> = (props) => {
return (
<div class={styles.footer}>
<For each={props.panels}>
{(positioned) => {
const { position } = positioned;
{(panel) => {
const position = panel.position;
return (
<div>
<svg viewBox="0 0 29 29">
@@ -131,10 +154,10 @@ export const PassagesDisplay: Component = () => {
setPanels([]);
let newPanels = [];
let positioneds = [];
let positioneds: PositionnedPanel[] = [];
let index = 0;
let chunk = {};
let chunk: Record<string, Record<string, Passage[]>> = {};
let chunkSize = 0;
console.log("passages=", passages());
@@ -154,7 +177,7 @@ export const PassagesDisplay: Component = () => {
const panelid = index++;
const panel = <PassagesPanel show={panelid == displayedPanelId()} passages={store} />;
newPanels.push(panel);
positioneds.push({ position: panelid, panel });
positioneds.push({ position: panelid, panel: panel });
chunk = {};
chunk[lineId] = byLinePassages;
@@ -166,7 +189,7 @@ export const PassagesDisplay: Component = () => {
const [store] = createStore(chunk);
const panel = <PassagesPanel show={panelId == displayedPanelId()} passages={store} />;
newPanels.push(panel);
positioneds.push({ position: panelId, panel });
positioneds.push({ position: panelId, panel: panel });
}
setPanels(positioneds);

View File

@@ -1,16 +1,16 @@
import { Component, createEffect, createResource, createSignal, useContext } from 'solid-js';
import { VoidComponent, createEffect, createResource, createSignal, ParentComponent, ParentProps, Show, useContext } from 'solid-js';
import { createDateNow, getTime } from '@solid-primitives/date';
import { AnimationOptions } from '@motionone/types';
import { Motion } from "@motionone/solid";
import { TrafficStatus } from './types';
import { Line, Passage, Passages, TrafficStatus } from './types';
import { renderLineTransportMode, renderLinePicto } from './utils';
import { BusinessDataContext, BusinessDataStore } from "./businessData";
import { BusinessDataContext } from "./businessData";
import styles from "./passagesPanel.module.css";
import styles from './passagesPanel.module.css';
const TtwPassage: Component = (props) => {
const TtwPassage: VoidComponent<{ passage: Passage, style: string, fontSize: number }> = (props) => {
const [dateNow] = createDateNow(5000);
@@ -18,6 +18,7 @@ const TtwPassage: Component = (props) => {
const ttwSec = refTs - (getTime(dateNow()) / 1000);
const isApproaching = ttwSec <= 60;
const transition: AnimationOptions = { duration: 3, repeat: Infinity };
return (
<div class={props.style}>
<svg viewBox={`0 0 215 ${props.fontSize}`}>
@@ -25,9 +26,9 @@ const TtwPassage: Component = (props) => {
x="100%" y="55%"
dominant-baseline="middle" text-anchor="end"
font-size={props.fontSize} style={{ fill: "#000000" }}
initial={isApproaching}
initial={isApproaching ? undefined : false}
animate={{ opacity: [1, 0, 1] }}
transition={{ duration: 3, repeat: Infinity }}>
transition={transition}>
{Math.floor(ttwSec / 60)} min
</Motion.text>
</svg>
@@ -35,12 +36,12 @@ const TtwPassage: Component = (props) => {
);
}
const UnavailablePassage: Component = (props) => {
const UnavailablePassage: VoidComponent<{ style: string }> = (props) => {
const textStyle = { fill: "#000000" };
return (
<div class={props.style}>
<svg viewbox="0 0 230 110">
<svg viewBox="0 0 230 110">
<text x="100%" y="26" font-size="25" text-anchor="end" style={textStyle}>Information</text>
<text x="100%" y="63" font-size="25" text-anchor="end" style={textStyle}>non</text>
<text x="100%" y="100" font-size="25" text-anchor="end" style={textStyle}>disponible</text>
@@ -50,7 +51,7 @@ const UnavailablePassage: Component = (props) => {
}
/* TODO: Manage end of service */
const Passages: Component = (props) => {
const DestinationPassages: VoidComponent<{ passages: Passage[], line: Line, destination: string }> = (props) => {
/* TODO: Find where to get data to compute traffic status. */
const trafficStatusColor = new Map<TrafficStatus, string>([
@@ -64,7 +65,10 @@ const Passages: Component = (props) => {
const passagesLength = props.passages.length;
const firstPassage = passagesLength > 0 ? props.passages[0] : undefined;
const secondPassage = passagesLength > 1 ? props.passages[1] : undefined;
const trafficStatusStyle = { fill: trafficStatusColor.get(props.line.trafficStatus) };
// TODO: Manage traffic status
// const trafficStatusStyle = { fill: trafficStatusColor.get(props.line.trafficStatus) };
const trafficStatusStyle = { fill: trafficStatusColor.get(TrafficStatus.UNKNOWN) };
return (
<div class={styles.line}>
@@ -73,7 +77,7 @@ const Passages: Component = (props) => {
</div>
{renderLinePicto(props.line, styles)}
<div class={styles.destination}>
<svg viewbox="0 0 600 40">
<svg viewBox="0 0 600 40">
<text x="0" y="50%" dominant-baseline="middle" font-size="40" style={{ fill: "#000000" }}>
{props.destination}
</text>
@@ -85,33 +89,39 @@ const Passages: Component = (props) => {
</svg>
</div>
<Show when={firstPassage !== undefined} fallback=<UnavailablePassage style={styles.unavailableFirstPassage} />>
<TtwPassage style={styles.firstPassage} passage={firstPassage} fontSize="50" />
<TtwPassage style={styles.firstPassage} passage={firstPassage} fontSize={50} />
</Show>
<Show when={secondPassage !== undefined} fallback=<UnavailablePassage style={styles.unavailableSecondPassage} />>
<TtwPassage style={styles.secondPassage} passage={secondPassage} fontSize="45" />
<TtwPassage style={styles.secondPassage} passage={secondPassage} fontSize={45} />
</Show>
</div >
);
}
export const PassagesPanel: Component = (props) => {
export type PassagesPanelComponentProps = ParentProps & { passages: Passages, show: boolean };
export type PassagesPanelComponent = ParentComponent<PassagesPanelComponentProps>;
export const PassagesPanel: PassagesPanelComponent = (props) => {
const { getLine } = useContext(BusinessDataContext);
const businessDataContext: BusinessDataStore | undefined = useContext(BusinessDataContext);
if (businessDataContext === undefined)
return <div />;
const getLines = async (lineIds: Array<number>) => {
const lines = await Promise.all(lineIds.map((lineId) => getLine(lineId)));
const { getLine } = businessDataContext;
const getLines = async (lineIds: string[]): Promise<Line[]> => {
const lines = await Promise.all<Promise<Line>[]>(lineIds.map((lineId) => getLine(lineId)));
return lines;
}
const [lineIds, setLinesIds] = createSignal([]);
const [lines] = createResource(lineIds, getLines);
const [lineIds, setLinesIds] = createSignal<string[]>([]);
const [lines] = createResource<Line[], string[]>(lineIds, getLines);
createEffect(async () => {
setLinesIds(Object.keys(props.passages));
});
return (
<div classList={{ [styles.passagesContainer]: true, [styles.displayed]: props.show }} style={{ "top": `${100 * props.position}%` }}>
<div classList={{ [styles.passagesContainer]: true, [styles.displayed]: props.show }} >
<Show when={lines() !== undefined} >
{() => {
const ret = [];
@@ -119,7 +129,7 @@ export const PassagesPanel: Component = (props) => {
const byLinePassages = props.passages[line.id];
if (byLinePassages !== undefined) {
for (const destination of Object.keys(byLinePassages)) {
ret.push(<Passages passages={byLinePassages[destination]} line={line} destination={destination} />);
ret.push(<DestinationPassages passages={byLinePassages[destination]} line={line} destination={destination} />);
}
}
}

View File

@@ -1,35 +1,39 @@
import { batch, createContext } from 'solid-js';
import { createContext, JSX } from 'solid-js';
import { createStore } from 'solid-js/store';
import { Marker as LeafletMarker } from 'leaflet';
import { Stop, Stops } from './types';
import { Stop } from './types';
interface Store {
getMarkers: () => Markers;
addMarkers?: (stopId, markers) => void;
setMarkers?: (markers) => void;
export type ByStopIdMarkers = Record<number, LeafletMarker[] | undefined>;
getStops: () => Stops;
setStops?: (stops) => void;
removeStops?: (stopIds: Array<number>) => void;
export interface SearchStore {
getDisplayedStops: () => Stop[];
setDisplayedStops: (stops: Stop[]) => void;
getDisplayedStops: () => Array<Stop>;
setDisplayedStops: (stops: Array<Stop>) => void;
addMarkers: (stopId: number, markers: LeafletMarker[]) => void;
};
export const SearchContext = createContext<Store>();
export const SearchContext = createContext<SearchStore>();
export function SearchProvider(props: { children: JSX.Element }) {
const [store, setStore] = createStore({ stops: {}, markers: {}, displayedStops: [] });
type Store = {
stops: Record<number, Stop | undefined>;
markers: ByStopIdMarkers;
displayedStops: Stop[];
};
const getDisplayedStops = () => {
const [store, setStore] = createStore<Store>({ stops: {}, markers: {}, displayedStops: [] });
const getDisplayedStops = (): Stop[] => {
return store.displayedStops;
}
const setDisplayedStops = (stops: Array<Stop>) => {
setStore((s) => {
const setDisplayedStops = (stops: Stop[]): void => {
setStore((s: Store) => {
setStore('displayedStops', stops);
return s;
});
}
@@ -46,7 +50,7 @@ export function SearchProvider(props: { children: JSX.Element }) {
return store.markers;
}
const addMarkers = (stopId: number, markers) => {
const addMarkers = (stopId: number, markers: L.Marker[]): void => {
setStore('markers', stopId, markers);
}

View File

@@ -1,11 +1,15 @@
import { Component, createEffect, createResource, createSignal, onMount, Show, useContext } from 'solid-js';
import { createEffect, createResource, createSignal, For, JSX, onMount, Show, useContext, VoidComponent } from 'solid-js';
import { Box, Button, Input, InputLeftAddon, InputGroup, HStack, List, ListItem, Progress, ProgressIndicator, VStack } from "@hope-ui/solid";
import 'leaflet/dist/leaflet.css';
import L from 'leaflet';
import { BusinessDataContext } from './businessData';
import { SearchContext } from './search';
import {
featureGroup as leafletFeatureGroup, LatLngLiteral as LeafletLatLngLiteral, Map as LeafletMap,
Marker as LeafletMarker, tileLayer as leafletTileLayer
} from 'leaflet';
import { BusinessDataContext, BusinessDataStore } from "./businessData";
import { SearchContext, SearchStore } from './search';
import { Stop } from './types';
import { renderLineTransportMode, renderLinePicto, TransportModeWeights } from './utils';
@@ -13,13 +17,15 @@ import { renderLineTransportMode, renderLinePicto, TransportModeWeights } from '
import styles from './stopManager.module.css';
const StopRepr: Component = (props) => {
const StopRepr: VoidComponent<{ stop: Stop }> = (props) => {
const { getLine } = useContext(BusinessDataContext);
const businessDataStore: BusinessDataStore | undefined = useContext(BusinessDataContext);
if (businessDataStore === undefined)
return <div />;
const [lineReprs] = createResource(props.stop.lines, fetchLinesRepr);
const { getLine } = businessDataStore;
const fetchLinesRepr = async (lineIds: Array<string>) => {
const fetchLinesRepr = async (lineIds: string[]): Promise<JSX.Element[]> => {
const reprs = [];
for (const lineId of lineIds) {
const line = await getLine(lineId);
@@ -31,26 +37,37 @@ const StopRepr: Component = (props) => {
return reprs;
}
const [lineReprs] = createResource<JSX.Element[], string[]>(props.stop.lines, fetchLinesRepr);
return (
<HStack height="100%">
{props.stop.name}
<For each={lineReprs()}>{(line) => line}</For>
<For each={lineReprs()}>{(line: JSX.Element) => line}</For>
</HStack>
);
}
const StopAreaRepr: Component = (props) => {
const StopAreaRepr: VoidComponent<{ stop: Stop }> = (props) => {
const { getLine } = useContext(BusinessDataContext);
const businessDataStore: BusinessDataStore | undefined = useContext(BusinessDataContext);
if (businessDataStore === undefined)
return <div />;
const fetchLinesRepr = async (stop: Stop) => {
const { getLine } = businessDataStore;
type ByTransportModeReprs = {
mode: JSX.Element | undefined;
[key: string]: JSX.Element | JSX.Element[] | undefined;
};
const fetchLinesRepr = async (stop: Stop): Promise<JSX.Element[]> => {
const lineIds = new Set(stop.lines);
const stops = stop.stops;
for (const stop of stops) {
stop.lines.forEach(lineIds.add, lineIds);
}
const byModeReprs = {};
const byModeReprs: Record<string, ByTransportModeReprs> = {};
for (const lineId of lineIds) {
const line = await getLine(lineId);
if (line !== undefined) {
@@ -64,7 +81,7 @@ const StopAreaRepr: Component = (props) => {
}
const reprs = [];
const sortedTransportModes = Object.keys(byModeReprs).sort((x, y) => TransportModeWeights[x] < TransportModeWeights[y]);
const sortedTransportModes = Object.keys(byModeReprs).sort((x, y) => TransportModeWeights[x] < TransportModeWeights[y] ? 1 : -1);
for (const transportMode of sortedTransportModes) {
const lines = byModeReprs[transportMode];
const repr = [lines.mode];
@@ -88,29 +105,34 @@ const StopAreaRepr: Component = (props) => {
}
const Map: Component = (props) => {
const Map: VoidComponent<{ stops: Stop[] }> = (props) => {
const mapCenter = [48.853, 2.35];
const mapCenter: LeafletLatLngLiteral = { lat: 48.853, lng: 2.35 };
const searchStore: SearchStore | undefined = useContext(SearchContext);
if (searchStore === undefined)
return <div />;
const { addMarkers } = searchStore;
const { addMarkers } = useContext(SearchContext);
let mapDiv: any;
let map = null;
const stopsLayerGroup = L.featureGroup();
let map: LeafletMap | undefined = undefined;
const stopsLayerGroup = leafletFeatureGroup();
const buildMap = (div: HTMLDivElement) => {
map = L.map(div).setView(mapCenter, 11);
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
map = new LeafletMap(div).setView(mapCenter, 11);
leafletTileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
attribution: '&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
}).addTo(map);
stopsLayerGroup.addTo(map);
}
const setMarker = (stop: Stop): Array<L.Marker> => {
const setMarker = (stop: Stop): L.Marker[] => {
const markers = [];
if (stop.lat !== undefined && stop.lon !== undefined) {
/* TODO: Add stop lines representation to popup. */
markers.push(L.marker([stop.lat, stop.lon]).bindPopup(`${stop.name}`).openPopup());
markers.push(new LeafletMarker([stop.lat, stop.lon]).bindPopup(`${stop.name}`).openPopup());
}
else {
for (const _stop of stop.stops) {
@@ -135,7 +157,7 @@ const Map: Component = (props) => {
}
const stopsBound = stopsLayerGroup.getBounds();
if (Object.keys(stopsBound).length) {
if (map !== undefined && Object.keys(stopsBound).length) {
map.fitBounds(stopsBound);
}
});
@@ -143,18 +165,26 @@ const Map: Component = (props) => {
return <div ref={mapDiv} id='main-map' style={{ width: "100%", height: "100%" }} />;
}
export const StopsManager: Component = () => {
export const StopsManager: VoidComponent = () => {
const businessDataStore: BusinessDataStore | undefined = useContext(BusinessDataContext);
const searchStore: SearchStore | undefined = useContext(SearchContext);
if (businessDataStore === undefined || searchStore === undefined)
return <div />;
const { searchStopByName } = businessDataStore;
const { setDisplayedStops } = searchStore;
// TODO: Use props instead
const [minCharactersNb] = createSignal<number>(4);
const [minCharactersNb, setMinCharactersNb] = createSignal<number>(4);
const [inProgress, setInProgress] = createSignal<boolean>(false);
const [foundStops, setFoundStops] = createSignal<Array<number>>([]);
const [foundStops, setFoundStops] = createSignal<Stop[]>([]);
const { getStop, searchStopByName } = useContext(BusinessDataContext);
const { setDisplayedStops } = useContext(SearchContext);
const onStopNameInput = async (event) => {
const onStopNameInput: JSX.EventHandler<HTMLInputElement, InputEvent> = async (event): Promise<void> => {
/* TODO: Add a tempo before fetching stop for giving time to user to finish his request */
const stopName = event.target.value;
const stopName = event.currentTarget.value;
if (stopName.length >= minCharactersNb()) {
console.log(`Fetching data for ${stopName}`);
setInProgress(true);

View File

@@ -6,12 +6,10 @@ export enum TrafficStatus {
BYPASSED
}
export class Passages { };
export class Passage {
line: number;
operator: string;
destinations: Array<string>;
destinations: string[];
atStop: boolean;
aimedArrivalTs: number;
expectedArrivalTs: number;
@@ -21,7 +19,7 @@ export class Passage {
arrivalStatus: string;
departStatus: string;
constructor(line: number, operator: string, destinations: Array<string>, atStop: boolean, aimedArrivalTs: number,
constructor(line: number, operator: string, destinations: string[], atStop: boolean, aimedArrivalTs: number,
expectedArrivalTs: number, arrivalPlatformName: string, aimedDepartTs: number, expectedDepartTs: number,
arrivalStatus: string, departStatus: string) {
this.line = line;
@@ -38,7 +36,7 @@ export class Passage {
}
};
export class Stops { };
export type Passages = Record<string, Record<string, Passage[]>>;
export class Stop {
id: number;
@@ -46,10 +44,10 @@ export class Stop {
town: string;
lat: number;
lon: number;
stops: Array<Stop>;
lines: Array<string>;
stops: Stop[];
lines: string[];
constructor(id: number, name: string, town: string, lat: number, lon: number, stops: Array<Stop>, lines: Array<string>) {
constructor(id: number, name: string, town: string, lat: number, lon: number, stops: Stop[], lines: string[]) {
this.id = id;
this.name = name;
this.town = town;
@@ -63,6 +61,8 @@ export class Stop {
}
};
export type Stops = Record<number, Stop>;
export class Line {
id: string;
shortName: string;
@@ -75,11 +75,11 @@ export class Line {
accessibility: boolean;
visualSignsAvailable: string; // TODO: Use an enum
audibleSignsAvailable: string; // TODO: Use an enum
stopIds: Array<number>;
stopIds: number[];
constructor(id: string, shortName: string, name: string, status: string, transportMode: string, backColorHexa: string,
foreColorHexa: string, operatorId: number, accessibility: boolean, visualSignsAvailable: string,
audibleSignsAvailable: string, stopIds: Array<number>) {
audibleSignsAvailable: string, stopIds: number[]) {
this.id = id;
this.shortName = shortName;
this.name = name;
@@ -94,3 +94,5 @@ export class Line {
this.stopIds = stopIds;
}
};
export type Lines = Record<string, Line>;

View File

@@ -1,8 +1,10 @@
import { JSX } from 'solid-js';
import { Line } from './types';
const validTransportModes = ["bus", "tram", "metro", "rer", "transilien", "funicular", "ter", "unknown"];
export const TransportModeWeights = {
export const TransportModeWeights: Record<string, number> = {
bus: 1,
tram: 2,
val: 3,
@@ -13,20 +15,19 @@ export const TransportModeWeights = {
ter: 8,
};
export function renderLineTransportMode(line: Line): JSX.Element {
return <img src={getTransportModeSrc(line.transportMode)} />
}
export function getTransportModeSrc(mode: string, color: boolean = true): string | null {
let ret = null;
export function getTransportModeSrc(mode: string, color: boolean = true): string | undefined {
let ret = undefined;
if (validTransportModes.includes(mode)) {
ret = `/public/symbole_${mode}_${color ? "" : "support_fonce_"}RVB.svg`;
}
return ret;
}
export function renderLineTransportMode(line: Line): JSX.Element {
return <img src={getTransportModeSrc(line.transportMode)} />
}
function renderBusLinePicto(line: Line, styles): JSX.Element {
function renderBusLinePicto(line: Line, styles: CSSModuleClasses): JSX.Element {
return (
<div class={styles.busLinePicto}>
<svg viewBox="0 0 31.5 14">
@@ -44,7 +45,7 @@ function renderBusLinePicto(line: Line, styles): JSX.Element {
);
}
function renderTramLinePicto(line: Line, styles): JSX.Element {
function renderTramLinePicto(line: Line, styles: CSSModuleClasses): JSX.Element {
const lineStyle = { fill: `#${line.backColorHexa}` };
return (
<div class={styles.tramLinePicto}>
@@ -64,10 +65,10 @@ function renderTramLinePicto(line: Line, styles): JSX.Element {
);
}
function renderMetroLinePicto(line: Line, styles): JSX.Element {
function renderMetroLinePicto(line: Line, styles: CSSModuleClasses): JSX.Element {
return (
<div class={styles.metroLinePicto}>
<svg viewbox="0 0 20 20">
<svg viewBox="0 0 20 20">
<circle cx="10" cy="10" r="10" style={{ fill: `#${line.backColorHexa}` }} />
<text x="50%"
y="55%"
@@ -81,10 +82,10 @@ function renderMetroLinePicto(line: Line, styles): JSX.Element {
);
}
function renderTrainLinePicto(line: Line, styles): JSX.Element {
function renderTrainLinePicto(line: Line, styles: CSSModuleClasses): JSX.Element {
return (
<div class={styles.trainLinePicto}>
<svg viewbox="0 0 20 20">
<svg viewBox="0 0 20 20">
<rect x="0" y="0" width="20" height="20" rx="4.5" ry="4.5" style={{ fill: `#${line.backColorHexa}` }} />
<text x="50%"
y="55%"
@@ -99,7 +100,7 @@ function renderTrainLinePicto(line: Line, styles): JSX.Element {
);
}
export function renderLinePicto(line: Line, styles): JSX.Element {
export function renderLinePicto(line: Line, styles: CSSModuleClasses): JSX.Element {
switch (line.transportMode) {
case "bus":
case "funicular":

View File

@@ -15,7 +15,8 @@
{
"name": "typescript-eslint-language-service"
}
]
],
"lib": ["ES2021", "DOM"],
},
"include": ["src"]
}