@@ -17,6 +19,60 @@ const TPL = /*html*/`
height: 100%;
overflow: hidden;
}
+
+ .leaflet-pane {
+ z-index: 1;
+ }
+
+ .geo-map-container.placing-note {
+ cursor: crosshair;
+ }
+
+ .geo-map-container .marker-pin {
+ position: relative;
+ }
+
+ .geo-map-container .leaflet-div-icon {
+ position: relative;
+ background: transparent;
+ border: 0;
+ overflow: visible;
+ }
+
+ .geo-map-container .leaflet-div-icon .icon-shadow {
+ position: absolute;
+ top: 0;
+ left: 0;
+ z-index: -1;
+ }
+
+ .geo-map-container .leaflet-div-icon .bx {
+ position: absolute;
+ top: 3px;
+ left: 2px;
+ background-color: white;
+ color: black;
+ padding: 2px;
+ border-radius: 50%;
+ font-size: 17px;
+ }
+
+ .geo-map-container .leaflet-div-icon .title-label {
+ display: block;
+ position: absolute;
+ top: 100%;
+ left: 50%;
+ transform: translateX(-50%);
+ font-size: 0.75rem;
+ height: 1rem;
+ color: black;
+ width: 100px;
+ text-align: center;
+ text-overflow: ellipsis;
+ text-shadow: -1px -1px 0 white, 1px -1px 0 white, -1px 1px 0 white, 1px 1px 0 white;
+ white-space: no-wrap;
+ overflow: hidden;
+ }
@@ -31,19 +87,30 @@ interface MapData {
const DEFAULT_COORDINATES: [number, number] = [3.878638227135724, 446.6630455551659];
const DEFAULT_ZOOM = 2;
+const LOCATION_ATTRIBUTE = "geolocation";
+const CHILD_NOTE_ICON = "bx bx-pin";
export default class GeoView extends ViewMode
{
+ private args: ViewModeArgs;
private $root: JQuery;
private $container!: JQuery;
private map?: Map;
private spacedUpdate: SpacedUpdate;
+ private currentMarkerData: Record;
+ private currentTrackData: Record;
+
constructor(args: ViewModeArgs) {
super(args, "geoMap");
+ this.args = args;
this.$root = $(TPL);
this.$container = this.$root.find(".geo-map-container");
this.spacedUpdate = new SpacedUpdate(() => this.onSave(), 5_000);
+
+ this.currentMarkerData = {};
+ this.currentTrackData = {};
+
args.$parent.append(this.$root);
}
@@ -78,6 +145,8 @@ export default class GeoView extends ViewMode {
map.on("moveend", updateFn);
map.on("zoomend", updateFn);
// map.on("click", (e) => this.#onMapClicked(e));
+
+ this.#reloadMarkers();
}
async #restoreViewportAndZoom() {
@@ -109,6 +178,42 @@ export default class GeoView extends ViewMode {
this.viewStorage.store(data);
}
+ async #reloadMarkers() {
+ if (!this.map) {
+ return;
+ }
+
+ // Delete all existing markers
+ for (const marker of Object.values(this.currentMarkerData)) {
+ marker.remove();
+ }
+
+ // Delete all existing tracks
+ for (const track of Object.values(this.currentTrackData)) {
+ track.remove();
+ }
+
+ // Add the new markers.
+ this.currentMarkerData = {};
+ for (const noteId of this.args.noteIds) {
+ const childNote = await froca.getNote(noteId);
+ if (!childNote) {
+ continue;
+ }
+
+ if (childNote.mime === "application/gpx+xml") {
+ // this.#processNoteWithGpxTrack(childNote);
+ continue;
+ }
+
+ const latLng = childNote.getAttributeValue("label", LOCATION_ATTRIBUTE);
+ if (latLng) {
+ const marker = processNoteWithMarker(this.map, childNote, latLng);
+ this.currentMarkerData[childNote.noteId] = marker;
+ }
+ }
+ }
+
get isFullHeight(): boolean {
return true;
}
diff --git a/apps/client/src/widgets/view_widgets/geo_view/markers.ts b/apps/client/src/widgets/view_widgets/geo_view/markers.ts
new file mode 100644
index 000000000..d25234650
--- /dev/null
+++ b/apps/client/src/widgets/view_widgets/geo_view/markers.ts
@@ -0,0 +1,55 @@
+import markerIcon from "leaflet/dist/images/marker-icon.png";
+import markerIconShadow from "leaflet/dist/images/marker-shadow.png";
+import { marker, latLng, divIcon, Map } from "leaflet";
+import type FNote from "../../../entities/fnote.js";
+import note_tooltip from "../../../services/note_tooltip.js";
+import openContextMenu from "../../type_widgets/geo_map_context_menu.js";
+
+export default function processNoteWithMarker(map: Map, note: FNote, location: string) {
+ const [lat, lng] = location.split(",", 2).map((el) => parseFloat(el));
+ const icon = buildIcon(note.getIcon(), note.getColorClass(), note.title);
+
+ const newMarker = marker(latLng(lat, lng), {
+ icon,
+ draggable: true,
+ autoPan: true,
+ autoPanSpeed: 5
+ })
+ .addTo(map)
+ .on("moveend", (e) => {
+ // this.moveMarker(note.noteId, (e.target as Marker).getLatLng());
+ });
+ newMarker.on("mousedown", ({ originalEvent }) => {
+ // Middle click to open in new tab
+ if (originalEvent.button === 1) {
+ const hoistedNoteId = this.hoistedNoteId;
+ //@ts-ignore, fix once tab manager is ported.
+ appContext.tabManager.openInNewTab(note.noteId, hoistedNoteId);
+ return true;
+ }
+ });
+ newMarker.on("contextmenu", (e) => {
+ openContextMenu(note.noteId, e.originalEvent);
+ });
+
+ const el = newMarker.getElement();
+ if (el) {
+ const $el = $(el);
+ $el.attr("data-href", `#${note.noteId}`);
+ note_tooltip.setupElementTooltip($($el));
+ }
+
+ return newMarker;
+}
+
+function buildIcon(bxIconClass: string, colorClass?: string, title?: string) {
+ return divIcon({
+ html: /*html*/`\
+
+
+
+ ${title ?? ""}`,
+ iconSize: [25, 41],
+ iconAnchor: [12, 41]
+ });
+}