kopia lustrzana https://github.com/FacilMap/facilmap
Add more translations
rodzic
4cb2f52928
commit
14c3ab1a4a
|
@ -37,6 +37,22 @@
|
|||
"disconnected-header": "Verbindung unterbrochen",
|
||||
"disconnected": "Die Verbindung zum Server ist verloren gegangen. Verbindung wird wiederhergestellt…"
|
||||
},
|
||||
"custom-import-dialog": {
|
||||
"existing-type": "Existierender Objekttyp „{{name}}“",
|
||||
"import-type": "Objekttyp „{{name}}“ importieren",
|
||||
"no-import": "Nicht importieren",
|
||||
"import-error": "Fehler beim Importieren",
|
||||
"dialog-title": "Benutzerdefinierter Import",
|
||||
"ok-label": "Importieren",
|
||||
"type": "Objekttyp",
|
||||
"map-to": "Importieren als…",
|
||||
"markers": "Marker des Typs {{typeName}} ({{count}})",
|
||||
"lines_one": "Linie des Typs {{typeName}} ({{count}})",
|
||||
"lines_other": "Linien des Typs {{typeName}} ({{count}})",
|
||||
"untyped-markers": "Marker ohne Typ ({{count}})",
|
||||
"untyped-lines_one": "Linie/Polygon ohne Typ ({{count}})",
|
||||
"untyped-lines_other": "Linien/Polygone ohne Typ ({{count}})"
|
||||
},
|
||||
"edit-filter-dialog": {
|
||||
"title": "Filter",
|
||||
"apply": "Anwenden",
|
||||
|
@ -358,6 +374,9 @@
|
|||
"units-us": "US customary (Meilen und Füße)"
|
||||
},
|
||||
"route-form": {
|
||||
"route-description-outer": "Route von {{inner}}",
|
||||
"route-description-inner": "{{destinations}} {{mode}}",
|
||||
"route-description-inner-joiner": " nach ",
|
||||
"find-destination-error": "Fehler bei der Suche nach dem Wegpunkt „{{query}}“",
|
||||
"some-destinations-not-found": "Manche Wegpunkte konnten nicht gefunden werden.",
|
||||
"route-calculation-error": "Error calculating route",
|
||||
|
@ -381,6 +400,60 @@
|
|||
"route-form-tab": {
|
||||
"tab-label": "Route"
|
||||
},
|
||||
"route-mode": {
|
||||
"car-alt": "Auto",
|
||||
"bicycle-alt": "Fahrrad",
|
||||
"pedestrian-alt": "Zu Fuß",
|
||||
"straight-alt": "Luftlinie",
|
||||
"car-title": "Mit dem Auto",
|
||||
"bicycle-title": "Mit dem Fahrrad",
|
||||
"pedestrian-title": "Zu Fuß",
|
||||
"straight-title": "Luftlinie",
|
||||
"car": "Auto",
|
||||
"hgv": "LKW",
|
||||
"bicycle": "Fahrrad",
|
||||
"road-bike": "Rennrad",
|
||||
"mountain-bike": "Mountainbike",
|
||||
"electric-bike": "Pedelec",
|
||||
"walking": "Zu Fuß",
|
||||
"hiking": "Wandern",
|
||||
"wheelchair": "Rollstuhl",
|
||||
"straight": "Luftlinie",
|
||||
"fastest": "Schnellste Route",
|
||||
"shortest": "Kürzteste Route",
|
||||
"avoid-highways": "Autobahnen vermeiden",
|
||||
"avoid-toll-roads": "Mautstraßen vermeiden",
|
||||
"avoid-ferries": "Fähren vermeiden",
|
||||
"avoid-fords": "Furten vermeiden",
|
||||
"avoid-steps": "Treppen vermeiden",
|
||||
"custom-alt": "Benutzerdefiniert",
|
||||
"load-details": "Details laden (Höhenprofil, Straßenqualität, …)"
|
||||
},
|
||||
"search-box": {
|
||||
"close-alt": "Schließen",
|
||||
"resize-tooltip": "Größe durch Ziehen verändern, durch Klick zurücksetzen"
|
||||
},
|
||||
"search-form": {
|
||||
"search-description": "Suche nach {{query}}",
|
||||
"search-error": "Fehler bei der Suche",
|
||||
"search-alt": "Suchen",
|
||||
"clear-alt": "Verstecken",
|
||||
"auto-zoom": "Automatisch zu den Ergebnissen zoomen",
|
||||
"zoom-to-all": "Zur Übersicht aller Ergebnisse zoomen"
|
||||
},
|
||||
"search-form-tab": {
|
||||
"tab-label": "Suche"
|
||||
},
|
||||
"search-results": {
|
||||
"no-results": "Es wurden keine Ergebnisse gefunden.",
|
||||
"zoom-to-result-tooltip": "Zum Suchergebnis zoomen",
|
||||
"zoom-to-result-alt": "Zoomen",
|
||||
"show-details-tooltip": "Details anzeigen",
|
||||
"show-details-alt": "Details",
|
||||
"select-all": "Alle auswählen",
|
||||
"add-to-map-label": "Zur Karte hinzufügen",
|
||||
"custom-type-mapping": "Benutzerdefiniert…"
|
||||
},
|
||||
"toolbox-add-dropdown": {
|
||||
"label": "Erstellen",
|
||||
"manage-types": "Objekttypen verwalten"
|
||||
|
|
|
@ -37,6 +37,24 @@
|
|||
"disconnected-header": "Disconnected",
|
||||
"disconnected": "The connection to the server was lost. Trying to reconnect…"
|
||||
},
|
||||
"custom-import-dialog": {
|
||||
"existing-type": "Existing type “{{name}}”",
|
||||
"import-type": "Import type “{{name}}”",
|
||||
"no-import": "Do not import",
|
||||
"import-error": "Error importing to map",
|
||||
"dialog-title": "Custom Import",
|
||||
"ok-label": "Import",
|
||||
"type": "Type",
|
||||
"map-to": "Map to…",
|
||||
"markers_one": "Marker of type {{typeName}} ({{count}})",
|
||||
"markers_other": "Markers of type {{typeName}} ({{count}})",
|
||||
"lines_one": "Line of type {{typeName}} ({{count}})",
|
||||
"lines_other": "Lines of type {{typeName}} ({{count}})",
|
||||
"untyped-markers_one": "Untyped marker ({{count}})",
|
||||
"untyped-markers_other": "Untyped markers ({{count}})",
|
||||
"untyped-lines_one": "Untyped line/polygon ({{count}})",
|
||||
"untyped-lines_other": "Untyped lines/polygons ({{count}})"
|
||||
},
|
||||
"edit-filter-dialog": {
|
||||
"title": "Filter",
|
||||
"apply": "Apply",
|
||||
|
@ -358,6 +376,9 @@
|
|||
"units-us": "US customary (miles, feet)"
|
||||
},
|
||||
"route-form": {
|
||||
"route-description-outer": "Route from {{inner}}",
|
||||
"route-description-inner": "{{destinations}} {{mode}}",
|
||||
"route-description-inner-joiner": " to ",
|
||||
"find-destination-error": "Error finding destination “{{query}}”",
|
||||
"some-destinations-not-found": "Some destinations could not be found.",
|
||||
"route-calculation-error": "Error calculating route",
|
||||
|
@ -381,6 +402,61 @@
|
|||
"route-form-tab": {
|
||||
"tab-label": "Route"
|
||||
},
|
||||
"route-mode": {
|
||||
"car-alt": "Car",
|
||||
"bicycle-alt": "Bicycle",
|
||||
"pedestrian-alt": "Foot",
|
||||
"straight-alt": "Straight",
|
||||
"car-title": "Go by car",
|
||||
"bicycle-title": "Go by bicycle",
|
||||
"pedestrian-title": "Go on foot",
|
||||
"straight-title": "Go in a straight line",
|
||||
"car": "Car",
|
||||
"hgv": "HGV",
|
||||
"bicycle": "Bicycle",
|
||||
"road-bike": "Road bike",
|
||||
"mountain-bike": "Mountain bike",
|
||||
"electric-bike": "Electric bike",
|
||||
"walking": "Walking",
|
||||
"hiking": "Hiking",
|
||||
"wheelchair": "Wheelchair",
|
||||
"straight": "Straight line",
|
||||
"fastest": "Fastest",
|
||||
"shortest": "Shortest",
|
||||
"avoid-highways": "Avoid highways",
|
||||
"avoid-toll-roads": "Avoid toll roads",
|
||||
"avoid-ferries": "Avoid ferries",
|
||||
"avoid-fords": "Avoid fords",
|
||||
"avoid-steps": "Avoid steps",
|
||||
"custom-alt": "Custom",
|
||||
"load-details": "Load route details (elevation, road types, …)"
|
||||
},
|
||||
"search-box": {
|
||||
"close-alt": "Close",
|
||||
"resize-tooltip": "Drag to resize, click to reset"
|
||||
},
|
||||
"search-form": {
|
||||
"search-description": "Search for {{query}}",
|
||||
"search-error": "Search error",
|
||||
"search-alt": "Search",
|
||||
"clear-alt": "Clear",
|
||||
"auto-zoom": "Auto-zoom to results",
|
||||
"zoom-to-all": "Zoom to all results"
|
||||
},
|
||||
"search-form-tab": {
|
||||
"tab-label": "Search"
|
||||
},
|
||||
"search-results": {
|
||||
"no-results": "No results have been found.",
|
||||
"zoom-to-result-tooltip": "Zoom to result",
|
||||
"zoom-to-result-alt": "Zoom",
|
||||
"show-details-tooltip": "Show details",
|
||||
"show-details-alt": "Details",
|
||||
"select-all": "Select all",
|
||||
"add-to-map-label_one": "Add selected item to map",
|
||||
"add-to-map-label_other": "Add selected items to map",
|
||||
"custom-type-mapping": "Custom type mapping…"
|
||||
},
|
||||
"toolbox-add-dropdown": {
|
||||
"label": "Add",
|
||||
"manage-types": "Manage types"
|
||||
|
|
|
@ -6,9 +6,11 @@
|
|||
import type { SearchBoxEventMap, SearchBoxTab, WritableSearchBoxContext } from "../facil-map-context-provider/search-box-context";
|
||||
import mitt from "mitt";
|
||||
import { injectContextRequired, requireMapContext } from "../facil-map-context-provider/facil-map-context-provider.vue";
|
||||
import { useI18n } from "../../utils/i18n";
|
||||
|
||||
const context = injectContextRequired();
|
||||
const mapContext = requireMapContext(context);
|
||||
const i18n = useI18n();
|
||||
|
||||
const tabs = reactive(new Map<string, SearchBoxTab>());
|
||||
const activeTabId = ref<string | undefined>();
|
||||
|
@ -228,7 +230,7 @@
|
|||
href="javascript:"
|
||||
@click="tab.onClose()"
|
||||
draggable="false"
|
||||
><Icon icon="remove" alt="Close"></Icon></a>
|
||||
><Icon icon="remove" :alt="i18n.t('search-box.close-alt')"></Icon></a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
@ -243,7 +245,7 @@
|
|||
v-show="!context.isNarrow"
|
||||
href="javascript:"
|
||||
class="fm-search-box-resize"
|
||||
v-tooltip.right="'Drag to resize, click to reset'"
|
||||
v-tooltip.right="i18n.t('search-box.resize-tooltip')"
|
||||
ref="resizeHandleRef"
|
||||
><Icon icon="resize-horizontal"></Icon></a>
|
||||
</div>
|
||||
|
|
|
@ -7,10 +7,12 @@
|
|||
import SearchBoxTab from "../search-box/search-box-tab.vue";
|
||||
import { injectContextRequired, requireMapContext, requireSearchBoxContext } from "../facil-map-context-provider/facil-map-context-provider.vue";
|
||||
import type { WritableSearchFormTabContext } from "../facil-map-context-provider/search-form-tab-context";
|
||||
import { useI18n } from "../../utils/i18n";
|
||||
|
||||
const context = injectContextRequired();
|
||||
const mapContext = requireMapContext(context);
|
||||
const searchBoxContext = requireSearchBoxContext(context);
|
||||
const i18n = useI18n();
|
||||
|
||||
const searchForm = ref<InstanceType<typeof SearchForm>>();
|
||||
|
||||
|
@ -38,7 +40,7 @@
|
|||
<template>
|
||||
<SearchBoxTab
|
||||
:id="`fm${context.id}-search-form-tab`"
|
||||
title="Search"
|
||||
:title="i18n.t('search-form-tab.tab-label')"
|
||||
:hashQuery="hashQuery"
|
||||
class="fm-search-form-tab"
|
||||
>
|
||||
|
|
|
@ -14,6 +14,7 @@
|
|||
import { computed, reactive, ref, watch } from "vue";
|
||||
import DropdownMenu from "../ui/dropdown-menu.vue";
|
||||
import { injectContextRequired, requireClientContext, requireMapContext } from "../facil-map-context-provider/facil-map-context-provider.vue";
|
||||
import { useI18n } from "../../utils/i18n";
|
||||
|
||||
const emit = defineEmits<{
|
||||
"hash-query-change": [query: HashQuery | undefined];
|
||||
|
@ -24,6 +25,7 @@
|
|||
const mapContext = requireMapContext(context);
|
||||
|
||||
const toasts = useToasts();
|
||||
const i18n = useI18n();
|
||||
|
||||
const layerId = Util.stamp(mapContext.value.components.searchResultsLayer);
|
||||
|
||||
|
@ -49,10 +51,10 @@
|
|||
return {
|
||||
query: loadedSearchString.value,
|
||||
...(zoomDestination.value && normalizeZoomDestination(mapContext.value.components.map, zoomDestination.value)),
|
||||
description: `Search for ${loadedSearchString.value}`
|
||||
description: i18n.t("search-form.search-description", { query: loadedSearchString.value })
|
||||
};
|
||||
} else if (loadingSearchString.value)
|
||||
return { query: loadingSearchString.value, description: `Search for ${loadedSearchString.value}` };
|
||||
return { query: loadingSearchString.value, description: i18n.t("search-form.search-description", { query: loadedSearchString.value }) };
|
||||
else
|
||||
return undefined;
|
||||
});
|
||||
|
@ -132,7 +134,7 @@
|
|||
}
|
||||
}
|
||||
} catch(err) {
|
||||
toasts.showErrorToast(`fm${context.id}-search-form-error`, "Search error", err);
|
||||
toasts.showErrorToast(`fm${context.id}-search-form-error`, i18n.t("search-form.search-error"), err);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
@ -194,7 +196,7 @@
|
|||
type="submit"
|
||||
class="btn btn-secondary"
|
||||
>
|
||||
<Icon icon="search" alt="Search"></Icon>
|
||||
<Icon icon="search" :alt="i18n.t('search-form.search-alt')"></Icon>
|
||||
</button>
|
||||
<button
|
||||
v-if="searchResults || mapResults || fileResult"
|
||||
|
@ -202,7 +204,7 @@
|
|||
class="btn btn-secondary"
|
||||
@click="reset()"
|
||||
>
|
||||
<Icon icon="remove" alt="Clear"></Icon>
|
||||
<Icon icon="remove" :alt="i18n.t('search-form.clear-alt')"></Icon>
|
||||
</button>
|
||||
<DropdownMenu noWrapper>
|
||||
<li>
|
||||
|
@ -211,7 +213,7 @@
|
|||
class="dropdown-item"
|
||||
@click.capture.stop.prevent="storage.autoZoom = !storage.autoZoom"
|
||||
>
|
||||
<Icon :icon="storage.autoZoom ? 'check' : 'unchecked'"></Icon> Auto-zoom to results
|
||||
<Icon :icon="storage.autoZoom ? 'check' : 'unchecked'"></Icon> {{i18n.t("search-form.auto-zoom")}}
|
||||
</a>
|
||||
</li>
|
||||
|
||||
|
@ -221,7 +223,7 @@
|
|||
class="dropdown-item"
|
||||
@click.capture.stop.prevent="storage.zoomToAll = !storage.zoomToAll"
|
||||
>
|
||||
<Icon :icon="storage.zoomToAll ? 'check' : 'unchecked'"></Icon> Zoom to all results
|
||||
<Icon :icon="storage.zoomToAll ? 'check' : 'unchecked'"></Icon> {{i18n.t("search-form.zoom-to-all")}}
|
||||
</a>
|
||||
</li>
|
||||
</DropdownMenu>
|
||||
|
|
|
@ -10,10 +10,12 @@
|
|||
import { injectContextRequired, requireClientContext } from "../facil-map-context-provider/facil-map-context-provider.vue";
|
||||
import { type LineWithTags, type MarkerWithTags, addToMap, searchResultToLineWithTags, searchResultToMarkerWithTags } from "../../utils/add";
|
||||
import { formatTypeName, getOrderedTypes } from "facilmap-utils";
|
||||
import { useI18n } from "../../utils/i18n";
|
||||
|
||||
const context = injectContextRequired();
|
||||
const client = requireClientContext(context);
|
||||
const toasts = useToasts();
|
||||
const i18n = useI18n();
|
||||
|
||||
const props = withDefaults(defineProps<{
|
||||
customTypes?: FileResultObject["types"];
|
||||
|
@ -43,25 +45,25 @@
|
|||
|
||||
for (const type of orderedTypes.value) {
|
||||
if (type.name == customType.name && type.type == customType.type)
|
||||
recommendedOptions.push({ key: `e${type.id}`, value: `e${type.id}`, text: `Existing type “${formatTypeName(type.name)}”` });
|
||||
recommendedOptions.push({ key: `e${type.id}`, value: `e${type.id}`, text: i18n.t("custom-import-dialog.existing-type", { name: formatTypeName(type.name) }) });
|
||||
}
|
||||
|
||||
if (client.value.writable == 2 && !typeExists(client.value, customType))
|
||||
recommendedOptions.push({ key: `i${customTypeId}`, value: `i${customTypeId}`, text: `Import type “${customType.name}”` });
|
||||
recommendedOptions.push({ key: `i${customTypeId}`, value: `i${customTypeId}`, text: i18n.t("custom-import-dialog.import-type", { name: customType.name }) });
|
||||
|
||||
recommendedOptions.push({ key: "false1", value: false, text: "Do not import" });
|
||||
recommendedOptions.push({ key: "false1", value: false, text: i18n.t("custom-import-dialog.no-import") });
|
||||
|
||||
|
||||
const otherOptions: Option[] = [];
|
||||
|
||||
for (const type of orderedTypes.value) {
|
||||
if (type.name != customType.name && type.type == customType.type)
|
||||
otherOptions.push({ key: `e${type.id}`, value: `e${type.id}`, text: `Existing type “${formatTypeName(type.name)}”` });
|
||||
otherOptions.push({ key: `e${type.id}`, value: `e${type.id}`, text: i18n.t("custom-import-dialog.existing-type", { name: formatTypeName(type.name) }) });
|
||||
}
|
||||
|
||||
for (const [customTypeId2, customType2] of Object.entries(props.customTypes)) {
|
||||
if (client.value.writable == 2 && customType2.type == customType.type && customTypeId2 != customTypeId && !typeExists(client.value, customType2))
|
||||
otherOptions.push({ key: `i${customTypeId2}`, value: `i${customTypeId2}`, text: `Import type “${customType2.name}”` });
|
||||
otherOptions.push({ key: `i${customTypeId2}`, value: `i${customTypeId2}`, text: i18n.t("custom-import-dialog.import-type", { name: customType2.name }) });
|
||||
}
|
||||
|
||||
|
||||
|
@ -89,17 +91,17 @@
|
|||
|
||||
const untypedMarkerMappingOptions = computed(() => {
|
||||
const options: Array<{ key: string; value: string | false; text: string }> = [];
|
||||
options.push({ key: "false", value: false, text: "Do not import" });
|
||||
options.push({ key: "false", value: false, text: i18n.t("custom-import-dialog.no-import") });
|
||||
|
||||
for (const customTypeId of Object.keys(props.customTypes)) {
|
||||
const customType = props.customTypes[customTypeId as any];
|
||||
if (client.value.writable && customType.type == "marker" && !typeExists(client.value, customType))
|
||||
options.push({ key: `i${customTypeId}`, value: `i${customTypeId}`, text: `Import type “${customType.name}”` });
|
||||
options.push({ key: `i${customTypeId}`, value: `i${customTypeId}`, text: i18n.t("custom-import-dialog.import-type", { name: customType.name }) });
|
||||
}
|
||||
|
||||
for (const type of orderedTypes.value) {
|
||||
if (type.type == "marker")
|
||||
options.push({ key: `e${type.id}`, value: `e${type.id}`, text: `Existing type “${formatTypeName(type.name)}”` });
|
||||
options.push({ key: `e${type.id}`, value: `e${type.id}`, text: i18n.t("custom-import-dialog.existing-type", { name: formatTypeName(type.name) }) });
|
||||
}
|
||||
|
||||
return options;
|
||||
|
@ -107,17 +109,17 @@
|
|||
|
||||
const untypedLineMappingOptions = computed(() => {
|
||||
const options: Array<{ key: string; value: string | false; text: string }> = [];
|
||||
options.push({ key: "false", value: false, text: "Do not import" });
|
||||
options.push({ key: "false", value: false, text: i18n.t("custom-import-dialog.no-import") });
|
||||
|
||||
for (const customTypeId of Object.keys(props.customTypes)) {
|
||||
const customType = props.customTypes[customTypeId as any];
|
||||
if (client.value.writable && customType.type == "line")
|
||||
options.push({ key: `i${customTypeId}`, value: `i${customTypeId}`, text: `Import type “${customType.name}”` });
|
||||
options.push({ key: `i${customTypeId}`, value: `i${customTypeId}`, text: i18n.t("custom-import-dialog.import-type", { name: customType.name }) });
|
||||
}
|
||||
|
||||
for (const type of orderedTypes.value) {
|
||||
if (type.type == "line")
|
||||
options.push({ key: `e${type.id}`, value: `e${type.id}`, text: `Existing type “${formatTypeName(type.name)}”` });
|
||||
options.push({ key: `e${type.id}`, value: `e${type.id}`, text: i18n.t("custom-import-dialog.existing-type", { name: formatTypeName(type.name) }) });
|
||||
}
|
||||
|
||||
return options;
|
||||
|
@ -155,17 +157,17 @@
|
|||
|
||||
modalRef.value?.modal.hide();
|
||||
} catch(err) {
|
||||
toasts.showErrorToast(`fm${context.id}-search-result-info-add-error`, "Error importing to map", err);
|
||||
toasts.showErrorToast(`fm${context.id}-search-result-info-add-error`, i18n.t("custom-import-dialog.import-error"), err);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ModalDialog
|
||||
title="Custom Import"
|
||||
:title="i18n.t('custom-import-dialog.dialog-title')"
|
||||
class="fm-search-results-custom-import"
|
||||
isCreate
|
||||
okLabel="Import"
|
||||
:okLabel="i18n.t('custom-import-dialog.ok-label')"
|
||||
@submit="$event.waitUntil(save())"
|
||||
ref="modalRef"
|
||||
@hidden="emit('hidden')"
|
||||
|
@ -173,14 +175,27 @@
|
|||
<table class="table table-striped table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Type</th>
|
||||
<th>Map to…</th>
|
||||
<th>{{i18n.t("custom-import-dialog.type")}}</th>
|
||||
<th>{{i18n.t("custom-import-dialog.map-to")}}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<!-- eslint-disable-next-line vue/require-v-for-key -->
|
||||
<tr v-for="(options, importTypeId) in customMappingOptions">
|
||||
<td><label :for="`${id}-map-type-${importTypeId}`">{{customTypes[importTypeId as number].type == 'marker' ? 'Markers' : 'Lines'}} of type “{{customTypes[importTypeId as number].name}}” ({{activeFileResultsByType[importTypeId as number].length}})</label></td>
|
||||
<td>
|
||||
<label :for="`${id}-map-type-${importTypeId}`">
|
||||
{{customTypes[importTypeId as number].type == 'marker'
|
||||
? i18n.t("custom-import-dialog.markers", {
|
||||
typeName: customTypes[importTypeId as number].name,
|
||||
count: activeFileResultsByType[importTypeId as number].length
|
||||
})
|
||||
: i18n.t("custom-import-dialog.lines", {
|
||||
typeName: customTypes[importTypeId as number].name,
|
||||
count: activeFileResultsByType[importTypeId as number].length
|
||||
})
|
||||
}}
|
||||
</label>
|
||||
</td>
|
||||
<td>
|
||||
<select :id="`${id}-map-type-${importTypeId}`" v-model="customMapping[importTypeId as number]">
|
||||
<option v-for="option in options" :key="option.key" :value="option.value">{{option.text}}</option>
|
||||
|
@ -188,7 +203,7 @@
|
|||
</td>
|
||||
</tr>
|
||||
<tr v-if="untypedMarkers.length > 0">
|
||||
<td><label :for="`${id}-map-untyped-markers`">Untyped markers ({{untypedMarkers.length}})</label></td>
|
||||
<td><label :for="`${id}-map-untyped-markers`">{{i18n.t("custom-import-dialog.untyped-markers", { count: untypedMarkers.length })}}</label></td>
|
||||
<td>
|
||||
<select :id="`${id}-map-untyped-markers`" v-model="untypedMarkerMapping">
|
||||
<option v-for="option in untypedMarkerMappingOptions" :key="option.key" :value="option.value">{{option.text}}</option>
|
||||
|
@ -196,7 +211,7 @@
|
|||
</td>
|
||||
</tr>
|
||||
<tr v-if="untypedLines.length > 0">
|
||||
<td><label :for="`${id}-map-untyped-lines`">Untyped lines/polygons ({{untypedLines.length}})</label></td>
|
||||
<td><label :for="`${id}-map-untyped-lines`">{{i18n.t("custom-import-dialog.untyped-lines", { count: untypedLines.length })}}</label></td>
|
||||
<td>
|
||||
<select :id="`${id}-map-untyped-lines`" v-model="untypedLineMapping">
|
||||
<option v-for="option in untypedLineMappingOptions" :key="option.key" :value="option.value">{{option.text}}</option>
|
||||
|
|
|
@ -15,11 +15,13 @@
|
|||
import { injectContextRequired, requireClientContext, requireMapContext } from "../facil-map-context-provider/facil-map-context-provider.vue";
|
||||
import AddToMapDropdown from "../ui/add-to-map-dropdown.vue";
|
||||
import { formatTypeName, normalizeLineName, normalizeMarkerName } from "facilmap-utils";
|
||||
import { useI18n } from "../../utils/i18n";
|
||||
|
||||
const context = injectContextRequired();
|
||||
const client = requireClientContext(context);
|
||||
const mapContext = requireMapContext(context);
|
||||
const searchBoxContext = toRef(() => context.components.searchBox);
|
||||
const i18n = useI18n();
|
||||
|
||||
const props = withDefaults(defineProps<{
|
||||
searchResults?: Array<SearchResult | FileResult>;
|
||||
|
@ -168,7 +170,7 @@
|
|||
v-if="(!searchResults || searchResults.length == 0) && (!mapResults || mapResults.length == 0)"
|
||||
class="alert alert-danger"
|
||||
>
|
||||
No results have been found.
|
||||
{{i18n.t("search-results.no-results")}}
|
||||
</div>
|
||||
|
||||
<ul v-if="mapResults && mapResults.length > 0" class="list-group">
|
||||
|
@ -184,8 +186,8 @@
|
|||
{{" "}}
|
||||
<span class="result-type">({{formatTypeName(client.types[result.typeId].name)}})</span>
|
||||
</span>
|
||||
<a v-if="showZoom" href="javascript:" @click="zoomToResult(result)" v-tooltip.hover.left="'Zoom to result'"><Icon icon="zoom-in" alt="Zoom"></Icon></a>
|
||||
<a href="javascript:" @click="handleOpen(result, $event)" v-tooltip.left="'Show details'"><Icon icon="arrow-right" alt="Details"></Icon></a>
|
||||
<a v-if="showZoom" href="javascript:" @click="zoomToResult(result)" v-tooltip.hover.left="i18n.t('search-results.zoom-to-result-tooltip')"><Icon icon="zoom-in" :alt="i18n.t('search-results.zoom-to-result-alt')"></Icon></a>
|
||||
<a href="javascript:" @click="handleOpen(result, $event)" v-tooltip.left="i18n.t('search-results.show-details-tooltip')"><Icon icon="arrow-right" :alt="i18n.t('search-results.show-details-alt')"></Icon></a>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
|
@ -204,8 +206,8 @@
|
|||
{{" "}}
|
||||
<span class="result-type" v-if="result.type">({{result.type}})</span>
|
||||
</span>
|
||||
<a v-if="showZoom" href="javascript:" @click="zoomToResult(result)" v-tooltip.left="'Zoom to result'"><Icon icon="zoom-in" alt="Zoom"></Icon></a>
|
||||
<a href="javascript:" @click="handleOpen(result, $event)" v-tooltip.right="'Show details'"><Icon icon="arrow-right" alt="Details"></Icon></a>
|
||||
<a v-if="showZoom" href="javascript:" @click="zoomToResult(result)" v-tooltip.left="i18n.t('search-results.zoom-to-result-tooltip')"><Icon icon="zoom-in" :alt="i18n.t('search-results.zoom-to-result-alt')"></Icon></a>
|
||||
<a href="javascript:" @click="handleOpen(result, $event)" v-tooltip.right="i18n.t('search-results.show-details-tooltip')"><Icon icon="arrow-right" :alt="i18n.t('search-results.show-details-alt')"></Icon></a>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
|
@ -218,10 +220,10 @@
|
|||
class="btn btn-secondary btn-sm"
|
||||
:class="{ active: isAllSelected }"
|
||||
@click="toggleSelectAll"
|
||||
>Select all</button>
|
||||
>{{i18n.t("search-results.select-all")}}</button>
|
||||
|
||||
<AddToMapDropdown
|
||||
:label="`Add selected item${activeSearchResults.length == 1 ? '' : 's'} to map`"
|
||||
:label="i18n.t('search-results.add-to-map-label', { count: activeSearchResults.length })"
|
||||
:markers="activeMarkersWithTags"
|
||||
:lines="activeLinesWithTags"
|
||||
size="sm"
|
||||
|
@ -233,7 +235,7 @@
|
|||
href="javascript:"
|
||||
class="dropdown-item"
|
||||
@click="customImport = true"
|
||||
>Custom type mapping…</a>
|
||||
>{{i18n.t("search-results.custom-type-mapping")}}</a>
|
||||
</li>
|
||||
</template>
|
||||
</AddToMapDropdown>
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<script setup lang="ts">
|
||||
import { type SlotsType, computed, defineComponent, h, ref, shallowRef, useSlots, watch, watchEffect } from "vue";
|
||||
import { maxSizeModifiers, type ButtonSize, type ButtonVariant, useMaxBreakpoint } from "../../utils/bootstrap";
|
||||
import { getMaxSizeModifiers, type ButtonSize, type ButtonVariant, useMaxBreakpoint } from "../../utils/bootstrap";
|
||||
import Dropdown from "bootstrap/js/dist/dropdown";
|
||||
import vLinkDisabled from "../../utils/link-disabled";
|
||||
import type { TooltipPlacement } from "../../utils/tooltip";
|
||||
|
@ -26,6 +26,7 @@
|
|||
tabindex?: number;
|
||||
tooltip?: string; // TODO
|
||||
tooltipPlacement?: TooltipPlacement;
|
||||
maxWidth?: string;
|
||||
}>(), {
|
||||
isOpen: undefined,
|
||||
noWrapper: false,
|
||||
|
@ -67,7 +68,7 @@
|
|||
...defaultConfig,
|
||||
modifiers: [
|
||||
...(defaultConfig.modifiers ?? []),
|
||||
...maxSizeModifiers
|
||||
...getMaxSizeModifiers({ maxWidth: props.maxWidth })
|
||||
],
|
||||
strategy: "fixed"
|
||||
})
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
import Tooltip from "bootstrap/js/dist/tooltip";
|
||||
import { useResizeObserver } from "../../utils/vue";
|
||||
import { useDomEventListener } from "../../utils/utils";
|
||||
import { maxSizeModifiers } from "../../utils/bootstrap";
|
||||
import { getMaxSizeModifiers } from "../../utils/bootstrap";
|
||||
|
||||
/**
|
||||
* Like Bootstrap Popover, but uses an existing popover element rather than creating a new one. This way, the popover
|
||||
|
@ -70,7 +70,7 @@
|
|||
...defaultConfig,
|
||||
modifiers: [
|
||||
...(defaultConfig.modifiers ?? []),
|
||||
...maxSizeModifiers
|
||||
...getMaxSizeModifiers()
|
||||
],
|
||||
strategy: "fixed"
|
||||
})
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
<script lang="ts">
|
||||
<script setup lang="ts">
|
||||
import type { RouteMode as RouteModeType } from "facilmap-types";
|
||||
import { type DecodedRouteMode, decodeRouteMode, encodeRouteMode } from "facilmap-utils";
|
||||
import Icon from "./icon.vue";
|
||||
|
@ -6,11 +6,29 @@
|
|||
import { getUniqueId } from "../../utils/utils";
|
||||
import vTooltip, { type TooltipPlacement } from "../../utils/tooltip";
|
||||
import DropdownMenu from "../ui/dropdown-menu.vue";
|
||||
import { useI18n } from "../../utils/i18n";
|
||||
|
||||
type Mode = Exclude<DecodedRouteMode['mode'], 'track'>;
|
||||
type Type = DecodedRouteMode['type'];
|
||||
|
||||
const constants: {
|
||||
const i18n = useI18n();
|
||||
|
||||
const props = withDefaults(defineProps<{
|
||||
modelValue: RouteModeType;
|
||||
tabindex?: number;
|
||||
disabled?: boolean;
|
||||
tooltipPlacement?: TooltipPlacement;
|
||||
}>(), {
|
||||
tooltipPlacement: "top"
|
||||
});
|
||||
|
||||
const emit = defineEmits<{
|
||||
"update:modelValue": [value: RouteModeType];
|
||||
}>();
|
||||
|
||||
const id = getUniqueId("fm-route-mode");
|
||||
|
||||
const constants = computed((): {
|
||||
modes: Array<Mode>;
|
||||
modeIcon: Record<Mode, string>;
|
||||
modeAlt: Record<Mode, string>;
|
||||
|
@ -22,7 +40,7 @@
|
|||
avoid: DecodedRouteMode['avoid'];
|
||||
avoidAllowed: Record<DecodedRouteMode['avoid'][0], (mode: DecodedRouteMode['mode'], type: Type) => boolean>;
|
||||
avoidText: Record<DecodedRouteMode['avoid'][0], string>;
|
||||
} = {
|
||||
} => ({
|
||||
modes: ["car", "bicycle", "pedestrian", ""],
|
||||
|
||||
modeIcon: {
|
||||
|
@ -33,17 +51,17 @@
|
|||
},
|
||||
|
||||
modeAlt: {
|
||||
car: "Car",
|
||||
bicycle: "Bicycle",
|
||||
pedestrian: "Foot",
|
||||
"": "Straight"
|
||||
car: i18n.t("route-mode.car-alt"),
|
||||
bicycle: i18n.t("route-mode.bicycle-alt"),
|
||||
pedestrian: i18n.t("route-mode.pedestrian-alt"),
|
||||
"": i18n.t("route-mode.straight-alt")
|
||||
},
|
||||
|
||||
modeTitle: {
|
||||
car: "by car",
|
||||
bicycle: "by bicycle",
|
||||
pedestrian: "on foot",
|
||||
"": "in a straight line"
|
||||
car: i18n.t("route-mode.car-title"),
|
||||
bicycle: i18n.t("route-mode.bicycle-title"),
|
||||
pedestrian: i18n.t("route-mode.pedestrian-title"),
|
||||
"": i18n.t("route-mode.straight-title")
|
||||
},
|
||||
|
||||
types: {
|
||||
|
@ -55,30 +73,30 @@
|
|||
|
||||
typeText: {
|
||||
car: {
|
||||
"": "Car",
|
||||
"hgv": "HGV"
|
||||
"": i18n.t("route-mode.car"),
|
||||
"hgv": i18n.t("route-mode.hgv")
|
||||
},
|
||||
bicycle: {
|
||||
"": "Bicycle",
|
||||
road: "Road bike",
|
||||
mountain: "Mountain bike",
|
||||
electric: "Electric bike"
|
||||
"": i18n.t("route-mode.bicycle"),
|
||||
road: i18n.t("route-mode.road-bike"),
|
||||
mountain: i18n.t("route-mode.mountain-bike"),
|
||||
electric: i18n.t("route-mode.electric-bike")
|
||||
},
|
||||
pedestrian: {
|
||||
"": "Walking",
|
||||
hiking: "Hiking",
|
||||
wheelchair: "Wheelchair"
|
||||
"": i18n.t("route-mode.walking"),
|
||||
hiking: i18n.t("route-mode.hiking"),
|
||||
wheelchair: i18n.t("route-mode.wheelchair")
|
||||
},
|
||||
"": {
|
||||
"": "Straight line"
|
||||
"": i18n.t("route-mode.straight")
|
||||
}
|
||||
},
|
||||
|
||||
preferences: ["fastest", "shortest"],
|
||||
|
||||
preferenceText: {
|
||||
fastest: "Fastest",
|
||||
shortest: "Shortest"
|
||||
fastest: i18n.t("route-mode.fastest"),
|
||||
shortest: i18n.t("route-mode.shortest")
|
||||
},
|
||||
|
||||
avoid: ["highways", "tollways", "ferries", "fords", "steps"],
|
||||
|
@ -96,30 +114,13 @@
|
|||
},
|
||||
|
||||
avoidText: {
|
||||
highways: "highways",
|
||||
tollways: "toll roads",
|
||||
ferries: "ferries",
|
||||
fords: "fords",
|
||||
steps: "steps"
|
||||
highways: i18n.t("route-mode.avoid-highways"),
|
||||
tollways: i18n.t("route-mode.avoid-toll-roads"),
|
||||
ferries: i18n.t("route-mode.avoid-ferries"),
|
||||
fords: i18n.t("route-mode.avoid-fords"),
|
||||
steps: i18n.t("route-mode.avoid-steps")
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<script setup lang="ts">
|
||||
const props = withDefaults(defineProps<{
|
||||
modelValue: RouteModeType;
|
||||
tabindex?: number;
|
||||
disabled?: boolean;
|
||||
tooltipPlacement?: TooltipPlacement;
|
||||
}>(), {
|
||||
tooltipPlacement: "top"
|
||||
});
|
||||
|
||||
const emit = defineEmits<{
|
||||
"update:modelValue": [value: RouteModeType];
|
||||
}>();
|
||||
|
||||
const id = getUniqueId("fm-route-mode");
|
||||
}));
|
||||
|
||||
const decodedMode = ref(decodeRouteMode(props.modelValue));
|
||||
|
||||
|
@ -134,7 +135,7 @@
|
|||
}
|
||||
}, { deep: true });
|
||||
|
||||
const types = computed(() => (Object.keys(constants.types) as Mode[]).map((mode) => constants.types[mode].map((type) => ([mode, type] as [Mode, Type]))).flat());
|
||||
const types = computed(() => (Object.keys(constants.value.types) as Mode[]).map((mode) => constants.value.types[mode].map((type) => ([mode, type] as [Mode, Type]))).flat());
|
||||
|
||||
function isTypeActive(mode: DecodedRouteMode['mode'], type: DecodedRouteMode['type']): boolean {
|
||||
return (!mode && !decodedMode.value.mode || mode == decodedMode.value.mode) && (!type && !decodedMode.value.type || type == decodedMode.value.type);
|
||||
|
@ -146,7 +147,7 @@
|
|||
|
||||
if(decodedMode.value.avoid) {
|
||||
for(let i=0; i < decodedMode.value.avoid.length; i++) {
|
||||
if(!constants.avoidAllowed[decodedMode.value.avoid[i]](decodedMode.value.mode, decodedMode.value.type))
|
||||
if(!constants.value.avoidAllowed[decodedMode.value.avoid[i]](decodedMode.value.mode, decodedMode.value.type))
|
||||
decodedMode.value.avoid.splice(i--, 1);
|
||||
}
|
||||
}
|
||||
|
@ -176,10 +177,9 @@
|
|||
v-model="decodedMode.mode"
|
||||
:value="mode"
|
||||
:tabindex="tabindex != null ? tabindex + idx : undefined"
|
||||
v-tooltip:[tooltipPlacement]="`Go ${constants.modeTitle[mode]}`"
|
||||
:disabled="disabled"
|
||||
/>
|
||||
<label class="btn btn-secondary" :for="`${id}-mode-${mode}`">
|
||||
<label class="btn btn-secondary" :for="`${id}-mode-${mode}`" v-tooltip:[tooltipPlacement]="constants.modeTitle[mode]">
|
||||
<Icon :icon="constants.modeIcon[mode]" :alt="constants.modeAlt[mode]"></Icon>
|
||||
</label>
|
||||
</template>
|
||||
|
@ -192,9 +192,10 @@
|
|||
:isDisabled="disabled"
|
||||
noWrapper
|
||||
menuClass="fm-route-mode-customize"
|
||||
maxWidth="32rem"
|
||||
>
|
||||
<template #label>
|
||||
<Icon icon="cog" alt="Custom"/>
|
||||
<Icon icon="cog" :alt="i18n.t('route-mode.custom-alt')"/>
|
||||
</template>
|
||||
|
||||
<template #default>
|
||||
|
@ -204,7 +205,7 @@
|
|||
href="javascript:"
|
||||
@click.capture.stop.prevent="decodedMode.details = !decodedMode.details"
|
||||
>
|
||||
<Icon :icon="decodedMode.details ? 'check' : 'unchecked'"></Icon> Load route details (elevation, road types, …)
|
||||
<Icon :icon="decodedMode.details ? 'check' : 'unchecked'"></Icon> {{i18n.t("route-mode.load-details")}}
|
||||
</a>
|
||||
</li>
|
||||
|
||||
|
@ -248,7 +249,7 @@
|
|||
href="javascript:"
|
||||
@click.capture.stop.prevent="toggleAvoid(avoid)"
|
||||
>
|
||||
<Icon :icon="decodedMode.avoid.includes(avoid) ? 'check' : 'unchecked'"></Icon> Avoid {{constants.avoidText[avoid]}}
|
||||
<Icon :icon="decodedMode.avoid.includes(avoid) ? 'check' : 'unchecked'"></Icon> {{constants.avoidText[avoid]}}
|
||||
</a>
|
||||
</li>
|
||||
</template>
|
||||
|
@ -269,7 +270,6 @@
|
|||
}
|
||||
|
||||
.fm-route-mode-customize {
|
||||
width: 380px;
|
||||
font-size: 0; /* https://stackoverflow.com/a/5647640/242365 */
|
||||
|
||||
li {
|
||||
|
|
|
@ -57,7 +57,7 @@ export type ButtonSize = "lg" | "sm";
|
|||
* An array of popper modifiers that uses popper-max-size-modifier to shrink the popover to prevent overflow
|
||||
* rather than move it, as is the default in Bootstrap.
|
||||
*/
|
||||
export const maxSizeModifiers: Array<Partial<Modifier<any, any>>> = [
|
||||
export const getMaxSizeModifiers = ({ maxWidth = "30rem" }: { maxWidth?: string } = {}): Array<Partial<Modifier<any, any>>> => [
|
||||
{
|
||||
...maxSize,
|
||||
options: {
|
||||
|
@ -75,7 +75,7 @@ export const maxSizeModifiers: Array<Partial<Modifier<any, any>>> = [
|
|||
|
||||
state.styles.popper = {
|
||||
...state.styles.popper,
|
||||
maxWidth: `min(30rem, ${width}px)`,
|
||||
maxWidth: `min(${maxWidth}, ${width}px)`,
|
||||
maxHeight: `${height}px`
|
||||
}
|
||||
}
|
||||
|
|
Ładowanie…
Reference in New Issue