♻️ Replace methods called to render Component with small Components

This commit is contained in:
2023-01-28 16:22:32 +01:00
parent e141aa15e5
commit 29ba26e80b
3 changed files with 141 additions and 161 deletions

View File

@@ -60,78 +60,59 @@ export const PassagesDisplay: Component = () => {
); );
// TODO: Sort transport modes by weight // TODO: Sort transport modes by weight
// TODO: Split this method to isolate the passagesPanel part. const Header: Component = (props) => {
function _computeHeader(title: string): JSX.Element { const computeTransportModes = async (lineIds: Array<number>) => {
let transportModes = []; const lines = await Promise.all(lineIds.map((lineId) => getLine(lineId)));
transportModes = new Set( return new Set(lines.map((line) => getTransportModeSrc(line.transportMode, false)));
Object.keys(passages()).map((lineId) => {
const line = _lines.get(lineId);
if (line !== undefined) {
return getTransportModeSrc(line.transportMode, false);
} }
return null;
}) const [linesIds, setLinesIds] = createSignal([]);
); const [transportModeUrls] = createResource(linesIds, computeTransportModes);
createEffect(() => {
setLinesIds(Object.keys(props.passages));
});
return ( return (
<div class={styles.header}> <div class={styles.header}>
<For each={Array.from(transportModes)}> <Show when={transportModeUrls() !== undefined} >
{(transportMode) => { <For each={Array.from(transportModeUrls())}>
return ( {(url) =>
<div class={styles.transportMode}> <div class={styles.transportMode}>
<img src={transportMode} /> <img src={url} />
</div> </div>
); }
}}
</For> </For>
</Show>
<div class={styles.title}> <div class={styles.title}>
<svg viewbox="0 0 1260 50"> <svg viewbox="0 0 1260 50">
<text <text x="0" y="50%" dominant-baseline="middle" font-size="50" style="fill: #ffffff">
x="0" {props.title}
y="50%"
dominant-baseline="middle"
font-size="50"
style="fill: #ffffff"
>
{title}
</text> </text>
</svg> </svg>
</div> </div>
<div class={styles.clock}> <div class={styles.clock}>
<svg viewbox="0 0 115 43"> <svg viewbox="0 0 115 43">
<text <text x="50%" y="55%" dominant-baseline="middle" text-anchor="middle" font-size="43" style="fill: #ffffff">
x="50%"
y="55%"
dominant-baseline="middle"
text-anchor="middle"
font-size="43"
style="fill: #ffffff"
>
{format(dateNow(), "HH:mm")} {format(dateNow(), "HH:mm")}
</text> </text>
</svg> </svg>
</div> </div>
</div> </div >
); );
} };
function _computeFooter(): JSX.Element { const Footer: Component = (props) => {
return ( return (
<div class={styles.footer}> <div class={styles.footer}>
<For each={panels}> <For each={props.panels}>
{(positioned) => { {(positioned) => {
const { position } = positioned; const { position } = positioned;
return ( return (
<div> <div>
<svg viewBox="0 0 29 29"> <svg viewBox="0 0 29 29">
<circle <circle cx="50%" cy="50%" r="13" stroke="#ffffff" stroke-width="3"
cx="50%" style={{ fill: `var(--idfm-${position == displayedPanelId() ? "white" : "black"})` }}
cy="50%"
r="13"
stroke="#ffffff"
stroke-width="3"
style={{
fill: `var(--idfm-${position == displayedPanelId() ? "white" : "black"})`,
}}
/> />
</svg> </svg>
</div> </div>
@@ -144,7 +125,7 @@ export const PassagesDisplay: Component = () => {
return ( return (
<div class={styles.passagesDisplay}> <div class={styles.passagesDisplay}>
{_computeHeader("Prochains passages")} <Header title="Prochains passages" passages={passages()} />
<div class={styles.panelsContainer}> <div class={styles.panelsContainer}>
{() => { {() => {
setPanels([]); setPanels([]);
@@ -167,16 +148,11 @@ export const PassagesDisplay: Component = () => {
if (byLinePassagesKeys.length <= maxPassagePerPanel - chunkSize) { if (byLinePassagesKeys.length <= maxPassagePerPanel - chunkSize) {
chunk[lineId] = byLinePassages; chunk[lineId] = byLinePassages;
chunkSize += byLinePassagesKeys.length; chunkSize += byLinePassagesKeys.length;
} else { }
console.log("chunk=", chunk); else {
const [store] = createStore(chunk); const [store] = createStore(chunk);
const panelid = index++; const panelid = index++;
const panel = ( const panel = <PassagesPanel show={panelid == displayedPanelId()} passages={store} />;
<PassagesPanel
show={panelid == displayedPanelId()}
passages={store}
lines={_lines} />
);
newPanels.push(panel); newPanels.push(panel);
positioneds.push({ position: panelid, panel }); positioneds.push({ position: panelid, panel });
@@ -188,12 +164,7 @@ export const PassagesDisplay: Component = () => {
if (chunkSize) { if (chunkSize) {
const panelId = index++; const panelId = index++;
const [store] = createStore(chunk); const [store] = createStore(chunk);
const panel = ( const panel = <PassagesPanel show={panelId == displayedPanelId()} passages={store} />;
<PassagesPanel
show={panelId == displayedPanelId()}
passages={store}
lines={_lines} />
);
newPanels.push(panel); newPanels.push(panel);
positioneds.push({ position: panelId, panel }); positioneds.push({ position: panelId, panel });
} }
@@ -202,7 +173,7 @@ export const PassagesDisplay: Component = () => {
return newPanels; return newPanels;
}} }}
</div> </div>
{_computeFooter()} <Footer panels={panels} />
</div> </div>
); );
}; };

View File

@@ -1,14 +1,56 @@
import { Component } from 'solid-js'; import { Component, createEffect, createResource, createSignal, useContext } from 'solid-js';
import { createDateNow, getTime } from '@solid-primitives/date'; import { createDateNow, getTime } from '@solid-primitives/date';
import { Motion } from "@motionone/solid"; import { Motion } from "@motionone/solid";
import { TrafficStatus } from './types'; import { TrafficStatus } from './types';
import { renderLineTransportMode, renderLinePicto } from './utils'; import { renderLineTransportMode, renderLinePicto } from './utils';
import { BusinessDataContext } from "./businessData";
import styles from "./passagesPanel.module.css"; import styles from "./passagesPanel.module.css";
export const PassagesPanel: Component = (props) => { const TtwPassage: Component = (props) => {
const [dateNow] = createDateNow(5000);
const refTs = props.passage.expectedDepartTs !== null ? props.passage.expectedDepartTs : props.passage.expectedArrivalTs;
const ttwSec = refTs - (getTime(dateNow()) / 1000);
const isApproaching = ttwSec <= 60;
return (
<div class={props.style}>
<svg viewBox={`0 0 215 ${props.fontSize}`}>
<Motion.text
x="100%" y="55%"
dominant-baseline="middle" text-anchor="end"
font-size={props.fontSize} style={{ fill: "#000000" }}
initial={isApproaching}
animate={{ opacity: [1, 0, 1] }}
transition={{ duration: 3, repeat: Infinity }}>
{Math.floor(ttwSec / 60)} min
</Motion.text>
</svg>
</div>
);
}
const UnavailablePassage: Component = (props) => {
const textStyle = { fill: "#000000" };
return (
<div class={props.style}>
<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>
</svg>
</div>
);
}
/* TODO: Manage end of service */
const Passages: Component = (props) => {
/* TODO: Find where to get data to compute traffic status. */ /* TODO: Find where to get data to compute traffic status. */
const trafficStatusColor = new Map<TrafficStatus, string>([ const trafficStatusColor = new Map<TrafficStatus, string>([
@@ -19,74 +61,21 @@ export const PassagesPanel: Component = (props) => {
[TrafficStatus.BYPASSED, "#ffffff"] [TrafficStatus.BYPASSED, "#ffffff"]
]); ]);
const [dateNow] = createDateNow(5000); 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) };
function _computeTtwPassage(class_, passage, fontSize) {
const refTs = passage.expectedDepartTs !== null ? passage.expectedDepartTs : passage.expectedArrivalTs;
const ttwSec = refTs - (getTime(dateNow()) / 1000);
const isApproaching = ttwSec <= 60;
return (
<div class={class_}>
<svg viewBox={`0 0 215 ${fontSize}`}>
<Motion.text
x="100%" y="55%"
dominant-baseline="middle" text-anchor="end"
font-size={fontSize} style={{ fill: "#000000" }}
initial={isApproaching}
animate={{ opacity: [1, 0, 1] }}
transition={{ duration: 3, repeat: Infinity }}>
{Math.floor(ttwSec / 60)} min
</Motion.text>
</svg>
</div>
);
}
function _computeUnavailablePassage(class_) {
const textStyle = { fill: "#000000" };
return (
<div class={class_}>
<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>
</svg>
</div>
);
}
function _computeSecondPassage(passage): JSX.Element {
return (
<Show when={passage !== undefined} fallback={_computeUnavailablePassage(styles.unavailableSecondPassage)}>
{_computeTtwPassage(styles.secondPassage, passage, 45)}
</Show>
);
}
function _computeFirstPassage(passage): JSX.Element {
return (
<Show when={passage !== undefined} fallback={_computeUnavailablePassage(styles.unavailableFirstPassage)}>
{_computeTtwPassage(styles.firstPassage, passage, 50)}
</Show>
);
}
/* TODO: Manage end of service */
function _genPassages(passages, line, destination) {
const passagesLength = passages.length;
const firstPassage = passagesLength > 0 ? passages[0] : undefined;
const secondPassage = passagesLength > 1 ? passages[1] : undefined;
const trafficStatusStyle = { fill: trafficStatusColor.get(line.trafficStatus) };
return ( return (
<div class={styles.line}> <div class={styles.line}>
<div class={styles.transportMode}> <div class={styles.transportMode}>
{renderLineTransportMode(line)} {renderLineTransportMode(props.line)}
</div> </div>
{renderLinePicto(line, styles)} {renderLinePicto(props.line, styles)}
<div class={styles.destination}> <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" }}> <text x="0" y="50%" dominant-baseline="middle" font-size="40" style={{ fill: "#000000" }}>
{destination} {props.destination}
</text> </text>
</svg> </svg>
</div> </div>
@@ -95,26 +84,48 @@ export const PassagesPanel: Component = (props) => {
<circle cx="50%" cy="50%" r="24" stroke="#231f20" stroke-width="3" style={trafficStatusStyle} /> <circle cx="50%" cy="50%" r="24" stroke="#231f20" stroke-width="3" style={trafficStatusStyle} />
</svg> </svg>
</div> </div>
{firstPassage ? _computeFirstPassage(firstPassage) : null} <Show when={firstPassage !== undefined} fallback=<UnavailablePassage style={styles.unavailableFirstPassage} />>
{secondPassage ? _computeSecondPassage(secondPassage) : null} <TtwPassage style={styles.firstPassage} passage={firstPassage} fontSize="50" />
</div> </Show>
<Show when={secondPassage !== undefined} fallback=<UnavailablePassage style={styles.unavailableSecondPassage} />>
<TtwPassage style={styles.secondPassage} passage={secondPassage} fontSize="45" />
</Show>
</div >
); );
}
export const PassagesPanel: Component = (props) => {
const { getLine } = useContext(BusinessDataContext);
const getLines = async (lineIds: Array<number>) => {
const lines = await Promise.all(lineIds.map((lineId) => getLine(lineId)));
return lines;
} }
const [lineIds, setLinesIds] = createSignal([]);
const [lines] = createResource(lineIds, getLines);
createEffect(async () => {
setLinesIds(Object.keys(props.passages));
});
return ( return (
<div classList={{ [styles.passagesContainer]: true, [styles.displayed]: props.show }} style={{ "top": `${100 * props.position}%` }}> <div classList={{ [styles.passagesContainer]: true, [styles.displayed]: props.show }} style={{ "top": `${100 * props.position}%` }}>
<Show when={lines() !== undefined} >
{() => { {() => {
const ret = []; const ret = [];
for (const lineId of Object.keys(props.passages)) { for (const line of lines()) {
const line = props.lines.get(lineId); const byLinePassages = props.passages[line.id];
const byLinePassages = props.passages[lineId]; if (byLinePassages !== undefined) {
for (const destination of Object.keys(byLinePassages)) { for (const destination of Object.keys(byLinePassages)) {
const passages = byLinePassages[destination]; ret.push(<Passages passages={byLinePassages[destination]} line={line} destination={destination} />);
ret.push(_genPassages(passages, line, destination)); }
} }
} }
return ret; return ret;
}} }}
</div> </Show>
</div >
); );
} }

View File

@@ -45,9 +45,7 @@ const StopAreaRepr: Component = (props) => {
const { getLine } = useContext(BusinessDataContext); const { getLine } = useContext(BusinessDataContext);
const [lineReprs] = createResource(props.stop, fetchLinesRepr); const fetchLinesRepr = async (stop: Stop) => {
async function fetchLinesRepr(stop) {
const lineIds = new Set(stop.lines); const lineIds = new Set(stop.lines);
const stops = stop.stops; const stops = stop.stops;
for (const stop of stops) { for (const stop of stops) {
@@ -81,6 +79,8 @@ const StopAreaRepr: Component = (props) => {
return reprs; return reprs;
} }
const [lineReprs] = createResource(props.stop, fetchLinesRepr);
return ( return (
<HStack height="100%"> <HStack height="100%">
{props.stop.name} {props.stop.name}
@@ -128,7 +128,7 @@ const Map: Component = (props) => {
/* TODO: Avoid to clear all layers... */ /* TODO: Avoid to clear all layers... */
stopsLayerGroup.clearLayers(); stopsLayerGroup.clearLayers();
for (const stop of Object.values(getStops())) { for (const stop of props.stops) {
const markers = setMarker(stop); const markers = setMarker(stop);
addMarkers(stop.id, markers); addMarkers(stop.id, markers);
for (const marker of markers) { for (const marker of markers) {
@@ -145,8 +145,6 @@ const Map: Component = (props) => {
return <div ref={mapDiv} id='main-map' style={{ width: "100%", height: "100%" }} />; return <div ref={mapDiv} id='main-map' style={{ width: "100%", height: "100%" }} />;
} }
export const StopsManager: Component = (props) => {
export const StopsManager: Component = () => { export const StopsManager: Component = () => {
const [minCharactersNb, setMinCharactersNb] = createSignal<number>(4); const [minCharactersNb, setMinCharactersNb] = createSignal<number>(4);
@@ -172,21 +170,21 @@ export const StopsManager: Component = () => {
<VStack h="100%"> <VStack h="100%">
<InputGroup w="50%" h="5%"> <InputGroup w="50%" h="5%">
<InputLeftAddon>🚉 🚏</InputLeftAddon> <InputLeftAddon>🚉 🚏</InputLeftAddon>
<Input onInput={_onStopNameInput} readOnly={_inProgress()} placeholder="Stop name..." /> <Input onInput={onStopNameInput} readOnly={inProgress()} placeholder="Stop name..." />
</InputGroup> </InputGroup>
<Progress size="xs" w="50%" indeterminate={_inProgress()}> <Progress size="xs" w="50%" indeterminate={inProgress()}>
<ProgressIndicator striped animated /> <ProgressIndicator striped animated />
</Progress> </Progress>
<Box w="100%" h="40%" borderWidth="1px" borderColor="var(--idfm-black)" borderRadius="$lg" overflow="scroll" marginBottom="2px"> <Box w="100%" h="40%" borderWidth="1px" borderColor="var(--idfm-black)" borderRadius="$lg" overflow="scroll" marginBottom="2px">
<List width="100%" height="100%"> <List width="100%" height="100%">
{() => { {() => {
const items = []; const items = [];
for (const stop of Object.values(getStops()).sort((x, y) => x.name.localeCompare(y.name))) { for (const stop of foundStops().sort((x, y) => x.name.localeCompare(y.name))) {
items.push( items.push(
<ListItem h="10%" borderWidth="1px" mb="0px" color="var(--idfm-black)" borderRadius="$lg"> <ListItem h="10%" borderWidth="1px" mb="0px" color="var(--idfm-black)" borderRadius="$lg">
<Button fullWidth="true" color="var(--idfm-black)" bg="white" onClick={() => { <Button fullWidth="true" color="var(--idfm-black)" bg="white" onClick={() => {
console.log(`${stop.id} clicked !!!`); console.log(`${stop.id} clicked !!!`);
setDisplayedStop(stop); setDisplayedStops([stop]);
}}> }}>
<Box w="100%" h="100%"> <Box w="100%" h="100%">
<Show when={stop.stops !== undefined} fallback={<StopRepr stop={stop} />}> <Show when={stop.stops !== undefined} fallback={<StopRepr stop={stop} />}>
@@ -201,7 +199,7 @@ export const StopsManager: Component = () => {
</List> </List>
</Box> </Box>
<Box borderWidth="1px" borderColor="var(--idfm-black)" borderRadius="$lg" h="55%" w="100%" overflow="scroll"> <Box borderWidth="1px" borderColor="var(--idfm-black)" borderRadius="$lg" h="55%" w="100%" overflow="scroll">
<Map /> <Map stops={foundStops()} />
</Box> </Box>
</VStack> </VStack>
); );