/
githubmirror
/
photoprism
Обзор
Документация
Войти
/
githubmirror
/
photoprism
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
develop
frontend/src/component/map.vue
274 строки
8 KB
Ömer Duran
Settings: Reorganize UI and add an Accessibility section #848 #799 #5429
08 июл 2026, 15:11
Не верифицирован
08 июл 2026, 15:11
6dc0aca
Код
Авторство
О чём код?
<template> <div ref="map" class="p-map" :class="{ 'is-marker-clickable': markerClickable }"></div> </template> <script> import * as map from "common/map"; let maplibregl = null; export default { name: "PMap", props: { latlng: { type: Array, default: () => [0.0, 0.0], validator: (value) => Array.isArray(value) && value.length === 2, }, zoom: { type: Number, default: 9, }, style: { type: String, default: "embedded", }, // Interactive mode props interactive: { type: Boolean, default: false, }, draggable: { type: Boolean, default: false, }, showControls: { type: Boolean, default: false, }, clickable: { type: Boolean, default: false, }, // When true, attaches a click listener to the marker's DOM element // that emits `marker-clicked`. The marker's cursor is gated on the // same prop so it only reads as pointer when the consumer is // actually listening (see `places.css` `.p-map.is-marker-clickable`). markerClickable: { type: Boolean, default: false, }, // Override animation duration (ms). -1 = use global setting, 0 = no animation. animateDuration: { type: Number, default: -1, }, }, emits: ["update:latlng", "marker-moved", "marker-clicked", "map-clicked"], data() { const settings = this.$config.getSettings(); return { map: null, marker: null, position: [0.0, 0.0], animate: this.$util.mapAnimateDuration(settings, this.animateDuration), options: { container: null, // Styles can be edited/created with https://maplibre.org/maputnik/. // To test new styles, put the style file in /assets/static/maps // and include it from there e.g. "/static/maps/embedded.json": // style: "/static/maps/embedded.json", style: `https://cdn.photoprism.app/maps/${this.style}.json`, glyphs: `https://cdn.photoprism.app/maps/font/{fontstack}/{range}.pbf`, zoom: this.zoom, interactive: this.interactive, attributionControl: false, }, loaded: false, }; }, watch: { latlng() { this.updatePosition(); }, }, mounted() { map.load().then((m) => { maplibregl = m; this.initMap(); }); }, beforeUnmount() { if (this.map) { this.map.remove(); } }, methods: { initMap() { if (this.map || !this.$refs.map || !maplibregl) { return; } try { this.options.container = this.$refs.map; // Set center based on coordinates or default if (!(this.latlng[0] && this.latlng[1] && !(this.latlng[0] === 0 && this.latlng[1] === 0))) { this.options.zoom = 2; this.options.center = [0, 20]; } else { this.options.center = [this.latlng[1], this.latlng[0]]; // Convert [lat, lng] to [lng, lat] for MapLibre } this.map = new maplibregl.Map(this.options); // Add controls if requested if (this.showControls) { this.map.addControl( new maplibregl.NavigationControl({ showCompass: true, showZoom: true, visualizePitch: false, }), "top-right" ); this.map.addControl(new maplibregl.ScaleControl({ maxWidth: 80, unit: "metric" }), "bottom-left"); this.map.addControl( new maplibregl.GeolocateControl({ positionOptions: { enableHighAccuracy: true, }, trackUserLocation: true, }), "top-right" ); } this.map.on("error", (e) => { console.error("map:", e); }); // Handle missing style images this.map.on("styleimagemissing", (e) => { const emptyImage = new ImageData(1, 1); if (e && e.id) { this.map.addImage(e.id, emptyImage); } }); this.map.on("load", () => { this.loaded = true; this.updatePosition(); this.map.resize(); }); // Add click handler for interactive mode if (this.clickable) { this.map.on("click", (e) => { const lat = e.lngLat.lat; const lng = e.lngLat.lng; this.$emit("map-clicked", { lat, lng }); this.$emit("update:latlng", [lat, lng]); }); } } catch (error) { console.error("map: initialization failed", error); this.loaded = false; } }, updatePosition() { if (!this.map || !this.loaded) { return; } if (this.position[0] === this.latlng[1] && this.position[1] === this.latlng[0] && this.marker) { return; } // Skip invalid or empty coordinates if (!(this.latlng[0] && this.latlng[1] && !(this.latlng[0] === 0 && this.latlng[1] === 0))) { if (this.marker) { this.marker.remove(); this.marker = null; } return; } this.position = [this.latlng[1], this.latlng[0]]; // Convert [lat, lng] to [lng, lat] for MapLibre if (this.animate > 0) { this.map.flyTo({ center: this.position, zoom: this.interactive ? this.zoom : undefined, // Only set zoom in interactive mode duration: this.animate, essential: true, // Respects prefers-reduced-motion }); } else { // Use setCenter for instant positioning (no animation) if (this.interactive) { this.map.setCenter(this.position, { zoom: this.zoom, animate: false, }); } else { this.map.setCenter(this.position); } } if (this.marker) { this.marker.setLngLat(this.position); } else { this.marker = new maplibregl.Marker({ color: "#3fb4df", draggable: this.draggable, }) .setLngLat(this.position) .addTo(this.map); // Add drag event listener for draggable markers if (this.draggable) { this.marker.on("dragend", () => { const lngLat = this.marker.getLngLat(); this.$emit("marker-moved", { lat: lngLat.lat, lng: lngLat.lng }); this.$emit("update:latlng", [lngLat.lat, lngLat.lng]); }); } // Add click listener on the marker's DOM element when the // consumer opted in via `markerClickable`. MapLibre renders // the default marker as a <button>, so we get keyboard // activation for free; `.stop` prevents the click from // bubbling to a possible map-level handler. if (this.markerClickable) { const el = this.marker.getElement(); if (el) { el.addEventListener("click", (ev) => { ev.stopPropagation(); const lngLat = this.marker.getLngLat(); this.$emit("marker-clicked", { lat: lngLat.lat, lng: lngLat.lng }); }); } } } }, // Public method to remove marker removeMarker() { if (this.marker) { this.marker.remove(); this.marker = null; } }, // Public method to fly to coordinates flyTo(lat, lng, zoom = this.zoom) { if (this.map) { if (this.animate > 0) { this.map.flyTo({ center: [lng, lat], zoom: zoom, duration: this.animate, essential: true, }); } else { this.map.jumpTo({ center: [lng, lat], zoom: zoom, }); } } }, }, }; </script>