Release 2.6.8
This commit is contained in:
@@ -2,6 +2,12 @@
|
|||||||
|
|
||||||
All notable changes to this project will be documented in this file.
|
All notable changes to this project will be documented in this file.
|
||||||
|
|
||||||
|
## 2.6.8 - 2026-08-08
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- The screen-group command buttons stay disabled until a real target group is selected, so the placeholder "Select Screen Group" state cannot send commands.
|
||||||
|
|
||||||
## 2.6.7 - 2026-08-08
|
## 2.6.7 - 2026-08-08
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "pulse-signage",
|
"name": "pulse-signage",
|
||||||
"version": "2.6.7",
|
"version": "2.6.8",
|
||||||
"private": false,
|
"private": false,
|
||||||
"description": "Pulse Signage application with MySQL and media storage",
|
"description": "Pulse Signage application with MySQL and media storage",
|
||||||
"repository": {
|
"repository": {
|
||||||
|
|||||||
@@ -114,6 +114,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
var selectedSlug = String(select.value || '').trim();
|
var selectedSlug = String(select.value || '').trim();
|
||||||
|
var hasSelectedGroup = Boolean(selectedSlug);
|
||||||
var actionTarget = '/clients/' + encodeURIComponent(selectedSlug || '__all__') + '/commands';
|
var actionTarget = '/clients/' + encodeURIComponent(selectedSlug || '__all__') + '/commands';
|
||||||
var selectedName = getSelectedScreenLabel();
|
var selectedName = getSelectedScreenLabel();
|
||||||
var selectedClients = getSelectedScreenClients(latestDashboardState);
|
var selectedClients = getSelectedScreenClients(latestDashboardState);
|
||||||
@@ -134,6 +135,10 @@
|
|||||||
var action = String(form.getAttribute('data-screen-command-action') || '').trim();
|
var action = String(form.getAttribute('data-screen-command-action') || '').trim();
|
||||||
var button = form.querySelector('button[type="submit"]');
|
var button = form.querySelector('button[type="submit"]');
|
||||||
|
|
||||||
|
if (button) {
|
||||||
|
button.disabled = !hasSelectedGroup;
|
||||||
|
}
|
||||||
|
|
||||||
if (action === 'pause' || action === 'blackout') {
|
if (action === 'pause' || action === 'blackout') {
|
||||||
updateToggleButton(button, form, latestDashboardState);
|
updateToggleButton(button, form, latestDashboardState);
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -42,17 +42,17 @@
|
|||||||
<div class="screen-command-actions">
|
<div class="screen-command-actions">
|
||||||
<form method="post" action="#" class="inline-form" data-confirm-message="Reload selected screen?" data-screen-command-form data-screen-command-action="reload" data-async-command>
|
<form method="post" action="#" class="inline-form" data-confirm-message="Reload selected screen?" data-screen-command-form data-screen-command-action="reload" data-async-command>
|
||||||
<input type="hidden" name="command" value="reload" />
|
<input type="hidden" name="command" value="reload" />
|
||||||
<button type="submit" class="btn btn-sm btn-danger"><i class="bi bi-arrow-repeat me-1" aria-hidden="true"></i>Reload screen</button>
|
<button type="submit" class="btn btn-sm btn-danger" disabled><i class="bi bi-arrow-repeat me-1" aria-hidden="true"></i>Reload screen</button>
|
||||||
</form>
|
</form>
|
||||||
<form method="post" action="#" class="inline-form" data-confirm-message="Pause selected screen?" data-screen-command-form data-screen-command-action="pause" data-async-command>
|
<form method="post" action="#" class="inline-form" data-confirm-message="Pause selected screen?" data-screen-command-form data-screen-command-action="pause" data-async-command>
|
||||||
<input type="hidden" name="command" value="pause" />
|
<input type="hidden" name="command" value="pause" />
|
||||||
<input type="hidden" name="paused" value="true" />
|
<input type="hidden" name="paused" value="true" />
|
||||||
<button type="submit" class="btn btn-sm btn-info"><i class="bi bi-pause-fill me-1" aria-hidden="true"></i>Pause screen</button>
|
<button type="submit" class="btn btn-sm btn-info" disabled><i class="bi bi-pause-fill me-1" aria-hidden="true"></i>Pause screen</button>
|
||||||
</form>
|
</form>
|
||||||
<form method="post" action="#" class="inline-form" data-confirm-message="Blackout selected screen?" data-screen-command-form data-screen-command-action="blackout" data-async-command>
|
<form method="post" action="#" class="inline-form" data-confirm-message="Blackout selected screen?" data-screen-command-form data-screen-command-action="blackout" data-async-command>
|
||||||
<input type="hidden" name="command" value="blackout" />
|
<input type="hidden" name="command" value="blackout" />
|
||||||
<input type="hidden" name="blackout" value="true" />
|
<input type="hidden" name="blackout" value="true" />
|
||||||
<button type="submit" class="btn btn-sm btn-secondary"><i class="bi bi-eye-slash me-1" aria-hidden="true"></i>Blackout screen</button>
|
<button type="submit" class="btn btn-sm btn-secondary" disabled><i class="bi bi-eye-slash me-1" aria-hidden="true"></i>Blackout screen</button>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -342,3 +342,181 @@ test('dashboard move client button opens the move modal for the selected row', (
|
|||||||
assert.equal(clientNameInput.value, 'Lobby Client');
|
assert.equal(clientNameInput.value, 'Lobby Client');
|
||||||
assert.equal(playerBaseUrlInput.value, 'http://player.local');
|
assert.equal(playerBaseUrlInput.value, 'http://player.local');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('dashboard screen group controls stay disabled until a group is selected', () => {
|
||||||
|
const script = fs.readFileSync(require.resolve('../src/web/public/js/dashboard/dashboard-page.js'), 'utf8');
|
||||||
|
const pauseInput = { value: 'true' };
|
||||||
|
const blackoutInput = { value: 'true' };
|
||||||
|
const reloadButton = {
|
||||||
|
disabled: false,
|
||||||
|
innerHTML: '',
|
||||||
|
setAttribute() {},
|
||||||
|
classList: { add() {}, remove() {} }
|
||||||
|
};
|
||||||
|
const pauseButton = {
|
||||||
|
disabled: false,
|
||||||
|
innerHTML: '',
|
||||||
|
setAttribute() {},
|
||||||
|
classList: { add() {}, remove() {} }
|
||||||
|
};
|
||||||
|
const blackoutButton = {
|
||||||
|
disabled: false,
|
||||||
|
innerHTML: '',
|
||||||
|
setAttribute() {},
|
||||||
|
classList: { add() {}, remove() {} }
|
||||||
|
};
|
||||||
|
const forms = [
|
||||||
|
{
|
||||||
|
getAttribute(name) {
|
||||||
|
if (name === 'data-screen-command-action') {
|
||||||
|
return 'reload';
|
||||||
|
}
|
||||||
|
return '';
|
||||||
|
},
|
||||||
|
setAttribute() {},
|
||||||
|
querySelector(selector) {
|
||||||
|
if (selector === 'button[type="submit"]') {
|
||||||
|
return reloadButton;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
getAttribute(name) {
|
||||||
|
if (name === 'data-screen-command-action') {
|
||||||
|
return 'pause';
|
||||||
|
}
|
||||||
|
return '';
|
||||||
|
},
|
||||||
|
setAttribute() {},
|
||||||
|
querySelector(selector) {
|
||||||
|
if (selector === 'button[type="submit"]') {
|
||||||
|
return pauseButton;
|
||||||
|
}
|
||||||
|
if (selector === 'input[name="paused"]') {
|
||||||
|
return pauseInput;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
getAttribute(name) {
|
||||||
|
if (name === 'data-screen-command-action') {
|
||||||
|
return 'blackout';
|
||||||
|
}
|
||||||
|
return '';
|
||||||
|
},
|
||||||
|
setAttribute() {},
|
||||||
|
querySelector(selector) {
|
||||||
|
if (selector === 'button[type="submit"]') {
|
||||||
|
return blackoutButton;
|
||||||
|
}
|
||||||
|
if (selector === 'input[name="blackout"]') {
|
||||||
|
return blackoutInput;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
];
|
||||||
|
const select = {
|
||||||
|
value: '',
|
||||||
|
options: [
|
||||||
|
{ textContent: 'Select Screen Group', getAttribute() { return ''; } },
|
||||||
|
{ textContent: 'Lobby', getAttribute(name) { return name === 'data-screen-name' ? 'Lobby' : ''; } },
|
||||||
|
{ textContent: 'All Screens', getAttribute(name) { return name === 'data-screen-name' ? 'All screens' : ''; } }
|
||||||
|
],
|
||||||
|
selectedIndex: 0,
|
||||||
|
dataset: {},
|
||||||
|
addEventListener(type, handler) {
|
||||||
|
if (type === 'change') {
|
||||||
|
this.changeHandler = handler;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const context = {
|
||||||
|
document: {
|
||||||
|
body: {
|
||||||
|
classList: {
|
||||||
|
toggle() {},
|
||||||
|
add() {},
|
||||||
|
remove() {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
getElementById(id) {
|
||||||
|
if (id === 'screen-command-select') {
|
||||||
|
return select;
|
||||||
|
}
|
||||||
|
if (id === 'dashboard-clients-table') {
|
||||||
|
return { getAttribute() { return 'false'; } };
|
||||||
|
}
|
||||||
|
if (id === 'dashboard-clients-table-body') {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
querySelector(selector) {
|
||||||
|
if (selector === '[data-screen-command-select]') {
|
||||||
|
return select;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
querySelectorAll(selector) {
|
||||||
|
if (selector === '[data-screen-command-form]') {
|
||||||
|
return forms;
|
||||||
|
}
|
||||||
|
return [];
|
||||||
|
},
|
||||||
|
addEventListener() {}
|
||||||
|
},
|
||||||
|
window: {
|
||||||
|
location: {
|
||||||
|
search: '',
|
||||||
|
protocol: 'http:',
|
||||||
|
host: 'example.test'
|
||||||
|
},
|
||||||
|
webUiHelpers: {
|
||||||
|
escapeHtml(value) { return String(value); },
|
||||||
|
formatDashboardDate(value) { return String(value); },
|
||||||
|
getClientRowKey(client) { return String(client && client.id || ''); },
|
||||||
|
getClientDisplayName(client) { return String(client && (client.client_name || client.name || client.clientId) || ''); },
|
||||||
|
setButtonVariant() {},
|
||||||
|
normalizeDisplayIp(value) { return String(value); }
|
||||||
|
},
|
||||||
|
WebSocket: null,
|
||||||
|
setTimeout() { return 1; },
|
||||||
|
clearTimeout() {},
|
||||||
|
alert() {},
|
||||||
|
prompt() {
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
webHandleDashboardState() {}
|
||||||
|
},
|
||||||
|
WebSocket: function MockWebSocket() {},
|
||||||
|
JSON: JSON,
|
||||||
|
Number: Number,
|
||||||
|
String: String,
|
||||||
|
Boolean: Boolean,
|
||||||
|
Array: Array,
|
||||||
|
Object: Object,
|
||||||
|
Math: Math,
|
||||||
|
Set: Set,
|
||||||
|
URLSearchParams: URLSearchParams,
|
||||||
|
FormData: function FormData() {}
|
||||||
|
};
|
||||||
|
context.window.document = context.document;
|
||||||
|
context.window.WebSocket = context.WebSocket;
|
||||||
|
|
||||||
|
vm.runInNewContext(script, context);
|
||||||
|
|
||||||
|
assert.equal(reloadButton.disabled, true);
|
||||||
|
assert.equal(pauseButton.disabled, true);
|
||||||
|
assert.equal(blackoutButton.disabled, true);
|
||||||
|
|
||||||
|
select.value = 'lobby';
|
||||||
|
select.selectedIndex = 1;
|
||||||
|
select.changeHandler();
|
||||||
|
|
||||||
|
assert.equal(reloadButton.disabled, false);
|
||||||
|
assert.equal(pauseButton.disabled, false);
|
||||||
|
assert.equal(blackoutButton.disabled, false);
|
||||||
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user