Map layout and interface improvements (#8628)

* Map layout and interface improvements:
* Disable drag to rotate
* Add keyboard shortcut to delete items
* Hide unselect button when selection is empty
* Add display template setting
* Fixed fitData button behaviour

* Removed unused hoveredFeatureId

* Added translations

* Expose clearFilters to the layout.
This commit is contained in:
Oreille
2021-10-12 15:50:18 +02:00
committed by GitHub
parent 23a126b026
commit 475f6349f0
12 changed files with 90 additions and 38 deletions

View File

@@ -59,6 +59,10 @@ function createLayoutWrapper<Options, Query>(layout: LayoutConfig): Component {
type: Function as PropType<() => Promise<void>>,
default: null,
},
clearFilters: {
type: Function as PropType<() => void>,
default: null,
},
},
emits: WRITABLE_PROPS.map((prop) => `update:${prop}` as const),
setup(props, { emit }) {

View File

@@ -145,16 +145,15 @@ export default defineComponent({
const controls = {
draw: new MapboxDraw(getDrawOptions(geometryType)),
fitData: new ButtonControl('mapboxgl-ctrl-fitdata', fitDataBounds),
navigation: new NavigationControl(),
navigation: new NavigationControl({
showCompass: false,
}),
geolocate: new GeolocateControl(),
};
onMounted(() => {
setupMap();
});
onUnmounted(() => {
map.remove();
const cleanup = setupMap();
onUnmounted(cleanup);
});
return {
@@ -168,11 +167,12 @@ export default defineComponent({
basemap,
};
function setupMap() {
function setupMap(): () => void {
map = new Map({
container: container.value!,
style: style.value,
attributionControl: false,
dragRotate: false,
logoPosition: 'bottom-right',
...props.defaultView,
...(mapboxKey ? { accessToken: mapboxKey } : {}),
@@ -200,6 +200,7 @@ export default defineComponent({
map.on('draw.delete', handleDrawUpdate);
map.on('draw.update', handleDrawUpdate);
map.on('draw.modechange', handleDrawModeChange);
window.addEventListener('keydown', handleKeyDown);
});
watch(
@@ -247,6 +248,11 @@ export default defineComponent({
loadValueFromProps();
}
);
return () => {
window.removeEventListener('keydown', handleKeyDown);
map.remove();
};
}
function resetValue(hard: boolean) {
@@ -383,6 +389,12 @@ export default defineComponent({
emit('input', serialize(currentGeometry));
}
}
function handleKeyDown(event) {
if ([8, 46].includes(event.keyCode)) {
controls.draw.trash();
}
}
},
});
</script>

View File

@@ -1529,8 +1529,9 @@ layouts:
invalid_geometry: Invalid geometry
auto_location_filter: Always filter data to view bounds
search_this_area: Search this area
clear_data_filter: Clear Data Filter
clear_location_filter: Clear Location Filter
clear_data_filter: Clear data filter
clear_location_filter: Clear location filter
no_results_here: No results in this area
panels:
metric:

View File

@@ -904,10 +904,11 @@ fields:
png: PNG
webP: WebP
tiff: Tiff
basemaps: Fond de cartes
basemaps_raster: Raster
basemaps_tile: Raster TileJSON
basemaps_style: Style de Mapbox
mapbox_key: Jeton d'accès Mapbox
mapbox_key: Clé d'accès Mapbox
mapbox_placeholder: pk.eyJ1Ijo.....
transforms_note: Le nom de la méthode Sharp et ses arguments. Voir https://sharp.pixelplumbing.com/api-constructor pour plus d'informations.
additional_transforms: Transformations supplémentaires
@@ -1500,20 +1501,21 @@ layouts:
end_date_field: Champ Date de fin
map:
map: Carte
basemap: Carte de base
layers: Afficher...
basemap: Fond de carte
layers: Couches
edit_custom_layers: Modifier la couche
cluster_options: Options de regroupement
cluster: Activer le clustering
cluster_radius: Rayon de grappe
cluster_minpoints: Taille minimale du cluster
cluster_maxzoom: Zoom maximum pour la grappe
cluster: Regrouper les données
cluster_radius: Rayon de regroupement
cluster_minpoints: Taille minimale des clusters
cluster_maxzoom: Zoom maximum pour le regroupement
field: Géométrie
invalid_geometry: Géométrie invalide
auto_location_filter: Toujours filtrer les données pour afficher les limites
search_this_area: Chercher dans cette zone
clear_data_filter: Réinitialiser le filtre
clear_location_filter: Éliminer le filtre de coupon
auto_location_filter: Toujours filter les données selon l'emprise
clear_data_filter: Enlever les filtres avancés
clear_location_filter: Enlever le filtre géographique
no_results_here: Aucun résultat dans cette zone
panels:
metric:
name: Indicateurs

View File

@@ -88,7 +88,9 @@ export default defineComponent({
});
const attributionControl = new AttributionControl({ compact: true });
const navigationControl = new NavigationControl();
const navigationControl = new NavigationControl({
showCompass: false,
});
const geolocateControl = new GeolocateControl();
const fitDataControl = new ButtonControl('mapboxgl-ctrl-fitdata', () => {
emit('fitdata');
@@ -115,6 +117,7 @@ export default defineComponent({
container: 'map-container',
style: style.value,
attributionControl: false,
dragRotate: false,
...props.camera,
...(mapboxKey ? { accessToken: mapboxKey } : {}),
});
@@ -245,6 +248,7 @@ export default defineComponent({
newSelection?.forEach((id) => {
map.setFeatureState({ id, source: '__directus' }, { selected: true });
});
boxSelectControl.showUnselect(newSelection?.length);
}
function onFeatureClick(event: MapLayerMouseEvent) {
@@ -470,6 +474,8 @@ export default defineComponent({
}
.mapboxgl-point-popup {
box-shadow: 10px 10px 10px solid red;
&.mapboxgl-popup-anchor-left .mapboxgl-popup-tip {
border-right-color: var(--background-normal);
}
@@ -493,12 +499,11 @@ export default defineComponent({
font-family: var(--family-sans-serif);
background-color: var(--background-normal);
border-radius: var(--border-radius);
box-shadow: var(--card-shadow);
pointer-events: none;
}
}
</style>
<style lang="scss">
#map-container.hover .mapboxgl-canvas-container {
cursor: pointer !important;
}

View File

@@ -61,6 +61,7 @@ export default defineLayout<LayoutOptions, LayoutQuery>({
const customLayerDrawerOpen = ref(false);
const displayTemplate = syncOption(layoutOptions, 'displayTemplate', undefined);
const cameraOptions = syncOption(layoutOptions, 'cameraOptions', undefined);
const customLayers = syncOption(layoutOptions, 'customLayers', layers);
const autoLocationFilter = syncOption(layoutOptions, 'autoLocationFilter', false);
@@ -120,8 +121,7 @@ export default defineLayout<LayoutOptions, LayoutQuery>({
});
const template = computed(() => {
if (info.value?.meta?.display_template) return info.value?.meta?.display_template;
return `{{ ${primaryKeyField.value?.field} }}`;
return displayTemplate.value || info.value?.meta?.display_template || `{{ ${primaryKeyField.value?.field} }}`;
});
const queryFields = computed(() => {
@@ -165,17 +165,22 @@ export default defineLayout<LayoutOptions, LayoutQuery>({
function clearLocationFilter() {
shouldUpdateCamera.value = true;
locationFilterOutdated.value = false;
locationFilter.value = undefined;
}
function fitGeoJSONBounds() {
if (!geojson.value?.features.length) {
return;
}
shouldUpdateCamera.value = true;
locationFilterOutdated.value = false;
if (geojson.value) {
geojsonBounds.value = geojson.value.bbox;
geojsonBounds.value = cloneDeep(geojson.value.bbox);
}
}
function clearDataFilters() {
locationFilter.value = undefined;
search.value = null;
props?.clearFilters();
}
const shouldUpdateCamera = ref(false);
@@ -322,7 +327,6 @@ export default defineLayout<LayoutOptions, LayoutQuery>({
});
return {
template,
geojson,
directusSource,
directusLayers,
@@ -338,6 +342,7 @@ export default defineLayout<LayoutOptions, LayoutQuery>({
handleSelect,
geometryFormat,
geometryField,
displayTemplate,
isGeometryFieldNative,
cameraOptions,
autoLocationFilter,
@@ -365,6 +370,7 @@ export default defineLayout<LayoutOptions, LayoutQuery>({
clearLocationFilter,
clearDataFilters,
locationFilter,
fitGeoJSONBounds,
};
async function resetPresetAndRefresh() {

View File

@@ -14,7 +14,7 @@
@featureclick="handleClick"
@featureselect="handleSelect"
@moveend="cameraOptionsWritable = $event"
@fitdata="clearLocationFilter"
@fitdata="fitGeoJSONBounds"
/>
<v-button
@@ -47,17 +47,17 @@
</v-info>
<v-progress-circular v-else-if="loading || geojsonLoading" indeterminate x-large class="center" />
<v-info
v-else-if="itemCount === 0 && (search || filter || !locationFilterOutdated)"
v-else-if="!loading && !itemCount && !locationFilterOutdated && (search || filter || locationFilter)"
icon="search"
center
:title="t('no_results')"
:title="t('layouts.map.no_results_here')"
>
<template #append>
<v-card-actions>
<v-button :disabled="!search && !locationFilter" @click="clearDataFilters">
<v-button :disabled="!search && !filter" @click="clearDataFilters">
{{ t('layouts.map.clear_data_filter') }}
</v-button>
<v-button :disabled="locationFilterOutdated" @click="clearLocationFilter">
<v-button :disabled="!locationFilter" @click="clearLocationFilter">
{{ t('layouts.map.clear_location_filter') }}
</v-button>
</v-card-actions>
@@ -213,6 +213,10 @@ export default defineComponent({
type: Boolean,
required: true,
},
fitGeoJSONBounds: {
type: Function as PropType<() => void>,
required: true,
},
updateLocationFilter: {
type: Function as PropType<() => void>,
required: true,

View File

@@ -19,6 +19,11 @@
</div>
</template>
<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"
@@ -48,6 +53,10 @@ import { useSync } from '@directus/shared/composables';
export default defineComponent({
inheritAttrs: false,
props: {
collection: {
type: String,
required: true,
},
geometryFields: {
type: Array as PropType<Item[]>,
required: true,
@@ -84,6 +93,10 @@ export default defineComponent({
type: Array as PropType<any[]>,
default: undefined,
},
displayTemplate: {
type: String as string | undefined,
default: undefined,
},
},
emits: [
'update:geometryField',
@@ -102,6 +115,7 @@ export default defineComponent({
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();
const { basemap } = toRefs(appStore);
@@ -113,6 +127,7 @@ export default defineComponent({
clusterDataWritable,
customLayerDrawerOpenWritable,
customLayersWritable,
displayTemplateWritable,
basemaps,
basemap,
};

View File

@@ -16,4 +16,5 @@ export type LayoutOptions = {
autoLocationFilter?: boolean;
clusterData?: boolean;
animateOptions?: any;
displayTemplate?: string;
};

View File

@@ -12,6 +12,7 @@
:search="search"
:collection="collection"
:reset-preset="resetPreset"
:clear-filters="clearFilters"
>
<collections-not-found v-if="!currentCollection || collection.startsWith('directus_')" />
<private-view

View File

@@ -113,10 +113,6 @@ export class BoxSelectControl {
const rect = container.getBoundingClientRect();
// @ts-ignore
return new Point(event.clientX - rect.left - container.clientLeft, event.clientY - rect.top - container.clientTop);
// return {
// x: event.clientX - rect.left - container.clientLeft,
// y: event.clientY - rect.top - container.clientTop
// };
}
onKeyDown(event: KeyboardEvent): void {
@@ -136,6 +132,10 @@ 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);

View File

@@ -29,6 +29,7 @@ export interface LayoutProps<Options = any, Query = any> {
selectMode: boolean;
readonly: boolean;
resetPreset?: () => Promise<void>;
clearFilters?: () => void;
}
interface LayoutContext {