Compare commits

..

7 commits

Author SHA1 Message Date
4c4dc3fc5c Display frontend and backend version 2024-12-25 17:09:41 +01:00
5ff197ad49 Fix link in README doc 2024-12-24 03:49:04 +01:00
c890800454 Initial README doc 2024-12-24 03:46:27 +01:00
ea82f02f51 Fix background opacity adjustment 2024-12-24 03:33:22 +01:00
230dddbfd6 Bump @maplibre/ngx-maplibre-gl 2024-12-23 13:40:34 +01:00
5479be90b8 Bump Angular to 18 2024-12-23 13:17:31 +01:00
7c9a057d32 Cleanup and update packages 2024-12-23 13:00:57 +01:00
10 changed files with 2490 additions and 2734 deletions

View file

@ -1,31 +1,7 @@
# GisafApp # Gisaf frontend
This project was generated with [angular-cli](https://github.com/angular/angular-cli) version 1.0.0-beta.24. The web app (front-end, user interface) for Gisaf,
a web based GIS initially developed for CSR Geomatics, Auroville.
## Development server See the [Gisaf server repository](https://code.philo.ydns.eu/philorg/gisaf-backend)
Run `ng serve` for a dev server. Navigate to `http://localhost:4200/`. The app will automatically reload if you change any of the source files. for information about this project.
## Code scaffolding
Run `ng generate component component-name` to generate a new component. You can also use `ng generate directive/pipe/service/class/module`.
## Build
Run `ng build` to build the project. The build artifacts will be stored in the `dist/` directory. Use the `-prod` flag for a production build.
## Running unit tests
Run `ng test` to execute the unit tests via [Karma](https://karma-runner.github.io).
## Running end-to-end tests
Run `ng e2e` to execute the end-to-end tests via [Protractor](http://www.protractortest.org/).
Before running the tests make sure you are serving the app via `ng serve`.
## Deploying to Github Pages
Run `ng github-pages:deploy` to deploy to Github Pages.
## Further help
To get more help on the `angular-cli` use `ng help` or go check out the [Angular-CLI README](https://github.com/angular/angular-cli/blob/master/README.md).

View file

@ -28,54 +28,53 @@
} }
], ],
"dependencies": { "dependencies": {
"@angular/animations": "^17.3.5", "@angular/animations": "^18.2.13",
"@angular/cdk": "^17.3.5", "@angular/cdk": "^18.2.14",
"@angular/common": "^17.3.5", "@angular/common": "^18.2.13",
"@angular/compiler": "^17.3.5", "@angular/compiler": "^18.2.13",
"@angular/core": "^17.3.5", "@angular/core": "^18.2.13",
"@angular/forms": "^17.3.5", "@angular/forms": "^18.2.13",
"@angular/material": "^17.3.5", "@angular/material": "^18.2.14",
"@angular/platform-browser": "^17.3.5", "@angular/platform-browser": "^18.2.13",
"@angular/platform-browser-dynamic": "^17.3.5", "@angular/platform-browser-dynamic": "^18.2.13",
"@angular/platform-server": "^17.3.5", "@angular/platform-server": "^18.2.13",
"@angular/router": "^17.3.5", "@angular/router": "^18.2.13",
"@mapbox/point-geometry": "^0.1.0", "@mapbox/point-geometry": "^0.1.0",
"@maplibre/ngx-maplibre-gl": "^17.4.3", "@maplibre/ngx-maplibre-gl": "^18.1.4",
"@turf/bbox": "^7.1.0", "@turf/bbox": "^7.1.0",
"@turf/distance": "^7.1.0", "@turf/distance": "^7.1.0",
"@turf/helpers": "^7.1.0", "@turf/helpers": "^7.1.0",
"angular-plotly.js": "^5.2.2", "angular-plotly.js": "^5.2.2",
"maplibre-gl": "^4.1.3", "maplibre-gl": "^4.7.1",
"ngx-flexible-layout": "~17.0.4", "ngx-flexible-layout": "~18.0.2",
"plotly.js-basic-dist-min": "2.31.1", "plotly.js-basic-dist-min": "2.31.1",
"rxjs": "^7.8.1", "rxjs": "^7.8.1",
"ts-helpers": "^1.1.2", "ts-helpers": "^1.1.2",
"zone.js": "~0.14.4" "zone.js": "~0.14.10"
}, },
"devDependencies": { "devDependencies": {
"@angular-devkit/build-angular": "^17.3.5", "@angular-devkit/build-angular": "^18.2.12",
"@angular/cli": "^17.3.5", "@angular/cli": "^18.2.12",
"@angular/compiler-cli": "^17.3.5", "@angular/compiler-cli": "^18.2.13",
"@angular/language-service": "^17.3.5", "@angular/language-service": "^18.2.13",
"@hey-api/openapi-ts": "^0.45", "@hey-api/openapi-ts": "^0.45.1",
"@types/geojson": "^7946.0.14", "@types/geojson": "^7946.0.15",
"@types/jasmine": "~5.1.4", "@types/jasmine": "~5.1.5",
"@types/jasminewd2": "^2.0.13", "@types/jasminewd2": "^2.0.13",
"@types/node": "^20.12.7", "@types/node": "^20.17.10",
"@types/plotly.js-dist-min": "^2.3.4", "@types/plotly.js-dist-min": "^2.3.4",
"codelyzer": "^6.0.2",
"eslint": "^9.17.0", "eslint": "^9.17.0",
"fontnik": "^0.7.2", "fontnik": "^0.7.2",
"jasmine-core": "~5.1.2", "jasmine-core": "~5.1.2",
"jasmine-spec-reporter": "~7.0.0", "jasmine-spec-reporter": "~7.0.0",
"karma": "~6.4.3", "karma": "~6.4.4",
"karma-chrome-launcher": "~3.2.0", "karma-chrome-launcher": "~3.2.0",
"karma-cli": "^2.0.0", "karma-cli": "^2.0.0",
"karma-coverage-istanbul-reporter": "~3.0.3", "karma-coverage-istanbul-reporter": "~3.0.3",
"karma-jasmine": "~5.1.0", "karma-jasmine": "~5.1.0",
"source-map-explorer": "^2.5.3", "source-map-explorer": "^2.5.3",
"ts-node": "^10.9.2", "ts-node": "^10.9.2",
"tslib": "^2.6.2", "tslib": "^2.8.1",
"typescript": "~5.4.5" "typescript": "~5.4.5"
}, },
"packageManager": "pnpm@9.15", "packageManager": "pnpm@9.15",

4852
pnpm-lock.yaml generated

File diff suppressed because it is too large Load diff

View file

@ -2,7 +2,7 @@
<mat-toolbar fxFlex="2em" id='top-toolbar'> <mat-toolbar fxFlex="2em" id='top-toolbar'>
<span <span
style='font-family:GisafSymbols' style='font-family:GisafSymbols'
matTooltip="Gisaf v. {{ (configService.conf | async).bsData?.version }}" matTooltip="Gisaf version: backend {{ version.backend }}, frontend {{ version.frontend }}"
matTooltipPosition="below" matTooltipPosition="below"
class='gisafIcon' class='gisafIcon'
> >
@ -47,4 +47,4 @@
<mat-icon>account_circle</mat-icon> <mat-icon>account_circle</mat-icon>
</a> </a>
</nav> </nav>
</ng-template> </ng-template>

View file

@ -1,5 +1,7 @@
import { Component, OnInit, import {
ChangeDetectionStrategy, ChangeDetectorRef } from '@angular/core' Component, OnInit,
ChangeDetectionStrategy, ChangeDetectorRef
} from '@angular/core'
import { Title } from '@angular/platform-browser' import { Title } from '@angular/platform-browser'
import { BootstrapService } from './_services/bootstrap.service' import { BootstrapService } from './_services/bootstrap.service'
import { ConfigService } from './_services/config.service' import { ConfigService } from './_services/config.service'
@ -8,80 +10,87 @@ import { MatDialog, MatDialogRef } from '@angular/material/dialog'
import { AuthenticationService } from './_services/authentication.service' import { AuthenticationService } from './_services/authentication.service'
import { LoginDialogComponent } from './login/login.component' import { LoginDialogComponent } from './login/login.component'
import versionJson from '../version.json'
export class Version {
public backend: string
public frontend: string
}
@Component({ @Component({
selector: 'app-root', selector: 'app-root',
templateUrl: './app.component.html', templateUrl: './app.component.html',
styleUrls: ['./app.component.css'], styleUrls: ['./app.component.css'],
changeDetection: ChangeDetectionStrategy.OnPush, changeDetection: ChangeDetectionStrategy.OnPush,
}) })
export class AppComponent implements OnInit { export class AppComponent implements OnInit {
title = 'Gisaf' title: string = 'Gisaf'
version: string version: Version = new Version()
routes = [ routes = [
{ {
'target': 'dashboard', 'target': 'dashboard',
'icon': 'home', 'icon': 'home',
'text': 'Home', 'text': 'Home',
}, },
{ {
'target': 'map', 'target': 'map',
'icon': 'map', 'icon': 'map',
'text': 'Map', 'text': 'Map',
}, },
{ {
'target': 'measures', 'target': 'measures',
'icon': 'insert_chart', 'icon': 'insert_chart',
'text': 'Measures', 'text': 'Measures',
}, },
] ]
constructor( constructor(
public configService: ConfigService, public configService: ConfigService,
private titleService: Title, private titleService: Title,
private bootstrapService: BootstrapService, private bootstrapService: BootstrapService,
public authenticationService: AuthenticationService, public authenticationService: AuthenticationService,
private snackBar: MatSnackBar, private snackBar: MatSnackBar,
private cdr: ChangeDetectorRef, private cdr: ChangeDetectorRef,
public dialogRef: MatDialogRef<LoginDialogComponent>, public dialogRef: MatDialogRef<LoginDialogComponent>,
public dialog: MatDialog public dialog: MatDialog
) {} ) { }
ngOnInit() { ngOnInit() {
// Bootstrap: set app wide configuration // Bootstrap: set app wide configuration
this.bootstrapService.get().subscribe({ this.bootstrapService.get().subscribe({
next: res => { next: res => {
this.version = res.version this.version.backend = res.version
this.title = res.title || this.title this.version.frontend = versionJson["version"]
this.titleService.setTitle(res.windowTitle || this.title) this.title = res.title || this.title
this.configService.setConf(res) this.titleService.setTitle(res.windowTitle || this.title)
if (res.redirect && (window != window.top)) { this.configService.setConf(res)
// Refusing to be embedded in an iframe if (res.redirect && (window != window.top)) {
let loc = res.redirect + window.location.pathname // Refusing to be embedded in an iframe
window.document.body.innerHTML = ` let loc = res.redirect + window.location.pathname
window.document.body.innerHTML = `
The web site you are visiting is trying to embed Gisaf (${this.title}) in an iFrame, it isn't a nice thing to do.</br> The web site you are visiting is trying to embed Gisaf (${this.title}) in an iFrame, it isn't a nice thing to do.</br>
Please click this link to go to the real site: <span style='cursor:pointer' onclick="window.open('${loc}')"><b>${loc}</b></span> Please click this link to go to the real site: <span style='cursor:pointer' onclick="window.open('${loc}')"><b>${loc}</b></span>
` `
} }
}, },
error: err => { error: err => {
this.snackBar.open( this.snackBar.open(
'Cannot connect to the server (' + err.statusText + '). Please retry later.', 'Cannot connect to the server (' + err.statusText + '). Please retry later.',
'OK' 'OK'
) )
} }
}) })
} }
openLoginDialog() { openLoginDialog() {
const dialogRef = this.dialog.open(LoginDialogComponent, { const dialogRef = this.dialog.open(LoginDialogComponent, {
height: '24em', height: '24em',
width: '21em' width: '21em'
}) })
// dialogRef.afterClosed().subscribe( // dialogRef.afterClosed().subscribe(
// result => {} // result => {}
// ) // )
} }
} }

View file

@ -2,7 +2,7 @@ import { BrowserModule } from '@angular/platform-browser'
import { BrowserAnimationsModule } from '@angular/platform-browser/animations' import { BrowserAnimationsModule } from '@angular/platform-browser/animations'
import { NgModule, LOCALE_ID } from '@angular/core' import { NgModule, LOCALE_ID } from '@angular/core'
import { FormsModule } from '@angular/forms' import { FormsModule } from '@angular/forms'
import { HttpClientModule } from '@angular/common/http' import { provideHttpClient, withInterceptorsFromDi } from '@angular/common/http'
import { MatButtonModule } from '@angular/material/button' import { MatButtonModule } from '@angular/material/button'
import { MatIconModule } from '@angular/material/icon' import { MatIconModule } from '@angular/material/icon'
@ -32,34 +32,26 @@ import { HtmlSnackbarComponent } from './custom-snackbar/custom-snackbar.compone
import { AppRoutingModule } from './app-routing.module' import { AppRoutingModule } from './app-routing.module'
@NgModule({ @NgModule({ declarations: [
declarations: [
AppComponent, AppComponent,
PageNotFoundComponent, PageNotFoundComponent,
HtmlSnackbarComponent, HtmlSnackbarComponent,
], ],
imports: [ bootstrap: [
AppComponent
], imports: [
// ApolloModule, // ApolloModule,
BrowserModule, BrowserModule,
BrowserAnimationsModule, BrowserAnimationsModule,
FormsModule, FormsModule,
HttpClientModule,
MatToolbarModule, MatToolbarModule,
MatButtonModule, MatButtonModule,
MatIconModule, MatIconModule,
MatSnackBarModule, MatSnackBarModule,
MatTooltipModule, MatTooltipModule,
FlexLayoutModule, FlexLayoutModule,
AppRoutingModule, AppRoutingModule,
LoginModule, LoginModule], providers: [
],
providers: [
ActionsService, ActionsService,
AuthenticationService, AuthenticationService,
BootstrapService, BootstrapService,
@ -75,9 +67,6 @@ import { AppRoutingModule } from './app-routing.module'
provide: LOCALE_ID, provide: LOCALE_ID,
useValue: "en-IN" useValue: "en-IN"
}, },
], provideHttpClient(withInterceptorsFromDi()),
bootstrap: [ ] })
AppComponent
]
})
export class AppModule {} export class AppModule {}

View file

@ -197,21 +197,35 @@ export class GisafMapboxComponent implements OnInit, OnDestroy {
} }
_getNewSingleOpacity(layerId: string, originalOpacity: number | object): number | object { _getNewSingleOpacity(layerId: string, originalOpacity: number | object): number | object {
if (typeof (originalOpacity) === 'number') { if (originalOpacity === undefined) {
return undefined
}
else if (typeof originalOpacity === 'number') {
return originalOpacity * this._baseStyleOpacity return originalOpacity * this._baseStyleOpacity
} }
else { else if (typeof originalOpacity === 'object') { // Also matches array
let newOpacity = {} if (originalOpacity[0] == 'interpolate') {
for (const k in originalOpacity) { // Change the last interpolation point
let v = originalOpacity[k] originalOpacity[Object.values(originalOpacity).length - 1] = this._baseStyleOpacity
if (k == 'stops') { return originalOpacity
newOpacity[k] = v.map(stop => [stop[0], stop[1] * this._baseStyleOpacity])
}
else {
newOpacity[k] = v
}
} }
return newOpacity else {
let newOpacity = {}
for (const k in originalOpacity) {
let v = originalOpacity[k]
if (k == 'stops') {
newOpacity[k] = v.map(stop => [stop[0], stop[1] * this._baseStyleOpacity])
}
else {
newOpacity[k] = v
}
}
return newOpacity
}
}
else {
console.log(`Cannot process opacity of layer ${layerId}, unknown type ${typeof originalOpacity}`)
return originalOpacity
} }
} }
@ -219,25 +233,30 @@ export class GisafMapboxComponent implements OnInit, OnDestroy {
let originalStyle = this.originalBaseStyle.style['layers'].find( let originalStyle = this.originalBaseStyle.style['layers'].find(
(_layer: object) => layer['id'] == _layer['id'] (_layer: object) => layer['id'] == _layer['id']
) )
if (!('paint' in originalStyle)) { let originalPaint = originalStyle['paint'] || {}
originalStyle['paint'] = {} if (layer['type'] == 'symbol') {
}
if (['raster', 'background', 'fill', 'line'].indexOf(layer['type']) != -1) {
let prop = layer['type'] + '-opacity'
return {
[prop]: this._getNewSingleOpacity(layer['id'], originalStyle['paint'][prop] || 1.0)
}
}
else {
let prop1 = 'text-opacity' let prop1 = 'text-opacity'
let prop2 = 'icon-opacity' let prop2 = 'icon-opacity'
let newOpacity1 = this._getNewSingleOpacity(layer['id'], originalStyle['paint'][prop1] || 1.0) let newOpacity1 = this._getNewSingleOpacity(layer['id'], originalPaint[prop1] || 1.0)
let newOpacity2 = this._getNewSingleOpacity(layer['id'], originalStyle['paint'][prop2] || 1.0) let newOpacity2 = this._getNewSingleOpacity(layer['id'], originalPaint[prop2] || 1.0)
return { return {
[prop1]: newOpacity1, [prop1]: newOpacity1,
[prop2]: newOpacity2, [prop2]: newOpacity2,
} }
} }
else if (layer['type'] == 'line') {
let prop = layer['type'] + '-opacity'
return {
[prop]: this._getNewSingleOpacity(layer['id'], originalPaint[prop] || 1.0)
}
}
else {
// layer['type'] in ['raster', 'background', 'fill', 'line', 'fill-extrusion']
let prop = layer['type'] + '-opacity'
return {
[prop]: this._getNewSingleOpacity(layer['id'], originalPaint[prop] || 1.0),
}
}
} }
applyBaseStyleOpacity() { applyBaseStyleOpacity() {
@ -247,7 +266,9 @@ export class GisafMapboxComponent implements OnInit, OnDestroy {
continue continue
} }
for (const [key, value] of Object.entries(this._getNewOpacity(bsLayer))) { for (const [key, value] of Object.entries(this._getNewOpacity(bsLayer))) {
this.map.setPaintProperty(bsLayer.id, key, value) if (value !== undefined) {
this.map.setPaintProperty(bsLayer.id, key, value)
}
} }
} }
} }

View file

@ -1,6 +1,4 @@
import { ElementRef, Input, ViewChild, Directive, HostListener } from '@angular/core' import { ElementRef, Input, ViewChild, Directive, HostListener, OutputRefSubscription } from '@angular/core'
import { Subscription } from 'rxjs'
import { MatButton } from '@angular/material/button' import { MatButton } from '@angular/material/button'
@ -41,13 +39,13 @@ export class GisafRulerDirective {
@Input() secondaryColor: string = 'white' @Input() secondaryColor: string = 'white'
@Input() fontHalo: number = 2 @Input() fontHalo: number = 2
@ViewChild(MatButton) button: MatButton @ViewChild(MatButton) button: MatButton
clickSubscription: Subscription clickSubscription: OutputRefSubscription
lineDrawn: boolean lineDrawn: boolean
constructor( constructor(
public elementRef: ElementRef, public elementRef: ElementRef,
private mapService: MapService private mapService: MapService
) {} ) { }
@HostListener('click') @HostListener('click')
onClick() { onClick() {
@ -103,7 +101,7 @@ export class GisafRulerDirective {
*/ */
labelFormat(n) { labelFormat(n) {
return n == 0 ? '0' : n < 1 ? `${(n * 1000).toFixed(2)} m`: `${n.toFixed(3)} km` return n == 0 ? '0' : n < 1 ? `${(n * 1000).toFixed(2)} m` : `${n.toFixed(3)} km`
} }
onMapClick(evt: MapMouseEvent) { onMapClick(evt: MapMouseEvent) {
@ -149,9 +147,9 @@ export class GisafRulerDirective {
addLayers() { addLayers() {
const map = this.mapService.mapInstance const map = this.mapService.mapInstance
map.addSource(SOURCE_LINE, { map.addSource(SOURCE_LINE, {
type: 'geojson', type: 'geojson',
data: this.getLineStringFeatures() data: this.getLineStringFeatures()
} }
) )
map.addSource(SOURCE_SYMBOL, { map.addSource(SOURCE_SYMBOL, {

3
src/version.json Normal file
View file

@ -0,0 +1,3 @@
{
"version": "0.0.0"
}

View file

@ -1,27 +1,28 @@
{ {
"compileOnSave": false, "compileOnSave": false,
"compilerOptions": { "compilerOptions": {
"importHelpers": true,
"module": "es2020",
"outDir": "./dist/out-tsc",
"sourceMap": true,
"declaration": false, "declaration": false,
"moduleResolution": "node", "esModuleInterop": true,
"experimentalDecorators": true, "experimentalDecorators": true,
"target": "ES2022", "importHelpers": true,
"typeRoots": [
"node_modules/@types",
"@types"
],
"lib": [ "lib": [
"es2019", "es2019",
"dom", "dom",
"esnext.asynciterable" "esnext.asynciterable"
], ],
"allowSyntheticDefaultImports": true, "module": "es2020",
"moduleResolution": "node",
"resolveJsonModule": true,
"outDir": "./dist/out-tsc",
"sourceMap": true,
"target": "ES2022",
"typeRoots": [
"node_modules/@types",
"@types"
],
"useDefineForClassFields": false "useDefineForClassFields": false
}, },
"angularCompilerOptions": { "angularCompilerOptions": {
"strictTemplates": true "strictTemplates": true
} }
} }