import { batch, createContext, createSignal, JSX } from 'solid-js'; import { createStore } from 'solid-js/store'; import { Line, Lines, Passage, Passages, Stop, StopShape, StopShapes, Stops } from './types'; export type StopDestinations = Record; export interface BusinessDataStore { getLine: (lineId: string) => Promise; getLinePassages: (lineId: string) => Record; getLineDestinations: (lineId: string) => string[]; getDestinationPassages: (lineId: string, destination: string) => Passage[]; passages: () => Passages; getPassagesLineIds: () => string[]; refreshPassages: (stopId: number) => Promise; addPassages: (passages: Passages) => void; clearPassages: () => void; getStop: (stopId: number) => Stop | undefined; searchStopByName: (name: string) => Promise; getStopDestinations: (stopId: number) => Promise; getStopShape: (stopId: number) => Promise; }; export const BusinessDataContext = createContext(); export function BusinessDataProvider(props: { children: JSX.Element }) { const [serverUrl] = createSignal("https://localhost:4443"); type Store = { lines: Lines; passages: Passages; stops: Stops; stopShapes: StopShapes; }; const [store, setStore] = createStore({ lines: {}, passages: {}, stops: {}, stopShapes: {} }); const getLine = async (lineId: string): Promise => { let line = store.lines[lineId]; if (line === undefined) { console.log(`${lineId} not found... fetch it from backend.`); const response = await fetch(`${serverUrl()}/line/${lineId}`, { headers: { 'Content-Type': 'application/json' } }); const json = await response.json(); if (response.ok) { setStore('lines', lineId, json); line = json; } else { console.warn(`No line found for ${lineId} line id:`, json); } } return line; } const getLinePassages = (lineId: string): Record => { return store.passages[lineId]; } const getLineDestinations = (lineId: string): string[] => { return Object.keys(store.passages[lineId]); } // TODO: Remove this method: it's based on the next passages and return nothing until the refreshPassages is called. const getDestinationPassages = (lineId: string, destination: string): Passage[] => { return store.passages[lineId][destination]; } const passages = (): Passages => { return store.passages; } const getPassagesLineIds = (): string[] => { return Object.keys(store.passages); } const _cleanupPassages = (passages: Passages): void => { const deadline = Math.floor(Date.now() / 1000) - 60; for (const linePassages of Object.values(passages)) { for (const destination of Object.keys(linePassages)) { const destinationPassages = linePassages[destination]; const cleaned = []; for (const passage of destinationPassages) { if (passage.expectedDepartTs > deadline) { cleaned.push(passage); } } linePassages[destination] = cleaned; } } } const refreshPassages = async (stopId: number): Promise => { console.log(`Fetching data for ${stopId}`); const httpOptions = { headers: { "Content-Type": "application/json" } }; const response = await fetch(`${serverUrl()}/stop/${stopId}/nextPassages`, httpOptions); const json = await response.json(); if (response.ok) { _cleanupPassages(json.passages); addPassages(json.passages); } else { console.warn(`No passage found for ${stopId} stop:`, json); } } const addPassages = (passages: Passages): void => { batch(() => { const storePassages = store.passages; for (const lineId of Object.keys(passages)) { const newLinePassages = passages[lineId]; const linePassages = storePassages[lineId]; if (linePassages === undefined) { setStore('passages', lineId, newLinePassages); } else { for (const destination of Object.keys(newLinePassages)) { const newLinePassagesDestination = newLinePassages[destination]; const linePassagesDestination = linePassages[destination]; if (linePassagesDestination === undefined) { setStore('passages', lineId, destination, newLinePassagesDestination); } else { if (linePassagesDestination.length - newLinePassagesDestination.length != 0) { console.log(`Server provides ${newLinePassagesDestination.length} passages, \ ${linePassagesDestination.length} here... refresh all them.`); setStore('passages', lineId, destination, newLinePassagesDestination); } else { linePassagesDestination.forEach((passage, index) => { const newPassage = newLinePassagesDestination[index]; if (passage.expectedDepartTs != newPassage.expectedDepartTs) { console.log(`Refresh expectedDepartTs (${passage.expectedDepartTs} -> ${newPassage.expectedDepartTs}`); setStore('passages', lineId, destination, index, 'expectedDepartTs', newPassage.expectedDepartTs); } if (passage.expectedArrivalTs != newPassage.expectedArrivalTs) { console.log(`Refresh expectedArrivalTs (${passage.expectedArrivalTs} -> ${newPassage.expectedArrivalTs}`); setStore('passages', lineId, destination, index, 'expectedArrivalTs', newPassage.expectedArrivalTs); } }); } } } } } }); } const clearPassages = (): void => { setStore((s: Store): Store => { for (const lineId of Object.keys(s.passages)) { setStore('passages', lineId, undefined); } return s; }); } const getStop = (stopId: number): Stop | undefined => { return store.stops[stopId]; } const searchStopByName = async (name: string): Promise => { const byIdStops: Stops = {}; const response = await fetch(`${serverUrl()}/stop/?name=${name}`, { headers: { 'Content-Type': 'application/json' } }); const json = await response.json(); if (response.ok) { for (const stop of json) { byIdStops[stop.id] = stop; setStore('stops', stop.id, stop); if (stop.stops !== undefined) { for (const innerStop of stop.stops) { setStore('stops', innerStop.id, innerStop); } } } } else { console.warn(`No stop found for '${name}' query:`, json); } return byIdStops; } const getStopDestinations = async (stopId: number): Promise => { const response = await fetch(`${serverUrl()}/stop/${stopId}/destinations`, { headers: { 'Content-Type': 'application/json' } }); const destinations = response.ok ? await response.json() : undefined; return destinations; } const getStopShape = async (stopId: number): Promise => { let shape = store.stopShapes[stopId]; if (shape === undefined) { console.log(`No shape found for ${stopId} stop... fetch it from backend.`); const response = await fetch(`${serverUrl()}/stop/${stopId}/shape`, { headers: { 'Content-Type': 'application/json' } }); const json = await response.json(); if (response.ok) { setStore('stopShapes', stopId, json); shape = json; } else { console.warn(`No shape found for ${stopId} stop:`, json); } } return shape; } return ( {props.children} ); } export interface BusinessDataStore { getLine: (lineId: string) => Promise; getLinePassages: (lineId: string) => Record; passages: () => Passages; refreshPassages: (stopId: number) => Promise; addPassages: (passages: Passages) => void; clearPassages: () => void; getStop: (stopId: number) => Stop | undefined; searchStopByName: (name: string) => Promise; };