Add the map in search view

Signed-off-by: Thomas Citharel <tcit@tcit.fr>
This commit is contained in:
Thomas Citharel
2022-09-01 10:00:17 +02:00
parent b36ce27bbe
commit eecb04516e
26 changed files with 1507 additions and 329 deletions

View File

@@ -3,7 +3,7 @@
class="mbz-card snap-center dark:bg-mbz-purple"
:class="{
'sm:flex sm:items-start': mode === 'row',
'max-w-xs w-[18rem] shrink-0 flex flex-col': mode === 'column',
'sm:max-w-xs sm:w-[18rem] shrink-0 flex flex-col': mode === 'column',
}"
:to="to"
:isInternal="isInternal"

View File

@@ -1,6 +1,6 @@
<template>
<div>
<h2 class="text-2xl">{{ title }}</h2>
<h2>{{ title }}</h2>
<div class="flex items-center mb-3 gap-1 eventMetadataBlock">
<slot name="icon"></slot>
<!-- Custom icons -->
@@ -15,7 +15,7 @@
/>
</span>
<o-icon v-else-if="icon" :icon="icon" size="is-medium" /> -->
<div class="content-wrapper">
<div class="content-wrapper overflow-hidden w-full">
<slot></slot>
</div>
</div>
@@ -28,13 +28,7 @@ defineProps<{
</script>
<style lang="scss" scoped>
div.eventMetadataBlock {
display: flex;
align-items: center;
margin-bottom: 1.75rem;
.content-wrapper {
overflow: hidden;
width: 100%;
max-width: calc(100vw - 32px - 20px);
&.padding-left {

View File

@@ -2,11 +2,11 @@
<div>
<event-metadata-block
v-if="!event.options.isOnline"
:title="$t('Location')"
:icon="physicalAddress ? physicalAddress.poiInfos.poiIcon.icon : 'earth'"
:title="t('Location')"
:icon="addressPOIInfos?.poiIcon?.icon ?? 'earth'"
>
<div class="address-wrapper">
<span v-if="!physicalAddress">{{ $t("No address defined") }}</span>
<span v-if="!physicalAddress">{{ t("No address defined") }}</span>
<div class="address" v-if="physicalAddress">
<address-info :address="physicalAddress" />
<o-button
@@ -15,12 +15,23 @@
@click="$emit('showMapModal', true)"
v-if="physicalAddress.geom"
>
{{ $t("Show map") }}
{{ t("Show map") }}
</o-button>
</div>
</div>
<template #icon>
<o-icon
v-if="addressPOIInfos?.poiIcon?.icon"
:icon="addressPOIInfos?.poiIcon?.icon"
customSize="36"
/>
<Earth v-else :size="36" />
</template>
</event-metadata-block>
<event-metadata-block :title="$t('Date and time')" icon="calendar">
<event-metadata-block :title="t('Date and time')">
<template #icon>
<Calendar :size="36" />
</template>
<event-full-date
:beginsOn="event.beginsOn.toString()"
:show-start-time="event.options.showStartTime"
@@ -32,7 +43,7 @@
</event-metadata-block>
<event-metadata-block
class="metadata-organized-by"
:title="$t('Organized by')"
:title="t('Organized by')"
>
<router-link
v-if="event.attributedTo"
@@ -66,16 +77,18 @@
</event-metadata-block>
<event-metadata-block
v-if="event.onlineAddress && urlToHostname(event.onlineAddress)"
icon="link"
:title="$t('Website')"
:title="t('Website')"
>
<template #icon>
<Link :size="36" />
</template>
<a
target="_blank"
class="hover:underline"
rel="noopener noreferrer ugc"
:href="event.onlineAddress"
:title="
$t('View page on {hostname} (in a new window)', {
t('View page on {hostname} (in a new window)', {
hostname: urlToHostname(event.onlineAddress),
})
"
@@ -85,9 +98,9 @@
<event-metadata-block
v-for="extra in extraMetadata"
:title="extra.title || extra.label"
:icon="extra.icon"
:key="extra.key"
>
<template #icon> <o-icon :icon="extra.icon" customSize="36" /> </template>
<span
v-if="
((extra.type == EventMetadataType.STRING &&
@@ -108,7 +121,7 @@
rel="noopener noreferrer ugc"
:href="extra.value"
:title="
$t('View page on {hostname} (in a new window)', {
t('View page on {hostname} (in a new window)', {
hostname: urlToHostname(extra.value),
})
"
@@ -123,7 +136,7 @@
rel="noopener noreferrer ugc"
:href="accountURL(extra)"
:title="
$t('View account on {hostname} (in a new window)', {
t('View account on {hostname} (in a new window)', {
hostname: urlToHostname(accountURL(extra)),
})
"
@@ -134,7 +147,7 @@
</div>
</template>
<script lang="ts" setup>
import { Address } from "@/types/address.model";
import { Address, addressToPoiInfos } from "@/types/address.model";
import { EventMetadataKeyType, EventMetadataType } from "@/types/enums";
import { IEvent } from "@/types/event.model";
import { computed } from "vue";
@@ -147,6 +160,10 @@ import AddressInfo from "../../components/Address/AddressInfo.vue";
import { IEventMetadataDescription } from "@/types/event-metadata";
import { eventMetaDataList } from "../../services/EventMetadata";
import { IUser } from "@/types/current-user.model";
import { useI18n } from "vue-i18n";
import Earth from "vue-material-design-icons/Earth.vue";
import Calendar from "vue-material-design-icons/Calendar.vue";
import Link from "vue-material-design-icons/Link.vue";
const props = withDefaults(
defineProps<{
@@ -157,12 +174,19 @@ const props = withDefaults(
{ showMap: false }
);
const { t } = useI18n({ useScope: "global" });
const physicalAddress = computed((): Address | null => {
if (!props.event.physicalAddress) return null;
return new Address(props.event.physicalAddress);
});
const addressPOIInfos = computed(() => {
if (!props.event.physicalAddress) return null;
return addressToPoiInfos(props.event.physicalAddress);
});
const extraMetadata = computed((): IEventMetadataDescription[] => {
return props.event.metadata.map((val) => {
const def = eventMetaDataList.find((dat) => dat.key === val.key);

View File

@@ -5,7 +5,7 @@
class="mbz-card shrink-0 dark:bg-mbz-purple dark:text-white rounded-lg shadow-lg my-4 flex items-center flex-col"
:class="{
'sm:flex-row': mode === 'row',
'max-w-xs w-[18rem] shrink-0 flex flex-col': mode === 'column',
'sm:max-w-xs sm:w-[18rem] shrink-0 flex flex-col': mode === 'column',
}"
>
<div class="flex-none p-2 md:p-4">
@@ -25,7 +25,7 @@
:class="{ 'sm:flex-1': mode === 'row' }"
>
<div class="flex gap-1 mb-2">
<div class="px-1 overflow-hidden flex-auto">
<div class="overflow-hidden flex-auto">
<h3
class="text-2xl leading-5 line-clamp-3 font-bold text-violet-3 dark:text-white"
dir="auto"

View File

@@ -0,0 +1,370 @@
<template>
<div
:class="[
'bottom-sheet',
{
opened: opened,
closed: opened === false,
moving: moving,
},
]"
v-on="handlers"
ref="bottomSheet"
:style="{
'pointer-events':
backgroundClickable && clickToClose === false ? 'none' : 'all',
}"
>
<div
v-if="overlay"
class="bottom-sheet__backdrop"
:style="{ background: overlayColor }"
/>
<div
:style="[
{ bottom: cardP + 'px', maxWidth: maxWidth, maxHeight: maxHeight },
{ height: isFullScreen ? '100%' : 'auto' },
{ 'pointer-events': 'all' },
]"
:class="[
'bottom-sheet__card bg-white dark:bg-gray-800',
{ stripe: stripe, square: !rounded },
effect,
]"
ref="bottomSheetCard"
>
<div class="bottom-sheet__pan" ref="pan">
<div class="bottom-sheet__bar bg-gray-700 dark:bg-gray-400" />
</div>
<div
:style="{ height: contentH }"
ref="bottomSheetCardContent"
class="bottom-sheet__content"
>
<slot />
</div>
</div>
</div>
</template>
<script lang="ts" setup>
import Hammer from "hammerjs";
import { onBeforeUnmount, reactive, ref } from "vue";
const inited = ref(false);
const opened = ref(false);
const contentH = ref("auto");
const hammer = reactive<{
pan: any;
content: any;
}>({
pan: null,
content: null,
});
const contentScroll = ref(0);
const cardP = ref<number>(0);
const cardH = ref<number>(0);
const moving = ref(false);
const stripe = ref(0);
const props = withDefaults(
defineProps<{
overlay?: boolean;
maxWidth?: string;
maxHeight?: string;
clickToClose?: boolean;
effect?: string;
rounded?: boolean;
swipeAble?: boolean;
isFullScreen?: boolean;
overlayColor?: string;
backgroundScrollable?: boolean;
backgroundClickable?: boolean;
}>(),
{
overlay: true,
maxWidth: "640px",
maxHeight: "95%",
clickToClose: true,
effect: "fx-default",
rounded: true,
swipeAble: true,
isFullScreen: false,
overlayColor: "#0000004D",
backgroundScrollable: false,
backgroundClickable: false,
}
);
const emit = defineEmits(["closed", "opened"]);
const bottomSheetCardContent = ref();
const bottomSheetCard = ref();
const pan = ref();
const isIphone = () => {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
const iPhone = /iPhone/.test(navigator.userAgent) && !window.MSStream;
const aspect = window.screen.width / window.screen.height;
return iPhone && aspect.toFixed(3) === "0.462";
};
const move = (event: any, type: any) => {
if (props.swipeAble) {
const delta = -event.deltaY;
if (
(type === "content" && event.type === "panup") ||
(type === "content" &&
event.type === "pandown" &&
contentScroll.value > 0)
) {
bottomSheetCardContent.value.scrollTop = contentScroll.value + delta;
} else if (event.type === "panup" || event.type === "pandown") {
moving.value = true;
if (event.deltaY > 0) {
cardP.value = delta;
}
}
if (event.isFinal) {
contentScroll.value = bottomSheetCardContent.value.scrollTop;
moving.value = false;
if (cardP.value < -30) {
opened.value = false;
cardP.value = (-cardH.value ?? 0) - stripe.value;
document.body.style.overflow = "";
emit("closed");
} else {
cardP.value = 0;
}
}
}
};
const init = () => {
return new Promise((resolve) => {
contentH.value = "auto";
stripe.value = isIphone() ? 20 : 0;
cardH.value = bottomSheetCard.value.clientHeight;
contentH.value = `${cardH.value - pan.value.clientHeight}px`;
bottomSheetCard.value.style.maxHeight = props.maxHeight;
cardP.value =
props.effect === "fx-slide-from-right" ||
props.effect === "fx-slide-from-left"
? 0
: -cardH.value - stripe.value;
if (!inited.value) {
inited.value = true;
const options = {
recognizers: [[Hammer.Pan, { direction: Hammer.DIRECTION_VERTICAL }]],
};
hammer.pan = new Hammer(pan.value, options as any);
hammer.pan?.on("panstart panup pandown panend", (e: any) => {
move(e, "pan");
});
hammer.content = new Hammer(bottomSheetCardContent.value, options as any);
hammer.content?.on("panstart panup pandown panend", (e: any) => {
move(e, "content");
});
}
setTimeout(() => {
resolve(undefined);
}, 100);
});
};
const open = async () => {
console.debug("open vue bottom sheet");
await init();
opened.value = true;
cardP.value = 0;
if (!props.backgroundScrollable) {
document.body.style.overflow = "hidden";
}
emit("opened");
};
const close = () => {
opened.value = false;
cardP.value =
props.effect === "fx-slide-from-right" ||
props.effect === "fx-slide-from-left"
? 0
: -cardH.value - stripe.value;
document.body.style.overflow = "";
emit("closed");
};
const clickOnBottomSheet = (event: any) => {
if (props.clickToClose) {
if (
event.target.classList.contains("bottom-sheet__backdrop") ||
event.target.classList.contains("bottom-sheet")
) {
close();
}
}
};
onBeforeUnmount(() => {
hammer?.pan?.destroy();
hammer?.content?.destroy();
});
const handlers = {
mousedown: clickOnBottomSheet,
touchstart: clickOnBottomSheet,
};
defineExpose({ open, close });
</script>
<style lang="scss" scoped>
.bottom-sheet {
z-index: 99999;
transition: all 0.4s ease;
position: relative;
* {
box-sizing: border-box;
}
&__content {
overflow-y: scroll;
}
&__backdrop {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
z-index: 9999;
opacity: 0;
visibility: hidden;
}
&__card {
width: 100%;
position: fixed;
border-radius: 14px 14px 0 0;
left: 50%;
z-index: 9999;
margin: 0 auto;
&.square {
border-radius: 0;
}
&.stripe {
padding-bottom: 20px;
}
&.fx-default {
transform: translate(-50%, 0);
transition: bottom 0.3s ease;
}
&.fx-fadein-scale {
transform: translate(-50%, 0) scale(0.7);
opacity: 0;
transition: all 0.3s;
}
&.fx-slide-from-right {
transform: translate(100%, 0);
opacity: 0;
transition: all 0.3s cubic-bezier(0.25, 0.5, 0.5, 0.9);
}
&.fx-slide-from-left {
transform: translate(-100%, 0);
opacity: 0;
transition: all 0.3s cubic-bezier(0.25, 0.5, 0.5, 0.9);
}
}
&__pan {
padding-bottom: 20px;
padding-top: 15px;
height: 38px;
}
&__bar {
display: block;
width: 50px;
height: 3px;
border-radius: 14px;
margin: 0 auto;
cursor: pointer;
}
&.closed {
opacity: 0;
visibility: hidden;
.bottom-sheet__backdrop {
animation: hide 0.3s ease;
}
}
&.moving {
.bottom-sheet__card {
transition: none;
}
}
&.opened {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
.bottom-sheet__backdrop {
animation: show 0.3s ease;
opacity: 1;
visibility: visible;
}
.bottom-sheet__card {
&.fx-fadein-scale {
transform: translate(-50%, 0) scale(1);
opacity: 1;
}
&.fx-slide-from-right {
transform: translate(-50%, 0);
opacity: 1;
}
&.fx-slide-from-left {
transform: translate(-50%, 0);
opacity: 1;
}
}
}
}
@keyframes show {
0% {
opacity: 0;
visibility: hidden;
}
100% {
opacity: 1;
visibility: visible;
}
}
@keyframes hide {
0% {
opacity: 1;
visibility: visible;
}
100% {
opacity: 0;
visibility: hidden;
}
}
</style>

View File

@@ -0,0 +1,400 @@
<template>
<div class="relative my-2">
<div style="height: 70vh" id="mapMountPoint" />
<vue-bottom-sheet
v-if="activeElement"
ref="myBottomSheet"
class="md:hidden"
max-height="70%"
:background-scrollable="false"
>
<event-card
v-if="instanceOfIEvent(activeElement)"
:event="(activeElement as IEvent)"
:has-border="false"
view-mode="column"
:options="{
isRemoteEvent: activeElement.__typename === 'EventResult',
isLoggedIn,
}"
/>
<group-card
v-else
:group="(activeElement as IGroup)"
:has-border="false"
view-mode="column"
:isRemoteGroup="activeElement.__typename === 'GroupResult'"
:isLoggedIn="isLoggedIn"
/>
</vue-bottom-sheet>
<div
class="absolute hidden md:block bottom-0 md:top-8 right-0 h-48 w-full md:w-80 overflow-y-visible text-white [box-shadow:0 6px 9px 2px rgba(119,119,119,.75)] -my-4 px-2 z-[1100]"
v-if="activeElement"
>
<event-card
v-if="instanceOfIEvent(activeElement)"
:event="(activeElement as IEvent)"
view-mode="column"
:has-border="false"
:options="{
isRemoteEvent: activeElement.__typename === 'EventResult',
isLoggedIn,
}"
/>
<group-card
v-else
:group="(activeElement as IGroup)"
:has-border="false"
view-mode="column"
:isRemoteGroup="activeElement.__typename === 'GroupResult'"
:isLoggedIn="isLoggedIn"
/>
</div>
</div>
</template>
<script setup lang="ts">
import "leaflet/dist/leaflet.css";
import {
computed,
createApp,
DefineComponent,
h,
onBeforeUnmount,
onMounted,
ref,
watch,
} from "vue";
import VueBottomSheet from "@/components/Map/VueBottomSheet.vue";
import {
map,
LatLngBounds,
tileLayer,
marker,
divIcon,
Map,
Marker,
} from "leaflet";
import { MarkerClusterGroup } from "leaflet.markercluster/src";
import { IGroup } from "@/types/actor";
import { IEvent, instanceOfIEvent } from "@/types/event.model";
import { ContentType } from "@/types/enums";
import Calendar from "vue-material-design-icons/Calendar.vue";
import AccountMultiple from "vue-material-design-icons/AccountMultiple.vue";
import GroupCard from "@/components/Group/GroupCard.vue";
import EventCard from "@/components/Event/EventCard.vue";
import debounce from "lodash/debounce";
import { Paginate } from "@/types/paginate";
import { TypeNamed } from "@/types/apollo";
const mapElement = ref<Map>();
const markers = ref<MarkerClusterGroup>();
const myBottomSheet = ref<typeof VueBottomSheet>();
const props = defineProps<{
contentType: ContentType;
events: Paginate<TypeNamed<IEvent>>;
groups: Paginate<TypeNamed<IGroup>>;
latitude?: number;
longitude?: number;
isLoggedIn: boolean | undefined;
}>();
const emit = defineEmits<{
(
e: "map-updated",
{ bounds, zoom }: { bounds: LatLngBounds; zoom: number }
): void;
}>();
const activeElement = ref<TypeNamed<IEvent> | TypeNamed<IGroup> | null>(null);
const events = computed(() => props.events?.elements ?? []);
const groups = computed(() => props.groups?.elements ?? []);
watch([events, groups], update);
function update() {
if (!mapElement.value || !mapElement.value.getBounds) return;
const rawBounds: LatLngBounds = mapElement.value.getBounds();
const bounds: LatLngBounds = mapElement.value.wrapLatLngBounds(rawBounds);
if (
bounds.getNorthWest().lat === 0 ||
bounds.getNorthWest().lat === bounds.getSouthEast().lat
)
return;
const zoom = mapElement.value.getZoom();
emit("map-updated", { bounds, zoom });
}
onBeforeUnmount(() => {
if (mapElement.value) {
mapElement.value.remove();
}
});
const initialView = computed<[[number, number], number]>(() => {
if (props.latitude && props.longitude) {
return [[props.latitude, props.longitude], 12];
}
return [[0, 0], 3];
});
watch(initialView, ([latlng, zoom]) => {
setLatLng(latlng, zoom);
});
const setLatLng = (latlng: [number, number], zoom: number) => {
console.debug("setting view to ", latlng, zoom);
mapElement.value?.setView(latlng, zoom);
};
onMounted(async () => {
mapElement.value = map("mapMountPoint");
setLatLng(...initialView.value);
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
// eslint-disable-next-line no-underscore-dangle
mapElement.value._onResize();
mapElement.value.on("click", () => {
activeElement.value = null;
if (myBottomSheet.value) {
myBottomSheet.value.close();
}
});
// mapElement.value.on('load', function () {
// console.log('load event')
// setTimeout(() => {
// console.log('invalidate size')
// mapElement.value.invalidateSize()
// }, 1000)
// })
markers.value = new MarkerClusterGroup({ chunkedLoading: true });
mapElement.value.on("zoom", debounce(update, 1000));
mapElement.value.on("moveend", debounce(update, 1000));
tileLayer("https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png", {
attribution:
'&copy; <a href="https://osm.org/copyright">OpenStreetMap</a> contributors',
className: "map-tiles",
}).addTo(mapElement.value);
});
const categoryToColorClass = (element: IEvent | IGroup): string => {
if (instanceOfIEvent(element)) {
return "marker-event";
}
return "marker-group";
};
const pointToLayer = (
element: TypeNamed<IEvent> | TypeNamed<IGroup>,
latlng: { lat: number; lng: number }
): Marker => {
const icon = divIcon({
html: `<div class="marker-container ${categoryToColorClass(element)}">
<div class="pin-icon-container">
<svg width="24" height="24" stroke-width="1.5" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M20 10C20 14.4183 12 22 12 22C12 22 4 14.4183 4 10C4 5.58172 7.58172 2 12 2C16.4183 2 20 5.58172 20 10Z" stroke="currentColor" stroke-width="1.5"></path>
</svg>
</div>
<div class="element-icon-container text-black">
${instanceOfIEvent(element) ? calendarHTML : AccountMultipleHTML}
</div>
</div>`,
iconSize: [50, 50],
iconAnchor: [25, 50],
// iconSize: [
// MARKER_TOUCH_TARGET_SIZE * 0.5,
// MARKER_TOUCH_TARGET_SIZE * 0.5,
// ],
});
return marker(latlng, { icon }).on("click", () => {
activeElement.value = element;
if (myBottomSheet.value) {
myBottomSheet.value.open();
}
});
};
// https://stackoverflow.com/a/68319134/10204399
const vueComponentToHTML = (
component: DefineComponent,
localProps: Record<string, any> = {}
) => {
const tempApp = createApp({
render() {
return h(component, localProps);
},
});
// in Vue 3 we need real element to mount to unlike in Vue 2 where mount() could be called without argument...
const el = document.createElement("div");
const mountedApp = tempApp.mount(el);
const html = mountedApp.$el.outerHTML as string;
// tempApp.unmount();
return html;
};
const calendarHTML = vueComponentToHTML(Calendar);
const AccountMultipleHTML = vueComponentToHTML(AccountMultiple);
update();
const eventMarkers = computed(() => {
return events.value?.reduce((acc, event) => {
if (event.physicalAddress?.geom) {
const [lng, lat] = event.physicalAddress.geom.split(";");
return [
...acc,
pointToLayer(event, {
lng: Number.parseFloat(lng),
lat: Number.parseFloat(lat),
}),
];
}
return acc;
}, [] as Marker[]);
});
const groupMarkers = computed(() => {
return groups.value?.reduce((acc: Marker[], group: TypeNamed<IGroup>) => {
if (group.physicalAddress?.geom) {
const [lng, lat] = group.physicalAddress.geom.split(";");
return [
...acc,
pointToLayer(group, {
lng: Number.parseFloat(lng),
lat: Number.parseFloat(lat),
}),
];
}
return acc;
}, [] as Marker[]);
});
watch([markers, eventMarkers, groupMarkers], () => {
if (!markers.value) return;
console.debug(
"something changed in the search map",
markers.value,
eventMarkers.value,
groupMarkers.value
);
markers.value?.clearLayers();
if (props.contentType !== ContentType.GROUPS) {
eventMarkers.value?.forEach((markerToAdd) => {
console.debug("adding event marker layer to markers");
markers.value.addLayer(markerToAdd);
});
}
if (props.contentType !== ContentType.EVENTS) {
groupMarkers.value?.forEach((markerToAdd) => {
console.debug("adding group marker layer to markers");
markers.value.addLayer(markerToAdd);
});
}
mapElement.value?.addLayer(markers.value);
});
</script>
<style>
/*
* https://github.com/mapbox/supercluster/blob/f073fade1caae0b2b1beffd013b74ff024ff413b/demo/cluster.css
*/
.marker-cluster-small {
background-color: rgba(181, 226, 140, 0.6);
}
.marker-cluster-small div {
background-color: rgba(110, 204, 57, 0.6);
}
.marker-cluster-medium {
background-color: rgba(241, 211, 87, 0.6);
}
.marker-cluster-medium div {
background-color: rgba(240, 194, 12, 0.6);
}
.marker-cluster-large {
background-color: rgba(253, 156, 115, 0.6);
}
.marker-cluster-large div {
background-color: rgba(241, 128, 23, 0.6);
}
.marker-cluster {
background-clip: padding-box;
border-radius: 20px;
}
.marker-cluster div {
width: 30px;
height: 30px;
margin-left: 5px;
margin-top: 5px;
text-align: center;
border-radius: 15px;
font: 12px "Helvetica Neue", Arial, Helvetica, sans-serif;
}
.marker-cluster span {
line-height: 30px;
}
:root {
--map-tiles-filter: brightness(0.6) invert(1) contrast(3) hue-rotate(200deg)
saturate(0.3) brightness(0.7);
}
@media (prefers-color-scheme: dark) {
.map-tiles {
filter: var(--map-tiles-filter, none);
}
}
.leaflet-div-icon {
background: none !important;
border: none !important;
}
.marker-container {
position: relative;
}
.marker-container.marker-event .pin-icon-container svg {
fill: yellow;
color: black;
}
.marker-container.marker-group .pin-icon-container svg {
fill: lightblue;
color: white;
}
.pin-icon-container {
position: absolute;
width: 50px;
height: 50px;
}
.pin-icon-container svg path {
stroke-width: 1;
}
.pin-icon-container svg {
width: 100%;
height: 100%;
}
.element-icon-container {
position: absolute;
transform: translate(12px, 8px);
}
</style>

View File

@@ -104,6 +104,7 @@ const icons: Record<string, () => Promise<any>> = {
),
Earth: () =>
import(`../../../node_modules/vue-material-design-icons/Earth.vue`),
Map: () => import(`../../../node_modules/vue-material-design-icons/Map.vue`),
MapMarker: () =>
import(`../../../node_modules/vue-material-design-icons/MapMarker.vue`),
Close: () =>
@@ -231,6 +232,8 @@ const icons: Record<string, () => Promise<any>> = {
import(`../../../node_modules/vue-material-design-icons/Filter.vue`),
CheckCircle: () =>
import(`../../../node_modules/vue-material-design-icons/CheckCircle.vue`),
ViewList: () =>
import(`../../../node_modules/vue-material-design-icons/ViewList.vue`),
};
const props = withDefaults(