Browse Source

Merge branch 'features/dialog-camera' of trungnd/iot-web-ui into master

pull/15/head
trungnd 1 year ago
parent
commit
b501707567
52 changed files with 1328 additions and 898 deletions
  1. +16
    -8
      src/app/app-routing.module.ts
  2. +14
    -6
      src/app/app.component.ts
  3. +0
    -308
      src/app/app.constants.ts
  4. +0
    -0
      src/app/modules/homepage/centralized-security-management/centralized-security-management.component.html
  5. +11
    -7
      src/app/modules/homepage/centralized-security-management/centralized-security-management.component.scss
  6. +21
    -0
      src/app/modules/homepage/centralized-security-management/centralized-security-management.component.spec.ts
  7. +220
    -0
      src/app/modules/homepage/centralized-security-management/centralized-security-management.component.ts
  8. +159
    -0
      src/app/modules/homepage/data/fake-data.ts
  9. +28
    -0
      src/app/modules/homepage/home-page.module.ts
  10. +19
    -0
      src/app/modules/homepage/home-page.routing.ts
  11. +41
    -0
      src/app/modules/homepage/homepage/home-page.component.html
  12. +6
    -0
      src/app/modules/homepage/homepage/home-page.component.scss
  13. +21
    -0
      src/app/modules/homepage/homepage/home-page.component.spec.ts
  14. +17
    -0
      src/app/modules/homepage/homepage/home-page.component.ts
  15. +49
    -0
      src/app/modules/homepage/security-system-details/security-system-details.component.html
  16. +30
    -32
      src/app/modules/homepage/security-system-details/security-system-details.component.scss
  17. +21
    -0
      src/app/modules/homepage/security-system-details/security-system-details.component.spec.ts
  18. +33
    -13
      src/app/modules/homepage/security-system-details/security-system-details.component.ts
  19. +0
    -2
      src/app/modules/overview/camera-stream/camera-stream.component.html
  20. +0
    -5
      src/app/modules/overview/camera-stream/camera-stream.component.scss
  21. +0
    -21
      src/app/modules/overview/camera-stream/camera-stream.component.spec.ts
  22. +0
    -331
      src/app/modules/overview/map/map.component.ts
  23. +0
    -58
      src/app/modules/overview/overall-ground/overall-ground.component.html
  24. +0
    -21
      src/app/modules/overview/overall-ground/overall-ground.component.spec.ts
  25. +0
    -25
      src/app/modules/overview/overview.module.ts
  26. +0
    -21
      src/app/modules/overview/overview.routing.ts
  27. +11
    -0
      src/app/shared/component/camera-dialog/camera-dialog.component.html
  28. +9
    -0
      src/app/shared/component/camera-dialog/camera-dialog.component.scss
  29. +21
    -0
      src/app/shared/component/camera-dialog/camera-dialog.component.spec.ts
  30. +11
    -9
      src/app/shared/component/camera-dialog/camera-dialog.component.ts
  31. +11
    -0
      src/app/shared/component/confirm-dialog/confirm-dialog.component.html
  32. +10
    -0
      src/app/shared/component/confirm-dialog/confirm-dialog.component.scss
  33. +21
    -0
      src/app/shared/component/confirm-dialog/confirm-dialog.component.spec.ts
  34. +25
    -0
      src/app/shared/component/confirm-dialog/confirm-dialog.component.ts
  35. +13
    -0
      src/app/shared/component/layout/layout.component.html
  36. +23
    -0
      src/app/shared/component/layout/layout.component.scss
  37. +21
    -0
      src/app/shared/component/layout/layout.component.spec.ts
  38. +10
    -0
      src/app/shared/component/layout/layout.component.ts
  39. +17
    -22
      src/app/shared/component/shared-component.module.ts
  40. +41
    -0
      src/app/shared/component/slider-range/slider-range.component.html
  41. +149
    -0
      src/app/shared/component/slider-range/slider-range.component.scss
  42. +21
    -0
      src/app/shared/component/slider-range/slider-range.component.spec.ts
  43. +103
    -0
      src/app/shared/component/slider-range/slider-range.component.ts
  44. +58
    -0
      src/app/shared/services/alarm-sound.service.ts
  45. +27
    -0
      src/app/shared/services/confirm-dialog.service.ts
  46. +1
    -3
      src/app/shared/services/mqtt-client.service.ts
  47. +17
    -5
      src/app/shared/shared-material.module.ts
  48. +2
    -1
      src/app/shared/shared.module.ts
  49. BIN
      src/assets/images/logo.png
  50. BIN
      src/assets/sound/alarm.mp3
  51. BIN
      src/assets/sound/alarm2.mp3
  52. BIN
      src/assets/sound/alarm_5m.mp3

+ 16
- 8
src/app/app-routing.module.ts View File

import { NgModule } from '@angular/core'; import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router'; import { RouterModule, Routes } from '@angular/router';
import { LayoutComponent } from './shared/component/layout/layout.component';


const routes: Routes = [ const routes: Routes = [
{ path: '',
redirectTo: '/overview',
pathMatch: 'full'
},
{ path: 'overview',
loadChildren: () => import('./modules/overview/overview.module').then(m => m.OverviewModule)
{ path: '', redirectTo: '/homepage', pathMatch: 'full' },
{
path: '',
component: LayoutComponent,
children: [
{
path: 'homepage',
loadChildren: () =>
import('./modules/homepage/home-page.module').then(
(m) => m.HomePageModule,
),
},
],
}, },
]; ];


@NgModule({ @NgModule({
imports: [RouterModule.forRoot(routes)], imports: [RouterModule.forRoot(routes)],
exports: [RouterModule]
exports: [RouterModule],
}) })
export class AppRoutingModule { }
export class AppRoutingModule {}

+ 14
- 6
src/app/app.component.ts View File

import {Component, OnInit} from '@angular/core';
import {SocketService} from "./shared/services/socket.service";
import {
AfterViewInit,
Component,
ElementRef,
OnInit,
ViewChild,
} from '@angular/core';
import { SocketService } from './shared/services/socket.service';
import { AlarmSoundService } from './shared/services/alarm-sound.service';


@Component({ @Component({
selector: 'app-root', selector: 'app-root',
templateUrl: './app.component.html', templateUrl: './app.component.html',
styleUrls: ['./app.component.scss']
styleUrls: ['./app.component.scss'],
}) })
export class AppComponent implements OnInit{
export class AppComponent implements OnInit {
title = 'Iot-web-ui'; title = 'Iot-web-ui';
constructor(private socketService$: SocketService) {
}

constructor(private socketService$: SocketService) {}

ngOnInit() { ngOnInit() {
this.socketService$.connect(); this.socketService$.connect();
} }

+ 0
- 308
src/app/app.constants.ts View File

export const ICON = {
sensorOff: `<div style="display: flex;
flex-direction: column;
align-items: center">
<div style="display: flex; flex-direction: row; gap: 5px">
<div class="tooltip">
<img style="width: 30px; height: 30px;" src="assets/images/sensor-off.png">
<div class="tooltiptext">
<div style="display: flex;
flex-direction: row;
justify-content: space-between;">
<div style="color: #F33152;
padding: 5px 13px;
background-color: rgba(243, 49, 82, 0.1);
border-radius: 20px;
text-align: center;
width: 120px;">Hệ thống báo động 2</div>
<a href="/overview/camera-stream">
<svg width="24px" height="24px" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" fill="none">
<g stroke="currentColor" stroke-width="2">
<path d="M16 16V8a1 1 0 00-1-1H5a1 1 0 00-1 1v8a1 1 0 001 1h10a1 1 0 001-1z"/>
<path stroke-linejoin="round" d="M20 7l-4 3v4l4 3V7z"/>
</g>
</svg>
</a>
</div>
<div><strong>Địa điểm:</strong> 120 Xa Lộ Hà Nội, Thành Phố, Thủ Đức, Thành phố Hồ Chí Minh</div>
<div><strong>Tọa độ:</strong> 10.8661° N, 106.8029° E</div>
<div><strong>Thời gian:</strong> 01:54, 16/05/2022</div>
<a href="/overview/overall-ground" target="_blank">Xem chi tiết</a>
</div>
</div>

<a href="/overview/camera-stream" target="_blank">
<img style="width: 30px; height: 30px;" src="assets/images/camera.png">
</a>
</div>
</div>
<style>
.tooltip {
position: relative;
display: inline-block;
}
.tooltip .tooltiptext {
visibility: hidden;
width: 300px;
padding: 10px;
background-color: #fff;
color: black;
border-radius: 6px;
position: absolute;
z-index: 1;
bottom: 100%;
left: 50%;
margin-left: -60px;
}

.tooltip:hover .tooltiptext {
visibility: visible;
}
</style>`,
sensorOn: `<div style="display: flex;
flex-direction: column;
align-items: center">
<div style="display: flex; flex-direction: row; gap: 5px">
<div class="tooltip">
<img style="width: 30px; height: 30px;" src="assets/images/sensor-on.png">
<div class="tooltiptext">
<div style="display: flex;
flex-direction: row;
justify-content: space-between;">
<div style="color: #F33152;
padding: 5px 13px;
background-color: rgba(243, 49, 82, 0.1);
border-radius: 20px;
text-align: center;
width: 120px;">Hệ thống báo động 2</div>
<a href="/overview/camera-stream">
<svg width="24px" height="24px" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" fill="none">
<g stroke="currentColor" stroke-width="2">
<path d="M16 16V8a1 1 0 00-1-1H5a1 1 0 00-1 1v8a1 1 0 001 1h10a1 1 0 001-1z"/>
<path stroke-linejoin="round" d="M20 7l-4 3v4l4 3V7z"/>
</g>
</svg>
</a>
</div>
<div><strong>Địa điểm:</strong> 120 Xa Lộ Hà Nội, Thành Phố, Thủ Đức, Thành phố Hồ Chí Minh</div>
<div><strong>Tọa độ:</strong> 10.8661° N, 106.8029° E</div>
<div><strong>Thời gian:</strong> 01:54, 16/05/2022</div>
<a href="/overview/overall-ground" target="_blank">Xem chi tiết</a>
</div>
</div>

<a href="/overview/camera-stream" target="_blank">
<img style="width: 30px; height: 30px;" src="assets/images/camera.png">
</a>
</div>
<!-- <div style="font-size: 11px"><b>ALARM: SMOKE ALERT</b></div>-->
</div>
<style>
.tooltip {
position: relative;
display: inline-block;
}
.tooltip .tooltiptext {
visibility: hidden;
width: 300px;
padding: 10px;
background-color: #fff;
color: black;
border-radius: 6px;
position: absolute;
z-index: 1;
bottom: 100%;
left: 50%;
margin-left: -60px;
}

.tooltip:hover .tooltiptext {
visibility: visible;
}
</style>`,
sensorActive: `<div>
<div style="width: 30px; height: 30px; position: absolute; display: flex; justify-content: center; align-items: center; border-radius: 50%; background: linear-gradient(#ff0000, #C70039);">
<div style="position: absolute !important; width: 100%; height: 100%; background: #ff0000; border-radius: 50%; z-index: 1; animation: sensor-on 2s ease-out infinite;"></div>
<div style="position: absolute !important; width: 100%; height: 100%; background: #ff0000; border-radius: 50%; z-index: 1; animation: sensor-on 2s ease-out infinite; animation-delay: 1s;"></div>
<img src="assets/images/sensor-on.png" style="width: 30px; height: 30px; z-index: 9;">
</div>
<img style="width: 30px; height: 30px;margin-left: 40px;" src="assets/images/camera.png">
<style>
@keyframes sensor-on {
100% {
transform: scale(2);
opacity: 0;
}
}
</style>
</div>`,
fireContent: `<div style="display: flex;
flex-direction: row;
justify-content: space-between;">
<div style="color: #F33152;
padding: 5px 13px;
background-color: rgba(243, 49, 82, 0.1);
border-radius: 20px;
text-align: center;
width: 100px;">Sự cố cháy</div>
<a href="/overview/camera-stream" target="_blank">
<svg width="24px" height="24px" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" fill="none">
<g stroke="currentColor" stroke-width="2">
<path d="M16 16V8a1 1 0 00-1-1H5a1 1 0 00-1 1v8a1 1 0 001 1h10a1 1 0 001-1z"/>
<path stroke-linejoin="round" d="M20 7l-4 3v4l4 3V7z"/>
</g>
</svg>
</a>
</div>
<div><strong>Địa điểm:</strong> 120 Xa Lộ Hà Nội, Thành Phố, Thủ Đức, Thành phố Hồ Chí Minh</div>
<div><strong>Tọa độ:</strong> 10.8661° N, 106.8029° E</div>
<div><strong>Thời gian:</strong> 01:54, 16/05/2022</div>
<a href="/overview/overall-ground" target="_blank">Xem chi tiết</a>`,

sensorActiveSmoke: `
<div style="display: flex;
flex-direction: column;
align-items: center">
<div style="display: flex; flex-direction: row; gap: 39px;width: 100%; justify-content: center;">
<div class="tooltip">
<div style="width: 30px; height: 30px; position: absolute; display: flex; justify-content: center; align-items: center; border-radius: 50%; background: linear-gradient(#ff0000, #C70039);">
<div style="position: absolute !important; width: 100%; height: 100%; background: #ff0000; border-radius: 50%; z-index: 1; animation: sensor-on 2s ease-out infinite;"></div>
<div style="position: absolute !important; width: 100%; height: 100%; background: #ff0000; border-radius: 50%; z-index: 1; animation: sensor-on 2s ease-out infinite; animation-delay: 1s;"></div>
<img src="assets/images/sensor-on.png" style="width: 30px; height: 30px; z-index: 9;">
</div>

<div class="tooltiptext">
<div style="display: flex;
flex-direction: row;
justify-content: space-between;">
<div style="color: #F33152;
padding: 5px 13px;
background-color: rgba(243, 49, 82, 0.1);
border-radius: 20px;
text-align: center;
width: 120px;">Hệ thống báo động 1</div>
<a href="/overview/camera-stream" target="_blank">
<svg width="24px" height="24px" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" fill="none">
<g stroke="currentColor" stroke-width="2">
<path d="M16 16V8a1 1 0 00-1-1H5a1 1 0 00-1 1v8a1 1 0 001 1h10a1 1 0 001-1z"/>
<path stroke-linejoin="round" d="M20 7l-4 3v4l4 3V7z"/>
</g>
</svg>
</a>
</div>
<div><strong>Địa điểm:</strong> 120 Xa Lộ Hà Nội, Thành Phố, Thủ Đức, Thành phố Hồ Chí Minh</div>
<div><strong>Tọa độ:</strong> 10.8661° N, 106.8029° E</div>
<div><strong>Thời gian:</strong> 01:54, 16/05/2022</div>
<a href="/overview/overall-ground" target="_blank">Xem chi tiết</a>
</div>
</div>

<a href="/overview/camera-stream" target="_blank">
<img style="width: 30px; height: 30px;" src="assets/images/camera.png">
</a>
</div>
<div style="background: #F11E1E; color: #FFF; padding: 2px 3px; font-size: 11px"><b>ALARM: SMOKE ALERT</b></div>
</div>
<style>
.tooltip {
position: relative;
display: inline-block;
}
.tooltip .tooltiptext {
visibility: hidden;
width: 300px;
padding: 10px;
background-color: #fff;
color: black;
border-radius: 6px;
position: absolute;
z-index: 1;
bottom: 100%;
left: 50%;
margin-left: -60px;
}

.tooltip:hover .tooltiptext {
visibility: visible;
}
@keyframes sensor-on {
100% {
transform: scale(2);
opacity: 0;
}
}
</style>`,
sensorActiveVib: `
<div style="display: flex;
flex-direction: column;
align-items: center">
<div style="display: flex; flex-direction: row; gap: 39px;width: 100%; justify-content: center;">
<div class="tooltip">
<div style="width: 30px; height: 30px; position: absolute; display: flex; justify-content: center; align-items: center; border-radius: 50%; background: linear-gradient(#ff0000, #C70039);">
<div style="position: absolute !important; width: 100%; height: 100%; background: #ff0000; border-radius: 50%; z-index: 1; animation: sensor-on 2s ease-out infinite;"></div>
<div style="position: absolute !important; width: 100%; height: 100%; background: #ff0000; border-radius: 50%; z-index: 1; animation: sensor-on 2s ease-out infinite; animation-delay: 1s;"></div>
<img src="assets/images/sensor-on.png" style="width: 30px; height: 30px; z-index: 9;">
</div>

<div class="tooltiptext">
<div style="display: flex;
flex-direction: row;
justify-content: space-between;">
<div style="color: #F33152;
padding: 5px 13px;
background-color: rgba(243, 49, 82, 0.1);
border-radius: 20px;
text-align: center;
width: 120px;">Hệ thống báo động 2</div>
<a href="/overview/camera-stream" target="_blank">
<svg width="24px" height="24px" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" fill="none">
<g stroke="currentColor" stroke-width="2">
<path d="M16 16V8a1 1 0 00-1-1H5a1 1 0 00-1 1v8a1 1 0 001 1h10a1 1 0 001-1z"/>
<path stroke-linejoin="round" d="M20 7l-4 3v4l4 3V7z"/>
</g>
</svg>
</a>
</div>
<div><strong>Địa điểm:</strong> 120 Xa Lộ Hà Nội, Thành Phố, Thủ Đức, Thành phố Hồ Chí Minh</div>
<div><strong>Tọa độ:</strong> 10.8661° N, 106.8029° E</div>
<div><strong>Thời gian:</strong> 01:54, 16/05/2022</div>
<a href="/overview/overall-ground" target="_blank">Xem chi tiết</a>
</div>
</div>

<a href="/overview/camera-stream" target="_blank">
<img style="width: 30px; height: 30px;" src="assets/images/camera.png">
</a>
</div>
<div style="background: #F11E1E; color: #FFF; padding: 2px 3px; font-size: 11px"><b>ALARM: VIBRATION ALERT</b></div>
</div>
<style>
.tooltip {
position: relative;
display: inline-block;
}
.tooltip .tooltiptext {
visibility: hidden;
width: 300px;
padding: 10px;
background-color: #fff;
color: black;
border-radius: 6px;
position: absolute;
z-index: 1;
bottom: 100%;
left: 50%;
margin-left: -60px;
}

.tooltip:hover .tooltiptext {
visibility: visible;
}
@keyframes sensor-on {
100% {
transform: scale(2);
opacity: 0;
}
}
</style>`,
}

src/app/modules/overview/map/map.component.html → src/app/modules/homepage/centralized-security-management/centralized-security-management.component.html View File


src/app/modules/overview/map/map.component.scss → src/app/modules/homepage/centralized-security-management/centralized-security-management.component.scss View File

.map-container { .map-container {
position: absolute; position: absolute;
top: 0;
left: 0; left: 0;
right: 0; right: 0;
bottom: 0;
margin: 30px;
width: 100%;
height: calc(100% - 7rem);
h2 {
padding: .5rem 3rem 0 ;
text-align: center;
}
} }


.map-frame { .map-frame {
border: 2px solid black;
height: 90%;
height: 100%;
} }


#map { #map {
opacity: 0; opacity: 0;
} }
} }
::ng-deep tooltip {
::ng-deep .tooltip {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: 5px; gap: 5px;

.dynamic-button{
cursor: pointer;
}
} }


::ng-deep.icon { ::ng-deep.icon {

+ 21
- 0
src/app/modules/homepage/centralized-security-management/centralized-security-management.component.spec.ts View File

import { ComponentFixture, TestBed } from '@angular/core/testing';

import { CentralizedSecurityManagementComponent } from './centralized-security-management.component';

describe('CentralizedSecurityManagementComponent', () => {
let component: CentralizedSecurityManagementComponent;
let fixture: ComponentFixture<CentralizedSecurityManagementComponent>;

beforeEach(() => {
TestBed.configureTestingModule({
declarations: [CentralizedSecurityManagementComponent]
});
fixture = TestBed.createComponent(CentralizedSecurityManagementComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});

it('should create', () => {
expect(component).toBeTruthy();
});
});

+ 220
- 0
src/app/modules/homepage/centralized-security-management/centralized-security-management.component.ts View File

import {
AfterViewInit,
Component,
OnDestroy,
OnInit,
Renderer2,
} from '@angular/core';
import * as L from 'leaflet';
import 'leaflet.markercluster';
import { Subscription, take } from 'rxjs';
import { SocketService } from '../../../shared/services/socket.service';
import { MatDialog } from '@angular/material/dialog';
import { AlarmSoundService } from '../../../shared/services/alarm-sound.service';
import { CameraDialogComponent } from '../../../shared/component/camera-dialog/camera-dialog.component';
import { alarmData, alarmDemo } from '../data/fake-data';
import { ConfirmDialogService } from '../../../shared/services/confirm-dialog.service';

@Component({
selector: 'app-centralized-security-management',
templateUrl: './centralized-security-management.component.html',
styleUrls: ['./centralized-security-management.component.scss'],
})
export class CentralizedSecurityManagementComponent
implements OnInit, AfterViewInit, OnDestroy
{
private map!: L.Map;
private markers!: L.MarkerClusterGroup;
private statusSubscription?: Subscription;
private messageSubscription?: Subscription;
data = alarmData;
alarmDemo = alarmDemo;
state1 = false;
state2 = false;
state5 = false;
state6 = false;
isReady = true;

constructor(
private socketService$: SocketService,
private dialog: MatDialog,
private renderer: Renderer2,
private alarmSoundService$: AlarmSoundService,
) {}

ngOnInit() {
this.statusSubscription = this.socketService$.status$.subscribe(
(isConnected) => {
if (isConnected) {
this.socketService$.sendMessage({ id: '0', type: 'get' });
this.messageSubscription = this.socketService$.messages$.subscribe(
(message) => {
this.onMessage(message);
},
);
}
},
);
}

openDialog(): void {
this.dialog.open(CameraDialogComponent, {
width: '80vw',
data: '',
});
}

ngAfterViewInit(): void {
this.initMap();
}

ngOnDestroy(): void {
this.statusSubscription?.unsubscribe();
this.messageSubscription?.unsubscribe();
this.socketService$.close();
this.alarmSoundService$.stopAlarm();
}

initMap(): void {
const mapContainer = document.getElementById('map');
if (mapContainer) {
this.map = L.map('map', {
center: [10.7483, 106.7537],
zoom: 12,
});

L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
maxZoom: 15,
minZoom: 3,
attribution:
'&copy; <a href="http://www.openstreetmap.org/copyright">OpenStreetMap</a>',
}).addTo(this.map);

this.addIconsToMap();
} else {
console.error('Map container not found');
}
}

onMessage(message: any) {
if (message.id == '0' && message.type === 'get') {
this.state1 = message.state1 === '0'; // 1 OFF // alarm 12h
this.state2 = message.state2 === '0'; // 1 OFF // alarm 1h
this.state5 = message.state5 === '1'; // 1 ON, 0 OFF
this.isReady = message.ready === '1';
this.alarmSoundService$.startAlarm(this.state5, this.isReady, this.state1, this.state2);
this.updateIcons();

}

}

addIconsToMap(): void {
this.markers = L.markerClusterGroup();
this.data.forEach((item) => this.addMarker(item));
this.addMarker(this.alarmDemo, true);
this.map.addLayer(this.markers);
}

addMarker(item: any, isDemo: boolean = false): void {
const icon = isDemo
? this.getIcon(this.state5, this.isReady, this.state1, this.state2)
: this.createIcon(item.warning);
const marker = L.marker(
[item.detail.coordinates.lat, item.detail.coordinates.lng],
{ icon },
).bindPopup(this.popupDetail(item));

marker.on('popupopen', (event) => this.bindPopupEvents(event));

this.markers.addLayer(marker);
}

bindPopupEvents(event: any): void {
const popupContainer = event.popup.getElement();
const button = popupContainer?.querySelector('.dynamic-button');
if (button) {
this.renderer.listen(button, 'click', () => this.openDialog());
}
}

updateIcons(): void {
this.markers.clearLayers();
this.addIconsToMap();
}

popupDetail(item: any): string {
return `
<div class="tooltip" style="width: 200px">
<div style="display: flex; flex-direction: row; justify-content: space-between; margin-bottom: 5px">
<div style="color: #F33152; padding: 5px 13px; background-color: rgba(243, 49, 82, 0.1); border-radius: 20px; text-align: center; max-width: 180px;">${item.title}</div>
<a class="dynamic-button">
<svg width="24px" height="24px" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" fill="none">
<g stroke="currentColor" stroke-width="2">
<path d="M16 16V8a1 1 0 00-1-1H5a1 1 0 00-1 1v8a1 1 0 001 1h10a1 1 0 001-1z"/>
<path stroke-linejoin="round" d="M20 7l-4 3v4l4 3V7z"/>
</g>
</svg>
</a>
</div>
<div><strong>Địa điểm:</strong> ${item.detail.position}</div>
<div><strong>Tọa độ:</strong> ${item.detail.coordinates.lat}, ${item.detail.coordinates.lng}</div>
<div><strong>Thời gian:</strong> ${item.detail.time}</div>
<a href="/overview/overall-ground" target="_blank">Xem chi tiết</a>
</div>`;
}

createIcon(
active: boolean,
className: string = '',
text: any = [],
): L.Icon<L.IconOptions> | L.DivIcon {
if (text.length < 1) {
return L.icon({
iconUrl: active
? '../../../../assets/images/sensor-on.png'
: '../../../../assets/images/sensor-off.png',
iconSize: [30, 30],
className: className,
});
} else {
let htmlContent = '';
text.forEach((item: any) => {
htmlContent += `<span><b>${item}</b></span><br>`;
});

return L.divIcon({
html: `<div class="icon">
<div style="z-index:9999" class="sensor-on">
<img alt="icon-alarm" src="assets/images/sensor-on.png" style="width: 30px; height: 30px">
</div>
<div style="background: #F11E1E; color: #FFF; padding: 2px 3px; font-size: 11px; margin-top: 10px; text-align: center">
${htmlContent}
</div>
</div>`,
iconSize: [200, 30],
className: className,
});
}
}

getIcon(
isTurnOn: boolean,
isReady: boolean,
fireArm: boolean,
fenceArm: boolean,
): L.Icon<L.IconOptions> | L.DivIcon {
if (isTurnOn && isReady) {
let text = [];
if (fireArm && fenceArm) {
text.push('FIRE ALARM', 'FENCE ALARM');
} else if (fireArm) {
text.push('FIRE ALARM');
} else if (fenceArm) {
text.push('FENCE ALARM');
}
return this.createIcon(true, '', text);
}
return this.createIcon(false);
}
}

+ 159
- 0
src/app/modules/homepage/data/fake-data.ts View File

export const alarmData = [
{
title: ' Alarm System 1',
detail: {
position: 'Hanoi',
coordinates: {
lat: 21.0285,
lng: 105.8542
},
time: '06-07-2024'
},
warning: true
},
{
title: ' Alarm System 2',
detail: {
position: 'Hai Phong',
coordinates: {
lat: 20.8449,
lng: 106.6881
},
time: '06-07-2024'
},
warning: true
},
{
title: ' Alarm System 3',
detail: {
position: 'Ha Long',
coordinates: {
lat: 20.9460,
lng: 107.0740
},
time: '06-07-2024'
},
warning: false
},
{
title: ' Alarm System 4',
detail: {
position: 'Vinh',
coordinates: {
lat: 18.6796,
lng: 105.6813
},
time: '06-07-2024'
},
warning: true
},
{
title: ' Alarm System 5',
detail: {
position: 'Dong Hoi',
coordinates: {
lat: 17.4834,
lng: 106.6000
},
time: '06-07-2024'
},
warning: false
},
{
title: ' Alarm System 6',
detail: {
position: 'Hue',
coordinates: {
lat: 16.4637,
lng: 107.5909
},
time: '06-07-2024'
},
warning: true
},
{
title: ' Alarm System 7',
detail: {
position: 'Da Nang',
coordinates: {
lat: 16.0471,
lng: 108.2068
},
time: '06-07-2024'
},
warning: false
},
{
title: ' Alarm System 8',
detail: {
position: 'Quy Nhon',
coordinates: {
lat: 13.7820,
lng: 109.2198
},
time: '06-07-2024'
},
warning: false
},
{
title: ' Alarm System 9',
detail: {
position: 'Nha Trang',
coordinates: {
lat: 12.2388,
lng: 109.1967
},
time: '06-07-2024'
},
warning: true
},
{
title: ' Alarm System 10',
detail: {
position: 'Da Lat',
coordinates: {
lat: 11.9416,
lng: 108.4580
},
time: '06-07-2024'
},
warning: false
},
{
title: ' Alarm System 11',
detail: {
position: 'Ho Chi Minh City',
coordinates: {
lat: 10.8231,
lng: 106.6297
},
time: '06-07-2024'
},
warning: false
},
{
title: ' Alarm System 12',
detail: {
position: 'Can Tho',
coordinates: {
lat: 10.0452,
lng: 105.7469
},
time: '06-07-2024'
},
warning: true
},

];

export const alarmDemo = {
title: ' Demo Alarm System', // Thêm mới warning
detail: {
position: 'Vinhomes Quận 9',
coordinates: {
lat: 10.7483,
lng: 106.8016
},
time: '06-07-2024'
},
}

+ 28
- 0
src/app/modules/homepage/home-page.module.ts View File

import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';

import {RouterModule} from "@angular/router";
import {SharedMaterialModule} from "../../shared/shared-material.module";
import { HomePageComponent } from './homepage/home-page.component';
import {homePageRoutes} from "./home-page.routing";
import {SharedModule} from "../../shared/shared.module";
import { CentralizedSecurityManagementComponent } from './centralized-security-management/centralized-security-management.component';
import { SecuritySystemDetailsComponent } from './security-system-details/security-system-details.component';
import {FormsModule} from "@angular/forms";


@NgModule({
declarations: [
HomePageComponent,
CentralizedSecurityManagementComponent,
SecuritySystemDetailsComponent,
],
imports: [
CommonModule,
RouterModule.forChild(homePageRoutes),
SharedMaterialModule,
SharedModule,
FormsModule,
],
})
export class HomePageModule {}

+ 19
- 0
src/app/modules/homepage/home-page.routing.ts View File

import { Routes } from '@angular/router';
import { HomePageComponent } from './homepage/home-page.component';
import { CentralizedSecurityManagementComponent } from './centralized-security-management/centralized-security-management.component';
import { SecuritySystemDetailsComponent } from './security-system-details/security-system-details.component';

export const homePageRoutes: Routes = [
{
path: '',
component: HomePageComponent,
},
{
path: 'centralized-security-management',
component: CentralizedSecurityManagementComponent,
},
{
path: 'security-system-details',
component: SecuritySystemDetailsComponent,
},
];

+ 41
- 0
src/app/modules/homepage/homepage/home-page.component.html View File

<div fxLayout="row" fxLayoutGap="30px" style="padding: 2rem 3rem 0">
<button mat-stroked-button routerLink="./centralized-security-management">
Centralized Security Management
</button>
<button mat-stroked-button routerLink="./security-system-details">
Security System Details
</button>
</div>


<mat-card class="sound-group">
<mat-card-header>
<mat-card-title>WHISTLE TIME: {{whistle.time}}s</mat-card-title>
</mat-card-header>
<mat-card-content>
<div class="volume-group">
<app-slider-range
[value]="whistle.time"
[icon]="'access_alarm'"
(valueChange)="whistle.time = $event"
></app-slider-range>
</div>
</mat-card-content>
</mat-card>


<mat-card class="sound-group">
<mat-card-header>
<mat-card-title>24-HOUR ZONE ALARM TIME: {{alarm.time}}s</mat-card-title>
</mat-card-header>
<mat-card-content>
<div class="volume-group">
<app-slider-range
[value]="alarm.time"
[icon]="'access_alarm'"
(valueChange)="alarm.time = $event"
></app-slider-range>
</div>

</mat-card-content>
</mat-card>

+ 6
- 0
src/app/modules/homepage/homepage/home-page.component.scss View File

.sound-group{
margin: 2rem 3rem;
}
button{
color: #ff7723 !important;
}

+ 21
- 0
src/app/modules/homepage/homepage/home-page.component.spec.ts View File

import { ComponentFixture, TestBed } from '@angular/core/testing';

import { HomePageComponent } from './home-page.component';

describe('HomepageComponent', () => {
let component: HomePageComponent;
let fixture: ComponentFixture<HomePageComponent>;

beforeEach(() => {
TestBed.configureTestingModule({
declarations: [HomePageComponent]
});
fixture = TestBed.createComponent(HomePageComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});

it('should create', () => {
expect(component).toBeTruthy();
});
});

+ 17
- 0
src/app/modules/homepage/homepage/home-page.component.ts View File

import { Component } from '@angular/core';

@Component({
selector: 'app-homepage',
templateUrl: './home-page.component.html',
styleUrls: ['./home-page.component.scss'],
})
export class HomePageComponent {
whistle = {
time: 10,
sound: 0,
};
alarm = {
time: 30,
sound: 0,
};
}

+ 49
- 0
src/app/modules/homepage/security-system-details/security-system-details.component.html View File

<div class="px-3 py-5" fxLayout="row" fxLayoutAlign="space-around center" fxLayoutGap="20px">
<div fxFlex="50" class="map-image">
<div class="card-state">
<img src="assets/images/ground.png">
<div class="state t2" id="State2">
<div fxLayout="row">
<div class="sensor-off" [class.sensor-on]="(state1 && state5 && status1)">
<img [src]="(state5 && status1) ? 'assets/images/sensor-on.png' : 'assets/images/sensor-off.png'" style="width: 30px; height: 30px">
</div>
<ng-container [ngTemplateOutlet]="camera"></ng-container>
</div>
<div *ngIf="(state1 && state5 && status1)" class="alarm-text"
[ngClass]="{'alarm-text-on': (state1 && state5 && status1) }">FIRE ALARM</div>
</div>

<div class="state t3" id="State3">
<div fxLayout="row">
<div class="sensor-off" [class.sensor-on]="(state2 && state5 && status2)">
<img [src]="(state5 && status2) ? 'assets/images/sensor-on.png' : 'assets/images/sensor-off.png'" style="width: 30px; height: 30px">
</div>
<ng-container [ngTemplateOutlet]="camera"></ng-container>
</div>
<div *ngIf="(state2 && state5 && status2)" class="alarm-text"
[ngClass]="{'alarm-text-on': (state2 && state5 && status2) }">FENCE ALARM</div>
</div>
<div class="state t4" id="State4" fxLayout="row" >
<img [src]="getImageSource()" style="width: 30px; height: 30px">
<ng-container [ngTemplateOutlet]="camera"></ng-container>
</div>
<div class="state t5 tooltip" id="State5" fxLayout="row">
<img [src]="getImageSource()" style="width: 30px; height: 30px">
<ng-container [ngTemplateOutlet]="camera"></ng-container>
</div>
<div class="state t6 tooltip" id="State6" fxLayout="row">
<img [src]="getImageSource()" style="width: 30px; height: 30px">
<ng-container [ngTemplateOutlet]="camera"></ng-container>

</div>
</div>
</div>
<div fxFlex="30" fxLayout="column" fxLayoutGap="50px">
<button [disabled]="!isConnected" mat-flat-button color="{{switchArm ? 'accent' : 'primary'}}" (click)="toggleState1()">{{ switchArm ? 'DISARM' : 'ARM'}}</button>
</div>
</div>
<ng-template #camera>
<div (click)="openDialog()">
<img style="width: 30px; height: 30px; cursor: pointer" src="assets/images/camera.png">
</div>
</ng-template>

src/app/modules/overview/overall-ground/overall-ground.component.scss → src/app/modules/homepage/security-system-details/security-system-details.component.scss View File



.topnav { .topnav {
overflow: hidden; overflow: hidden;
background-color: #0A1128;
background-color: #0a1128;
} }


body { body {


.card { .card {
background-color: white; background-color: white;
box-shadow: 2px 2px 12px 1px rgba(140, 140, 140, .5);
box-shadow: 2px 2px 12px 1px rgba(140, 140, 140, 0.5);
} }


.card-title { .card-title {
font-size: 1.2rem; font-size: 1.2rem;
font-weight: bold; font-weight: bold;
color: #034078
color: #034078;
} }


.reading { .reading {
font-size: 1.2rem; font-size: 1.2rem;
color: #1282A2;
color: #1282a2;
} }


.button { .button {
/*.button:hover {background-color: #0f8b8d}*/ /*.button:hover {background-color: #0f8b8d}*/
.button:active { .button:active {
background-color: #0f8b8d; background-color: #0f8b8d;
box-shadow: 2px 2px #CDCDCD;
box-shadow: 2px 2px #cdcdcd;
transform: translateY(2px); transform: translateY(2px);
} }


margin: 0 auto; margin: 0 auto;
} }


.map-image{
img{
.map-image {
img {
height: 100%; height: 100%;
width: 100%; width: 100%;
} }
.card-state{
.card-state {
width: 100%; width: 100%;
height: 100%; height: 100%;
position: relative; position: relative;
} }
.state{
.state {
position: absolute; position: absolute;
color: red; color: red;
&.t1{
&.t1 {
top: 5%; top: 5%;
left: 10%; left: 10%;
} }
&.t2{
&.t2 {
top: 50%; top: 50%;
left: 50%; left: 50%;
transform: translate(-50%, -50%); transform: translate(-50%, -50%);
} }
&.t3{
&.t3 {
top: 5%; top: 5%;
left: 50%; left: 50%;
transform: translate(-50%)
transform: translate(-50%);
} }
&.t4{
&.t4 {
top: 47%; top: 47%;
right: 12%; right: 12%;
} }
&.t5{
&.t5 {
top: 88%; top: 88%;
left: 50%; left: 50%;
transform: translate(-50%); transform: translate(-50%);

} }
&.t6{
&.t6 {
top: 47%; top: 47%;
left: 5%; left: 5%;
width: 100px; width: 100px;
.alarm-text-off{
.alarm-text-off {
width: 100px !important; width: 100px !important;
} }
} }
width: 30px; width: 30px;
height: 30px; height: 30px;
position: absolute; position: absolute;
background: linear-gradient(#ff0000, #C70039);
background: linear-gradient(#ff0000, #c70039);
display: flex !important; display: flex !important;
justify-content: center; justify-content: center;
align-items: center; align-items: center;
border-radius: 50%; border-radius: 50%;
img{
img {
z-index: 9; z-index: 9;
} }
&:before, &:after {
&:before,
&:after {
position: absolute; position: absolute;
content: '';
content: "";
width: 100%; width: 100%;
height: 100%; height: 100%;
background: #ff0000; background: #ff0000;
animation: sensor-on 2s 1s ease-out infinite; animation: sensor-on 2s 1s ease-out infinite;
} }
} }
.sensor-off{
.sensor-off {
display: inline-block; display: inline-block;
} }
@keyframes sensor-on{
100%{
@keyframes sensor-on {
100% {
transform: scale(2); transform: scale(2);
opacity: 0; opacity: 0;
} }
} }
} }
.alarm-text{
.alarm-text {
font-size: 10px; font-size: 10px;
padding: 2px 4px; padding: 2px 4px;
width: 100px; width: 100px;
background: #bfe9f4; background: #bfe9f4;
color: #004aad; color: #004aad;
} }
&-on{
background: #F11E1E;
color: #FFF;
&-on {
background: #f11e1e;
color: #fff;
} }

} }
.tooltip { .tooltip {
position: relative; position: relative;
bottom: 100%; bottom: 100%;
left: -50%; left: -50%;
} }

.tooltip:hover .tooltiptext { .tooltip:hover .tooltiptext {
visibility: visible; visibility: visible;
cursor: pointer; cursor: pointer;
} }
a{
a {
color: blue; color: blue;
} }

+ 21
- 0
src/app/modules/homepage/security-system-details/security-system-details.component.spec.ts View File

import { ComponentFixture, TestBed } from '@angular/core/testing';

import { SecuritySystemDetailsComponent } from './security-system-details.component';

describe('SecuritySystemDetailsComponent', () => {
let component: SecuritySystemDetailsComponent;
let fixture: ComponentFixture<SecuritySystemDetailsComponent>;

beforeEach(() => {
TestBed.configureTestingModule({
declarations: [SecuritySystemDetailsComponent]
});
fixture = TestBed.createComponent(SecuritySystemDetailsComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});

it('should create', () => {
expect(component).toBeTruthy();
});
});

src/app/modules/overview/overall-ground/overall-ground.component.ts → src/app/modules/homepage/security-system-details/security-system-details.component.ts View File

import { Component, OnDestroy, OnInit } from '@angular/core';
import {SocketService} from "../../../shared/services/socket.service";
import {Component, OnDestroy, OnInit} from '@angular/core';
import {Subscription} from "rxjs"; import {Subscription} from "rxjs";
import {SocketService} from "../../../shared/services/socket.service";
import {ToastrService} from "ngx-toastr"; import {ToastrService} from "ngx-toastr";

import {CameraDialogComponent} from "../../../shared/component/camera-dialog/camera-dialog.component";
import {MatDialog} from "@angular/material/dialog";
import { ConfirmDialogService } from '../../../shared/services/confirm-dialog.service';


@Component({ @Component({
selector: 'app-overall-ground',
templateUrl: './overall-ground.component.html',
styleUrls: ['./overall-ground.component.scss']
selector: 'app-security-system-details',
templateUrl: './security-system-details.component.html',
styleUrls: ['./security-system-details.component.scss']
}) })
export class OverallGroundComponent implements OnInit, OnDestroy {
export class SecuritySystemDetailsComponent implements OnInit, OnDestroy {
isConnected = false; isConnected = false;
state1 = false; state1 = false;
status1 = false;
state2 = false; state2 = false;
status2 = false;
state3 = ''; state3 = '';
state4 = ''; state4 = '';
state5 = false; state5 = false;


constructor( constructor(
private socketService$: SocketService, private socketService$: SocketService,
private toastr: ToastrService
) {
}
private toastr: ToastrService,
private dialog: MatDialog,
private confirm$: ConfirmDialogService
) {}


ngOnInit() { ngOnInit() {
// this.socketService$.connect(); // this.socketService$.connect();
onMessage(message: any) { onMessage(message: any) {
if (message.id == '0' && message.type === 'get') { if (message.id == '0' && message.type === 'get') {
this.state1 = message.state1 === '0'; // 1 OFF // alarm 12h this.state1 = message.state1 === '0'; // 1 OFF // alarm 12h
this.status1 = message.status1 === '1'; // 0 not, 1 ready, 2 error, 3 bypass
this.state2 = message.state2 === '0'; // 1 OFF // alarm 1h this.state2 = message.state2 === '0'; // 1 OFF // alarm 1h
this.status2 = message.status2 === '1'; // 0 not, 1 ready, 2 error, 3 bypass
this.state3 = message.state3 === '1' ? 'ON' : 'OFF'; this.state3 = message.state3 === '1' ? 'ON' : 'OFF';
this.state4 = message.state4 === '1' ? 'ON' : 'OFF'; this.state4 = message.state4 === '1' ? 'ON' : 'OFF';
this.state5 = message.state5 === '1'; // alarm 9h && 6h // 1 ON, 0 OFF this.state5 = message.state5 === '1'; // alarm 9h && 6h // 1 ON, 0 OFF
this.switchWarning = message.state6 === '1';// alarm 9h && 6h // 1 ON, 0 OFF this.switchWarning = message.state6 === '1';// alarm 9h && 6h // 1 ON, 0 OFF


this.isReady = message.ready === '1'; this.isReady = message.ready === '1';
if (message.ready === '0' && this.state5){ // not ready and ON arm
this.toastr.warning('System not ready', 'Warning', {timeOut: 5000});
if ((message.status1 === '0' || message.status2 === '0') && this.state5) { // not ready and ON arm
const data = [];
if (message.status1 === '0'){
data.push({key: 'status1', value: false})
}
if (message.status2 === '0'){
data.push({key: 'status2', value: false})
}
this.confirm$.openDialog(data);
// this.toastr.warning('System not ready', 'Warning', {timeOut: 5000});
} }
} }
} }

openDialog(): void {
this.dialog.open(CameraDialogComponent, {
width: '80vw',
data: '',
});
}
} }

+ 0
- 2
src/app/modules/overview/camera-stream/camera-stream.component.html View File

<h3 style="text-align: center">Camera Stream</h3>
<canvas class="video" #videoPlayer></canvas>

+ 0
- 5
src/app/modules/overview/camera-stream/camera-stream.component.scss View File

.video {
width: 78% !important;
display: block;
margin: 0 auto
}

+ 0
- 21
src/app/modules/overview/camera-stream/camera-stream.component.spec.ts View File

import { ComponentFixture, TestBed } from '@angular/core/testing';

import { CameraStreamComponent } from './camera-stream.component';

describe('CameraStreamComponent', () => {
let component: CameraStreamComponent;
let fixture: ComponentFixture<CameraStreamComponent>;

beforeEach(() => {
TestBed.configureTestingModule({
declarations: [CameraStreamComponent]
});
fixture = TestBed.createComponent(CameraStreamComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});

it('should create', () => {
expect(component).toBeTruthy();
});
});

+ 0
- 331
src/app/modules/overview/map/map.component.ts View File

import {
AfterViewInit,
Component,
OnDestroy,
OnInit
} from '@angular/core';
import * as L from 'leaflet';
import 'leaflet.markercluster';
import { Subscription } from 'rxjs';
import { SocketService } from '../../../shared/services/socket.service';


const alarmData = [
{
title: ' Alarm System 1',
detail: {
position: 'Hanoi',
coordinates: {
lat: 21.0285,
lng: 105.8542
},
time: '06-07-2024'
},
warning: true
},
{
title: ' Alarm System 2',
detail: {
position: 'Hai Phong',
coordinates: {
lat: 20.8449,
lng: 106.6881
},
time: '06-07-2024'
},
warning: true
},
{
title: ' Alarm System 3',
detail: {
position: 'Ha Long',
coordinates: {
lat: 20.9460,
lng: 107.0740
},
time: '06-07-2024'
},
warning: false
},
{
title: ' Alarm System 4',
detail: {
position: 'Vinh',
coordinates: {
lat: 18.6796,
lng: 105.6813
},
time: '06-07-2024'
},
warning: true
},
{
title: ' Alarm System 5',
detail: {
position: 'Dong Hoi',
coordinates: {
lat: 17.4834,
lng: 106.6000
},
time: '06-07-2024'
},
warning: false
},
{
title: ' Alarm System 6',
detail: {
position: 'Hue',
coordinates: {
lat: 16.4637,
lng: 107.5909
},
time: '06-07-2024'
},
warning: true
},
{
title: ' Alarm System 7',
detail: {
position: 'Da Nang',
coordinates: {
lat: 16.0471,
lng: 108.2068
},
time: '06-07-2024'
},
warning: false
},
{
title: ' Alarm System 8',
detail: {
position: 'Quy Nhon',
coordinates: {
lat: 13.7820,
lng: 109.2198
},
time: '06-07-2024'
},
warning: false
},
{
title: ' Alarm System 9',
detail: {
position: 'Nha Trang',
coordinates: {
lat: 12.2388,
lng: 109.1967
},
time: '06-07-2024'
},
warning: true
},
{
title: ' Alarm System 10',
detail: {
position: 'Da Lat',
coordinates: {
lat: 11.9416,
lng: 108.4580
},
time: '06-07-2024'
},
warning: false
},
{
title: ' Alarm System 11',
detail: {
position: 'Ho Chi Minh City',
coordinates: {
lat: 10.8231,
lng: 106.6297
},
time: '06-07-2024'
},
warning: false
},
{
title: ' Alarm System 12',
detail: {
position: 'Can Tho',
coordinates: {
lat: 10.0452,
lng: 105.7469
},
time: '06-07-2024'
},
warning: true
},

];


const alarmDemo = {
title: ' Demo Alarm System', // Thêm mới warning
detail: {
position: 'Vinhomes Quận 9',
coordinates: {
lat: 10.7483,
lng: 106.8016
},
time: '06-07-2024'
},
}
@Component({
selector: 'app-map',
templateUrl: './map.component.html',
styleUrls: ['./map.component.scss']
})
export class MapComponent implements OnInit, AfterViewInit, OnDestroy {
private map!: L.Map;
private markers!: L.MarkerClusterGroup;
private statusSubscription?: Subscription;
private messageSubscription?: Subscription;
data = alarmData;
alarmDemo = alarmDemo;
state1 = false;
state2 = false;
state5 = false;
state6 = false;
isReady = true;

constructor(private socketService$: SocketService) { }

ngOnInit() {
this.statusSubscription = this.socketService$.status$.subscribe(isConnected => {
if (isConnected) {
this.socketService$.sendMessage({ id: '0', type: 'get' });
this.messageSubscription = this.socketService$.messages$.subscribe(message => {
this.onMessage(message);
});
}
});
}

ngAfterViewInit(): void {
this.initMap();
}

ngOnDestroy(): void {
this.statusSubscription?.unsubscribe();
this.messageSubscription?.unsubscribe();
this.socketService$.close();
}

initMap(): void {
const mapContainer = document.getElementById('map');
if (mapContainer) {
this.map = L.map('map', {
center: [10.7483, 106.7537],
zoom: 12
});

L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
maxZoom: 15,
minZoom: 3,
attribution: '&copy; <a href="http://www.openstreetmap.org/copyright">OpenStreetMap</a>'
}).addTo(this.map);

this.addIconToMap();
} else {
console.error('Map container not found');
}
}

onMessage(message: any) {
if (message.id == '0' && message.type === 'get') {
this.state1 = message.state1 === '0';// 1 OFF // alarm 12h
this.state2 = message.state2 === '0'; // 1 OFF // alarm 1h
this.state5 = message.state5 === '1';// 1 ON, 0 OFF
this.isReady = message.ready === '1';
this.updateIcons();
}
}

addIconToMap() {
this.markers = L.markerClusterGroup();
this.data.forEach((item: any) => {
// create icon
const icon = this.createIcon(item.warning);
// add icon into map
L.marker([item.detail.coordinates.lat, item.detail.coordinates.lng], { icon: icon })
.bindPopup(this.popupDetail(item))
.addTo(this.markers);
});
const iconDemo = this.getIcon(this.state5, this.isReady, this.state1, this.state2);
L.marker([this.alarmDemo.detail.coordinates.lat,this.alarmDemo.detail.coordinates.lng], { icon: iconDemo })
.bindPopup(this.popupDetail(this.alarmDemo))
.addTo(this.markers);

this.markers.addTo(this.map);
}

updateIcons(): void {
//clear layer icon mỗi lần update
this.markers.clearLayers();
this.addIconToMap();
}

popupDetail(item: any): string {
return `
<div class="tooltip" style="width: 200px">
<div style="display: flex; flex-direction: row; justify-content: space-between; margin-bottom: 5px">
<div style="color: #F33152; padding: 5px 13px; background-color: rgba(243, 49, 82, 0.1); border-radius: 20px; text-align: center; max-width: 180px;">${item.title}</div>
<a href="/overview/camera-stream" target="_blank">
<svg width="24px" height="24px" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" fill="none">
<g stroke="currentColor" stroke-width="2">
<path d="M16 16V8a1 1 0 00-1-1H5a1 1 0 00-1 1v8a1 1 0 001 1h10a1 1 0 001-1z"/>
<path stroke-linejoin="round" d="M20 7l-4 3v4l4 3V7z"/>
</g>
</svg>
</a>
</div>
<div><strong>Địa điểm:</strong> ${item.detail.position}</div>
<div><strong>Tọa độ:</strong> ${item.detail.coordinates.lat}, ${item.detail.coordinates.lng}</div>
<div><strong>Thời gian:</strong> ${item.detail.time}</div>
<a href="/overview/overall-ground" target="_blank">Xem chi tiết</a>
</div>`;
}

createIcon(active: boolean, className: string = '', text: any = []): L.Icon<L.IconOptions> | L.DivIcon {
if (text.length < 1) {
return L.icon({
iconUrl: active ? '../../../../assets/images/sensor-on.png' : '../../../../assets/images/sensor-off.png',
iconSize: [30, 30],
className: className
});
} else {
let htmlContent = '';
text.forEach((item: any) => {
htmlContent += `<span><b>${item}</b></span><br>`;
});

return L.divIcon({
html: `<div class="icon">
<div style="z-index:9999" class="sensor-on">
<img alt="icon-alarm" src ="assets/images/sensor-on.png" style="width: 30px; height: 30px">
</div>
<div style="background: #F11E1E; color: #FFF; padding: 2px 3px; font-size: 11px; margin-top: 10px; text-align: center">
${htmlContent}
</div>
</div>`,
iconSize: [200, 30],
className: className
});
}
}

getIcon(isTurnOn: boolean, isReady: boolean, fireArm: boolean, fenceArm: boolean): L.Icon<L.IconOptions> | L.DivIcon {
if (isTurnOn && isReady) {
let text = [];
if (fireArm && fenceArm) {
text.push('FIRE ALARM', 'FENCE ALARM');
} else if (fireArm) {
text.push('FIRE ALARM');
} else if (fenceArm) {
text.push('FENCE ALARM');
}
return this.createIcon(true, '', text);
}
return this.createIcon(false);
}
}

+ 0
- 58
src/app/modules/overview/overall-ground/overall-ground.component.html View File

<div class="px-3 py-5" fxLayout="row" fxLayoutAlign="space-around center" fxLayoutGap="20px">
<div fxFlex="50" class="map-image">
<div class="card-state">
<img src="assets/images/ground.png">


<div class="state t2" id="State2">
<div>
<div class="sensor-off" [class.sensor-on]="(state1 && state5 && isReady)">
<img [src]="state5 ? 'assets/images/sensor-on.png' : 'assets/images/sensor-off.png'" style="width: 30px; height: 30px">
</div>
<a href="/overview/camera-stream" target="_blank">
<img style="width: 30px; height: 30px;" src="assets/images/camera.png">
</a>
</div>
<div *ngIf="(state1 && state5 && isReady)" class="alarm-text"
[ngClass]="{'alarm-text-on': (state1 && state5 && isReady) }">FIRE ALARM</div>
</div>

<div class="state t3" id="State3">
<div>
<div class="sensor-off" [class.sensor-on]="(state2 && state5 && isReady)">
<img [src]="state5 ? 'assets/images/sensor-on.png' : 'assets/images/sensor-off.png'" style="width: 30px; height: 30px">
</div>
<a href="/overview/camera-stream" target="_blank">
<img style="width: 30px; height: 30px;" src="assets/images/camera.png">
</a>
</div>
<div *ngIf="(state2 && state5 && isReady)" class="alarm-text"
[ngClass]="{'alarm-text-on': (state2 && state5 && isReady) }">FENCE ALARM</div>
</div>
<div class="state t4" id="State4">
<div>
<img [src]="getImageSource()" style="width: 30px; height: 30px">
<a href="/overview/camera-stream" target="_blank">
<img style="width: 30px; height: 30px;" src="assets/images/camera.png">
</a>
</div>
</div>
<div class="state t5 tooltip" id="State5" >
<img [src]="getImageSource()" style="width: 30px; height: 30px">
<a href="/overview/camera-stream" target="_blank">
<img style="width: 30px; height: 30px;" src="assets/images/camera.png">
</a>
</div>
<div class="state t6 tooltip" id="State6">
<img [src]="getImageSource()" style="width: 30px; height: 30px">
<a href="/overview/camera-stream" target="_blank">
<img style="width: 30px; height: 30px;" src="assets/images/camera.png">
</a>
</div>
</div>
</div>
<div fxFlex="30" fxLayout="column" fxLayoutGap="50px">
<button [disabled]="!isConnected" mat-flat-button color="{{switchArm ? 'accent' : 'primary'}}" (click)="toggleState1()">{{ switchArm ? 'DISARM' : 'ARM'}}</button>

</div>
</div>

+ 0
- 21
src/app/modules/overview/overall-ground/overall-ground.component.spec.ts View File

import { ComponentFixture, TestBed } from '@angular/core/testing';

import { OverallGroundComponent } from './overall-ground.component';

describe('OverallGroundComponent', () => {
let component: OverallGroundComponent;
let fixture: ComponentFixture<OverallGroundComponent>;

beforeEach(() => {
TestBed.configureTestingModule({
declarations: [OverallGroundComponent]
});
fixture = TestBed.createComponent(OverallGroundComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});

it('should create', () => {
expect(component).toBeTruthy();
});
});

+ 0
- 25
src/app/modules/overview/overview.module.ts View File

import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';

import {OverallGroundComponent} from "./overall-ground/overall-ground.component";
import {RouterModule} from "@angular/router";
import {overviewRoutes} from "./overview.routing";
import {MapComponent} from "./map/map.component";
import {SharedMaterialModule} from "../../shared/shared-material.module";
import { CameraStreamComponent } from './camera-stream/camera-stream.component';


@NgModule({
declarations: [
OverallGroundComponent,
MapComponent,
CameraStreamComponent
],
imports: [
CommonModule,
RouterModule.forChild(overviewRoutes),
SharedMaterialModule,

]
})
export class OverviewModule { }

+ 0
- 21
src/app/modules/overview/overview.routing.ts View File

import {Routes} from "@angular/router";
import {MapComponent} from "./map/map.component";
import {OverallGroundComponent} from "./overall-ground/overall-ground.component";
import {CameraStreamComponent} from "./camera-stream/camera-stream.component";

export const overviewRoutes: Routes = [
{
path: '',
component: MapComponent,
},
{
path: 'overall-ground',
component: OverallGroundComponent
},
{
path: 'camera-stream',
component: CameraStreamComponent
}

];


+ 11
- 0
src/app/shared/component/camera-dialog/camera-dialog.component.html View File


<div style="padding: 10px 20px" fxLayout="row" fxLayoutAlign=" center">
<div style="text-align: center; font-size: 24px; width: 95%">Camera Stream</div>
<button mat-icon-button color="warn" (click)="onClose()">
<mat-icon>close</mat-icon>
</button>
</div>
<div mat-dialog-content>
<canvas class="video" #videoPlayer></canvas>
</div>


+ 9
- 0
src/app/shared/component/camera-dialog/camera-dialog.component.scss View File

.video {
width: 90% !important;
//height: 90%;
display: block;
margin: 0 auto
}
.mat-mdc-dialog-content{
max-height: unset;
}

+ 21
- 0
src/app/shared/component/camera-dialog/camera-dialog.component.spec.ts View File

import { ComponentFixture, TestBed } from '@angular/core/testing';

import { CameraDialogComponent } from './camera-dialog.component';

describe('CameraDialogComponent', () => {
let component: CameraDialogComponent;
let fixture: ComponentFixture<CameraDialogComponent>;

beforeEach(() => {
TestBed.configureTestingModule({
declarations: [CameraDialogComponent]
});
fixture = TestBed.createComponent(CameraDialogComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});

it('should create', () => {
expect(component).toBeTruthy();
});
});

src/app/modules/overview/camera-stream/camera-stream.component.ts → src/app/shared/component/camera-dialog/camera-dialog.component.ts View File

import {Component, OnInit, ViewChild, ElementRef, AfterViewInit, Renderer2} from '@angular/core';
import {AfterViewInit, Component, ElementRef, ViewChild} from '@angular/core';
import {MatDialogRef} from "@angular/material/dialog";
import {loadPlayer, Player} from "rtsp-relay/browser"; import {loadPlayer, Player} from "rtsp-relay/browser";


@Component({ @Component({
selector: 'app-camera-stream',
templateUrl: './camera-stream.component.html',
styleUrls: ['./camera-stream.component.scss']
selector: 'app-camera-dialog',
templateUrl: './camera-dialog.component.html',
styleUrls: ['./camera-dialog.component.scss']
}) })
export class CameraStreamComponent implements OnInit, AfterViewInit{
player?: Player;
export class CameraDialogComponent implements AfterViewInit{


player?: Player;
@ViewChild('videoPlayer') @ViewChild('videoPlayer')
videoPlayer?: ElementRef<HTMLCanvasElement>; videoPlayer?: ElementRef<HTMLCanvasElement>;


constructor(private el: ElementRef, private renderer: Renderer2) {}


ngOnInit() {
}
constructor(public dialogRef: MatDialogRef<CameraDialogComponent>) { }


async ngAfterViewInit() { async ngAfterViewInit() {
const connect = async () => { const connect = async () => {
connect(); connect();
} }


onClose(): void {
this.dialogRef.close();
}


} }

+ 11
- 0
src/app/shared/component/confirm-dialog/confirm-dialog.component.html View File

<h5 mat-dialog-title >CONFIRM TO IGNORE THE WARNING</h5>
<mat-dialog-content style="padding-bottom: 4px">
<div *ngFor="let item of data">
<mat-checkbox [(ngModel)]="item.value" (ngModelChange)="submitChange(item)" name="sensor">
{{ (item.key == 'status1' ? 'Fire' : 'ff')}}
</mat-checkbox>
</div>
</mat-dialog-content>
<mat-dialog-actions align="end">
<button mat-button mat-dialog-close cdkFocusInitial>Close</button>
</mat-dialog-actions>

+ 10
- 0
src/app/shared/component/confirm-dialog/confirm-dialog.component.scss View File

.mat-mdc-dialog-title {
border-bottom: solid 1px #eeeeee;
font-size: 14px;
color: #FFFFFF;
background: orange;
}
::ng-deep.mdc-checkbox .mdc-checkbox__native-control:enabled:checked~.mdc-checkbox__background{
background: orange !important;
border: orange !important;
}

+ 21
- 0
src/app/shared/component/confirm-dialog/confirm-dialog.component.spec.ts View File

import { ComponentFixture, TestBed } from '@angular/core/testing';

import { ConfirmDialogComponent } from './confirm-dialog.component';

describe('ConfirmDialogComponent', () => {
let component: ConfirmDialogComponent;
let fixture: ComponentFixture<ConfirmDialogComponent>;

beforeEach(() => {
TestBed.configureTestingModule({
declarations: [ConfirmDialogComponent]
});
fixture = TestBed.createComponent(ConfirmDialogComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});

it('should create', () => {
expect(component).toBeTruthy();
});
});

+ 25
- 0
src/app/shared/component/confirm-dialog/confirm-dialog.component.ts View File

import { Component } from '@angular/core';
import { MatDialogRef } from '@angular/material/dialog';

@Component({
selector: 'app-confirm-dialog',
templateUrl: './confirm-dialog.component.html',
styleUrls: ['./confirm-dialog.component.scss'],
})
export class ConfirmDialogComponent {
constructor(public dialogRef: MatDialogRef<ConfirmDialogComponent>) {}
data = [
{
key: 'status1',
value: false,
},
{
name: 'status2',
value: false,
},
];
submitChange(item: any): void{
console.log(item);
// call socket here
}
}

+ 13
- 0
src/app/shared/component/layout/layout.component.html View File

<div class="layout-container">
<div class="header mat-elevation-z1">
<img src="../../../../../../assets/images/logo.png" >
<div>
<button mat-button routerLink="/homepage" routerLinkActive="active">Home</button>
</div>
</div>
<div>
<router-outlet></router-outlet>
</div>
</div>



+ 23
- 0
src/app/shared/component/layout/layout.component.scss View File

.layout-container{
display: flex;
flex-direction: column;
justify-content: space-between;
.header {
height: 3rem;
padding: .25rem 2rem;
display: flex;
flex-direction: row;
align-items: center;
img{
width: 7rem;
height: 3rem;
}
button {
width: 5rem;
}
.active {
color: #ff7723 !important;
}
}

}

+ 21
- 0
src/app/shared/component/layout/layout.component.spec.ts View File

import { ComponentFixture, TestBed } from '@angular/core/testing';

import { LayoutComponent } from './layout.component';

describe('LayoutComponent', () => {
let component: LayoutComponent;
let fixture: ComponentFixture<LayoutComponent>;

beforeEach(() => {
TestBed.configureTestingModule({
declarations: [LayoutComponent]
});
fixture = TestBed.createComponent(LayoutComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});

it('should create', () => {
expect(component).toBeTruthy();
});
});

+ 10
- 0
src/app/shared/component/layout/layout.component.ts View File

import { Component } from '@angular/core';

@Component({
selector: 'app-layout',
templateUrl: './layout.component.html',
styleUrls: ['./layout.component.scss']
})
export class LayoutComponent {

}

+ 17
- 22
src/app/shared/component/shared-component.module.ts View File

import {NgModule} from "@angular/core";
import {CommonModule} from "@angular/common";
import {SharedMaterialModule} from "../shared-material.module";
import {RouterModule} from "@angular/router";
const components = [
];
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { SharedMaterialModule } from '../shared-material.module';
import { RouterModule } from '@angular/router';
import { LayoutComponent } from './layout/layout.component';
import { SliderRangeComponent } from './slider-range/slider-range.component';
import { FormsModule } from '@angular/forms';
import { CameraDialogComponent } from './camera-dialog/camera-dialog.component';
import { ConfirmDialogComponent } from './confirm-dialog/confirm-dialog.component';


@NgModule({ @NgModule({
declarations: [ declarations: [
// ...components,
LayoutComponent,
SliderRangeComponent,
CameraDialogComponent,
ConfirmDialogComponent,
], ],
imports: [
CommonModule,
SharedMaterialModule,
RouterModule,
],
exports: [
// ...components,

],
providers: []
imports: [CommonModule, SharedMaterialModule, RouterModule, FormsModule],
exports: [LayoutComponent, SliderRangeComponent, CameraDialogComponent],
providers: [],
}) })
export class SharedComponentModule {
}
export class SharedComponentModule {}

+ 41
- 0
src/app/shared/component/slider-range/slider-range.component.html View File

<ng-content></ng-content>
<div class="volume-group">
<div class="speaker">
<mat-icon>{{icon}}</mat-icon>
</div>
<button class="volume-control" (click)="decreaseVolume()">
<svg width="24px" height="24px" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink">
<title>Icons/Minus square fill</title>
<g id="Icons/Minus-square-fill" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="MinusSquare">
<path
d="M19.5,3 L4.5,3 C3.67157288,3 3,3.67157288 3,4.5 L3,19.5 C3,20.3284271 3.67157288,21 4.5,21 L19.5,21 C20.3284271,21 21,20.3284271 21,19.5 L21,4.5 C21,3.67157288 20.3284271,3 19.5,3 Z M15.75,12.75 L8.25,12.75 C7.83578644,12.75 7.5,12.4142136 7.5,12 C7.5,11.5857864 7.83578644,11.25 8.25,11.25 L15.75,11.25 C16.1642136,11.25 16.5,11.5857864 16.5,12 C16.5,12.4142136 16.1642136,12.75 15.75,12.75 Z"
id="Shape" fill="currentColor" fill-rule="nonzero"></path>
<rect id="Rectangle" x="0" y="0" width="24" height="24"></rect>
</g>
</g>
</svg>
</button>
<div class="volume-control" style="padding: 1rem 0">
<div class="volume-slider">
<input type="range" [(ngModel)]="value" (input)="onSliderChange($event)"
[ngStyle]="onSliderChangeBackground()"
[max]="max">
</div>
</div>
<button class="volume-control" (click)="increaseVolume()">
<svg width="24px" height="24px" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink">
<title>Icons/Plus square fill</title>
<g id="Icons/Plus-square-fill" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="Plus">
<path
d="M19.5,3 L4.5,3 C3.67157288,3 3,3.67157288 3,4.5 L3,19.5 C3,20.3284271 3.67157288,21 4.5,21 L19.5,21 C20.3284271,21 21,20.3284271 21,19.5 L21,4.5 C21,3.67157288 20.3284271,3 19.5,3 Z M17.25,12.75 L12.75,12.75 L12.75,17.25 C12.75,17.6642136 12.4142136,18 12,18 C11.5857864,18 11.25,17.6642136 11.25,17.25 L11.25,12.75 L6.75,12.75 C6.33578644,12.75 6,12.4142136 6,12 C6,11.5857864 6.33578644,11.25 6.75,11.25 L11.25,11.25 L11.25,6.75 C11.25,6.33578644 11.5857864,6 12,6 C12.4142136,6 12.75,6.33578644 12.75,6.75 L12.75,11.25 L17.25,11.25 C17.6642136,11.25 18,11.5857864 18,12 C18,12.4142136 17.6642136,12.75 17.25,12.75 Z"
id="Shape" fill="currentColor" fill-rule="nonzero"></path>
<rect id="Rectangle" x="0" y="0" width="24" height="24"></rect>
</g>
</g>
</svg>
</button>
</div>

+ 149
- 0
src/app/shared/component/slider-range/slider-range.component.scss View File

.volume-group {
margin-top: 1.5rem;
padding: 0 2rem;
display: flex;
flex-direction: row;
gap: 1.5rem;
align-items: center;
justify-content: space-between;

.speaker {
background: transparent;
padding: 1rem;
clip-path: polygon(50% 0, 100% 25%, 100% 75%, 50% 100%, 0 75%, 0 25%);
position: relative;
color: #ff7723;
&:before {
content: "";
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
clip-path: polygon(
50% 0,
100% 25%,
100% 75%,
50% 100%,
0 75%,
0 25%,
50% 0,
50% 1px,
1px calc(25% + .5px),
1px calc(75% - .5px),
50% calc(100% - 1px),
calc(100% - 1px) calc(75% - .5px),
calc(100% - 1px) calc(25% + .5px),
50% 1px
);
background-color: #ff7723;
}
}

.volume-control {
padding: .63rem;
border-top: solid .06rem #ff7723;
border-bottom: solid .06rem #ff7723;
background-color: transparent;
border-left: none;
border-right: none;

svg {
cursor: pointer;
width: 1.5rem;
height: 1.5rem;
}

}

.volume-slider {
border: .06rem solid #eed3c2;
height: .75rem;
width: 39rem;
position: relative;

input[type="range"] {
position: relative;
-webkit-appearance: none;
-moz-appearance: none;
display: block;
outline: none;
height: 4px;
width: 38.5rem;
appearance: none;
background-color: transparent;
margin: .25rem;

&::-webkit-slider-thumb {
-webkit-appearance: none;
appearance: none;
background: #ff7723
;
border: .06rem solid #ff7723;
height: .88rem;
width: .88rem;
padding: 2px;
background-clip: content-box;
transition: transform .2s ease;

&:focus {
cursor: pointer;
border: .12rem solid #ff7723;
transform: scale(1.2);
}

&:hover {
cursor: pointer;
border: .12rem solid #ff7723;
transform: scale(1.2);
}
}

&::-moz-range-thumb {
border-radius: 0;
background: #ff7723;
border: .06rem solid #ff7723;
height: .5rem;
width: .5rem ;
padding: 3px;
background-clip: content-box;
&:focus {
cursor: pointer;
border: .14rem solid #ff7723;
transform: scale(1);
}

&:hover {
cursor: pointer;
border: .14rem solid #ff7723;
transform: scale(1);
}
}

&::-ms-thumb {
-webkit-appearance: none;
appearance: none;
background: #ff7723;
border: .06rem solid #ff7723;
height: .88rem;
width: .88rem;
padding: 3px;
background-clip: content-box;
&:focus {
cursor: pointer;
border: .12rem solid #ff7723;
transform: scale(1.2);
}

&:hover {
cursor: pointer;
border: .12rem solid #ff7723;
transform: scale(1.2);
}
}
}
}
}
svg{
color: #ff7723;
}

+ 21
- 0
src/app/shared/component/slider-range/slider-range.component.spec.ts View File

import { ComponentFixture, TestBed } from '@angular/core/testing';

import { SliderRangeComponent } from './slider-range.component';

describe('SilderRangeComponent', () => {
let component: SliderRangeComponent;
let fixture: ComponentFixture<SliderRangeComponent>;

beforeEach(() => {
TestBed.configureTestingModule({
declarations: [SliderRangeComponent]
});
fixture = TestBed.createComponent(SliderRangeComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});

it('should create', () => {
expect(component).toBeTruthy();
});
});

+ 103
- 0
src/app/shared/component/slider-range/slider-range.component.ts View File

import {
Component,
EventEmitter,
forwardRef,
Input,
Output,
} from '@angular/core';
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';

@Component({
selector: 'app-slider-range',
templateUrl: './slider-range.component.html',
styleUrls: ['./slider-range.component.scss'],
providers: [
{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => SliderRangeComponent),
multi: true,
},
],
})
export class SliderRangeComponent implements ControlValueAccessor {
@Input() value: number = 10;
@Input() max: number = 300;
@Input() icon: string = '';
@Output() valueChange = new EventEmitter<number>();

onChange = (value: number) => {};
onTouched = () => {};

constructor() {}

writeValue(value: number): void {
this.value = value;
}

registerOnChange(fn: any): void {
this.onChange = fn;
}

registerOnTouched(fn: any): void {
this.onTouched = fn;
}

setDisabledState?(isDisabled: boolean): void {
// Handle the disabled state if needed
}

onSliderChange(event: any) {
this.value = event.target.value;
this.onChange(this.value);
this.onTouched();
this.valueChange.emit(this.value);
}

onSliderChangeBackground() {
let valPercent = (this.value / this.max) * 100;

// Adjust valPercent based on your conditions
switch (true) {
case valPercent > 10 && valPercent <= 30:
valPercent -= 0.5;
break;
case valPercent > 30 && valPercent <= 50:
valPercent -= 1;
break;
case valPercent > 50 && valPercent <= 70:
valPercent -= 1.5;
break;
case valPercent > 70 && valPercent <= 90:
valPercent -= 2;
break;
case valPercent > 90 && valPercent <= 100:
valPercent -= 2.3;
break;
default:
break;
}

// Return CSS object with background style
return {
background: `linear-gradient(to right, #ff7723 ${valPercent}%, transparent ${valPercent}%)`,
};
}

increaseVolume() {
if (this.value < this.max) {
this.value += 10;
this.onChange(this.value);
this.onTouched();
this.valueChange.emit(this.value);
}
}

decreaseVolume() {
if (this.value > 0) {
this.value -= 10;
this.onChange(this.value);
this.onTouched();
this.valueChange.emit(this.value);
}
}
}

+ 58
- 0
src/app/shared/services/alarm-sound.service.ts View File

import { Injectable } from '@angular/core';

@Injectable({
providedIn: 'root',
})
export class AlarmSoundService {
private alertInterval: any;
private alertDuration: number = 10000;
private audio = new Audio();
private isPlaying: boolean = false;

constructor() {
this.audio.src = 'assets/sound/alarm_5m.mp3';
this.audio.load();
this.audio.onended = () => {
// Khi phát hết âm thanh, đặt biến isPlaying về false
this.isPlaying = false;
};
}

playSound(): void {
if (!this.isPlaying) {
this.audio.play().then(() => {
this.isPlaying = true;
this.alertInterval = setInterval(() => {
this.stopAlarm();
}, this.alertDuration);
}).catch((error) => {
console.error('Error playing audio:', error);
});
}
}

stopSound(): void {
if (this.audio) {
this.audio.pause();
this.audio.currentTime = 0;
this.isPlaying = false;
}
}

startAlarm( isTurnOn: boolean, isReady: boolean, fireArm: boolean, fenceArm: boolean): void {
if (isTurnOn && isReady && (fireArm || fenceArm)) {
//this.alertDuration = // setting time
this.playSound();
}else{
this.stopAlarm();
}
}

stopAlarm(): void {
this.stopSound();
if (this.alertInterval){
clearInterval(this.alertInterval);
}
}

}

+ 27
- 0
src/app/shared/services/confirm-dialog.service.ts View File

import { ElementRef, Injectable } from '@angular/core';
import { MatDialog } from '@angular/material/dialog';
import { take } from 'rxjs';
import { ConfirmDialogComponent } from '../component/confirm-dialog/confirm-dialog.component';

@Injectable({
providedIn: 'root',
})
export class ConfirmDialogService {
private isDialogOpen = false;

constructor(private dialog: MatDialog) {}

openDialog(data: any[]): void {
if (!this.isDialogOpen) {
this.isDialogOpen = true;
const dialogRef = this.dialog.open(ConfirmDialogComponent);

dialogRef
.afterClosed()
.pipe(take(1))
.subscribe(() => {
this.isDialogOpen = false;
});
}
}
}

+ 1
- 3
src/app/shared/services/mqtt-client.service.ts View File

import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { webSocket, WebSocketSubject } from 'rxjs/webSocket';
import { BehaviorSubject,Subject } from "rxjs";
import { config } from "../../../assets/config/config";

import { import {
IMqttMessage, IMqttMessage,
IMqttServiceOptions, IMqttServiceOptions,

+ 17
- 5
src/app/shared/shared-material.module.ts View File

import { NgModule } from "@angular/core";
import { FlexLayoutModule } from "@angular/flex-layout";
import { MatButtonModule } from "@angular/material/button";
import { NgModule } from '@angular/core';
import { FlexLayoutModule } from '@angular/flex-layout';
import { MatButtonModule } from '@angular/material/button';
import { MatDialogModule } from '@angular/material/dialog';
import { MatIconModule } from '@angular/material/icon';
import { MatMenuModule } from '@angular/material/menu';
import { MatExpansionModule } from '@angular/material/expansion';
import { MatCardModule } from '@angular/material/card';
import { MatCheckboxModule } from '@angular/material/checkbox';


@NgModule({ @NgModule({
exports: [ exports: [
MatButtonModule, MatButtonModule,
FlexLayoutModule
]
FlexLayoutModule,
MatDialogModule,
MatIconModule,
MatMenuModule,
MatExpansionModule,
MatCardModule,
MatCheckboxModule,
],
}) })
export class SharedMaterialModule {} export class SharedMaterialModule {}

+ 2
- 1
src/app/shared/shared.module.ts View File

import { NgModule } from '@angular/core'; import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common'; import { CommonModule } from '@angular/common';
import {SharedComponentModule} from "./component/shared-component.module"; import {SharedComponentModule} from "./component/shared-component.module";
import {FormsModule} from "@angular/forms";


@NgModule({ @NgModule({
declarations: [], declarations: [],
imports: [ imports: [
CommonModule, CommonModule,
SharedComponentModule, SharedComponentModule,
FormsModule
], ],
exports: [ exports: [
SharedComponentModule, SharedComponentModule,

BIN
src/assets/images/logo.png View File

Before After
Width: 500  |  Height: 224  |  Size: 25KB

BIN
src/assets/sound/alarm.mp3 View File


BIN
src/assets/sound/alarm2.mp3 View File


BIN
src/assets/sound/alarm_5m.mp3 View File


Loading…
Cancel
Save