Merge pull request 'Talkgroup icons' (#48) from icons into trunk
Reviewed-on: #48
This commit is contained in:
commit
7c9bb74a67
9 changed files with 168 additions and 41 deletions
10
client/admin/package-lock.json
generated
10
client/admin/package-lock.json
generated
|
@ -18,6 +18,7 @@
|
||||||
"@angular/router": "^18.2.0",
|
"@angular/router": "^18.2.0",
|
||||||
"@ng-icons/core": "^29.6.1",
|
"@ng-icons/core": "^29.6.1",
|
||||||
"@ng-icons/ionicons": "^29.6.1",
|
"@ng-icons/ionicons": "^29.6.1",
|
||||||
|
"@ng-icons/material-icons": "^29.10.0",
|
||||||
"rxjs": "~7.8.0",
|
"rxjs": "~7.8.0",
|
||||||
"tslib": "^2.3.0",
|
"tslib": "^2.3.0",
|
||||||
"zone.js": "~0.14.10"
|
"zone.js": "~0.14.10"
|
||||||
|
@ -3624,6 +3625,15 @@
|
||||||
"tslib": "^2.3.0"
|
"tslib": "^2.3.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@ng-icons/material-icons": {
|
||||||
|
"version": "29.10.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@ng-icons/material-icons/-/material-icons-29.10.0.tgz",
|
||||||
|
"integrity": "sha512-zmOJQeDpCoISxmmlzYbBMwHrmMz4RnT0UNOxWbfSSlgU8+XTHUUbVomS72ltFkkgY/rt5VF8rnp2ILmmDeZZWA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"tslib": "^2.3.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@ngtools/webpack": {
|
"node_modules/@ngtools/webpack": {
|
||||||
"version": "18.2.12",
|
"version": "18.2.12",
|
||||||
"resolved": "https://registry.npmjs.org/@ngtools/webpack/-/webpack-18.2.12.tgz",
|
"resolved": "https://registry.npmjs.org/@ngtools/webpack/-/webpack-18.2.12.tgz",
|
||||||
|
|
|
@ -20,6 +20,7 @@
|
||||||
"@angular/router": "^18.2.0",
|
"@angular/router": "^18.2.0",
|
||||||
"@ng-icons/core": "^29.6.1",
|
"@ng-icons/core": "^29.6.1",
|
||||||
"@ng-icons/ionicons": "^29.6.1",
|
"@ng-icons/ionicons": "^29.6.1",
|
||||||
|
"@ng-icons/material-icons": "^29.10.0",
|
||||||
"rxjs": "~7.8.0",
|
"rxjs": "~7.8.0",
|
||||||
"tslib": "^2.3.0",
|
"tslib": "^2.3.0",
|
||||||
"zone.js": "~0.14.10"
|
"zone.js": "~0.14.10"
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { ApplicationConfig, provideZoneChangeDetection } from '@angular/core';
|
import { ApplicationConfig, provideZoneChangeDetection } from '@angular/core';
|
||||||
import { provideRouter, withDebugTracing } from '@angular/router';
|
import { provideRouter, } from '@angular/router';
|
||||||
import { environment } from './../environments/environment';
|
import { environment } from './../environments/environment';
|
||||||
import {
|
import {
|
||||||
HttpRequest,
|
HttpRequest,
|
||||||
|
@ -43,7 +43,7 @@ export function authIntercept(
|
||||||
export const appConfig: ApplicationConfig = {
|
export const appConfig: ApplicationConfig = {
|
||||||
providers: [
|
providers: [
|
||||||
provideZoneChangeDetection({ eventCoalescing: true }),
|
provideZoneChangeDetection({ eventCoalescing: true }),
|
||||||
provideRouter(routes, withDebugTracing()),
|
provideRouter(routes),
|
||||||
provideHttpClient(withInterceptors([apiBaseInterceptor, authIntercept])),
|
provideHttpClient(withInterceptors([apiBaseInterceptor, authIntercept])),
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
|
@ -31,23 +31,61 @@ export interface System {
|
||||||
|
|
||||||
export interface Metadata {
|
export interface Metadata {
|
||||||
encrypted: boolean | null;
|
encrypted: boolean | null;
|
||||||
|
icon: string | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Talkgroup {
|
export interface IconMap {
|
||||||
id: number;
|
[name: string]: string;
|
||||||
system_id: number;
|
}
|
||||||
tgid: number;
|
|
||||||
name: string;
|
export const iconMapping: IconMap = {
|
||||||
alpha_tag: string;
|
'police': 'matLocalPoliceOutline',
|
||||||
tg_group: string;
|
'fire': 'matFireTruckOutline',
|
||||||
frequency: number;
|
'ems': 'matEmergencyOutline',
|
||||||
metadata: Metadata | null;
|
'bus': 'matDirectionsBusOutline',
|
||||||
tags: string[];
|
'': 'matGroupWorkOutline',
|
||||||
alert: boolean;
|
};
|
||||||
|
|
||||||
|
export class Talkgroup {
|
||||||
|
id!: number;
|
||||||
|
system_id!: number;
|
||||||
|
tgid!: number;
|
||||||
|
name!: string;
|
||||||
|
alpha_tag!: string;
|
||||||
|
tg_group!: string;
|
||||||
|
frequency!: number;
|
||||||
|
metadata!: Metadata | null;
|
||||||
|
tags!: string[];
|
||||||
|
alert!: boolean;
|
||||||
system?: System;
|
system?: System;
|
||||||
alert_config: AlertRule[];
|
alert_config!: AlertRule[];
|
||||||
weight: number;
|
weight!: number;
|
||||||
learned?: boolean;
|
learned?: boolean;
|
||||||
|
icon?: string;
|
||||||
|
iconSvg?: string;
|
||||||
|
constructor(
|
||||||
|
id: number,
|
||||||
|
system_id: number,
|
||||||
|
tgid: number,
|
||||||
|
name: string,
|
||||||
|
alpha_tag: string,
|
||||||
|
tg_group: string,
|
||||||
|
frequency: number,
|
||||||
|
metadata: Metadata | null,
|
||||||
|
tags: string[],
|
||||||
|
alert: boolean,
|
||||||
|
alert_config: AlertRule[],
|
||||||
|
weight: number,
|
||||||
|
system?: System,
|
||||||
|
learned?: boolean,
|
||||||
|
icon?: string,
|
||||||
|
) {
|
||||||
|
this.iconSvg = this.iconMap(this.metadata?.icon!);
|
||||||
|
}
|
||||||
|
|
||||||
|
iconMap(icon: string): string {
|
||||||
|
return iconMapping[icon]!;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface TalkgroupUI extends Talkgroup {
|
export interface TalkgroupUI extends Talkgroup {
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<div class="flex">
|
<div class="flex">
|
||||||
<form [formGroup]="form" (ngSubmit)="submit()">
|
<form *ngIf="tg" [formGroup]="form" (ngSubmit)="submit()">
|
||||||
<div class="p-1 columns-2">
|
<div class="p-1 columns-2">
|
||||||
<label class="w-full" for="name">Name: </label
|
<label class="w-full" for="name">Name: </label
|
||||||
><input
|
><input
|
||||||
|
@ -7,7 +7,6 @@
|
||||||
type="text"
|
type="text"
|
||||||
class="w-full input input-bordered"
|
class="w-full input input-bordered"
|
||||||
formControlName="name"
|
formControlName="name"
|
||||||
[(ngModel)]="tg.name"
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="p-1 columns-2">
|
<div class="p-1 columns-2">
|
||||||
|
@ -17,7 +16,6 @@
|
||||||
type="text"
|
type="text"
|
||||||
class="w-full input input-bordered"
|
class="w-full input input-bordered"
|
||||||
formControlName="alpha_tag"
|
formControlName="alpha_tag"
|
||||||
[(ngModel)]="tg.alpha_tag"
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="p-1 columns-2">
|
<div class="p-1 columns-2">
|
||||||
|
@ -27,7 +25,6 @@
|
||||||
type="text"
|
type="text"
|
||||||
class="w-full input input-bordered"
|
class="w-full input input-bordered"
|
||||||
formControlName="tg_group"
|
formControlName="tg_group"
|
||||||
[(ngModel)]="tg.tg_group"
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="p-1 columns-2">
|
<div class="p-1 columns-2">
|
||||||
|
@ -37,7 +34,6 @@
|
||||||
type="text"
|
type="text"
|
||||||
class="w-full input input-bordered"
|
class="w-full input input-bordered"
|
||||||
formControlName="frequency"
|
formControlName="frequency"
|
||||||
[(ngModel)]="tg.frequency"
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="p-1 columns-2">
|
<div class="p-1 columns-2">
|
||||||
|
@ -47,7 +43,6 @@
|
||||||
id="alert"
|
id="alert"
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
formControlName="alert"
|
formControlName="alert"
|
||||||
[(ngModel)]="tg.alert"
|
|
||||||
class="checkbox"
|
class="checkbox"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
@ -59,10 +54,23 @@
|
||||||
type="text"
|
type="text"
|
||||||
class="w-full input input-bordered"
|
class="w-full input input-bordered"
|
||||||
formControlName="weight"
|
formControlName="weight"
|
||||||
[(ngModel)]="tg.weight"
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
<div>Rules:</div>
|
||||||
<alert-rule-builder [rules]="tg.alert_config" />
|
<alert-rule-builder [rules]="tg.alert_config" />
|
||||||
|
<div>
|
||||||
|
<label for="icon">Icon: </label>
|
||||||
|
<select
|
||||||
|
class="select"
|
||||||
|
name="icon"
|
||||||
|
id="icon"
|
||||||
|
formControlName="icon"
|
||||||
|
>
|
||||||
|
@for(opt of iconMapping | keyvalue; track opt) {
|
||||||
|
<option value="{{ opt.key }}" [selected]="opt.key == tg.metadata?.icon">{{ opt.key | titlecase }}</option>
|
||||||
|
}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
<input type="submit" class="btn btn-secondary" value="Save" />
|
<input type="submit" class="btn btn-secondary" value="Save" />
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { Component, inject } from '@angular/core';
|
import { Component, inject } from '@angular/core';
|
||||||
import { Talkgroup, TalkgroupUpdate } from '../../talkgroup';
|
import { Talkgroup, TalkgroupUpdate, IconMap, iconMapping } from '../../talkgroup';
|
||||||
import { TalkgroupService } from '../talkgroups.service';
|
import { TalkgroupService } from '../talkgroups.service';
|
||||||
import { AlertRuleBuilderComponent } from './alert-rule-builder/alert-rule-builder.component';
|
import { AlertRuleBuilderComponent } from './alert-rule-builder/alert-rule-builder.component';
|
||||||
import { CommonModule } from '@angular/common';
|
import { CommonModule } from '@angular/common';
|
||||||
|
@ -22,6 +22,7 @@ import { Observable } from 'rxjs';
|
||||||
})
|
})
|
||||||
export class TalkgroupRecordComponent {
|
export class TalkgroupRecordComponent {
|
||||||
tg!: Talkgroup;
|
tg!: Talkgroup;
|
||||||
|
iconMapping: IconMap = iconMapping;
|
||||||
tgService: TalkgroupService = inject(TalkgroupService);
|
tgService: TalkgroupService = inject(TalkgroupService);
|
||||||
|
|
||||||
form!: FormGroup;
|
form!: FormGroup;
|
||||||
|
@ -39,16 +40,19 @@ export class TalkgroupRecordComponent {
|
||||||
.getTalkgroup(Number(sysId), Number(tgId))
|
.getTalkgroup(Number(sysId), Number(tgId))
|
||||||
.subscribe((data: Talkgroup) => {
|
.subscribe((data: Talkgroup) => {
|
||||||
this.tg = data;
|
this.tg = data;
|
||||||
|
this.form = new FormGroup({
|
||||||
|
name: new FormControl(this.tg.name),
|
||||||
|
alpha_tag: new FormControl(this.tg.alpha_tag),
|
||||||
|
tg_group: new FormControl(this.tg.tg_group),
|
||||||
|
frequency: new FormControl(this.tg.frequency),
|
||||||
|
alert: new FormControl(this.tg.alert),
|
||||||
|
weight: new FormControl(this.tg.weight),
|
||||||
|
icon: new FormControl(this.tg.icon),
|
||||||
|
});
|
||||||
|
console.log(this.tg.icon);
|
||||||
});
|
});
|
||||||
|
|
||||||
this.form = new FormGroup({
|
|
||||||
name: new FormControl(''),
|
|
||||||
alpha_tag: new FormControl(''),
|
|
||||||
tg_group: new FormControl(''),
|
|
||||||
frequency: new FormControl(0),
|
|
||||||
alert: new FormControl(true),
|
|
||||||
weight: new FormControl(1.0),
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
submit() {
|
submit() {
|
||||||
|
@ -58,22 +62,32 @@ export class TalkgroupRecordComponent {
|
||||||
id: this.tg.id,
|
id: this.tg.id,
|
||||||
};
|
};
|
||||||
if (this.form.controls['name'].dirty) {
|
if (this.form.controls['name'].dirty) {
|
||||||
tgu.name = this.tg.name;
|
tgu.name = this.form.controls['name'].value;
|
||||||
}
|
}
|
||||||
if (this.form.controls['alpha_tag'].dirty) {
|
if (this.form.controls['alpha_tag'].dirty) {
|
||||||
tgu.alpha_tag = this.tg.alpha_tag;
|
tgu.alpha_tag = this.form.controls['alpha_tag'].value;
|
||||||
}
|
}
|
||||||
if (this.form.controls['tg_group'].dirty) {
|
if (this.form.controls['tg_group'].dirty) {
|
||||||
tgu.tg_group = this.tg.tg_group;
|
tgu.tg_group = this.form.controls['tg_group'].value;
|
||||||
}
|
}
|
||||||
if (this.form.controls['frequency'].dirty) {
|
if (this.form.controls['frequency'].dirty) {
|
||||||
tgu.frequency = this.tg.frequency;
|
tgu.frequency = this.form.controls['frequency'].value;
|
||||||
}
|
}
|
||||||
if (this.form.controls['alert'].dirty) {
|
if (this.form.controls['alert'].dirty) {
|
||||||
tgu.alert = this.tg.alert;
|
tgu.alert = this.form.controls['alert'].value;
|
||||||
}
|
}
|
||||||
if (this.form.controls['weight'].dirty) {
|
if (this.form.controls['weight'].dirty) {
|
||||||
tgu.weight = Number(this.tg.weight);
|
tgu.weight = Number(this.form.controls['weight'].value);
|
||||||
|
}
|
||||||
|
if (this.form.controls['icon'].dirty) {
|
||||||
|
if (tgu.metadata == null) {
|
||||||
|
tgu.metadata = {};
|
||||||
|
}
|
||||||
|
if (this.tg.icon == null || this.tg.icon == '') {
|
||||||
|
tgu.metadata = Object.assign(tgu.metadata, {icon: undefined});
|
||||||
|
} else {
|
||||||
|
tgu.metadata = Object.assign(tgu.metadata!, { icon: this.form.controls['icon'] });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
this.tgService
|
this.tgService
|
||||||
.putTalkgroup(tgu)
|
.putTalkgroup(tgu)
|
||||||
|
|
|
@ -0,0 +1,2 @@
|
||||||
|
.tgIcon {
|
||||||
|
}
|
|
@ -3,20 +3,27 @@
|
||||||
<table class="table">
|
<table class="table">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
|
<th></th>
|
||||||
<th>Sys</th>
|
<th>Sys</th>
|
||||||
<th>Sys ID</th>
|
<th>Sys ID</th>
|
||||||
|
<th>Group</th>
|
||||||
<th>Name</th>
|
<th>Name</th>
|
||||||
|
<th>Alpha</th>
|
||||||
<th>TG ID</th>
|
<th>TG ID</th>
|
||||||
<th>Learned</th>
|
<th>Learned</th>
|
||||||
<th></th>
|
<th></th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
@for (tg of talkgroups$ | async; track tg.id) {
|
@for (tg of talkgroups$ | async ; track tg.id) {
|
||||||
<tr>
|
<tr>
|
||||||
|
<!-- <td class="tgIcon" [innerHTML]="(tg | iconify).iconSvg! | sanitizeHtml"></td> -->
|
||||||
|
<td class="tgIcon"><ng-icon [name]="(tg | iconify).iconSvg!"></ng-icon></td>
|
||||||
<td>{{ tg.system?.name }}</td>
|
<td>{{ tg.system?.name }}</td>
|
||||||
<td>{{ tg.system?.id }}</td>
|
<td>{{ tg.system?.id }}</td>
|
||||||
|
<td>{{ tg.tg_group }}</td>
|
||||||
<td>{{ tg.name }}</td>
|
<td>{{ tg.name }}</td>
|
||||||
|
<td>{{ tg.alpha_tag }}</td>
|
||||||
<td>{{ tg.tgid }}</td>
|
<td>{{ tg.tgid }}</td>
|
||||||
<td>{{ tg?.learned ? "Y" : "" }}</td>
|
<td>{{ tg?.learned ? "Y" : "" }}</td>
|
||||||
<td>
|
<td>
|
||||||
|
|
|
@ -1,13 +1,52 @@
|
||||||
import { Component, inject } from '@angular/core';
|
import { Component, inject, Pipe, PipeTransform } from '@angular/core';
|
||||||
import { TalkgroupService } from './talkgroups.service';
|
import { TalkgroupService } from './talkgroups.service';
|
||||||
import { Talkgroup } from '../talkgroup';
|
|
||||||
import { NgIconComponent, provideIcons } from '@ng-icons/core';
|
import { NgIconComponent, provideIcons } from '@ng-icons/core';
|
||||||
import { ionCreateOutline } from '@ng-icons/ionicons';
|
import { ionCreateOutline } from '@ng-icons/ionicons';
|
||||||
|
import {
|
||||||
|
matFireTruckOutline,
|
||||||
|
matLocalPoliceOutline,
|
||||||
|
matEmergencyOutline,
|
||||||
|
matDirectionsBusOutline,
|
||||||
|
matGroupWorkOutline,
|
||||||
|
} from '@ng-icons/material-icons/outline';
|
||||||
|
import { Talkgroup, iconMapping } from '../talkgroup';
|
||||||
import { ActivatedRoute } from '@angular/router';
|
import { ActivatedRoute } from '@angular/router';
|
||||||
import { Observable } from 'rxjs';
|
import { Observable } from 'rxjs';
|
||||||
import { switchMap } from 'rxjs/operators';
|
import { switchMap } from 'rxjs/operators';
|
||||||
import { RouterModule, RouterOutlet, RouterLink } from '@angular/router';
|
import { RouterModule, RouterOutlet, RouterLink } from '@angular/router';
|
||||||
import { CommonModule } from '@angular/common';
|
import { CommonModule } from '@angular/common';
|
||||||
|
import { DomSanitizer, SafeHtml } from '@angular/platform-browser';
|
||||||
|
|
||||||
|
|
||||||
|
@Pipe({
|
||||||
|
standalone: true,
|
||||||
|
name: 'iconify',
|
||||||
|
})
|
||||||
|
export class IconifyPipe implements PipeTransform {
|
||||||
|
transform(value: Talkgroup): Talkgroup {
|
||||||
|
if (value?.metadata?.icon != null) {
|
||||||
|
value.iconSvg = iconMapping[value?.metadata?.icon];
|
||||||
|
} else if (value?.metadata?.icon == undefined) {
|
||||||
|
value.iconSvg = iconMapping[''];
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Pipe({
|
||||||
|
standalone: true,
|
||||||
|
name: 'sanitizeHtml'
|
||||||
|
})
|
||||||
|
export class SanitizeHtmlPipe implements PipeTransform {
|
||||||
|
|
||||||
|
constructor(private _sanitizer:DomSanitizer) {
|
||||||
|
}
|
||||||
|
|
||||||
|
transform(v:string):SafeHtml {
|
||||||
|
return this._sanitizer.bypassSecurityTrustHtml(v);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'talkgroups',
|
selector: 'talkgroups',
|
||||||
|
@ -18,10 +57,18 @@ import { CommonModule } from '@angular/common';
|
||||||
RouterModule,
|
RouterModule,
|
||||||
RouterLink,
|
RouterLink,
|
||||||
CommonModule,
|
CommonModule,
|
||||||
|
IconifyPipe,
|
||||||
|
SanitizeHtmlPipe,
|
||||||
],
|
],
|
||||||
templateUrl: './talkgroups.component.html',
|
templateUrl: './talkgroups.component.html',
|
||||||
styleUrl: './talkgroups.component.css',
|
styleUrl: './talkgroups.component.css',
|
||||||
providers: [provideIcons({ ionCreateOutline })],
|
providers: [provideIcons({ ionCreateOutline,
|
||||||
|
matFireTruckOutline,
|
||||||
|
matLocalPoliceOutline,
|
||||||
|
matEmergencyOutline,
|
||||||
|
matDirectionsBusOutline,
|
||||||
|
matGroupWorkOutline,
|
||||||
|
})],
|
||||||
})
|
})
|
||||||
export class TalkgroupsComponent {
|
export class TalkgroupsComponent {
|
||||||
selectedSys: number = 0;
|
selectedSys: number = 0;
|
||||||
|
|
Loading…
Reference in a new issue