Setup RadialMenu Popup

This commit is contained in:
ingalls
2025-12-15 22:30:07 -07:00
parent c518d773b3
commit c46c71e7da
5 changed files with 79 additions and 74 deletions

View File

@@ -676,25 +676,7 @@ const noMenuShown = computed<boolean>(() => {
&& (!route.name || !String(route.name).startsWith('home-menu'))
});
watch(mapStore.radial, () => {
if (mapStore.radial.cot) {
mapStore.map.scrollZoom.disable();
mapStore.map.touchZoomRotate.disableRotation();
mapStore.map.dragRotate.disable();
mapStore.map.dragPan.disable();
const id = mapStore.radial.cot.properties ? mapStore.radial.cot.properties.id : mapStore.radial.cot.id;
if (!mapStore.locked.includes(id)) {
mapStore.locked.push(mapStore.radial.cot.properties ? mapStore.radial.cot.properties.id : mapStore.radial.cot.id);
}
} else {
mapStore.map.scrollZoom.enable();
mapStore.map.touchZoomRotate.enableRotation();
mapStore.map.dragRotate.enable();
mapStore.map.dragPan.enable();
mapStore.locked.pop();
}
})
onMounted(async () => {
// ensure uncaught errors in the stack are captured into vue context

View File

@@ -73,3 +73,13 @@ svg.menu > g.center > use {
cursor: pointer;
fill: white;
}
.radial-menu-popup .maplibregl-popup-content {
background: transparent;
box-shadow: none;
padding: 0;
}
.radial-menu-popup .maplibregl-popup-tip {
display: none;
}

View File

@@ -1,14 +1,4 @@
<template>
<div
ref='radial-menu'
class='position-absolute'
style='pointer-events: none; z-index: 1000;'
:style='{
top: `${mapStore.radial.y - (size / 2)}px`,
left: `${mapStore.radial.x - (size / 2)}px`,
}'
/>
<svg
id='icons'
class='d-none'
@@ -125,12 +115,13 @@
</template>
<script setup>
import { ref, onMounted, nextTick, useTemplateRef } from 'vue';
import { ref, shallowRef, onMounted, onUnmounted, nextTick } from 'vue';
import { OriginMode } from '../../../base/cot.ts';
import Subscription from '../../../base/subscription.ts';
import RadialMenu from './RadialMenu.js';
import './RadialMenu.css';
import { useMapStore } from '../../../stores/map.ts';
import mapgl from 'maplibre-gl';
const mapStore = useMapStore();
@@ -143,29 +134,59 @@ const props = defineProps({
const emit = defineEmits(['close', 'click']);
const menuRef = useTemplateRef('radial-menu');
const menuItems = ref([]);
const menu = ref();
const menu = shallowRef();
const popup = shallowRef();
onUnmounted(() => {
if (menu.value) {
menu.value.close();
}
if (popup.value) {
popup.value.remove();
}
});
onMounted(async () => {
await genMenuItems();
nextTick(() => {
if (!menuRef.value) {
console.warn('Warning: Could not mount Menu. menuRef is null.');
return; // Skip menu initialization
}
const container = document.createElement('div');
container.style.width = `${props.size}px`;
container.style.height = `${props.size}px`;
menu.value = new RadialMenu({
parent: menuRef.value,
parent: container,
size: props.size,
closeOnClick: true,
menuItems: menuItems.value,
onClick: (item) => {
emit('click', `${mapStore.radial.mode}:${item.id}`);
},
onClose: () => {
emit('close');
}
});
menu.value.open();
if (mapStore.radial.lngLat) {
popup.value = new mapgl.Popup({
closeButton: false,
closeOnClick: true,
maxWidth: 'none',
anchor: 'center',
className: 'radial-menu-popup'
})
.setLngLat(mapStore.radial.lngLat)
.setDOMContent(container)
.addTo(mapStore.map);
popup.value.on('close', () => {
emit('close');
});
} else {
emit('close');
}
})
});

View File

@@ -24,7 +24,7 @@
</template>
<script setup lang='ts'>
import { ref, onMounted, onUnmounted, watch } from 'vue';
import { ref, onMounted, onUnmounted, watch, markRaw } from 'vue';
import Feature from './FeatureRow.vue'
import { useMapStore } from '../../../stores/map.ts';
import mapgl from 'maplibre-gl';
@@ -33,40 +33,43 @@ const emit = defineEmits([ 'selected' ]);
const mapStore = useMapStore();
const selectMenu = ref<HTMLElement | null>(null);
let popup: mapgl.Popup | undefined;
onMounted(() => {
if (!selectMenu.value) return;
if (!mapStore.select.popup) {
mapStore.select.popup = markRaw(new mapgl.Popup({
closeButton: false,
closeOnClick: false,
maxWidth: 'none',
className: 'multiple-select-popup'
}));
mapStore.select.popup.on('close', () => {
if (mapStore.select.feats.length) {
mapStore.select.feats = [];
}
});
}
const lngLat = mapStore.map.unproject([mapStore.select.x, mapStore.select.y]);
popup = new mapgl.Popup({
closeButton: false,
closeOnClick: false,
maxWidth: 'none',
className: 'multiple-select-popup'
})
mapStore.select.popup
.setLngLat(lngLat)
.setDOMContent(selectMenu.value)
.addTo(mapStore.map);
popup.on('close', () => {
if (mapStore.select.feats.length) {
mapStore.select.feats = [];
}
});
});
watch(() => [mapStore.select.x, mapStore.select.y], ([x, y]) => {
if (popup) {
if (mapStore.select.popup) {
const lngLat = mapStore.map.unproject([x, y]);
popup.setLngLat(lngLat);
mapStore.select.popup.setLngLat(lngLat);
}
});
onUnmounted(() => {
if (popup) {
popup.remove();
if (mapStore.select.popup) {
mapStore.select.popup.remove();
}
});
</script>

View File

@@ -80,12 +80,14 @@ export const useMapStore = defineStore('cloudtak', {
feats: Array<COT | MapGeoJSONFeature>;
x: number;
y: number;
popup?: mapgl.Popup;
},
radial: {
mode: string | undefined;
cot: Feature | MapGeoJSONFeature | undefined;
x: number;
y: number;
lngLat?: LngLat;
},
overlays: Array<Overlay>
} => {
@@ -140,6 +142,7 @@ export const useMapStore = defineStore('cloudtak', {
mode: undefined,
cot: undefined,
x: 0, y: 0,
lngLat: undefined
},
overlays: [],
@@ -332,10 +335,6 @@ export const useMapStore = defineStore('cloudtak', {
};
this.map.flyTo(flyTo);
if (this.radial.mode) {
this.radial.x = this.container ? this.container.clientWidth / 2 : 0;
this.radial.y = this.container ? this.container.clientHeight / 2 : 0;
}
}
}
}
@@ -889,24 +888,14 @@ export const useMapStore = defineStore('cloudtak', {
if (!opts.mode) opts.mode = this.featureSource(feat) || 'feat';
if (opts.point.x < 150 || opts.point.y < 150) {
const flyTo: mapgl.FlyToOptions = {
speed: Infinity,
center: [opts.lngLat.lng, opts.lngLat.lat]
};
if (this.map.getZoom() < 3) flyTo.zoom = 4;
this.map.flyTo(flyTo)
this.radial.x = this.container ? this.container.clientWidth / 2 : 0;
this.radial.y = this.container ? this.container.clientHeight / 2 : 0;
} else {
this.radial.x = opts.point.x;
this.radial.y = opts.point.y;
}
this.radial.cot = feat;
this.radial.mode = opts.mode;
if (feat.properties && feat.properties.center) {
this.radial.lngLat = mapgl.LngLat.convert(feat.properties.center as LngLatLike);
} else {
this.radial.lngLat = opts.lngLat;
}
}
}
})