import GeoJSON from 'ol/format/GeoJSON.js';
import GPX from 'ol/format/GPX.js';
import Select from 'ol/interaction/Select.js';
+import Overlay from 'ol/Overlay.js';
import MapPart from './MapPart.js';
import { SentinelCloudless } from './OpenLayerTileSources.js';
let mapPart = this;
this.#map.on('singleclick', function(e) {
let feature = null;
- // we chose only one
+ // we chose the first one
e.map.forEachFeatureAtPixel(e.pixel, function(f) {
feature = f;
+ return true;
});
if (feature !== null)
mapPart.callbacks['onFeatureSingleClick'](feature.get('path'));
}
});
}
+
+ enableFeaturePopup() {
+ // we cannot use 'this' in the function provided to OpenLayers
+ let mapPart = this;
+ /**
+ * Elements that make up the popup.
+ */
+ const container = document.getElementById('popup');
+ const content = document.getElementById('popup-content');
+ const closer = document.getElementById('popup-closer');
+
+ /**
+ * Create an overlay to anchor the popup to the map.
+ */
+ const overlay = new Overlay({
+ element: container,
+ autoPan: false,
+ autoPanAnimation: {
+ duration: 250,
+ },
+ });
+ this.#map.addOverlay(overlay);
+
+ let selected = null;
+ this.#map.on('pointermove', function(e) {
+ if (selected !== null) {
+ selected.setStyle(undefined);
+ selected = null;
+ }
+
+ e.map.forEachFeatureAtPixel(e.pixel, function(f) {
+ selected = f;
+ return true;
+ });
+
+ if (selected == null) {
+ overlay.setPosition(undefined);
+ return;
+ }
+ const coordinate = e.coordinate;
+ const path = selected.get('path');
+ const res = mapPart.callbacks['onFeaturePopup'](path);
+ if (res != null) {
+ content.innerHTML = res;
+ overlay.setPosition(coordinate);
+ } else {
+ overlay.setPosition(undefined);
+ }
+ });
+ }
}
width: 100%;
height: 100vh;
}
+
+ .ol-popup {
+ position: absolute;
+ background-color: white;
+ box-shadow: 0 1px 4px rgba(0, 0, 0, 0.2);
+ padding: 5px;
+ border-radius: 10px;
+ border: 1px solid #cccccc;
+ bottom: 12px;
+ left: -50px;
+ min-width: 130px;
+ }
+
+ .ol-popup:after,
+ .ol-popup:before {
+ top: 100%;
+ border: solid transparent;
+ content: " ";
+ height: 0;
+ width: 0;
+ position: absolute;
+ pointer-events: none;
+ }
+
+ .ol-popup:after {
+ border-top-color: white;
+ border-width: 10px;
+ left: 48px;
+ margin-left: -10px;
+ }
+
+ .ol-popup:before {
+ border-top-color: #cccccc;
+ border-width: 11px;
+ left: 48px;
+ margin-left: -11px;
+ }
+
+ .ol-popup-closer {
+ text-decoration: none;
+ position: absolute;
+ top: 2px;
+ right: 8px;
+ }
+
+ .ol-popup-closer:after {
+ content: "✖";
+ }
+
+ #popup-content {
+ font: 16px sans-serif;
+ }
</style>
</head>
<body>
<div id="map" class="map"></div>
+
+ <!-- Popup -->
+ <div id="popup" class="ol-popup">
+ <div id="popup-content"></div>
+ </div>
</body>
</html>
\ No newline at end of file
/** Event when a feature has been selected. */
record FeatureSelectedEvent(String path) {
};
+ /** Event when a feature popup is requested. */
+ record FeaturePopupEvent(String path) {
+ };
}
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionStage;
import java.util.function.Consumer;
+import java.util.function.Function;
import org.argeo.api.cms.CmsLog;
import org.argeo.app.geo.ux.JsImplementation;
* CALLBACKS
*/
public void onFeatureSelected(Consumer<FeatureSelectedEvent> toDo) {
- addCallback("FeatureSelected", (arr) -> toDo.accept(new FeatureSelectedEvent((String) arr[0])));
+ addCallback("FeatureSelected", (arr) -> {
+ toDo.accept(new FeatureSelectedEvent((String) arr[0]));
+ return null;
+ });
}
public void onFeatureSingleClick(Consumer<FeatureSingleClickEvent> toDo) {
- addCallback("FeatureSingleClick", (arr) -> toDo.accept(new FeatureSingleClickEvent((String) arr[0])));
+ addCallback("FeatureSingleClick", (arr) -> {
+ toDo.accept(new FeatureSingleClickEvent((String) arr[0]));
+ return null;
+ });
+ }
+
+ public void onFeaturePopup(Function<FeaturePopupEvent, String> toDo) {
+ addCallback("FeaturePopup", (arr) -> {
+ return toDo.apply(new FeaturePopupEvent((String) arr[0]));
+ });
}
- protected void addCallback(String suffix, Consumer<Object[]> toDo) {
+ protected void addCallback(String suffix, Function<Object[], Object> toDo) {
pageLoaded.thenAccept((ready) -> {
// browser functions must be directly on window (RAP specific)
new BrowserFunction(browser, mapVar + "__on" + suffix) {
@Override
public Object function(Object[] arguments) {
- toDo.accept(arguments);
- return null;
+ Object result = toDo.apply(arguments);
+ return result;
}
};
import org.argeo.api.cms.ux.CmsStyle;
import org.argeo.app.ux.SuiteStyle;
import org.argeo.cms.Localized;
+import org.argeo.cms.acr.ContentUtils;
import org.argeo.cms.swt.CmsSwtUtils;
import org.argeo.cms.swt.acr.Img;
import org.argeo.cms.swt.dialogs.CmsFeedback;
* CONTENT
*/
public static String toLink(Content content) {
- return content != null ? "#" + CmsSwtUtils.cleanPathForUrl(content.getPath()) : null;
+ return content != null ? "#" + ContentUtils.cleanPathForUrl(content.getPath()) : null;
}
public static Text addFormLine(Composite parent, Localized label, Content content, QNamed property,
import org.argeo.app.api.EntityType;
import org.argeo.app.swt.ux.SuiteSwtUtils;
import org.argeo.app.ux.SuiteUxEvent;
+import org.argeo.cms.acr.ContentUtils;
import org.argeo.cms.jcr.acr.JcrContent;
import org.argeo.cms.swt.CmsSwtUtils;
import org.argeo.cms.swt.dialogs.LightweightDialog;
}
public static String toLink(Node node) {
- return node != null ? "#" + CmsSwtUtils.cleanPathForUrl(JcrContent.nodeToContent(node).getPath()) : null;
+ return node != null ? "#" + ContentUtils.cleanPathForUrl(JcrContent.nodeToContent(node).getPath()) : null;
}
public static Control addLink(Composite parent, String label, Node node, CmsStyle style)