facilmap/frontend/src/lib/components/overpass-form/overpass-form.vue

200 wiersze
6.1 KiB
Vue
Czysty Zwykły widok Historia

2023-09-12 09:15:28 +00:00
<script setup lang="ts">
2023-11-01 18:45:16 +00:00
import { getOverpassPreset, overpassPresets, validateOverpassQuery } from "facilmap-leaflet";
2023-09-12 09:15:28 +00:00
import { debounce } from "lodash-es";
2023-11-01 18:45:16 +00:00
import { computed, ref, watch } from "vue";
import { injectMapContextRequired } from "../leaflet-map/leaflet-map.vue";
import { injectContextRequired } from "../../utils/context";
const context = injectContextRequired();
const mapContext = injectMapContextRequired();
const activeTab = ref(0);
const searchTerm = ref("");
const customQuery = ref("");
const customQueryValidationState = ref<boolean>();
const customQueryValidationError = ref<string>();
const customQueryAbortController = ref<AbortController>();
const validateCustomQueryDebounced = debounce(validateCustomQuery, 500);
const categories = computed(() => {
return overpassPresets.map((cat) => {
const presets = cat.presets.map((presets) => presets.map((preset) => ({ ...preset, isChecked: mapContext.overpassPresets.includes(preset) })));
return {
...cat,
presets,
checked: presets.flat().filter((preset) => preset.isChecked).length
2023-09-12 09:15:28 +00:00
}
2023-11-01 18:45:16 +00:00
});
});
const filteredPresets = computed(() => {
if (!searchTerm.value)
return [];
const lowerTerm = searchTerm.value.toLowerCase();
return categories.value.map((cat) => cat.presets).flat().flat().filter((preset) => preset.label.toLowerCase().includes(lowerTerm));
});
watch(() => mapContext.overpassCustom, () => {
customQuery.value = mapContext.overpassCustom;
}, { immediate: true });
function togglePreset(key: string, enable: boolean): void {
const without = mapContext.overpassPresets.filter((p) => p.key != key);
mapContext.components.overpassLayer.setQuery([
...without,
...(enable ? [getOverpassPreset(key)!] : [])
]);
}
2023-09-12 09:15:28 +00:00
2023-11-01 18:45:16 +00:00
function toggleIsCustom(): void {
if (mapContext.overpassIsCustom) {
mapContext.components.overpassLayer.setQuery(mapContext.overpassPresets);
if (customQueryAbortController.value)
customQueryAbortController.value.abort();
} else {
mapContext.components.overpassLayer.setQuery(mapContext.overpassCustom);
validateCustomQuery();
2023-09-12 09:15:28 +00:00
}
2023-11-01 18:45:16 +00:00
}
2023-09-12 09:15:28 +00:00
2023-11-01 18:45:16 +00:00
function handleCustomQueryInput(): void {
if (customQueryAbortController.value)
customQueryAbortController.value.abort();
validateCustomQueryDebounced();
}
2023-09-12 09:15:28 +00:00
2023-11-01 18:45:16 +00:00
async function validateCustomQuery(): Promise<void> {
const query = customQuery.value;
if (!query) {
customQueryValidationState.value = undefined;
customQueryValidationError.value = undefined;
} else {
const abortController = new AbortController();
customQueryAbortController.value = abortController;
const result = await validateOverpassQuery(query, customQueryAbortController.value.signal);
if (!abortController.signal.aborted) {
customQueryValidationState.value = !result;
customQueryValidationError.value = result;
} else
return;
2023-09-12 09:15:28 +00:00
}
2023-11-01 18:45:16 +00:00
if (customQueryValidationState.value !== false)
mapContext.components.overpassLayer.setQuery(query);
2023-09-12 09:15:28 +00:00
}
</script>
<template>
<div class="fm-overpass-form">
<template v-if="!mapContext.overpassIsCustom">
2023-10-24 00:08:47 +00:00
<input class="form-control" type="search" v-model="searchTerm" placeholder="Filter…" autofocus />
2023-09-12 09:15:28 +00:00
<hr />
<div v-if="searchTerm" class="checkbox-grid">
2023-11-01 18:45:16 +00:00
<template v-for="preset in filteredPresets" :key="preset.key">
2023-10-30 00:14:54 +00:00
<input
type="checkbox"
class="form-check-input"
2023-11-01 18:45:16 +00:00
:id="`fm${context.id}-overpass-form-preset-${preset.key}`"
2023-10-30 00:14:54 +00:00
:checked="preset.isChecked"
2023-11-01 18:45:16 +00:00
@update="togglePreset(preset.key, $event)"
2023-10-30 00:14:54 +00:00
/>
2023-11-01 18:45:16 +00:00
<label :for="`fm${context.id}-overpass-form-preset-${preset.key}`" class="form-check-label">
2023-10-30 00:14:54 +00:00
{{preset.label}}
</label>
</template>
2023-09-12 09:15:28 +00:00
</div>
2023-10-25 02:09:33 +00:00
<ul class="nav nav-pills">
2023-11-01 18:45:16 +00:00
<template v-for="(category, idx) in categories" :key="idx">
2023-10-25 02:09:33 +00:00
<li class="nav-item">
<a
href="javascript:"
class="nav-link"
:class="{ active: activeTab === idx }"
@click="activeTab = idx"
>
{{category.label}}
2023-10-30 00:14:54 +00:00
<span
v-if="category.checked > 0"
class="badge"
:class="activeTab == idx ? 'bg-secondary' : 'bg-primary'"
>{{category.checked}}</span>
2023-10-25 02:09:33 +00:00
</a>
</li>
</template>
</ul>
2023-11-01 18:45:16 +00:00
<template v-for="(presets, idx) in categories[activeTab].presets" :key="idx">
2023-10-25 02:09:33 +00:00
<hr />
<div class="checkbox-grid">
2023-11-01 18:45:16 +00:00
<template v-for="preset in presets" :key="preset.key">
2023-11-03 03:02:12 +00:00
<div class="form-check">
<input
type="checkbox"
class="form-check-input"
:id="`fm${context.id}-overpass-form-preset-${preset.key}`"
:checked="preset.isChecked"
@update="togglePreset(preset.key, $event)"
/>
<label :for="`fm${context.id}-overpass-form-preset-${preset.key}`" class="form-check-label">
{{preset.label}}
</label>
</div>
2023-10-30 00:14:54 +00:00
</template>
2023-10-25 02:09:33 +00:00
</div>
</template>
2023-09-12 09:15:28 +00:00
</template>
<template v-else>
2023-10-30 00:14:54 +00:00
<textarea v-model="customQuery" rows="5" :state="customQueryValidationState" class="form-control text-monospace" @input="handleCustomQueryInput"></textarea>
<div class="invalid-feedback" v-if="customQueryValidationError"><pre>{{customQueryValidationError}}</pre></div>
2023-09-12 09:15:28 +00:00
<hr />
<p>
Enter an <a href="https://wiki.openstreetmap.org/wiki/Overpass_API/Overpass_QL#The_Query_Statement" target="_blank">Overpass query statement</a>
here. Settings and an <code>out</code> statement are added automatically in the background. For ways and relations, a marker will be shown at
the geometric centre, no lines or polygons are drawn.
</p>
<p>
Example queries are <code>nwr[amenity=parking]</code> to get parking places or
<code>(nwr[amenity=atm];nwr[amenity=bank][atm][atm!=no];)</code> for ATMs.
</p>
</template>
<hr />
2023-11-03 03:02:12 +00:00
<div class="btn-toolbar">
2023-10-24 00:08:47 +00:00
<button
type="button"
2023-11-03 03:02:12 +00:00
class="btn btn-secondary"
2023-10-24 00:08:47 +00:00
:class="{ active: mapContext.overpassIsCustom }"
2023-09-12 09:15:28 +00:00
@click="toggleIsCustom()"
2023-10-24 00:08:47 +00:00
>Custom query</button>
2023-10-30 00:14:54 +00:00
</div>
2023-09-12 09:15:28 +00:00
</div>
</template>
<style lang="scss">
.fm-overpass-form {
display: flex;
flex-direction: column;
.checkbox-grid {
column-width: 160px;
padding: 0 .25rem;
}
pre {
color: inherit;
font-size: inherit;
}
fieldset + hr, p + hr {
margin-top: 0;
}
}
</style>