From b24875009d846981101d8e7783086da317f87ce1 Mon Sep 17 00:00:00 2001 From: bramjanssen Date: Thu, 31 Aug 2023 11:17:46 +0200 Subject: [PATCH 01/29] feat: first try at sharing --- src/app/models/vineyarddoc.model.ts | 4 +++ src/app/services/vineyard.service.ts | 44 +++++++++++++++++++++------- 2 files changed, 38 insertions(+), 10 deletions(-) diff --git a/src/app/models/vineyarddoc.model.ts b/src/app/models/vineyarddoc.model.ts index 23c2b67..5f6dceb 100644 --- a/src/app/models/vineyarddoc.model.ts +++ b/src/app/models/vineyarddoc.model.ts @@ -3,3 +3,7 @@ import { Vineyard } from './vineyard.model'; export interface VineyardDoc extends Vineyard { location: string; } +export interface SharedVineyardDoc { + user: string; + vineyard: string; +} diff --git a/src/app/services/vineyard.service.ts b/src/app/services/vineyard.service.ts index 01f7182..ccad3f4 100644 --- a/src/app/services/vineyard.service.ts +++ b/src/app/services/vineyard.service.ts @@ -1,11 +1,11 @@ import { Polygon } from 'ol/geom'; -import { VineyardDoc } from '../models/vineyarddoc.model'; +import { SharedVineyardDoc, VineyardDoc } from '../models/vineyarddoc.model'; import { MeteoStatEntry, Vineyard } from '../models/vineyard.model'; import { Variety } from '../models/variety.model'; import { UtilService } from './util.service'; import { Injectable } from '@angular/core'; import { HttpClient } from '@angular/common/http'; -import { BehaviorSubject, Observable, of } from 'rxjs'; +import { BehaviorSubject, combineLatest, forkJoin, Observable, of } from 'rxjs'; import { catchError, map, switchMap } from 'rxjs/operators'; import { Action, ActionType } from '../models/action.model'; import { @@ -33,6 +33,8 @@ export class VineyardService { private _vineyardCollection: AngularFirestoreCollection; + private _sharedVineyardCollection: AngularFirestoreCollection; + private _API: string = 'https://us-central1-winery-f4d20.cloudfunctions.net'; constructor( @@ -48,6 +50,7 @@ export class VineyardService { this.authService.getUser().subscribe((user: User) => { if (user) { this._vineyardCollection = fireStore.collection(`users/${user.uid}/vineyards`); + this._sharedVineyardCollection = fireStore.collection(`users/${user.uid}/sharedVineyards`); this.getVineyards(); } else { this._vineyards$.next([]); @@ -76,15 +79,9 @@ export class VineyardService { } getVineyards(): void { - this._vineyardCollection - .snapshotChanges() + combineLatest(this.getUserVineyards(), this.getSharedVineyards()) .pipe( - map((data: DocumentChangeAction[]) => - data.map((d: DocumentChangeAction) => ({ - ...d.payload.doc.data(), - id: (d.payload.doc as any).id, - })) - ), + map(([owned, shared]) => [...owned, ...shared]), map((docs: VineyardDoc[]) => docs.map((d: VineyardDoc) => ({ ...d, @@ -99,10 +96,37 @@ export class VineyardService { ) ) .subscribe((vineyards: Vineyard[]) => { + console.log('Vineyards', vineyards); this._vineyards$.next(vineyards); }); } + private getUserVineyards(): Observable { + return this._vineyardCollection.snapshotChanges().pipe( + map((data: DocumentChangeAction[]) => + data.map((d: DocumentChangeAction) => ({ + ...d.payload.doc.data(), + id: (d.payload.doc as any).id, + })) + ) + ); + } + + private getSharedVineyards(): Observable { + return this._sharedVineyardCollection.snapshotChanges().pipe( + map((data: DocumentChangeAction[]) => + data.map((d: DocumentChangeAction) => d.payload.doc.data()) + ), + switchMap((shared: SharedVineyardDoc[]) => + forkJoin( + shared.map((s: SharedVineyardDoc) => + this.fireStore.collection(`users/${s.user}/vineyards`).doc(s.vineyard).get() + ) + ) + ) + ); + } + getInfo(id: string): Vineyard { return this._vineyards$.getValue().find((v: Vineyard) => v.id === id); } From 448b35542bf4b2d9271fd4051bb1fbdbcfae7373 Mon Sep 17 00:00:00 2001 From: bramjanssen Date: Thu, 31 Aug 2023 20:17:18 +0200 Subject: [PATCH 02/29] feat: first setup of getting shared vineyards --- src/app/services/vineyard.service.ts | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/src/app/services/vineyard.service.ts b/src/app/services/vineyard.service.ts index ccad3f4..143717b 100644 --- a/src/app/services/vineyard.service.ts +++ b/src/app/services/vineyard.service.ts @@ -120,9 +120,22 @@ export class VineyardService { switchMap((shared: SharedVineyardDoc[]) => forkJoin( shared.map((s: SharedVineyardDoc) => - this.fireStore.collection(`users/${s.user}/vineyards`).doc(s.vineyard).get() + this.fireStore + .collection(`users/${s.user}/vineyards`) + .doc(s.vineyard) + .get() + .pipe( + map((doc) => ({ + ...doc.data(), + id: doc.id, + })), + catchError((error: any) => { + console.error(`Cannot open vineyard ${s.vineyard}`, error); + return of(undefined); + }) + ) ) - ) + ).pipe(map((docs) => docs.filter((d) => !!d))) ) ); } From 4213c3306964cff060aee0a667d3db9aaf4b1473 Mon Sep 17 00:00:00 2001 From: bramjanssen Date: Thu, 31 Aug 2023 20:18:43 +0200 Subject: [PATCH 03/29] chore: code cleanup --- src/app/services/vineyard.service.ts | 65 +--------------------------- 1 file changed, 1 insertion(+), 64 deletions(-) diff --git a/src/app/services/vineyard.service.ts b/src/app/services/vineyard.service.ts index 143717b..93c781b 100644 --- a/src/app/services/vineyard.service.ts +++ b/src/app/services/vineyard.service.ts @@ -1,13 +1,11 @@ import { Polygon } from 'ol/geom'; import { SharedVineyardDoc, VineyardDoc } from '../models/vineyarddoc.model'; -import { MeteoStatEntry, Vineyard } from '../models/vineyard.model'; -import { Variety } from '../models/variety.model'; +import { Vineyard } from '../models/vineyard.model'; import { UtilService } from './util.service'; import { Injectable } from '@angular/core'; import { HttpClient } from '@angular/common/http'; import { BehaviorSubject, combineLatest, forkJoin, Observable, of } from 'rxjs'; import { catchError, map, switchMap } from 'rxjs/operators'; -import { Action, ActionType } from '../models/action.model'; import { AngularFirestore, AngularFirestoreCollection, @@ -15,7 +13,6 @@ import { DocumentReference, } from '@angular/fire/firestore'; import { GeoJSON } from 'ol/format'; -import * as moment from 'moment'; import { AuthService } from './auth.service'; import { User } from 'firebase'; import { getCenter } from 'ol/extent'; @@ -35,8 +32,6 @@ export class VineyardService { private _sharedVineyardCollection: AngularFirestoreCollection; - private _API: string = 'https://us-central1-winery-f4d20.cloudfunctions.net'; - constructor( private http: HttpClient, private utilService: UtilService, @@ -62,10 +57,6 @@ export class VineyardService { return this._vineyards$; } - setActiveVineyard(id: string): void { - this._activeVineyard$.next(id ? this._vineyards$.getValue().find((v: Vineyard) => v.id === id) : null); - } - getActiveVineyard(): Observable { return this._activeVineyard$; } @@ -144,40 +135,6 @@ export class VineyardService { return this._vineyards$.getValue().find((v: Vineyard) => v.id === id); } - updateTempStats(v: Vineyard): Observable { - return this.http.get(`${this._API}/updateTemp?vineyardId=${v.id}`).pipe( - switchMap(() => of(true)), - catchError(() => of(false)) - ); - } - - getVarieties(info: Vineyard, year: number = new Date().getFullYear()): Variety[] { - const plantingActions = this.getActions(info, [ActionType.Planting], year); - return plantingActions.length > 0 - ? [] - .concat(...plantingActions.map((a: Action) => a.variety)) - .map((id: string) => info.varieties.find((v: Variety) => v.id === id)) - : []; - } - - getFirstPlanting(info: Vineyard): Action { - const plantingActions = this.getActions(info, [ActionType.Planting]); - return plantingActions.length > 0 ? plantingActions[0] : undefined; - } - - getLastPlanting(info: Vineyard): Action { - const plantingActions = this.getActions(info, [ActionType.Planting]); - return plantingActions.length > 0 ? plantingActions[plantingActions.length - 1] : undefined; - } - - getActions(info: Vineyard, types: ActionType[] = [], maxYear: number = new Date().getFullYear()): Action[] { - let actions = info ? info.actions : []; - if (types.length > 0) { - actions = actions.filter((a: Action) => types.indexOf(a.type) >= 0); - } - return actions.length > 0 ? actions.filter((a: Action) => a.date.year() <= maxYear) : actions; - } - updateLocation(id: string, geometry: Polygon): void { const polygons = this._vineyards$.getValue().map((v: Vineyard) => v.id === id @@ -206,14 +163,6 @@ export class VineyardService { .forEach((d: VineyardDoc) => this._vineyardCollection.doc(d.id).set(d)); } - updateVineyard(vineyard: Vineyard): void { - const vineyards: Vineyard[] = this._vineyards$ - .getValue() - .map((v: Vineyard) => (v.id === vineyard.id ? vineyard : v)); - this._vineyards$.next(vineyards); - this.saveVineyards([vineyard.id]); - } - async addVineyard(name: string, address: string, location: Polygon): Promise { const geoJSON = new GeoJSON({ dataProjection: 'EPSG:4326', @@ -237,18 +186,6 @@ export class VineyardService { this._vineyards$.next(vineyards); } - getMeteoYears(info: Vineyard): number[] { - return info && info.meteo && info.meteo.data - ? [...new Set(info.meteo.data.map((e: MeteoStatEntry) => moment(e.date).year()))] - : []; - } - - getMeteoByYears(info: Vineyard, years: number[]): MeteoStatEntry[] { - return info && info.meteo && info.meteo.data - ? info.meteo.data.filter((e: MeteoStatEntry) => years.indexOf(moment(e.date).year()) >= 0) - : []; - } - getLocation(vineyard: Vineyard): [number, number] { return getCenter(transformExtent(vineyard.location.getExtent(), 'EPSG:3857', 'EPSG:4326')); } From 16ffd31c62d2574efa3e84e4aafaf874c3993c4c Mon Sep 17 00:00:00 2001 From: bramjanssen Date: Fri, 1 Sep 2023 09:40:56 +0200 Subject: [PATCH 04/29] feat: added owner property to vineyard object --- src/app/map/map.page.ts | 1 + src/app/models/vineyard.model.ts | 7 ++++++- src/app/models/vineyarddoc.model.ts | 4 ++-- src/app/services/vineyard.service.ts | 2 ++ 4 files changed, 11 insertions(+), 3 deletions(-) diff --git a/src/app/map/map.page.ts b/src/app/map/map.page.ts index 70b44b3..bd1bb73 100644 --- a/src/app/map/map.page.ts +++ b/src/app/map/map.page.ts @@ -403,6 +403,7 @@ export class MapPage implements OnInit, AfterViewInit { geometry: v.location, name: v.id, title: v.name, + owner: v.owner, }) ) ); diff --git a/src/app/models/vineyard.model.ts b/src/app/models/vineyard.model.ts index 12acd45..69b96a1 100644 --- a/src/app/models/vineyard.model.ts +++ b/src/app/models/vineyard.model.ts @@ -19,7 +19,8 @@ export interface MeteoStatEntry { export interface MeteoStats { data: MeteoStatEntry[]; } -export interface Vineyard { + +export interface VineyardBase { id?: string; name: string; address?: string; @@ -28,3 +29,7 @@ export interface Vineyard { varieties: Variety[]; meteo: MeteoStats; } + +export interface Vineyard extends VineyardBase { + owner: boolean; +} diff --git a/src/app/models/vineyarddoc.model.ts b/src/app/models/vineyarddoc.model.ts index 5f6dceb..bc74f0c 100644 --- a/src/app/models/vineyarddoc.model.ts +++ b/src/app/models/vineyarddoc.model.ts @@ -1,6 +1,6 @@ -import { Vineyard } from './vineyard.model'; +import { VineyardBase } from './vineyard.model'; -export interface VineyardDoc extends Vineyard { +export interface VineyardDoc extends VineyardBase { location: string; } export interface SharedVineyardDoc { diff --git a/src/app/services/vineyard.service.ts b/src/app/services/vineyard.service.ts index 93c781b..c00013f 100644 --- a/src/app/services/vineyard.service.ts +++ b/src/app/services/vineyard.service.ts @@ -98,6 +98,7 @@ export class VineyardService { data.map((d: DocumentChangeAction) => ({ ...d.payload.doc.data(), id: (d.payload.doc as any).id, + owner: true, })) ) ); @@ -119,6 +120,7 @@ export class VineyardService { map((doc) => ({ ...doc.data(), id: doc.id, + owner: false, })), catchError((error: any) => { console.error(`Cannot open vineyard ${s.vineyard}`, error); From a21f23fbe2c0e1a09f8ac95a8e51d8a64b6a2182 Mon Sep 17 00:00:00 2001 From: bramjanssen Date: Fri, 1 Sep 2023 09:43:30 +0200 Subject: [PATCH 05/29] chore: code cleanup --- src/app/map/map.page.ts | 8 -------- 1 file changed, 8 deletions(-) diff --git a/src/app/map/map.page.ts b/src/app/map/map.page.ts index bd1bb73..f78791f 100644 --- a/src/app/map/map.page.ts +++ b/src/app/map/map.page.ts @@ -368,10 +368,6 @@ export class MapPage implements OnInit, AfterViewInit { } } - private _getSnapInteraction(): Snap { - return new Snap({ source: this._featureLayer.getSource() }); - } - private _getFeatureLayer(): VectorLayer { return new VectorLayer({ zIndex: 99, @@ -433,10 +429,6 @@ export class MapPage implements OnInit, AfterViewInit { }); } - logout() { - this.authService.logout(); - } - zoomToLocation(extent: number[]) { this.view.fit(buffer(transformExtent(extent, 'EPSG:4326', 'EPSG:3857'), 100)); } From 8c7946072b97e123f1f67fcd4b295d1fa12d1008 Mon Sep 17 00:00:00 2001 From: bramjanssen Date: Fri, 1 Sep 2023 10:01:54 +0200 Subject: [PATCH 06/29] feat: use a different color to indicate a shared field --- src/app/map/map.page.html | 11 +++++++++++ src/app/map/map.page.scss | 29 +++++++++++++++++++++++++++++ src/app/map/map.page.ts | 19 +++++++++++++++++++ 3 files changed, 59 insertions(+) diff --git a/src/app/map/map.page.html b/src/app/map/map.page.html index ec3287d..bc05528 100644 --- a/src/app/map/map.page.html +++ b/src/app/map/map.page.html @@ -5,6 +5,17 @@ +
+
+
+ Your vineyards +
+
+
+ Shared vineyards +
+
+
diff --git a/src/app/map/map.page.scss b/src/app/map/map.page.scss index 39875e4..66cafa7 100644 --- a/src/app/map/map.page.scss +++ b/src/app/map/map.page.scss @@ -79,4 +79,33 @@ ion-fab-button { margin-top: .5rem; +} + + +#legend { + position: absolute; + right: calc(1rem + 2px); + top: 10rem; + background-color: white; + padding: 1rem; + border-radius: var(--ion-border-radius); + font-size: smaller; + display: flex; + flex-direction: column; + + .legend-item { + display: flex; + align-items: center; + margin: 0.25rem 0; + + .color { + height: 24px; + width: 24px; + border-width: 2px; + border-style: solid; + margin-right: 1rem; + + } + + } } \ No newline at end of file diff --git a/src/app/map/map.page.ts b/src/app/map/map.page.ts index f78791f..2e8c903 100644 --- a/src/app/map/map.page.ts +++ b/src/app/map/map.page.ts @@ -33,6 +33,7 @@ import { ModalController } from '@ionic/angular'; import { AddVineyardComponent } from './addvineyard/addvineyard.component'; import { ConfirmComponent } from '../shared/components/confirm/confirm.component'; import { NON_PREMIUM_ROLES } from '../models/userdata.model'; +import { Fill, Stroke, Style } from 'ol/style'; @Component({ selector: 'app-map', @@ -368,12 +369,30 @@ export class MapPage implements OnInit, AfterViewInit { } } + private _getFeatureStyle(feature: Feature): Style { + const [fill, stroke] = this.getFeatureColors(feature.get('owner')); + return new Style({ + fill: new Fill({ + color: fill, + }), + stroke: new Stroke({ + color: stroke, + width: 2, + }), + }); + } + + public getFeatureColors(owner: boolean): [string, string] { + return owner ? ['rgba(95, 118, 232, 0.5)', 'rgb(86,107,210)'] : ['rgba(193,95,232,0.5)', 'rgb(175,86,210)']; + } + private _getFeatureLayer(): VectorLayer { return new VectorLayer({ zIndex: 99, source: new VectorSource({ features: [], }), + style: (feature) => this._getFeatureStyle(feature), }); } From b540457ee139dd44501a62c77e6992a6db64a42c Mon Sep 17 00:00:00 2001 From: bramjanssen Date: Fri, 1 Sep 2023 10:08:53 +0200 Subject: [PATCH 07/29] feat: add visual indicator to popup that field is shared --- src/app/map/map.page.html | 6 +- src/app/map/map.page.scss | 156 +++++++++++++++++++++----------------- 2 files changed, 91 insertions(+), 71 deletions(-) diff --git a/src/app/map/map.page.html b/src/app/map/map.page.html index bc05528..ab31a1c 100644 --- a/src/app/map/map.page.html +++ b/src/app/map/map.page.html @@ -21,9 +21,11 @@