feat : stop message sync with 2nd screen also

This commit is contained in:
karthik 2025-09-18 12:27:47 +05:30
parent 7ebce2dbcd
commit 1cfe2fd970
9 changed files with 153 additions and 106 deletions

View File

@ -1,4 +1,3 @@
// main.js
const { app, BrowserWindow, screen, ipcMain } = require('electron');
const path = require('path');
const fs = require('fs');
@ -23,7 +22,7 @@ function createWindows() {
},
});
mainWindow.loadURL('http://10.74.231.124:4200/login');
mainWindow.loadURL('http://10.150.40.124:4200/login');
screenWindow = new BrowserWindow({
x: secondary.bounds.x,
@ -38,7 +37,7 @@ function createWindows() {
},
});
screenWindow.loadURL('http://10.74.231.124:4200/shared-display');
screenWindow.loadURL('http://10.150.40.124:4200/shared-display');
// Handle opening second screen and send initial data
ipcMain.on('open-second-screen', () => {
@ -58,6 +57,13 @@ function createWindows() {
}
});
// Add this new handler
ipcMain.on('sync-stop-message', (event, message) => {
if (screenWindow && screenWindow.webContents) {
screenWindow.webContents.send('update-stop-message', message);
}
});
// Handle BTID request
ipcMain.handle('get-btid', () => {
try {

View File

@ -1,12 +1,12 @@
// preload.js
const { contextBridge, ipcRenderer } = require('electron');
contextBridge.exposeInMainWorld('electronAPI', {
openSecondScreen: () => ipcRenderer.send('open-second-screen'),
closeSecondScreen: () => ipcRenderer.send('close-second-screen'),
getBtid: () => ipcRenderer.invoke('get-btid'),
// New: Send data to main process
syncSharedData: (data) => ipcRenderer.send('sync-shared-data', data),
// New: Receive data in second window
onUpdateSharedData: (callback) => ipcRenderer.on('update-shared-data', (event, data) => callback(data)),
// Add these new lines
syncStopMessage: (message) => ipcRenderer.send('sync-stop-message', message),
onUpdateStopMessage: (callback) => ipcRenderer.on('update-stop-message', (event, message) => callback(message)),
});

View File

@ -668,3 +668,28 @@
border-radius: 4px;
cursor: pointer;
}
/* Style for the scrolling stop message */
.stop-message {
background-color: red;
color: white;
font-weight: bold;
padding: 2px 5px;
margin: 0 10px;
border-radius: 4px;
display: inline-block;
max-width: 50%;
overflow: hidden;
}
.stop-message marquee {
font-size: 14px;
line-height: 20px;
}
/* Existing styles for other elements */
.text-light {
color: white !important;
}
/* Add other existing styles as needed */

View File

@ -3,24 +3,25 @@
<div class="text-light small ps-3">
<span class="date-time">{{ dateTime }}</span>
</div>
<div class="flex-grow-1 text-center">
<div *ngIf="stopMessage" class="stop-message">
<marquee behavior="scroll" direction="left" scrollamount="5">{{ stopMessage }}</marquee>
</div>
</div>
<div
class="d-flex align-items-center text-light small justify-content-end flex-grow-1"
>
<span class="btno-label btno-space">
<b>Operator:</b> {{ userName }}
</span>
<span class="btno-label me-3">
<b>B.T.No: </b> {{ btid }}
</span>
<div
class="status_box text-center"
[ngClass]="liveStatusOk ? 'live-green' : 'live-red'"
>
@if( liveStatusOk){
@if (liveStatusOk) {
<span>LIVE</span>
} @else {
<span>OFF-LINE</span>
@ -106,7 +107,6 @@
>
Race No. | {{ selectedRace }}
</button>
<div class="d-flex flex-column text-end">
<div
class="nav-button w-100 text-end pe-3 py-2 border-top"
@ -217,18 +217,10 @@
</div>
<!-- View Log Modal -->
<!-- <div class="viewlog-modal-overlay" *ngIf="showLogModal" (click)="closeModals()">
<div class="viewlog-modal-box" (click)="$event.stopPropagation()">
<h5>VIEW-LOG</h5>
<button class="cancel-btn" (click)="closeModals()">CANCEL</button>
</div>
</div> -->
<div class="viewlog-modal-overlay" *ngIf="showLogModal" (click)="closeModals()">
<div class="viewlog-modal-box" (click)="$event.stopPropagation()">
<!-- Modal Header -->
<h3 class="modal-title">VIEW-LOG</h3>
<!-- Scrollable Table Area -->
<div class="table-container">
<table class="view-log-table">
@ -240,7 +232,7 @@
<th>Tickets</th>
<th>Count</th>
<th>Amount</th>
<th>Barcode</th> <!-- ✅ New Column -->
<th>Barcode</th>
</tr>
</thead>
<tbody>
@ -256,7 +248,6 @@
</tbody>
</table>
</div>
<!-- CANCEL Button at Bottom -->
<div class="modal-footer">
<button class="cancel-btn" (click)="closeModals()">CANCEL</button>
@ -264,48 +255,6 @@
</div>
</div>
<!-- <div
class="viewlog-modal-overlay"
*ngIf="showLogModal"
(click)="closeModals()"
>
<div class="viewlog-modal-box" (click)="$event.stopPropagation()">
<h5 class="modal-title text-center">VIEW-LOG</h5>
<div class="modal-body" style="max-height: 400px; overflow-y: auto;">
<ng-container *ngIf="formattedTicketLogs.length > 0; else noLogs">
<table style="width: 100%; border-collapse: collapse;">
<thead>
<tr style="text-align: left;">
<th>Pool</th>
<th>Numbers</th>
<th>×Count</th>
<th>Amount</th>
</tr>
</thead>
<tbody>
<tr *ngFor="let log of formattedTicketLogs">
<td>{{ log.label }}</td>
<td>{{ log.numbers.join(',') }}</td>
<td>*{{ log.count }}</td>
<td>₹ {{ log.amount }}</td>
</tr>
</tbody>
</table>
</ng-container>
<ng-template #noLogs>
<p>No tickets found.</p>
</ng-template>
</div>
<div class="modal-footer text-center mt-3">
<button class="cancel-btn" (click)="closeModals()">CANCEL</button>
</div>
</div>
</div> -->
<!-- Venue Modal -->
<div class="modal-overlay" *ngIf="showVenueModal" (click)="closeModals()">
<div class="modal-box" (click)="$event.stopPropagation()">

View File

@ -44,7 +44,7 @@ export class NavbarComponent implements OnInit, OnDestroy {
selectedRaceId: number = 0;
enabledHorseNumbers: number[] = [];
multiLegBaseRaceIdx: number = 0;
multiLegBaseRaceIdx = 0;
currentPool: string | null = null;
private prevEnabledKey = '';
@ -75,6 +75,9 @@ export class NavbarComponent implements OnInit, OnDestroy {
private currentDate: string = '';
private eventSource: EventSource | undefined;
stopMessage: string = '';
private stopMessageTimeout: number | null = null;
formattedTicketLogs: {
pool: string;
horses: string;
@ -108,7 +111,7 @@ export class NavbarComponent implements OnInit, OnDestroy {
}, 1000);
});
// === Load structuredRaceCard from rpinfo if available (prefer structured) ===
// Load structuredRaceCard from rpinfo if available (prefer structured)
const rpCached = localStorage.getItem('rpinfo');
if (rpCached) {
try {
@ -120,7 +123,7 @@ export class NavbarComponent implements OnInit, OnDestroy {
this.raceCardData = {};
}
} else {
// fallback to older raceCardData key if present (non-structured)
// Fallback to older raceCardData key if present (non-structured)
const fallback = localStorage.getItem('raceCardData');
if (fallback) {
try {
@ -155,10 +158,51 @@ export class NavbarComponent implements OnInit, OnDestroy {
const statuses$ = this.stopbetService.getStopbetStatuses();
if (statuses$ && typeof statuses$.subscribe === 'function') {
this.stopbetSubscription = statuses$.subscribe((statuses: Map<number, string>) => {
this.stopbetStatuses.clear();
statuses.forEach((value, key) => {
this.stopbetStatuses.set(key, value);
const newStatuses = new Map(statuses);
const newStops: number[] = [];
// Detect newly stopped races
newStatuses.forEach((value, key) => {
const prev = this.stopbetStatuses.get(key);
if (prev === 'N' || prev === undefined) {
if (value === 'Y' || value === 'S') {
newStops.push(key);
}
}
});
this.stopbetStatuses = newStatuses;
// If there are new stops, display the message for 30 seconds
if (newStops.length > 0) {
newStops.sort((a, b) => a - b);
this.stopMessage = newStops.map(r => `Race ${r} Stopped`).join(' - ');
// Sync new stop message to electron main if available
if ((window as any).electronAPI) {
try {
(window as any).electronAPI.syncStopMessage(this.stopMessage);
} catch (e) {
console.warn('[NAVBAR] electronAPI.syncStopMessage failed:', e);
}
}
if (this.stopMessageTimeout) {
clearTimeout(this.stopMessageTimeout);
}
this.stopMessageTimeout = window.setTimeout(() => {
this.stopMessage = '';
this.stopMessageTimeout = null;
// Sync cleared message to electron main if available
if ((window as any).electronAPI) {
try {
(window as any).electronAPI.syncStopMessage(this.stopMessage);
} catch (e) {
console.warn('[NAVBAR] electronAPI.syncStopMessage (clear) failed:', e);
}
}
}, 50000);
}
console.log('[NAVBAR] Updated stopbetStatuses:', Object.fromEntries(this.stopbetStatuses));
this.checkAndSwitchIfStopped();
this.updateEnabledHorseNumbers();
@ -432,7 +476,6 @@ export class NavbarComponent implements OnInit, OnDestroy {
}
openVenueModal() {
// Prefer structuredRaceCard loaded at init (rpinfo). If empty, try legacy key.
if (!this.raceCardData || Object.keys(this.raceCardData).length === 0) {
const cachedData = localStorage.getItem('raceCardData');
if (cachedData) {
@ -447,7 +490,6 @@ export class NavbarComponent implements OnInit, OnDestroy {
}
}
// Use lowercase 'venue' as structuredRaceCard uses .venue
this.selectedVenue = this.raceCardData?.venue ?? this.raceCardData?.Venue ?? 'Select Venue';
this.updateEnabledHorseNumbers();
this.showVenueModal = true;
@ -500,7 +542,6 @@ export class NavbarComponent implements OnInit, OnDestroy {
}
selectRace(race: number) {
// Adjust race if the attempted one is stopped
const adjustedRace = this.isOpen(race) ? race : this.getOpenRaceStartingFrom(race);
this.selectedRace = adjustedRace;
@ -513,11 +554,9 @@ export class NavbarComponent implements OnInit, OnDestroy {
value: this.selectedRace,
});
// Use this.raceCardData (structured) rather than re-parsing localStorage
const raceList = this.raceCardData?.raceVenueRaces?.races ?? [];
const selectedRaceEntry = raceList[this.selectedRace - 1] ?? [];
// Determine runnerCount defensively based on possible shapes
let runnerCount = 12;
if (Array.isArray(selectedRaceEntry)) {
runnerCount = selectedRaceEntry.length || 12;
@ -573,22 +612,18 @@ export class NavbarComponent implements OnInit, OnDestroy {
displayBarcode = '';
if (rawLabel) {
// 1⃣ Extract pool (first word)
const parts = rawLabel.split(/\s+/);
pool = parts[0];
// 2⃣ Extract ticket count (*n) & price if exists
const countMatch = rawLabel.match(/\*(\d+)(?:\s+(Rs\s*\d+))?/);
if (countMatch) {
ticketCountLabel = `*${countMatch[1]}`;
price = countMatch[2] || '';
}
// 3⃣ Extract horses part (between pool name & ticket count)
const horsesPartMatch = rawLabel.match(/^\w+\s+(.+?)\s+\*\d+/);
if (horsesPartMatch) {
horses = horsesPartMatch[1].trim();
// Special pools split into races
if (['MJP', 'JKP', 'TRE'].includes(pool)) {
horsesArray = horses.split('/').map((r) => r.trim().split(',').map((h) => h.trim()));
} else {
@ -627,7 +662,7 @@ export class NavbarComponent implements OnInit, OnDestroy {
logout(): void {
const name = localStorage.getItem('userName') || 'Unknown User';
const employeeId = localStorage.getItem('employeeId') || '000000';
const stopbetData = localStorage.getItem('stopbetStatuses'); // Save stopbetStatuses
const stopbetData = localStorage.getItem('stopbetStatuses');
const printData = {
name,
@ -675,9 +710,11 @@ export class NavbarComponent implements OnInit, OnDestroy {
this.eventSource.close();
} catch {}
}
if (this.stopMessageTimeout) {
clearTimeout(this.stopMessageTimeout);
}
}
// Add trackByHorse for use in *ngFor
trackByHorse(index: number, item: number): number {
return item;
}

View File

@ -98,14 +98,14 @@ div[style*="background-color: black"] .custom-cell {
/* === Footer Always at Bottom === */
.footer {
height: 10vh;
background-color: #1c2c46a8;
color: white;
background-color: #fafbfca2;
color: rgb(126, 0, 0);
display: flex;
align-items: center;
justify-content: center;
margin-top: 15%;
font-weight: bold;
font-size: 1rem;
font-size: 1.5rem;
border-radius: 0.5rem;
}
@ -160,3 +160,21 @@ div[style*="background-color: black"] .custom-cell {
margin-bottom: 1rem;
}
}
/* Add these new styles for scrolling */
.live-data-text.scrolling {
white-space: nowrap;
overflow: hidden;
box-sizing: border-box;
animation: scroll 20s linear infinite; /* Adjust duration for speed */
width: 55%; /* Ensure it takes full width */
}
@keyframes scroll {
0% {
transform: translateX(100%);
}
100% {
transform: translateX(-100%);
}
}

View File

@ -44,9 +44,10 @@
</div>
</div>
<footer class="footer" [ngStyle]="{ 'background-color': isConnected ? '#d1ffd1' : '#ffcccc' }">
<div class="live-data-text">
Live Data: {{ message || 'Disconnected' }}
<!-- Updated footer: Conditional message, background, and scrolling class -->
<footer class="footer" >
<div class="live-data-text" [class.scrolling]="!!stopMessage">
{{ stopMessage ? ('Races Stopped: ' + stopMessage) : (message ? ('Live Data: ' + message) : 'Hello everyone!') }}
</div>
</footer>
</footer>
</div>

View File

@ -1,4 +1,3 @@
// shared-table.component.ts
import { Component, Input, OnInit } from '@angular/core';
import { CommonModule } from '@angular/common';
import { SelectionData } from '../selection.service/selection.service';
@ -19,18 +18,28 @@ export class SharedTableComponent implements OnInit {
message = '';
isConnected = false;
// Add this new property
stopMessage: string = '';
constructor(private websocketService: WebsocketService) {}
ngOnInit() {
this.websocketService.message$.subscribe((msg) => {
this.message = msg;
console.log('[SHARED TABLE] WebSocket message:', msg);
});
// this.websocketService.message$.subscribe((msg) => {
// this.message = msg;
// console.log('[SHARED TABLE] WebSocket message:', msg);
// });
this.websocketService.isConnected$.subscribe((status) => {
this.isConnected = status;
console.log('[SHARED TABLE] WebSocket connection status:', status);
// this.websocketService.isConnected$.subscribe((status) => {
// this.isConnected = status;
// console.log('[SHARED TABLE] WebSocket connection status:', status);
// });
// Add this: Listen for stop message updates
if (window.electronAPI) {
window.electronAPI.onUpdateStopMessage((message: string) => {
this.stopMessage = message;
console.log('[SHARED TABLE] Received stop message:', message);
});
}
}
}

View File

@ -1,10 +1,12 @@
// src/electron.d.ts
interface ElectronAPI {
openSecondScreen: () => void;
closeSecondScreen: () => void;
getBtid: () => Promise<string | null>;
syncSharedData: (data: SharedData) => void;
onUpdateSharedData: (callback: (data: SharedData) => void) => void;
// Add these new lines
syncStopMessage: (message: string) => void;
onUpdateStopMessage: (callback: (message: string) => void) => void;
}
interface SharedData {