diff --git a/frontend/src/extra/iconHamburgerMenu.tsx b/frontend/src/extra/iconHamburgerMenu.tsx new file mode 100644 index 0000000..ee51b6c --- /dev/null +++ b/frontend/src/extra/iconHamburgerMenu.tsx @@ -0,0 +1,19 @@ +import { createIcon } from "@hope-ui/solid"; + + +// From https://github.com/hope-ui/hope-ui/blob/main/apps/docs/src/icons/IconHamburgerMenu.tsx + +export const IconHamburgerMenu = createIcon({ + viewBox: "0 0 15 15", + path: () => ( + + ), +}); diff --git a/frontend/src/passagesDisplay.module.css b/frontend/src/passagesDisplay.module.css index 6c92b9e..243740b 100644 --- a/frontend/src/passagesDisplay.module.css +++ b/frontend/src/passagesDisplay.module.css @@ -39,6 +39,21 @@ margin-right: auto; } +.header .menu { + height: calc(80/100*100%); + aspect-ratio: 1; + margin-right: calc(30/1920*100%); +} + +.header .menu button { + height: 100%; + aspect-ratio: 1; + + background-color: var(--idfm-black); + border: solid var(--idfm-white) 3px; + border-radius: calc(9/86*100%); +} + .header .clock { width: calc(175/1920*100%); height: calc(80/100*100%); diff --git a/frontend/src/passagesDisplay.tsx b/frontend/src/passagesDisplay.tsx index 3c6ebf4..15de72c 100644 --- a/frontend/src/passagesDisplay.tsx +++ b/frontend/src/passagesDisplay.tsx @@ -1,6 +1,7 @@ -import { createEffect, createResource, createSignal, For, JSX, ParentComponent, Show, useContext, VoidComponent } from "solid-js"; +import { createContext, createEffect, createResource, createSignal, For, JSX, ParentComponent, Show, useContext, VoidComponent, VoidProps } 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"; @@ -9,45 +10,224 @@ import { SearchContext, SearchStore } from "./search"; import { Passage, Passages } from "./types"; import { getTransportModeSrc } from "./utils"; import { PassagesPanel } from "./passagesPanel"; - +import { IconHamburgerMenu } from './extra/iconHamburgerMenu'; import styles from "./passagesDisplay.module.css"; -export const PassagesDisplay: ParentComponent = () => { +type PositionnedPanel = { + position: number; + // TODO: Should be PassagesPanelComponent ? + panel: JSX.Element; +}; - const maxPassagePerPanel = 5; - const syncPeriodMsec = 20 * 1000; - const panelSwitchPeriodMsec = 4 * 1000; + +interface PassagesDisplayStore { + isPassagesRefreshEnabled: () => boolean; + enablePassagesRefresh: () => void; + disablePassagesRefresh: () => void; + togglePassagesRefresh: () => void; + + getPanels: () => Array; + setPanels: (panels: Array) => void; + + getDisplayedPanelId: () => number; + setDisplayedPanelId: (panelId: number) => void; +}; + +const PassagesDisplayContext = createContext(); + +function PassagesDisplayProvider(props: { children: JSX.Element }) { + + type Store = { + refreshEnabled: boolean; + panels: Array; + 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 = (): Array => { + return store.panels; + } + const setPanels = (panels: Array): 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); - // TODO: Use props instead - const searchStore: SearchStore | undefined = useContext(SearchContext); + const passagesDisplayStore: PassagesDisplayStore | undefined = useContext(PassagesDisplayContext); - if (businessDataStore === undefined || searchStore === undefined) + if (businessDataStore === undefined || passagesDisplayStore === undefined) return
; - const { passages, getLine, getLinePassages, refreshPassages, clearPassages } = businessDataStore; - const { getDisplayedStops } = searchStore; - - const [displayedPanelId, setDisplayedPanelId] = createSignal(0); - - type PositionnedPanel = { - position: number; - // TODO: Should be PassagesPanelComponent ? - panel: JSX.Element; - }; - const [panels, setPanels] = createStore([]); + 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} + + +
+
+ + + /> + + togglePassagesRefresh()}>{isPassagesRefreshEnabled() ? "Disable" : "Enable"} + + +
+
+ + + {format(dateNow(), "HH:mm")} + + +
+
+ ); +}; + +const Footer: VoidComponent<{}> = () => { + + const passagesDisplayStore: PassagesDisplayStore | undefined = useContext(PassagesDisplayContext); + + if (passagesDisplayStore === undefined) + return
; + + const { getDisplayedPanelId, getPanels } = passagesDisplayStore; + + return ( +
+ + {(panel) => { + const position = panel.position; + return ( +
+ + + +
+ ); + }} +
+
+ ); +} + +const Body: ParentComponent<{ maxPassagesPerPanel: number, syncPeriodMsec: number, panelSwitchPeriodMsec: number }> = (props) => { + + const businessDataStore: BusinessDataStore | undefined = useContext(BusinessDataContext); + const passagesDisplayStore: PassagesDisplayStore | undefined = useContext(PassagesDisplayContext); + // TODO: Use props instead + const searchStore: SearchStore | undefined = useContext(SearchContext); + + if (businessDataStore === undefined || passagesDisplayStore === undefined || searchStore === undefined) { + return
; + } + + const { getLinePassages, passages, clearPassages, refreshPassages } = businessDataStore; + const { isPassagesRefreshEnabled, getDisplayedPanelId, setDisplayedPanelId, getPanels, setPanels } = passagesDisplayStore; + const { getDisplayedStops } = searchStore; + setInterval(() => { - let nextPanelId = displayedPanelId() + 1; - if (nextPanelId >= panels.length) { + let nextPanelId = getDisplayedPanelId() + 1; + if (nextPanelId >= getPanels().length) { nextPanelId = 0; } setDisplayedPanelId(nextPanelId); - }, panelSwitchPeriodMsec); + }, 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 #########"); @@ -64,137 +244,67 @@ export const PassagesDisplay: ParentComponent = () => { } }); - setInterval( - async () => { - const stops = getDisplayedStops(); - if (stops.length > 0) { - refreshPassages(stops[0].id); - } - }, - syncPeriodMsec - ); + return ( +
+ {() => { + setPanels([]); - // TODO: Sort transport modes by weight - const Header: VoidComponent<{ passages: Passages, title: string }> = (props) => { + let newPanels = []; + let positioneds: PositionnedPanel[] = []; + let index = 0; - 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); + let chunk: Record> = {}; + let chunkSize = 0; + + for (const lineId of Object.keys(passages())) { + const byLinePassages = getLinePassages(lineId); + const byLinePassagesKeys = Object.keys(byLinePassages); + + if (byLinePassagesKeys.length <= props.maxPassagesPerPanel - chunkSize) { + chunk[lineId] = byLinePassages; + chunkSize += byLinePassagesKeys.length; + } + else { + const panelid = index++; + const panel = ; + newPanels.push(panel); + positioneds.push({ position: panelid, panel: panel }); + + chunk = {}; + chunk[lineId] = byLinePassages; + chunkSize = byLinePassagesKeys.length; + } + } + if (chunkSize) { + const panelId = index++; + const panel = ; + newPanels.push(panel); + positioneds.push({ position: panelId, panel: panel }); } - } - return Array.from(urls); - } - const [linesIds, setLinesIds] = createSignal([]); - const [transportModeUrls] = createResource(linesIds, computeTransportModes); + setPanels(positioneds); - createEffect(() => { - setLinesIds(Object.keys(props.passages)); - }); + return newPanels; + }} +
+ ); +} - return ( -
- - - {(url) => -
- -
- } -
-
-
- - - {props.title} - - -
-
- - - {format(dateNow(), "HH:mm")} - - -
-
- ); - }; +export const PassagesDisplay: ParentComponent = () => { - const Footer: VoidComponent<{ panels: PositionnedPanel[] }> = (props) => { - return ( -
- - {(panel) => { - const position = panel.position; - return ( -
- - - -
- ); - }} -
-
- ); - } + const MAX_PASSAGES_PER_PANEL = 5; + + // TODO: Use props. + const syncPeriodMsec = 20 * 1000; + const panelSwitchPeriodMsec = 4 * 1000; return (
-
-
- {() => { - setPanels([]); - - let newPanels = []; - let positioneds: PositionnedPanel[] = []; - let index = 0; - - let chunk: Record> = {}; - let chunkSize = 0; - - console.log("passages=", passages()); - for (const lineId of Object.keys(passages())) { - console.log("lineId=", lineId); - const byLinePassages = getLinePassages(lineId); - console.log("byLinePassages=", byLinePassages); - const byLinePassagesKeys = Object.keys(byLinePassages); - console.log("byLinePassagesKeys=", byLinePassagesKeys); - - if (byLinePassagesKeys.length <= maxPassagePerPanel - chunkSize) { - chunk[lineId] = byLinePassages; - chunkSize += byLinePassagesKeys.length; - } - else { - const panelid = index++; - const panel = ; - newPanels.push(panel); - positioneds.push({ position: panelid, panel: panel }); - - chunk = {}; - chunk[lineId] = byLinePassages; - chunkSize = byLinePassagesKeys.length; - } - } - if (chunkSize) { - const panelId = index++; - const panel = ; - newPanels.push(panel); - positioneds.push({ position: panelId, panel: panel }); - } - - setPanels(positioneds); - return newPanels; - }} -
-
); };