@matdata/yasqe 5.18.0 → 5.19.1
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 +285 -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 +314 -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.1",
|
|
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,274 @@ 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 mapWrapper = document.createElement("div");
|
|
868
|
+
mapWrapper.className = "yasqe_mapPopup_map";
|
|
869
|
+
this.mapPopup.appendChild(mapWrapper);
|
|
870
|
+
|
|
871
|
+
const mapContainer = document.createElement("div");
|
|
872
|
+
mapContainer.style.height = "100%";
|
|
873
|
+
mapContainer.style.width = "100%";
|
|
874
|
+
mapWrapper.appendChild(mapContainer);
|
|
875
|
+
|
|
876
|
+
const geometryControls = document.createElement("div");
|
|
877
|
+
geometryControls.className = "yasqe_mapPopup_geometryControls leaflet-bar";
|
|
878
|
+
mapWrapper.appendChild(geometryControls);
|
|
879
|
+
|
|
880
|
+
const hint = document.createElement("div");
|
|
881
|
+
hint.className = "yasqe_mapPopup_hint";
|
|
882
|
+
hint.textContent = "Click the map to add coordinates";
|
|
883
|
+
this.mapPopup.appendChild(hint);
|
|
884
|
+
|
|
885
|
+
const preview = document.createElement("textarea");
|
|
886
|
+
preview.className = "yasqe_mapPopup_preview";
|
|
887
|
+
preview.readOnly = true;
|
|
888
|
+
preview.rows = 2;
|
|
889
|
+
this.mapPopup.appendChild(preview);
|
|
890
|
+
|
|
891
|
+
const actions = document.createElement("div");
|
|
892
|
+
actions.className = "yasqe_mapPopup_actions";
|
|
893
|
+
const undoBtn = document.createElement("button");
|
|
894
|
+
undoBtn.className = "yasqe_btn yasqe_btn-sm";
|
|
895
|
+
undoBtn.textContent = "Undo";
|
|
896
|
+
const clearBtn = document.createElement("button");
|
|
897
|
+
clearBtn.className = "yasqe_btn yasqe_btn-sm";
|
|
898
|
+
clearBtn.textContent = "Clear";
|
|
899
|
+
const insertBtn = document.createElement("button");
|
|
900
|
+
insertBtn.className = "yasqe_btn yasqe_btn-sm";
|
|
901
|
+
insertBtn.textContent = "Insert WKT";
|
|
902
|
+
actions.appendChild(undoBtn);
|
|
903
|
+
actions.appendChild(clearBtn);
|
|
904
|
+
actions.appendChild(insertBtn);
|
|
905
|
+
this.mapPopup.appendChild(actions);
|
|
906
|
+
|
|
907
|
+
let geometryType: WktGeometryType = "POINT";
|
|
908
|
+
let coordinates: WktCoordinate[] = [];
|
|
909
|
+
let marker: L.Marker | undefined;
|
|
910
|
+
let shape: L.Polyline | L.Polygon | undefined;
|
|
911
|
+
const geometryButtons: Partial<Record<WktGeometryType, HTMLButtonElement>> = {};
|
|
912
|
+
|
|
913
|
+
const getMapAccentColor = () => {
|
|
914
|
+
const accent = getComputedStyle(this.rootEl).getPropertyValue("--yasgui-accent-color").trim();
|
|
915
|
+
return accent || "#337ab7";
|
|
916
|
+
};
|
|
917
|
+
|
|
918
|
+
const map = L.map(mapContainer, {
|
|
919
|
+
zoomControl: true,
|
|
920
|
+
attributionControl: true,
|
|
921
|
+
}).setView([51.505, -0.09], 2);
|
|
922
|
+
map.getContainer().style.cursor = "crosshair";
|
|
923
|
+
L.tileLayer("https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png", {
|
|
924
|
+
attribution: "© OpenStreetMap contributors",
|
|
925
|
+
maxZoom: 19,
|
|
926
|
+
}).addTo(map);
|
|
927
|
+
L.DomEvent.disableClickPropagation(geometryControls);
|
|
928
|
+
L.DomEvent.disableScrollPropagation(geometryControls);
|
|
929
|
+
|
|
930
|
+
const updatePreview = () => {
|
|
931
|
+
const wkt = coordinatesToWkt(geometryType, coordinates);
|
|
932
|
+
preview.value = wkt || "";
|
|
933
|
+
insertBtn.disabled = !wkt;
|
|
934
|
+
undoBtn.disabled = coordinates.length === 0;
|
|
935
|
+
clearBtn.disabled = coordinates.length === 0;
|
|
936
|
+
};
|
|
937
|
+
|
|
938
|
+
const redrawGeometry = () => {
|
|
939
|
+
if (marker) {
|
|
940
|
+
map.removeLayer(marker);
|
|
941
|
+
marker = undefined;
|
|
942
|
+
}
|
|
943
|
+
if (shape) {
|
|
944
|
+
map.removeLayer(shape);
|
|
945
|
+
shape = undefined;
|
|
946
|
+
}
|
|
947
|
+
|
|
948
|
+
const latLngs = coordinates.map((coord) => L.latLng(coord.lat, coord.lng));
|
|
949
|
+
const accentColor = getMapAccentColor();
|
|
950
|
+
if (geometryType === "POINT" && latLngs.length > 0) {
|
|
951
|
+
marker = L.marker(latLngs[latLngs.length - 1]).addTo(map);
|
|
952
|
+
} else if (geometryType === "LINESTRING" && latLngs.length > 0) {
|
|
953
|
+
shape = L.polyline(latLngs, { color: accentColor }).addTo(map);
|
|
954
|
+
} else if (geometryType === "POLYGON" && latLngs.length > 0) {
|
|
955
|
+
shape = L.polygon(latLngs, { color: accentColor }).addTo(map);
|
|
956
|
+
}
|
|
957
|
+
|
|
958
|
+
updatePreview();
|
|
959
|
+
};
|
|
960
|
+
|
|
961
|
+
const updateGeometryButtons = () => {
|
|
962
|
+
for (const type of ["POINT", "LINESTRING", "POLYGON"] as WktGeometryType[]) {
|
|
963
|
+
const button = geometryButtons[type];
|
|
964
|
+
if (!button) continue;
|
|
965
|
+
button.classList.toggle("active", type === geometryType);
|
|
966
|
+
button.setAttribute("aria-pressed", type === geometryType ? "true" : "false");
|
|
967
|
+
}
|
|
968
|
+
};
|
|
969
|
+
|
|
970
|
+
const createGeometryButton = (type: WktGeometryType, iconClass: string, label: string) => {
|
|
971
|
+
const button = document.createElement("button");
|
|
972
|
+
button.type = "button";
|
|
973
|
+
button.className = "yasqe_mapPopup_geometryBtn";
|
|
974
|
+
button.title = label;
|
|
975
|
+
button.setAttribute("aria-label", label);
|
|
976
|
+
button.setAttribute("aria-pressed", "false");
|
|
977
|
+
const icon = document.createElement("i");
|
|
978
|
+
addClass(icon, "fas");
|
|
979
|
+
addClass(icon, iconClass);
|
|
980
|
+
icon.setAttribute("aria-hidden", "true");
|
|
981
|
+
button.appendChild(icon);
|
|
982
|
+
button.onclick = (event: MouseEvent) => {
|
|
983
|
+
event.preventDefault();
|
|
984
|
+
event.stopPropagation();
|
|
985
|
+
if (geometryType === type) return;
|
|
986
|
+
geometryType = type;
|
|
987
|
+
coordinates = [];
|
|
988
|
+
updateGeometryButtons();
|
|
989
|
+
redrawGeometry();
|
|
990
|
+
};
|
|
991
|
+
geometryControls.appendChild(button);
|
|
992
|
+
return button;
|
|
993
|
+
};
|
|
994
|
+
|
|
995
|
+
geometryButtons.POINT = createGeometryButton("POINT", "fa-location-dot", "Point");
|
|
996
|
+
geometryButtons.LINESTRING = createGeometryButton("LINESTRING", "fa-slash", "LineString");
|
|
997
|
+
geometryButtons.POLYGON = createGeometryButton("POLYGON", "fa-draw-polygon", "Polygon");
|
|
998
|
+
updateGeometryButtons();
|
|
999
|
+
|
|
1000
|
+
const closePopup = () => {
|
|
1001
|
+
document.removeEventListener("keydown", handleKeyDown);
|
|
1002
|
+
document.body.removeEventListener("click", closeOnOutsideClick, true);
|
|
1003
|
+
map.remove();
|
|
1004
|
+
this.mapPopup?.remove();
|
|
1005
|
+
this.mapPopup = undefined;
|
|
1006
|
+
this.closeMapPopupHandler = undefined;
|
|
1007
|
+
};
|
|
1008
|
+
|
|
1009
|
+
const handleKeyDown = (event: KeyboardEvent) => {
|
|
1010
|
+
if (event.key === "Escape") {
|
|
1011
|
+
closePopup();
|
|
1012
|
+
}
|
|
1013
|
+
};
|
|
1014
|
+
|
|
1015
|
+
const closeOnOutsideClick = (event: MouseEvent) => {
|
|
1016
|
+
if (!this.mapPopup) return;
|
|
1017
|
+
if (this.mapPopup.contains(event.target as Node) || this.mapBtn?.contains(event.target as Node)) return;
|
|
1018
|
+
closePopup();
|
|
1019
|
+
};
|
|
1020
|
+
|
|
1021
|
+
map.on("click", (event: L.LeafletMouseEvent) => {
|
|
1022
|
+
const coordinate: WktCoordinate = { lat: event.latlng.lat, lng: event.latlng.lng };
|
|
1023
|
+
if (geometryType === "POINT") {
|
|
1024
|
+
coordinates = [coordinate];
|
|
1025
|
+
} else {
|
|
1026
|
+
coordinates.push(coordinate);
|
|
1027
|
+
}
|
|
1028
|
+
redrawGeometry();
|
|
1029
|
+
});
|
|
1030
|
+
|
|
1031
|
+
undoBtn.onclick = () => {
|
|
1032
|
+
coordinates.pop();
|
|
1033
|
+
redrawGeometry();
|
|
1034
|
+
};
|
|
1035
|
+
|
|
1036
|
+
clearBtn.onclick = () => {
|
|
1037
|
+
coordinates = [];
|
|
1038
|
+
redrawGeometry();
|
|
1039
|
+
};
|
|
1040
|
+
|
|
1041
|
+
insertBtn.onclick = () => {
|
|
1042
|
+
const wkt = coordinatesToWkt(geometryType, coordinates);
|
|
1043
|
+
if (!wkt) return;
|
|
1044
|
+
this.replaceSelection(wrapWktLiteral(wkt));
|
|
1045
|
+
closePopup();
|
|
1046
|
+
};
|
|
1047
|
+
|
|
1048
|
+
closeBtn.onclick = () => closePopup();
|
|
1049
|
+
this.closeMapPopupHandler = closePopup;
|
|
1050
|
+
|
|
1051
|
+
document.addEventListener("keydown", handleKeyDown);
|
|
1052
|
+
document.body.addEventListener("click", closeOnOutsideClick, true);
|
|
1053
|
+
|
|
1054
|
+
const positionPopup = () => {
|
|
1055
|
+
if (!this.mapPopup) return;
|
|
1056
|
+
const popupWidth = this.mapPopup.offsetWidth || this.mapPopup.scrollWidth || 520;
|
|
1057
|
+
const popupHeight = this.mapPopup.offsetHeight || this.mapPopup.scrollHeight || this.mapPopup.clientHeight;
|
|
1058
|
+
const viewportWidth = window.innerWidth;
|
|
1059
|
+
const viewportHeight = window.innerHeight;
|
|
1060
|
+
|
|
1061
|
+
this.mapPopup.style.position = "fixed";
|
|
1062
|
+
this.mapPopup.style.bottom = "auto";
|
|
1063
|
+
|
|
1064
|
+
if (anchorPoint) {
|
|
1065
|
+
const padding = 20;
|
|
1066
|
+
let left = anchorPoint.x + 12;
|
|
1067
|
+
let top = anchorPoint.y + 12;
|
|
1068
|
+
if (left + popupWidth > viewportWidth - padding) {
|
|
1069
|
+
left = Math.max(padding, anchorPoint.x - popupWidth - 12);
|
|
1070
|
+
}
|
|
1071
|
+
if (top + popupHeight > viewportHeight - padding) {
|
|
1072
|
+
top = Math.max(padding, anchorPoint.y - popupHeight - 12);
|
|
1073
|
+
}
|
|
1074
|
+
this.mapPopup.style.left = `${Math.max(padding, left)}px`;
|
|
1075
|
+
this.mapPopup.style.top = `${Math.max(padding, top)}px`;
|
|
1076
|
+
this.mapPopup.style.right = "auto";
|
|
1077
|
+
return;
|
|
1078
|
+
}
|
|
1079
|
+
|
|
1080
|
+
const buttonsRect = buttons.getBoundingClientRect();
|
|
1081
|
+
const fallbackLeft = Math.min(viewportWidth - popupWidth - 20, Math.max(20, buttonsRect.right - popupWidth));
|
|
1082
|
+
this.mapPopup.style.left = `${Math.max(20, fallbackLeft)}px`;
|
|
1083
|
+
this.mapPopup.style.top = "20px";
|
|
1084
|
+
this.mapPopup.style.right = "auto";
|
|
1085
|
+
};
|
|
1086
|
+
|
|
1087
|
+
if (typeof window !== "undefined" && typeof window.requestAnimationFrame === "function") {
|
|
1088
|
+
window.requestAnimationFrame(() => {
|
|
1089
|
+
positionPopup();
|
|
1090
|
+
map.invalidateSize();
|
|
1091
|
+
});
|
|
1092
|
+
} else {
|
|
1093
|
+
setTimeout(() => {
|
|
1094
|
+
positionPopup();
|
|
1095
|
+
map.invalidateSize();
|
|
1096
|
+
}, 0);
|
|
1097
|
+
}
|
|
1098
|
+
|
|
1099
|
+
redrawGeometry();
|
|
1100
|
+
}
|
|
1101
|
+
|
|
1102
|
+
public toggleMapWidget(anchorPoint?: { x: number; y: number }) {
|
|
1103
|
+
const buttons = this.getWrapperElement().querySelector(".yasqe_buttons");
|
|
1104
|
+
if (!buttons || !(buttons instanceof HTMLDivElement)) return;
|
|
1105
|
+
this.toggleMapPopup(buttons, anchorPoint);
|
|
1106
|
+
}
|
|
1107
|
+
|
|
797
1108
|
public toggleFullscreen() {
|
|
798
1109
|
this.isFullscreen = !this.isFullscreen;
|
|
799
1110
|
if (this.isFullscreen) {
|
|
@@ -1857,6 +2168,7 @@ export class Yasqe extends CodeMirror {
|
|
|
1857
2168
|
public destroy() {
|
|
1858
2169
|
// Abort running query
|
|
1859
2170
|
this.abortQuery();
|
|
2171
|
+
this.closeMapPopupHandler?.();
|
|
1860
2172
|
this.unregisterEventListeners();
|
|
1861
2173
|
this.horizontalResizeWrapper?.removeEventListener("mousedown", this.initDrag, false);
|
|
1862
2174
|
this.horizontalResizeWrapper?.removeEventListener("dblclick", this.expandEditor);
|
|
@@ -2043,6 +2355,7 @@ export interface Config extends Partial<CodeMirror.EditorConfiguration> {
|
|
|
2043
2355
|
queryingDisabled: string | undefined; // The string will be the message displayed when hovered
|
|
2044
2356
|
prefixCcApi: string; // the suggested default prefixes URL API getter
|
|
2045
2357
|
showFormatButton: boolean; // Show a button to format the query
|
|
2358
|
+
showMapButton: boolean; // Show a button to create WKT literals from map input
|
|
2046
2359
|
checkConstructVariables: boolean; // Check for undefined variables in CONSTRUCT queries
|
|
2047
2360
|
snippets: Snippet[]; // Code snippets to show in the snippets bar
|
|
2048
2361
|
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
|
}
|