facilmap/frontend/src/lib/components/multiple-info/multiple-info.vue

176 wiersze
5.9 KiB
Vue

<script setup lang="ts">
import type { ID, Line, Marker } from "facilmap-types";
import { combineZoomDestinations, flyTo, getZoomDestinationForLine, getZoomDestinationForMarker } from "../../utils/zoom";
import Icon from "../ui/icon.vue";
import MarkerInfo from "../marker-info/marker-info.vue";
import LineInfo from "../line-info/line-info.vue";
import { computed, ref, watch } from "vue";
import { useToasts } from "../ui/toasts/toasts.vue";
import { showConfirm } from "../ui/alert.vue";
import { useCarousel } from "../../utils/carousel";
import ZoomToObjectButton from "../ui/zoom-to-object-button.vue";
import { injectContextRequired, requireClientContext, requireMapContext } from "../facil-map-context-provider/facil-map-context-provider.vue";
import vTooltip from "../../utils/tooltip";
import { formatTypeName, isLine, isMarker, normalizeLineName, normalizeMarkerName } from "facilmap-utils";
import { useI18n } from "../../utils/i18n";
const context = injectContextRequired();
const client = requireClientContext(context);
const mapContext = requireMapContext(context);
const toasts = useToasts();
const i18n = useI18n();
const props = defineProps<{
objects: Array<Marker | Line>;
}>();
const emit = defineEmits<{
"click-object": [object: Marker | Line, event: MouseEvent];
}>();
const isDeleting = ref(false);
const openedObjectId = ref<ID>();
const openedObjectType = ref<"marker" | "line">();
const carouselRef = ref<HTMLElement>();
const carousel = useCarousel(carouselRef);
function zoomToObject(object: Marker | Line): void {
const zoomDestination = isMarker(object) ? getZoomDestinationForMarker(object) : isLine(object) ? getZoomDestinationForLine(object) : undefined;
if (zoomDestination)
flyTo(mapContext.value.components.map, zoomDestination);
}
function openObject(object: Marker | Line): void {
openedObjectId.value = object.id;
openedObjectType.value = isMarker(object) ? "marker" : isLine(object) ? "line" : undefined;
carousel.setTab(1);
}
const openedObject = computed(() => {
let openedObject: Marker | Line | undefined = undefined;
if (openedObjectId.value != null) {
if (openedObjectType.value == "marker")
openedObject = client.value.markers[openedObjectId.value];
else if (openedObjectType.value == "line")
openedObject = client.value.lines[openedObjectId.value];
}
return openedObject && props.objects.includes(openedObject) ? openedObject : undefined;
});
watch(openedObject, () => {
if (!openedObject.value)
carousel.setTab(0);
});
async function deleteObjects(): Promise<void> {
toasts.hideToast(`fm${context.id}-multiple-info-delete`);
if (!props.objects || !await showConfirm({
title: i18n.t("multiple-info.delete-objects-title", { count: props.objects.length }),
message: i18n.t("multiple-info.delete-objects-message", { count: props.objects.length }),
variant: "danger",
okLabel: i18n.t("multiple-info.delete-objects-ok")
}))
return;
isDeleting.value = true;
try {
for (const object of props.objects) {
if (isMarker(object))
await client.value.deleteMarker({ id: object.id });
else if (isLine(object))
await client.value.deleteLine({ id: object.id });
}
} catch (err) {
toasts.showErrorToast(`fm${context.id}-multiple-info-delete`, i18n.t("multiple-info.delete-objects-error"), err);
} finally {
isDeleting.value = false;
}
}
const zoomDestination = computed(() => {
return combineZoomDestinations(props.objects.map((object) => {
if (isMarker(object))
return getZoomDestinationForMarker(object);
else if (isLine(object))
return getZoomDestinationForLine(object);
else
return undefined;
}));
});
</script>
<template>
<div class="fm-multiple-info">
<div class="carousel slide fm-flex-carousel" ref="carouselRef">
<div class="carousel-item" :class="{ active: carousel.tab === 0 }">
<ul class="list-group fm-search-box-collapse-point">
<li v-for="object in props.objects" :key="`${isMarker(object) ? 'm' : 'l'}-${object.id}`" class="list-group-item active">
<span class="text-break">
<a href="javascript:" @click="emit('click-object', object, $event)">{{isMarker(object) ? normalizeMarkerName(object.name) : normalizeLineName(object.name)}}</a>
{{" "}}
<span class="result-type" v-if="client.types[object.typeId]">({{formatTypeName(client.types[object.typeId].name)}})</span>
</span>
<a href="javascript:" @click="zoomToObject(object)" v-tooltip.left="i18n.t('multiple-info.zoom-to-object')"><Icon icon="zoom-in" :alt="i18n.t('multiple-info.zoom')"></Icon></a>
<a href="javascript:" @click="openObject(object)" v-tooltip.right="i18n.t('multiple-info.show-details')"><Icon icon="arrow-right" :alt="i18n.t('multiple-info.details')"></Icon></a>
</li>
</ul>
<div class="btn-toolbar mt-2">
<ZoomToObjectButton
v-if="zoomDestination"
label="selection"
size="sm"
:destination="zoomDestination"
></ZoomToObjectButton>
<button
v-if="!client.readonly"
type="button"
class="btn btn-secondary btn-sm"
@click="deleteObjects()"
:disabled="isDeleting || mapContext.interaction"
>
<div v-if="isDeleting" class="spinner-border spinner-border-sm"></div>
{{i18n.t("multiple-info.delete")}}
</button>
</div>
</div>
<div class="carousel-item" :class="{ active: carousel.tab === 1 }">
<MarkerInfo
v-if="openedObject && isMarker(openedObject)"
:markerId="openedObject.id"
show-back-button
@back="carousel.setTab(0)"
></MarkerInfo>
<LineInfo
v-else-if="openedObject && isLine(openedObject)"
:lineId="openedObject.id"
show-back-button
@back="carousel.setTab(0)"
></LineInfo>
</div>
</div>
</div>
</template>
<style lang="scss">
.fm-multiple-info {
display: flex;
flex-direction: column;
min-height: 0;
.list-group-item {
display: flex;
align-items: center;
> :first-child {
flex-grow: 1;
}
}
}
</style>