User positions
This commit is contained in:
		@@ -11,7 +11,6 @@ export const Adjectives = [
 | 
			
		||||
    'complete',
 | 
			
		||||
    'offbeat',
 | 
			
		||||
    'selective',
 | 
			
		||||
    'unknown',
 | 
			
		||||
    'unwilling',
 | 
			
		||||
    'lively',
 | 
			
		||||
    'nebulous',
 | 
			
		||||
 
 | 
			
		||||
@@ -5,7 +5,7 @@ export interface LatLng {
 | 
			
		||||
 | 
			
		||||
export interface MapData {
 | 
			
		||||
    circles?: Circle[];
 | 
			
		||||
    locations?: Marker[];
 | 
			
		||||
    locations?: {[key: string]: Marker};
 | 
			
		||||
    markers?: Marker[];
 | 
			
		||||
    measurements?: Measurement[];
 | 
			
		||||
    polygons?: Polygon[];
 | 
			
		||||
@@ -16,6 +16,7 @@ export interface MapData {
 | 
			
		||||
export interface MapSymbol {
 | 
			
		||||
    symbol?: any;
 | 
			
		||||
    latlng?: LatLng | LatLng[];
 | 
			
		||||
    new?: boolean;
 | 
			
		||||
    noDelete?: boolean;
 | 
			
		||||
    noDeleteTool?: boolean;
 | 
			
		||||
    noSelect?: boolean;
 | 
			
		||||
 
 | 
			
		||||
@@ -1,129 +1,157 @@
 | 
			
		||||
import {Injectable} from "@angular/core";
 | 
			
		||||
import {AngularFirestore, AngularFirestoreCollection} from "@angular/fire/firestore";
 | 
			
		||||
import {BehaviorSubject, Subscription} from "rxjs";
 | 
			
		||||
import {AngularFirestore, AngularFirestoreDocument} from "@angular/fire/firestore";
 | 
			
		||||
import {BehaviorSubject, combineLatest, Subscription} from "rxjs";
 | 
			
		||||
import {Circle, MapData, MapSymbol, Marker, Measurement, Polygon, Polyline, Rectangle} from "../models/mapSymbol";
 | 
			
		||||
import * as _ from 'lodash';
 | 
			
		||||
import {map} from "rxjs/operators";
 | 
			
		||||
 | 
			
		||||
export const LOCATION_COLLECTION = 'Users';
 | 
			
		||||
export const MAP_COLLECTION = 'Maps';
 | 
			
		||||
 | 
			
		||||
@Injectable({
 | 
			
		||||
    providedIn: 'root'
 | 
			
		||||
})
 | 
			
		||||
export class SyncService {
 | 
			
		||||
    private code: string;
 | 
			
		||||
    private changed = false;
 | 
			
		||||
    private collection: AngularFirestoreCollection;
 | 
			
		||||
    private
 | 
			
		||||
 | 
			
		||||
    private location;
 | 
			
		||||
    private locationChanged = false;
 | 
			
		||||
    private locationDoc: AngularFirestoreDocument;
 | 
			
		||||
    private mapDoc: AngularFirestoreDocument;
 | 
			
		||||
    private mapChanged = false;
 | 
			
		||||
    private mapSub: Subscription;
 | 
			
		||||
    private name: string;
 | 
			
		||||
    private saveInterval;
 | 
			
		||||
    private saveInterval: number;
 | 
			
		||||
 | 
			
		||||
    mapSymbols = new BehaviorSubject<MapData>({});
 | 
			
		||||
    mapData = new BehaviorSubject<MapData>({});
 | 
			
		||||
 | 
			
		||||
    constructor(private db: AngularFirestore) {
 | 
			
		||||
        this.collection = this.db.collection('Maps');
 | 
			
		||||
    constructor(private db: AngularFirestore) { }
 | 
			
		||||
 | 
			
		||||
    private addMapSymbol(s: MapSymbol, key: string) {
 | 
			
		||||
        s.new = true;
 | 
			
		||||
        let map = this.mapData.value;
 | 
			
		||||
        if(!map[key]) map[key] = [];
 | 
			
		||||
        map[key].push(s);
 | 
			
		||||
        this.mapData.next(map);
 | 
			
		||||
        this.mapChanged = true;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    async exists(mapCode: string) {
 | 
			
		||||
        return (await this.collection.doc(mapCode).ref.get()).exists;
 | 
			
		||||
        return (await this.db.collection(MAP_COLLECTION).doc(mapCode).ref.get()).exists;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    addCircle(circle: Circle) {
 | 
			
		||||
        let map = this.mapSymbols.value;
 | 
			
		||||
        if(!map.circles) map.circles = [];
 | 
			
		||||
        map.circles.push(circle);
 | 
			
		||||
        this.mapSymbols.next(map);
 | 
			
		||||
        this.changed = true;
 | 
			
		||||
        this.addMapSymbol(circle, 'circles');
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    addMarker(marker: Marker) {
 | 
			
		||||
        let map = this.mapSymbols.value;
 | 
			
		||||
        if(!map.markers) map.markers = [];
 | 
			
		||||
        map.markers.push(marker);
 | 
			
		||||
        this.mapSymbols.next(map);
 | 
			
		||||
        this.changed = true;
 | 
			
		||||
        this.addMapSymbol(marker, 'markers');
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    addMeasurement(measurement: Measurement) {
 | 
			
		||||
        let map = this.mapSymbols.value;
 | 
			
		||||
        if(!map.measurements) map.measurements = [];
 | 
			
		||||
        map.measurements.push(measurement);
 | 
			
		||||
        this.mapSymbols.next(map);
 | 
			
		||||
        this.changed = true;
 | 
			
		||||
        this.addMapSymbol(measurement, 'measurements');
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    addMyLocation(location: Marker) {
 | 
			
		||||
        let map = this.mapSymbols.value;
 | 
			
		||||
        if(!map.locations) map.locations = [];
 | 
			
		||||
        let markForSave = this.name == null;
 | 
			
		||||
        this.name = location.label;
 | 
			
		||||
        map.locations = map.locations.filter(l => l.label != location.label);
 | 
			
		||||
        map.locations.push(location);
 | 
			
		||||
        this.mapSymbols.next(map);
 | 
			
		||||
        this.changed = true;
 | 
			
		||||
        if(markForSave) this.save();
 | 
			
		||||
        let markForSave = this.location == null;
 | 
			
		||||
        if(!this.locationChanged) this.locationChanged = !_.isEqual(this.location, location);
 | 
			
		||||
        if(this.locationChanged) this.location = location;
 | 
			
		||||
        if(markForSave) this.save(true);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    addPolygon(polygon: Polygon) {
 | 
			
		||||
        let map = this.mapSymbols.value;
 | 
			
		||||
        if(!map.polygons) map.polygons = [];
 | 
			
		||||
        map.polygons.push(polygon);
 | 
			
		||||
        this.mapSymbols.next(map);
 | 
			
		||||
        this.changed = true;
 | 
			
		||||
        this.addMapSymbol(polygon, 'polygons');
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    addPolyline(polyline: Polyline) {
 | 
			
		||||
        let map = this.mapSymbols.value;
 | 
			
		||||
        if(!map.polylines) map.polylines = [];
 | 
			
		||||
        map.polylines.push(polyline);
 | 
			
		||||
        this.mapSymbols.next(map);
 | 
			
		||||
        this.changed = true;
 | 
			
		||||
        this.addMapSymbol(polyline, 'polylines')
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    addRectangle(rect: Rectangle) {
 | 
			
		||||
        let map = this.mapSymbols.value;
 | 
			
		||||
        if(!map.rectangles) map.rectangles = [];
 | 
			
		||||
        map.rectangles.push(rect);
 | 
			
		||||
        this.mapSymbols.next(map);
 | 
			
		||||
        this.changed = true;
 | 
			
		||||
        this.addMapSymbol(rect, 'rectangles')
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    delete(...symbols) {
 | 
			
		||||
        let map = this.mapSymbols.value;
 | 
			
		||||
        if(map.circles) symbols.forEach(s => map.circles = map.circles.filter(c => !_.isEqual(s, c)));
 | 
			
		||||
        if(map.locations) symbols.forEach(s => map.locations = map.locations.filter(m => !_.isEqual(s, m)));
 | 
			
		||||
        if(map.markers) symbols.forEach(s => map.markers = map.markers.filter(m => !_.isEqual(s, m)));
 | 
			
		||||
        if(map.measurements) symbols.forEach(s => map.measurements = map.measurements.filter(m => !_.isEqual(s, m)));
 | 
			
		||||
        if(map.polygons) symbols.forEach(s => map.polygons = map.polygons.filter(p => !_.isEqual(s , p)));
 | 
			
		||||
        if(map.polylines) symbols.forEach(s => map.polylines = map.polylines.filter(p => !_.isEqual(s, p)));
 | 
			
		||||
        if(map.rectangles) symbols.forEach(s => map.rectangles = map.rectangles.filter(r => !_.isEqual(s, r)));
 | 
			
		||||
        this.mapSymbols.next(map);
 | 
			
		||||
        this.changed = true;
 | 
			
		||||
        let map = this.mapData.value;
 | 
			
		||||
        [map.circles, map.markers, map.measurements, map.polygons, map.polylines, map.rectangles]
 | 
			
		||||
            .forEach((storage: MapSymbol[]) => symbols.forEach(s => storage = storage.filter(ss => !_.isEqual(s, ss))));
 | 
			
		||||
        this.mapData.next(map);
 | 
			
		||||
        this.mapChanged = true;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    load(mapCode: string) {
 | 
			
		||||
    load(mapCode: string, username: string) {
 | 
			
		||||
        this.unload();
 | 
			
		||||
        this.mapDoc = this.db.collection(MAP_COLLECTION).doc(mapCode);
 | 
			
		||||
        this.locationDoc = this.mapDoc.collection(LOCATION_COLLECTION).doc(username);
 | 
			
		||||
 | 
			
		||||
        this.mapSub = combineLatest(this.mapDoc.valueChanges(), this.mapDoc.collection(LOCATION_COLLECTION).snapshotChanges())
 | 
			
		||||
            .pipe(map(data => {
 | 
			
		||||
                let newMap = data[0];
 | 
			
		||||
                let oldMap = this.mapData.value;
 | 
			
		||||
                let mergedMap = this.mergeMaps(newMap, oldMap);
 | 
			
		||||
 | 
			
		||||
                let locations = data[1].map(doc => ({id: doc.payload.doc.id, data: <Marker>doc.payload.doc.data()}));
 | 
			
		||||
                locations.filter(l => l.id != username).forEach(l => {
 | 
			
		||||
                    if(!mergedMap.locations) mergedMap.locations = {};
 | 
			
		||||
                    mergedMap.locations[l.id] = l.data;
 | 
			
		||||
                });
 | 
			
		||||
 | 
			
		||||
                return mergedMap;
 | 
			
		||||
            })).subscribe((mapData: MapData) => {
 | 
			
		||||
                this.mapData.next(mapData);
 | 
			
		||||
                if(this.saveInterval) clearInterval(this.saveInterval);
 | 
			
		||||
                this.saveInterval = setInterval(() => this.save(), (mapData.locations && Object.keys(mapData.locations).length > 0) ? 5_000 : 30_000)
 | 
			
		||||
            });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    mergeMaps(newMap: MapData, oldMap: MapData) {
 | 
			
		||||
        let map = Object.assign({}, newMap);
 | 
			
		||||
        Object.keys(oldMap).forEach(key => {
 | 
			
		||||
            if(Array.isArray(map[key])) {
 | 
			
		||||
                if(!map[key]) map[key] = [];
 | 
			
		||||
                oldMap[key].filter(s => !_.find(map[key], s) && s.new).forEach(s => map[key].push(s));
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
        if(!map.locations) map.locations = {};
 | 
			
		||||
        return map;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    removeMyLocation() {
 | 
			
		||||
        this.location = null;
 | 
			
		||||
        return this.locationDoc.delete();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    save(locationOnly?) {
 | 
			
		||||
        if(this.locationDoc && this.locationChanged) {
 | 
			
		||||
            let ignore = this.locationDoc.set(this.location);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if(!locationOnly && this.mapDoc && this.mapChanged) {
 | 
			
		||||
            let map = this.mapData.value;
 | 
			
		||||
            Object.values(map).forEach(val => val.filter(s => s.new).forEach(s => delete s.new));
 | 
			
		||||
            delete map.locations;
 | 
			
		||||
            let ignore = this.mapDoc.set(map);
 | 
			
		||||
            this.mapChanged = false;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    async unload() {
 | 
			
		||||
        if(this.saveInterval) clearInterval(this.saveInterval);
 | 
			
		||||
        this.mapData.next({});
 | 
			
		||||
 | 
			
		||||
        if(this.mapSub) {
 | 
			
		||||
            this.mapSub.unsubscribe();
 | 
			
		||||
            this.mapSub = null;
 | 
			
		||||
        }
 | 
			
		||||
        this.code = mapCode;
 | 
			
		||||
        this.mapSub = this.collection.doc(this.code).valueChanges().subscribe((newMap: MapData) => {
 | 
			
		||||
            this.mapSymbols.next(Object.assign({}, newMap)); // TODO: Add merge operation so pending changes arn't lost
 | 
			
		||||
            this.changed = false;
 | 
			
		||||
            if(this.saveInterval) clearInterval(this.saveInterval);
 | 
			
		||||
            this.saveInterval = setInterval(() => this.save(), (newMap.locations && newMap.locations.length > 1) ? 2_000 : 30_000)
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    removeMyLocation() {
 | 
			
		||||
        let map = this.mapSymbols.value;
 | 
			
		||||
        if(map.locations) map.locations = map.locations.filter(l => l.label != this.name);
 | 
			
		||||
        this.name = null;
 | 
			
		||||
        this.changed = true;
 | 
			
		||||
        this.save();
 | 
			
		||||
    }
 | 
			
		||||
        if(this.mapDoc) {
 | 
			
		||||
            this.mapDoc = null;
 | 
			
		||||
            this.mapChanged = false;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
    save() {
 | 
			
		||||
        if(this.code && this.mapSymbols.value && this.changed) {
 | 
			
		||||
            this.collection.doc(this.code).set(this.mapSymbols.value);
 | 
			
		||||
            this.changed = false;
 | 
			
		||||
        if(this.locationDoc) {
 | 
			
		||||
            this.locationChanged = false;
 | 
			
		||||
            await this.removeMyLocation();
 | 
			
		||||
            this.locationDoc = null;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -18,7 +18,7 @@ export class HomeComponent {
 | 
			
		||||
    async new() {
 | 
			
		||||
        let mapCode: string;
 | 
			
		||||
        do {
 | 
			
		||||
            mapCode = Array(8).fill(0).map(() => chars[Math.round(Math.random() * chars.length)]).join('');
 | 
			
		||||
            mapCode = Array(8).fill(0).map(() => chars[Math.floor(Math.random() * chars.length)]).join('');
 | 
			
		||||
        } while (await this.syncService.exists(mapCode));
 | 
			
		||||
        return this.router.navigate(['/', mapCode]);
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
@@ -1,4 +1,4 @@
 | 
			
		||||
import {Component, HostListener, isDevMode, OnDestroy, OnInit} from "@angular/core";
 | 
			
		||||
import {Component, isDevMode, OnDestroy, OnInit} from "@angular/core";
 | 
			
		||||
import {PhysicsService} from "../../services/physics.service";
 | 
			
		||||
import {filter, finalize, skip, take} from "rxjs/operators";
 | 
			
		||||
import {MatBottomSheet, MatSnackBar} from "@angular/material";
 | 
			
		||||
@@ -195,26 +195,26 @@ export class MapComponent implements OnDestroy, OnInit {
 | 
			
		||||
    ];
 | 
			
		||||
 | 
			
		||||
    constructor(public physicsService: PhysicsService, private syncService: SyncService, private snackBar: MatSnackBar, private bottomSheet: MatBottomSheet, private dialog: MatDialog, private route: ActivatedRoute) {
 | 
			
		||||
        this.name = Adjectives[Math.floor(Math.random() * Adjectives.length)] + ' ' + Nouns[Math.floor(Math.random() * Nouns.length)];
 | 
			
		||||
        (window.onbeforeunload as any) = this.ngOnDestroy();
 | 
			
		||||
        this.route.params.subscribe(params => {
 | 
			
		||||
            this.code = params['code'];
 | 
			
		||||
            this.syncService.load(this.code);
 | 
			
		||||
            this.syncService.load(this.code, this.name);
 | 
			
		||||
        })
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @HostListener('window:beforeunload', ['$event'])
 | 
			
		||||
    ngOnDestroy(): void {
 | 
			
		||||
        this.syncService.removeMyLocation();
 | 
			
		||||
        let ignore = this.syncService.unload();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    ngOnInit() {
 | 
			
		||||
        this.name = Adjectives[Math.round(Math.random() * Adjectives.length)] + ' ' + Nouns[Math.round(Math.random() * Nouns.length)];
 | 
			
		||||
        this.map = new MapService('map');
 | 
			
		||||
 | 
			
		||||
        // Handle drawing the map after updates
 | 
			
		||||
        this.syncService.mapSymbols.pipe(filter(s => !!s)).subscribe((map: MapData) => {
 | 
			
		||||
        this.syncService.mapData.pipe(filter(s => !!s)).subscribe((map: MapData) => {
 | 
			
		||||
            this.map.deleteAll();
 | 
			
		||||
            if (map.circles) map.circles.forEach(c => this.map.newCircle(c));
 | 
			
		||||
            if (map.locations) map.locations.filter(l => l.label != this.name).forEach(l => this.map.newMarker(l));
 | 
			
		||||
            if (map.locations) Object.values(map.locations).forEach(l => this.map.newMarker(l));
 | 
			
		||||
            if (map.markers) map.markers.forEach(m => this.map.newMarker(m));
 | 
			
		||||
            if (map.measurements) map.measurements.forEach(m => this.map.newMeasurement(m));
 | 
			
		||||
            if (map.polygons) map.polygons.forEach(p => this.map.newPolygon(p));
 | 
			
		||||
@@ -237,8 +237,6 @@ export class MapComponent implements OnDestroy, OnInit {
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
        // Display location
 | 
			
		||||
        this.physicsService.info.pipe(filter(coord => !!coord)).subscribe(pos => {
 | 
			
		||||
            if (!this.position) this.center({lat: pos.latitude, lng: pos.longitude});
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user