diff --git a/docutils.js b/docutils.js index 38120cc6..74034519 100644 --- a/docutils.js +++ b/docutils.js @@ -1,4 +1,97 @@ -/* eslint-disable @typescript-eslint/no-unused-vars */ +function onLoad(wc, attributes, events, pkgSrc) { + /* Show private attributes for dev purpose */ + const showPrivate = + new URLSearchParams(window.location.search).get("private") === "true"; + + /* Attributes */ + const attrs = Object.keys(attributes); + const booleanAttrs = Object.entries(attributes) + .filter(([, attr]) => attr.type === "boolean") + .map(([key]) => key); + const booleanTrueByDefault = booleanAttrs.filter( + (key) => attributes[key].defaultValue === "true", + ); + const reloadAttrs = Object.entries(attributes) + .filter(([key, attr]) => !!attr.reload) + .map(([key]) => key); + + const descriptionByAttr = Object.entries(attributes) + .filter(([key, attr]) => { + if (showPrivate) { + return true; + } + return attr.public; + }) + .reduce((acc, [key, attr]) => { + acc[key] = attr.description; + return acc; + }, {}); + + const defaultValueByAttr = Object.entries(attributes).reduce( + (acc, [key, attr]) => { + acc[key] = attr.defaultValue; + return acc; + }, + {}, + ); + + /* Events */ + const evts = Object.keys(events); + const descriptionByEvent = Object.entries(events) + .filter(([key, attr]) => { + if (showPrivate) { + return true; + } + return attr.public; + }) + .reduce((acc, [key, attr]) => { + acc[key] = attr.description; + return acc; + }, {}); + + /* Build HTML */ + const attrsContent = generateAttributesTable( + wc, + attrs, + booleanAttrs, + booleanTrueByDefault, + descriptionByAttr, + defaultValueByAttr, + reloadAttrs, + ); + if (attrsContent) { + document.querySelector("#attributes").innerHTML = attrsContent; + } else { + document.querySelector("#attributesDoc").remove(); + } + + const evtsContent = generateEventsTable(wc, evts, descriptionByEvent); + if (evtsContent) { + document.querySelector("#events").innerHTML = evtsContent; + } else { + document.querySelector("#eventsDoc").remove(); + } + + document.querySelector("#code").innerHTML = generateCodeText( + wc, + attrs, + pkgSrc, + ); + wc.addEventListener("mwc:attribute", (event) => { + document.querySelector("#code").innerHTML = generateCodeText( + wc, + attrs, + pkgSrc, + ); + }); + applyPermalinkParameters(wc); + evts.forEach((eventName) => { + wc.addEventListener(eventName, (event) => { + console.log(`${eventName} event`, event); + }); + }); +} + function applyPermalinkParameters(wc) { const params = new URLSearchParams(window.location.search); @@ -68,6 +161,9 @@ function generateAttributesTable( defaultValueByAttr = {}, reloadAttrs = [], ) { + if (!attrs?.length) { + return null; + } let innerHMTL = ` @@ -112,8 +208,7 @@ function generateAttributesTable( type="text" class="border" name="${key}" - value="${wc.getAttribute(key) || ""}" - placeholder="${defaultValueByAttr[key] || ""}" + value="${wc.getAttribute(key) || defaultValueByAttr[key] || ""}" /> ` } @@ -139,7 +234,7 @@ function generateCodeText( let codeText = ""; codeText = `<script\n\ttype="module"\n\tsrc="${pkgSrc}"> </script> -<${wc.localName}\n\tid="map"\n\tstyle="display:block;width:100%;height:500px;border:1px solid #e5e7eb;border-radius:16px;"`; +<${wc.localName}${wc.id ? '\n\tid="' + wc.id + '"' : ""}\n\tstyle="display:block;width:100%;height:500px;border:1px solid #e5e7eb;border-radius:16px;"`; attrs.forEach((key) => { const attributeValue = wc.getAttribute(key); @@ -166,6 +261,9 @@ function generateCodeText( // Generates a HTML table with all events of a web component function generateEventsTable(wc, events, descriptionByEvent = {}) { + if (!events?.length) { + return null; + } let innerHMTL = `
diff --git a/index.html b/index.html index 1ddc6bc5..f73940ae 100644 --- a/index.html +++ b/index.html @@ -46,19 +46,24 @@

Usage example

>
-

Attributes

-
-

Events

-
+      
+

Attributes

+
+
+
+

Events

+
 document.getElementById('map').addEventListener('mwc:attribute', (event) => {
   console.log('Display last data received:', event.data);
 });
-
+ > +
+


More mobility web components

@@ -74,141 +79,29 @@

More mobility web components

const pkgSrc = "https://www.unpkg.com/@geops/mobility-web-component"; const wc = document.querySelector("geops-mobility"); - window.addEventListener("load", () => { const attributes = window.MobilityMapAttributes; - const events = window.MobilityMapEvents || { - "mwc:selectedfeature": { - description: - "Event fired when a feature is selected on the map. The event data contains the selected feature in GeoJSON format.", - public:true - }, - "mwc:stopssearchselect": { - description: - "Only when search attribute is 'true'. Event fired when a stop is selected in the stops search results. The event data contains the selected stop.", - public:true - - }, - "mwc:attribute": { - description: - "Event fired when an web component's attribute is changed. The event data contains the list of all attributes and their values.", - public:true - - }, - "mwc:singleclick": { - description: - "Event fired when the map is clicked. The event data contains the map coordinates in EPSG:3857 and the pixel coordinates.", - public:true - - }, - "mwc:permalink": { - description: - "Event fired when the map's state changes. The event data contains the permalink URL search parameters as string.", - public:true - - }, - "mwc:messageready": { - description: - "Only if the web-component is embedded in an iframe. Message event fired when the web component is ready to receive messages from parent window.", - - public:true - }, - }; - + const events = window.MobilityMapEvents; - - // Add special parameters + // Add page parameters attributes.fullscreen = { type: "boolean", defaultValue: "false", description: `Load the page in fullscreen mode.`, reload: true, - public:false, + public: false, }; attributes.debug = { type: "boolean", defaultValue: "false", - description: "Displays debug information for vehicles when true, use only for debugging.", + description: + "Displays debug information for vehicles when true, use only for debugging.", reload: true, - public:false, + public: false, }; - /* Show private attributes for dev purpose */ - const showPrivate = new URLSearchParams(window.location.search).get("private") === "true"; - - /* Attributes */ - const attrs = Object.keys(attributes); - const booleanAttrs = Object.entries(attributes) - .filter(([, attr]) => attr.type === "boolean") - .map(([key]) => key); - const booleanTrueByDefault = booleanAttrs.filter( - (key) => attributes[key].defaultValue === "true", - ); - const reloadAttrs = Object.entries(attributes) - .filter(([key, attr]) => !!attr.reload) - .map(([key]) => key); - - const descriptionByAttr = Object.entries(attributes).filter(([key, attr]) => { - if (showPrivate) { - return true; - } - return attr.public; - }).reduce((acc, [key, attr]) => { - acc[key] = attr.description; - return acc; - }, {}); - - const defaultValueByAttr = Object.entries(attributes).reduce((acc, [key, attr]) => { - acc[key] = attr.defaultValue; - return acc; - }, {}); - - /* Events */ - const evts = Object.keys(events); - const descriptionByEvent = Object.entries(events).filter(([key, attr]) => { - if (showPrivate) { - return true; - } - return attr.public; - }).reduce((acc, [key, attr]) => { - acc[key] = attr.description; - return acc; - }, {}); - - document.querySelector("#attributes").innerHTML = - generateAttributesTable( - wc, - attrs, - booleanAttrs, - booleanTrueByDefault, - descriptionByAttr, - defaultValueByAttr, - reloadAttrs, - ); - document.querySelector("#events").innerHTML = generateEventsTable( - wc, - evts, - descriptionByEvent, - ); - document.querySelector("#code").innerHTML = generateCodeText( - wc, - attrs, - pkgSrc, - ); - wc.addEventListener("mwc:attribute", (event) => { - document.querySelector("#code").innerHTML = generateCodeText( - wc, - attrs, - pkgSrc, - ); - }); - applyPermalinkParameters(wc); - evts.forEach((eventName) => { - wc.addEventListener(eventName, (event) => { - console.log(`${eventName} event`, event); - }); - }); + onLoad(wc, attributes, events, pkgSrc); }); diff --git a/search.html b/search.html index 1911b2bf..67d3f38a 100644 --- a/search.html +++ b/search.html @@ -58,6 +58,7 @@

Usage example


 
       More mobility web components
 
       window.addEventListener("load", () => {
 
+        const attributes = window.MobilitySearchAttributes;
+        const events = window.MobilitySearchEvents;
+ const descriptionByEvent = {
+          "mwc:stopssearchselect":
+            "Only when search attribute is 'true'. Event fired when a stop is selected in the stops search results. The event data contains the selected stop.",
+          "mwc:attribute":
+            "Event fired when an web component's attribute is changed. The event data contains the list of all attributes and their values.",
+        };
+
         // Add special parameters
         window.MobilityMapAttributes.fullscreen = {
           type: "boolean",
@@ -93,67 +103,7 @@ 

More mobility web components

reload: true, }; - /* Attributes */ - const attrs = Object.keys(MobilitySearchAttributes); - const booleanAttrs = Object.entries(MobilitySearchAttributes) - .filter(([, attr]) => attr.type === "boolean") - .map(([key]) => key); - const booleanTrueByDefault = booleanAttrs.filter( - (key) => MobilitySearchAttributes[key].defaultValue === "true", - ); - const reloadAttrs = Object.entries(MobilitySearchAttributes) - .filter(([key, attr]) => !!attr.reload) - .map(([key]) => key); - - const descriptionByAttr = Object.entries(MobilitySearchAttributes).reduce((acc, [key, attr]) => { - acc[key] = attr.description; - return acc; - }, {}); - - /* Events */ - const events = [ - "mwc:stopssearchselect", - "mwc:attribute", - ]; - const descriptionByEvent = { - "mwc:stopssearchselect": - "Only when search attribute is 'true'. Event fired when a stop is selected in the stops search results. The event data contains the selected stop.", - "mwc:attribute": - "Event fired when an web component's attribute is changed. The event data contains the list of all attributes and their values.", - }; - - document.querySelector("#attributes").innerHTML = - generateAttributesTable( - wc, - attrs, - booleanAttrs, - booleanTrueByDefault, - descriptionByAttr, - reloadAttrs, - ); - document.querySelector("#events").innerHTML = generateEventsTable( - wc, - events, - descriptionByEvent, - ); - document.querySelector("#code").innerHTML = generateCodeText( - wc, - attrs, - pkgSrc, - ); - wc.addEventListener("mwc:attribute", (event) => { - document.querySelector("#code").innerHTML = generateCodeText( - wc, - attrs, - pkgSrc, - ); - }); - applyPermalinkParameters(wc); - events.forEach((eventName) => { - wc.addEventListener(eventName, (event) => { - console.log(`${eventName} event`, event); - }); - }); + onLoad(wc, attributes, events, pkgSrc); }); diff --git a/src/LayoutState/LayoutState.tsx b/src/LayoutState/LayoutState.tsx index 78abfad3..f83d4c05 100644 --- a/src/LayoutState/LayoutState.tsx +++ b/src/LayoutState/LayoutState.tsx @@ -49,6 +49,7 @@ function LayoutState() { setIsOverlayOpen, setIsSearchOpen, setIsShareMenuOpen, + setLinesIds, setStationId, setTrainId, share, @@ -122,6 +123,7 @@ function LayoutState() { setStationId(null); setTrainId(null); setFeaturesInfos(null); + setLinesIds(null); } }, [ isSearchOpen, @@ -129,6 +131,7 @@ function LayoutState() { setIsExportMenuOpen, setIsLayerTreeOpen, setIsShareMenuOpen, + setLinesIds, setStationId, setTrainId, ]); @@ -141,6 +144,7 @@ function LayoutState() { setStationId(null); setTrainId(null); setFeaturesInfos(null); + setLinesIds(null); } }, [ isShareMenuOpen, @@ -148,6 +152,7 @@ function LayoutState() { setIsExportMenuOpen, setIsLayerTreeOpen, setIsSearchOpen, + setLinesIds, setStationId, setTrainId, ]); @@ -160,6 +165,7 @@ function LayoutState() { setFeaturesInfos(null); setTrainId(null); setStationId(null); + setLinesIds(null); setIsShareMenuOpen(false); } }, [ @@ -169,6 +175,7 @@ function LayoutState() { setIsLayerTreeOpen, setIsSearchOpen, setIsShareMenuOpen, + setLinesIds, setStationId, setTrainId, ]); @@ -180,6 +187,7 @@ function LayoutState() { setIsSearchOpen(false); setFeaturesInfos(null); setTrainId(null); + setLinesIds(null); setIsShareMenuOpen(false); setStationId(null); } @@ -190,6 +198,7 @@ function LayoutState() { setIsLayerTreeOpen, setIsSearchOpen, setIsShareMenuOpen, + setLinesIds, setStationId, setTrainId, ]); @@ -223,6 +232,7 @@ function LayoutState() { setIsSearchOpen(false); setTrainId(null); setIsShareMenuOpen(false); + setLinesIds(null); } }, [ setFeaturesInfos, @@ -230,6 +240,7 @@ function LayoutState() { setIsLayerTreeOpen, setIsSearchOpen, setIsShareMenuOpen, + setLinesIds, setTrainId, stationId, ]); diff --git a/src/LinesNetworkPlanLayerHighlight/LinesNetworkPlanLayerHighlight.tsx b/src/LinesNetworkPlanLayerHighlight/LinesNetworkPlanLayerHighlight.tsx index 3ea06a49..98e6ffc3 100644 --- a/src/LinesNetworkPlanLayerHighlight/LinesNetworkPlanLayerHighlight.tsx +++ b/src/LinesNetworkPlanLayerHighlight/LinesNetworkPlanLayerHighlight.tsx @@ -12,8 +12,15 @@ import useMapContext from "../utils/hooks/useMapContext"; import type { MaplibreStyleLayerOptions } from "mobility-toolbox-js/ol/layers/MaplibreStyleLayer"; function LinesNetworkPlanLayerHighlight(props: MaplibreStyleLayerOptions) { - const { baseLayer, featuresInfos, linesNetworkPlanLayer, map } = - useMapContext(); + const { + baseLayer, + featuresInfos, + lines, + linesIds, + linesNetworkPlanLayer, + map, + setLinesIds, + } = useMapContext(); const layer = useMemo(() => { if (!baseLayer) { @@ -42,7 +49,7 @@ function LinesNetworkPlanLayerHighlight(props: MaplibreStyleLayerOptions) { }, [map, layer]); useEffect(() => { - if (!layer || !featuresInfos || !linesNetworkPlanLayer) { + if (!layer || !featuresInfos?.length || !linesNetworkPlanLayer) { return; } const features = @@ -50,11 +57,6 @@ function LinesNetworkPlanLayerHighlight(props: MaplibreStyleLayerOptions) { return featuresInfo.layer === linesNetworkPlanLayer; })?.features || []; - if (features?.length) { - layer.setVisible(true); - } else { - layer.setVisible(false); - } const ids = [ ...new Set( (features || []).map((f) => { @@ -62,26 +64,24 @@ function LinesNetworkPlanLayerHighlight(props: MaplibreStyleLayerOptions) { }), ), ]; + setLinesIds(ids); + }, [featuresInfos, layer, linesNetworkPlanLayer, setLinesIds]); - try { - highlightLinesNetworkPlan(ids, baseLayer); - } catch (e) { - // eslint-disable-next-line no-console - console.error("Error setting filter for highlight layer", e); + useEffect(() => { + if (!layer || !baseLayer?.loaded) { + return; + } + if (linesIds?.length) { + highlightLinesNetworkPlan(linesIds, baseLayer); + layer.setVisible(true); } - return () => { layer?.setVisible(false); // Reset the filter highlightLinesNetworkPlan(undefined, baseLayer); }; - }, [ - baseLayer, - baseLayer?.mapLibreMap, - featuresInfos, - layer, - linesNetworkPlanLayer, - ]); + }, [baseLayer, baseLayer?.loaded, layer, lines, linesIds]); + return null; } diff --git a/src/MobilityMap/MobilityMap.tsx b/src/MobilityMap/MobilityMap.tsx index b7cebb24..414a670c 100644 --- a/src/MobilityMap/MobilityMap.tsx +++ b/src/MobilityMap/MobilityMap.tsx @@ -87,6 +87,8 @@ function MobilityMap(props: MobilityMapProps) { const [map, setMap] = useState(); const [stationId, setStationId] = useState(); const [trainId, setTrainId] = useState(); + const [linesIds, setLinesIds] = useState(); + const [featuresInfos, setFeaturesInfos] = useState< LayerGetFeatureInfoResponse[] >([]); @@ -101,7 +103,7 @@ function MobilityMap(props: MobilityMapProps) { const [previewNotifications, setPreviewNotifications] = useState(); - const { lang, layers } = props; + const { lang, layers, lines } = props; // Apply initial visibility of layers useInitialLayersVisiblity(map, layers); @@ -136,6 +138,7 @@ function MobilityMap(props: MobilityMapProps) { isSearchOpen, isShareMenuOpen, isTracking, + linesIds, linesNetworkPlanLayer, map, mapsetLayer, @@ -169,6 +172,7 @@ function MobilityMap(props: MobilityMapProps) { setIsSearchOpen, setIsShareMenuOpen, setIsTracking, + setLinesIds, setLinesNetworkPlanLayer, setMap, setMapsetLayer, @@ -195,7 +199,6 @@ function MobilityMap(props: MobilityMapProps) { featuresInfos, featuresInfosHovered, hasDetails, - hasStations, hasGeolocation, hasLayerTree, hasLnp, @@ -206,6 +209,7 @@ function MobilityMap(props: MobilityMapProps) { hasRealtime, hasSearch, hasShare, + hasStations, hasToolbar, isEmbed, isExportMenuOpen, @@ -215,10 +219,11 @@ function MobilityMap(props: MobilityMapProps) { isSearchOpen, isShareMenuOpen, isTracking, + linesIds, linesNetworkPlanLayer, map, - notificationsLayer, mapsetLayer, + notificationsLayer, permalinkUrlSearchParams, previewNotifications, realtimeLayer, @@ -234,6 +239,11 @@ function MobilityMap(props: MobilityMapProps) { useEffect(() => { i18n.locale(lang); }, [lang]); + + useEffect(() => { + setLinesIds(lines?.split(",")); + }, [lines]); + return ( {/* There is a bug in tailwindcss@4 , variables are not imported in the shadow dom diff --git a/src/MobilityMap/MobilityMapAttributes.ts b/src/MobilityMap/MobilityMapAttributes.ts index b2e7755a..def6f643 100644 --- a/src/MobilityMap/MobilityMapAttributes.ts +++ b/src/MobilityMap/MobilityMapAttributes.ts @@ -32,6 +32,7 @@ export type MobilityMapAttributeName = | "layers" | "layersconfig" | "layertree" + | "lines" | "lnp" | "mapset" | "mapsetbbox" @@ -152,6 +153,10 @@ where: public: true, type: "boolean", }, + lines: { + description: `A comma separated list of line ids to highlight on the linesnetworkplan layer. The line ids are the original_line_id property of the lines in the network plan.
Ex: S1,RE10,RE1.`, + public: false, + }, lnp: { defaultValue: "false", description: `Add the linesnetworkplans layer to the map. This layer will display lines network plans on the map.`, diff --git a/src/MobilityMap/MobilityMapEvents.ts b/src/MobilityMap/MobilityMapEvents.ts new file mode 100644 index 00000000..d9677757 --- /dev/null +++ b/src/MobilityMap/MobilityMapEvents.ts @@ -0,0 +1,53 @@ +export interface WebComponentEventDoc { + description: string; + public?: boolean; +} + +export type MobilityMapEventName = + | "mwc:attribute" + | "mwc:messageready" + | "mwc:permalink" + | "mwc:selectedfeature" + | "mwc:singleclick" + | "mwc:stopssearchselect"; + +export type MobilityMapEvents = Record< + MobilityMapEventName, + WebComponentEventDoc +>; + +const attrs: MobilityMapEvents = { + "mwc:attribute": { + description: + "Event fired when an web component's attribute is changed. The event data contains the list of all attributes and their values.", + public: true, + }, + "mwc:messageready": { + description: + "Only if the web-component is embedded in an iframe. Message event fired when the web component is ready to receive messages from parent window.", + + public: true, + }, + "mwc:permalink": { + description: + "Event fired when the map's state changes. The event data contains the permalink URL search parameters as string.", + public: true, + }, + "mwc:selectedfeature": { + description: + "Event fired when a feature is selected on the map. The event data contains the selected feature in GeoJSON format.", + public: true, + }, + "mwc:singleclick": { + description: + "Event fired when the map is clicked. The event data contains the map coordinates in EPSG:3857 and the pixel coordinates.", + public: true, + }, + "mwc:stopssearchselect": { + description: + "Only when search attribute is 'true'. Event fired when a stop is selected in the stops search results. The event data contains the selected stop.", + public: true, + }, +}; + +export default attrs; diff --git a/src/MobilitySearch/MobilitySearchEvents.ts b/src/MobilitySearch/MobilitySearchEvents.ts new file mode 100644 index 00000000..0de3d8f9 --- /dev/null +++ b/src/MobilitySearch/MobilitySearchEvents.ts @@ -0,0 +1,21 @@ +import MobilityMapEvents from "../MobilityMap/MobilityMapEvents"; + +import type { WebComponentEventDoc } from "../MobilityMap/MobilityMapEvents"; + +export type MobilitySearchEventName = "mwc:attribute" | "mwc:stopssearchselect"; + +export type MobilitySearchAttributes = Record< + MobilitySearchEventName, + WebComponentEventDoc +>; + +const attrs: MobilitySearchAttributes = { + "mwc:attribute": MobilityMapEvents["mwc:attribute"], + "mwc:stopssearchselect": { + description: + "Event fired when a stop is selected in the stops search results. The event data contains the selected stop.", + public: true, + }, +}; + +export default attrs; diff --git a/src/OverlayDetails/OverlayDetails.tsx b/src/OverlayDetails/OverlayDetails.tsx index 9bc88e06..f855789f 100644 --- a/src/OverlayDetails/OverlayDetails.tsx +++ b/src/OverlayDetails/OverlayDetails.tsx @@ -16,6 +16,7 @@ function OverlayDetails() { realtimeLayer, selectedFeature, setFeaturesInfos, + setLinesIds, setStationId, setTrainId, stationId, @@ -51,6 +52,7 @@ function OverlayDetails() { setFeaturesInfos(null); setTrainId(null); setStationId(null); + setLinesIds(null); }} /> void; setIsShareMenuOpen: (isShareMenuOpen: boolean) => void; setIsTracking: (isTracking: boolean) => void; + setLinesIds: (linesIds: string[]) => void; setLinesNetworkPlanLayer: (layer?: MaplibreStyleLayer) => void; setMap: (map?: Map) => void; setMapsetLayer: (mapsetLayer?: MapsetLayer) => void;