@@ -94,14 +94,104 @@ function PrintSequenceOptions()
9494
9595</head>
9696
97- <body onunload='DisableTestMode();DisableDMXTestMode ();'>
97+ <body onunload='HandlePageUnload ();'>
9898
9999 <script type="text/javascript">
100100 if (!window.console) console = { log: function () { } };
101101
102102 var modelInfos = [];
103103 var lastEnabledState = 0;
104104
105+ // fppd runs one test at a time, but the Channel Testing and Channel Fader
106+ // tabs each have their own Enable Test Mode checkbox. Remember which tab
107+ // started the running test so switching tabs shows the truth rather than an
108+ // unticked box sitting next to output that is still running. sessionStorage
109+ // carries the owner across navigating away from and back to this page.
110+ var TEST_OWNER_KEY = 'fppTestOwner';
111+ var testOwner = null;
112+ var pageUnloading = false;
113+
114+ function StoreTestOwner(owner) {
115+ // The Test Stop we fire on unload usually doesn't make it out before the
116+ // page goes away, so keep the stored owner rather than clearing it - the
117+ // test is most likely still running when we come back.
118+ if (pageUnloading && !owner) {
119+ return;
120+ }
121+ testOwner = owner;
122+ try {
123+ if (owner) {
124+ sessionStorage.setItem(TEST_OWNER_KEY, owner);
125+ } else {
126+ sessionStorage.removeItem(TEST_OWNER_KEY);
127+ }
128+ } catch (e) {
129+ // sessionStorage blocked; the in-memory owner still covers this page view
130+ }
131+ }
132+
133+ function LoadTestOwner() {
134+ try {
135+ return sessionStorage.getItem(TEST_OWNER_KEY);
136+ } catch (e) {
137+ return null;
138+ }
139+ }
140+
141+ // Each tab warns when the other tab owns the running test, since enabling
142+ // test mode here replaces it.
143+ function UpdateTakeoverNotices() {
144+ $('#channelTestTakeover').toggleClass('d-none', testOwner != 'dmx');
145+ $('#dmxTestTakeover').toggleClass('d-none', testOwner != 'channels');
146+ }
147+
148+ // Point both tabs' checkboxes at the one test fppd is actually running.
149+ // 'enabled' is fppd's real state, from api/testmode.
150+ function ApplyTestOwnership(enabled) {
151+ var owner = null;
152+ if (enabled) {
153+ // A test we never started (another browser, or a page load with no
154+ // stored owner) is attributed to the Channel Testing tab, whose
155+ // controls GetTestMode() fills in from the running test.
156+ owner = testOwner || LoadTestOwner() || 'channels';
157+ }
158+ StoreTestOwner(owner);
159+
160+ lastEnabledState = (owner == 'channels') ? 1 : 0;
161+ dmxLastEnabled = (owner == 'dmx') ? 1 : 0;
162+ $('#testModeEnabled').prop('checked', owner == 'channels');
163+ $('#dmxTestEnabled').prop('checked', owner == 'dmx');
164+ if (owner != 'dmx') {
165+ DMXSineStop();
166+ }
167+ UpdateTakeoverNotices();
168+ }
169+
170+ // Re-read fppd's test state when switching between the top-level tabs.
171+ function RefreshTestState() {
172+ $.ajax({
173+ url: "api/testmode",
174+ dataType: 'json',
175+ success: function (data) {
176+ ApplyTestOwnership(data.enabled ? true : false);
177+ }
178+ });
179+ }
180+
181+ function StopRunningTest() {
182+ var data = {
183+ "command": "Test Stop",
184+ "multisyncCommand": $('#multisyncEnabled').is(':checked') || $('#dmxMultisyncEnabled').is(':checked'),
185+ "multisyncHosts": "",
186+ "args": []
187+ };
188+ $.post("api/command", JSON.stringify(data)).done(function () {
189+ ApplyTestOwnership(false);
190+ }).fail(function () {
191+ DialogError("Failed to stop Test Mode", "Stop failed");
192+ });
193+ }
194+
105195 function StringsChanged() {
106196 var id = parseInt($('#modelName').val());
107197
@@ -256,8 +346,7 @@ function GetTestMode() {
256346 dataType: 'json',
257347 success: function (data) {
258348 if (data.enabled) {
259- $('#testModeEnabled').prop('checked', true);
260- lastEnabledState = 1;
349+ ApplyTestOwnership(true);
261350
262351 if (data.hasOwnProperty('cycleMS')) {
263352 $("#testModeCycleMSText").html(data.cycleMS);
@@ -299,11 +388,11 @@ function GetTestMode() {
299388 }
300389 }
301390 else {
302- $('#testModeEnabled').prop('checked', false);
391+ ApplyTestOwnership( false);
303392 }
304393 },
305394 failure: function (data) {
306- $('#testModeEnabled').prop('checked', false);
395+ ApplyTestOwnership( false);
307396 }
308397 });
309398 }
@@ -551,6 +640,24 @@ function SetTestMode() {
551640 }
552641
553642 lastEnabledState = enabled;
643+
644+ if (enabled) {
645+ // This tab now owns the one fppd test, replacing anything the Channel
646+ // Fader tab had running.
647+ StoreTestOwner('channels');
648+ $('#dmxTestEnabled').prop('checked', false);
649+ dmxLastEnabled = 0;
650+ DMXSineStop();
651+ } else if (testOwner == 'channels') {
652+ StoreTestOwner(null);
653+ }
654+ UpdateTakeoverNotices();
655+ }
656+
657+ function HandlePageUnload() {
658+ pageUnloading = true;
659+ DisableTestMode();
660+ DisableDMXTestMode();
554661 }
555662
556663 function DisableTestMode() {
@@ -1082,6 +1189,13 @@ function SetDMXTestMode() {
10821189 });
10831190
10841191 dmxLastEnabled = enabled;
1192+
1193+ if (enabled && count > 0) {
1194+ StoreTestOwner('dmx');
1195+ } else if (testOwner == 'dmx') {
1196+ StoreTestOwner(null);
1197+ }
1198+ UpdateTakeoverNotices();
10851199 }
10861200
10871201 function DisableDMXTestMode() {
@@ -1268,6 +1382,12 @@ function DisableDMXTestMode() {
12681382 })
12691383 .css('background-color', '#ff00ff');
12701384
1385+ // Switching tabs doesn't stop the running test, so re-read its state to
1386+ // keep the checkbox on the newly shown tab honest.
1387+ $('#tab-channels-tab, #tab-dmx-tab, #tab-sequence-tab').on('shown.bs.tab', function () {
1388+ RefreshTestState();
1389+ });
1390+
12711391 UpdateWhiteSliderState();
12721392 GetTestMode();
12731393 RebuildDMXSliders();
@@ -1447,6 +1567,14 @@ function DisableDMXTestMode() {
14471567 aria-labelledby="interface-settings-tab">
14481568
14491569
1570+ <div id='channelTestTakeover' class="alert alert-warning d-none d-flex flex-wrap align-items-center gap-2"
1571+ role="alert">
1572+ <i class="fas fa-exclamation-triangle"></i>
1573+ <span>A test started on the <b>Channel Fader</b> tab is still running. Enabling
1574+ test mode here will take it over.</span>
1575+ <button type="button" class="btn btn-sm btn-outline-secondary ms-auto"
1576+ onclick="StopRunningTest();">Stop Test</button>
1577+ </div>
14501578 <!-- Page-Wide Settings -->
14511579 <div class="backdrop-dark mb-3">
14521580 <div class="row">
@@ -2000,6 +2128,14 @@ class="custom-control-label"><b>Fill</b></label>
20002128 </div>
20012129 </div>
20022130 <div id='tab-dmx' class="tab-pane fade" role="tabpanel" aria-labelledby="tab-dmx-tab">
2131+ <div id='dmxTestTakeover' class="alert alert-warning d-none d-flex flex-wrap align-items-center gap-2"
2132+ role="alert">
2133+ <i class="fas fa-exclamation-triangle"></i>
2134+ <span>A test started on the <b>Channel Testing</b> tab is still running. Enabling
2135+ test mode here will take it over.</span>
2136+ <button type="button" class="btn btn-sm btn-outline-secondary ms-auto"
2137+ onclick="StopRunningTest();">Stop Test</button>
2138+ </div>
20032139 <div class="row">
20042140 <div class="col-md-3">
20052141 <div class="backdrop-dark">
0 commit comments