feat: add custom marker icons with configurable size
- Add markerIcon files field to marker.yml for custom JPG/PNG/SVG icons - Add markerIconSize range field (20-500px, default 40px) with unit display - Layout icon fields side-by-side (50/50 width) in marker blueprint - Add markerIconUrl prop in index.php to auto-detect uploaded icon - Add markerIconSize prop in index.php to read size from page data - Update MapPreview.vue to display custom images instead of default pins - Set icon dimensions dynamically based on markerIconSize value - Icon size updates on save/reload (reactive implementation deferred) - Remove custom tiles background functionality (not needed) Note: Custom icons show uploaded image, may have white background on transparent PNGs depending on image processing. Size is non-reactive and requires save + reload to update in preview. Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
925e98aea7
commit
b19635f324
6 changed files with 161 additions and 58 deletions
|
|
@ -55,3 +55,24 @@ tabs:
|
|||
type: map-editor
|
||||
mode: single
|
||||
help: Déplacez le marqueur
|
||||
markerIcon:
|
||||
label: Icône personnalisée
|
||||
type: files
|
||||
multiple: false
|
||||
accept:
|
||||
- image/jpeg
|
||||
- image/png
|
||||
- image/svg+xml
|
||||
width: 1/2
|
||||
help: Image à utiliser comme marqueur (JPG, PNG ou SVG). Laissez vide pour utiliser le marqueur par défaut.
|
||||
|
||||
markerIconSize:
|
||||
label: Taille de l'icône
|
||||
type: range
|
||||
min: 20
|
||||
max: 500
|
||||
step: 5
|
||||
default: 40
|
||||
after: px
|
||||
width: 1/2
|
||||
help: Taille de l'icône en pixels
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
|
@ -28,6 +28,23 @@ Kirby::plugin('geoproject/map-editor', [
|
|||
},
|
||||
'longitude' => function ($longitude = null) {
|
||||
return $longitude;
|
||||
},
|
||||
'markerIconUrl' => function ($markerIconUrl = null) {
|
||||
// Auto-detect marker icon from page files
|
||||
if ($markerIconUrl === null && $this->model()) {
|
||||
$iconFile = $this->model()->markerIcon()->toFile();
|
||||
if ($iconFile) {
|
||||
return $iconFile->url();
|
||||
}
|
||||
}
|
||||
return $markerIconUrl;
|
||||
},
|
||||
'markerIconSize' => function ($markerIconSize = 40) {
|
||||
// Auto-detect marker icon size from page
|
||||
if ($this->model() && $this->model()->markerIconSize()->isNotEmpty()) {
|
||||
return (int) $this->model()->markerIconSize()->value();
|
||||
}
|
||||
return $markerIconSize;
|
||||
}
|
||||
]
|
||||
]
|
||||
|
|
|
|||
|
|
@ -79,6 +79,14 @@ export default {
|
|||
type: [Number, String],
|
||||
default: null,
|
||||
},
|
||||
markerIconUrl: {
|
||||
type: String,
|
||||
default: null,
|
||||
},
|
||||
markerIconSize: {
|
||||
type: Number,
|
||||
default: 40,
|
||||
},
|
||||
},
|
||||
|
||||
setup(props, { emit }) {
|
||||
|
|
@ -138,6 +146,8 @@ export default {
|
|||
id: 'single-marker',
|
||||
position: { lat, lon },
|
||||
title: 'Current position',
|
||||
iconUrl: props.markerIconUrl,
|
||||
iconSize: props.markerIconSize,
|
||||
}];
|
||||
}
|
||||
return [];
|
||||
|
|
|
|||
|
|
@ -181,6 +181,25 @@ export default {
|
|||
const el = document.createElement("div");
|
||||
el.className = "custom-marker";
|
||||
|
||||
// Check if custom icon is provided
|
||||
if (markerData.iconUrl) {
|
||||
// Use custom image
|
||||
el.classList.add("custom-icon");
|
||||
const img = document.createElement("img");
|
||||
img.src = markerData.iconUrl;
|
||||
img.className = "marker-icon-image";
|
||||
|
||||
// Set size from marker data or default to 40px
|
||||
const size = markerData.iconSize || 40;
|
||||
img.style.width = `${size}px`;
|
||||
img.style.height = `${size}px`;
|
||||
|
||||
if (props.selectedMarkerId === markerData.id) {
|
||||
img.classList.add("selected");
|
||||
}
|
||||
el.appendChild(img);
|
||||
} else {
|
||||
// Use default pin marker
|
||||
// Create inner wrapper for visual transforms (isolates from MapLibre transforms)
|
||||
const inner = document.createElement("div");
|
||||
inner.className = "marker-inner";
|
||||
|
|
@ -194,6 +213,7 @@ export default {
|
|||
numberEl.textContent = index + 1;
|
||||
inner.appendChild(numberEl);
|
||||
el.appendChild(inner);
|
||||
}
|
||||
|
||||
try {
|
||||
const coords = [markerData.position.lon, markerData.position.lat];
|
||||
|
|
@ -252,6 +272,7 @@ export default {
|
|||
function updateMarkerSelection(selectedId) {
|
||||
markerElements.value.forEach(({ element }, markerId) => {
|
||||
if (element) {
|
||||
// Handle default pin marker
|
||||
const inner = element.querySelector('.marker-inner');
|
||||
if (inner) {
|
||||
if (markerId === selectedId) {
|
||||
|
|
@ -260,6 +281,16 @@ export default {
|
|||
inner.classList.remove("selected");
|
||||
}
|
||||
}
|
||||
|
||||
// Handle custom icon marker
|
||||
const img = element.querySelector('.marker-icon-image');
|
||||
if (img) {
|
||||
if (markerId === selectedId) {
|
||||
img.classList.add("selected");
|
||||
} else {
|
||||
img.classList.remove("selected");
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
@ -399,6 +430,30 @@ export default {
|
|||
pointer-events: none;
|
||||
}
|
||||
|
||||
/* Custom icon marker */
|
||||
.custom-marker.custom-icon {
|
||||
cursor: grab;
|
||||
}
|
||||
|
||||
.custom-marker.custom-icon:active {
|
||||
cursor: grabbing;
|
||||
}
|
||||
|
||||
.marker-icon-image {
|
||||
object-fit: contain;
|
||||
filter: drop-shadow(0 2px 4px rgba(0, 0, 0, 0.3));
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.marker-icon-image:hover {
|
||||
transform: scale(1.1);
|
||||
filter: drop-shadow(0 4px 8px rgba(0, 0, 0, 0.4));
|
||||
}
|
||||
|
||||
.marker-icon-image.selected {
|
||||
filter: drop-shadow(0 4px 12px rgba(52, 152, 219, 0.8));
|
||||
}
|
||||
|
||||
/* MapLibre controls styling */
|
||||
.maplibregl-ctrl-group {
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue