import { createContext, 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 { IconButton, Menu, MenuTrigger, MenuContent, MenuItem } from "@hope-ui/solid"; import { format } from "date-fns"; import { BusinessDataContext, BusinessDataStore } from "./businessData"; import { AppContextContext, AppContextStore } from "./appContext"; import { getTransportModeSrc, PositionedPanel } from "./utils"; import { PassagesPanel } from "./passagesPanel"; import { IconHamburgerMenu } from './extra/iconHamburgerMenu'; import "./passagesDisplay.scss"; interface PassagesDisplayStore { isPassagesRefreshEnabled: () => boolean; enablePassagesRefresh: () => void; disablePassagesRefresh: () => void; togglePassagesRefresh: () => void; getPanels: () => PositionedPanel[]; setPanels: (panels: PositionedPanel[]) => void; getDisplayedPanelId: () => number; setDisplayedPanelId: (panelId: number) => void; }; const PassagesDisplayContext = createContext(); function PassagesDisplayProvider(props: { children: JSX.Element }) { type Store = { refreshEnabled: boolean; panels: PositionedPanel[]; displayedPanelId: number; }; const [store, setStore] = createStore({ refreshEnabled: true, panels: [], displayedPanelId: 0 }); const isPassagesRefreshEnabled = (): boolean => { return store.refreshEnabled; } const enablePassagesRefresh = (): void => { setStore('refreshEnabled', true); } const disablePassagesRefresh = (): void => { setStore('refreshEnabled', false); } const togglePassagesRefresh = (): void => { setStore('refreshEnabled', !store.refreshEnabled); } const getPanels = (): PositionedPanel[] => { return store.panels; } const setPanels = (panels: PositionedPanel[]): void => { setStore('panels', panels); } const getDisplayedPanelId = (): number => { return store.displayedPanelId; } const setDisplayedPanelId = (panelId: number): void => { setStore('displayedPanelId', panelId); } return ( {props.children} ); } // TODO: Sort transport modes by weight const Header: VoidComponent<{ title: string }> = (props) => { const businessDataStore: BusinessDataStore | undefined = useContext(BusinessDataContext); const passagesDisplayStore: PassagesDisplayStore | undefined = useContext(PassagesDisplayContext); if (businessDataStore === undefined || passagesDisplayStore === undefined) return
; const { getLine, passages } = businessDataStore; const { isPassagesRefreshEnabled, togglePassagesRefresh } = passagesDisplayStore; const [dateNow] = createDateNow(1000); const computeTransportModes = async (lineIds: string[]): Promise => { const lines = await Promise.all(lineIds.map((lineId) => getLine(lineId))); const urls: Set = 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); createEffect(() => { setLinesIds(Object.keys(passages())); }); return (
{(url) =>
}
{props.title}
{format(dateNow(), "HH:mm")}
); }; const Footer: VoidComponent<{}> = () => { const passagesDisplayStore: PassagesDisplayStore | undefined = useContext(PassagesDisplayContext); if (passagesDisplayStore === undefined) return
; const { getDisplayedPanelId, getPanels } = passagesDisplayStore; return ( ); } const Body: ParentComponent<{ maxPassagesPerPanel: number, syncPeriodMsec: number, panelSwitchPeriodMsec: number }> = (props) => { const businessDataStore: BusinessDataStore | undefined = useContext(BusinessDataContext); const appContextStore: AppContextStore | undefined = useContext(AppContextContext); const passagesDisplayStore: PassagesDisplayStore | undefined = useContext(PassagesDisplayContext); if (businessDataStore === undefined || appContextStore === undefined || passagesDisplayStore === undefined) { return
; } const { getLineDestinations, passages, getPassagesLineIds, clearPassages, refreshPassages } = businessDataStore; const { isPassagesRefreshEnabled, getDisplayedPanelId, setDisplayedPanelId, getPanels, setPanels } = passagesDisplayStore; const { getDisplayedStops } = appContextStore; setInterval(() => { let nextPanelId = getDisplayedPanelId() + 1; if (nextPanelId >= getPanels().length) { nextPanelId = 0; } setDisplayedPanelId(nextPanelId); }, props.panelSwitchPeriodMsec); setInterval( async () => { if (isPassagesRefreshEnabled()) { const stops = getDisplayedStops(); if (stops.length > 0) { refreshPassages(stops[0].id); } } else { console.log("Passages refresh disabled... skip it."); } }, props.syncPeriodMsec ); createEffect(() => { console.log("######### onStopIdUpdate #########"); // Track local.stopIp to force dependency. console.log("getDisplayedStop=", getDisplayedStops()); clearPassages(); }); createEffect(async () => { console.log(`## OnPassageUpdate ${passages()} ##`); const stops = getDisplayedStops(); if (stops.length > 0) { refreshPassages(stops[0].id); } }); return (
{() => { setPanels([]); let newPanels = []; let positioneds: PositionedPanel[] = []; let index = 0; let lineIds: string[] = []; let destinationsNb = 0; for (const lineId of getPassagesLineIds()) { const lineDestinations = getLineDestinations(lineId); if (lineDestinations.length <= props.maxPassagesPerPanel - destinationsNb) { lineIds.push(lineId); destinationsNb += lineDestinations.length; } else { const panelid = index++; const panel = ; newPanels.push(panel); positioneds.push({ position: panelid, panel: panel }); lineIds = [lineId]; destinationsNb = lineDestinations.length; } } if (destinationsNb) { const panelId = index++; const panel = ; newPanels.push(panel); positioneds.push({ position: panelId, panel: panel }); } setPanels(positioneds); return newPanels; }}
); } export const PassagesDisplay: ParentComponent = () => { const MAX_PASSAGES_PER_PANEL = 5; // TODO: Use props. const syncPeriodMsec = 20 * 1000; const panelSwitchPeriodMsec = 4 * 1000; return (
); };