| 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 {} |
| 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(); | ||||
| } | } |
| 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>`, | |||||
| } |
| .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 { |
| 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(); | |||||
| }); | |||||
| }); |
| 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: | |||||
| '© <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); | |||||
| } | |||||
| } |
| 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' | |||||
| }, | |||||
| } |
| 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 {} |
| 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, | |||||
| }, | |||||
| ]; |
| <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> |
| .sound-group{ | |||||
| margin: 2rem 3rem; | |||||
| } | |||||
| button{ | |||||
| color: #ff7723 !important; | |||||
| } |
| 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(); | |||||
| }); | |||||
| }); |
| 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, | |||||
| }; | |||||
| } |
| <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> |
| .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; | ||||
| } | } |
| 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(); | |||||
| }); | |||||
| }); |
| 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: '', | |||||
| }); | |||||
| } | |||||
| } | } |
| <h3 style="text-align: center">Camera Stream</h3> | |||||
| <canvas class="video" #videoPlayer></canvas> |
| .video { | |||||
| width: 78% !important; | |||||
| display: block; | |||||
| margin: 0 auto | |||||
| } |
| 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(); | |||||
| }); | |||||
| }); |
| 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: '© <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); | |||||
| } | |||||
| } |
| <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> |
| 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(); | |||||
| }); | |||||
| }); |
| 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 { } |
| 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 | |||||
| } | |||||
| ]; | |||||
| <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> | |||||
| .video { | |||||
| width: 90% !important; | |||||
| //height: 90%; | |||||
| display: block; | |||||
| margin: 0 auto | |||||
| } | |||||
| .mat-mdc-dialog-content{ | |||||
| max-height: unset; | |||||
| } |
| 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(); | |||||
| }); | |||||
| }); |
| 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(); | |||||
| } | |||||
| } | } |
| <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> |
| .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; | |||||
| } |
| 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(); | |||||
| }); | |||||
| }); |
| 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 | |||||
| } | |||||
| } |
| <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> | |||||
| .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; | |||||
| } | |||||
| } | |||||
| } |
| 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(); | |||||
| }); | |||||
| }); |
| import { Component } from '@angular/core'; | |||||
| @Component({ | |||||
| selector: 'app-layout', | |||||
| templateUrl: './layout.component.html', | |||||
| styleUrls: ['./layout.component.scss'] | |||||
| }) | |||||
| export class LayoutComponent { | |||||
| } |
| 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 {} |
| <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> |
| .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; | |||||
| } |
| 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(); | |||||
| }); | |||||
| }); |
| 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); | |||||
| } | |||||
| } | |||||
| } |
| 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); | |||||
| } | |||||
| } | |||||
| } |
| 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; | |||||
| }); | |||||
| } | |||||
| } | |||||
| } |
| 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, |
| 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 {} |
| 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, |