201 lines
5.2 KiB
TypeScript
201 lines
5.2 KiB
TypeScript
import { createEffect, For, JSX, lazy, ParentComponent, useContext, Show, VoidComponent } from 'solid-js';
|
|
import { lazily } from 'solidjs-lazily';
|
|
import { createScrollPosition } from "@solid-primitives/scroll";
|
|
|
|
import { Stop } from '../types';
|
|
import { PositionedPanel } from '../utils';
|
|
|
|
import { BusinessDataContext, BusinessDataStore } from "../businessData";
|
|
import { SearchContext, SearchProvider, SearchStore } from "./searchStore";
|
|
import { StopsPanel } from "./stopPanel";
|
|
|
|
const { Map } = lazily(() => import("./map"));
|
|
|
|
|
|
import "./stopsSearchMenu.scss";
|
|
|
|
|
|
const StopNameInput: VoidComponent<{ onInput: JSX.EventHandler<HTMLInputElement, InputEvent>, leftAddon: string, placeholder: string }> = (props) => {
|
|
return (
|
|
<div class="stopNameInput">
|
|
<div class="leftAddon">{props.leftAddon}</div>
|
|
<input type="text" oninput={props.onInput} placeholder={props.placeholder} />
|
|
</div>);
|
|
};
|
|
|
|
|
|
const Header: VoidComponent<{ title: string, minCharsNb: number }> = (props) => {
|
|
const businessDataStore: BusinessDataStore | undefined = useContext(BusinessDataContext);
|
|
const searchStore: SearchStore | undefined = useContext(SearchContext);
|
|
|
|
if (businessDataStore === undefined || searchStore === undefined)
|
|
return <div />;
|
|
|
|
const { setSearchText } = searchStore;
|
|
|
|
const onStopNameInput: JSX.EventHandler<HTMLInputElement, InputEvent> = async (event): Promise<void> => {
|
|
const stopName = event.currentTarget.value;
|
|
if (stopName.length >= props.minCharsNb) {
|
|
await setSearchText(stopName, businessDataStore);
|
|
}
|
|
}
|
|
|
|
return (
|
|
<div class="header">
|
|
<div class="title">
|
|
<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>
|
|
<StopNameInput onInput={onStopNameInput} leftAddon="🚉 🚏" placeholder="Stop name..." />
|
|
</div >
|
|
);
|
|
};
|
|
|
|
|
|
const StopsPanels: ParentComponent<{ maxStopsPerPanel: number }> = (props) => {
|
|
const searchStore: SearchStore | undefined = useContext(SearchContext);
|
|
|
|
if (searchStore === undefined) {
|
|
return <div />;
|
|
}
|
|
|
|
const { getDisplayedPanelId, getFoundStops, getPanels, setDisplayedPanelId, setPanels } = searchStore;
|
|
let stopsPanelsRef: HTMLDivElement | undefined = undefined
|
|
const stopsPanelsScroll = createScrollPosition(() => stopsPanelsRef);
|
|
const yStopsPanelsScroll = () => stopsPanelsScroll.y;
|
|
|
|
createEffect(() => {
|
|
yStopsPanelsScroll();
|
|
|
|
for (const panel of getPanels()) {
|
|
const panelDiv = panel.panel();
|
|
const panelDivClientRect = panelDiv.getBoundingClientRect();
|
|
if (panelDivClientRect.y > 0) {
|
|
setDisplayedPanelId(panel.position);
|
|
break;
|
|
}
|
|
}
|
|
});
|
|
|
|
return (
|
|
<div ref={stopsPanelsRef} class="stopsPanels">
|
|
{() => {
|
|
setPanels([]);
|
|
|
|
let newPanels = [];
|
|
let positioneds: PositionedPanel[] = [];
|
|
|
|
let stops: Stop[] = [];
|
|
|
|
for (const stop of getFoundStops()) {
|
|
if (stops.length < props.maxStopsPerPanel) {
|
|
stops.push(stop);
|
|
}
|
|
else {
|
|
const panelId = newPanels.length;
|
|
const panel = <StopsPanel stops={stops} show={panelId == getDisplayedPanelId()} />;
|
|
newPanels.push(panel);
|
|
positioneds.push({ position: panelId, panel: panel });
|
|
stops = [stop];
|
|
}
|
|
}
|
|
if (stops.length) {
|
|
const panelId = newPanels.length;
|
|
const panel = <StopsPanel stops={stops} show={panelId == getDisplayedPanelId()} />;
|
|
newPanels.push(panel);
|
|
positioneds.push({ position: panelId, panel: panel });
|
|
}
|
|
|
|
setPanels(positioneds);
|
|
|
|
return newPanels;
|
|
}}
|
|
</div>
|
|
);
|
|
};
|
|
|
|
|
|
const MapPlaceholder: VoidComponent<{}> = () => {
|
|
const searchStore: SearchStore | undefined = useContext(SearchContext);
|
|
|
|
if (searchStore === undefined) {
|
|
return <div />;
|
|
}
|
|
|
|
const { enableMap } = searchStore;
|
|
|
|
const onDoubleClick = (): void => {
|
|
console.log('!!! ON DOUBLE CLICK');
|
|
enableMap(true);
|
|
}
|
|
|
|
return <div
|
|
class="mapPlaceholder" ondblclick={() => onDoubleClick()}>
|
|
Double-clic pour activer la carte
|
|
</div>;
|
|
};
|
|
|
|
|
|
const Body: VoidComponent<{}> = () => {
|
|
const searchStore: SearchStore | undefined = useContext(SearchContext);
|
|
if (searchStore === undefined) {
|
|
return <div />;
|
|
}
|
|
|
|
const { isMapEnabled } = searchStore;
|
|
|
|
const maxStopsPerPanel = 5;
|
|
|
|
return <div class="body">
|
|
<StopsPanels maxStopsPerPanel={maxStopsPerPanel} />
|
|
<Show when={isMapEnabled()} fallback={<MapPlaceholder />}>
|
|
<Map />
|
|
</Show>
|
|
</div>;
|
|
};
|
|
|
|
|
|
const Footer: VoidComponent<{}> = () => {
|
|
const searchStore: SearchStore | undefined = useContext(SearchContext);
|
|
if (searchStore === undefined) {
|
|
return <div />;
|
|
}
|
|
|
|
const { getDisplayedPanelId, getPanels } = searchStore;
|
|
|
|
return (
|
|
<div class="footer">
|
|
<For each={getPanels()}>
|
|
{(panel) => {
|
|
const position = panel.position;
|
|
return (
|
|
<div>
|
|
<svg viewBox="0 0 29 29">
|
|
<circle cx="50%" cy="50%" r="13" stroke="#ffffff" stroke-width="3"
|
|
style={{ fill: `var(--idfm-${position == getDisplayedPanelId() ? "white" : "black"})` }}
|
|
/>
|
|
</svg>
|
|
</div>
|
|
);
|
|
}}
|
|
</For>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
|
|
export const StopsSearchMenu: VoidComponent = () => {
|
|
return (
|
|
<div class="stopSearchMenu">
|
|
<SearchProvider>
|
|
<Header title="Recherche de l'arrêt..." minCharsNb={4} />
|
|
<Body />
|
|
<Footer />
|
|
</SearchProvider>
|
|
</div>
|
|
);
|
|
};
|