Skip to content

Commit 9d102d8

Browse files
Copilottakker99
andcommitted
Implement apply/cancel pattern for monitoring controls and fix disconnect behavior
Co-authored-by: takker99 <37929109+takker99@users.noreply.github.com>
1 parent 8da49ee commit 9d102d8

File tree

1 file changed

+152
-48
lines changed

1 file changed

+152
-48
lines changed

src/App.tsx

Lines changed: 152 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,13 @@ export function App() {
6868
return Math.max(500, Math.min(10000, timeout))
6969
})
7070

71+
// Pending values for monitoring controls (used during editing)
72+
const [pendingPollingInterval, setPendingPollingInterval] =
73+
useState<string>('')
74+
const [pendingRequestTimeout, setPendingRequestTimeout] = useState<string>('')
75+
const [showMonitoringApplyCancel, setShowMonitoringApplyCancel] =
76+
useState(false)
77+
7178
// Instances (initialized via useEffect)
7279
const [serialManager] = useState(() => new SerialManager())
7380
const [modbusClient] = useState(() => new ModbusClient())
@@ -191,6 +198,13 @@ export function App() {
191198

192199
const handleDisconnect = async () => {
193200
try {
201+
// Stop monitoring if currently active
202+
if (isMonitoring) {
203+
modbusClient.stopMonitoring()
204+
setIsMonitoring(false)
205+
addLog('Info', 'Stopped monitoring due to disconnection')
206+
}
207+
194208
await serialManager.disconnect()
195209
} catch (error) {
196210
addLog('Error', `Disconnection error: ${(error as Error).message}`)
@@ -283,56 +297,115 @@ export function App() {
283297
addLog('Info', `Protocol changed to ${newProtocol.toUpperCase()}`)
284298
}
285299

286-
const handlePollingIntervalChange = (value: number) => {
287-
// Clamp to valid range
288-
const clampedValue = Math.max(100, Math.min(60000, value))
289-
setPollingInterval(clampedValue)
290-
localStorage.setItem('modbus-polling-interval', clampedValue.toString())
291-
292-
if (clampedValue !== value) {
293-
addLog(
294-
'Warning',
295-
`Polling interval clamped to ${clampedValue}ms (valid range: 100-60000ms)`
296-
)
300+
const handlePendingPollingIntervalChange = (e: Event) => {
301+
const target = e.currentTarget as HTMLInputElement
302+
const value = target.value
303+
setPendingPollingInterval(value)
304+
305+
// Show apply/cancel buttons when value differs from current
306+
if (value !== pollingInterval.toString()) {
307+
setShowMonitoringApplyCancel(true)
308+
} else if (
309+
pendingRequestTimeout === requestTimeout.toString() ||
310+
pendingRequestTimeout === ''
311+
) {
312+
setShowMonitoringApplyCancel(false)
297313
}
314+
}
298315

299-
// Auto-restart monitoring if currently active
300-
if (isMonitoring) {
301-
const config: ModbusReadConfig = { ...readConfig, slaveId }
302-
modbusClient.stopMonitoring()
303-
modbusClient.startMonitoring(config, clampedValue, requestTimeout)
304-
addLog(
305-
'Info',
306-
`Monitoring updated with new polling interval: ${clampedValue}ms`
307-
)
316+
const handlePendingRequestTimeoutChange = (e: Event) => {
317+
const target = e.currentTarget as HTMLInputElement
318+
const value = target.value
319+
setPendingRequestTimeout(value)
320+
321+
// Show apply/cancel buttons when value differs from current
322+
if (value !== requestTimeout.toString()) {
323+
setShowMonitoringApplyCancel(true)
324+
} else if (
325+
pendingPollingInterval === pollingInterval.toString() ||
326+
pendingPollingInterval === ''
327+
) {
328+
setShowMonitoringApplyCancel(false)
308329
}
309330
}
310331

311-
const handleRequestTimeoutChange = (value: number) => {
312-
// Clamp to valid range
313-
const clampedValue = Math.max(500, Math.min(10000, value))
314-
setRequestTimeout(clampedValue)
315-
localStorage.setItem('modbus-request-timeout', clampedValue.toString())
332+
const validateMonitoringValues = () => {
333+
const pollingValue =
334+
pendingPollingInterval === ''
335+
? pollingInterval
336+
: Number.parseInt(pendingPollingInterval, 10)
337+
const timeoutValue =
338+
pendingRequestTimeout === ''
339+
? requestTimeout
340+
: Number.parseInt(pendingRequestTimeout, 10)
341+
342+
return (
343+
!Number.isNaN(pollingValue) &&
344+
!Number.isNaN(timeoutValue) &&
345+
pollingValue >= 100 &&
346+
pollingValue <= 60000 &&
347+
timeoutValue >= 500 &&
348+
timeoutValue <= 10000
349+
)
350+
}
316351

317-
if (clampedValue !== value) {
352+
const handleApplyMonitoringChanges = () => {
353+
const newPollingInterval =
354+
pendingPollingInterval === ''
355+
? pollingInterval
356+
: Number.parseInt(pendingPollingInterval, 10)
357+
const newRequestTimeout =
358+
pendingRequestTimeout === ''
359+
? requestTimeout
360+
: Number.parseInt(pendingRequestTimeout, 10)
361+
362+
// Clamp values to valid ranges
363+
const clampedPolling = Math.max(100, Math.min(60000, newPollingInterval))
364+
const clampedTimeout = Math.max(500, Math.min(10000, newRequestTimeout))
365+
366+
// Update state and localStorage
367+
setPollingInterval(clampedPolling)
368+
setRequestTimeout(clampedTimeout)
369+
localStorage.setItem('modbus-polling-interval', clampedPolling.toString())
370+
localStorage.setItem('modbus-request-timeout', clampedTimeout.toString())
371+
372+
// Clear pending values and hide buttons
373+
setPendingPollingInterval('')
374+
setPendingRequestTimeout('')
375+
setShowMonitoringApplyCancel(false)
376+
377+
// Show warnings if values were clamped
378+
if (clampedPolling !== newPollingInterval) {
379+
addLog(
380+
'Warning',
381+
`Polling interval clamped to ${clampedPolling}ms (valid range: 100-60000ms)`
382+
)
383+
}
384+
if (clampedTimeout !== newRequestTimeout) {
318385
addLog(
319386
'Warning',
320-
`Request timeout clamped to ${clampedValue}ms (valid range: 500-10000ms)`
387+
`Request timeout clamped to ${clampedTimeout}ms (valid range: 500-10000ms)`
321388
)
322389
}
323390

324-
// Auto-restart monitoring if currently active
391+
// Restart monitoring if currently active
325392
if (isMonitoring) {
326393
const config: ModbusReadConfig = { ...readConfig, slaveId }
327394
modbusClient.stopMonitoring()
328-
modbusClient.startMonitoring(config, pollingInterval, clampedValue)
395+
modbusClient.startMonitoring(config, clampedPolling, clampedTimeout)
329396
addLog(
330397
'Info',
331-
`Monitoring updated with new request timeout: ${clampedValue}ms`
398+
`Monitoring updated with new settings (interval: ${clampedPolling}ms, timeout: ${clampedTimeout}ms)`
332399
)
333400
}
334401
}
335402

403+
const handleCancelMonitoringChanges = () => {
404+
setPendingPollingInterval('')
405+
setPendingRequestTimeout('')
406+
setShowMonitoringApplyCancel(false)
407+
}
408+
336409
const clearLogs = () => {
337410
setLogs([])
338411
setData([])
@@ -741,11 +814,13 @@ export function App() {
741814
id="pollingInterval"
742815
max="60000"
743816
min="100"
744-
onChange={(e) =>
745-
handlePollingIntervalChange(Number(e.currentTarget.value))
746-
}
817+
onChange={handlePendingPollingIntervalChange}
747818
type="number"
748-
value={pollingInterval}
819+
value={
820+
pendingPollingInterval === ''
821+
? pollingInterval
822+
: pendingPollingInterval
823+
}
749824
/>
750825
</div>
751826

@@ -756,24 +831,53 @@ export function App() {
756831
id="requestTimeout"
757832
max="10000"
758833
min="500"
759-
onChange={(e) =>
760-
handleRequestTimeoutChange(Number(e.currentTarget.value))
761-
}
834+
onChange={handlePendingRequestTimeoutChange}
762835
type="number"
763-
value={requestTimeout}
836+
value={
837+
pendingRequestTimeout === ''
838+
? requestTimeout
839+
: pendingRequestTimeout
840+
}
764841
/>
765842
</div>
766843

767-
<div className="form-group">
768-
<button
769-
className="btn btn-secondary"
770-
disabled={!isConnected}
771-
onClick={handleMonitorToggle}
772-
type="button"
773-
>
774-
{isMonitoring ? 'Stop Monitor' : 'Start Monitor'}
775-
</button>
776-
</div>
844+
{!showMonitoringApplyCancel && (
845+
<div className="form-group">
846+
<button
847+
className="btn btn-secondary"
848+
disabled={!isConnected}
849+
onClick={handleMonitorToggle}
850+
type="button"
851+
>
852+
{isMonitoring ? 'Stop Monitor' : 'Start Monitor'}
853+
</button>
854+
</div>
855+
)}
856+
857+
{showMonitoringApplyCancel && (
858+
<>
859+
<div className="form-group">
860+
<button
861+
className="btn btn-success"
862+
disabled={!isConnected || !validateMonitoringValues()}
863+
onClick={handleApplyMonitoringChanges}
864+
type="button"
865+
>
866+
Apply
867+
</button>
868+
</div>
869+
<div className="form-group">
870+
<button
871+
className="btn btn-secondary"
872+
disabled={!isConnected}
873+
onClick={handleCancelMonitoringChanges}
874+
type="button"
875+
>
876+
Cancel
877+
</button>
878+
</div>
879+
</>
880+
)}
777881
</div>
778882
</div>
779883
</section>

0 commit comments

Comments
 (0)