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