@matdata/yasqe 5.18.0 → 5.19.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build/ts/src/defaults.d.ts +1 -0
- package/build/ts/src/defaults.js +1 -0
- package/build/ts/src/defaults.js.map +1 -1
- package/build/ts/src/index.d.ts +12 -0
- package/build/ts/src/index.js +281 -3
- package/build/ts/src/index.js.map +1 -1
- package/build/ts/src/mapWidget.d.ts +7 -0
- package/build/ts/src/mapWidget.js +40 -0
- package/build/ts/src/mapWidget.js.map +1 -0
- package/build/yasqe.min.css +1 -1
- package/build/yasqe.min.css.map +3 -3
- package/build/yasqe.min.js +96 -90
- package/build/yasqe.min.js.map +4 -4
- package/package.json +3 -1
- package/src/defaults.ts +1 -0
- package/src/index.ts +309 -1
- package/src/mapWidget.ts +53 -0
- package/src/scss/buttons.scss +126 -1
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@matdata/yasqe",
|
|
3
3
|
"description": "Yet Another SPARQL Query Editor",
|
|
4
|
-
"version": "5.
|
|
4
|
+
"version": "5.19.0",
|
|
5
5
|
"main": "build/yasqe.min.js",
|
|
6
6
|
"types": "build/ts/src/index.d.ts",
|
|
7
7
|
"license": "MIT",
|
|
@@ -26,12 +26,14 @@
|
|
|
26
26
|
"dependencies": {
|
|
27
27
|
"@matdata/yasgui-utils": "^4.6.1",
|
|
28
28
|
"codemirror": "^5.51.0",
|
|
29
|
+
"leaflet": "^1.9.4",
|
|
29
30
|
"lodash-es": "^4.17.15",
|
|
30
31
|
"query-string": "^6.10.1",
|
|
31
32
|
"sparql-formatter": "^1.0.2"
|
|
32
33
|
},
|
|
33
34
|
"devDependencies": {
|
|
34
35
|
"@types/codemirror": "0.0.100",
|
|
36
|
+
"@types/leaflet": "^1.9.21",
|
|
35
37
|
"@types/lodash-es": "^4.17.3",
|
|
36
38
|
"@types/node": "^22.5.4"
|
|
37
39
|
},
|
package/src/defaults.ts
CHANGED
package/src/index.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import "./scss/yasqe.scss";
|
|
2
2
|
import "./scss/buttons.scss";
|
|
3
|
+
import "leaflet/dist/leaflet.css";
|
|
3
4
|
import { findFirstPrefixLine } from "./prefixFold";
|
|
4
5
|
import { getPrefixesFromQuery, addPrefixes, removePrefixes, Prefixes } from "./prefixUtils";
|
|
5
6
|
import { getPreviousNonWsToken, getNextNonWsToken, getCompleteToken } from "./tokenUtils";
|
|
@@ -17,6 +18,8 @@ import getDefaults from "./defaults";
|
|
|
17
18
|
import CodeMirror from "./CodeMirror";
|
|
18
19
|
import { YasqeAjaxConfig } from "./sparql";
|
|
19
20
|
import { spfmt } from "sparql-formatter";
|
|
21
|
+
import * as L from "leaflet";
|
|
22
|
+
import { coordinatesToWkt, wrapWktLiteral, WktCoordinate, WktGeometryType } from "./mapWidget";
|
|
20
23
|
|
|
21
24
|
// Toast notification timing constants
|
|
22
25
|
const TOAST_DEFAULT_DURATION = 3000; // 3 seconds
|
|
@@ -68,6 +71,9 @@ export class Yasqe extends CodeMirror {
|
|
|
68
71
|
private hamburgerBtn: HTMLButtonElement | undefined;
|
|
69
72
|
private hamburgerMenu: HTMLDivElement | undefined;
|
|
70
73
|
private shareBtn: HTMLButtonElement | undefined;
|
|
74
|
+
private mapBtn: HTMLButtonElement | undefined;
|
|
75
|
+
private mapPopup: HTMLDivElement | undefined;
|
|
76
|
+
private closeMapPopupHandler: (() => void) | undefined;
|
|
71
77
|
private isFullscreen: boolean = false;
|
|
72
78
|
private horizontalResizeWrapper?: HTMLDivElement;
|
|
73
79
|
private snippetsBar?: HTMLDivElement;
|
|
@@ -631,7 +637,26 @@ export class Yasqe extends CodeMirror {
|
|
|
631
637
|
}
|
|
632
638
|
|
|
633
639
|
/**
|
|
634
|
-
* Draw
|
|
640
|
+
* Draw map btn (FIFTH)
|
|
641
|
+
*/
|
|
642
|
+
if (this.config.showMapButton) {
|
|
643
|
+
this.mapBtn = document.createElement("button");
|
|
644
|
+
addClass(this.mapBtn, "yasqe_mapButton");
|
|
645
|
+
const mapIcon = document.createElement("i");
|
|
646
|
+
addClass(mapIcon, "fas");
|
|
647
|
+
addClass(mapIcon, "fa-map-location-dot");
|
|
648
|
+
mapIcon.setAttribute("aria-hidden", "true");
|
|
649
|
+
this.mapBtn.appendChild(mapIcon);
|
|
650
|
+
this.mapBtn.onclick = (event: MouseEvent) => {
|
|
651
|
+
this.toggleMapPopup(buttons, { x: event.clientX, y: event.clientY });
|
|
652
|
+
};
|
|
653
|
+
this.mapBtn.title = "Open map";
|
|
654
|
+
this.mapBtn.setAttribute("aria-label", "Open map");
|
|
655
|
+
buttons.appendChild(this.mapBtn);
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
/**
|
|
659
|
+
* Draw fullscreen btn (SIXTH)
|
|
635
660
|
*/
|
|
636
661
|
this.fullscreenBtn = document.createElement("button");
|
|
637
662
|
addClass(this.fullscreenBtn, "yasqe_fullscreenButton");
|
|
@@ -743,6 +768,24 @@ export class Yasqe extends CodeMirror {
|
|
|
743
768
|
this.hamburgerMenu.appendChild(formatItem);
|
|
744
769
|
}
|
|
745
770
|
|
|
771
|
+
if (this.config.showMapButton) {
|
|
772
|
+
const mapItem = document.createElement("button");
|
|
773
|
+
mapItem.className = "yasqe_hamburgerMenuItem";
|
|
774
|
+
const mapIconMenu = document.createElement("i");
|
|
775
|
+
addClass(mapIconMenu, "fas");
|
|
776
|
+
addClass(mapIconMenu, "fa-map-location-dot");
|
|
777
|
+
mapIconMenu.setAttribute("aria-hidden", "true");
|
|
778
|
+
mapItem.appendChild(mapIconMenu);
|
|
779
|
+
const mapLabel = document.createElement("span");
|
|
780
|
+
mapLabel.textContent = "Open map";
|
|
781
|
+
mapItem.appendChild(mapLabel);
|
|
782
|
+
mapItem.onclick = (event: MouseEvent) => {
|
|
783
|
+
this.closeHamburgerMenu();
|
|
784
|
+
this.toggleMapPopup(buttons, { x: event.clientX, y: event.clientY });
|
|
785
|
+
};
|
|
786
|
+
this.hamburgerMenu.appendChild(mapItem);
|
|
787
|
+
}
|
|
788
|
+
|
|
746
789
|
const fullscreenItem = document.createElement("button");
|
|
747
790
|
fullscreenItem.className = "yasqe_hamburgerMenuItem";
|
|
748
791
|
const fullscreenIconMenu = document.createElement("i");
|
|
@@ -794,6 +837,269 @@ export class Yasqe extends CodeMirror {
|
|
|
794
837
|
removeClass(this.hamburgerMenu, "active");
|
|
795
838
|
this.hamburgerBtn.setAttribute("aria-expanded", "false");
|
|
796
839
|
}
|
|
840
|
+
private toggleMapPopup(buttons: HTMLDivElement, anchorPoint?: { x: number; y: number }) {
|
|
841
|
+
if (this.mapPopup) {
|
|
842
|
+
this.closeMapPopupHandler?.();
|
|
843
|
+
return;
|
|
844
|
+
}
|
|
845
|
+
this.createMapPopup(buttons, anchorPoint);
|
|
846
|
+
}
|
|
847
|
+
|
|
848
|
+
private createMapPopup(buttons: HTMLDivElement, anchorPoint?: { x: number; y: number }) {
|
|
849
|
+
this.mapPopup = document.createElement("div");
|
|
850
|
+
this.mapPopup.className = "yasqe_mapPopup";
|
|
851
|
+
buttons.appendChild(this.mapPopup);
|
|
852
|
+
|
|
853
|
+
const header = document.createElement("div");
|
|
854
|
+
header.className = "yasqe_mapPopup_header";
|
|
855
|
+
const title = document.createElement("div");
|
|
856
|
+
title.className = "yasqe_mapPopup_title";
|
|
857
|
+
title.textContent = "Create WKT";
|
|
858
|
+
header.appendChild(title);
|
|
859
|
+
|
|
860
|
+
const closeBtn = document.createElement("button");
|
|
861
|
+
closeBtn.className = "yasqe_mapPopup_close";
|
|
862
|
+
closeBtn.setAttribute("aria-label", "Close map");
|
|
863
|
+
closeBtn.innerHTML = "×";
|
|
864
|
+
header.appendChild(closeBtn);
|
|
865
|
+
this.mapPopup.appendChild(header);
|
|
866
|
+
|
|
867
|
+
const mapContainer = document.createElement("div");
|
|
868
|
+
mapContainer.className = "yasqe_mapPopup_map";
|
|
869
|
+
this.mapPopup.appendChild(mapContainer);
|
|
870
|
+
|
|
871
|
+
const geometryControls = document.createElement("div");
|
|
872
|
+
geometryControls.className = "yasqe_mapPopup_geometryControls leaflet-bar";
|
|
873
|
+
mapContainer.appendChild(geometryControls);
|
|
874
|
+
|
|
875
|
+
const hint = document.createElement("div");
|
|
876
|
+
hint.className = "yasqe_mapPopup_hint";
|
|
877
|
+
hint.textContent = "Click the map to add coordinates";
|
|
878
|
+
this.mapPopup.appendChild(hint);
|
|
879
|
+
|
|
880
|
+
const preview = document.createElement("textarea");
|
|
881
|
+
preview.className = "yasqe_mapPopup_preview";
|
|
882
|
+
preview.readOnly = true;
|
|
883
|
+
preview.rows = 2;
|
|
884
|
+
this.mapPopup.appendChild(preview);
|
|
885
|
+
|
|
886
|
+
const actions = document.createElement("div");
|
|
887
|
+
actions.className = "yasqe_mapPopup_actions";
|
|
888
|
+
const undoBtn = document.createElement("button");
|
|
889
|
+
undoBtn.className = "yasqe_btn yasqe_btn-sm";
|
|
890
|
+
undoBtn.textContent = "Undo";
|
|
891
|
+
const clearBtn = document.createElement("button");
|
|
892
|
+
clearBtn.className = "yasqe_btn yasqe_btn-sm";
|
|
893
|
+
clearBtn.textContent = "Clear";
|
|
894
|
+
const insertBtn = document.createElement("button");
|
|
895
|
+
insertBtn.className = "yasqe_btn yasqe_btn-sm";
|
|
896
|
+
insertBtn.textContent = "Insert WKT";
|
|
897
|
+
actions.appendChild(undoBtn);
|
|
898
|
+
actions.appendChild(clearBtn);
|
|
899
|
+
actions.appendChild(insertBtn);
|
|
900
|
+
this.mapPopup.appendChild(actions);
|
|
901
|
+
|
|
902
|
+
let geometryType: WktGeometryType = "POINT";
|
|
903
|
+
let coordinates: WktCoordinate[] = [];
|
|
904
|
+
let marker: L.Marker | undefined;
|
|
905
|
+
let shape: L.Polyline | L.Polygon | undefined;
|
|
906
|
+
const geometryButtons: Partial<Record<WktGeometryType, HTMLButtonElement>> = {};
|
|
907
|
+
|
|
908
|
+
const getMapAccentColor = () => {
|
|
909
|
+
const accent = getComputedStyle(this.rootEl).getPropertyValue("--yasgui-accent-color").trim();
|
|
910
|
+
return accent || "#337ab7";
|
|
911
|
+
};
|
|
912
|
+
|
|
913
|
+
const map = L.map(mapContainer, {
|
|
914
|
+
zoomControl: true,
|
|
915
|
+
attributionControl: true,
|
|
916
|
+
}).setView([51.505, -0.09], 2);
|
|
917
|
+
map.getContainer().style.cursor = "crosshair";
|
|
918
|
+
L.tileLayer("https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png", {
|
|
919
|
+
attribution: "© OpenStreetMap contributors",
|
|
920
|
+
maxZoom: 19,
|
|
921
|
+
}).addTo(map);
|
|
922
|
+
L.DomEvent.disableClickPropagation(geometryControls);
|
|
923
|
+
L.DomEvent.disableScrollPropagation(geometryControls);
|
|
924
|
+
|
|
925
|
+
const updatePreview = () => {
|
|
926
|
+
const wkt = coordinatesToWkt(geometryType, coordinates);
|
|
927
|
+
preview.value = wkt || "";
|
|
928
|
+
insertBtn.disabled = !wkt;
|
|
929
|
+
undoBtn.disabled = coordinates.length === 0;
|
|
930
|
+
clearBtn.disabled = coordinates.length === 0;
|
|
931
|
+
};
|
|
932
|
+
|
|
933
|
+
const redrawGeometry = () => {
|
|
934
|
+
if (marker) {
|
|
935
|
+
map.removeLayer(marker);
|
|
936
|
+
marker = undefined;
|
|
937
|
+
}
|
|
938
|
+
if (shape) {
|
|
939
|
+
map.removeLayer(shape);
|
|
940
|
+
shape = undefined;
|
|
941
|
+
}
|
|
942
|
+
|
|
943
|
+
const latLngs = coordinates.map((coord) => L.latLng(coord.lat, coord.lng));
|
|
944
|
+
const accentColor = getMapAccentColor();
|
|
945
|
+
if (geometryType === "POINT" && latLngs.length > 0) {
|
|
946
|
+
marker = L.marker(latLngs[latLngs.length - 1]).addTo(map);
|
|
947
|
+
} else if (geometryType === "LINESTRING" && latLngs.length > 0) {
|
|
948
|
+
shape = L.polyline(latLngs, { color: accentColor }).addTo(map);
|
|
949
|
+
} else if (geometryType === "POLYGON" && latLngs.length > 0) {
|
|
950
|
+
shape = L.polygon(latLngs, { color: accentColor }).addTo(map);
|
|
951
|
+
}
|
|
952
|
+
|
|
953
|
+
updatePreview();
|
|
954
|
+
};
|
|
955
|
+
|
|
956
|
+
const updateGeometryButtons = () => {
|
|
957
|
+
for (const type of ["POINT", "LINESTRING", "POLYGON"] as WktGeometryType[]) {
|
|
958
|
+
const button = geometryButtons[type];
|
|
959
|
+
if (!button) continue;
|
|
960
|
+
button.classList.toggle("active", type === geometryType);
|
|
961
|
+
button.setAttribute("aria-pressed", type === geometryType ? "true" : "false");
|
|
962
|
+
}
|
|
963
|
+
};
|
|
964
|
+
|
|
965
|
+
const createGeometryButton = (type: WktGeometryType, iconClass: string, label: string) => {
|
|
966
|
+
const button = document.createElement("button");
|
|
967
|
+
button.type = "button";
|
|
968
|
+
button.className = "yasqe_mapPopup_geometryBtn";
|
|
969
|
+
button.title = label;
|
|
970
|
+
button.setAttribute("aria-label", label);
|
|
971
|
+
button.setAttribute("aria-pressed", "false");
|
|
972
|
+
const icon = document.createElement("i");
|
|
973
|
+
addClass(icon, "fas");
|
|
974
|
+
addClass(icon, iconClass);
|
|
975
|
+
icon.setAttribute("aria-hidden", "true");
|
|
976
|
+
button.appendChild(icon);
|
|
977
|
+
button.onclick = (event: MouseEvent) => {
|
|
978
|
+
event.preventDefault();
|
|
979
|
+
event.stopPropagation();
|
|
980
|
+
if (geometryType === type) return;
|
|
981
|
+
geometryType = type;
|
|
982
|
+
coordinates = [];
|
|
983
|
+
updateGeometryButtons();
|
|
984
|
+
redrawGeometry();
|
|
985
|
+
};
|
|
986
|
+
geometryControls.appendChild(button);
|
|
987
|
+
return button;
|
|
988
|
+
};
|
|
989
|
+
|
|
990
|
+
geometryButtons.POINT = createGeometryButton("POINT", "fa-location-dot", "Point");
|
|
991
|
+
geometryButtons.LINESTRING = createGeometryButton("LINESTRING", "fa-slash", "LineString");
|
|
992
|
+
geometryButtons.POLYGON = createGeometryButton("POLYGON", "fa-draw-polygon", "Polygon");
|
|
993
|
+
updateGeometryButtons();
|
|
994
|
+
|
|
995
|
+
const closePopup = () => {
|
|
996
|
+
document.removeEventListener("keydown", handleKeyDown);
|
|
997
|
+
document.body.removeEventListener("click", closeOnOutsideClick, true);
|
|
998
|
+
map.remove();
|
|
999
|
+
this.mapPopup?.remove();
|
|
1000
|
+
this.mapPopup = undefined;
|
|
1001
|
+
this.closeMapPopupHandler = undefined;
|
|
1002
|
+
};
|
|
1003
|
+
|
|
1004
|
+
const handleKeyDown = (event: KeyboardEvent) => {
|
|
1005
|
+
if (event.key === "Escape") {
|
|
1006
|
+
closePopup();
|
|
1007
|
+
}
|
|
1008
|
+
};
|
|
1009
|
+
|
|
1010
|
+
const closeOnOutsideClick = (event: MouseEvent) => {
|
|
1011
|
+
if (!this.mapPopup) return;
|
|
1012
|
+
if (this.mapPopup.contains(event.target as Node) || this.mapBtn?.contains(event.target as Node)) return;
|
|
1013
|
+
closePopup();
|
|
1014
|
+
};
|
|
1015
|
+
|
|
1016
|
+
map.on("click", (event: L.LeafletMouseEvent) => {
|
|
1017
|
+
const coordinate: WktCoordinate = { lat: event.latlng.lat, lng: event.latlng.lng };
|
|
1018
|
+
if (geometryType === "POINT") {
|
|
1019
|
+
coordinates = [coordinate];
|
|
1020
|
+
} else {
|
|
1021
|
+
coordinates.push(coordinate);
|
|
1022
|
+
}
|
|
1023
|
+
redrawGeometry();
|
|
1024
|
+
});
|
|
1025
|
+
|
|
1026
|
+
undoBtn.onclick = () => {
|
|
1027
|
+
coordinates.pop();
|
|
1028
|
+
redrawGeometry();
|
|
1029
|
+
};
|
|
1030
|
+
|
|
1031
|
+
clearBtn.onclick = () => {
|
|
1032
|
+
coordinates = [];
|
|
1033
|
+
redrawGeometry();
|
|
1034
|
+
};
|
|
1035
|
+
|
|
1036
|
+
insertBtn.onclick = () => {
|
|
1037
|
+
const wkt = coordinatesToWkt(geometryType, coordinates);
|
|
1038
|
+
if (!wkt) return;
|
|
1039
|
+
this.replaceSelection(wrapWktLiteral(wkt));
|
|
1040
|
+
closePopup();
|
|
1041
|
+
};
|
|
1042
|
+
|
|
1043
|
+
closeBtn.onclick = () => closePopup();
|
|
1044
|
+
this.closeMapPopupHandler = closePopup;
|
|
1045
|
+
|
|
1046
|
+
document.addEventListener("keydown", handleKeyDown);
|
|
1047
|
+
document.body.addEventListener("click", closeOnOutsideClick, true);
|
|
1048
|
+
|
|
1049
|
+
const positionPopup = () => {
|
|
1050
|
+
if (!this.mapPopup) return;
|
|
1051
|
+
const popupWidth = this.mapPopup.offsetWidth || this.mapPopup.scrollWidth || 520;
|
|
1052
|
+
const popupHeight = this.mapPopup.offsetHeight || this.mapPopup.scrollHeight || this.mapPopup.clientHeight;
|
|
1053
|
+
const viewportWidth = window.innerWidth;
|
|
1054
|
+
const viewportHeight = window.innerHeight;
|
|
1055
|
+
|
|
1056
|
+
this.mapPopup.style.position = "fixed";
|
|
1057
|
+
this.mapPopup.style.bottom = "auto";
|
|
1058
|
+
|
|
1059
|
+
if (anchorPoint) {
|
|
1060
|
+
const padding = 20;
|
|
1061
|
+
let left = anchorPoint.x + 12;
|
|
1062
|
+
let top = anchorPoint.y + 12;
|
|
1063
|
+
if (left + popupWidth > viewportWidth - padding) {
|
|
1064
|
+
left = Math.max(padding, anchorPoint.x - popupWidth - 12);
|
|
1065
|
+
}
|
|
1066
|
+
if (top + popupHeight > viewportHeight - padding) {
|
|
1067
|
+
top = Math.max(padding, anchorPoint.y - popupHeight - 12);
|
|
1068
|
+
}
|
|
1069
|
+
this.mapPopup.style.left = `${Math.max(padding, left)}px`;
|
|
1070
|
+
this.mapPopup.style.top = `${Math.max(padding, top)}px`;
|
|
1071
|
+
this.mapPopup.style.right = "auto";
|
|
1072
|
+
return;
|
|
1073
|
+
}
|
|
1074
|
+
|
|
1075
|
+
const buttonsRect = buttons.getBoundingClientRect();
|
|
1076
|
+
const fallbackLeft = Math.min(viewportWidth - popupWidth - 20, Math.max(20, buttonsRect.right - popupWidth));
|
|
1077
|
+
this.mapPopup.style.left = `${Math.max(20, fallbackLeft)}px`;
|
|
1078
|
+
this.mapPopup.style.top = "20px";
|
|
1079
|
+
this.mapPopup.style.right = "auto";
|
|
1080
|
+
};
|
|
1081
|
+
|
|
1082
|
+
if (typeof window !== "undefined" && typeof window.requestAnimationFrame === "function") {
|
|
1083
|
+
window.requestAnimationFrame(() => {
|
|
1084
|
+
positionPopup();
|
|
1085
|
+
map.invalidateSize();
|
|
1086
|
+
});
|
|
1087
|
+
} else {
|
|
1088
|
+
setTimeout(() => {
|
|
1089
|
+
positionPopup();
|
|
1090
|
+
map.invalidateSize();
|
|
1091
|
+
}, 0);
|
|
1092
|
+
}
|
|
1093
|
+
|
|
1094
|
+
redrawGeometry();
|
|
1095
|
+
}
|
|
1096
|
+
|
|
1097
|
+
public toggleMapWidget(anchorPoint?: { x: number; y: number }) {
|
|
1098
|
+
const buttons = this.getWrapperElement().querySelector(".yasqe_buttons");
|
|
1099
|
+
if (!buttons || !(buttons instanceof HTMLDivElement)) return;
|
|
1100
|
+
this.toggleMapPopup(buttons, anchorPoint);
|
|
1101
|
+
}
|
|
1102
|
+
|
|
797
1103
|
public toggleFullscreen() {
|
|
798
1104
|
this.isFullscreen = !this.isFullscreen;
|
|
799
1105
|
if (this.isFullscreen) {
|
|
@@ -1857,6 +2163,7 @@ export class Yasqe extends CodeMirror {
|
|
|
1857
2163
|
public destroy() {
|
|
1858
2164
|
// Abort running query
|
|
1859
2165
|
this.abortQuery();
|
|
2166
|
+
this.closeMapPopupHandler?.();
|
|
1860
2167
|
this.unregisterEventListeners();
|
|
1861
2168
|
this.horizontalResizeWrapper?.removeEventListener("mousedown", this.initDrag, false);
|
|
1862
2169
|
this.horizontalResizeWrapper?.removeEventListener("dblclick", this.expandEditor);
|
|
@@ -2043,6 +2350,7 @@ export interface Config extends Partial<CodeMirror.EditorConfiguration> {
|
|
|
2043
2350
|
queryingDisabled: string | undefined; // The string will be the message displayed when hovered
|
|
2044
2351
|
prefixCcApi: string; // the suggested default prefixes URL API getter
|
|
2045
2352
|
showFormatButton: boolean; // Show a button to format the query
|
|
2353
|
+
showMapButton: boolean; // Show a button to create WKT literals from map input
|
|
2046
2354
|
checkConstructVariables: boolean; // Check for undefined variables in CONSTRUCT queries
|
|
2047
2355
|
snippets: Snippet[]; // Code snippets to show in the snippets bar
|
|
2048
2356
|
showSnippetsBar: boolean; // Show the snippets bar
|
package/src/mapWidget.ts
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
export type WktGeometryType = "POINT" | "LINESTRING" | "POLYGON";
|
|
2
|
+
const GEO_WKT_LITERAL_DATATYPE = "<http://www.opengis.net/ont/geosparql#wktLiteral>";
|
|
3
|
+
|
|
4
|
+
export interface WktCoordinate {
|
|
5
|
+
lat: number;
|
|
6
|
+
lng: number;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
const MAX_DECIMALS = 6;
|
|
10
|
+
|
|
11
|
+
function normalizeCoordinate(value: number): string {
|
|
12
|
+
return Number(value.toFixed(MAX_DECIMALS)).toString();
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function sameCoordinate(left: WktCoordinate, right: WktCoordinate): boolean {
|
|
16
|
+
return left.lat === right.lat && left.lng === right.lng;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function formatCoordinates(coordinates: WktCoordinate[]) {
|
|
20
|
+
return coordinates.map((coord) => `${normalizeCoordinate(coord.lng)} ${normalizeCoordinate(coord.lat)}`).join(", ");
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function coordinatesToWkt(geometryType: WktGeometryType, coordinates: WktCoordinate[]): string | undefined {
|
|
24
|
+
if (!Array.isArray(coordinates) || coordinates.length === 0) return undefined;
|
|
25
|
+
|
|
26
|
+
if (geometryType === "POINT") {
|
|
27
|
+
const point = coordinates[coordinates.length - 1];
|
|
28
|
+
return `POINT(${normalizeCoordinate(point.lng)} ${normalizeCoordinate(point.lat)})`;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
if (geometryType === "LINESTRING") {
|
|
32
|
+
if (coordinates.length < 2) return undefined;
|
|
33
|
+
return `LINESTRING(${formatCoordinates(coordinates)})`;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
if (geometryType !== "POLYGON") return undefined;
|
|
37
|
+
if (coordinates.length < 3) return undefined;
|
|
38
|
+
const uniqueCoordinates = new Set(
|
|
39
|
+
coordinates.map((coord) => `${normalizeCoordinate(coord.lng)} ${normalizeCoordinate(coord.lat)}`),
|
|
40
|
+
);
|
|
41
|
+
if (uniqueCoordinates.size < 3) return undefined;
|
|
42
|
+
|
|
43
|
+
const polygonCoordinates = coordinates.slice();
|
|
44
|
+
if (!sameCoordinate(polygonCoordinates[0], polygonCoordinates[polygonCoordinates.length - 1])) {
|
|
45
|
+
polygonCoordinates.push(polygonCoordinates[0]);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
return `POLYGON((${formatCoordinates(polygonCoordinates)}))`;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export function wrapWktLiteral(wkt: string) {
|
|
52
|
+
return `"${wkt}"^^${GEO_WKT_LITERAL_DATATYPE}`;
|
|
53
|
+
}
|
package/src/scss/buttons.scss
CHANGED
|
@@ -292,6 +292,129 @@
|
|
|
292
292
|
}
|
|
293
293
|
}
|
|
294
294
|
}
|
|
295
|
+
.yasqe_mapPopup {
|
|
296
|
+
position: absolute;
|
|
297
|
+
padding: 12px;
|
|
298
|
+
background-color: var(--yasgui-bg-primary, #fff);
|
|
299
|
+
border: 1px solid var(--yasgui-border-color, #e3e3e3);
|
|
300
|
+
border-radius: 4px;
|
|
301
|
+
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
|
|
302
|
+
box-sizing: border-box;
|
|
303
|
+
width: min(520px, calc(100vw - 40px));
|
|
304
|
+
max-height: calc(100vh - 40px);
|
|
305
|
+
overflow-y: auto;
|
|
306
|
+
overflow-x: hidden;
|
|
307
|
+
display: flex;
|
|
308
|
+
flex-direction: column;
|
|
309
|
+
gap: 8px;
|
|
310
|
+
z-index: 10001;
|
|
311
|
+
|
|
312
|
+
.yasqe_mapPopup_header {
|
|
313
|
+
display: flex;
|
|
314
|
+
align-items: center;
|
|
315
|
+
justify-content: space-between;
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
.yasqe_mapPopup_title {
|
|
319
|
+
font-weight: 600;
|
|
320
|
+
font-size: 14px;
|
|
321
|
+
color: var(--yasgui-text-primary, #333);
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
.yasqe_mapPopup_close {
|
|
325
|
+
border: none;
|
|
326
|
+
background: none;
|
|
327
|
+
color: var(--yasgui-text-secondary, #505050);
|
|
328
|
+
font-size: 20px;
|
|
329
|
+
line-height: 1;
|
|
330
|
+
cursor: pointer;
|
|
331
|
+
margin-left: 0;
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
.yasqe_mapPopup_map {
|
|
335
|
+
position: relative;
|
|
336
|
+
height: 280px;
|
|
337
|
+
border: 1px solid var(--yasgui-border-color, #e3e3e3);
|
|
338
|
+
border-radius: 4px;
|
|
339
|
+
|
|
340
|
+
.leaflet-container,
|
|
341
|
+
.leaflet-container .leaflet-tile-pane,
|
|
342
|
+
.leaflet-container .leaflet-overlay-pane {
|
|
343
|
+
cursor: crosshair !important;
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
.leaflet-control-container,
|
|
347
|
+
.leaflet-control-container * {
|
|
348
|
+
cursor: pointer;
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
.yasqe_mapPopup_geometryControls {
|
|
353
|
+
position: absolute;
|
|
354
|
+
top: 10px;
|
|
355
|
+
right: 10px;
|
|
356
|
+
z-index: 500;
|
|
357
|
+
display: flex;
|
|
358
|
+
flex-direction: column;
|
|
359
|
+
border: 1px solid var(--yasgui-border-color, #e3e3e3);
|
|
360
|
+
border-radius: 4px;
|
|
361
|
+
overflow: hidden;
|
|
362
|
+
box-shadow: var(--yasgui-graph-shadow, 0 2px 4px rgba(0, 0, 0, 0.2));
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
.yasqe_mapPopup_geometryBtn {
|
|
366
|
+
appearance: none;
|
|
367
|
+
width: 30px;
|
|
368
|
+
height: 30px;
|
|
369
|
+
margin: 0;
|
|
370
|
+
border: none;
|
|
371
|
+
border-radius: 0;
|
|
372
|
+
border-bottom: 1px solid var(--yasgui-border-color, #e3e3e3);
|
|
373
|
+
background: var(--yasgui-bg-primary, #fff);
|
|
374
|
+
color: var(--yasgui-text-primary, #333);
|
|
375
|
+
cursor: pointer;
|
|
376
|
+
display: flex;
|
|
377
|
+
align-items: center;
|
|
378
|
+
justify-content: center;
|
|
379
|
+
|
|
380
|
+
i {
|
|
381
|
+
font-size: 14px;
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
&:hover {
|
|
385
|
+
background: var(--yasgui-bg-secondary, #f0f0f0);
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
&.active {
|
|
389
|
+
background: var(--yasgui-accent-color, #337ab7);
|
|
390
|
+
color: #fff;
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
&:last-child {
|
|
394
|
+
border-bottom: none;
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
.yasqe_mapPopup_hint {
|
|
399
|
+
color: var(--yasgui-text-secondary, #666);
|
|
400
|
+
font-size: 12px;
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
.yasqe_mapPopup_preview {
|
|
404
|
+
width: 100%;
|
|
405
|
+
box-sizing: border-box;
|
|
406
|
+
resize: vertical;
|
|
407
|
+
border: 1px solid var(--yasgui-border-color, #e3e3e3);
|
|
408
|
+
background-color: var(--yasgui-bg-secondary, #f8f8f8);
|
|
409
|
+
color: var(--yasgui-text-primary, #333);
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
.yasqe_mapPopup_actions {
|
|
413
|
+
display: flex;
|
|
414
|
+
justify-content: flex-end;
|
|
415
|
+
gap: 6px;
|
|
416
|
+
}
|
|
417
|
+
}
|
|
295
418
|
|
|
296
419
|
.yasqe_saveWrapper {
|
|
297
420
|
position: relative;
|
|
@@ -323,7 +446,8 @@
|
|
|
323
446
|
}
|
|
324
447
|
}
|
|
325
448
|
|
|
326
|
-
.yasqe_formatButton
|
|
449
|
+
.yasqe_formatButton,
|
|
450
|
+
.yasqe_mapButton {
|
|
327
451
|
display: inline-flex;
|
|
328
452
|
align-items: center;
|
|
329
453
|
justify-content: center;
|
|
@@ -451,6 +575,7 @@
|
|
|
451
575
|
.yasqe_share,
|
|
452
576
|
.yasqe_saveWrapper,
|
|
453
577
|
.yasqe_formatButton,
|
|
578
|
+
.yasqe_mapButton,
|
|
454
579
|
.yasqe_fullscreenButton {
|
|
455
580
|
display: none !important;
|
|
456
581
|
}
|