facilmap/frontend/src/lib/components/import-tab.vue

139 wiersze
4.8 KiB
Vue
Czysty Zwykły widok Historia

2023-11-01 18:45:16 +00:00
<script setup lang="ts">
import { FileResultObject, parseFiles } from "../utils/files";
import pluralize from "pluralize";
import { SearchResultsLayer } from "facilmap-leaflet";
import { Util } from "leaflet";
import FileResults from "./file-results.vue";
import SearchBoxTab from "./search-box/search-box-tab.vue";
2023-11-06 02:22:33 +00:00
import { computed, markRaw, ref, shallowReactive } from "vue";
2023-11-01 18:45:16 +00:00
import { useDomEventListener, useEventListener } from "../utils/utils";
import { useToasts } from "./ui/toasts/toasts.vue";
2023-11-06 02:22:33 +00:00
import { injectContextRequired, requireMapContext, requireSearchBoxContext } from "./facil-map-context-provider/facil-map-context-provider.vue";
2023-11-01 18:45:16 +00:00
const context = injectContextRequired();
2023-11-06 02:22:33 +00:00
const mapContext = requireMapContext(context);
const searchBoxContext = requireSearchBoxContext(context);
2023-11-01 18:45:16 +00:00
const toasts = useToasts();
const fileInputRef = ref<HTMLInputElement>();
const files = ref<Array<FileResultObject & { title: string }>>([]);
2023-11-06 02:22:33 +00:00
const layers = shallowReactive<SearchResultsLayer[]>([]);
2023-11-01 18:45:16 +00:00
useEventListener(mapContext, "import-file", handleImportFile);
useEventListener(mapContext, "open-selection", handleOpenSelection);
2023-11-06 02:22:33 +00:00
useDomEventListener(mapContext.value.components.container, "dragenter", handleMapDragEnter);
useDomEventListener(mapContext.value.components.container, "dragover", handleMapDragOver);
useDomEventListener(mapContext.value.components.container, "drop", handleMapDrop);
2023-11-01 18:45:16 +00:00
2023-11-06 02:22:33 +00:00
const layerIds = computed(() => layers.map((layer) => Util.stamp(layer)));
2023-11-01 18:45:16 +00:00
function handleImportFile(): void {
fileInputRef.value?.click();
}
function handleOpenSelection(): void {
for (let i = 0; i < layerIds.value.length; i++) {
2023-11-06 02:22:33 +00:00
if (mapContext.value.selection.some((item) => item.type == "searchResult" && item.layerId == layerIds.value[i])) {
searchBoxContext.value.activateTab(`fm${context.id}-import-tab-${i}`);
2023-11-01 18:45:16 +00:00
break;
}
}
}
function handleMapDragEnter(event: Event): void {
event.preventDefault();
}
function handleMapDragOver(event: Event): void {
event.preventDefault();
}
function handleMapDrop(event: Event): void {
event.preventDefault();
importFiles((event as DragEvent).dataTransfer?.files);
}
async function importFiles(fileList: FileList | undefined): Promise<void> {
toasts.hideToast(`fm${context.id}-import-error`);
if(!fileList || fileList.length == 0)
return;
try {
const loadedFiles = await Promise.all([...fileList].map((file) => new Promise<string>((resolve, reject) => {
const reader = new FileReader();
reader.onload = () => {
resolve(reader.result as string);
};
reader.onerror = () => {
reject(reader.error);
};
reader.readAsText(file);
})));
const result = {
...parseFiles(loadedFiles),
title: (fileList.length == 1 && fileList[0].name) || pluralize("file", fileList.length, true)
};
if (result.features.length == 0 && result.errors)
toasts.showErrorToast(`fm${context.id}-import-error`, "Parsing error", `The selected ${pluralize("file", fileList.length)} could not be parsed.`);
else if (result.features.length == 0)
toasts.showErrorToast(`fm${context.id}-import-error`, "No geometries", `The selected ${pluralize("file", fileList.length)} did not contain any geometries.`);
else {
if (result.errors)
toasts.showErrorToast(`fm${context.id}-import-error`, "Parsing error", "Some of the selected files could not be parsed.", { variant: "warning" });
2023-11-06 02:22:33 +00:00
const layer = markRaw(new SearchResultsLayer(result.features, { pathOptions: { weight: 7 } }).addTo(mapContext.value.components.map));
mapContext.value.components.map.flyToBounds(layer.getBounds());
mapContext.value.components.selectionHandler.addSearchResultLayer(layer);
2023-11-01 18:45:16 +00:00
files.value.push(result);
2023-11-06 02:22:33 +00:00
layers.push(layer);
2023-11-01 18:45:16 +00:00
setTimeout(() => {
2023-11-06 02:22:33 +00:00
searchBoxContext.value.activateTab(`fm${context.id}-import-tab-${files.value.length - 1}`);
2023-11-01 18:45:16 +00:00
}, 0);
}
} catch (err) {
toasts.showErrorToast(`fm${context.id}-import-error`, "Error reading files", err);
}
}
function close(idx: number): void {
files.value.splice(idx, 1);
2023-11-06 02:22:33 +00:00
mapContext.value.components.selectionHandler.removeSearchResultLayer(layers[idx]);
layers[idx].remove();
layers.splice(idx, 1);
2023-11-01 18:45:16 +00:00
}
</script>
<template>
<div>
<input type="file" multiple class="d-none" ref="fileInputRef" @change="importFiles(fileInputRef!.files ?? undefined)">
<template v-for="(file, idx) in files" :key="idx">
<SearchBoxTab
:id="`fm${context.id}-import-tab-${idx}`"
class="fm-import-tab"
isCloseable
:title="file.title"
@close="close(idx)"
>
<FileResults
:file="file"
:layer-id="layerIds[idx]"
auto-zoom
></FileResults>
</SearchBoxTab>
</template>
</div>
</template>
<style lang="scss">
.fm-import-tab.fm-import-tab.fm-import-tab {
padding: 0.5rem;
display: flex;
flex-direction: column;
}
</style>