mirror of
https://gitlab.com/openstapps/openstapps.git
synced 2026-04-16 03:09:15 +00:00
feat: add external directions referral
feat: change map page feat: add error handling and timeout to location fetching in directions resolves #124 resolves #122
This commit is contained in:
5
.changeset/orange-knives-happen.md
Normal file
5
.changeset/orange-knives-happen.md
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
'@openstapps/app': minor
|
||||||
|
---
|
||||||
|
|
||||||
|
Add directions to inPlace and place list items
|
||||||
8
.changeset/proud-cameras-fail.md
Normal file
8
.changeset/proud-cameras-fail.md
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
---
|
||||||
|
'@openstapps/app': minor
|
||||||
|
---
|
||||||
|
|
||||||
|
Adjust map button and item behavior on different screen sizes
|
||||||
|
|
||||||
|
- Small screens will show the item without margins below the map actions
|
||||||
|
- Large screens will show the list item on the left side
|
||||||
5
.changeset/thick-mails-peel.md
Normal file
5
.changeset/thick-mails-peel.md
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
'@openstapps/app': minor
|
||||||
|
---
|
||||||
|
|
||||||
|
Map items are now native list items
|
||||||
@@ -8,7 +8,7 @@
|
|||||||
"parserOptions": {
|
"parserOptions": {
|
||||||
"ecmaVersion": 2020,
|
"ecmaVersion": 2020,
|
||||||
"sourceType": "module",
|
"sourceType": "module",
|
||||||
"project": ["tsconfig.json", "tsconfig.spec.json", "e2e/tsconfig.e2e.json"],
|
"project": ["tsconfig.json", "tsconfig.spec.json", "cypress/tsconfig.json"],
|
||||||
"createDefaultProgram": true
|
"createDefaultProgram": true
|
||||||
},
|
},
|
||||||
"extends": [
|
"extends": [
|
||||||
|
|||||||
@@ -63,6 +63,7 @@
|
|||||||
"@awesome-cordova-plugins/core": "5.45.0",
|
"@awesome-cordova-plugins/core": "5.45.0",
|
||||||
"@capacitor/app": "4.1.1",
|
"@capacitor/app": "4.1.1",
|
||||||
"@capacitor/browser": "4.1.0",
|
"@capacitor/browser": "4.1.0",
|
||||||
|
"@capacitor/clipboard": "4.1.0",
|
||||||
"@capacitor/core": "4.6.1",
|
"@capacitor/core": "4.6.1",
|
||||||
"@capacitor/device": "4.1.0",
|
"@capacitor/device": "4.1.0",
|
||||||
"@capacitor/dialog": "4.1.0",
|
"@capacitor/dialog": "4.1.0",
|
||||||
@@ -87,6 +88,7 @@
|
|||||||
"@openstapps/collection-utils": "workspace:*",
|
"@openstapps/collection-utils": "workspace:*",
|
||||||
"@openstapps/core": "workspace:*",
|
"@openstapps/core": "workspace:*",
|
||||||
"@transistorsoft/capacitor-background-fetch": "1.0.2",
|
"@transistorsoft/capacitor-background-fetch": "1.0.2",
|
||||||
|
"@types/dom-view-transitions": "1.0.1",
|
||||||
"capacitor-secure-storage-plugin": "0.8.1",
|
"capacitor-secure-storage-plugin": "0.8.1",
|
||||||
"cordova-plugin-calendar": "5.1.6",
|
"cordova-plugin-calendar": "5.1.6",
|
||||||
"deepmerge": "4.3.1",
|
"deepmerge": "4.3.1",
|
||||||
|
|||||||
@@ -42,6 +42,7 @@ export class ActionChipListComponent {
|
|||||||
event:
|
event:
|
||||||
item.type === SCThingType.AcademicEvent ||
|
item.type === SCThingType.AcademicEvent ||
|
||||||
(item.type === SCThingType.DateSeries && (item as SCDateSeries).dates.length > 0),
|
(item.type === SCThingType.DateSeries && (item as SCDateSeries).dates.length > 0),
|
||||||
|
navigate: ('inPlace' in item && item.inPlace && 'geo' in item.inPlace) || 'geo' in item,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -14,5 +14,6 @@
|
|||||||
-->
|
-->
|
||||||
|
|
||||||
<stapps-locate-action-chip *ngIf="applicable.locate" [item]="item"></stapps-locate-action-chip>
|
<stapps-locate-action-chip *ngIf="applicable.locate" [item]="item"></stapps-locate-action-chip>
|
||||||
|
<stapps-navigate-action-chip *ngIf="applicable.navigate" [item]="$any(item)"></stapps-navigate-action-chip>
|
||||||
<!-- Add Event Chip needs to load data and should be the last -->
|
<!-- Add Event Chip needs to load data and should be the last -->
|
||||||
<stapps-add-event-action-chip *ngIf="applicable.event" [item]="item"></stapps-add-event-action-chip>
|
<stapps-add-event-action-chip *ngIf="applicable.event" [item]="item"></stapps-add-event-action-chip>
|
||||||
|
|||||||
@@ -0,0 +1,35 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2023 StApps
|
||||||
|
* This program is free software: you can redistribute it and/or modify it
|
||||||
|
* under the terms of the GNU General Public License as published by the Free
|
||||||
|
* Software Foundation, version 3.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||||
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||||
|
* more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License along with
|
||||||
|
* this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
import {Component, Input} from '@angular/core';
|
||||||
|
import {SCPlaceWithoutReferences, SCThings} from '@openstapps/core';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'stapps-navigate-action-chip',
|
||||||
|
templateUrl: 'navigate-action-chip.html',
|
||||||
|
styleUrls: ['navigate-action-chip.scss'],
|
||||||
|
})
|
||||||
|
export class NavigateActionChipComponent {
|
||||||
|
place: SCPlaceWithoutReferences;
|
||||||
|
|
||||||
|
@Input({required: true}) set item(value: SCThings) {
|
||||||
|
if ('geo' in value) {
|
||||||
|
this.place = value;
|
||||||
|
} else if ('inPlace' in value && value.inPlace && 'geo' in value.inPlace) {
|
||||||
|
this.place = value.inPlace;
|
||||||
|
} else {
|
||||||
|
console.error('Invalid place', value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
<!--
|
<!--
|
||||||
~ Copyright (C) 2022 StApps
|
~ Copyright (C) 2023 StApps
|
||||||
~ This program is free software: you can redistribute it and/or modify it
|
~ This program is free software: you can redistribute it and/or modify it
|
||||||
~ under the terms of the GNU General Public License as published by the Free
|
~ under the terms of the GNU General Public License as published by the Free
|
||||||
~ Software Foundation, version 3.
|
~ Software Foundation, version 3.
|
||||||
@@ -12,15 +12,7 @@
|
|||||||
~ You should have received a copy of the GNU General Public License along with
|
~ You should have received a copy of the GNU General Public License along with
|
||||||
~ this program. If not, see <https://www.gnu.org/licenses/>.
|
~ this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
-->
|
-->
|
||||||
|
<ion-chip [color]="'primary'" [outline]="true" [geoNavigation]="place">
|
||||||
<ion-header translucent>
|
<ion-icon name="directions"></ion-icon>
|
||||||
<ion-toolbar color="primary" mode="ios">
|
<ion-label>{{'map.directions.TITLE' | translate}}</ion-label>
|
||||||
<ion-title>{{ 'map.modals.single.TITLE' | translate }}</ion-title>
|
</ion-chip>
|
||||||
<ion-buttons slot="end">
|
|
||||||
<ion-button (click)="modalController.dismiss()">{{ 'app.ui.CLOSE' | translate }}</ion-button>
|
|
||||||
</ion-buttons>
|
|
||||||
</ion-toolbar>
|
|
||||||
</ion-header>
|
|
||||||
<ion-content>
|
|
||||||
<stapps-data-detail-content [item]="$any(item)" [openAsModal]="true"></stapps-data-detail-content>
|
|
||||||
</ion-content>
|
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*!
|
||||||
* Copyright (C) 2021 StApps
|
* Copyright (C) 2023 StApps
|
||||||
* This program is free software: you can redistribute it and/or modify it
|
* This program is free software: you can redistribute it and/or modify it
|
||||||
* under the terms of the GNU General Public License as published by the Free
|
* under the terms of the GNU General Public License as published by the Free
|
||||||
* Software Foundation, version 3.
|
* Software Foundation, version 3.
|
||||||
@@ -12,20 +12,3 @@
|
|||||||
* You should have received a copy of the GNU General Public License along with
|
* You should have received a copy of the GNU General Public License along with
|
||||||
* this program. If not, see <https://www.gnu.org/licenses/>.
|
* this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
import {Component, Input} from '@angular/core';
|
|
||||||
import {SCPlace} from '@openstapps/core';
|
|
||||||
import {ModalController} from '@ionic/angular';
|
|
||||||
|
|
||||||
@Component({
|
|
||||||
selector: 'app-map-single-modal',
|
|
||||||
templateUrl: './map-single.html',
|
|
||||||
styleUrls: ['./map-single.scss'],
|
|
||||||
})
|
|
||||||
export class MapSingleModalComponent {
|
|
||||||
/**
|
|
||||||
* The item to be shown
|
|
||||||
*/
|
|
||||||
@Input() item: SCPlace;
|
|
||||||
|
|
||||||
constructor(readonly modalController: ModalController) {}
|
|
||||||
}
|
|
||||||
@@ -102,6 +102,8 @@ import {StappsRatingComponent} from './elements/rating.component';
|
|||||||
import {DishCharacteristicsComponent} from './types/dish/dish-characteristics.component';
|
import {DishCharacteristicsComponent} from './types/dish/dish-characteristics.component';
|
||||||
import {SkeletonListComponent} from './list/skeleton-list.component';
|
import {SkeletonListComponent} from './list/skeleton-list.component';
|
||||||
import {CertificationsInDetailComponent} from './elements/certifications-in-detail.component';
|
import {CertificationsInDetailComponent} from './elements/certifications-in-detail.component';
|
||||||
|
import {GeoNavigationDirective} from '../map/geo-navigation.directive';
|
||||||
|
import {NavigateActionChipComponent} from './chips/data/navigate-action-chip.component';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Module for handling data
|
* Module for handling data
|
||||||
@@ -110,6 +112,7 @@ import {CertificationsInDetailComponent} from './elements/certifications-in-deta
|
|||||||
declarations: [
|
declarations: [
|
||||||
ActionChipListComponent,
|
ActionChipListComponent,
|
||||||
AddEventActionChipComponent,
|
AddEventActionChipComponent,
|
||||||
|
NavigateActionChipComponent,
|
||||||
EditEventSelectionComponent,
|
EditEventSelectionComponent,
|
||||||
AddressDetailComponent,
|
AddressDetailComponent,
|
||||||
CatalogDetailContentComponent,
|
CatalogDetailContentComponent,
|
||||||
@@ -194,6 +197,7 @@ import {CertificationsInDetailComponent} from './elements/certifications-in-deta
|
|||||||
TranslateModule.forChild(),
|
TranslateModule.forChild(),
|
||||||
ThingTranslateModule.forChild(),
|
ThingTranslateModule.forChild(),
|
||||||
UtilModule,
|
UtilModule,
|
||||||
|
GeoNavigationDirective,
|
||||||
],
|
],
|
||||||
providers: [
|
providers: [
|
||||||
CoordinatedSearchProvider,
|
CoordinatedSearchProvider,
|
||||||
|
|||||||
@@ -63,3 +63,4 @@
|
|||||||
<stapps-data-list-item [item]="$any(item.inPlace)"></stapps-data-list-item>
|
<stapps-data-list-item [item]="$any(item.inPlace)"></stapps-data-list-item>
|
||||||
</ion-card-content>
|
</ion-card-content>
|
||||||
</ion-card>
|
</ion-card>
|
||||||
|
<stapps-map-widget *ngIf="item.inPlace?.geo" [place]="item.inPlace"></stapps-map-widget>
|
||||||
|
|||||||
@@ -38,9 +38,5 @@
|
|||||||
</ion-card>
|
</ion-card>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
<ng-container *ngIf="hasValidLocation">
|
<ng-container *ngIf="hasValidLocation">
|
||||||
<stapps-map-widget
|
<stapps-map-widget [place]="item" expandable="true"></stapps-map-widget>
|
||||||
class="map-widget expand-when-space"
|
|
||||||
[place]="item"
|
|
||||||
expandable="true"
|
|
||||||
></stapps-map-widget>
|
|
||||||
</ng-container>
|
</ng-container>
|
||||||
|
|||||||
@@ -12,10 +12,3 @@
|
|||||||
* You should have received a copy of the GNU General Public License along with
|
* You should have received a copy of the GNU General Public License along with
|
||||||
* this program. If not, see <https://www.gnu.org/licenses/>.
|
* this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
.map-widget {
|
|
||||||
position: relative;
|
|
||||||
width: auto;
|
|
||||||
height: 300px;
|
|
||||||
min-height: 300px;
|
|
||||||
}
|
|
||||||
|
|||||||
92
frontend/app/src/app/modules/map/geo-navigation.directive.ts
Normal file
92
frontend/app/src/app/modules/map/geo-navigation.directive.ts
Normal file
@@ -0,0 +1,92 @@
|
|||||||
|
import {Directive, HostListener, Input} from '@angular/core';
|
||||||
|
import {SCPlaceWithoutReferences, SCThings, SCThingWithoutReferences} from '@openstapps/core';
|
||||||
|
import {Device} from '@capacitor/device';
|
||||||
|
import {ActionSheetController, ActionSheetOptions, ToastController} from '@ionic/angular';
|
||||||
|
import {TranslateService} from '@ngx-translate/core';
|
||||||
|
import {ThingTranslateService} from '../../translation/thing-translate.service';
|
||||||
|
import {Clipboard} from '@capacitor/clipboard';
|
||||||
|
import {PositionService} from './position.service';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A button that provides navigation options to the user via an action sheet
|
||||||
|
* @example
|
||||||
|
* <ion-button shape="round" [geoNavigation]="place">
|
||||||
|
* <ion-icon name="directions" slot="start"></ion-icon>
|
||||||
|
* <ion-label>{{'map.directions.TITLE' | translate}}</ion-label>
|
||||||
|
* </ion-button>
|
||||||
|
*/
|
||||||
|
@Directive({
|
||||||
|
selector: '[geoNavigation]',
|
||||||
|
standalone: true,
|
||||||
|
})
|
||||||
|
export class GeoNavigationDirective {
|
||||||
|
@Input({required: true}) geoNavigation: SCThingWithoutReferences &
|
||||||
|
Pick<SCPlaceWithoutReferences, 'geo' | 'address'>;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private actionSheetController: ActionSheetController,
|
||||||
|
private translateService: TranslateService,
|
||||||
|
private thingTranslate: ThingTranslateService,
|
||||||
|
private toastController: ToastController,
|
||||||
|
private positionService: PositionService,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
@HostListener('click', ['$event'])
|
||||||
|
async presentActionSheet(event: Event) {
|
||||||
|
event.stopPropagation();
|
||||||
|
const {operatingSystem} = await Device.getInfo();
|
||||||
|
const [lon, lat] = this.geoNavigation.geo.point.coordinates;
|
||||||
|
|
||||||
|
const supportedMapProviders =
|
||||||
|
operatingSystem === 'mac' || operatingSystem === 'ios'
|
||||||
|
? ['OSM_ROUTING', 'APPLE_MAPS', 'GOOGLE_MAPS']
|
||||||
|
: ['OSM_ROUTING', 'GOOGLE_MAPS'];
|
||||||
|
const address = this.geoNavigation.address
|
||||||
|
? this.translateService.instant(
|
||||||
|
'map.directions.ADDRESS',
|
||||||
|
this.thingTranslate.get(this.geoNavigation as SCThings, 'address'),
|
||||||
|
)
|
||||||
|
: `${lat}, ${lon}`;
|
||||||
|
|
||||||
|
const options: ActionSheetOptions = {
|
||||||
|
header: this.translateService.instant('map.directions.TITLE_LONG', {
|
||||||
|
name: this.thingTranslate.get(this.geoNavigation as SCThings, 'name'),
|
||||||
|
}),
|
||||||
|
subHeader: address,
|
||||||
|
buttons: [
|
||||||
|
{
|
||||||
|
text: this.translateService.instant('map.directions.COPY_ADDRESS'),
|
||||||
|
role: 'selected',
|
||||||
|
handler: async () => {
|
||||||
|
await Clipboard.write({string: address});
|
||||||
|
this.toastController
|
||||||
|
.create({
|
||||||
|
message: this.translateService.instant('map.directions.ADDRESS_COPIED'),
|
||||||
|
duration: 500,
|
||||||
|
})
|
||||||
|
.then(toast => toast.present());
|
||||||
|
},
|
||||||
|
},
|
||||||
|
...supportedMapProviders.map(provider => ({
|
||||||
|
text: this.translateService.instant(`map.directions.${provider}.TITLE`),
|
||||||
|
handler: () => {
|
||||||
|
const url: string = this.translateService.instant(`map.directions.${provider}.URL`, {
|
||||||
|
lat,
|
||||||
|
lon,
|
||||||
|
posLat: this.positionService.position?.latitude ?? 0,
|
||||||
|
posLon: this.positionService.position?.longitude ?? 0,
|
||||||
|
});
|
||||||
|
window.open(url.replace(/&?\w+=0,0/, ''), '_blank', 'noreferrer');
|
||||||
|
},
|
||||||
|
})),
|
||||||
|
{
|
||||||
|
text: this.translateService.instant('abort'),
|
||||||
|
role: 'cancel',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
const actionSheet = await this.actionSheetController.create(options);
|
||||||
|
await actionSheet.present();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,36 +0,0 @@
|
|||||||
<!--
|
|
||||||
~ Copyright (C) 2022 StApps
|
|
||||||
~ This program is free software: you can redistribute it and/or modify it
|
|
||||||
~ under the terms of the GNU General Public License as published by the Free
|
|
||||||
~ Software Foundation, version 3.
|
|
||||||
~
|
|
||||||
~ This program is distributed in the hope that it will be useful, but WITHOUT
|
|
||||||
~ ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
||||||
~ FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
|
||||||
~ more details.
|
|
||||||
~
|
|
||||||
~ You should have received a copy of the GNU General Public License along with
|
|
||||||
~ this program. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
-->
|
|
||||||
|
|
||||||
<ion-card class="compact">
|
|
||||||
<ion-card-header>
|
|
||||||
<stapps-data-list-item [item]="$any(item)" id="show-more"></stapps-data-list-item>
|
|
||||||
<stapps-skeleton-list-item *ngIf="!item"></stapps-skeleton-list-item>
|
|
||||||
</ion-card-header>
|
|
||||||
<ion-card-content>
|
|
||||||
<ion-note>
|
|
||||||
<span *ngIf="item.address as address">
|
|
||||||
<span *ngIf="$any(item).inPlace">{{ $any(item).inPlace.name }},</span>
|
|
||||||
{{ address.streetAddress }}, {{ address.addressLocality }}
|
|
||||||
</span>
|
|
||||||
</ion-note>
|
|
||||||
<ion-button
|
|
||||||
size="small"
|
|
||||||
class="show-more-button"
|
|
||||||
fill="clear"
|
|
||||||
[routerLink]="['/data-detail', item.uid]"
|
|
||||||
>{{ 'map.page.buttons.MORE' | translate }}</ion-button
|
|
||||||
>
|
|
||||||
</ion-card-content>
|
|
||||||
</ion-card>
|
|
||||||
@@ -1,72 +0,0 @@
|
|||||||
/*!
|
|
||||||
* Copyright (C) 2022 StApps
|
|
||||||
* This program is free software: you can redistribute it and/or modify it
|
|
||||||
* under the terms of the GNU General Public License as published by the Free
|
|
||||||
* Software Foundation, version 3.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
|
||||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
||||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
|
||||||
* more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License along with
|
|
||||||
* this program. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
@import '../../../../theme/util/mixins';
|
|
||||||
|
|
||||||
:host {
|
|
||||||
display: block;
|
|
||||||
max-width: 100%;
|
|
||||||
|
|
||||||
ion-card {
|
|
||||||
overflow: visible;
|
|
||||||
padding: 0;
|
|
||||||
|
|
||||||
ion-card-header {
|
|
||||||
padding: 0;
|
|
||||||
border-bottom: var(--border-width-default) solid var(--border-color-default);
|
|
||||||
|
|
||||||
stapps-data-list-item {
|
|
||||||
--ion-margin: 0;
|
|
||||||
|
|
||||||
&::ng-deep ion-item {
|
|
||||||
--padding-start: 0;
|
|
||||||
--padding-end: 0;
|
|
||||||
|
|
||||||
ion-label {
|
|
||||||
white-space: break-spaces;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.close {
|
|
||||||
--padding-top: 0;
|
|
||||||
--padding-bottom: 0;
|
|
||||||
--padding-start: 0;
|
|
||||||
--padding-end: 0;
|
|
||||||
|
|
||||||
position: absolute;
|
|
||||||
z-index: 1;
|
|
||||||
top: -15px;
|
|
||||||
right: -15px;
|
|
||||||
|
|
||||||
ion-icon {
|
|
||||||
width: 30px;
|
|
||||||
height: 30px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ion-card-content {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
padding: var(--spacing-md);
|
|
||||||
|
|
||||||
.show-more-button {
|
|
||||||
margin-left: auto;
|
|
||||||
text-transform: uppercase;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,41 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2021 StApps
|
|
||||||
* This program is free software: you can redistribute it and/or modify it
|
|
||||||
* under the terms of the GNU General Public License as published by the Free
|
|
||||||
* Software Foundation, version 3.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
|
||||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
||||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
|
||||||
* more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License along with
|
|
||||||
* this program. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
import {Component, EventEmitter, Input, Output} from '@angular/core';
|
|
||||||
import {SCPlace} from '@openstapps/core';
|
|
||||||
import {IonRouterOutlet} from '@ionic/angular';
|
|
||||||
|
|
||||||
@Component({
|
|
||||||
selector: 'stapps-map-item',
|
|
||||||
templateUrl: './map-item.component.html',
|
|
||||||
styleUrls: ['./map-item.component.scss'],
|
|
||||||
})
|
|
||||||
export class MapItemComponent {
|
|
||||||
/**
|
|
||||||
* An item to show
|
|
||||||
*/
|
|
||||||
@Input() item: SCPlace;
|
|
||||||
|
|
||||||
// eslint-disable-next-line @angular-eslint/no-output-on-prefix
|
|
||||||
@Output() onClose = new EventEmitter<void>();
|
|
||||||
|
|
||||||
constructor(readonly routerOutlet: IonRouterOutlet) {}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Action when edit is clicked
|
|
||||||
*/
|
|
||||||
onCloseClick() {
|
|
||||||
this.onClose.emit();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -29,12 +29,11 @@ import {StAppsWebHttpClient} from '../data/stapps-web-http-client.provider';
|
|||||||
import {MenuModule} from '../menu/menu.module';
|
import {MenuModule} from '../menu/menu.module';
|
||||||
import {MapProvider} from './map.provider';
|
import {MapProvider} from './map.provider';
|
||||||
import {MapPageComponent} from './page/map-page.component';
|
import {MapPageComponent} from './page/map-page.component';
|
||||||
import {MapListModalComponent} from './page/modals/map-list-modal.component';
|
import {MapListModalComponent} from './page/map-list-modal.component';
|
||||||
import {MapSingleModalComponent} from './page/modals/map-single-modal.component';
|
|
||||||
import {MapItemComponent} from './item/map-item.component';
|
|
||||||
import {NgModule} from '@angular/core';
|
import {NgModule} from '@angular/core';
|
||||||
import {UtilModule} from '../../util/util.module';
|
import {UtilModule} from '../../util/util.module';
|
||||||
import {IonIconModule} from '../../util/ion-icon/ion-icon.module';
|
import {IonIconModule} from '../../util/ion-icon/ion-icon.module';
|
||||||
|
import {GeoNavigationDirective} from './geo-navigation.directive';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initializes the default area to show in advance (before components are initialized)
|
* Initializes the default area to show in advance (before components are initialized)
|
||||||
@@ -56,7 +55,7 @@ const mapRoutes: Routes = [
|
|||||||
* Module containing map related stuff
|
* Module containing map related stuff
|
||||||
*/
|
*/
|
||||||
@NgModule({
|
@NgModule({
|
||||||
declarations: [MapPageComponent, MapListModalComponent, MapSingleModalComponent, MapItemComponent],
|
declarations: [MapPageComponent, MapListModalComponent],
|
||||||
exports: [],
|
exports: [],
|
||||||
imports: [
|
imports: [
|
||||||
CommonModule,
|
CommonModule,
|
||||||
@@ -71,6 +70,8 @@ const mapRoutes: Routes = [
|
|||||||
FormsModule,
|
FormsModule,
|
||||||
ThingTranslateModule,
|
ThingTranslateModule,
|
||||||
UtilModule,
|
UtilModule,
|
||||||
|
GeoNavigationDirective,
|
||||||
|
GeoNavigationDirective,
|
||||||
],
|
],
|
||||||
providers: [Geolocation, MapProvider, DataProvider, DataFacetsProvider, StAppsWebHttpClient],
|
providers: [Geolocation, MapProvider, DataProvider, DataFacetsProvider, StAppsWebHttpClient],
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -14,7 +14,7 @@
|
|||||||
*/
|
*/
|
||||||
import {Component, Input, OnInit} from '@angular/core';
|
import {Component, Input, OnInit} from '@angular/core';
|
||||||
import {SCSearchBooleanFilter, SCPlace, SCSearchFilter} from '@openstapps/core';
|
import {SCSearchBooleanFilter, SCPlace, SCSearchFilter} from '@openstapps/core';
|
||||||
import {MapProvider} from '../../map.provider';
|
import {MapProvider} from '../map.provider';
|
||||||
import {ModalController} from '@ionic/angular';
|
import {ModalController} from '@ionic/angular';
|
||||||
import {LatLngBounds} from 'leaflet';
|
import {LatLngBounds} from 'leaflet';
|
||||||
|
|
||||||
@@ -23,8 +23,8 @@ import {LatLngBounds} from 'leaflet';
|
|||||||
*/
|
*/
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'map-list-modal',
|
selector: 'map-list-modal',
|
||||||
templateUrl: 'map-list.html',
|
templateUrl: 'map-list-modal.html',
|
||||||
styleUrls: ['map-list.scss'],
|
styleUrls: ['map-list-modal.scss'],
|
||||||
})
|
})
|
||||||
export class MapListModalComponent implements OnInit {
|
export class MapListModalComponent implements OnInit {
|
||||||
/**
|
/**
|
||||||
@@ -29,6 +29,7 @@ import {Geolocation, PermissionStatus} from '@capacitor/geolocation';
|
|||||||
import {Capacitor} from '@capacitor/core';
|
import {Capacitor} from '@capacitor/core';
|
||||||
import {pauseWhen} from '../../../util/pause-when';
|
import {pauseWhen} from '../../../util/pause-when';
|
||||||
import {takeUntilDestroyed} from '@angular/core/rxjs-interop';
|
import {takeUntilDestroyed} from '@angular/core/rxjs-interop';
|
||||||
|
import {startViewTransition} from '../../../util/view-transition';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The main page of the map
|
* The main page of the map
|
||||||
@@ -100,7 +101,17 @@ export class MapPageComponent implements OnInit {
|
|||||||
/**
|
/**
|
||||||
* Options of the leaflet map
|
* Options of the leaflet map
|
||||||
*/
|
*/
|
||||||
options: MapOptions;
|
options: MapOptions = {
|
||||||
|
center: geoJSON(this.mapProvider.defaultPolygon).getBounds().getCenter(),
|
||||||
|
layers: [
|
||||||
|
tileLayer('https://osm.server.uni-frankfurt.de/tiles/roads/x={x}&y={y}&z={z}', {
|
||||||
|
attribution: '© <a href="http://osm.org/copyright">OpenStreetMap</a> contributors',
|
||||||
|
maxZoom: this.MAX_ZOOM,
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
zoom: this.DEFAULT_ZOOM,
|
||||||
|
zoomControl: false,
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Position of the user on the map
|
* Position of the user on the map
|
||||||
@@ -134,20 +145,7 @@ export class MapPageComponent implements OnInit {
|
|||||||
private dataRoutingService: DataRoutingService,
|
private dataRoutingService: DataRoutingService,
|
||||||
private positionService: PositionService,
|
private positionService: PositionService,
|
||||||
readonly routerOutlet: IonRouterOutlet,
|
readonly routerOutlet: IonRouterOutlet,
|
||||||
) {
|
) {}
|
||||||
// initialize the options
|
|
||||||
this.options = {
|
|
||||||
center: geoJSON(this.mapProvider.defaultPolygon).getBounds().getCenter(),
|
|
||||||
layers: [
|
|
||||||
tileLayer('https://osm.server.uni-frankfurt.de/tiles/roads/x={x}&y={y}&z={z}', {
|
|
||||||
attribution: '© <a href="http://osm.org/copyright">OpenStreetMap</a> contributors',
|
|
||||||
maxZoom: this.MAX_ZOOM,
|
|
||||||
}),
|
|
||||||
],
|
|
||||||
zoom: this.DEFAULT_ZOOM,
|
|
||||||
zoomControl: false,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
this.dataRoutingService
|
this.dataRoutingService
|
||||||
@@ -305,6 +303,7 @@ export class MapPageComponent implements OnInit {
|
|||||||
*/
|
*/
|
||||||
async onMapReady(map: Map) {
|
async onMapReady(map: Map) {
|
||||||
this.map = map;
|
this.map = map;
|
||||||
|
this.map.attributionControl.setPosition('topright');
|
||||||
const interval = window.setInterval(() =>
|
const interval = window.setInterval(() =>
|
||||||
MapProvider.invalidateWhenRendered(map, this.mapContainer, interval),
|
MapProvider.invalidateWhenRendered(map, this.mapContainer, interval),
|
||||||
);
|
);
|
||||||
@@ -384,10 +383,12 @@ export class MapPageComponent implements OnInit {
|
|||||||
* Resets the map = fetch all the items based on the filters (and go to component's base location)
|
* Resets the map = fetch all the items based on the filters (and go to component's base location)
|
||||||
*/
|
*/
|
||||||
async resetView() {
|
async resetView() {
|
||||||
|
startViewTransition(async () => {
|
||||||
this.location.go('/map');
|
this.location.go('/map');
|
||||||
await this.fetchAndUpdateItems(this.items.length > 0);
|
await this.fetchAndUpdateItems(this.items.length > 0);
|
||||||
|
|
||||||
this.ref.detectChanges();
|
this.ref.detectChanges();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -414,6 +415,7 @@ export class MapPageComponent implements OnInit {
|
|||||||
* @param uid Uuid of the place
|
* @param uid Uuid of the place
|
||||||
*/
|
*/
|
||||||
async showItem(uid: SCUuid) {
|
async showItem(uid: SCUuid) {
|
||||||
|
startViewTransition(async () => {
|
||||||
const response = await this.mapProvider.searchPlace(uid);
|
const response = await this.mapProvider.searchPlace(uid);
|
||||||
this.items = response.data as SCPlace[];
|
this.items = response.data as SCPlace[];
|
||||||
this.distance = this.positionService.getDistance(this.items[0].geo.point);
|
this.distance = this.positionService.getDistance(this.items[0].geo.point);
|
||||||
@@ -423,5 +425,6 @@ export class MapPageComponent implements OnInit {
|
|||||||
this.location.go(url);
|
this.location.go(url);
|
||||||
// center the selected place
|
// center the selected place
|
||||||
this.focus(geoJSON(this.items[0].geo.point).getBounds().getCenter());
|
this.focus(geoJSON(this.items[0].geo.point).getBounds().getCenter());
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -43,7 +43,7 @@
|
|||||||
</ion-toolbar>
|
</ion-toolbar>
|
||||||
</ion-header>
|
</ion-header>
|
||||||
|
|
||||||
<ion-content fullscreen id="map">
|
<ion-content id="map">
|
||||||
<div
|
<div
|
||||||
class="map-container"
|
class="map-container"
|
||||||
#mapContainer
|
#mapContainer
|
||||||
@@ -57,7 +57,7 @@
|
|||||||
<div *ngIf="position" [leafletLayer]="positionMarker"></div>
|
<div *ngIf="position" [leafletLayer]="positionMarker"></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="floating-content">
|
<div class="floating-content">
|
||||||
<div class="map-buttons above">
|
<div class="map-buttons">
|
||||||
<ion-button
|
<ion-button
|
||||||
*ngIf="items.length > 1"
|
*ngIf="items.length > 1"
|
||||||
color="light"
|
color="light"
|
||||||
@@ -67,7 +67,13 @@
|
|||||||
>
|
>
|
||||||
<ion-icon name="list"></ion-icon> {{ 'map.page.buttons.SHOW_LIST' | translate }}
|
<ion-icon name="list"></ion-icon> {{ 'map.page.buttons.SHOW_LIST' | translate }}
|
||||||
</ion-button>
|
</ion-button>
|
||||||
<ion-button color="light" shape="round" size="small" (click)="onPositionClick()">
|
<ion-button
|
||||||
|
color="light"
|
||||||
|
shape="round"
|
||||||
|
size="small"
|
||||||
|
(click)="onPositionClick()"
|
||||||
|
class="location-button"
|
||||||
|
>
|
||||||
<ion-icon *ngIf="position !== null; else noLocationIcon" name="my_location"></ion-icon>
|
<ion-icon *ngIf="position !== null; else noLocationIcon" name="my_location"></ion-icon>
|
||||||
<ng-template #noLocationIcon>
|
<ng-template #noLocationIcon>
|
||||||
<ion-icon
|
<ion-icon
|
||||||
@@ -80,30 +86,12 @@
|
|||||||
</ng-template>
|
</ng-template>
|
||||||
</ion-button>
|
</ion-button>
|
||||||
</div>
|
</div>
|
||||||
<stapps-map-item *ngIf="items.length === 1" [item]="items[0]" (onClose)="resetView()"></stapps-map-item>
|
<ion-card class="map-item">
|
||||||
</div>
|
<stapps-data-list-item *ngIf="items.length === 1" [item]="$any(items[0])"></stapps-data-list-item>
|
||||||
<div class="map-buttons floating-buttons">
|
<ion-button fill="clear" class="close" (click)="resetView()">
|
||||||
<ion-button
|
<ion-icon size="22" name="close" slot="icon-only"></ion-icon>
|
||||||
*ngIf="items.length > 1"
|
|
||||||
color="light"
|
|
||||||
shape="round"
|
|
||||||
size="small"
|
|
||||||
(click)="mapListModal.present()"
|
|
||||||
>
|
|
||||||
<ion-icon name="list"></ion-icon> {{ 'map.page.buttons.SHOW_LIST' | translate }}
|
|
||||||
</ion-button>
|
|
||||||
<ion-button color="light" shape="round" size="small" (click)="onPositionClick()">
|
|
||||||
<ion-icon *ngIf="position !== null; else noLocationIcon" name="my_location"></ion-icon>
|
|
||||||
<ng-template #noLocationIcon>
|
|
||||||
<ion-icon
|
|
||||||
*ngIf="locationStatus && locationStatus.location === 'denied'; else pendingLocationIcon"
|
|
||||||
name="location_disabled"
|
|
||||||
></ion-icon>
|
|
||||||
</ng-template>
|
|
||||||
<ng-template #pendingLocationIcon>
|
|
||||||
<ion-icon name="location_searching"></ion-icon>
|
|
||||||
</ng-template>
|
|
||||||
</ion-button>
|
</ion-button>
|
||||||
|
</ion-card>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<ion-modal [canDismiss]="true" #mapListModal>
|
<ion-modal [canDismiss]="true" #mapListModal>
|
||||||
|
|||||||
@@ -12,113 +12,87 @@
|
|||||||
* You should have received a copy of the GNU General Public License along with
|
* You should have received a copy of the GNU General Public License along with
|
||||||
* this program. If not, see <https://www.gnu.org/licenses/>.
|
* this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
@import '../../../../theme/util/mixins';
|
||||||
|
|
||||||
ion-content {
|
$bottom-offset: 7px; // no idea what happened here
|
||||||
// fixes the unexpected issue that the content is not fullscreen (behind the header)
|
|
||||||
position: absolute;
|
|
||||||
|
|
||||||
div.map-container {
|
.map-container {
|
||||||
position: fixed;
|
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
|
||||||
|
|
||||||
& > div {
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ion-toolbar:first-of-type {
|
ion-toolbar:first-of-type {
|
||||||
padding: 0 var(--spacing-md) var(--spacing-xs);
|
padding: 0 var(--spacing-md) var(--spacing-xs);
|
||||||
}
|
}
|
||||||
|
|
||||||
div.map-buttons {
|
.floating-content {
|
||||||
ion-button {
|
|
||||||
// important for iOS
|
|
||||||
// TODO: find an option that is better suited for the iOS theme
|
|
||||||
--box-shadow: var(--map-box-shadow);
|
|
||||||
|
|
||||||
align-self: flex-end;
|
|
||||||
margin: 4px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
::ng-deep {
|
|
||||||
.stapps-location {
|
|
||||||
ion-icon {
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
color: #fd435c;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.stapps-device-location {
|
|
||||||
ion-icon {
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
color: #4387fd;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
div.floating-content {
|
|
||||||
position: fixed;
|
position: fixed;
|
||||||
z-index: 1000;
|
z-index: 1000;
|
||||||
right: 0;
|
right: 0;
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
|
|
||||||
display: block;
|
display: flex;
|
||||||
justify-content: center;
|
flex-flow: row-reverse wrap;
|
||||||
|
align-items: flex-end;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
|
||||||
width: 100%;
|
.map-buttons {
|
||||||
padding: 0 var(--spacing-md) 8vh;
|
|
||||||
|
|
||||||
ion-card {
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.map-buttons {
|
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: flex-end;
|
justify-content: flex-end;
|
||||||
}
|
|
||||||
|
|
||||||
stapps-map-item {
|
ion-button {
|
||||||
position: center;
|
// important for iOS
|
||||||
justify-self: center;
|
// TODO: find an option that is better suited for the iOS theme
|
||||||
width: 550px;
|
--box-shadow: var(--map-box-shadow);
|
||||||
margin: var(--spacing-sm) auto;
|
|
||||||
|
align-self: flex-end;
|
||||||
|
margin: var(--spacing-md);
|
||||||
|
|
||||||
|
&.location-button {
|
||||||
|
view-transition-name: location-button;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
div.floating-buttons {
|
.map-item {
|
||||||
|
position: relative;
|
||||||
|
max-width: 550px;
|
||||||
|
margin: var(--spacing-md);
|
||||||
|
|
||||||
|
.close {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
z-index: 1000;
|
top: 0;
|
||||||
right: 10px;
|
right: 0;
|
||||||
bottom: 15px;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.map-buttons.above {
|
|
||||||
display: none;
|
|
||||||
min-width: 70%;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (width <= 667px) {
|
|
||||||
div.map-buttons.above {
|
|
||||||
display: flex;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
div.floating-content {
|
::ng-deep ion-item {
|
||||||
justify-content: normal;
|
margin: 0;
|
||||||
padding: 0 var(--spacing-md) var(--spacing-lg);
|
}
|
||||||
|
}
|
||||||
|
|
||||||
stapps-map-item {
|
@include ion-md-down {
|
||||||
display: grid;
|
.md {
|
||||||
|
ion-content {
|
||||||
|
--padding-bottom: $bottom-offset;
|
||||||
|
}
|
||||||
|
|
||||||
|
.floating-content {
|
||||||
|
bottom: $bottom-offset;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.map-buttons ion-button {
|
||||||
|
margin: var(--spacing-sm);
|
||||||
|
}
|
||||||
|
|
||||||
|
.map-item {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
max-width: unset;
|
||||||
}
|
margin: 0;
|
||||||
|
|
||||||
div.map-buttons.floating-buttons {
|
border-bottom-right-radius: 0;
|
||||||
display: none;
|
border-bottom-left-radius: 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +0,0 @@
|
|||||||
:host {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
}
|
|
||||||
@@ -12,9 +12,9 @@
|
|||||||
* You should have received a copy of the GNU General Public License along with
|
* You should have received a copy of the GNU General Public License along with
|
||||||
* this program. If not, see <https://www.gnu.org/licenses/>.
|
* this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
import {Component, ElementRef, Input, OnInit, ViewChild} from '@angular/core';
|
import {Component, ElementRef, HostBinding, Input, OnInit, ViewChild} from '@angular/core';
|
||||||
import {Router} from '@angular/router';
|
import {Router} from '@angular/router';
|
||||||
import {SCPlace} from '@openstapps/core';
|
import {SCPlaceWithoutReferences, SCThingWithoutReferences} from '@openstapps/core';
|
||||||
import {geoJSON, Map, MapOptions, tileLayer} from 'leaflet';
|
import {geoJSON, Map, MapOptions, tileLayer} from 'leaflet';
|
||||||
import {MapProvider} from '../map.provider';
|
import {MapProvider} from '../map.provider';
|
||||||
|
|
||||||
@@ -27,6 +27,8 @@ import {MapProvider} from '../map.provider';
|
|||||||
templateUrl: './map-widget.html',
|
templateUrl: './map-widget.html',
|
||||||
})
|
})
|
||||||
export class MapWidgetComponent implements OnInit {
|
export class MapWidgetComponent implements OnInit {
|
||||||
|
@HostBinding('class.expand-when-space') expandWhenSpace = true;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A leaflet map showed
|
* A leaflet map showed
|
||||||
*/
|
*/
|
||||||
@@ -45,7 +47,7 @@ export class MapWidgetComponent implements OnInit {
|
|||||||
/**
|
/**
|
||||||
* A place to show on the map
|
* A place to show on the map
|
||||||
*/
|
*/
|
||||||
@Input() place: SCPlace;
|
@Input() place: SCThingWithoutReferences & Pick<SCPlaceWithoutReferences, 'geo' | 'address'>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Indicates if the expand button should be visible
|
* Indicates if the expand button should be visible
|
||||||
|
|||||||
@@ -21,6 +21,10 @@
|
|||||||
[leafletOptions]="options"
|
[leafletOptions]="options"
|
||||||
></div>
|
></div>
|
||||||
<div class="map-buttons" *ngIf="showExpandButton">
|
<div class="map-buttons" *ngIf="showExpandButton">
|
||||||
|
<ion-button color="primary" shape="round" size="small" [geoNavigation]="place">
|
||||||
|
<ion-icon name="directions" slot="start"></ion-icon>
|
||||||
|
{{'map.directions.TITLE' | translate}}
|
||||||
|
</ion-button>
|
||||||
<ion-button color="primary" shape="round" size="small" [routerLink]="['/map', place.uid]">
|
<ion-button color="primary" shape="round" size="small" [routerLink]="['/map', place.uid]">
|
||||||
<ion-icon name="zoom_out_map"></ion-icon>
|
<ion-icon name="zoom_out_map"></ion-icon>
|
||||||
</ion-button>
|
</ion-button>
|
||||||
|
|||||||
@@ -12,6 +12,12 @@
|
|||||||
* You should have received a copy of the GNU General Public License along with
|
* You should have received a copy of the GNU General Public License along with
|
||||||
* this program. If not, see <https://www.gnu.org/licenses/>.
|
* this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
:host {
|
||||||
|
position: relative;
|
||||||
|
width: auto;
|
||||||
|
height: 300px;
|
||||||
|
min-height: 300px;
|
||||||
|
}
|
||||||
|
|
||||||
div.map-container {
|
div.map-container {
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
|
|||||||
@@ -17,7 +17,8 @@
|
|||||||
import english from '../../assets/i18n/en.json';
|
import english from '../../assets/i18n/en.json';
|
||||||
import german from '../../assets/i18n/de.json';
|
import german from '../../assets/i18n/de.json';
|
||||||
|
|
||||||
const exceptions = new Set([
|
const exceptions = new Set(
|
||||||
|
[
|
||||||
'login',
|
'login',
|
||||||
'ok',
|
'ok',
|
||||||
'protein',
|
'protein',
|
||||||
@@ -32,8 +33,15 @@ const exceptions = new Set([
|
|||||||
'home',
|
'home',
|
||||||
'email',
|
'email',
|
||||||
'logins',
|
'logins',
|
||||||
|
'google maps',
|
||||||
|
'apple maps',
|
||||||
|
'openstreetmaps routing',
|
||||||
'https://www.swffm.de/essen-trinken/uebersicht/umweltscore',
|
'https://www.swffm.de/essen-trinken/uebersicht/umweltscore',
|
||||||
]);
|
'https://www.google.com/maps/dir/?api=1&destination={{lat}},{{lon}}&origin={{posLat}},{{posLon}}',
|
||||||
|
'https://maps.apple.com/?daddr={{lat}},{{lon}}&saddr={{posLat}},{{posLon}}',
|
||||||
|
'https://www.openstreetmap.org/directions?from={{posLat}},{{posLon}}&to={{lat}},{{lon}}#map=15/{{lat}}/{{lon}}',
|
||||||
|
].map(it => it.toLowerCase()),
|
||||||
|
);
|
||||||
|
|
||||||
const languages = [
|
const languages = [
|
||||||
['english', english],
|
['english', english],
|
||||||
|
|||||||
14
frontend/app/src/app/util/view-transition.ts
Normal file
14
frontend/app/src/app/util/view-transition.ts
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
/**
|
||||||
|
* Performs a view transition
|
||||||
|
*
|
||||||
|
* This is a progressive enhancement for (as of right now) Chromium-based browsers
|
||||||
|
* - Firefox position: [positive](https://mozilla.github.io/standards-positions/#view-transitions)
|
||||||
|
* - WebKit position: [support](https://github.com/WebKit/standards-positions/issues/48#issuecomment-1679760489)
|
||||||
|
*/
|
||||||
|
export function startViewTransition(runner: () => Promise<void>) {
|
||||||
|
if ('startViewTransition' in document) {
|
||||||
|
document.startViewTransition(runner);
|
||||||
|
} else {
|
||||||
|
void runner();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -244,6 +244,25 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"map": {
|
"map": {
|
||||||
|
"directions": {
|
||||||
|
"TITLE": "Anbindung",
|
||||||
|
"TITLE_LONG": "Anbindung nach {{name}}",
|
||||||
|
"ADDRESS": "{{streetAddress}}, {{postalCode}} {{addressLocality}}",
|
||||||
|
"COPY_ADDRESS": "Adresse kopieren",
|
||||||
|
"ADDRESS_COPIED": "Adresse wurde kopiert",
|
||||||
|
"GOOGLE_MAPS": {
|
||||||
|
"TITLE": "Google Maps",
|
||||||
|
"URL": "https://www.google.com/maps/dir/?api=1&destination={{lat}},{{lon}}&origin={{posLat}},{{posLon}}"
|
||||||
|
},
|
||||||
|
"APPLE_MAPS": {
|
||||||
|
"TITLE": "Apple Maps",
|
||||||
|
"URL": "https://maps.apple.com/?daddr={{lat}},{{lon}}&saddr={{posLat}},{{posLon}}"
|
||||||
|
},
|
||||||
|
"OSM_ROUTING": {
|
||||||
|
"TITLE": "OpenStreetMaps Routing",
|
||||||
|
"URL": "https://routing.openstreetmap.de/?loc={{posLat}},{{posLon}}&loc={{lat}},{{lon}}&srv=2&hl=de"
|
||||||
|
}
|
||||||
|
},
|
||||||
"page": {
|
"page": {
|
||||||
"TITLE": "Karte",
|
"TITLE": "Karte",
|
||||||
"search_bar": {
|
"search_bar": {
|
||||||
|
|||||||
@@ -244,6 +244,25 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"map": {
|
"map": {
|
||||||
|
"directions": {
|
||||||
|
"TITLE": "Directions",
|
||||||
|
"TITLE_LONG": "Directions to {{name}}",
|
||||||
|
"ADDRESS": "{{streetAddress}}, {{addressLocality}} {{postalCode}}",
|
||||||
|
"COPY_ADDRESS": "Copy Address",
|
||||||
|
"ADDRESS_COPIED": "Address copied",
|
||||||
|
"GOOGLE_MAPS": {
|
||||||
|
"TITLE": "Google Maps",
|
||||||
|
"URL": "https://www.google.com/maps/dir/?api=1&destination={{lat}},{{lon}}&origin={{posLat}},{{posLon}}"
|
||||||
|
},
|
||||||
|
"APPLE_MAPS": {
|
||||||
|
"TITLE": "Apple Maps",
|
||||||
|
"URL": "https://maps.apple.com/?daddr={{lat}},{{lon}}&saddr={{posLat}},{{posLon}}"
|
||||||
|
},
|
||||||
|
"OSM_ROUTING": {
|
||||||
|
"TITLE": "OpenStreetMaps Routing",
|
||||||
|
"URL": "https://routing.openstreetmap.de/?loc={{posLat}},{{posLon}}&loc={{lat}},{{lon}}&srv=2&hl=en"
|
||||||
|
}
|
||||||
|
},
|
||||||
"page": {
|
"page": {
|
||||||
"TITLE": "Map",
|
"TITLE": "Map",
|
||||||
"search_bar": {
|
"search_bar": {
|
||||||
|
|||||||
Binary file not shown.
@@ -131,6 +131,18 @@ ion-alert {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.stapps-location ion-icon {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
color: #fd435c;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stapps-device-location ion-icon {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
color: #4387fd;
|
||||||
|
}
|
||||||
|
|
||||||
.add-event-popover {
|
.add-event-popover {
|
||||||
--width: fit-content;
|
--width: fit-content;
|
||||||
--max-width: 95%;
|
--max-width: 95%;
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
"extends": "./tsconfig.json",
|
"extends": "./tsconfig.json",
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"outDir": "../out-tsc/spec",
|
"outDir": "../out-tsc/spec",
|
||||||
"types": ["jasmine", "node"],
|
"types": ["jasmine", "node", "dom-view-transitions"],
|
||||||
"paths": {
|
"paths": {
|
||||||
"@capacitor/*": ["__mocks__/@capacitor/*"]
|
"@capacitor/*": ["__mocks__/@capacitor/*"]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,8 +5,7 @@
|
|||||||
"type": "module",
|
"type": "module",
|
||||||
"license": "GPL-3.0-only",
|
"license": "GPL-3.0-only",
|
||||||
"author": "Rainer Killinger <mail-openstapps@killinger.co>",
|
"author": "Rainer Killinger <mail-openstapps@killinger.co>",
|
||||||
"contributors": [
|
"contributors": [],
|
||||||
],
|
|
||||||
"files": [
|
"files": [
|
||||||
"Dockerfile",
|
"Dockerfile",
|
||||||
"CHANGELOG.md"
|
"CHANGELOG.md"
|
||||||
|
|||||||
20
pnpm-lock.yaml
generated
20
pnpm-lock.yaml
generated
@@ -752,6 +752,9 @@ importers:
|
|||||||
'@capacitor/browser':
|
'@capacitor/browser':
|
||||||
specifier: 4.1.0
|
specifier: 4.1.0
|
||||||
version: 4.1.0(@capacitor/core@4.6.1)
|
version: 4.1.0(@capacitor/core@4.6.1)
|
||||||
|
'@capacitor/clipboard':
|
||||||
|
specifier: 4.1.0
|
||||||
|
version: 4.1.0(@capacitor/core@4.6.1)
|
||||||
'@capacitor/core':
|
'@capacitor/core':
|
||||||
specifier: 4.6.1
|
specifier: 4.6.1
|
||||||
version: 4.6.1
|
version: 4.6.1
|
||||||
@@ -824,6 +827,9 @@ importers:
|
|||||||
'@transistorsoft/capacitor-background-fetch':
|
'@transistorsoft/capacitor-background-fetch':
|
||||||
specifier: 1.0.2
|
specifier: 1.0.2
|
||||||
version: 1.0.2(@capacitor/core@4.6.1)
|
version: 1.0.2(@capacitor/core@4.6.1)
|
||||||
|
'@types/dom-view-transitions':
|
||||||
|
specifier: 1.0.1
|
||||||
|
version: 1.0.1
|
||||||
capacitor-secure-storage-plugin:
|
capacitor-secure-storage-plugin:
|
||||||
specifier: 0.8.1
|
specifier: 0.8.1
|
||||||
version: 0.8.1(@capacitor/core@4.6.1)
|
version: 0.8.1(@capacitor/core@4.6.1)
|
||||||
@@ -1089,6 +1095,8 @@ importers:
|
|||||||
|
|
||||||
images/app-builder: {}
|
images/app-builder: {}
|
||||||
|
|
||||||
|
images/app-cypress: {}
|
||||||
|
|
||||||
images/node-base: {}
|
images/node-base: {}
|
||||||
|
|
||||||
images/node-builder: {}
|
images/node-builder: {}
|
||||||
@@ -4910,6 +4918,14 @@ packages:
|
|||||||
- supports-color
|
- supports-color
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/@capacitor/clipboard@4.1.0(@capacitor/core@4.6.1):
|
||||||
|
resolution: {integrity: sha512-lfUwDqZces3mQcBOyfxpBCsRWWSfLuPzekA1N3RaMgYVhD6/rdzFnzfRiksj1hm4It+lnULK0y+N5nxVnTt+0Q==}
|
||||||
|
peerDependencies:
|
||||||
|
'@capacitor/core': ^4.0.0
|
||||||
|
dependencies:
|
||||||
|
'@capacitor/core': 4.6.1
|
||||||
|
dev: false
|
||||||
|
|
||||||
/@capacitor/core@4.6.1:
|
/@capacitor/core@4.6.1:
|
||||||
resolution: {integrity: sha512-7A2IV9E8umgu9u0fChUTjQJq+Jp25GJZMmWxoQN/nVx/1rcpFJ4m1xo3NPBoIRs+aV7FR+BM17mPrnkKlA8N2g==}
|
resolution: {integrity: sha512-7A2IV9E8umgu9u0fChUTjQJq+Jp25GJZMmWxoQN/nVx/1rcpFJ4m1xo3NPBoIRs+aV7FR+BM17mPrnkKlA8N2g==}
|
||||||
dependencies:
|
dependencies:
|
||||||
@@ -6735,6 +6751,10 @@ packages:
|
|||||||
'@types/node': 18.15.3
|
'@types/node': 18.15.3
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
/@types/dom-view-transitions@1.0.1:
|
||||||
|
resolution: {integrity: sha512-A9S1ijj/4MX06I1W/6on8lhaYyq1Ir7gaOvfllW1o4RzVWW88HAeqX0pUx9VgOLnNpdiGeUW2CTkg18p5LWIrA==}
|
||||||
|
dev: false
|
||||||
|
|
||||||
/@types/eslint-scope@3.7.4:
|
/@types/eslint-scope@3.7.4:
|
||||||
resolution: {integrity: sha512-9K4zoImiZc3HlIp6AVUDE4CWYx22a+lhSZMYNpbjW04+YF0KWj4pJXnEMjdnFTiQibFFmElcsasJXDbdI/EPhA==}
|
resolution: {integrity: sha512-9K4zoImiZc3HlIp6AVUDE4CWYx22a+lhSZMYNpbjW04+YF0KWj4pJXnEMjdnFTiQibFFmElcsasJXDbdI/EPhA==}
|
||||||
dependencies:
|
dependencies:
|
||||||
|
|||||||
Reference in New Issue
Block a user