mirror of
https://github.com/directus/directus.git
synced 2026-01-27 05:28:06 -05:00
Map layout and interface improvements (#9288)
* Map layout and interface improvements * Fix marker not showing up on geocoder search * Replaced geocoder search placeholder * Fix geocoder hit area * Fix item popup positioning * Removed unselect button * Removed "No results" popup * Removed option to filter map on demand vs automatically * Renamed Geometry field option * Added placeholder to template option * Hide "Delete" button when no feature are selected * Lint fix Co-authored-by: rijkvanzanten <rijkvanzanten@me.com>
This commit is contained in:
@@ -1,6 +1,13 @@
|
||||
<template>
|
||||
<div class="interface-map">
|
||||
<div class="map" :class="{ loading: mapLoading, error: geometryParsingError || geometryOptionsError }">
|
||||
<div
|
||||
class="map"
|
||||
:class="{
|
||||
loading: mapLoading,
|
||||
error: geometryParsingError || geometryOptionsError,
|
||||
'has-selection': selection.length > 0,
|
||||
}"
|
||||
>
|
||||
<div ref="container" />
|
||||
</div>
|
||||
<div
|
||||
@@ -58,7 +65,7 @@
|
||||
import 'maplibre-gl/dist/maplibre-gl.css';
|
||||
import '@mapbox/mapbox-gl-draw/dist/mapbox-gl-draw.css';
|
||||
import { defineComponent, onMounted, onUnmounted, PropType, ref, watch, toRefs, computed } from 'vue';
|
||||
import {
|
||||
import maplibre, {
|
||||
LngLatLike,
|
||||
LngLatBoundsLike,
|
||||
AnimationOptions,
|
||||
@@ -66,7 +73,6 @@ import {
|
||||
Map,
|
||||
NavigationControl,
|
||||
GeolocateControl,
|
||||
PointLike,
|
||||
} from 'maplibre-gl';
|
||||
import MapboxDraw from '@mapbox/mapbox-gl-draw';
|
||||
// @ts-ignore
|
||||
@@ -152,6 +158,7 @@ export default defineComponent({
|
||||
const geometryType = (props.fieldData?.schema?.geometry_type ?? props.geometryType) as GeometryType;
|
||||
const geometryFormat = props.geometryFormat || getGeometryFormatForType(props.type)!;
|
||||
|
||||
const mapboxKey = getSetting('mapbox_key');
|
||||
const basemaps = getBasemapSources();
|
||||
const appStore = useAppStore();
|
||||
const { basemap } = toRefs(appStore);
|
||||
@@ -169,6 +176,8 @@ export default defineComponent({
|
||||
geometryOptionsError.value = error;
|
||||
}
|
||||
|
||||
const selection = ref<GeoJSON.Feature[]>([]);
|
||||
|
||||
const location = ref<LngLatLike | null>();
|
||||
const projection = ref<{ x: number; y: number } | null>();
|
||||
function updateProjection() {
|
||||
@@ -176,8 +185,6 @@ export default defineComponent({
|
||||
}
|
||||
watch(location, updateProjection);
|
||||
|
||||
const mapboxKey = getSetting('mapbox_key');
|
||||
|
||||
const controls = {
|
||||
draw: new MapboxDraw(getDrawOptions(geometryType)),
|
||||
fitData: new ButtonControl('mapboxgl-ctrl-fitdata', fitDataBounds),
|
||||
@@ -194,6 +201,8 @@ export default defineComponent({
|
||||
collapsed: true,
|
||||
flyTo: { speed: 1.4 },
|
||||
marker: false,
|
||||
mapboxgl: maplibre as any,
|
||||
placeholder: t('layouts.map.find_location'),
|
||||
}) as any),
|
||||
};
|
||||
|
||||
@@ -238,6 +247,7 @@ export default defineComponent({
|
||||
basemap,
|
||||
location,
|
||||
projection,
|
||||
selection,
|
||||
};
|
||||
|
||||
function setupMap(): () => void {
|
||||
@@ -275,6 +285,7 @@ export default defineComponent({
|
||||
map.on('draw.delete', handleDrawUpdate);
|
||||
map.on('draw.update', handleDrawUpdate);
|
||||
map.on('draw.modechange', handleDrawModeChange);
|
||||
map.on('draw.selectionchange', handleSelectionChange);
|
||||
map.on('move', updateProjection);
|
||||
for (const layer of activeLayers) {
|
||||
map.on('mousedown', layer, hideTooltip);
|
||||
@@ -437,6 +448,10 @@ export default defineComponent({
|
||||
}
|
||||
}
|
||||
|
||||
function handleSelectionChange(event: any) {
|
||||
selection.value = event.features;
|
||||
}
|
||||
|
||||
function handleDrawUpdate() {
|
||||
currentGeometry = getCurrentGeometry();
|
||||
if (!currentGeometry) {
|
||||
@@ -447,7 +462,7 @@ export default defineComponent({
|
||||
}
|
||||
}
|
||||
|
||||
function handleKeyDown(event) {
|
||||
function handleKeyDown(event: any) {
|
||||
if ([8, 46].includes(event.keyCode)) {
|
||||
controls.draw.trash();
|
||||
}
|
||||
@@ -477,6 +492,10 @@ export default defineComponent({
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
&:not(.has-selection) :deep(.mapbox-gl-draw_trash) {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.v-info {
|
||||
@@ -528,13 +547,13 @@ export default defineComponent({
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
:deep(.fade-enter-active),
|
||||
:deep(.fade-leave-active) {
|
||||
.fade-enter-active,
|
||||
.fade-leave-active {
|
||||
transition: opacity var(--medium) var(--transition);
|
||||
}
|
||||
|
||||
:deep(.fade-enter-from),
|
||||
:deep(.fade-leave-to) {
|
||||
.fade-enter-from,
|
||||
.fade-leave-to {
|
||||
opacity: 0;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -993,7 +993,7 @@ field_options:
|
||||
auth_password_policy:
|
||||
none_text: Няма - не се препоръчва
|
||||
weak_text: Слаба - минимум от 8 символа
|
||||
strong_text: Силна - главни, малки, числа и специални символи
|
||||
strong_text: Силна - главни, малки, числа и специални символи
|
||||
storage_asset_presets:
|
||||
fit:
|
||||
contain_text: Напасване (запазване на съотношението)
|
||||
@@ -1557,7 +1557,6 @@ layouts:
|
||||
map: Географска карта
|
||||
basemap: Основна карта
|
||||
layers: Слоеве
|
||||
edit_custom_layers: Редактиране на слоевете
|
||||
cluster_options: Настройки за групиране
|
||||
cluster: Групиране на близките обекти
|
||||
cluster_radius: Радиус за групиране
|
||||
@@ -1565,11 +1564,6 @@ layouts:
|
||||
cluster_maxzoom: Максимално увеличение при групиране
|
||||
field: Геометрия
|
||||
invalid_geometry: Невалидна геометрия
|
||||
auto_location_filter: Филтриране според изгледа
|
||||
search_this_area: Търсене в тази зона
|
||||
clear_data_filter: Изчистване на филтрирането
|
||||
clear_location_filter: Изчистване на филтрирането
|
||||
no_results_here: Няма резултати в тази зона
|
||||
panels:
|
||||
metric:
|
||||
name: Метричен Индикатор
|
||||
|
||||
@@ -1319,7 +1319,6 @@ layouts:
|
||||
map: Mapa
|
||||
basemap: Mapa base
|
||||
layers: Capes
|
||||
edit_custom_layers: Edita capes
|
||||
cluster_options: Opcions de clustering
|
||||
cluster_radius: Radi del cluster
|
||||
cluster_minpoints: Mida mínima del clúster
|
||||
|
||||
@@ -1475,18 +1475,12 @@ layouts:
|
||||
map:
|
||||
map: Kort
|
||||
layers: Lag
|
||||
edit_custom_layers: Rediger Lag
|
||||
cluster_options: Indstillinger for klyngedannelse
|
||||
cluster: Klynge Nærliggende Data
|
||||
cluster_minpoints: Mindste klyngestørrelse
|
||||
cluster_maxzoom: Maksimum zoom til klyngedannelse
|
||||
field: Geometri
|
||||
invalid_geometry: Ugyldig geometri
|
||||
auto_location_filter: Auto-filter inden for visning
|
||||
search_this_area: Søg i dette område
|
||||
clear_data_filter: Ryd datafilter
|
||||
clear_location_filter: Ryd placeringsfilter
|
||||
no_results_here: Ingen resultater i dette område
|
||||
panels:
|
||||
metric:
|
||||
name: Metrisk
|
||||
|
||||
@@ -1557,7 +1557,6 @@ layouts:
|
||||
map: Karte
|
||||
basemap: Basiskarte
|
||||
layers: Ebenen
|
||||
edit_custom_layers: Ebenen bearbeiten
|
||||
cluster_options: Cluster-Optionen
|
||||
cluster: Cluster Nachbardaten
|
||||
cluster_radius: Cluster-Radius
|
||||
@@ -1565,11 +1564,6 @@ layouts:
|
||||
cluster_maxzoom: Maximaler Zoom für Cluster
|
||||
field: Dimensionen
|
||||
invalid_geometry: Ungültige Geometrie
|
||||
auto_location_filter: Auto-Filter in der Ansicht
|
||||
search_this_area: Diesen Bereich durchsuchen
|
||||
clear_data_filter: Datenfilter löschen
|
||||
clear_location_filter: Standortfilter löschen
|
||||
no_results_here: Keine Ergebnisse in diesem Bereich
|
||||
panels:
|
||||
metric:
|
||||
name: Metrik
|
||||
|
||||
@@ -1571,19 +1571,15 @@ layouts:
|
||||
map: Map
|
||||
basemap: Basemap
|
||||
layers: Layers
|
||||
edit_custom_layers: Edit Layers
|
||||
cluster_options: Clustering options
|
||||
cluster: Cluster Nearby Data
|
||||
cluster_radius: Cluster radius
|
||||
cluster_minpoints: Cluster minimum size
|
||||
cluster_maxzoom: Maximum zoom for clustering
|
||||
field: Geometry
|
||||
field: Geospatial Field
|
||||
invalid_geometry: Invalid geometry
|
||||
auto_location_filter: Auto-Filter within View
|
||||
search_this_area: Search this area
|
||||
clear_data_filter: Clear data filter
|
||||
clear_location_filter: Clear location filter
|
||||
no_results_here: No results in this area
|
||||
find_location: Find location...
|
||||
default_template: Using Collection Default...
|
||||
|
||||
panels:
|
||||
metric:
|
||||
|
||||
@@ -1403,7 +1403,7 @@ interfaces:
|
||||
name: Détail du groupe
|
||||
description: Rendre les champs comme une section pliable
|
||||
show_header: En-tête de groupe
|
||||
header_icon: Icônes de l'en-tête
|
||||
header_icon: Icônes de l'en-tête
|
||||
header_color: Couleur d'en-tête
|
||||
start_open: Démarrer Ouverture
|
||||
start_closed: Commencer réduit
|
||||
@@ -1522,20 +1522,14 @@ layouts:
|
||||
end_date_field: Champ Date de fin
|
||||
map:
|
||||
map: Carte
|
||||
basemap: Carte de base
|
||||
basemap: Fond de carte
|
||||
layers: Afficher...
|
||||
edit_custom_layers: Modifier la couche
|
||||
cluster_options: Options de regroupement
|
||||
cluster_radius: Rayon de grappe
|
||||
cluster_minpoints: Taille minimale du cluster
|
||||
cluster_maxzoom: Zoom maximum pour la grappe
|
||||
field: Géométrie
|
||||
field: Champ de la géométrie
|
||||
invalid_geometry: Géométrie invalide
|
||||
auto_location_filter: Filtre automatique dans la vue
|
||||
search_this_area: Chercher dans cette zone
|
||||
clear_data_filter: Réinitialiser le filtre
|
||||
clear_location_filter: Réinitialiser le filtre de localisation
|
||||
no_results_here: Aucun résultat dans cette zone
|
||||
panels:
|
||||
metric:
|
||||
name: Indicateurs
|
||||
|
||||
@@ -1495,7 +1495,6 @@ layouts:
|
||||
map: Térkép
|
||||
basemap: Alaptérkép
|
||||
layers: Rétegek
|
||||
edit_custom_layers: Rétegek szerkesztése
|
||||
cluster_options: Klaszterezés beállításai
|
||||
cluster_radius: Klaszter sugara
|
||||
cluster_minpoints: Minimális klaszter méret
|
||||
|
||||
@@ -1530,7 +1530,6 @@ layouts:
|
||||
map: Mappa
|
||||
basemap: Mappa base
|
||||
layers: Livelli
|
||||
edit_custom_layers: Modifica i livelli
|
||||
cluster_options: Opzioni di raggruppamento
|
||||
cluster: Raggruppa dati vicini
|
||||
cluster_radius: Raggio del Cluster
|
||||
@@ -1538,11 +1537,6 @@ layouts:
|
||||
cluster_maxzoom: Zoom massimo per il raggruppamento
|
||||
field: Geometria
|
||||
invalid_geometry: Geometria non valida
|
||||
auto_location_filter: Filtro automatico nella vista
|
||||
search_this_area: Cerca in quest'area
|
||||
clear_data_filter: Rimuovi il filtro sui dati
|
||||
clear_location_filter: Rimuovi il filtro sulla posizione
|
||||
no_results_here: Non ci sono risultati in quest'area
|
||||
panels:
|
||||
metric:
|
||||
name: Metrica
|
||||
|
||||
@@ -1554,7 +1554,6 @@ layouts:
|
||||
map: Mapa
|
||||
basemap: Mapa bazowa
|
||||
layers: Warstwy
|
||||
edit_custom_layers: Edytuj warstwy
|
||||
cluster_options: Opcje klastrowania
|
||||
cluster: Klaster danych w pobliżu
|
||||
cluster_radius: Promień klastra
|
||||
@@ -1562,11 +1561,6 @@ layouts:
|
||||
cluster_maxzoom: Maksymalne powiększenie klastrów
|
||||
field: Geometria
|
||||
invalid_geometry: Nieprawidłowa geometria
|
||||
auto_location_filter: Automatyczne filtrowanie w widoku
|
||||
search_this_area: Przeszukaj ten obszar
|
||||
clear_data_filter: Wyczyść filtr danych
|
||||
clear_location_filter: Wyczyść filtr lokalizacji
|
||||
no_results_here: Brak wyników w tym obszarze
|
||||
panels:
|
||||
metric:
|
||||
name: Metryczny
|
||||
|
||||
@@ -1518,7 +1518,6 @@ layouts:
|
||||
map: Карта
|
||||
basemap: Основа карты
|
||||
layers: Слои
|
||||
edit_custom_layers: Редактировать слои
|
||||
cluster_options: Настройки кластеров
|
||||
cluster_radius: Радиус кластера
|
||||
cluster_minpoints: Минимальный размер кластера
|
||||
|
||||
@@ -1370,7 +1370,6 @@ layouts:
|
||||
map: Zemljevid
|
||||
basemap: Osnovni zemljevid
|
||||
layers: Plasti
|
||||
edit_custom_layers: Uredi plasti
|
||||
cluster_options: Možnosti gruče
|
||||
cluster_radius: Polmer gruče
|
||||
cluster_minpoints: Minimalna velikost gruče
|
||||
|
||||
@@ -1485,7 +1485,6 @@ layouts:
|
||||
map: 地图
|
||||
basemap: 底图
|
||||
layers: 图层
|
||||
edit_custom_layers: 编辑图层
|
||||
cluster_options: 集群选项
|
||||
cluster_radius: 集群半径
|
||||
cluster_minpoints: 集群最小尺寸
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
<script lang="ts">
|
||||
import 'maplibre-gl/dist/maplibre-gl.css';
|
||||
import {
|
||||
import maplibre, {
|
||||
MapboxGeoJSONFeature,
|
||||
MapLayerMouseEvent,
|
||||
AttributionControl,
|
||||
@@ -24,6 +24,7 @@ import {
|
||||
import MapboxGeocoder from '@mapbox/mapbox-gl-geocoder';
|
||||
import '@mapbox/mapbox-gl-geocoder/dist/mapbox-gl-geocoder.css';
|
||||
import { ref, watch, PropType, onMounted, onUnmounted, defineComponent, toRefs, computed, WatchStopHandle } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
|
||||
import getSetting from '@/utils/get-setting';
|
||||
import { useAppStore } from '@/stores';
|
||||
@@ -64,6 +65,7 @@ export default defineComponent({
|
||||
},
|
||||
emits: ['moveend', 'featureclick', 'featureselect', 'fitdata', 'updateitempopup'],
|
||||
setup(props, { emit }) {
|
||||
const { t } = useI18n();
|
||||
const appStore = useAppStore();
|
||||
let map: Map;
|
||||
const hoveredFeature = ref<MapboxGeoJSONFeature>();
|
||||
@@ -90,7 +92,6 @@ export default defineComponent({
|
||||
const boxSelectControl = new BoxSelectControl({
|
||||
boxElementClass: 'map-selection-box',
|
||||
selectButtonClass: 'mapboxgl-ctrl-select',
|
||||
unselectButtonClass: 'mapboxgl-ctrl-unselect',
|
||||
layers: ['__directus_polygons', '__directus_points', '__directus_lines'],
|
||||
});
|
||||
let geocoderControl: MapboxGeocoder | undefined;
|
||||
@@ -102,6 +103,8 @@ export default defineComponent({
|
||||
collapsed: true,
|
||||
marker: { element: marker } as any,
|
||||
flyTo: { speed: 1.4 },
|
||||
mapboxgl: maplibre as any,
|
||||
placeholder: t('layouts.map.find_location'),
|
||||
});
|
||||
}
|
||||
onMounted(() => {
|
||||
@@ -176,7 +179,7 @@ export default defineComponent({
|
||||
}
|
||||
|
||||
function fitBounds() {
|
||||
const bbox = props.data.bbox?.map((x) => x % 90);
|
||||
const bbox = props.data.bbox;
|
||||
if (map && bbox) {
|
||||
map.fitBounds(bbox as LngLatBoundsLike, {
|
||||
padding: 100,
|
||||
@@ -247,7 +250,6 @@ export default defineComponent({
|
||||
newSelection?.forEach((id) => {
|
||||
map.setFeatureState({ id, source: '__directus' }, { selected: true });
|
||||
});
|
||||
boxSelectControl.showUnselect(newSelection?.length);
|
||||
}
|
||||
|
||||
function onFeatureClick(event: MapLayerMouseEvent) {
|
||||
|
||||
@@ -7,7 +7,7 @@ import { useI18n } from 'vue-i18n';
|
||||
import { toRefs, computed, ref, watch } from 'vue';
|
||||
|
||||
import { toGeoJSON } from '@/utils/geometry';
|
||||
import { layers } from './style';
|
||||
import { layers as directusLayers } from './style';
|
||||
import { useRouter } from 'vue-router';
|
||||
import { useSync } from '@directus/shared/composables';
|
||||
import { LayoutOptions, LayoutQuery } from './types';
|
||||
@@ -59,12 +59,8 @@ export default defineLayout<LayoutOptions, LayoutQuery>({
|
||||
};
|
||||
});
|
||||
|
||||
const customLayerDrawerOpen = ref(false);
|
||||
|
||||
const displayTemplate = syncRefProperty(layoutOptions, 'displayTemplate', undefined);
|
||||
const cameraOptions = syncRefProperty(layoutOptions, 'cameraOptions', undefined);
|
||||
const customLayers = syncRefProperty(layoutOptions, 'customLayers', layers);
|
||||
const autoLocationFilter = syncRefProperty(layoutOptions, 'autoLocationFilter', false);
|
||||
const clusterData = syncRefProperty(layoutOptions, 'clusterData', false);
|
||||
const geometryField = syncRefProperty(layoutOptions, 'geometryField', undefined);
|
||||
const geometryFormat = computed<GeometryFormat | undefined>({
|
||||
@@ -130,8 +126,6 @@ export default defineLayout<LayoutOptions, LayoutQuery>({
|
||||
.filter((e) => !!e) as string[];
|
||||
});
|
||||
|
||||
const locationFilterOutdated = ref(false);
|
||||
|
||||
function getLocationFilter(): Filter | undefined {
|
||||
if (!isGeometryFieldNative.value || !cameraOptions.value || !geometryField.value) {
|
||||
return;
|
||||
@@ -157,49 +151,24 @@ export default defineLayout<LayoutOptions, LayoutQuery>({
|
||||
} as Filter;
|
||||
}
|
||||
|
||||
function updateLocationFilter() {
|
||||
locationFilterOutdated.value = false;
|
||||
locationFilter.value = getLocationFilter();
|
||||
}
|
||||
const shouldUpdateCamera = ref(false);
|
||||
|
||||
function clearLocationFilter() {
|
||||
function fitDataBounds() {
|
||||
shouldUpdateCamera.value = true;
|
||||
locationFilterOutdated.value = false;
|
||||
locationFilter.value = undefined;
|
||||
}
|
||||
|
||||
function fitGeoJSONBounds() {
|
||||
if (!geojson.value?.features.length) {
|
||||
if (isGeometryFieldNative.value) {
|
||||
locationFilter.value = undefined;
|
||||
return;
|
||||
}
|
||||
shouldUpdateCamera.value = true;
|
||||
locationFilterOutdated.value = false;
|
||||
if (geojson.value) {
|
||||
if (geojson.value?.features.length) {
|
||||
geojsonBounds.value = cloneDeep(geojson.value.bbox);
|
||||
}
|
||||
}
|
||||
|
||||
function clearDataFilters() {
|
||||
props?.clearFilters?.();
|
||||
}
|
||||
|
||||
const shouldUpdateCamera = ref(false);
|
||||
|
||||
watch(
|
||||
() => cameraOptions.value,
|
||||
() => {
|
||||
shouldUpdateCamera.value = false;
|
||||
locationFilterOutdated.value = true;
|
||||
if (autoLocationFilter.value) {
|
||||
updateLocationFilter();
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
watch(
|
||||
() => autoLocationFilter.value,
|
||||
(value) => {
|
||||
if (value) updateLocationFilter();
|
||||
locationFilter.value = getLocationFilter();
|
||||
}
|
||||
);
|
||||
|
||||
@@ -251,7 +220,6 @@ export default defineLayout<LayoutOptions, LayoutQuery>({
|
||||
}
|
||||
}
|
||||
|
||||
const directusLayers = ref(layers);
|
||||
const directusSource = ref({
|
||||
type: 'geojson',
|
||||
data: {
|
||||
@@ -261,17 +229,6 @@ export default defineLayout<LayoutOptions, LayoutQuery>({
|
||||
});
|
||||
|
||||
watch(() => clusterData.value, updateSource, { immediate: true });
|
||||
updateLayers();
|
||||
|
||||
function updateLayers() {
|
||||
customLayerDrawerOpen.value = false;
|
||||
directusLayers.value = customLayers.value ?? [];
|
||||
}
|
||||
|
||||
function resetLayers() {
|
||||
directusLayers.value = cloneDeep(layers);
|
||||
customLayers.value = directusLayers.value;
|
||||
}
|
||||
|
||||
function updateSource() {
|
||||
directusSource.value = merge({}, directusSource.value, {
|
||||
@@ -340,9 +297,6 @@ export default defineLayout<LayoutOptions, LayoutQuery>({
|
||||
geojson,
|
||||
directusSource,
|
||||
directusLayers,
|
||||
customLayers,
|
||||
updateLayers,
|
||||
resetLayers,
|
||||
featureId,
|
||||
geojsonBounds,
|
||||
geojsonLoading,
|
||||
@@ -355,7 +309,6 @@ export default defineLayout<LayoutOptions, LayoutQuery>({
|
||||
displayTemplate,
|
||||
isGeometryFieldNative,
|
||||
cameraOptions,
|
||||
autoLocationFilter,
|
||||
clusterData,
|
||||
items,
|
||||
loading,
|
||||
@@ -374,13 +327,7 @@ export default defineLayout<LayoutOptions, LayoutQuery>({
|
||||
refresh,
|
||||
resetPresetAndRefresh,
|
||||
geometryFields,
|
||||
customLayerDrawerOpen,
|
||||
locationFilterOutdated,
|
||||
updateLocationFilter,
|
||||
clearLocationFilter,
|
||||
clearDataFilters,
|
||||
locationFilter,
|
||||
fitGeoJSONBounds,
|
||||
fitDataBounds,
|
||||
template,
|
||||
itemPopup,
|
||||
updateItemPopup,
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
@featureclick="handleClick"
|
||||
@featureselect="handleSelect"
|
||||
@moveend="cameraOptionsWritable = $event"
|
||||
@fitdata="fitGeoJSONBounds"
|
||||
@fitdata="fitDataBounds"
|
||||
@updateitempopup="updateItemPopup"
|
||||
/>
|
||||
|
||||
@@ -26,15 +26,6 @@
|
||||
<render-template :template="template" :item="itemPopup.item" :collection="collection" />
|
||||
</div>
|
||||
|
||||
<v-button
|
||||
v-if="isGeometryFieldNative && !autoLocationFilter && locationFilterOutdatedWritable"
|
||||
small
|
||||
class="location-filter"
|
||||
@click="updateLocationFilter"
|
||||
>
|
||||
{{ t('layouts.map.search_this_area') }}
|
||||
</v-button>
|
||||
|
||||
<transition name="fade">
|
||||
<v-info v-if="error" type="danger" :title="t('unexpected_error')" icon="error" center>
|
||||
{{ t('unexpected_error_copy') }}
|
||||
@@ -55,23 +46,6 @@
|
||||
{{ geojsonError }}
|
||||
</v-info>
|
||||
<v-progress-circular v-else-if="loading || geojsonLoading" indeterminate x-large class="center" />
|
||||
<v-info
|
||||
v-else-if="!loading && !itemCount && !locationFilterOutdated && (search || filter || locationFilter)"
|
||||
icon="search"
|
||||
center
|
||||
:title="t('layouts.map.no_results_here')"
|
||||
>
|
||||
<template #append>
|
||||
<v-card-actions>
|
||||
<v-button :disabled="!search && !filter" @click="clearDataFilters">
|
||||
{{ t('layouts.map.clear_data_filter') }}
|
||||
</v-button>
|
||||
<v-button :disabled="!locationFilter" @click="clearLocationFilter">
|
||||
{{ t('layouts.map.clear_location_filter') }}
|
||||
</v-button>
|
||||
</v-card-actions>
|
||||
</template>
|
||||
</v-info>
|
||||
</transition>
|
||||
|
||||
<template v-if="loading || itemCount > 0">
|
||||
@@ -124,7 +98,6 @@ import { defineComponent, PropType } from 'vue';
|
||||
import MapComponent from './components/map.vue';
|
||||
import { useSync } from '@directus/shared/composables';
|
||||
import { GeometryOptions, Item } from '@directus/shared/types';
|
||||
import { Filter } from '@directus/shared/types';
|
||||
|
||||
export default defineComponent({
|
||||
components: { MapComponent },
|
||||
@@ -138,10 +111,6 @@ export default defineComponent({
|
||||
type: Array as PropType<Item[]>,
|
||||
default: () => [],
|
||||
},
|
||||
search: {
|
||||
type: String as PropType<string | null>,
|
||||
default: null,
|
||||
},
|
||||
loading: {
|
||||
type: Boolean,
|
||||
required: true,
|
||||
@@ -222,38 +191,10 @@ export default defineComponent({
|
||||
type: Boolean,
|
||||
default: undefined,
|
||||
},
|
||||
locationFilterOutdated: {
|
||||
type: Boolean,
|
||||
required: true,
|
||||
},
|
||||
fitGeoJSONBounds: {
|
||||
fitDataBounds: {
|
||||
type: Function as PropType<() => void>,
|
||||
required: true,
|
||||
},
|
||||
updateLocationFilter: {
|
||||
type: Function as PropType<() => void>,
|
||||
required: true,
|
||||
},
|
||||
clearDataFilters: {
|
||||
type: Function as PropType<() => void>,
|
||||
required: true,
|
||||
},
|
||||
clearLocationFilter: {
|
||||
type: Function as PropType<() => void>,
|
||||
required: true,
|
||||
},
|
||||
isGeometryFieldNative: {
|
||||
type: Boolean,
|
||||
required: true,
|
||||
},
|
||||
filter: {
|
||||
type: Object as PropType<Filter>,
|
||||
default: null,
|
||||
},
|
||||
locationFilter: {
|
||||
type: Object as PropType<Filter>,
|
||||
default: null,
|
||||
},
|
||||
template: {
|
||||
type: String,
|
||||
default: () => undefined,
|
||||
@@ -267,15 +208,14 @@ export default defineComponent({
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
emits: ['update:cameraOptions', 'update:limit', 'update:locationFilterOutdated'],
|
||||
emits: ['update:cameraOptions', 'update:limit'],
|
||||
setup(props, { emit }) {
|
||||
const { t, n } = useI18n();
|
||||
|
||||
const cameraOptionsWritable = useSync(props, 'cameraOptions', emit);
|
||||
const limitWritable = useSync(props, 'limit', emit);
|
||||
const locationFilterOutdatedWritable = useSync(props, 'locationFilterOutdated', emit);
|
||||
|
||||
return { t, n, cameraOptionsWritable, limitWritable, locationFilterOutdatedWritable };
|
||||
return { t, n, cameraOptionsWritable, limitWritable };
|
||||
},
|
||||
});
|
||||
</script>
|
||||
@@ -338,8 +278,8 @@ export default defineComponent({
|
||||
background-color: var(--background-page);
|
||||
border-radius: var(--border-radius);
|
||||
box-shadow: var(--card-shadow);
|
||||
transform: translate(-50%, -140%);
|
||||
pointer-events: none;
|
||||
translate: -50% calc(-100% - 12px);
|
||||
}
|
||||
|
||||
.mapboxgl-ctrl-dropdown {
|
||||
|
||||
@@ -21,14 +21,10 @@
|
||||
|
||||
<div class="field">
|
||||
<div class="type-label">{{ t('display_template') }}</div>
|
||||
<v-field-template v-model="displayTemplateWritable" :collection="collection" />
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
<v-checkbox
|
||||
v-model="autoLocationFilterWritable"
|
||||
:label="t('layouts.map.auto_location_filter')"
|
||||
:disabled="geometryOptions && geometryOptions.geometryFormat !== 'native'"
|
||||
<v-field-template
|
||||
v-model="displayTemplateWritable"
|
||||
:collection="collection"
|
||||
:placeholder="t('layouts.map.default_template')"
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -65,10 +61,6 @@ export default defineComponent({
|
||||
type: String,
|
||||
default: undefined,
|
||||
},
|
||||
autoLocationFilter: {
|
||||
type: Boolean,
|
||||
default: undefined,
|
||||
},
|
||||
geometryOptions: {
|
||||
type: Object as PropType<GeometryOptions>,
|
||||
default: undefined,
|
||||
@@ -77,44 +69,19 @@ export default defineComponent({
|
||||
type: Boolean,
|
||||
default: undefined,
|
||||
},
|
||||
customLayerDrawerOpen: {
|
||||
type: Boolean,
|
||||
required: true,
|
||||
},
|
||||
resetLayers: {
|
||||
type: Function as PropType<() => void>,
|
||||
required: true,
|
||||
},
|
||||
updateLayers: {
|
||||
type: Function as PropType<() => void>,
|
||||
required: true,
|
||||
},
|
||||
customLayers: {
|
||||
type: Array as PropType<any[]>,
|
||||
default: undefined,
|
||||
},
|
||||
displayTemplate: {
|
||||
type: String as string | undefined,
|
||||
default: undefined,
|
||||
},
|
||||
},
|
||||
emits: [
|
||||
'update:geometryField',
|
||||
'update:autoLocationFilter',
|
||||
'update:clusterData',
|
||||
'update:customLayerDrawerOpen',
|
||||
'update:customLayers',
|
||||
],
|
||||
emits: ['update:geometryField', 'update:autoLocationFilter', 'update:clusterData'],
|
||||
setup(props, { emit }) {
|
||||
const { t } = useI18n();
|
||||
|
||||
const appStore = useAppStore();
|
||||
|
||||
const geometryFieldWritable = useSync(props, 'geometryField', emit);
|
||||
const autoLocationFilterWritable = useSync(props, 'autoLocationFilter', emit);
|
||||
const clusterDataWritable = useSync(props, 'clusterData', emit);
|
||||
const customLayerDrawerOpenWritable = useSync(props, 'customLayerDrawerOpen', emit);
|
||||
const customLayersWritable = useSync(props, 'customLayers', emit);
|
||||
const displayTemplateWritable = useSync(props, 'displayTemplate', emit);
|
||||
|
||||
const basemaps = getBasemapSources();
|
||||
@@ -123,10 +90,7 @@ export default defineComponent({
|
||||
return {
|
||||
t,
|
||||
geometryFieldWritable,
|
||||
autoLocationFilterWritable,
|
||||
clusterDataWritable,
|
||||
customLayerDrawerOpenWritable,
|
||||
customLayersWritable,
|
||||
displayTemplateWritable,
|
||||
basemaps,
|
||||
basemap,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { CameraOptions, AnyLayer } from 'maplibre-gl';
|
||||
import { CameraOptions } from 'maplibre-gl';
|
||||
import { GeometryFormat } from '@directus/shared/types';
|
||||
|
||||
export type LayoutQuery = {
|
||||
@@ -10,7 +10,6 @@ export type LayoutQuery = {
|
||||
|
||||
export type LayoutOptions = {
|
||||
cameraOptions?: CameraOptions & { bbox: any };
|
||||
customLayers?: Array<AnyLayer>;
|
||||
geometryFormat?: GeometryFormat;
|
||||
geometryField?: string;
|
||||
autoLocationFilter?: boolean;
|
||||
|
||||
@@ -113,6 +113,7 @@
|
||||
font-family: inherit !important;
|
||||
line-height: inherit !important;
|
||||
background-color: var(--background-page);
|
||||
overflow: hidden;
|
||||
|
||||
&,
|
||||
&.suggestions {
|
||||
|
||||
@@ -7,7 +7,7 @@ export class ButtonControl {
|
||||
constructor(private className: string, private callback: (...args: any) => any) {
|
||||
this.element = document.createElement('button');
|
||||
this.element.className = this.className;
|
||||
this.element.onclick = callback;
|
||||
this.element.onpointerdown = callback;
|
||||
this.active = false;
|
||||
}
|
||||
click(...args: any[]): void {
|
||||
@@ -36,7 +36,6 @@ type BoxSelectControlOptions = {
|
||||
groupElementClass?: string;
|
||||
boxElementClass?: string;
|
||||
selectButtonClass?: string;
|
||||
unselectButtonClass?: string;
|
||||
layers: string[];
|
||||
};
|
||||
|
||||
@@ -45,7 +44,6 @@ export class BoxSelectControl {
|
||||
boxElement: HTMLElement;
|
||||
|
||||
selectButton: ButtonControl;
|
||||
unselectButton: ButtonControl;
|
||||
|
||||
map?: Map & { fire: (event: string, data?: any) => void };
|
||||
layers: string[];
|
||||
@@ -70,13 +68,7 @@ export class BoxSelectControl {
|
||||
this.selectButton = new ButtonControl(options?.selectButtonClass ?? 'ctrl-select', () => {
|
||||
this.activate(!this.shiftPressed);
|
||||
});
|
||||
this.unselectButton = new ButtonControl(options?.unselectButtonClass ?? 'ctrl-unselect', () => {
|
||||
this.reset();
|
||||
this.activate(false);
|
||||
this.map!.fire('select.end', { features: [] });
|
||||
});
|
||||
this.groupElement.appendChild(this.selectButton.element);
|
||||
this.groupElement.appendChild(this.unselectButton.element);
|
||||
|
||||
this.onKeyDownHandler = this.onKeyDown.bind(this);
|
||||
this.onKeyUpHandler = this.onKeyUp.bind(this);
|
||||
@@ -132,10 +124,6 @@ export class BoxSelectControl {
|
||||
this.map!.fire(`select.${yes ? 'enable' : 'disable'}`);
|
||||
}
|
||||
|
||||
showUnselect(yes: boolean): void {
|
||||
this.unselectButton.show(yes);
|
||||
}
|
||||
|
||||
onKeyUp(event: KeyboardEvent): void {
|
||||
if (event.key == 'Shift') {
|
||||
this.activate(false);
|
||||
@@ -171,6 +159,9 @@ export class BoxSelectControl {
|
||||
|
||||
onMouseUp(event: MouseEvent): void {
|
||||
this.reset();
|
||||
if (!this.active()) {
|
||||
return;
|
||||
}
|
||||
const features = this.map!.queryRenderedFeatures([this.startPos!, this.lastPos!], {
|
||||
layers: this.layers,
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user