Skip to content

Commit 015bb37

Browse files
author
danhnguyen
committed
feat: marker and popup
1 parent 0643e29 commit 015bb37

File tree

12 files changed

+461
-5
lines changed

12 files changed

+461
-5
lines changed

examples/src/views/Home.vue

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import {
77
Mapbox,
88
CircleLayer,
99
LineLayer,
10+
Marker,
1011
useMapbox,
1112
} from 'vue3-mapbox';
1213
import type {
@@ -102,6 +103,7 @@ watchEffect(() => {
102103
}"
103104
/>
104105
</GeoJsonSource>
106+
<Marker :lnglat="[103.8097, 1.3535]" draggable />
105107
<GeoJsonSource :data="circleData">
106108
<CircleLayer
107109
:style="{

libs/components/Mapbox.vue

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -201,8 +201,9 @@ watch(() => unref(mapOptions).renderWorldCopies!, setRenderWorldCopies);
201201
</script>
202202
<template>
203203
<div ref="maplibreElRef" class="maplibre_container">
204-
<slot v-if="mapStatus >= MapboxStatus.Loading" name="beforeLoad" />
205-
<slot v-if="mapStatus >= MapboxStatus.Loaded" name="default" />
204+
<slot v-if="mapStatus === MapboxStatus.Loading" name="beforeLoad" />
205+
<slot v-if="mapStatus === MapboxStatus.Error" name="error" />
206+
<slot v-if="mapStatus === MapboxStatus.Loaded" name="default" />
206207
</div>
207208
</template>
208209
<style lang="scss">

libs/components/Marker.vue

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
<script lang="ts" setup>
2+
import { inject, ref, useSlots, watch } from 'vue';
3+
import { MapProvideKey } from '@libs/enums';
4+
import { useCreateMarker } from '@libs/composables';
5+
import type { Anchor } from '@libs/types';
6+
import type {
7+
LngLatLike,
8+
Alignment,
9+
PointLike,
10+
MarkerOptions,
11+
Popup,
12+
} from 'maplibre-gl';
13+
14+
interface MarkerProps {
15+
lnglat?: LngLatLike;
16+
popup?: Popup;
17+
options?: MarkerOptions;
18+
draggable?: boolean;
19+
element?: HTMLElement | undefined;
20+
21+
offset?: PointLike | undefined;
22+
anchor?: Anchor | undefined;
23+
24+
color?: string | undefined;
25+
clickTolerance?: number | null | undefined;
26+
rotation?: number | undefined;
27+
rotationAlignment?: Alignment | undefined;
28+
pitchAlignment?: Alignment | undefined;
29+
scale?: number | undefined;
30+
occludedOpacity?: number | undefined;
31+
}
32+
33+
interface Emits {
34+
(e: 'dragstart', ev: Event): void;
35+
(e: 'drag', ev: Event): void;
36+
(e: 'dragend', ev: Event): void;
37+
}
38+
39+
const props = withDefaults(defineProps<MarkerProps>(), {
40+
options: () => ({}),
41+
});
42+
43+
const emits = defineEmits<Emits>();
44+
45+
const slots = useSlots();
46+
47+
const mapInstance = inject(MapProvideKey, ref(null));
48+
const markerElRef = ref<HTMLElement>();
49+
50+
const { setDraggable, setLngLat } = useCreateMarker({
51+
map: mapInstance,
52+
el: slots.default?.() ? markerElRef : undefined,
53+
lnglat: props.lnglat,
54+
popup: props.popup,
55+
options: {
56+
...props.options,
57+
...(props.draggable === undefined
58+
? {}
59+
: {
60+
draggable: props.draggable,
61+
}),
62+
},
63+
on: {
64+
dragstart: (ev) => emits('dragstart', ev),
65+
drag: (ev) => emits('drag', ev),
66+
dragend: (ev) => emits('dragend', ev),
67+
},
68+
});
69+
70+
watch(
71+
() => props.lnglat,
72+
(lnglat) => {
73+
lnglat && setLngLat(lnglat);
74+
},
75+
);
76+
77+
watch(() => props.draggable, setDraggable);
78+
</script>
79+
<template>
80+
<div ref="markerElRef">
81+
<slot />
82+
</div>
83+
</template>

libs/components/Popup.vue

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
<script lang="ts" setup>
2+
import { inject, ref, watch } from 'vue';
3+
import { MapProvideKey } from '@libs/enums';
4+
import { useCreatePopup } from '@libs/composables';
5+
import type { LngLatLike, PopupOptions } from 'maplibre-gl';
6+
7+
interface PopupProps {
8+
className?: string;
9+
lnglat?: LngLatLike;
10+
show?: boolean;
11+
withMap?: boolean;
12+
options?: PopupOptions;
13+
}
14+
15+
interface Emits {
16+
(event: 'close'): void;
17+
(event: 'open'): void;
18+
(event: 'update:show', show: boolean): void;
19+
}
20+
21+
const props = withDefaults(defineProps<PopupProps>(), {
22+
show: true,
23+
withMap: true,
24+
});
25+
26+
const emits = defineEmits<Emits>();
27+
28+
const mapInstance = inject(MapProvideKey, ref(null));
29+
const popupElRef = ref<HTMLElement>();
30+
31+
const { setLngLat, show, hide } = useCreatePopup({
32+
map: mapInstance,
33+
el: popupElRef,
34+
lnglat: props.lnglat,
35+
show: props.show,
36+
withMap: props.withMap,
37+
options: {
38+
...props.options,
39+
className: props.className,
40+
},
41+
on: {
42+
open: () => {
43+
emits('open');
44+
emits('update:show', true);
45+
},
46+
close: async () => {
47+
emits('close');
48+
emits('update:show', false);
49+
},
50+
},
51+
});
52+
53+
watch(
54+
() => props.show,
55+
(isShow) => {
56+
isShow ? show() : hide();
57+
},
58+
);
59+
60+
watch(
61+
() => props.lnglat,
62+
(lnglat) => {
63+
lnglat && setLngLat(lnglat);
64+
},
65+
);
66+
</script>
67+
<template>
68+
<div ref="popupElRef" class="mapboxgl-popup-content-inner">
69+
<slot />
70+
</div>
71+
</template>

libs/components/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,5 @@ export { default as CircleLayer } from './CircleLayer.vue';
66
export { default as LineLayer } from './LineLayer.vue';
77
export { default as SymbolLayer } from './SymbolLayer.vue';
88
export { default as Image } from './Image.vue';
9+
export { default as Popup } from './Popup.vue';
10+
export { default as Marker } from './Marker.vue';

libs/composables/map/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,5 @@ export * from './useMapbox';
33
export * from './useLayer';
44
export * from './useCreateImage';
55
export * from './useGeoJsonSource';
6+
export * from './useCreatePopup';
7+
export * from './useCreateMarker';

libs/composables/map/useCreateMapbox.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@ export function useCreateMapbox(
7878
}
7979

8080
function mapEventError(ev: MapLibreEvent) {
81+
mapStatus.value = MapboxStatus.Error;
8182
console.warn('map error', ev);
8283
}
8384

Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
import { onUnmounted, watchEffect, watch, unref, shallowRef } from 'vue';
2+
import { Marker } from 'maplibre-gl';
3+
import { lngLatLikeHasValue } from '@libs/helpers';
4+
import type { Nullable } from '@libs/types';
5+
import type { MaybeRef, Ref } from 'vue';
6+
import type {
7+
Alignment,
8+
Listener,
9+
LngLatLike,
10+
Map,
11+
MarkerOptions,
12+
Popup,
13+
} from 'maplibre-gl';
14+
15+
interface CreateMarkerProps {
16+
map: MaybeRef<Nullable<Map>>;
17+
lnglat?: LngLatLike;
18+
popup?: MaybeRef<Nullable<Popup>>;
19+
el?: Ref<HTMLElement | undefined>;
20+
options?: MarkerOptions;
21+
on?: {
22+
dragstart?: (e: Event) => void;
23+
drag?: (e: Event) => void;
24+
dragend?: (e: Event) => void;
25+
};
26+
}
27+
28+
export function useCreateMarker({
29+
map: mapRef,
30+
lnglat: lnglatVal,
31+
popup: popupRef,
32+
el,
33+
options = {},
34+
on = {},
35+
}: CreateMarkerProps) {
36+
const marker = shallowRef<Nullable<Marker>>(null);
37+
let oPopup = unref(popupRef);
38+
39+
function dragstartEventFn(ev: Event) {
40+
on.dragstart?.(ev);
41+
}
42+
43+
function dragEventFn(ev: Event) {
44+
on.drag?.(ev);
45+
}
46+
47+
function dragendEventFn(ev: Event) {
48+
on.dragend?.(ev);
49+
}
50+
51+
const stopEffect = watchEffect((onCleanUp) => {
52+
const map = unref(mapRef);
53+
if (map && !marker.value) {
54+
marker.value = new Marker({
55+
...options,
56+
element: el?.value,
57+
});
58+
59+
if (lnglatVal && lngLatLikeHasValue(lnglatVal)) setLngLat(lnglatVal);
60+
61+
oPopup && setPopup(oPopup);
62+
marker.value.addTo(map);
63+
64+
marker.value.on('dragstart', dragstartEventFn as Listener);
65+
marker.value.on('drag', dragEventFn as Listener);
66+
marker.value.on('dragend', dragendEventFn as Listener);
67+
}
68+
69+
onCleanUp(removeMarker);
70+
});
71+
72+
watch(() => unref(popupRef)!, setPopup);
73+
74+
function setLngLat(lnglat: LngLatLike) {
75+
if (marker.value) marker.value.setLngLat(lnglat);
76+
}
77+
78+
function setPopup(popup?: Popup | null) {
79+
oPopup = popup;
80+
if (marker.value) marker.value.setPopup(popup!);
81+
}
82+
83+
function setOffset(offset: [number, number]) {
84+
if (marker.value) marker.value.setOffset(offset);
85+
}
86+
87+
function setDraggable(draggable: boolean) {
88+
if (marker.value) marker.value.setDraggable(draggable);
89+
}
90+
91+
function togglePopup() {
92+
if (marker.value) marker.value.togglePopup();
93+
}
94+
95+
function getElement() {
96+
if (marker.value) return marker.value.getElement();
97+
return null;
98+
}
99+
100+
function setRotation(rotation: number) {
101+
if (marker.value) marker.value.setRotation(rotation);
102+
}
103+
104+
function setRotationAlignment(alignment: Alignment) {
105+
if (marker.value) marker.value.setRotationAlignment(alignment);
106+
}
107+
108+
function setPitchAlignment(alignment: Alignment) {
109+
if (marker.value) marker.value.setPitchAlignment(alignment);
110+
}
111+
112+
function setOpacity(opacity: string, opacityWhenCovered?: string) {
113+
if (marker.value) marker.value.setOpacity(opacity, opacityWhenCovered);
114+
}
115+
116+
function removeMarker() {
117+
if (marker.value) {
118+
marker.value.off('dragstart', dragstartEventFn as Listener);
119+
marker.value.off('drag', dragEventFn as Listener);
120+
marker.value.off('dragend', dragendEventFn as Listener);
121+
marker.value.remove();
122+
}
123+
marker.value = null;
124+
}
125+
126+
onUnmounted(() => {
127+
stopEffect();
128+
oPopup = null;
129+
});
130+
131+
return {
132+
setLngLat,
133+
setPopup,
134+
setOffset,
135+
setDraggable,
136+
togglePopup,
137+
getElement,
138+
setRotation,
139+
setRotationAlignment,
140+
setPitchAlignment,
141+
setOpacity,
142+
removeMarker,
143+
};
144+
}

0 commit comments

Comments
 (0)