Compare commits

..

No commits in common. "develop" and "1.7.8" have entirely different histories.

50 changed files with 1243 additions and 21946 deletions

View File

@ -1,16 +0,0 @@
# Editor configuration, see https://editorconfig.org
root = true
[*]
charset = utf-8
indent_style = tab
indent_size = 4
insert_final_newline = true
trim_trailing_whitespace = true
[*.ts]
quote_type = single
[*.md]
max_line_length = off
trim_trailing_whitespace = false

View File

@ -2,24 +2,23 @@ name: Build Functions
run-name: Build Functions run-name: Build Functions
on: on:
push: push:
paths: paths:
- 'firebase/**' - 'functions/**'
- 'functions/**'
jobs: jobs:
build: build:
name: Build NPM Project name: Build NPM Project
runs-on: ubuntu-latest runs-on: ubuntu-latest
container: node container: node
steps: steps:
- name: Clone Repository - name: Clone Repository
uses: ztimson/actions/clone@develop uses: ztimson/actions/clone@develop
- name: Install Dependencies - name: Install Dependencies
run: npm i run: npm i
working-directory: functions working-directory: functions
- name: Build Project - name: Build Project
run: npm run build run: npm run build
working-directory: functions working-directory: functions

View File

@ -2,55 +2,46 @@ name: Build Website
run-name: Build Website run-name: Build Website
on: on:
push: push:
paths-ignore: paths-ignore:
- 'firebase/**' - 'functions/**'
- 'functions/**'
jobs: jobs:
build: build:
name: Build NPM Project name: Build NPM Project
runs-on: ubuntu-latest runs-on: ubuntu-latest
container: node container: node
steps: steps:
- name: Clone Repository - name: Clone Repository
uses: ztimson/actions/clone@develop uses: ztimson/actions/clone@develop
- name: Install Dependencies - name: Install Dependencies
run: npm i run: npm i
- name: Build Project - name: Build Project
run: npm run build run: npm run build
- name: Upload Artifacts - name: Upload Artifacts
uses: actions/upload-artifact@v3 if: ${{inputs.artifacts}} != "false"
with: uses: actions/upload-artifact@v3
name: website
path: dist
retention-days: 7
tag:
name: Tag Version
needs: build
runs-on: ubuntu-latest
container: node
steps:
- name: Clone Repository
uses: ztimson/actions/clone@develop
- name: Get Version Number
run: echo "VERSION=$(cat package.json | grep version | grep -Eo ':.+' | grep -Eo '[[:alnum:]\.\/\-]+')" >> $GITHUB_ENV
- name: Tag Version
uses: ztimson/actions/tag@develop
with:
tag: ${{env.VERSION}}
publish:
name: Build & Push Dockerfile
needs: build
uses: ztimson/actions/.github/workflows/docker.yaml@develop
with: with:
name: ztimson/map-alliance name: website
repository: ${{github.server_url}}/${{github.repository}}.git path: dist
pass: ${{secrets.DEPLOY_TOKEN}} retention-days: 7
tag:
name: Tag Version
needs: build
runs-on: ubuntu-latest
container: node
steps:
- name: Clone Repository
uses: ztimson/actions/clone@develop
- name: Get Version Number
run: echo "VERSION=$(cat package.json | grep version | grep -Eo ':.+' | grep -Eo '[[:alnum:]\.\/\-]+')" >> $GITHUB_ENV
- name: Tag Version
uses: ztimson/actions/tag@develop
with:
tag: ${{env.VERSION}}

View File

@ -1,22 +0,0 @@
FROM node as build
# Variables
ARG NODE_ENV=prod
ARG NODE_OPTIONS="--max_old_space_size=4096"
ENV NG_CLI_ANALYTICS=ci \
NODE_ENV=${NODE_ENV} \
NODE_OPTIONS=${NODE_OPTIONS}
# Setup
WORKDIR /app
COPY . .
# Install & build
RUN if [ ! -d "dist" ]; then npm ci && npm run build; fi
# Use Nginx to serve
FROM nginx:1.20-alpine
COPY --from=build /app/dist/browser /usr/share/nginx/html
COPY docker/robots.txt /usr/share/nginx/html/robots.txt
COPY docker/nginx.conf /etc/nginx/nginx.conf
EXPOSE 80

View File

@ -34,7 +34,6 @@
- [Demo](#demo) - [Demo](#demo)
- [Built With](#built-with) - [Built With](#built-with)
- [Setup](#setup) - [Setup](#setup)
- [Production](#production)
- [Development](#development) - [Development](#development)
- [License](#license) - [License](#license)
@ -53,27 +52,11 @@ Website: https://maps.zakscode.com
### Built With ### Built With
[![Angular](https://img.shields.io/badge/Angular-DD0031?style=for-the-badge&logo=angular)](https://angular.io/) [![Angular](https://img.shields.io/badge/Angular-DD0031?style=for-the-badge&logo=angular)](https://angular.io/)
[![Docker](https://img.shields.io/badge/Docker-384d54?style=for-the-badge&logo=docker)](https://docker.com/)
[![Firebase](https://img.shields.io/badge/Firebase-FFFFFF?style=for-the-badge&logo=firebase)](https://firebase.google.com/) [![Firebase](https://img.shields.io/badge/Firebase-FFFFFF?style=for-the-badge&logo=firebase)](https://firebase.google.com/)
[![TypeScript](https://img.shields.io/badge/TypeScript-3178C6?style=for-the-badge&logo=typescript&logoColor=white)](https://typescriptlang.org/) [![TypeScript](https://img.shields.io/badge/TypeScript-3178C6?style=for-the-badge&logo=typescript&logoColor=white)](https://typescriptlang.org/)
## Setup ## Setup
<details>
<summary>
<h3 id="production" style="display: inline">
Production
</h3>
</summary>
#### Prerequisites
- [Docker](https://docs.docker.com/install/)
#### Instructions
1. Run the docker image: `docker run -p 80:80 ztimson/map-alliance`
2. Open http://localhost
</details>
<details> <details>
<summary> <summary>
<h3 id="development" style="display: inline"> <h3 id="development" style="display: inline">
@ -87,7 +70,7 @@ Website: https://maps.zakscode.com
#### Instructions #### Instructions
1. Install the dependencies: `npm install` 1. Install the dependencies: `npm install`
2. Start the Angular server: `npm run start` 2. Start the Angular server: `npm run start`
3. Open http://localhost:4200 3. Open [http://localhost:4200](http://localhost:4200)
</details> </details>

View File

@ -1,87 +1,91 @@
{ {
"$schema": "./node_modules/@angular/cli/lib/config/schema.json", "$schema": "./node_modules/@angular/cli/lib/config/schema.json",
"version": 1, "version": 1,
"newProjectRoot": "projects", "newProjectRoot": "projects",
"projects": { "projects": {
"proj": { "MapAlliance": {
"projectType": "application", "projectType": "application",
"schematics": { "schematics": {
"@schematics/angular:component": { "@schematics/angular:component": {
"style": "scss" "style": "scss"
} }
}, },
"root": "", "root": "",
"sourceRoot": "src", "sourceRoot": "src",
"prefix": "app", "prefix": "app",
"architect": { "architect": {
"build": { "build": {
"builder": "@angular-devkit/build-angular:application", "builder": "@angular-devkit/build-angular:browser",
"options": { "options": {
"outputPath": "dist", "outputPath": "dist/MapAlliance",
"index": "src/index.html", "index": "src/index.html",
"browser": "src/main.ts", "main": "src/main.ts",
"polyfills": ["zone.js"], "polyfills": "src/polyfills.ts",
"tsConfig": "tsconfig.json", "tsConfig": "tsconfig.json",
"inlineStyleLanguage": "scss", "aot": false,
"assets": [ "assets": [
"src/assets", "src/assets",
"src/manifest.json" "src/manifest.webmanifest"
], ],
"styles": [ "styles": [
"./node_modules/bootstrap-scss/bootstrap.scss", "./node_modules/bootstrap-scss/bootstrap.scss",
"./node_modules/leaflet/dist/leaflet.css", "./node_modules/leaflet/dist/leaflet.css",
"src/styles.scss" "src/styles.scss"
], ],
"scripts": [ "scripts": [
"./node_modules/leaflet/dist/leaflet.js", "./node_modules/leaflet/dist/leaflet.js",
"./node_modules/leaflet-polylinedecorator/dist/leaflet.polylineDecorator.js", "./node_modules/leaflet-polylinedecorator/dist/leaflet.polylineDecorator.js",
"./node_modules/leaflet-openweathermap/leaflet-openweathermap.js", "./node_modules/leaflet-openweathermap/leaflet-openweathermap.js",
"./node_modules/leaflet-rotatedmarker/leaflet.rotatedMarker.js", "./node_modules/leaflet-rotatedmarker/leaflet.rotatedMarker.js",
"./node_modules/leaflet-bing-layer/leaflet-bing-layer.js", "./node_modules/leaflet-bing-layer/leaflet-bing-layer.js",
"./node_modules/leaflet.gridlayer.googlemutant/Leaflet.GoogleMutant.js", "./node_modules/leaflet.gridlayer.googlemutant/Leaflet.GoogleMutant.js",
"./node_modules/esri-leaflet/dist/esri-leaflet.js", "./node_modules/esri-leaflet/dist/esri-leaflet.js",
"./src/assets/js/leaflet.Editable.js", "./src/assets/js/leaflet.Editable.js",
"./node_modules/jquery/dist/jquery.js" "./node_modules/jquery/dist/jquery.js"
] ]
}, },
"configurations": { "configurations": {
"production": { "production": {
"budgets": [ "fileReplacements": [
{ {
"type": "initial", "replace": "src/environments/environment.ts",
"maximumWarning": "2mb", "with": "src/environments/environment.prod.ts"
"maximumError": "5mb" }
}, ],
{ "optimization": true,
"type": "anyComponentStyle", "outputHashing": "all",
"maximumWarning": "250kb", "sourceMap": false,
"maximumError": "1mb" "extractCss": true,
} "namedChunks": false,
], "aot": true,
"outputHashing": "all", "extractLicenses": true,
"serviceWorker": "ngsw-config.json" "vendorChunk": false,
}, "buildOptimizer": true,
"development": { "budgets": [
"optimization": false, {
"extractLicenses": false, "type": "initial",
"sourceMap": true "maximumWarning": "2mb",
} "maximumError": "5mb"
}, }
"defaultConfiguration": "production" ],
}, "serviceWorker": true,
"serve": { "ngswConfigPath": "ngsw-config.json"
"builder": "@angular-devkit/build-angular:dev-server", }
"configurations": { }
"production": { },
"buildTarget": "proj:build:production" "serve": {
}, "builder": "@angular-devkit/build-angular:dev-server",
"development": { "options": {
"buildTarget": "proj:build:development" "browserTarget": "MapAlliance:build"
} },
}, "configurations": {
"defaultConfiguration": "development" "production": {
} "browserTarget": "MapAlliance:build:production"
} }
} }
} }
}
}
},
"defaultProject": "MapAlliance"
} }

12
browserslist Normal file
View File

@ -0,0 +1,12 @@
# This file is used by the build system to adjust CSS and JS output to support the specified browsers below.
# For additional information regarding the format and rule options, please see:
# https://github.com/browserslist/browserslist#queries
# You can see what browsers were selected by your queries by running:
# npx browserslist
> 0.5%
last 2 versions
Firefox ESR
not dead
not IE 9-11 # For IE 9-11 support, remove 'not'.

View File

@ -1,31 +0,0 @@
worker_processes auto;
pid /var/run/nginx.pid;
events {
worker_connections 1024;
}
http {
include mime.types;
default_type application/octet-stream;
gzip on;
gzip_proxied any;
gzip_types text/plain text/css application/xml application/xhtml+xml application/rss+xml application/javascript application/x-javascript application/json application/x-font-woff;
gzip_vary on;
gzip_disable "MSIE [1-6]\.(?!.*SV1)";
sendfile off;
keepalive_timeout 65;
server {
listen 80;
index index.html;
root /usr/share/nginx/html;
autoindex off;
location / {
try_files $uri $uri/ /index.html;
}
}
}

View File

@ -1,2 +0,0 @@
User-Agent: *
Allow: /

View File

@ -1,10 +1,10 @@
{ {
"hosting": { "hosting": {
"public": "dist/browser", "public": "dist/MapAlliance",
"ignore": [ "ignore": [
".*",
"firebase.json", "firebase.json",
"node_modules" "**/.*",
"**/node_modules/**"
], ],
"rewrites": [ "rewrites": [
{ {
@ -14,11 +14,12 @@
] ]
}, },
"firestore": { "firestore": {
"rules": "firebase/firestore.rules", "rules": "firestore.rules",
"indexes": "firebase/firestore.indexes.json" "indexes": "firestore.indexes.json"
}, },
"functions": { "functions": {
"predeploy": [ "predeploy": [
"npm --prefix \"$RESOURCE_DIR\" run lint",
"npm --prefix \"$RESOURCE_DIR\" run build" "npm --prefix \"$RESOURCE_DIR\" run build"
] ]
} }

View File

@ -7,21 +7,20 @@
"installMode": "prefetch", "installMode": "prefetch",
"resources": { "resources": {
"files": [ "files": [
"/assets/images/logo.png", "/favicon.ico",
"/index.html", "/index.html",
"/manifest.json",
"/*.css", "/*.css",
"/*.js" "/*.js"
] ]
} }
}, }, {
{
"name": "assets", "name": "assets",
"installMode": "lazy", "installMode": "lazy",
"updateMode": "prefetch", "updateMode": "prefetch",
"resources": { "resources": {
"files": [ "files": [
"/assets/**" "/assets/**",
"/*.(eot|svg|cur|jpg|png|webp|gif|otf|ttf|woff|woff2|ani)"
] ]
} }
} }

20535
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,51 +1,64 @@
{ {
"name": "map-alliance", "name": "map-alliance",
"version": "2.0.0", "version": "1.7.8",
"scripts": { "scripts": {
"start": "ng serve", "ng": "ng",
"build": "ng build", "start": "ng serve --host 0.0.0.0",
"watch": "ng build --watch --configuration development", "build": "ng build",
"deploy": "firebase deploy --only hosting --token \"${FIREBASE_TOKEN}\"" "test": "ng test",
}, "lint": "ng lint",
"private": true, "e2e": "ng e2e",
"dependencies": { "deploy": "firebase deploy --only hosting --token \"${FIREBASE_TOKEN}\""
"@angular/animations": "^17.0.0", },
"@angular/cdk": "^17.0.0", "private": true,
"@angular/common": "^17.0.0", "dependencies": {
"@angular/compiler": "^17.0.0", "@angular/animations": "~9.1.11",
"@angular/core": "^17.0.0", "@angular/cdk": "^9.2.4",
"@angular/fire": "^17.0.0", "@angular/common": "~9.1.11",
"@angular/forms": "^17.0.0", "@angular/compiler": "~9.1.11",
"@angular/material": "^17.0.0", "@angular/core": "~9.1.11",
"@angular/platform-browser": "^17.0.0", "@angular/fire": "^5.2.1",
"@angular/platform-browser-dynamic": "^17.0.0", "@angular/forms": "~9.1.11",
"@angular/pwa": "^17.0.0", "@angular/material": "^9.2.4",
"@angular/router": "^17.0.0", "@angular/platform-browser": "~9.1.11",
"@angular/service-worker": "^17.0.0", "@angular/platform-browser-dynamic": "~9.1.11",
"bootstrap-scss": "^4.3.1", "@angular/pwa": "^0.800.4",
"classlist.js": "^1.1.20150312", "@angular/router": "~9.1.11",
"esri-leaflet": "^2.3.0", "@angular/service-worker": "~9.1.11",
"firebase": "^10.7.0", "@types/leaflet": "^1.5.1",
"hammerjs": "^2.0.8", "@types/lodash": "^4.14.138",
"jquery": "^3.4.1", "bootstrap-scss": "^4.3.1",
"leaflet": "^1.5.1", "classlist.js": "^1.1.20150312",
"leaflet-bing-layer": "^3.3.1", "esri-leaflet": "^2.3.0",
"leaflet-openweathermap": "^1.0.0", "firebase": "^6.3.0",
"leaflet-polylinedecorator": "^1.6.0", "hammerjs": "^2.0.8",
"leaflet-rotatedmarker": "^0.2.0", "jquery": "^3.4.1",
"leaflet.gridlayer.googlemutant": "^0.8.0", "leaflet": "^1.5.1",
"ng-click-outside": "^9.0.0", "leaflet-bing-layer": "^3.3.1",
"ngx-color-picker": "^16.0.0", "leaflet-openweathermap": "^1.0.0",
"rxjs": "~7.8.0", "leaflet-polylinedecorator": "^1.6.0",
"tslib": "^2.3.0", "leaflet-rotatedmarker": "^0.2.0",
"zone.js": "~0.14.2" "leaflet.gridlayer.googlemutant": "^0.8.0",
}, "lodash": "^4.17.15",
"devDependencies": { "momentjs": "^2.0.0",
"@angular-devkit/build-angular": "^17.0.6", "ng-click-outside": "^5.0.0",
"@angular/cli": "^17.0.0", "ngx-color-picker": "^8.1.0",
"@angular/compiler-cli": "^17.0.0", "rxjs": "~6.5.5",
"@types/leaflet": "^1.5.1", "ts-md5": "^1.2.5",
"firebase-tools": "^12.0.0", "tslib": "^1.13.0",
"typescript": "~5.2.2" "web-animations-js": "^2.3.2",
} "whatwg-fetch": "^3.0.0",
"zone.js": "~0.10.3"
},
"devDependencies": {
"@angular-devkit/build-angular": "~0.901.8",
"@angular/cli": "~9.1.8",
"@angular/compiler-cli": "~9.1.11",
"@angular/language-service": "~9.1.11",
"@types/node": "~8.9.4",
"firebase-tools": "^7.0.2",
"ts-node": "~7.0.0",
"tslint": "~5.15.0",
"typescript": "~3.8.3"
}
} }

View File

@ -8,14 +8,7 @@ import {SwUpdate} from "@angular/service-worker";
}) })
export class AppComponent { export class AppComponent {
constructor(private snackbar: MatSnackBar, private update: SwUpdate) { constructor(private snackbar: MatSnackBar, private update: SwUpdate) {
// Check for updates update.available.subscribe(() => snackbar.open('Update Available!! 🚀', 'Reload').onAction().subscribe(async () => update.activateUpdate()))
(async () => { update.activated.subscribe(() => window.location.reload());
if(await update.checkForUpdate())
snackbar.open('Update Available!! 🚀', 'Reload')
.onAction().subscribe(async () => {
await update.activateUpdate();
location.reload();
})
})();
} }
} }

View File

@ -1,51 +1,58 @@
import {isDevMode, NgModule} from "@angular/core"; import {NgModule} from "@angular/core";
import {FormsModule} from "@angular/forms";
import {BrowserModule} from "@angular/platform-browser";
import {BrowserAnimationsModule} from "@angular/platform-browser/animations";
import {ServiceWorkerModule} from "@angular/service-worker";
import {ClickOutsideModule} from "ng-click-outside";
import {ColorPickerModule} from "ngx-color-picker";
import {AppComponent} from './app.component';
import {AppRouting} from './app.routing'; import {AppRouting} from './app.routing';
import {AppComponent} from './app.component';
import {environment} from '../environments/environment';
import {MapComponent} from "./views/map/map.component";
import {HomeComponent} from "./views/home/home.component";
import {MaterialModule} from "./material.module";
import {CalibrateComponent} from "./components/calibrate/calibrate.component"; import {CalibrateComponent} from "./components/calibrate/calibrate.component";
import {PermissionsComponent} from "./components/permissions/permissions.component";
import {ToolbarComponent} from "./components/toolbar/toolbar.component";
import {PaletteComponent} from "./components/palette/palette.component"
import {ColorPickerDialogComponent} from "./components/colorPickerDialog/colorPickerDialog.component"; import {ColorPickerDialogComponent} from "./components/colorPickerDialog/colorPickerDialog.component";
import {DimensionsDialogComponent} from "./components/dimensionsDialog/dimensionsDialog.component"; import {DimensionsDialogComponent} from "./components/dimensionsDialog/dimensionsDialog.component";
import {EditSymbolComponent} from "./components/editSymbol/editSymbol.component"; import {EditSymbolComponent} from "./components/editSymbol/editSymbol.component";
import {PaletteComponent} from "./components/palette/palette.component"; import {AngularFireModule} from "@angular/fire";
import {PermissionsComponent} from "./components/permissions/permissions.component"; import {AngularFirestoreModule} from "@angular/fire/firestore";
import {BrowserAnimationsModule} from "@angular/platform-browser/animations";
import {BrowserModule} from "@angular/platform-browser";
import {ClickOutsideModule} from "ng-click-outside";
import {ColorPickerModule} from "ngx-color-picker";
import {FormsModule} from "@angular/forms";
import {ServiceWorkerModule} from "@angular/service-worker";
import {StarrySkyComponent} from "./components/starrySky/starrySky.component"; import {StarrySkyComponent} from "./components/starrySky/starrySky.component";
import {ToolbarComponent} from "./components/toolbar/toolbar.component"; import {AngularFireAuthModule} from "@angular/fire/auth";
import {MaterialModule} from "./material.module";
import {HomeComponent} from "./views/home/home.component";
import {MapComponent} from "./views/map/map.component";
@NgModule({ @NgModule({
declarations: [ declarations: [
AppComponent, AppComponent,
CalibrateComponent, CalibrateComponent,
ColorPickerDialogComponent, ColorPickerDialogComponent,
DimensionsDialogComponent, DimensionsDialogComponent,
EditSymbolComponent, EditSymbolComponent,
HomeComponent, HomeComponent,
MapComponent, MapComponent,
PaletteComponent, PaletteComponent,
PermissionsComponent, PermissionsComponent,
StarrySkyComponent, StarrySkyComponent,
ToolbarComponent ToolbarComponent
], ],
imports: [ imports: [
AppRouting, AngularFireModule.initializeApp(environment.firebaseConfig),
BrowserAnimationsModule, AngularFirestoreModule.enablePersistence(),
BrowserModule, AngularFireAuthModule,
FormsModule, AppRouting,
ClickOutsideModule, BrowserAnimationsModule,
ColorPickerModule, BrowserModule,
MaterialModule, ClickOutsideModule,
ServiceWorkerModule.register('ngsw-worker.js', { ColorPickerModule,
enabled: !isDevMode(), FormsModule,
registrationStrategy: 'registerWhenStable:30000' // when stable or after 30 seconds MaterialModule,
}) ServiceWorkerModule.register('ngsw-worker.js', {enabled: environment.production}),
], ],
bootstrap: [AppComponent] providers: [],
entryComponents: [CalibrateComponent, ColorPickerDialogComponent, DimensionsDialogComponent, EditSymbolComponent, PermissionsComponent],
bootstrap: [AppComponent]
}) })
export class AppModule {} export class AppModule {
}

View File

@ -1,33 +1,31 @@
<div class="pt-2"> <div>
<mat-button-toggle-group class="mb-2" (change)="physicsService.mode = $event.value"> <mat-button-toggle-group class="mb-2" (change)="physicsService.mode = $event.value">
<mat-button-toggle value="gps" [checked]="physicsService.mode == 'gps'">GPS</mat-button-toggle> <mat-button-toggle value="gps" [checked]="physicsService.mode == 'gps'">GPS</mat-button-toggle>
<mat-button-toggle value="orientation" [checked]="physicsService.mode == 'orientation'">Compass</mat-button-toggle> <mat-button-toggle value="orientation" [checked]="physicsService.mode == 'orientation'">Compass</mat-button-toggle>
</mat-button-toggle-group> </mat-button-toggle-group>
<div *ngIf="physicsService.mode == 'orientation'" [@expand] [@collapse]> <div *ngIf="physicsService.mode == 'orientation'" [@expand] [@collapse]>
<div class="row mt-2"> <div class="row mt-2">
<div class="col-5 pr-0"> <div class="col-5 pr-0">
<mat-form-field appearance="fill" class="w-100"> <mat-form-field appearance="fill" class="w-100">
<mat-label>Shift +/-</mat-label> <mat-label>Shift +/-</mat-label>
<input matInput type="number" min="-180" max="180" [(ngModel)]="calibration"> <input matInput type="number" min="-180" max="180" [(ngModel)]="calibration">
</mat-form-field> </mat-form-field>
</div> </div>
<div class="col-2 text-center"><h1> = </h1></div> <div class="col-2 text-center"><h1> = </h1></div>
<div class="col-5 pl-0"> <div class="col-5 pl-0">
<mat-form-field appearance="fill" class="w-100"> <mat-form-field appearance="fill" class="w-100">
<mat-label>Heading</mat-label> <mat-label>Heading</mat-label>
<input matInput type="number" [value]="(physicsService.info | async)?.heading | number : '1.0-0'" readonly> <input matInput type="number" [value]="(physicsService.info | async)?.heading | number : '1.0-0'" readonly>
</mat-form-field> </mat-form-field>
</div> </div>
</div> </div>
<div> <div>
<mat-slider style="width: 96%" [min]="-180" [max]="180" [step]="1" color="accent" showTickMarks discrete> <mat-slider class="w-100" [max]="180" [min]="-180" [step]="1" [tickInterval]="90" [thumbLabel]="true" [ngModel]="calibration" (input)="calibration = $event.value"></mat-slider>
<input [(ngModel)]="calibration" matSliderThumb> </div>
</mat-slider> </div>
</div> <mat-divider class="mb-1"></mat-divider>
</div> <div>
<mat-divider class="mb-1"></mat-divider> <button mat-button *ngIf="physicsService.mode == 'orientation'" class="float-left" (click)="setN()">Set 0°</button>
<div> <button mat-button class="float-right" (click)="close()">Close</button>
<button mat-button *ngIf="physicsService.mode == 'orientation'" class="float-left" (click)="setN()">Set 0°</button> </div>
<button mat-button class="float-right" (click)="close()">Close</button>
</div>
</div> </div>

View File

@ -1,40 +1,35 @@
import {Component} from "@angular/core"; import {Component} from "@angular/core";
import {MatBottomSheetRef} from "@angular/material/bottom-sheet"; import {MatBottomSheetRef} from "@angular/material/bottom-sheet";
import {PhysicsService} from "../../services/physics.service"; import {PhysicsService} from "../../services/physics.service";
import {collapse, expand} from "../../utils/animations"; import {collapse, expand} from "../../animations";
@Component({ @Component({
selector: 'calibrate', selector: 'calibrate',
templateUrl: 'calibrate.component.html', templateUrl: 'calibrate.component.html',
animations: [collapse, expand] animations: [collapse, expand]
}) })
export class CalibrateComponent { export class CalibrateComponent {
private _calibration = 0; private _calibration = 0;
get calibration() { return this._calibration; } get calibration() { return this._calibration; }
set calibration(c: number) {
this._calibration = c;
this.physicsService.calibrate.next(c);
}
set calibration(c: number) { constructor(private bottomSheetRef: MatBottomSheetRef, public physicsService: PhysicsService) {
this._calibration = c; this._calibration = this.physicsService.calibrate.value;
this.physicsService.calibrate.next(c); }
}
constructor(private bottomSheetRef: MatBottomSheetRef, public physicsService: PhysicsService) { close() {
this._calibration = this.physicsService.calibrate.value; this.bottomSheetRef.dismiss();
} }
close() { setN() {
this.bottomSheetRef.dismiss(); let currentHeading = Math.round(this.physicsService.orientation.value.alpha);
} if(currentHeading > 0) {
this.calibration = currentHeading > 180 ? currentHeading - 360 : currentHeading;
setCalibration(target: any) { } else {
this.calibration = target.value; this.calibration = -currentHeading;
} }
}
setN() {
let currentHeading = Math.round(this.physicsService.orientation.value.alpha ?? 0);
if(currentHeading > 0) {
this.calibration = currentHeading > 180 ? currentHeading - 360 : currentHeading;
} else {
this.calibration = -currentHeading;
}
}
} }

View File

@ -1,7 +1,7 @@
<div> <div>
<form id="dimensionsForm" class="d-flex align-items-center pt-4 px-4 pb-0" (ngSubmit)="close()"> <form id="dimensionsForm" class="d-flex align-items-center" (ngSubmit)="close()">
<ng-container *ngFor="let d of dimensions; let i = index"> <ng-container *ngFor="let d of dimensions; let i = index">
<div class="flex-shrink-0 mb-2 mx-2" *ngIf="i != 0">X</div> <div class="flex-shrink-0 mx-2" *ngIf="i != 0">X</div>
<div class="flex-shrink-1"> <div class="flex-shrink-1">
<mat-form-field class='w-100' appearance="fill"> <mat-form-field class='w-100' appearance="fill">
<mat-label>{{d}}</mat-label> <mat-label>{{d}}</mat-label>

View File

@ -1,3 +1,3 @@
.mat-form-field-infix { ::ng-deep .mat-form-field-infix {
width: auto !important; width: auto !important;
} }

View File

@ -1,75 +1,75 @@
@function stars ($n) { @function stars ($n) {
$value: '#{random(4000)}px #{random(4000)}px #ffffff'; $value: '#{random(2000)}px #{random(2000)}px #FFF';
@for $i from 2 through $n { @for $i from 2 through $n {
$value: '#{$value} , #{random(4000)}px #{random(4000)}px #ffffff'; $value: '#{$value} , #{random(2000)}px #{random(2000)}px #FFF';
} }
@return unquote($value) @return unquote($value)
} }
.sky { .sky {
height: 100%; height: 100%;
width: 100%; width: 100%;
background: radial-gradient(ellipse at bottom, #1B2735 0%, #090A0F 100%); background: radial-gradient(ellipse at bottom, #1B2735 0%, #090A0F 100%);
overflow: hidden overflow: hidden
} }
.shooting-star { .shooting-star {
position: absolute; position: absolute;
top: 110%; top: 110%;
width: 30px; width: 30px;
height: 3px; height: 3px;
border-radius: 50%; border-radius: 50%;
background-color: white; background-color: white;
animation: shoot 20s linear infinite; animation: shoot 20s linear infinite;
animation-delay: 1s; animation-delay: 1s;
transform: rotate(-40deg); transform: rotate(-30deg);
} }
.stars { .stars {
background: transparent; background: transparent;
border-radius: 50%; border-radius: 50%;
&.foreground { &.foreground {
width: 1px; width: 1px;
height: 1px; height: 1px;
box-shadow: stars(4000); box-shadow: stars(1000);
animation: spin 200s linear infinite; animation: spin 200s linear infinite;
} }
&.midground { &.midground {
width: 2px; width: 2px;
height: 2px; height: 2px;
box-shadow: stars(2000); box-shadow: stars(750);
animation: spin 300s linear infinite; animation: spin 300s linear infinite;
} }
&.background { &.background {
width: 4px; width: 4px;
height: 4px; height: 4px;
box-shadow: stars(1000); box-shadow: stars(500);
animation: spin 350s linear infinite; animation: spin 350s linear infinite;
} }
} }
@keyframes shoot { @keyframes shoot{
0% { 0%{
top: -10%; top: -10%;
left: 80%; left: 80%;
} }
5% { 5%{
top: 110%; top: 110%;
left: 20%; left: 20%;
} }
100% { 100% {
top: 110%; top: 110%;
} }
} }
@keyframes spin { @keyframes spin {
from { from {
transform: translateY(0); transform: translateY(0);
} }
to { to {
transform: translateY(-2000px); transform: translateY(-2000px);
} }
} }

View File

@ -1,6 +1,6 @@
<mat-toolbar id="toolbar"> <mat-toolbar id="toolbar">
<button mat-icon-button class="p-0" routerLink="/"> <button mat-icon-button routerLink="/">
<img src="/assets/images/logo.png" style="height: 36px; width: 36px"> <img src="/assets/images/logo.png" height="35px" width="auto">
</button> </button>
<small class="ml-1">{{version}}</small> <small class="ml-1">{{version}}</small>
<mat-progress-spinner *ngIf="(status | async) == 'staving'" color="primary" class="text-muted pl-2"></mat-progress-spinner> <mat-progress-spinner *ngIf="(status | async) == 'staving'" color="primary" class="text-muted pl-2"></mat-progress-spinner>

View File

@ -1,22 +1,3 @@
.mat-toolbar { .selected {
position: absolute; background-color: rgba(70, 70, 70, 0.8);
height: 45px !important;
z-index: 5000;
color: rgba(255, 255, 255, 0.54);
background-color: rgba(0, 0, 0, 0.8);
.selected {
background-color: rgba(70, 70, 70, 0.8);
}
.mat-mdc-icon-button {
height: 40px;
width: 40px;
padding: 8px;
&:hover {
background-color: rgba(90, 90, 90, 0.8) !important;
}
}
} }

View File

@ -1 +1,2 @@
export interface Map { } export interface Map {
}

View File

@ -1,6 +1,6 @@
import {User as FirebaseUser} from '@angular/fire/auth' import {User as FirebaseUser} from "firebase"
import {DocumentReference} from '@angular/fire/firestore'; import {AngularFirestoreDocument} from '@angular/fire/firestore';
export interface User extends FirebaseUser { export interface User extends FirebaseUser {
ref?: DocumentReference; ref?: AngularFirestoreDocument;
} }

View File

@ -1,54 +1,46 @@
import {Injectable} from '@angular/core'; import {Injectable} from "@angular/core";
import {BehaviorSubject} from 'rxjs'; import {BehaviorSubject, from} from "rxjs";
import {Router} from '@angular/router'; import {AngularFirestore} from "@angular/fire/firestore";
import {AngularFireAuth} from "@angular/fire/auth";
import {auth} from 'firebase';
import {Router} from "@angular/router";
import {flatMap, map, skip} from 'rxjs/operators';
import {User} from '../models/user'; import {User} from '../models/user';
import {getAuth, FacebookAuthProvider, GoogleAuthProvider, signInWithPopup} from 'firebase/auth';
import {collection, getFirestore, doc, getDoc} from 'firebase/firestore';
@Injectable({ @Injectable({
providedIn: 'root' providedIn: 'root'
}) })
export class AuthService { export class AuthService {
readonly collection = 'Users'; readonly collection = 'Users';
authenticated = false; authenticated = false;
user = new BehaviorSubject<User | false>(null); user = new BehaviorSubject<User>(null);
private get auth() { return getAuth(); } constructor(private afAuth: AngularFireAuth, private router: Router, private db: AngularFirestore) {
private get db() { return getFirestore(); } this.user.subscribe(user => {
this.authenticated = user instanceof Object
});
this.afAuth.user.pipe(
flatMap((user: any) => {
if(!user) return from([false]);
let ref = this.db.collection(this.collection).doc(user.uid);
return ref.valueChanges().pipe(map(dbUser => Object.assign({ref: ref}, user, dbUser)))
})
).subscribe(user => this.user.next(<User>user));
}
constructor(private router: Router) { async loginWithGoogle() {
this.user.subscribe(user => this.authenticated = user instanceof Object); this.afAuth.auth.signInWithPopup(new auth.GoogleAuthProvider());
this.whoAmI(); return this.user.pipe(skip(1));
} }
async loginWithGoogle() { async loginWithFacebook() {
const result = await signInWithPopup(this.auth, new GoogleAuthProvider()); this.afAuth.auth.signInWithPopup(new auth.FacebookAuthProvider());
this.user.next(result.user); return this.user.pipe(skip(1));
return result.user; }
}
async loginWithFacebook() { async logout() {
const result = await signInWithPopup(this.auth, new FacebookAuthProvider()); await this.afAuth.auth.signOut();
this.user.next(result.user); return this.router.navigate(['/']);
return result.user; }
}
async logout() {
await this.auth.signOut();
this.user.next(false);
return this.router.navigate(['/']);
}
async whoAmI() {
await this.auth.authStateReady();
const user = this.auth.currentUser || false;
if(!!user) {
const ref = doc(collection(this.db, this.collection), user.uid);
const data = await getDoc(ref);
Object.assign(user, {ref, ...data});
}
this.user.next(user);
return user;
}
} }

View File

@ -1,31 +1,31 @@
import {BehaviorSubject} from "rxjs"; import {BehaviorSubject} from "rxjs";
import {latLngDistance} from "../utils/misc"; import {latLngDistance} from "../utils";
import {environment} from "../../environments/environment"; import {environment} from "../../environments/environment";
import {Circle, LatLng, MapSymbol, Marker, Measurement, Polygon, Polyline, Rectangle} from "../models/mapSymbol"; import {Circle, LatLng, MapSymbol, Marker, Measurement, Polygon, Polyline, Rectangle} from "../models/mapSymbol";
declare const L; declare const L;
export enum MapLayers { export enum MapLayers {
BING, BING,
GOOGLE_HYBRID, GOOGLE_HYBRID,
GOOGLE_ROAD, GOOGLE_ROAD,
GOOGLE_SATELLITE, GOOGLE_SATELLITE,
GOOGLE_TERRAIN, GOOGLE_TERRAIN,
ESRI_TOPOGRAPHIC, ESRI_TOPOGRAPHIC,
ESRI_IMAGERY, ESRI_IMAGERY,
ESRI_IMAGERY_CLARITY ESRI_IMAGERY_CLARITY
} }
export enum WeatherLayers { export enum WeatherLayers {
CLOUDS_NEW, CLOUDS_NEW,
PRECIPITATION_NEW, PRECIPITATION_NEW,
SEA_LEVEL_PRESSURE, SEA_LEVEL_PRESSURE,
WIND_NEW, WIND_NEW,
TEMP_NEW TEMP_NEW
} }
function buildMarker(color?: string) { function buildMarker(color?: string) {
const markerHtmlStyles = ` const markerHtmlStyles = `
background-color: ${color || '#e9403d'}; background-color: ${color || '#e9403d'};
width: 3rem; width: 3rem;
height: 3rem; height: 3rem;
@ -36,191 +36,193 @@ function buildMarker(color?: string) {
border-radius: 3rem 3rem 0; border-radius: 3rem 3rem 0;
transform: rotate(45deg); transform: rotate(45deg);
border: 2px solid #FFFFFF`; border: 2px solid #FFFFFF`;
return L.divIcon({className: "my-custom-pin", iconAnchor: [0, 24], labelAnchor: [-6, 0], popupAnchor: [0, -36], html: `<span style="${markerHtmlStyles}" />`}); return L.divIcon({className: "my-custom-pin", iconAnchor: [0, 24], labelAnchor: [-6, 0], popupAnchor: [0, -36], html: `<span style="${markerHtmlStyles}" />`});
} }
const ARROW = L.icon({iconUrl: '/assets/images/arrow.png', iconSize: [40, 45], iconAnchor: [20, 23]}); const ARROW = L.icon({iconUrl: '/assets/images/arrow.png', iconSize: [40, 45], iconAnchor: [20, 23]});
const DOT = L.icon({iconUrl: '/assets/images/dot.png', iconSize: [25, 25], iconAnchor: [13, 13]}); const DOT = L.icon({iconUrl: '/assets/images/dot.png', iconSize: [25, 25], iconAnchor: [13, 13]});
const MARKER = buildMarker(); const MARKER = buildMarker();
export class MapService { export class MapService {
private readonly map; private readonly map;
private circles: L.Circle[] = []; private circles = [];
private markers: L.Marker[] = []; private markers = [];
private measurements: any[] = []; private measurements = [];
private mapLayer!: any; private mapLayer;
private polygons: L.Polygon[] = []; private polygons = [];
private polylines: L.Polyline[] = []; private polylines = [];
private rectangles: L.Rectangle[] = []; private rectangles = [];
private weatherLayer?: any; private weatherLayer;
click = new BehaviorSubject<{latlng: LatLng, symbol?: MapSymbol, item?: any} | null>(null); click = new BehaviorSubject<{latlng: LatLng, symbol?: MapSymbol, item?: any}>(null);
touch = new BehaviorSubject<{type: string, latlng: LatLng} | null>(null); touch = new BehaviorSubject<{type: string, latlng: LatLng}>(null);
constructor(private elementId: string) { constructor(private elementId: string) {
this.map = L.map(this.elementId, {attributionControl: false, editable: true, tap: true, zoomControl: false, maxBoundsViscosity: 1, doubleClickZoom: false}).setView({lat: 0, lng: 0}, 2); this.map = L.map(elementId, {attributionControl: false, editable: true, tap: true, zoomControl: false, maxBoundsViscosity: 1, doubleClickZoom: false}).setView({lat: 0, lng: 0}, 10);
this.map.on('click', (e) => this.click.next({latlng: {lat: e.latlng.lat, lng: e.latlng.lng}})); this.map.on('click', (e) => this.click.next({latlng: {lat: e.latlng.lat, lng: e.latlng.lng}}));
this.map.on('mousedown touchstart', (e) => this.touch.next({type: 'start', latlng: {lat: e.latlng.lat, lng: e.latlng.lng}})); this.map.on('touchstart', (e) => this.touch.next({type: 'start', latlng: {lat: e.latlng.lat, lng: e.latlng.lng}}));
this.map.on('mousemove touchmove', (e) => this.touch.next({type: 'move', latlng: {lat: e.latlng.lat, lng: e.latlng.lng}})); this.map.on('touchmove', (e) => this.touch.next({type: 'move', latlng: {lat: e.latlng.lat, lng: e.latlng.lng}}));
this.map.on('mouseup touchend', (e) => this.touch.next({type: 'end', latlng: {lat: e.latlng.lat, lng: e.latlng.lng}})); this.map.on('touchend', (e) => this.touch.next({type: 'end', latlng: {lat: e.latlng.lat, lng: e.latlng.lng}}));
this.setMapLayer(); this.setMapLayer();
} }
private getIcon(name: string) { private getIcon(name: string) {
switch(name) { switch(name) {
case 'arrow': case 'arrow':
return ARROW; return ARROW;
case 'dot': case 'dot':
return DOT; return DOT;
default: default:
return MARKER; return MARKER;
} }
} }
centerOn(latlng: LatLng, zoom=14) { centerOn(latlng: LatLng, zoom=14) {
this.map.setView(latlng, zoom); this.map.setView(latlng, zoom);
} }
delete(...symbols) { delete(...symbols) {
symbols.forEach(s => { symbols.forEach(s => {
this.map.removeLayer(s); this.map.removeLayer(s);
this.circles = this.circles.filter(c => c != s); this.circles = this.circles.filter(c => c != s);
this.markers = this.markers.filter(m => m != s); this.markers = this.markers.filter(m => m != s);
this.measurements = this.measurements.filter(m => m != s); this.measurements = this.measurements.filter(m => m != s);
this.polygons = this.polygons.filter(p => p != s); this.polygons = this.polygons.filter(p => p != s);
this.polylines = this.polylines.filter(p => p != s); this.polylines = this.polylines.filter(p => p != s);
this.rectangles = this.rectangles.filter(r => r != s); this.rectangles = this.rectangles.filter(r => r != s);
}); });
} }
deleteAll() { deleteAll() {
this.circles.forEach(c => this.delete(c)); this.circles.forEach(c => this.delete(c));
this.markers.forEach(m => this.delete(m)); this.markers.forEach(m => this.delete(m));
this.measurements.forEach(m => this.delete(m)); this.measurements.forEach(m => this.delete(m));
this.polygons.forEach(p => this.delete(p)); this.polygons.forEach(p => this.delete(p));
this.polylines.forEach(p => this.delete(p)); this.polylines.forEach(p => this.delete(p));
this.rectangles.forEach(r => this.delete(r)); this.rectangles.forEach(r => this.delete(r));
} }
lock(unlock?: boolean) { lock(unlock?: boolean) {
if(unlock) { if(unlock) {
this.map.setMaxBounds(null); this.map.setMaxBounds(null);
this.map.boxZoom.disable(); this.map.boxZoom.disable();
this.map.touchZoom.enable(); this.map.touchZoom.enable();
this.map.scrollWheelZoom.enable(); this.map.scrollWheelZoom.enable();
} else { } else {
this.map.setMaxBounds(this.map.getBounds()); this.map.setMaxBounds(this.map.getBounds());
this.map.boxZoom.disable(); this.map.boxZoom.disable();
this.map.touchZoom.disable(); this.map.touchZoom.disable();
this.map.scrollWheelZoom.disable(); this.map.scrollWheelZoom.disable();
} }
} }
newCircle(c: Circle) { newCircle(c: Circle) {
const circle = L.circle(c.latlng, Object.assign({color: '#e9403d', autoPan: false}, c)).addTo(this.map); let circle = L.circle(c.latlng, Object.assign({color: '#e9403d', autoPan: false}, c)).addTo(this.map);
if(c.label) circle.bindTooltip(c.label, {permanent: true, direction: 'center'}); if(c.label) circle.bindTooltip(c.label, {permanent: true, direction: 'center'});
circle.on('click', e => this.click.next({latlng: {lat: e.latlng.lat, lng: e.latlng.lng}, symbol: c, item: circle})); circle.on('click', e => this.click.next({latlng: {lat: e.latlng.lat, lng: e.latlng.lng}, symbol: c, item: circle}));
if(!c.noDelete) this.circles.push(circle); if(!c.noDelete) this.circles.push(circle);
return circle; return circle;
} }
newMarker(m: Marker) { newMarker(m: Marker) {
let icon = m.icon ? this.getIcon(m.icon) : buildMarker(m.color); let icon = m.icon ? this.getIcon(m.icon) : buildMarker(m.color);
let marker = L.marker(m.latlng, Object.assign({autoPan: false}, m, {icon: icon})).addTo(this.map); let marker = L.marker(m.latlng, Object.assign({autoPan: false}, m, {icon: icon})).addTo(this.map);
if(m.label) marker.bindTooltip(m.label, {permanent: true, direction: 'bottom'}); if(m.label) marker.bindTooltip(m.label, {permanent: true, direction: 'bottom'});
marker.on('click', e => this.click.next({latlng: {lat: e.latlng.lat, lng: e.latlng.lng}, symbol: m, item: marker})); marker.on('click', e => this.click.next({latlng: {lat: e.latlng.lat, lng: e.latlng.lng}, symbol: m, item: marker}));
if(!m.noDelete) this.markers.push(marker); if(!m.noDelete) this.markers.push(marker);
return marker; return marker;
} }
newMeasurement(m: Measurement) { newMeasurement(m: Measurement) {
let line = L.polyline([m.latlng, m.latlng2], Object.assign({color: '#e9403d', autoPan: false, weight: 10, lineCap: "square", dashArray: '10, 20'}, m)).addTo(this.map); let line = L.polyline([m.latlng, m.latlng2], Object.assign({color: '#e9403d', autoPan: false, weight: 10, lineCap: "square", dashArray: '10, 20'}, m)).addTo(this.map);
if(!m.noDelete) this.measurements.push(line); if(!m.noDelete) this.measurements.push(line);
let distance = latLngDistance(m.latlng, m.latlng2); let distance = latLngDistance(m.latlng, m.latlng2);
line.bindPopup(`${distance > 1000 ? Math.round(distance / 100) / 10 : Math.round(distance)} ${distance > 1000 ? 'k' : ''}m`, {autoPan: false, autoClose: false, closeOnClick: false}).openPopup(); line.bindPopup(`${distance > 1000 ? Math.round(distance / 100) / 10 : Math.round(distance)} ${distance > 1000 ? 'k' : ''}m`, {autoPan: false, autoClose: false, closeOnClick: false}).openPopup();
line.on('click', e => this.click.next({latlng: {lat: e.latlng.lat, lng: e.latlng.lng}, symbol: m, item: line})); line.on('click', e => this.click.next({latlng: {lat: e.latlng.lat, lng: e.latlng.lng}, symbol: m, item: line}));
return line; return line;
} }
newPolygon(p: Polygon) { newPolygon(p: Polygon) {
let polygon = new L.Polygon(p.latlng, Object.assign({color: '#e9403d', autoPan: false}, p)).addTo(this.map); let polygon = new L.Polygon(p.latlng, Object.assign({color: '#e9403d', autoPan: false}, p)).addTo(this.map);
if(p.label) polygon.bindTooltip(p.label, {permanent: true}); if(p.label) polygon.bindTooltip(p.label, {permanent: true});
polygon.on('click', e => this.click.next({latlng: {lat: e.latlng.lat, lng: e.latlng.lng}, symbol: p, item: polygon})); polygon.on('click', e => this.click.next({latlng: {lat: e.latlng.lat, lng: e.latlng.lng}, symbol: p, item: polygon}));
if(!p.noDelete) this.polygons.push(polygon); if(!p.noDelete) this.polygons.push(polygon);
return polygon; return polygon;
} }
newPolyline(p: Polyline) { newPolyline(p: Polyline) {
let polyline = new L.Polyline(p.latlng, Object.assign({color: '#e9403d', autoPan: false, weight: 10}, p)).addTo(this.map); let polyline = new L.Polyline(p.latlng, Object.assign({color: '#e9403d', autoPan: false, weight: 10}, p)).addTo(this.map);
polyline.on('click', e => this.click.next({latlng: {lat: e.latlng.lat, lng: e.latlng.lng}, symbol: p, item: polyline})); polyline.on('click', e => this.click.next({latlng: {lat: e.latlng.lat, lng: e.latlng.lng}, symbol: p, item: polyline}));
if(!p.noDelete) this.polylines.push(polyline); if(!p.noDelete) this.polylines.push(polyline);
return polyline; return polyline;
} }
newRectangle(r: Rectangle) { newRectangle(r: Rectangle) {
let rect = new L.Rectangle([r.latlng, r.latlng2], Object.assign({color: '#e9403d', autoPan: false}, r)).addTo(this.map); let rect = new L.Rectangle([r.latlng, r.latlng2], Object.assign({color: '#e9403d', autoPan: false}, r)).addTo(this.map);
if(r.label) rect.bindTooltip(r.label, {permanent: true, direction: 'center'}); if(r.label) rect.bindTooltip(r.label, {permanent: true, direction: 'center'});
rect.on('click', e => this.click.next({latlng: {lat: e.latlng.lat, lng: e.latlng.lng}, symbol: r, item: rect})); rect.on('click', e => this.click.next({latlng: {lat: e.latlng.lat, lng: e.latlng.lng}, symbol: r, item: rect}));
if(!r.noDelete) this.rectangles.push(rect); if(!r.noDelete) this.rectangles.push(rect);
return rect; return rect;
} }
setMapLayer(layer?: MapLayers) { setMapLayer(layer?: MapLayers) {
if(this.mapLayer) this.map.removeLayer(this.mapLayer); if(this.mapLayer) this.map.removeLayer(this.mapLayer);
if(layer == null) layer = MapLayers.GOOGLE_HYBRID; if(layer == null) layer = MapLayers.GOOGLE_HYBRID;
switch(layer) { switch(layer) {
case MapLayers.BING: case MapLayers.BING:
this.mapLayer = L.tileLayer.bing(environment.bing); this.mapLayer = L.tileLayer.bing(environment.bing);
break; break;
case MapLayers.GOOGLE_HYBRID: case MapLayers.GOOGLE_HYBRID:
this.mapLayer = L.gridLayer.googleMutant({type: 'hybrid'}); this.mapLayer = L.gridLayer.googleMutant({type: 'hybrid'});
break; break;
case MapLayers.GOOGLE_ROAD: case MapLayers.GOOGLE_ROAD:
this.mapLayer = L.gridLayer.googleMutant({type: 'roadmap'}); this.mapLayer = L.gridLayer.googleMutant({type: 'roadmap'});
break; break;
case MapLayers.GOOGLE_SATELLITE: case MapLayers.GOOGLE_SATELLITE:
this.mapLayer = L.gridLayer.googleMutant({type: 'satellite'}); this.mapLayer = L.gridLayer.googleMutant({type: 'satellite'});
break; break;
case MapLayers.GOOGLE_TERRAIN: case MapLayers.GOOGLE_TERRAIN:
this.mapLayer = L.gridLayer.googleMutant({type: 'terrain'}); this.mapLayer = L.gridLayer.googleMutant({type: 'terrain'});
break; break;
case MapLayers.ESRI_TOPOGRAPHIC: case MapLayers.ESRI_TOPOGRAPHIC:
this.mapLayer = L.esri.basemapLayer('Topographic'); this.mapLayer = L.esri.basemapLayer('Topographic');
break; break;
case MapLayers.ESRI_IMAGERY: case MapLayers.ESRI_IMAGERY:
this.mapLayer = L.esri.basemapLayer('Imagery'); this.mapLayer = L.esri.basemapLayer('Imagery');
break; break;
case MapLayers.ESRI_IMAGERY_CLARITY: case MapLayers.ESRI_IMAGERY_CLARITY:
this.mapLayer = L.esri.basemapLayer('ImageryClarity'); this.mapLayer = L.esri.basemapLayer('ImageryClarity');
break; break;
} }
this.mapLayer.addTo(this.map); this.mapLayer.addTo(this.map);
if(this.weatherLayer) this.setWeatherLayer(this.weatherLayer.name); if(this.weatherLayer) this.setWeatherLayer(this.weatherLayer.name);
} }
setWeatherLayer(layer?: WeatherLayers) { setWeatherLayer(layer?: WeatherLayers) {
if(this.weatherLayer) { if(this.weatherLayer) {
this.map.removeLayer(this.weatherLayer.layer); this.map.removeLayer(this.weatherLayer.layer);
this.weatherLayer = null; this.weatherLayer = null;
} }
switch(layer) { switch(layer) {
case WeatherLayers.CLOUDS_NEW: case WeatherLayers.CLOUDS_NEW:
this.weatherLayer = {name: WeatherLayers.CLOUDS_NEW, layer: L.OWM.clouds({appId: environment.openWeather, opacity: 0.5})}; this.weatherLayer = {name: WeatherLayers.CLOUDS_NEW, layer: L.OWM.clouds({appId: environment.openWeather, opacity: 0.5})};
break; break;
case WeatherLayers.PRECIPITATION_NEW: case WeatherLayers.PRECIPITATION_NEW:
this.weatherLayer = {name: WeatherLayers.PRECIPITATION_NEW, layer: L.OWM.precipitation({appId: environment.openWeather, opacity: 0.5})}; this.weatherLayer = {name: WeatherLayers.PRECIPITATION_NEW, layer: L.OWM.precipitation({appId: environment.openWeather, opacity: 0.5})};
break; break;
case WeatherLayers.SEA_LEVEL_PRESSURE: case WeatherLayers.SEA_LEVEL_PRESSURE:
this.weatherLayer = {name: WeatherLayers.SEA_LEVEL_PRESSURE, layer: L.OWM.pressure({appId: environment.openWeather, opacity: 0.5})}; this.weatherLayer = {name: WeatherLayers.SEA_LEVEL_PRESSURE, layer: L.OWM.pressure({appId: environment.openWeather, opacity: 0.5})};
break; break;
case WeatherLayers.WIND_NEW: case WeatherLayers.WIND_NEW:
this.weatherLayer = {name: WeatherLayers.WIND_NEW, layer: L.OWM.wind({appId: environment.openWeather, opacity: 0.5})}; this.weatherLayer = {name: WeatherLayers.WIND_NEW, layer: L.OWM.wind({appId: environment.openWeather, opacity: 0.5})};
break; break;
case WeatherLayers.TEMP_NEW: case WeatherLayers.TEMP_NEW:
this.weatherLayer = {name: WeatherLayers.TEMP_NEW, layer: L.OWM.temperature({appId: environment.openWeather, opacity: 0.5})}; this.weatherLayer = {name: WeatherLayers.TEMP_NEW, layer: L.OWM.temperature({appId: environment.openWeather, opacity: 0.5})};
break; break;
} }
if(this.weatherLayer) this.weatherLayer.layer.addTo(this.map); if(this.weatherLayer) this.weatherLayer.layer.addTo(this.map);
} }
} }

View File

@ -1,66 +1,65 @@
import {EventEmitter, Injectable} from '@angular/core'; import {EventEmitter, Injectable} from '@angular/core';
import {BehaviorSubject, combineLatest} from "rxjs"; import {BehaviorSubject, combineLatest} from "rxjs";
import {PermissionsService} from "../components/permissions/permissions.service"; import {PermissionsService} from "../components/permissions/permissions.service";
import {Position} from '../models/mapSymbol';
@Injectable({ @Injectable({
providedIn: 'root' providedIn: 'root'
}) })
export class PhysicsService { export class PhysicsService {
private _mode!: string; private _mode: string;
get mode() { return this._mode; } get mode() { return this._mode; }
set mode(mode: string) { set mode(mode: string) {
this._mode = mode; this._mode = mode;
localStorage.setItem('headingMode', mode); localStorage.setItem('headingMode', mode);
this.calibrate.next(this.calibrate.value); this.calibrate.next(this.calibrate.value);
} }
requireCalibration = new EventEmitter(); requireCalibration = new EventEmitter();
calibrate = new BehaviorSubject<number>(Infinity); calibrate = new BehaviorSubject<number>(Infinity);
info = new BehaviorSubject<any>(null); info = new BehaviorSubject(null);
motion = new BehaviorSubject<DeviceMotionEvent | null>(null); motion = new BehaviorSubject<DeviceMotionEvent>(null);
orientation = new BehaviorSubject<DeviceOrientationEvent | null>(null); orientation = new BehaviorSubject<DeviceOrientationEvent>(null);
position = new BehaviorSubject<Position | null>(null); position = new BehaviorSubject<Position>(null);
constructor(permissionsService: PermissionsService) { constructor(permissionsService: PermissionsService) {
this.mode = localStorage.getItem('headingMode'); this.mode = localStorage.getItem('headingMode');
permissionsService.requestPermission('geolocation', 'gps_fixed', 'Can we use your location?').then(granted => { permissionsService.requestPermission('geolocation', 'gps_fixed', 'Can we use your location?').then(granted => {
if(granted) { if(granted) {
// Gather physical data // Gather physical data
window.addEventListener('devicemotion', motion => this.motion.next(motion)); window.addEventListener('devicemotion', motion => this.motion.next(motion));
window.addEventListener('deviceorientation', orientation => this.orientation.next(orientation)); window.addEventListener('deviceorientation', orientation => this.orientation.next(orientation));
navigator.geolocation.watchPosition(position => this.position.next(<any>position)); navigator.geolocation.watchPosition(position => this.position.next(position));
// Combine data into one nice package // Combine data into one nice package
combineLatest(this.position, this.orientation, this.calibrate).subscribe((data: any) => { combineLatest(this.position, this.orientation, this.calibrate).subscribe(data => {
if(!data[0]) return; if(!data[0]) return;
let info = { let info = {
accuracy: data[0].coords.accuracy, accuracy: data[0].coords.accuracy,
altitude: data[0].coords.altitude, altitude: data[0].coords.altitude,
altitudeAccuracy: data[0].coords.altitudeAccuracy, altitudeAccuracy: data[0].coords.altitudeAccuracy,
heading: data[0].coords.heading, heading: data[0].coords.heading,
latitude: data[0].coords.latitude, latitude: data[0].coords.latitude,
longitude: data[0].coords.longitude, longitude: data[0].coords.longitude,
speed: data[0].coords.speed speed: data[0].coords.speed
}; };
if(this.mode == null) this.mode = info.heading ? 'gps' : 'orientation'; if(this.mode == null) this.mode = info.heading ? 'gps' : 'orientation';
if(this.mode == 'orientation') { if(this.mode == 'orientation') {
if((!data[1] || !data[1].absolute) && data[2] == Infinity) { if((!data[1] || !data[1].absolute) && data[2] == Infinity) {
this.calibrate.next(0); this.calibrate.next(0);
this.requireCalibration.emit(); this.requireCalibration.emit();
} }
info.heading = -Number(data[1] ? data[1].alpha : 0) + Number(data[2] == Infinity ? 0 : data[2]); info.heading = -(data[1] ? data[1].alpha : 0) + (data[2] == Infinity ? 0 : data[2]);
if(info.heading < 0) info.heading += 360; if(info.heading < 0) info.heading += 360;
if(info.heading >= 360) info.heading -= 360; if(info.heading >= 360) info.heading -= 360;
} }
this.info.next(info); this.info.next(info);
}) })
} }
}); });
} }
} }

View File

@ -1,200 +1,201 @@
import {Injectable} from "@angular/core"; import {Injectable} from "@angular/core";
import {onSnapshot, setDoc, collection, getFirestore, doc, DocumentReference, getDoc} from 'firebase/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, Position, Rectangle} from "../models/mapSymbol"; import {Circle, MapData, MapSymbol, Marker, Measurement, Polygon, Polyline, Position, Rectangle} from "../models/mapSymbol";
import {filter} from "rxjs/operators"; import {Md5} from 'ts-md5';
import {randomStringBuilder} from '../utils/string'; import {filter, map} from "rxjs/operators";
export const LOCATION_COLLECTION = 'Users'; export const LOCATION_COLLECTION = 'Users';
export const MAP_COLLECTION = 'Maps'; export const MAP_COLLECTION = 'Maps';
@Injectable({ @Injectable({
providedIn: 'root' providedIn: 'root'
}) })
export class SyncService { export class SyncService {
private location?: any; private location;
private locationChanged = false; private locationChanged = false;
private locationDoc?: DocumentReference | null; private locationDoc: AngularFirestoreDocument;
private locationSub?: Function; private mapCode: string;
private mapCode?: string | null; private mapDoc: AngularFirestoreDocument;
private mapDoc?: DocumentReference | null; private mapChanged = false;
private mapChanged = false; private mapSub: Subscription;
private mapSub?: Subscription | null; private saveInterval: number;
private saveInterval?: any; private username: string;
private username?: string | null;
freeze = new BehaviorSubject<boolean>(false); freeze = new BehaviorSubject<boolean>(false);
mapData = new BehaviorSubject<MapData>({}); mapData = new BehaviorSubject<MapData>({});
status = new BehaviorSubject<string | null>(null); status = new BehaviorSubject<string>(null);
get db() { return getFirestore(); } constructor(private db: AngularFirestore) {
// Handle prompting the user before exit if there are changes
this.status.pipe(filter(s => !s)).subscribe(() => window.onbeforeunload = () => this.unload());
this.status.pipe(filter(s => !!s)).subscribe(() => {
window.onbeforeunload = e => {
this.removeLocation();
let ignore = this.save();
e.returnValue = 'Please wait for us to finish saving!';
return e.returnValue;
}
});
}
constructor() { private addMapSymbol(s: MapSymbol, key: string) {
// Handle prompting the user before exit if there are changes let map = this.mapData.value;
this.status.pipe(filter(s => !s)).subscribe(() => window.onbeforeunload = () => this.unload()); if(!map[key]) map[key] = {};
this.status.pipe(filter(s => !!s)).subscribe(() => { s.updated = new Date().getTime();
window.onbeforeunload = e => { if(!s.id) s.id = Md5.hashStr(s.updated.toString()).toString();
this.removeLocation(); map[key][s.id] = s;
let ignore = this.save(); this.mapData.next(map);
e.returnValue = 'Please wait for us to finish saving!'; this.mapChanged = true;
return e.returnValue; this.status.next('modified');
} }
});
}
private addMapSymbol(s: MapSymbol, key: string) { async exists(mapCode: string) {
let map = this.mapData.value; return (await this.db.collection(MAP_COLLECTION).doc(mapCode).ref.get()).exists;
if(!map[key]) map[key] = {}; }
s.updated = new Date().getTime();
if(!s.id) s.id = randomStringBuilder(32, true, true);
map[key][s.id] = s;
this.mapData.next(map);
this.mapChanged = true;
this.status.next('modified');
}
async exists(mapCode: string) { addCircle(circle: Circle) {
const value = await getDoc(doc(collection(this.db, MAP_COLLECTION), mapCode)); this.addMapSymbol(circle, 'circles');
return value.exists(); }
}
addCircle(circle: Circle) { addMarker(marker: Marker) {
this.addMapSymbol(circle, 'circles'); this.addMapSymbol(marker, 'markers');
} }
addMarker(marker: Marker) { addMeasurement(measurement: Measurement) {
this.addMapSymbol(marker, 'markers'); this.addMapSymbol(measurement, 'measurements');
} }
addMeasurement(measurement: Measurement) { addMyLocation(location: Position) {
this.addMapSymbol(measurement, 'measurements'); location.timestamp = new Date();
} let markForSave = this.location == null;
this.locationChanged = true;
this.location = location;
if(markForSave) return this.save(false, true);
}
async addMyLocation(location: Position) { addPolygon(polygon: Polygon) {
location.timestamp = new Date(); this.addMapSymbol(polygon, 'polygons');
let markForSave = this.location == null; }
this.locationChanged = true;
this.location = location;
if(markForSave)
await this.save(false, true);
}
addPolygon(polygon: Polygon) { addPolyline(polyline: Polyline) {
this.addMapSymbol(polygon, 'polygons'); this.addMapSymbol(polyline, 'polylines')
} }
addPolyline(polyline: Polyline) { addRectangle(rect: Rectangle) {
this.addMapSymbol(polyline, 'polylines') this.addMapSymbol(rect, 'rectangles')
} }
addRectangle(rect: Rectangle) { delete(...symbols) {
this.addMapSymbol(rect, 'rectangles') let map = this.mapData.value;
} Object.keys(map).forEach(key => symbols.filter(s => !!map[key][s.id]).forEach(s => {
map[key][s.id].updated = new Date().getTime();
map[key][s.id].deleted = true
}));
this.mapData.next(map);
this.mapChanged = true;
this.status.next('modified');
}
delete(...symbols) { load(mapCode: string, username: string) {
let map = this.mapData.value; this.mapCode = mapCode;
Object.keys(map).forEach(key => symbols.filter(s => !!map[key][s.id]).forEach(s => { this.username = username.replace(/\s/g, '');
map[key][s.id].updated = new Date().getTime(); this.mapDoc = this.db.collection(MAP_COLLECTION).doc(mapCode);
map[key][s.id].deleted = true this.locationDoc = this.mapDoc.collection(LOCATION_COLLECTION).doc(username);
}));
this.mapData.next(map);
this.mapChanged = true;
this.status.next('modified');
}
load(mapCode: string, username: string) { this.mapDoc.valueChanges().subscribe(() => this.status.next(null));
this.mapCode = mapCode; this.mapSub = combineLatest(this.mapDoc.valueChanges(), this.mapDoc.collection(LOCATION_COLLECTION, ref => {
this.username = username.replace(/\s/g, ''); let aMinuteAgo = new Date();
this.mapDoc = doc(collection(this.db, MAP_COLLECTION), mapCode); aMinuteAgo.setMinutes(aMinuteAgo.getMinutes() - 1);
this.locationDoc = doc(collection(this.db, LOCATION_COLLECTION), username); return ref.where('timestamp', '>=', aMinuteAgo);
}).snapshotChanges(), this.freeze)
.pipe(map(data => {
let oldMap = this.mapData.value;
if(data[2]) return;
let newMap = data[0] || {};
let mergedMap = this.mergeMaps(newMap, oldMap);
onSnapshot(this.mapDoc, map => { let locations = data[1].map(doc => ({id: doc.payload.doc.id, data: <Marker>doc.payload.doc.data()}));
this.status.next(null); locations.filter(l => l.id != username).forEach(l => {
const data = this.mergeMaps( map.data() || {}, this.mapData.value); mergedMap.locations[l.id] = l.data;
if(this.locationSub) this.locationSub(); });
this.locationSub = onSnapshot(collection(this.mapDoc, LOCATION_COLLECTION), docs => {
docs.forEach(doc => {
const d: any = doc.data();
if(d.timestamp.seconds * 1000 > (new Date().getTime() - 60000 * 3))
data.locations[doc.id] = <any>{id: doc.id, ...doc.data()}
});
this.mapData.next(data);
})
this.mapData.next(data);
if(this.saveInterval) clearInterval(this.saveInterval);
const hasUserLocations = Object.keys(this.mapData.value?.locations)?.length > 0 || false;
this.saveInterval = setInterval(() => this.save(), hasUserLocations ? 5_000 : 30_000);
});
} return mergedMap;
})).subscribe((mapData: MapData) => {
if(!mapData) return;
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) { mergeMaps(newMap: MapData, oldMap: MapData) {
let map: MapData = {locations: {}}; let map: MapData = {locations: {}};
let twoMinAgo = new Date(); let twoMinAgo = new Date();
twoMinAgo.setMinutes(twoMinAgo.getMinutes() - 2); twoMinAgo.setMinutes(twoMinAgo.getMinutes() - 2);
Object.keys(newMap).forEach(key => { Object.keys(newMap).forEach(key => {
if(!map[key]) map[key] = {}; if(!map[key]) map[key] = {};
Object.keys(newMap[key]).filter(id => !newMap[key][id].deleted || newMap[key][id].updated > twoMinAgo) Object.keys(newMap[key]).filter(id => !newMap[key][id].deleted || newMap[key][id].updated > twoMinAgo)
.forEach(id => map[key][id] = newMap[key][id]); .forEach(id => map[key][id] = newMap[key][id]);
}); });
Object.keys(oldMap).filter(key => key != 'locations').forEach(key => { Object.keys(oldMap).filter(key => key != 'locations').forEach(key => {
if(!map[key]) map[key] = {}; if(!map[key]) map[key] = {};
Object.keys(oldMap[key]).filter(id => { Object.keys(oldMap[key]).filter(id => {
let newS = map[key][id] || false; let newS = map[key][id] || false;
return !newS && !oldMap[key][id].deleted || newS && oldMap[key][id].updated > newS.updated; return !newS && !oldMap[key][id].deleted || newS && oldMap[key][id].updated > newS.updated;
}).forEach(id => map[key][id] = oldMap[key][id]); }).forEach(id => map[key][id] = oldMap[key][id]);
}); });
return map; return map;
} }
removeLocation() { removeLocation() {
// Hack to delete doc even if page is closed // Hack to delete doc even if page is closed
navigator.sendBeacon(`https://us-central1-mapalliance-ab38a.cloudfunctions.net/closeSession/?mapCode=${this.mapCode}&username=${this.username}`); navigator.sendBeacon(`https://us-central1-mapalliance-ab38a.cloudfunctions.net/closeSession/?mapCode=${this.mapCode}&username=${this.username}`);
} }
save(map=true, location=true) { save(map=true, location=true) {
let promises: Promise<any>[] = []; let promises = [];
if(location && this.locationDoc && this.locationChanged) { if(location && this.locationDoc && this.locationChanged) {
promises.push(setDoc(this.locationDoc, this.location)); promises.push(this.locationDoc.set(this.location));
} }
if(map && this.mapDoc && this.mapChanged) { if(map && this.mapDoc && this.mapChanged) {
this.status.next('saving'); this.status.next('saving');
let map = this.mapData.value; let map = this.mapData.value;
delete map.locations; delete map.locations;
promises.push(setDoc(this.mapDoc, map)); promises.push(this.mapDoc.set(map));
this.mapChanged = false; this.mapChanged = false;
} }
return Promise.all(promises) return Promise.all(promises)
} }
unload() { unload() {
this.removeLocation(); this.removeLocation();
if(this.saveInterval) clearInterval(this.saveInterval); if(this.saveInterval) clearInterval(this.saveInterval);
let saving = this.save(true, false); let saving = this.save(true, false);
if(this.mapSub) { if(this.mapSub) {
this.mapSub.unsubscribe(); this.mapSub.unsubscribe();
this.mapSub = null; this.mapSub = null;
} }
if(this.mapDoc) { if(this.mapDoc) {
this.mapDoc = null; this.mapDoc = null;
this.mapChanged = false; this.mapChanged = false;
} }
if(this.locationDoc) { if(this.locationDoc) {
this.location = null; this.location = null;
this.locationChanged = false; this.locationChanged = false;
this.locationDoc = null; this.locationDoc = null;
} }
this.mapCode = null; this.mapCode = null;
this.username = null; this.username = null;
this.mapData.next({}); this.mapData.next({});
return saving; return saving;
} }
} }

View File

@ -1,4 +1,4 @@
import {LatLng} from "../models/mapSymbol"; import {LatLng} from "./models/mapSymbol";
export const R = 6_371; // Radius of the Earth export const R = 6_371; // Radius of the Earth

View File

@ -1,136 +0,0 @@
/**
* String of all letters
*
*/
const LETTER_LIST = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
/**
* String of all numbers
*
*/
const NUMBER_LIST = '0123456789';
/**
* String of all symbols
*/
const SYMBOL_LIST = '~`!@#$%^&*()_-+={[}]|\\:;"\'<,>.?/';
/**
* String of all letters, numbers & symbols
*/
const CHAR_LIST = LETTER_LIST + NUMBER_LIST + SYMBOL_LIST;
export function formatPhoneNumber(number: string) {
const parts = /(\+?1)?.*?(\d{3}).*?(\d{3}).*?(\d{4})/g.exec(number);
if(!parts) throw new Error(`Number cannot be parsed: ${number}`);
return `${parts[1] ?? ''} (${parts[2]}) ${parts[3]}-${parts[4]}`.trim();
}
/**
* Insert a string into another string at a given position
*
* @example
* ```
* console.log(insertAt('Hello world!', ' glorious', 5);
* // Output: Hello glorious world!
* ```
*
* @param {string} target - Parent string you want to modify
* @param {string} str - Value that will be injected to parent
* @param {number} index - Position to inject string at
* @returns {string} - New string
*/
export function insertAt(target: string, str: string, index: number): String {
return `${target.slice(0, index)}${str}${target.slice(index + 1)}`;
}
/**
* Generate a string of random characters.
*
* @example
* ```ts
* const random = randomString();
* const randomByte = randomString(8, "01")
* ```
*
* @param {number} length - length of generated string
* @param {string} pool - character pool to generate string from
* @return {string} generated string
*/
export function randomString(length: number, pool: string = CHAR_LIST): string {
return Array(length).fill(null).map(() => {
const n = ~~(Math.random() * pool.length);
return pool[n];
}).join('');
}
/**
* Generate a random string with fine control over letters, numbers & symbols
*
* @example
* ```ts
* const randomLetter = randomString(1, true);
* const randomChar = randomString(1, true, true, true);
* ```
*
* @param {number} length - length of generated string
* @param {boolean} letters - Add letters to pool
* @param {boolean} numbers - Add numbers to pool
* @param {boolean} symbols - Add symbols to pool
* @return {string} generated string
*/
export function randomStringBuilder(length: number, letters = false, numbers = false, symbols = false): string {
if(!letters && !numbers && !symbols) throw new Error('Must enable at least one: letters, numbers, symbols');
return Array(length).fill(null).map(() => {
let c;
do {
const type = ~~(Math.random() * 3);
if(letters && type == 0) {
c = LETTER_LIST[~~(Math.random() * LETTER_LIST.length)];
} else if(numbers && type == 1) {
c = NUMBER_LIST[~~(Math.random() * NUMBER_LIST.length)];
} else if(symbols && type == 2) {
c = SYMBOL_LIST[~~(Math.random() * SYMBOL_LIST.length)];
}
} while(!c);
return c;
}).join('');
}
/**
* Find all substrings that match a given pattern.
*
* Roughly based on `String.prototype.matchAll`.
*
* @param {string} value - String to search.
* @param {RegExp | string} regex - Regular expression to match.
* @return {RegExpExecArray[]} Found matches.
*/
export function matchAll(value: string, regex: RegExp | string): RegExpExecArray[] {
if(typeof regex === 'string') {
regex = new RegExp(regex, 'g');
}
// https://stackoverflow.com/a/60290199
if(!regex.global) {
throw new TypeError('Regular expression must be global.');
}
let ret: RegExpExecArray[] = [];
let match: RegExpExecArray | null;
while((match = regex.exec(value)) !== null) {
ret.push(match);
}
return ret;
}
/**
* Check if email is valid
*
* @param {string} email - Target
* @returns {boolean} - Follows format
*/
export function validateEmail(email: string) {
return /^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)*$/.test(email);
}

View File

@ -1,42 +1,42 @@
<div [@fadeIn] class="h-100 w-100"> <div [@fadeIn] class="h-100 w-100">
<stary-sky></stary-sky> <stary-sky></stary-sky>
<div class="controls d-flex flex-column"> <div class="badge">
<img src="assets/images/logo.png" class="mb-4 py-1" alt="logo" width="200px"> <img src="assets/images/logo.png" width="200px" height="auto">
<div *ngIf="!authService.authenticated"> </div>
<!--<button mat-flat-button class="w-100 text-white mb-3 py-2" style="background-color: #3b5998" (click)="authService.loginWithFacebook()"> <div class="controls">
<img class="mr-2" src="/assets/images/facebook.png" height="18px" width="auto"> Facebook <div *ngIf="!authService.authenticated">
</button>--> <!--<button mat-flat-button class="w-100 text-white mb-3" style="background-color: #3b5998" (click)="authService.loginWithFacebook()">
<button mat-flat-button class="w-100 mb-1 py-2" style="background-color: #efe8e8" (click)="authService.loginWithGoogle()"> <img class="mr-2" src="/assets/images/facebook.png" height="18px" width="auto"> Facebook
<img class="mr-2" src="/assets/images/google.png" height="18px" width="auto"> Google </button>-->
</button> <button mat-flat-button class="w-100 mb-1" style="background-color: #efe8e8" (click)="authService.loginWithGoogle()">
</div> <img class="mr-2" src="/assets/images/google.png" height="18px" width="auto"> Google
<div *ngIf="authService.authenticated"> </button>
<button mat-flat-button class="w-100 text-white mb-1 py-2" style="background-color: #dd0330" (click)="authService.logout()"> </div>
<mat-icon>logout</mat-icon> Logout <div *ngIf="authService.authenticated">
</button> <button mat-flat-button class="w-100 text-white mb-1" style="background-color: #dd0330" (click)="authService.logout()">
</div> <mat-icon>logout</mat-icon> Logout
<div> </button>
<hr style="background-color: #FFFFFF90"> </div>
</div> <hr style="background-color: #FFFFFF90">
<div> <div>
<button *ngIf="authService.authenticated" mat-flat-button class="w-100 mb-3 py-2" style="background-color: #efe8e8" (click)="new()"> <button *ngIf="authService.authenticated" mat-flat-button class="w-100 mb-3" style="background-color: #efe8e8" (click)="new()">
<mat-icon>history</mat-icon> Recent <mat-icon>history</mat-icon> Recent
</button> </button>
<button mat-flat-button class="w-100 text-white mb-3 py-2" style="background-color: #dd0330" (click)="new()"> <button mat-flat-button class="w-100 text-white mb-3" style="background-color: #dd0330" (click)="new()">
<mat-icon>add</mat-icon> New Map <mat-icon>add</mat-icon> New Map
</button> </button>
<div class="w-100"> <div class="w-100">
<form class="input-group"> <form class="input-group">
<input type="text" class="form-control" [(ngModel)]="code" name="code" placeholder="Code" (keyup)="isValid()"> <input type="text" class="form-control" [(ngModel)]="code" name="code" placeholder="Code" (keyup)="isValid()">
<div class="input-group-append"> <div class="input-group-append">
<button class="btn btn-danger" [routerLink]="['/', code]" style="background-color: #dd0330" [disabled]="!valid || code.length < 4">Open</button> <button class="btn btn-danger" [routerLink]="['/', code]" style="background-color: #dd0330" [disabled]="!valid || code.length < 4">Open</button>
</div> </div>
<small *ngIf="!valid && code.length > 3" class="mt-2 text-danger">Codes can only be made up of letters and numbers!</small> <small *ngIf="!valid && code.length > 3" class="mt-2 text-danger">Codes can only be made up of letters and numbers!</small>
</form> </form>
</div> </div>
</div> </div>
</div> </div>
<span class="credits text-white p-3"> <span class="credits text-white">
Created By <a href="https://zakscode.com" target="_blank">Zak Timson</a> Created By <a href="https://zakscode.com" target="_blank">Zak Timson</a>
</span> </span>
</div> </div>

View File

@ -1,15 +1,23 @@
.badge {
position: absolute;
top: 15%;
left: 50%;
z-index: 5000;
transform: translateX(-50%);
}
.controls { .controls {
position: absolute; position: absolute;
top: 50%; bottom: 15%;
left: 50%; left: 50%;
width: 200px; width: 200px;
z-index: 5000; z-index: 5000;
transform: translate(-50%, -50%); transform: translateX(-50%);
} }
.credits { .credits {
position: absolute; position: absolute;
bottom: 0; bottom: 2%;
right: 0; right: 2%;
z-index: 5000; z-index: 5000;
} }

View File

@ -2,8 +2,7 @@ import {Component} from "@angular/core";
import {Router} from "@angular/router"; import {Router} from "@angular/router";
import {SyncService} from "../../services/sync.service"; import {SyncService} from "../../services/sync.service";
import {AuthService} from "../../services/auth.service"; import {AuthService} from "../../services/auth.service";
import {fadeIn} from "../../utils/animations"; import {fadeIn} from "../../animations";
import {randomStringBuilder} from '../../utils/string';
const chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'; const chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
@ -22,7 +21,7 @@ export class HomeComponent {
async new() { async new() {
let mapCode: string; let mapCode: string;
do { do {
mapCode = randomStringBuilder(8, true, true); 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]);
} }

View File

@ -1,32 +1,64 @@
#map { #map {
height: 100vh; height: 100vh;
} }
.palette { .palette {
position: fixed; position: fixed;
z-index: 5000; z-index: 5000;
top: 60px; top: 60px;
right: 15px; right: 15px;
} }
.share { .share {
position: fixed; position: fixed;
z-index: 5000; z-index: 5000;
top: 60px; top: 60px;
right: 15px; right: 15px;
} }
.info { .info {
background-color: #00000050; background-color: #00000050;
position: fixed; position: fixed;
z-index: 5000; z-index: 5000;
bottom: 15px; bottom: 15px;
left: 15px; left: 15px;
} }
.gps { .gps {
position: fixed; position: fixed;
z-index: 5000; z-index: 5000;
bottom: 15px; bottom: 15px;
right: 15px; right: 15px;
}
::ng-deep .mat-menu-panel {
background-color: rgba(0, 0, 0, 0.8) !important;
.mat-menu-item {
color: rgba(255, 255, 255, 0.54);
&:hover {
background-color: rgba(90, 90, 90, 0.8) !important;
}
.mat-icon-no-color {
color: rgba(255, 255, 255, 0.54)
}
}
}
::ng-deep .mat-toolbar {
position: absolute;
height: 45px !important;
z-index: 5000;
color: rgba(255, 255, 255, 0.54);
background-color: rgba(0, 0, 0, 0.8);
.selected {
background-color: rgba(75, 75, 75, 0.8);
}
.mat-icon-button:hover {
background-color: rgba(90, 90, 90, 0.8) !important;
}
} }

View File

@ -1,20 +1,19 @@
import {Component, OnDestroy, OnInit} from "@angular/core"; import {Component, OnDestroy, OnInit} from "@angular/core";
import {PermissionsService} from '../../components/permissions/permissions.service';
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 {CalibrateComponent} from "../../components/calibrate/calibrate.component"; import {CalibrateComponent} from "../../components/calibrate/calibrate.component";
import {ToolbarItem} from "../../models/toolbarItem"; import {ToolbarItem} from "../../models/toolbarItem";
import {flyInRight, flyOutRight} from "../../utils/animations"; import {flyInRight, flyOutRight} from "../../animations";
import {MapLayers, MapService, WeatherLayers} from "../../services/map.service"; import {MapLayers, MapService, WeatherLayers} from "../../services/map.service";
import {Subscription} from "rxjs"; import {Subscription} from "rxjs";
import {copyToClipboard, relativeLatLng} from "../../utils/misc"; import {copyToClipboard, relativeLatLng} from "../../utils";
import {ActivatedRoute} from "@angular/router"; import {ActivatedRoute} from "@angular/router";
import {DimensionsDialogComponent} from "../../components/dimensionsDialog/dimensionsDialog.component"; import {DimensionsDialogComponent} from "../../components/dimensionsDialog/dimensionsDialog.component";
import {MatDialog} from "@angular/material/dialog"; import {MatDialog} from "@angular/material/dialog";
import {SyncService} from "../../services/sync.service"; import {SyncService} from "../../services/sync.service";
import {MapData, Marker} from "../../models/mapSymbol"; import {MapData, Marker} from "../../models/mapSymbol";
import {Adjectives} from "../../models/adjectives"; import {Adjectives} from "../../adjectives";
import {Nouns} from "../../models/nounes"; import {Nouns} from "../../nounes";
import {EditSymbolComponent} from "../../components/editSymbol/editSymbol.component"; import {EditSymbolComponent} from "../../components/editSymbol/editSymbol.component";
import {MatSnackBar} from "@angular/material/snack-bar"; import {MatSnackBar} from "@angular/material/snack-bar";
import {MatBottomSheet} from "@angular/material/bottom-sheet"; import {MatBottomSheet} from "@angular/material/bottom-sheet";
@ -35,7 +34,7 @@ export class MapComponent implements OnDestroy, OnInit {
map: MapService; map: MapService;
name: string; name: string;
polygon: any; polygon: any;
position?: {heading: number, altitude: number, speed: number, latitude: number, longitude: number}; position;
positionMarker = {arrow: null, circle: null}; positionMarker = {arrow: null, circle: null};
shareDialog = false; shareDialog = false;
showPalette = false; showPalette = false;
@ -43,7 +42,7 @@ export class MapComponent implements OnDestroy, OnInit {
menu: ToolbarItem[]; menu: ToolbarItem[];
constructor(public physicsService: PhysicsService, private permissionsService: PermissionsService, public syncService: SyncService, private snackBar: MatSnackBar, private bottomSheet: MatBottomSheet, private dialog: MatDialog, private route: ActivatedRoute) { constructor(public physicsService: PhysicsService, public syncService: SyncService, private snackBar: MatSnackBar, private bottomSheet: MatBottomSheet, private dialog: MatDialog, private route: ActivatedRoute) {
this.name = localStorage.getItem('callSign'); this.name = localStorage.getItem('callSign');
if(!this.name) { if(!this.name) {
this.name = Adjectives[Math.floor(Math.random() * Adjectives.length)] + ' ' + Nouns[Math.floor(Math.random() * Nouns.length)]; this.name = Adjectives[Math.floor(Math.random() * Adjectives.length)] + ' ' + Nouns[Math.floor(Math.random() * Nouns.length)];
@ -76,7 +75,7 @@ export class MapComponent implements OnDestroy, OnInit {
{name: 'Sea Level Pressure', toggle: true, click: () => this.map.setWeatherLayer(WeatherLayers.SEA_LEVEL_PRESSURE)}, {name: 'Sea Level Pressure', toggle: true, click: () => this.map.setWeatherLayer(WeatherLayers.SEA_LEVEL_PRESSURE)},
{name: 'Clouds', toggle: true, click: () => this.map.setWeatherLayer(WeatherLayers.CLOUDS_NEW)}, {name: 'Clouds', toggle: true, click: () => this.map.setWeatherLayer(WeatherLayers.CLOUDS_NEW)},
]}, ]},
{name: 'Calibrate', icon: 'explore', click: this.startCalibrating}, {name: 'Calibrate', icon: 'explore', toggle: true, onEnabled: this.startCalibrating, onDisabled: this.unsub},
{name: 'Share', icon: 'share', toggle: true, onEnabled: () => this.share(), onDisabled: () => this.shareDialog = false}, {name: 'Share', icon: 'share', toggle: true, onEnabled: () => this.share(), onDisabled: () => this.shareDialog = false},
{name: 'Messages', icon: 'chat', hidden: true}, {name: 'Messages', icon: 'chat', hidden: true},
{name: 'Identity', icon: 'perm_identity', hidden: true}, {name: 'Identity', icon: 'perm_identity', hidden: true},
@ -100,7 +99,7 @@ export class MapComponent implements OnDestroy, OnInit {
this.syncService.mapData.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) Object.values(map.circles).filter(c => !c.deleted).forEach(c => this.map.newCircle(c)); if (map.circles) Object.values(map.circles).filter(c => !c.deleted).forEach(c => this.map.newCircle(c));
if (map.locations) Object.values(map.locations).forEach(l => this.map.newMarker({...l, icon: 'dot', noDeleteTool: true})); if (map.locations) Object.values(map.locations).forEach(l => this.map.newMarker(Object.assign(l, {icon: 'dot', noDeleteTool: true})));
if (map.markers) Object.values(map.markers).filter(m => !m.deleted).forEach(m => this.map.newMarker(m)); if (map.markers) Object.values(map.markers).filter(m => !m.deleted).forEach(m => this.map.newMarker(m));
if (map.measurements) Object.values(map.measurements).filter(m => !m.deleted).forEach(m => this.map.newMeasurement(m)); if (map.measurements) Object.values(map.measurements).filter(m => !m.deleted).forEach(m => this.map.newMeasurement(m));
if (map.polygons) Object.values(map.polygons).filter(p => !p.deleted).forEach(p => this.map.newPolygon(p)); if (map.polygons) Object.values(map.polygons).filter(p => !p.deleted).forEach(p => this.map.newPolygon(p));
@ -147,7 +146,7 @@ export class MapComponent implements OnDestroy, OnInit {
}); });
} }
center(pos?: any) { center(pos?) {
if (!pos) pos = {lat: this.position.latitude, lng: this.position.longitude}; if (!pos) pos = {lat: this.position.latitude, lng: this.position.longitude};
this.map.centerOn(pos); this.map.centerOn(pos);
} }
@ -178,21 +177,14 @@ export class MapComponent implements OnDestroy, OnInit {
} }
startCalibrating = (menuItem?) => { startCalibrating = (menuItem?) => {
if(this.calibration) { this.calibration = this.bottomSheet.open(CalibrateComponent, {hasBackdrop: false, disableClose: true});
if(this.sub) this.sub.unsubscribe(); this.sub = this.calibration.afterDismissed().pipe(finalize(() => {
this.calibration.dismiss(); menuItem.enabled = false;
this.sub = null; })).subscribe(() => {
this.calibration = null; this.calibration.dismiss();
} else { this.calibration = null;
this.calibration = this.bottomSheet.open(CalibrateComponent, {hasBackdrop: false, disableClose: true}); this.sub = null;
this.sub = this.calibration.afterDismissed().pipe(finalize(() => { });
menuItem.enabled = false;
})).subscribe(() => {
this.calibration.dismiss();
this.calibration = null;
this.sub = null;
});
}
}; };
startCircle = menuItem => { startCircle = menuItem => {

0
src/assets/.gitkeep Normal file
View File

View File

@ -1,6 +1,6 @@
export const environment = { export const environment = {
bing: 'Ah3zXeKgjcCebkqxGBZ4zROLJ_NJm7djhArju4--__5Jg9p19VgCtPkLmv-FxS_C', bing: 'Ah3zXeKgjcCebkqxGBZ4zROLJ_NJm7djhArju4--__5Jg9p19VgCtPkLmv-FxS_C',
firebase: { firebaseConfig: {
apiKey: "AIzaSyDFtvCY6nH_HUoTBNf_5b-E8nRweSLYtxE", apiKey: "AIzaSyDFtvCY6nH_HUoTBNf_5b-E8nRweSLYtxE",
authDomain: "mapalliance-ab38a.firebaseapp.com", authDomain: "mapalliance-ab38a.firebaseapp.com",
databaseURL: "https://mapalliance-ab38a.firebaseio.com", databaseURL: "https://mapalliance-ab38a.firebaseio.com",

View File

@ -1,6 +1,10 @@
// This file can be replaced during build by using the `fileReplacements` array.
// `ng build --prod` replaces `environment.ts` with `environment.prod.ts`.
// The list of file replacements can be found in `angular.json`.
export const environment = { export const environment = {
bing: 'Ah3zXeKgjcCebkqxGBZ4zROLJ_NJm7djhArju4--__5Jg9p19VgCtPkLmv-FxS_C', bing: 'Ah3zXeKgjcCebkqxGBZ4zROLJ_NJm7djhArju4--__5Jg9p19VgCtPkLmv-FxS_C',
firebase: { firebaseConfig: {
apiKey: "AIzaSyDFtvCY6nH_HUoTBNf_5b-E8nRweSLYtxE", apiKey: "AIzaSyDFtvCY6nH_HUoTBNf_5b-E8nRweSLYtxE",
authDomain: "mapalliance-ab38a.firebaseapp.com", authDomain: "mapalliance-ab38a.firebaseapp.com",
databaseURL: "https://mapalliance-ab38a.firebaseio.com", databaseURL: "https://mapalliance-ab38a.firebaseio.com",
@ -13,3 +17,12 @@ export const environment = {
openWeather: 'e8391af54b6fc09dc82b019fc68b8409', openWeather: 'e8391af54b6fc09dc82b019fc68b8409',
production: false production: false
}; };
/*
* For easier debugging in development mode, you can import the following file
* to ignore zone related error stack frames such as `zone.run`, `zoneDelegate.invokeTask`.
*
* This import should be commented out in production mode because it will have a negative impact
* on performance if an error is thrown.
*/
// import 'zone.js/dist/zone-error'; // Included with Angular CLI.

View File

@ -1,37 +1,36 @@
<!doctype html> <!doctype html>
<html lang="en"> <html lang="en">
<head> <head>
<title>Map Alliance</title> <title>Map Alliance</title>
<base href="/"> <base href="/">
<!-- Generic --> <meta charset="utf-8">
<meta charset="utf-8"> <meta name="viewport" content="width=device-width, user-scalable=no">
<meta name="viewport" content="width=device-width, user-scalable=no"> <meta name="theme-color" content="#000000">
<meta name="theme-color" content="#000000"> <meta name="description" content="View & edit maps collaborative with real time GPS positioning.">
<meta name="description" content="View &amp; edit maps collaborative with real time GPS positioning."> <!-- Google / Search Engine Tags -->
<!-- Google / Search Engine Tags --> <meta itemprop="name" content="Map Alliance">
<meta itemprop="name" content="Map Alliance"> <meta itemprop="description" content="View & edit maps collaborative with real time GPS positioning.">
<meta itemprop="description" content="View &amp; edit maps collaborative with real time GPS positioning."> <meta itemprop="image" content="/assets/icons/icon-512x512.png">
<meta itemprop="image" content="/assets/icons/icon-512x512.png"> <!-- Facebook Meta Tags -->
<!-- Facebook Meta Tags --> <meta property="og:type" content="website">
<meta property="og:type" content="website"> <meta property="og:title" content="Map Alliance">
<meta property="og:title" content="Map Alliance"> <meta property="og:description" content="View & edit maps collaborative with real time GPS positioning.">
<meta property="og:description" content="View &amp; edit maps collaborative with real time GPS positioning."> <meta property="og:image" content="/assets/icons/icon-512x512.png">
<meta property="og:image" content="/assets/icons/icon-512x512.png"> <!-- Twitter Meta Tags -->
<!-- Twitter Meta Tags --> <meta name="twitter:card" content="summary_large_image">
<meta name="twitter:card" content="summary_large_image"> <meta name="twitter:title" content="Map Alliance">
<meta name="twitter:title" content="Map Alliance"> <meta name="twitter:description" content="View & edit maps collaborative with real time GPS positioning.">
<meta name="twitter:description" content="View &amp; edit maps collaborative with real time GPS positioning."> <meta name="twitter:image" content="/assets/icons/icon-512x512.png">
<meta name="twitter:image" content="/assets/icons/icon-512x512.png">
<link rel="icon" type="image/x-icon" href="/assets/images/logo.png"> <link rel="icon" type="image/x-icon" href="/assets/images/logo.png">
<link rel="manifest" href="manifest.json"> <link href="https://fonts.googleapis.com/css?family=Roboto:300,400,500|Material+Icons" rel="stylesheet">
<link rel="manifest" href="manifest.webmanifest">
<script src="https://maps.googleapis.com/maps/api/js?key=AIzaSyDFtvCY6nH_HUoTBNf_5b-E8nRweSLYtxE"></script> <script src="https://maps.googleapis.com/maps/api/js?key=AIzaSyDFtvCY6nH_HUoTBNf_5b-E8nRweSLYtxE" async defer></script>
<meta name="theme-color" content="#1976d2">
</head> </head>
<body> <body>
<app-root></app-root> <app-root></app-root>
<noscript>Please enable JavaScript to continue using this application.</noscript> <noscript>Please enable JavaScript to continue using this application.</noscript>
</body> </body>
</html> </html>

View File

@ -1,58 +1,61 @@
import {platformBrowserDynamic} from '@angular/platform-browser-dynamic';
import {initializeApp} from 'firebase/app';
import 'hammerjs'; import 'hammerjs';
import {enableProdMode} from '@angular/core';
import {platformBrowserDynamic} from '@angular/platform-browser-dynamic';
import {AppModule} from './app/app.module'; import {AppModule} from './app/app.module';
import {environment} from './environments/environment'; import {environment} from './environments/environment';
initializeApp(environment.firebase); if (environment.production) {
enableProdMode();
}
platformBrowserDynamic().bootstrapModule(AppModule) platformBrowserDynamic().bootstrapModule(AppModule)
.catch(err => console.error(err)); .catch(err => console.error(err));
// Leaflet touch polyfill =========================================== // leaflet ===========================================
declare const L; declare const L;
// Touch support // Touch support
L.Map.mergeOptions({touchExtend: true}); L.Map.mergeOptions({touchExtend: true});
L.Map.TouchExtend = L.Handler.extend({ L.Map.TouchExtend = L.Handler.extend({
initialize: function (map) { initialize: function (map) {
this._map = map; this._map = map;
this._container = map._container; this._container = map._container;
this._pane = map._panes.overlayPane; this._pane = map._panes.overlayPane;
}, },
addHooks: function () { addHooks: function () {
L.DomEvent.on(this._container, 'touchstart', this._onTouchStart, this); L.DomEvent.on(this._container, 'touchstart', this._onTouchStart, this);
L.DomEvent.on(this._container, 'touchend', this._onTouchEnd, this); L.DomEvent.on(this._container, 'touchend', this._onTouchEnd, this);
L.DomEvent.on(this._container, 'touchmove', this._onTouchMove, this); L.DomEvent.on(this._container, 'touchmove', this._onTouchMove, this);
}, },
removeHooks: function () { removeHooks: function () {
L.DomEvent.off(this._container, 'touchstart', this._onTouchStart); L.DomEvent.off(this._container, 'touchstart', this._onTouchStart);
L.DomEvent.off(this._container, 'touchend', this._onTouchEnd); L.DomEvent.off(this._container, 'touchend', this._onTouchEnd);
L.DomEvent.off(this._container, 'touchmove', this._onTouchMove); L.DomEvent.off(this._container, 'touchmove', this._onTouchMove);
}, },
_eventWrapper: function(e) { _eventWrapper: function(e) {
let containerPoint = this._map.mouseEventToContainerPoint(e); let containerPoint = this._map.mouseEventToContainerPoint(e);
let layerPoint = this._map.containerPointToLayerPoint(containerPoint); let layerPoint = this._map.containerPointToLayerPoint(containerPoint);
let latlng = this._map.layerPointToLatLng(layerPoint); let latlng = this._map.layerPointToLatLng(layerPoint);
return { return {
latlng: latlng, latlng: latlng,
layerPoint: layerPoint, layerPoint: layerPoint,
containerPoint: containerPoint, containerPoint: containerPoint,
originalEvent: e originalEvent: e
} }
}, },
_onTouchStart: function (e) { _onTouchStart: function (e) {
if (!this._map._loaded) return; if (!this._map._loaded) return;
this._map.fire('touchstart', this._eventWrapper(e)); this._map.fire('touchstart', this._eventWrapper(e));
}, },
_onTouchEnd: function (e) { _onTouchEnd: function (e) {
if (!this._map._loaded) return; if (!this._map._loaded) return;
this._map.fire('touchend', this._eventWrapper(e)); this._map.fire('touchend', this._eventWrapper(e));
}, },
_onTouchMove: function(e) { _onTouchMove: function(e) {
if(!this._map._loaded) return; if(!this._map._loaded) return;
this._map.fire('touchmove', this._eventWrapper(e)); this._map.fire('touchmove', this._eventWrapper(e));
} }
}); });
L.Map.addInitHook('addHandler', 'touchExtend', L.Map.TouchExtend); L.Map.addInitHook('addHandler', 'touchExtend', L.Map.TouchExtend);

View File

@ -1,51 +0,0 @@
{
"name": "Map Alliance",
"short_name": "Map Alliance",
"start_url": "/",
"scope": "/",
"display": "standalone",
"theme_color": "#000000",
"background_color": "#000000",
"icons": [
{
"src": "assets/icons/icon-72x72.png",
"sizes": "72x72",
"type": "image/png"
},
{
"src": "assets/icons/icon-96x96.png",
"sizes": "96x96",
"type": "image/png"
},
{
"src": "assets/icons/icon-128x128.png",
"sizes": "128x128",
"type": "image/png"
},
{
"src": "assets/icons/icon-144x144.png",
"sizes": "144x144",
"type": "image/png"
},
{
"src": "assets/icons/icon-152x152.png",
"sizes": "152x152",
"type": "image/png"
},
{
"src": "assets/icons/icon-192x192.png",
"sizes": "192x192",
"type": "image/png"
},
{
"src": "assets/icons/icon-384x384.png",
"sizes": "384x384",
"type": "image/png"
},
{
"src": "assets/icons/icon-512x512.png",
"sizes": "512x512",
"type": "image/png"
}
]
}

51
src/manifest.webmanifest Normal file
View File

@ -0,0 +1,51 @@
{
"name": "Map Alliance",
"short_name": "Map Alliance",
"theme_color": "#000000",
"background_color": "#000000",
"display": "standalone",
"scope": "/",
"start_url": "/",
"icons": [
{
"src": "assets/icons/icon-72x72.png",
"sizes": "72x72",
"type": "image/png"
},
{
"src": "assets/icons/icon-96x96.png",
"sizes": "96x96",
"type": "image/png"
},
{
"src": "assets/icons/icon-128x128.png",
"sizes": "128x128",
"type": "image/png"
},
{
"src": "assets/icons/icon-144x144.png",
"sizes": "144x144",
"type": "image/png"
},
{
"src": "assets/icons/icon-152x152.png",
"sizes": "152x152",
"type": "image/png"
},
{
"src": "assets/icons/icon-192x192.png",
"sizes": "192x192",
"type": "image/png"
},
{
"src": "assets/icons/icon-384x384.png",
"sizes": "384x384",
"type": "image/png"
},
{
"src": "assets/icons/icon-512x512.png",
"sizes": "512x512",
"type": "image/png"
}
]
}

63
src/polyfills.ts Normal file
View File

@ -0,0 +1,63 @@
/**
* This file includes polyfills needed by Angular and is loaded before the app.
* You can add your own extra polyfills to this file.
*
* This file is divided into 2 sections:
* 1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers.
* 2. Application imports. Files imported after ZoneJS that should be loaded before your main
* file.
*
* The current setup is for so-called "evergreen" browsers; the last versions of browsers that
* automatically update themselves. This includes Safari >= 10, Chrome >= 55 (including Opera),
* Edge >= 13 on the desktop, and iOS 10 and Chrome on mobile.
*
* Learn more in https://angular.io/guide/browser-support
*/
/***************************************************************************************************
* BROWSER POLYFILLS
*/
/** IE10 and IE11 requires the following for NgClass support on SVG elements */
import 'classlist.js'; // Run `npm install --save classlist.js`.
/**
* Web Animations `@angular/platform-browser/animations`
* Only required if AnimationBuilder is used within the application and using IE/Edge or Safari.
* Standard animation support in Angular DOES NOT require any polyfills (as of Angular 6.0).
*/
import 'web-animations-js'; // Run `npm install --save web-animations-js`.
/**
* By default, zone.js will patch all possible macroTask and DomEvents
* user can disable parts of macroTask/DomEvents patch by setting following flags
* because those flags need to be set before `zone.js` being loaded, and webpack
* will put import in the top of bundle, so user need to create a separate file
* in this directory (for example: zone-flags.ts), and put the following flags
* into that file, and then add the following code before importing zone.js.
* import './zone-flags.ts';
*
* The flags allowed in zone-flags.ts are listed here.
*
* The following flags will work for all browsers.
*
* (window as any).__Zone_disable_requestAnimationFrame = true; // disable patch requestAnimationFrame
* (window as any).__Zone_disable_on_property = true; // disable patch onProperty such as onclick
* (window as any).__zone_symbol__UNPATCHED_EVENTS = ['scroll', 'mousemove']; // disable patch specified eventNames
*
* in IE/Edge developer tools, the addEventListener will also be wrapped by zone.js
* with the following flag, it will bypass `zone.js` patch for IE/Edge
*
* (window as any).__Zone_enable_cross_context_check = true;
*
*/
/***************************************************************************************************
* Zone JS is required by default for Angular itself.
*/
import 'zone.js/dist/zone'; // Included with Angular CLI.
import 'whatwg-fetch'; // Run `npm install --save whatwg-fetch`
/***************************************************************************************************
* APPLICATION IMPORTS
*/

View File

@ -1,130 +1,101 @@
@use '@angular/material' as mat; @import '../node_modules/@angular/material/theming';
@import url("https://fonts.googleapis.com/css?family=Roboto:300,400,500|Material+Icons");
@include mat.core(); @include mat-core();
$my-theme: mat.define-light-theme((
color: ( $custom-theme-primary: mat-palette($mat-blue, 600);
primary: mat.define-palette(mat.$blue-palette, 600), $custom-theme-accent: mat-palette($mat-red, 600);
accent: mat.define-palette(mat.$red-palette, 600), $custom-theme-warn: mat-palette($mat-orange, 600);
),
typography: mat.define-typography-config(), $custom-theme: mat-light-theme($custom-theme-primary, $custom-theme-accent, $custom-theme-warn);
density: 0,
)); @include angular-material-theme($custom-theme);
@include mat.all-component-themes($my-theme);
:focus { :focus {
outline: none !important; outline: none !important;
} }
html, body { html, body {
height: 100vh; height: 100vh;
overflow: hidden; overflow: hidden;
} }
body { body {
margin: 0; margin: 0;
overscroll-behavior: none; overscroll-behavior: none;
font-family: Roboto, "Helvetica Neue", sans-serif; font-family: Roboto, "Helvetica Neue", sans-serif;
background-color: black; background-color: black;
} }
.curs-pointer { .curs-pointer {
cursor: pointer; cursor: pointer;
} }
.d-relative { .d-relative {
position: relative; position: relative;
} }
.overflow-hidden { .overflow-hidden {
overflow: hidden; overflow: hidden;
} }
.cdk-overlay-pane.p-0 { .cdk-overlay-pane.p-0 {
.mat-dialog-container { .mat-dialog-container {
padding: 0; padding: 0;
overflow: hidden; overflow: hidden;
} }
} }
.cdk-overlay-pane.pb-0 { .cdk-overlay-pane.pb-0 {
.mat-dialog-container { .mat-dialog-container {
padding-bottom: 0; padding-bottom: 0;
overflow: hidden; overflow: hidden;
} }
} }
.cdk-overlay-container { a[href^="http://maps.google.com/maps"]{display:none !important}
z-index: 5500 !important; a[href^="https://maps.google.com/maps"]{display:none !important}
}
.mat-mdc-menu-panel {
background-color: rgba(0, 0, 0, 0.8) !important;
.mat-mdc-menu-item {
color: rgba(255, 255, 255, 0.54);
&:hover {
background-color: rgba(90, 90, 90, 0.8) !important;
}
.mat-icon-no-color {
color: rgba(255, 255, 255, 0.54)
}
}
}
// Hide watermarks =============================================================
.leaflet-tooltip {
background: rgba(0, 0, 0, 0.6) !important;
border-color: rgba(0, 0, 0, 0.6) !important;
color: white;
&.leaflet-tooltip-right:before {
border-right-color: rgba(0, 0, 0, 0.6) !important;
}
&.leaflet-tooltip-bottom:before {
border-bottom-color: rgba(0, 0, 0, 0.6) !important;
}
&.leaflet-tooltip-top:before {
border-top-color: rgba(0, 0, 0, 0.6) !important;
}
&.leaflet-tooltip-left:before {
border-left-color: rgba(0, 0, 0, 0.6) !important;
}
}
.leaflet-tooltip-bottom:before {
display: none;
}
.leaflet-popup-content-wrapper {
background: rgba(0, 0, 0, 0.6) !important;
color: white !important;
}
.leaflet-popup-tip {
background: rgba(0, 0, 0, 0.6) !important;
}
a[href^="http://maps.google.com/maps"] {
display: none !important
}
a[href^="https://maps.google.com/maps"] {
display: none !important
}
.gmnoprint a, .gmnoprint span, .gm-style-cc { .gmnoprint a, .gmnoprint span, .gm-style-cc {
display: none; display: none;
} }
.gmnoprint div { .gmnoprint div {
background: none !important; background: none !important;
} }
.cdk-overlay-container {
z-index: 5500 !important;
}
.leaflet-tooltip {
background: rgba(0, 0, 0, 0.6) !important;
border-color: rgba(0, 0, 0, 0.6) !important;
color: white;
&.leaflet-tooltip-right:before {
border-right-color: rgba(0, 0, 0, 0.6) !important;
}
&.leaflet-tooltip-bottom:before {
border-bottom-color: rgba(0, 0, 0, 0.6) !important;
}
&.leaflet-tooltip-top:before {
border-top-color: rgba(0, 0, 0, 0.6) !important;
}
&.leaflet-tooltip-left:before {
border-left-color: rgba(0, 0, 0, 0.6) !important;
}
}
.leaflet-tooltip-bottom:before {
display: none;
}
.leaflet-popup-content-wrapper {
background: rgba(0, 0, 0, 0.6) !important;
color: white !important;
}
.leaflet-popup-tip {
background: rgba(0, 0, 0, 0.6) !important;
}

View File

@ -1,42 +1,30 @@
/* To learn more about this file see: https://angular.io/config/tsconfig. */
{ {
"compileOnSave": false, "compileOnSave": false,
"compilerOptions": { "compilerOptions": {
"outDir": "./out-tsc/app", "baseUrl": "./",
"forceConsistentCasingInFileNames": true, "outDir": "./out-tsc/app",
"strict": true, "resolveJsonModule": true,
"noImplicitOverride": true, "sourceMap": true,
"noPropertyAccessFromIndexSignature": true, "declaration": false,
"noImplicitReturns": true, "downlevelIteration": true,
"noFallthroughCasesInSwitch": true, "module": "esnext",
"skipLibCheck": true, "moduleResolution": "node",
"esModuleInterop": true, "experimentalDecorators": true,
"sourceMap": true, "importHelpers": true,
"noImplicitAny": false, "target": "es2015",
"declaration": false, "typeRoots": [
"experimentalDecorators": true, "node_modules/@types"
"moduleResolution": "node", ],
"importHelpers": true, "lib": [
"target": "ES2022", "es2018",
"module": "ES2022", "dom"
"useDefineForClassFields": false, ],
"strictNullChecks": false, "include": [
"resolveJsonModule": true, "src/**/*.ts"
"lib": [ ],
"ES2022", "exclude": [
"dom" "src/test.ts",
] "src/**/*.spec.ts"
}, ]
"angularCompilerOptions": { }
"enableI18nLegacyMessageIdFormat": false,
"strictInjectionParameters": true,
"strictInputAccessModifiers": true,
"strictTemplates": true
},
"files": [
"src/main.ts"
],
"include": [
"src/**/*.d.ts"
]
} }