Version 4.8.0

This commit is contained in:
Chrystian Huot 2020-10-25 10:53:08 -04:00
parent 41e31c5d41
commit 45edec5931
16 changed files with 807 additions and 5951 deletions

View file

@ -1,3 +1,12 @@
# Version 4.8
- Add downstream.system.id_as property to allow export system with a different id.
- Add system.order for system list ordering on the client side.
- Fix client main screen unscrollable overflow while in landscape.
- Fix issue 26 - date in documentation for mask isn't clear.
- The skip button now also allows you to skip the one second delay between calls.
- Node modules update.
# Version 4.7
- New dirWatch.type='sdr-trunk'.

941
client/package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -1,29 +1,29 @@
{
"author": "Chrystian Huot <chrystian.huot@saubeo.solutions>",
"dependencies": {
"@angular/animations": "^10.1.5",
"@angular/cdk": "^10.2.4",
"@angular/common": "~10.1.5",
"@angular/compiler": "~10.1.5",
"@angular/core": "~10.1.5",
"@angular/forms": "~10.1.5",
"@angular/material": "^10.2.4",
"@angular/platform-browser": "~10.1.5",
"@angular/platform-browser-dynamic": "~10.1.5",
"@angular/service-worker": "~10.1.5",
"@angular/animations": "^10.2.0",
"@angular/cdk": "^10.2.5",
"@angular/common": "~10.2.0",
"@angular/compiler": "~10.2.0",
"@angular/core": "~10.2.0",
"@angular/forms": "~10.2.0",
"@angular/material": "^10.2.5",
"@angular/platform-browser": "~10.2.0",
"@angular/platform-browser-dynamic": "~10.2.0",
"@angular/service-worker": "~10.2.0",
"rxjs": "~6.6.3",
"tslib": "^2.0.3",
"zone.js": "~0.10.3"
"zone.js": "~0.11.2"
},
"devDependencies": {
"@angular-devkit/build-angular": "~0.1001.6",
"@angular/cli": "^10.1.6",
"@angular/compiler-cli": "~10.1.5",
"@types/node": "~14.11.8",
"@angular-devkit/build-angular": "~0.1002.0",
"@angular/cli": "^10.2.0",
"@angular/compiler-cli": "~10.2.0",
"@types/node": "~14.14.2",
"codelyzer": "^6.0.1",
"ts-node": "~9.0.0",
"tslint": "~6.1.3",
"typescript": "~3.9.7"
"typescript": "~4.0.3"
},
"license": "LICENSE",
"name": "rdio-scanner-client",
@ -32,5 +32,5 @@
"build": "ng build --base-href ./ --prod",
"start": "ng serve"
},
"version": "4.7.6"
"version": "4.8.0"
}

View file

@ -0,0 +1,20 @@
/*
* *****************************************************************************
* Copyright (C) 2019-2020 Chrystian Huot
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
* ****************************************************************************
*/
export * from './rdio-scanner.module';

View file

@ -1,6 +1,7 @@
:host {
box-sizing: border-box;
display: block;
max-height: 100%;
max-width: 640px;
min-width: 0;
padding: 24px;

View file

@ -17,7 +17,8 @@
* ****************************************************************************
*/
import { Component, ElementRef, EventEmitter, HostListener, OnDestroy, Output } from '@angular/core';
import { Component, ElementRef, HostListener, OnDestroy, ViewChild } from '@angular/core';
import { MatSidenav } from '@angular/material/sidenav';
import { RdioScannerEvent, RdioScannerLivefeedMode } from './rdio-scanner';
import { AppRdioScannerService } from './rdio-scanner.service';
@ -27,12 +28,14 @@ import { AppRdioScannerService } from './rdio-scanner.service';
templateUrl: './rdio-scanner.component.html',
})
export class AppRdioScannerComponent implements OnDestroy {
@Output() readonly live = new EventEmitter<boolean>();
private eventSubscription = this.appRdioScannerService.event.subscribe((event: RdioScannerEvent) => this.eventHandler(event));
private livefeedMode: RdioScannerLivefeedMode = RdioScannerLivefeedMode.Offline;
@ViewChild('searchPanel') private searchPanel: MatSidenav | undefined;
@ViewChild('selectPanel') private selectPanel: MatSidenav | undefined;
constructor(
private appRdioScannerService: AppRdioScannerService,
private ngElementRef: ElementRef,
@ -47,10 +50,20 @@ export class AppRdioScannerComponent implements OnDestroy {
}
}
start(): void {
this.appRdioScannerService.startLivefeed();
}
stop(): void {
this.appRdioScannerService.stopLivefeed();
this.searchPanel?.close();
this.selectPanel?.close();
}
ngOnDestroy(): void {
this.eventSubscription.unsubscribe();
this.live.complete();
}
toggleFullscreen(): void {
@ -64,10 +77,13 @@ export class AppRdioScannerComponent implements OnDestroy {
if (el.exitFullscreen) {
el.exitFullscreen();
} else if (el.mozCancelFullScreen) {
el.mozCancelFullScreen();
} else if (el.msExitFullscreen) {
el.msExitFullscreen();
} else if (el.webkitExitFullscreen) {
el.webkitExitFullscreen();
}
@ -77,10 +93,13 @@ export class AppRdioScannerComponent implements OnDestroy {
if (el.requestFullscreen) {
el.requestFullscreen();
} else if (el.mozRequestFullScreen) {
el.mozRequestFullScreen();
} else if (el.msRequestFullscreen) {
el.msRequestFullscreen();
} else if (el.webkitRequestFullscreen) {
el.webkitRequestFullscreen();
}

View file

@ -18,7 +18,7 @@
*/
import { DOCUMENT } from '@angular/common';
import { EventEmitter, Inject, Injectable, OnDestroy } from '@angular/core';
import { EventEmitter, Inject, Injectable, OnDestroy, SystemJsNgModuleLoader } from '@angular/core';
import { interval, Subscription, timer } from 'rxjs';
import { takeWhile } from 'rxjs/operators';
import {
@ -209,7 +209,7 @@ export class AppRdioScannerService implements OnDestroy {
});
}
holdSystem(options?: { resubscribe: boolean }): void {
holdSystem(options?: { resubscribe?: boolean }): void {
const call = this.call || this.callPrevious;
if (call && this.livefeedMap) {
@ -263,7 +263,7 @@ export class AppRdioScannerService implements OnDestroy {
}
}
holdTalkgroup(options?: { resubscribe: boolean }): void {
holdTalkgroup(options?: { resubscribe?: boolean }): void {
const call = this.call || this.callPrevious;
if (call && this.livefeedMap) {
@ -345,6 +345,14 @@ export class AppRdioScannerService implements OnDestroy {
if (this.livefeedMode === RdioScannerLivefeedMode.Offline) {
this.livefeedMode = RdioScannerLivefeedMode.Playback;
if (this.livefeedMapPriorToHoldSystem) {
this.holdSystem({ resubscribe: false });
}
if (this.livefeedMapPriorToHoldTalkgroup) {
this.holdTalkgroup({ resubscribe: false });
}
this.event.emit({ livefeedMode: this.livefeedMode, playbackPending: id });
} else if (this.livefeedMode === RdioScannerLivefeedMode.Playback) {
@ -489,10 +497,24 @@ export class AppRdioScannerService implements OnDestroy {
});
} else {
if (this.skipDelay) {
this.skipDelay?.unsubscribe();
this.skipDelay = undefined;
}
play();
}
}
startLivefeed(): void {
this.livefeedMode = RdioScannerLivefeedMode.Online;
this.event.emit({ livefeedMode: this.livefeedMode });
this.sendtoWebsocket(WebsocketCommand.LivefeedMap, this.livefeedMap);
}
stop(options?: { emit?: boolean }): void {
if (this.audioSource) {
this.audioSource.onended = null;
@ -513,6 +535,18 @@ export class AppRdioScannerService implements OnDestroy {
}
}
stopLivefeed(): void {
this.livefeedMode = RdioScannerLivefeedMode.Offline;
this.clearQueue();
this.event.emit({ livefeedMode: this.livefeedMode, queue: 0 });
this.stop();
this.sendtoWebsocket(WebsocketCommand.LivefeedMap, null);
}
stopPlaybackMode(): void {
this.livefeedMode = RdioScannerLivefeedMode.Offline;
@ -745,7 +779,7 @@ export class AppRdioScannerService implements OnDestroy {
dimmerDelay: typeof config.dimmerDelay === 'number' ? config.dimmerDelay : 5000,
groups: typeof config.groups !== null && typeof config.groups === 'object' ? config.groups : {},
keypadBeeps: config.keypadBeeps !== null && typeof config.keypadBeeps === 'object' ? config.keypadBeeps : {},
systems: Array.isArray(config.systems) ? config.systems : [],
systems: Array.isArray(config.systems) ? config.systems.slice() : [],
tags: typeof config.tags !== null && typeof config.tags === 'object' ? config.tags : {},
};
@ -905,26 +939,6 @@ export class AppRdioScannerService implements OnDestroy {
}
}
private startLivefeed(): void {
this.livefeedMode = RdioScannerLivefeedMode.Online;
this.event.emit({ livefeedMode: this.livefeedMode });
this.sendtoWebsocket(WebsocketCommand.LivefeedMap, this.livefeedMap);
}
private stopLivefeed(): void {
this.livefeedMode = RdioScannerLivefeedMode.Offline;
this.clearQueue();
this.event.emit({ livefeedMode: this.livefeedMode, queue: 0 });
this.stop();
this.sendtoWebsocket(WebsocketCommand.LivefeedMap, null);
}
private storeLivefeedMap(): void {
window?.localStorage?.setItem(AppRdioScannerService.LOCAL_STORAGE_KEY, JSON.stringify(this.livefeedMap));
}
@ -933,6 +947,10 @@ export class AppRdioScannerService implements OnDestroy {
if (Array.isArray(this.config?.systems)) {
call.systemData = this.config.systems.find((system) => system.id === call.system);
if (!call.systemData) {
console.log(call.system, call.systemData);
}
if (Array.isArray(call.systemData?.talkgroups)) {
call.talkgroupData = call.systemData?.talkgroups.find((talkgroup) => talkgroup.id === call.talkgroup);
}

View file

@ -146,6 +146,7 @@ export interface RdioScannerSystem {
id: number;
label: string;
led?: 'blue' | 'cyan' | 'green' | 'magenta' | 'red' | 'white' | 'yellow';
order?: number;
talkgroups: RdioScannerTalkgroup[];
units?: RdioScannerUnit[];
}

View file

@ -60,7 +60,7 @@ export class AppRdioScannerSelectComponent implements OnDestroy {
private eventHandler(event: RdioScannerEvent): void {
if (event.config) {
this.systems = event.config.systems;
this.systems = this.sortSystems(event.config.systems);
}
if (event.groups) {
@ -71,4 +71,21 @@ export class AppRdioScannerSelectComponent implements OnDestroy {
this.map = event.map;
}
}
private sortSystems(systems: RdioScannerSystem[]): RdioScannerSystem[] {
return systems.sort((sysA, sysB) => {
if (typeof sysA.order === 'number' && typeof sysB.order !== 'number') {
return -1;
} else if (typeof sysA.order !== 'number' && typeof sysB.order === 'number') {
return 1;
} else if (typeof sysA.order === 'number' && typeof sysB.order === 'number') {
return sysA.order - sysB.order;
} else {
return sysA.id - sysB.id;
}
});
}
}

View file

@ -322,7 +322,7 @@ dirWatch: {
- **frequency** - You may want to fake the frequency which will be displayed on _Rdio Scanner_. Let say that you are recording an AM frequency from _RTLSDR-Airband_, here you would put that frequency.
- **frequency** - You may want to simulate the frequency that will be displayed. Say you are recording an AM frequency from _RTLSDR-Airband_, here you would put that frequency.
- **mask** - Some metadata can be extracted from the file name of the audio file using specific META tags. You may define a simple mask for the dirWatch or an array of masks. Here is the list of possible META tags:
- **#DATE** - extract the date like _20200608_ or _2020-06-08_.
- **#DATE** - extract the date like _20201231 (YYYYMMMDD)_ or _2020-12-31 (YYYY-MM-DD)_.
- **#HZ** - extract the frequency in hertz like _119100000_.
- **#KHZ** - extract the frequency in kilohertz like _119100_.
- **#MHZ** - extract the frequency in megahertz like _119.100_.
@ -407,16 +407,24 @@ downstreams: {
disabled: boolean;
systems: number | number[] | {
id: number;
talkgroups: number | number[] | "*" | undefined;
id_as: number | undefined; // optional, downstream as a dirrerent system.id
talkgroups: number | number[] | {
id: number,
id_as: number, // optional, downstream as a dirrerent talkgroup.id
} | "*";
} | {
id: number;
talkgroups: number | number[];
}[] | "*" | undefined;
id_as: number | undefined; // optional, downstream as a different system.id
talkgroups: number | number[] | {
id: number,
id_as: number, // optional, downstream as a dirrerent talkgroup.id
} | "*";
}[] | "*";
url: string;
}[]
```
Default value is an empty array `[]` .
Default value is an empty array `[]`.
**Examples**
@ -428,6 +436,7 @@ Default value is an empty array `[]` .
"systems": [
{
"id": 11,
"id_as": 7537,
"talkgroups": [
54241,
54125
@ -442,6 +451,7 @@ Default value is an empty array `[]` .
},
{
"id": 21,
"id_as": 8086,
"talkgroups": [
60040,
60041,
@ -603,6 +613,7 @@ systems: {
id: number;
label: string;
led?: 'blue' | 'cyan' | 'green' | 'magenta' | 'red' | 'white' | 'yellow';
order?: number;
talkgroups: {
id: number;
label: string;
@ -621,7 +632,8 @@ systems: {
- **id** - System ID.
- **label** - System label shown on the left side of second row.
- **led** - Optional LED color for the whole system. By default the LED is _green_.
- **led** - Optional, LED color for the whole system. By default the LED is _green_.
- **order** - Optional, sort order with which systems are displayed on the client side.
- **talkgroups** - Talkgroups for the system.
- **id** - Talkgroup ID.
- **label** - Talkgroup label shown on the left side of third row.

View file

@ -10,5 +10,5 @@
"start": "node run",
"update": "node update"
},
"version": "4.7.6"
"version": "4.8.0"
}

View file

@ -839,6 +839,10 @@ function parseConfig(config) {
system.led = ledColors.includes(system.led) ? system.led : 'green';
}
if (system.order !== undefined && typeof system.order !== 'number') {
delete system.order;
}
if (!Array.isArray(system.talkgroups)) {
system.talkgroups = [];
}

View file

@ -133,8 +133,8 @@ class Downstream {
}
form.append('sources', JSON.stringify(call.sources));
form.append('system', call.system);
form.append('talkgroup', call.talkgroup);
form.append('system', this.getSystemId(call, downstream));
form.append('talkgroup', this.getTalkgroupId(call, downstream));
form.submit(apiUrl, (error, response) => {
const message = `Downstream: system=${call.system} talkgroup=${call.talkgroup} file=${call.audioName} to=${downstream.url}`;
@ -150,6 +150,56 @@ class Downstream {
}
});
}
getSystemId(call, downstream) {
const parseSystem = (system) => {
if (Array.isArray(system)) {
system = system.find((sys) => sys !== null && typeof sys === 'object' && sys.id === call.system);
return system ? parseSystem(system) : call.system;
} else if (system !== null && typeof system === 'object') {
return typeof system.id_as === 'number' && system.id === call.system ? system.id_as : call.system;
} else {
return call.system;
}
}
return parseSystem(downstream.systems);
}
getTalkgroupId(call, downstream) {
const parseSystem = (system) => {
const parseTalkgroup = (talkgroup) => {
if (Array.isArray(talkgroup)) {
talkgroup = talkgroup.find((tg) => tg !== null && typeof tg === 'object' && tg.id === call.talkgroup);
return talkgroup ? parseTalkgroup(talkgroup) : call.talkgroup;
} else if (talkgroup !== null && typeof talkgroup === 'object') {
return typeof talkgroup.id_as === 'number' && talkgroup.id === call.talkgroup ? talkgroup.id_as : talkgroup.id;
} else {
return call.talkgroup;
}
}
if (Array.isArray(system)) {
system = system.find((sys) => sys !== null && typeof sys === 'object' && sys.id === call.system);
return system ? parseSystem(system) : call.system;
} else if (system !== null && typeof system === 'object') {
return system.id === call.system ? parseTalkgroup(system.talkgroups) : call.talkgroup;
} else {
return call.system;
}
}
return parseSystem(downstream.systems);
}
}
module.exports = Downstream;

View file

@ -53,6 +53,10 @@ rdioScannerSystemFactory.Schema = {
type: DataTypes.STRING,
allowNull: true,
},
order: {
type: DataTypes.INTEGER,
allowNull: true,
},
talkgroups: {
type: DataTypes.JSON,
defaultValue: [],

5536
server/package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -1,17 +1,17 @@
{
"author": "Chrystian Huot <chrystian.huot@saubeo.solutions>",
"dependencies": {
"chokidar": "^3.4.2",
"chokidar": "^3.4.3",
"cors": "^2.8.5",
"dotenv": "^8.2.0",
"express": "^4.17.1",
"form-data": "^3.0.0",
"helmet": "^4.1.1",
"mariadb": "^2.4.2",
"mariadb": "^2.5.1",
"mime-types": "^2.1.27",
"multer": "^1.4.2",
"mysql2": "^2.2.5",
"nodemon": "^2.0.4",
"nodemon": "^2.0.6",
"pg": "^8.4.1",
"sequelize": "^6.3.5",
"sqlite3": "^5.0.0",
@ -20,9 +20,9 @@
"ws": "^7.3.1"
},
"devDependencies": {
"eslint": "^7.11.0",
"webpack": "^5.0.0",
"webpack-cli": "^4.0.0",
"eslint": "^7.12.0",
"webpack": "^5.2.0",
"webpack-cli": "^4.1.0",
"webpack-node-externals": "^2.5.2"
},
"license": "LICENSE",
@ -39,5 +39,5 @@
"build": "webpack",
"start": "nodemon"
},
"version": "4.7.6"
"version": "4.8.0"
}