mirror of
https://gitlab.com/openstapps/openstapps.git
synced 2026-01-20 08:33:11 +00:00
refactor: polish map module ui/ux
This commit is contained in:
@@ -51,7 +51,7 @@ export class AboutLicensesComponent implements OnInit {
|
||||
modal.dismiss();
|
||||
},
|
||||
},
|
||||
swipeToClose: true,
|
||||
canDismiss: true,
|
||||
});
|
||||
return await modal.present();
|
||||
}
|
||||
|
||||
@@ -249,7 +249,7 @@ export class AddEventPopoverComponent implements OnInit, OnDestroy {
|
||||
async export() {
|
||||
const modal = await this.modalController.create({
|
||||
component: AddEventReviewModalComponent,
|
||||
swipeToClose: true,
|
||||
canDismiss: true,
|
||||
cssClass: 'add-modal',
|
||||
componentProps: {
|
||||
dismissAction: () => {
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
<ion-modal
|
||||
trigger="show-more"
|
||||
[presentingElement]="routerOutlet.nativeEl"
|
||||
swipeToClose="true"
|
||||
canDismiss="true"
|
||||
class="modal-large"
|
||||
>
|
||||
<ng-template>
|
||||
@@ -31,14 +31,6 @@
|
||||
</ng-template>
|
||||
</ion-modal>
|
||||
<stapps-skeleton-list-item *ngIf="!item"></stapps-skeleton-list-item>
|
||||
<ion-button
|
||||
size="small"
|
||||
fill="clear"
|
||||
class="close"
|
||||
(click)="onCloseClick()"
|
||||
>
|
||||
<ion-icon name="cancel" fill></ion-icon>
|
||||
</ion-button>
|
||||
</ion-card-header>
|
||||
<ion-card-content>
|
||||
<ion-note>
|
||||
@@ -52,7 +44,7 @@
|
||||
}}</ion-button>
|
||||
<ion-modal
|
||||
trigger="show-more-button"
|
||||
swipeToClose="true"
|
||||
canDismiss="true"
|
||||
[presentingElement]="routerOutlet.nativeEl"
|
||||
class="modal-large"
|
||||
>
|
||||
|
||||
@@ -22,7 +22,7 @@ import {
|
||||
SCUuid,
|
||||
} from '@openstapps/core';
|
||||
import {Point, Polygon} from 'geojson';
|
||||
import {divIcon, geoJSON, icon, LatLng, Map, marker, Marker} from 'leaflet';
|
||||
import {divIcon, geoJSON, LatLng, Map, marker, Marker} from 'leaflet';
|
||||
import {DataProvider} from '../data/data.provider';
|
||||
import {MapPosition, PositionService} from './position.service';
|
||||
import {hasValidLocation} from '../data/types/place/place-types';
|
||||
@@ -45,14 +45,20 @@ export class MapProvider {
|
||||
* Provide a point marker for a leaflet map
|
||||
*
|
||||
* @param point Point to get marker for
|
||||
* @param className CSS class name
|
||||
* @param iconSize Size of the position icon
|
||||
*/
|
||||
static getPointMarker(point: Point) {
|
||||
static getPointMarker(point: Point, className: string, iconSize: number) {
|
||||
return marker(geoJSON(point).getBounds().getCenter(), {
|
||||
icon: icon({
|
||||
iconAnchor: [13, 41],
|
||||
iconSize: [25, 41],
|
||||
iconUrl: '../assets/marker-icon.png',
|
||||
shadowUrl: '../assets/marker-shadow.png',
|
||||
icon: divIcon({
|
||||
className: className,
|
||||
html: `<span
|
||||
name="${SCIcon`location_on`}"
|
||||
class="material-symbols-rounded map-location-pin"
|
||||
style="font-size: ${iconSize}px;"
|
||||
>${SCIcon`location_on`}</span>`,
|
||||
iconSize: [iconSize, iconSize],
|
||||
iconAnchor: [iconSize / 2, iconSize],
|
||||
}),
|
||||
});
|
||||
}
|
||||
@@ -80,12 +86,13 @@ export class MapProvider {
|
||||
transform-origin: center;
|
||||
transform: rotate(${position.heading}deg);
|
||||
font-size: ${iconSize}px;
|
||||
color: var(--ion-color-primary);
|
||||
"
|
||||
>${SCIcon`navigation`}</span>`
|
||||
: `<span
|
||||
name="${SCIcon`person_pin_circle`}"
|
||||
class="material-symbols-rounded map-location-pin"
|
||||
style="font-size: ${iconSize}px;"
|
||||
style="font-size: ${iconSize}px; color: var(--ion-color-primary);"
|
||||
>${SCIcon`person_pin_circle`}</span>`,
|
||||
iconSize: [iconSize, iconSize],
|
||||
}),
|
||||
|
||||
@@ -13,7 +13,6 @@
|
||||
* this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
import {Location} from '@angular/common';
|
||||
import {trigger, style, animate, transition} from '@angular/animations';
|
||||
import {
|
||||
ChangeDetectorRef,
|
||||
Component,
|
||||
@@ -60,24 +59,6 @@ import {Capacitor} from '@capacitor/core';
|
||||
styleUrls: ['./map-page.scss'],
|
||||
templateUrl: './map-page.html',
|
||||
providers: [ContextMenuService],
|
||||
animations: [
|
||||
trigger('fadeInOut', [
|
||||
transition(':enter', [
|
||||
style({transform: 'translateY(200%)', opacity: 0}),
|
||||
animate(
|
||||
'500ms ease-in-out',
|
||||
style({transform: 'translateY(0%)', opacity: 1}),
|
||||
),
|
||||
]),
|
||||
transition(':leave', [
|
||||
style({transform: 'translateY(0%)', opacity: 1}),
|
||||
animate(
|
||||
'500ms ease-in-out',
|
||||
style({transform: 'translateY(200%)', opacity: 0}),
|
||||
),
|
||||
]),
|
||||
]),
|
||||
],
|
||||
})
|
||||
export class MapPageComponent {
|
||||
/**
|
||||
@@ -230,7 +211,11 @@ export class MapPageComponent {
|
||||
return polygonLayer.on('click', this.showItem.bind(this, place.uid));
|
||||
}
|
||||
|
||||
const markerLayer = MapProvider.getPointMarker(place.geo.point);
|
||||
const markerLayer = MapProvider.getPointMarker(
|
||||
place.geo.point,
|
||||
'stapps-location',
|
||||
32,
|
||||
);
|
||||
|
||||
return markerLayer.on('click', this.showItem.bind(this, place.uid));
|
||||
};
|
||||
@@ -327,7 +312,7 @@ export class MapPageComponent {
|
||||
this.positionMarker = MapProvider.getPositionMarker(
|
||||
position,
|
||||
'stapps-device-location',
|
||||
30,
|
||||
32,
|
||||
);
|
||||
},
|
||||
error: async _error => {
|
||||
@@ -407,14 +392,13 @@ export class MapPageComponent {
|
||||
await (
|
||||
await this.alertController.create({
|
||||
header: location.TITLE,
|
||||
subHeader: location.SUBTITLE,
|
||||
message: `${
|
||||
this.locationStatus?.location === 'denied'
|
||||
? location.NOT_ALLOWED
|
||||
: this.locationStatus?.location !== 'granted'
|
||||
? location.NOT_ENABLED
|
||||
: unknownError
|
||||
}.`,
|
||||
}`,
|
||||
buttons: ['OK'],
|
||||
})
|
||||
).present();
|
||||
|
||||
@@ -59,7 +59,6 @@
|
||||
<div class="map-buttons above">
|
||||
<ion-button
|
||||
*ngIf="items.length > 1"
|
||||
[@fadeInOut]
|
||||
color="light"
|
||||
shape="round"
|
||||
size="small"
|
||||
@@ -70,24 +69,31 @@
|
||||
}}
|
||||
</ion-button>
|
||||
<ion-button
|
||||
[disabled]="position === undefined"
|
||||
color="light"
|
||||
shape="round"
|
||||
size="small"
|
||||
(click)="onPositionClick()"
|
||||
>
|
||||
<ion-icon
|
||||
*ngIf="position !== null; else questionIcon"
|
||||
*ngIf="position !== null; else noLocationIcon"
|
||||
name="my_location"
|
||||
></ion-icon>
|
||||
<ng-template #questionIcon>
|
||||
<ion-icon name="location_searching"></ion-icon>
|
||||
<ng-template #noLocationIcon>
|
||||
<ion-icon
|
||||
*ngIf="
|
||||
locationStatus.location !== 'denied';
|
||||
else deniedLocationIcon
|
||||
"
|
||||
name="location_searching"
|
||||
></ion-icon>
|
||||
</ng-template>
|
||||
<ng-template #deniedLocationIcon>
|
||||
<ion-icon name="location_disabled"></ion-icon>
|
||||
</ng-template>
|
||||
</ion-button>
|
||||
</div>
|
||||
<stapps-map-item
|
||||
*ngIf="items.length === 1"
|
||||
[@fadeInOut]
|
||||
[item]="items[0]"
|
||||
(onClose)="resetView()"
|
||||
></stapps-map-item>
|
||||
@@ -95,7 +101,6 @@
|
||||
<div class="map-buttons floating-buttons">
|
||||
<ion-button
|
||||
*ngIf="items.length > 1"
|
||||
[@fadeInOut]
|
||||
color="light"
|
||||
shape="round"
|
||||
size="small"
|
||||
@@ -106,24 +111,29 @@
|
||||
}}
|
||||
</ion-button>
|
||||
<ion-button
|
||||
[disabled]="position === undefined"
|
||||
color="light"
|
||||
shape="round"
|
||||
size="small"
|
||||
(click)="onPositionClick()"
|
||||
>
|
||||
<ion-icon
|
||||
*ngIf="position !== null; else questionIcon"
|
||||
*ngIf="position !== null; else noLocationIcon"
|
||||
name="my_location"
|
||||
></ion-icon>
|
||||
<ng-template #questionIcon>
|
||||
<ion-icon name="location_searching"></ion-icon>
|
||||
<ng-template #noLocationIcon>
|
||||
<ion-icon
|
||||
*ngIf="locationStatus.location !== 'denied'; else deniedLocationIcon"
|
||||
name="location_searching"
|
||||
></ion-icon>
|
||||
</ng-template>
|
||||
<ng-template #deniedLocationIcon>
|
||||
<ion-icon name="location_disabled"></ion-icon>
|
||||
</ng-template>
|
||||
</ion-button>
|
||||
</div>
|
||||
|
||||
<ion-modal
|
||||
[swipeToClose]="true"
|
||||
[canDismiss]="true"
|
||||
[presentingElement]="routerOutlet.nativeEl"
|
||||
#mapListModal
|
||||
>
|
||||
@@ -131,6 +141,7 @@
|
||||
<map-list-modal
|
||||
style="height: 100%"
|
||||
[filterQuery]="filterQuery"
|
||||
[mapBounds]="this.map.getBounds()"
|
||||
[queryText]="queryText"
|
||||
></map-list-modal>
|
||||
</ng-template>
|
||||
|
||||
@@ -4,6 +4,7 @@ ion-content {
|
||||
div.map-container {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: fixed;
|
||||
}
|
||||
& > div {
|
||||
overflow: hidden;
|
||||
@@ -33,7 +34,7 @@ ion-toolbar:first-of-type {
|
||||
|
||||
div.floating-content {
|
||||
display: block;
|
||||
position: absolute;
|
||||
position: fixed;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
@@ -41,7 +42,7 @@ ion-toolbar:first-of-type {
|
||||
width: 100%;
|
||||
padding: 0 var(--spacing-md) 8vh;
|
||||
justify-content: center;
|
||||
|
||||
|
||||
ion-card {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
@@ -13,9 +13,10 @@
|
||||
* this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
import {Component, Input, OnInit} from '@angular/core';
|
||||
import {SCPlace, SCSearchFilter} from '@openstapps/core';
|
||||
import {SCSearchBooleanFilter, SCPlace, SCSearchFilter} from '@openstapps/core';
|
||||
import {MapProvider} from '../../map.provider';
|
||||
import {ModalController} from '@ionic/angular';
|
||||
import {LatLngBounds} from 'leaflet';
|
||||
|
||||
/**
|
||||
* Modal showing a provided list of places
|
||||
@@ -31,6 +32,11 @@ export class MapListModalComponent implements OnInit {
|
||||
*/
|
||||
@Input() filterQuery?: SCSearchFilter;
|
||||
|
||||
/**
|
||||
* Map visible boundaries limiting items in lust
|
||||
*/
|
||||
@Input() mapBounds?: LatLngBounds;
|
||||
|
||||
/**
|
||||
* Places to show in the list
|
||||
*/
|
||||
@@ -50,8 +56,44 @@ export class MapListModalComponent implements OnInit {
|
||||
* Populate the list with the results from the search
|
||||
*/
|
||||
ngOnInit() {
|
||||
let geofencedFilter: SCSearchBooleanFilter | undefined;
|
||||
if (typeof this.mapBounds !== 'undefined') {
|
||||
geofencedFilter = {
|
||||
arguments: {
|
||||
operation: 'and',
|
||||
filters: [
|
||||
{
|
||||
type: 'geo',
|
||||
arguments: {
|
||||
field: 'geo',
|
||||
shape: {
|
||||
coordinates: [
|
||||
[
|
||||
this.mapBounds.getNorthWest().lng,
|
||||
this.mapBounds.getNorthWest().lat,
|
||||
],
|
||||
[
|
||||
this.mapBounds.getSouthEast().lng,
|
||||
this.mapBounds.getSouthEast().lat,
|
||||
],
|
||||
],
|
||||
type: 'envelope',
|
||||
},
|
||||
spatialRelation: 'intersects',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
type: 'boolean',
|
||||
};
|
||||
if (typeof this.filterQuery !== 'undefined') {
|
||||
geofencedFilter.arguments.filters.push(this.filterQuery);
|
||||
}
|
||||
}
|
||||
|
||||
const geofencedFilterQuery = geofencedFilter ?? this.filterQuery;
|
||||
this.mapProvider
|
||||
.searchPlaces(this.filterQuery, this.queryText)
|
||||
.searchPlaces(geofencedFilterQuery, this.queryText)
|
||||
.then(result => {
|
||||
this.items = result.data as SCPlace[];
|
||||
});
|
||||
|
||||
@@ -58,7 +58,11 @@ export class MapWidgetComponent implements OnInit {
|
||||
* Prepare the map
|
||||
*/
|
||||
ngOnInit() {
|
||||
const markerLayer = MapProvider.getPointMarker(this.place.geo.point);
|
||||
const markerLayer = MapProvider.getPointMarker(
|
||||
this.place.geo.point,
|
||||
'stapps-location',
|
||||
32,
|
||||
);
|
||||
this.options = {
|
||||
center: geoJSON(this.place.geo.polygon || this.place.geo.point)
|
||||
.getBounds()
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
type="overlay"
|
||||
menuId="context"
|
||||
contentId="{{ contentId }}"
|
||||
maxEdgeStart="0"
|
||||
side="end"
|
||||
>
|
||||
<ion-toolbar color="primary" mode="ios">
|
||||
|
||||
@@ -44,7 +44,7 @@ export class ModalEventCreatorComponent implements OnInit, OnDestroy {
|
||||
isModal: true,
|
||||
inputItem: item,
|
||||
},
|
||||
swipeToClose: true,
|
||||
canDismiss: true,
|
||||
presentingElement: await this.modalController.getTop(),
|
||||
});
|
||||
return modal.present();
|
||||
|
||||
@@ -81,7 +81,7 @@
|
||||
</ion-fab>
|
||||
|
||||
<ion-modal
|
||||
swipeToClose="true"
|
||||
canDismiss="true"
|
||||
[presentingElement]="routerOutlet.nativeEl"
|
||||
[isOpen]="isModalOpen"
|
||||
(ionModalWillDismiss)="onModalDismiss()"
|
||||
|
||||
@@ -112,7 +112,7 @@ export class CalendarSyncSettingsComponent implements OnInit {
|
||||
|
||||
const modal = await this.modalController.create({
|
||||
component: AddEventReviewModalComponent,
|
||||
swipeToClose: true,
|
||||
canDismiss: true,
|
||||
cssClass: 'add-modal',
|
||||
componentProps: {
|
||||
dismissAction: () => {
|
||||
|
||||
@@ -213,10 +213,9 @@
|
||||
"MORE": "Mehr"
|
||||
},
|
||||
"geolocation": {
|
||||
"TITLE": "Standort",
|
||||
"SUBTITLE": "Standort nicht erreichbar",
|
||||
"NOT_ENABLED": "Standortermittlung auf Deinem Gerät ist nicht aktiviert",
|
||||
"NOT_ALLOWED": "Zugriff auf den Standort für die App nicht zugelassen"
|
||||
"TITLE": "Standort nicht verfügbar",
|
||||
"NOT_ENABLED": "Die Standortermittlung auf dem Gerät ist nicht aktiviert",
|
||||
"NOT_ALLOWED": "Du hast den Zugriff auf deinen Standort für diese App abgelehnt. Nutze die Datenschutz-Einstellungen deines Gerätes um den Zugriff zu erlauben."
|
||||
}
|
||||
},
|
||||
"modals": {
|
||||
|
||||
@@ -213,10 +213,9 @@
|
||||
"MORE": "More"
|
||||
},
|
||||
"geolocation": {
|
||||
"TITLE": "Location",
|
||||
"SUBTITLE": "Location not available",
|
||||
"NOT_ENABLED": "Location service is not enabled on your device",
|
||||
"NOT_ALLOWED": "The app is not allowed to access your location"
|
||||
"TITLE": "Location not available",
|
||||
"NOT_ENABLED": "Location services are not enabled on your device",
|
||||
"NOT_ALLOWED": "The app is not allowed to access your location. Use your device privacy settings to allow it again."
|
||||
}
|
||||
},
|
||||
"modals": {
|
||||
|
||||
Binary file not shown.
@@ -45,7 +45,7 @@ stapps-icon {
|
||||
|
||||
.map-location-pin {
|
||||
font-variation-settings: 'FILL' 1;
|
||||
color: var(--ion-color-primary);
|
||||
color: var(--ion-color-tertiary);
|
||||
|
||||
&::before {
|
||||
content: attr(name);
|
||||
|
||||
Reference in New Issue
Block a user