Compare commits
No commits in common. "develop" and "1.7.8" have entirely different histories.
@ -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
|
|
33
.github/workflows/functions.yaml
vendored
33
.github/workflows/functions.yaml
vendored
@ -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
|
||||||
|
83
.github/workflows/website.yaml
vendored
83
.github/workflows/website.yaml
vendored
@ -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}}
|
||||||
|
22
Dockerfile
22
Dockerfile
@ -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
|
|
19
README.md
19
README.md
@ -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
|
||||||
[](https://angular.io/)
|
[](https://angular.io/)
|
||||||
[](https://docker.com/)
|
|
||||||
[](https://firebase.google.com/)
|
[](https://firebase.google.com/)
|
||||||
[](https://typescriptlang.org/)
|
[](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>
|
||||||
|
|
||||||
|
174
angular.json
174
angular.json
@ -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
12
browserslist
Normal 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'.
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,2 +0,0 @@
|
|||||||
User-Agent: *
|
|
||||||
Allow: /
|
|
@ -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"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
@ -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
20535
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
111
package.json
111
package.json
@ -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"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -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();
|
|
||||||
})
|
|
||||||
})();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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 {
|
||||||
|
}
|
||||||
|
@ -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>
|
||||||
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -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>
|
||||||
|
@ -1,3 +1,3 @@
|
|||||||
.mat-form-field-infix {
|
::ng-deep .mat-form-field-infix {
|
||||||
width: auto !important;
|
width: auto !important;
|
||||||
}
|
}
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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>
|
||||||
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1 +1,2 @@
|
|||||||
export interface Map { }
|
export interface Map {
|
||||||
|
}
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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);
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
|
|
@ -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);
|
|
||||||
}
|
|
@ -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>
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
@ -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]);
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -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
0
src/assets/.gitkeep
Normal 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",
|
||||||
|
@ -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.
|
||||||
|
@ -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 & 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 & 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 & 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 & 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>
|
||||||
|
89
src/main.ts
89
src/main.ts
@ -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);
|
||||||
|
@ -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
51
src/manifest.webmanifest
Normal 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
63
src/polyfills.ts
Normal 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
|
||||||
|
*/
|
165
src/styles.scss
165
src/styles.scss
@ -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;
|
||||||
|
}
|
||||||
|
@ -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"
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user