Add onboarding weather and template gradients
This commit is contained in:
@@ -77,6 +77,28 @@
|
||||
});
|
||||
}
|
||||
|
||||
function updateDataSourceToggle(form, response) {
|
||||
var toggleButton = document.querySelector('button[form="' + form.id + '"][data-async-data-source-toggle]');
|
||||
if (!toggleButton) {
|
||||
return;
|
||||
}
|
||||
|
||||
var willEnable = toggleButton.getAttribute('data-enabled') !== 'true';
|
||||
toggleButton.setAttribute('data-enabled', willEnable ? 'true' : 'false');
|
||||
toggleButton.className = toggleButton.className.replace(/btn-(danger|success)/g, willEnable ? 'btn-danger' : 'btn-success');
|
||||
toggleButton.innerHTML = '<i class="bi ' + (willEnable ? 'bi-pause-fill' : 'bi-play-fill') + ' me-1" aria-hidden="true"></i>' + (willEnable ? 'Disable' : 'Enable');
|
||||
|
||||
var message = '';
|
||||
try {
|
||||
message = new URL(response.url, window.location.href).searchParams.get('message') || '';
|
||||
} catch (_error) {
|
||||
message = '';
|
||||
}
|
||||
if (typeof showToast === 'function') {
|
||||
showToast(message || (willEnable ? 'Data source enabled.' : 'Data source disabled.'), 'success');
|
||||
}
|
||||
}
|
||||
|
||||
function initDeleteButtons() {
|
||||
document.addEventListener('click', function (event) {
|
||||
var deleteButton = event.target.closest('[data-delete-action-url]');
|
||||
@@ -610,7 +632,7 @@
|
||||
} catch (_error) {
|
||||
actionPath = String(form.action || '');
|
||||
}
|
||||
var shouldReloadAfterSuccess = /^\/announcements\/\d+\/(?:play|stop)$/.test(actionPath) || /^\/data-sources\/(?:api-sources|rss-feeds|weather)\/\d+$/.test(actionPath);
|
||||
var shouldReloadAfterSuccess = /^\/announcements\/\d+\/(?:play|stop)$/.test(actionPath);
|
||||
|
||||
event.preventDefault();
|
||||
|
||||
@@ -641,6 +663,11 @@
|
||||
return;
|
||||
}
|
||||
|
||||
if (/^\/data-sources\/(?:api-sources|rss-feeds|weather)\/\d+$/.test(actionPath)) {
|
||||
updateDataSourceToggle(form, response);
|
||||
return;
|
||||
}
|
||||
|
||||
if (shouldReloadAfterSuccess && response && response.ok) {
|
||||
window.location.reload();
|
||||
return;
|
||||
|
||||
@@ -7,7 +7,9 @@
|
||||
var setButtonVariant = webUiHelpers.setButtonVariant;
|
||||
var normalizeDisplayIp = webUiHelpers.normalizeDisplayIp;
|
||||
var LIST_PAGE_SIZE = 25;
|
||||
var CLIENT_ROW_REMOVAL_DELAY_MS = 1000;
|
||||
var latestDashboardState = null;
|
||||
var pendingClientRowRemovals = {};
|
||||
|
||||
function getClientSearchInput() {
|
||||
var table = document.getElementById('dashboard-clients-table');
|
||||
@@ -276,6 +278,21 @@
|
||||
return compareClientSortValues(leftValue, rightValue);
|
||||
}
|
||||
|
||||
function compareClientNames(leftValue, rightValue) {
|
||||
var leftName = String(leftValue || '').trim();
|
||||
var rightName = String(rightValue || '').trim();
|
||||
if (!leftName && !rightName) {
|
||||
return 0;
|
||||
}
|
||||
if (!leftName) {
|
||||
return 1;
|
||||
}
|
||||
if (!rightName) {
|
||||
return -1;
|
||||
}
|
||||
return leftName.localeCompare(rightName, undefined, { sensitivity: 'base', numeric: true });
|
||||
}
|
||||
|
||||
var sortKeys = accessors[normalizedSortKey]
|
||||
? [normalizedSortKey]
|
||||
: ['client'];
|
||||
@@ -283,7 +300,9 @@
|
||||
return (Array.isArray(clients) ? clients.slice() : []).sort(function (leftClient, rightClient) {
|
||||
for (var index = 0; index < sortKeys.length; index += 1) {
|
||||
var sortKeyName = sortKeys[index];
|
||||
var comparison = compareValues(accessors[sortKeyName](leftClient), accessors[sortKeyName](rightClient));
|
||||
var comparison = sortKeyName === 'client'
|
||||
? compareClientNames(accessors[sortKeyName](leftClient), accessors[sortKeyName](rightClient))
|
||||
: compareValues(accessors[sortKeyName](leftClient), accessors[sortKeyName](rightClient));
|
||||
|
||||
if (comparison !== 0) {
|
||||
return index === 0 && normalizedDirection === 'desc' ? comparison * -1 : comparison;
|
||||
@@ -336,7 +355,7 @@
|
||||
form: document.getElementById('client-move-screen-form'),
|
||||
targetSelect: document.getElementById('client-move-screen-target'),
|
||||
connectionInput: document.querySelector('[data-client-move-connection-id]'),
|
||||
deviceInput: document.querySelector('[data-client-move-device-id]'),
|
||||
clientIdInput: document.querySelector('[data-client-move-client-id]'),
|
||||
clientNameInput: document.querySelector('[data-client-move-client-name]'),
|
||||
playerBaseUrlInput: document.querySelector('[data-client-move-player-base-url]')
|
||||
};
|
||||
@@ -350,7 +369,7 @@
|
||||
|
||||
var currentScreenSlug = String(row.getAttribute('data-client-screen-slug') || '').trim();
|
||||
var connectionId = String(row.getAttribute('data-client-id') || '').trim();
|
||||
var deviceId = String(row.getAttribute('data-client-device-id') || '').trim();
|
||||
var clientId = String(row.getAttribute('data-client-client-id') || '').trim();
|
||||
var playerBaseUrl = String(row.getAttribute('data-client-player-base-url') || '').trim();
|
||||
var clientNameCell = row.querySelector('td[data-label="Client"] > div');
|
||||
var clientName = String(clientNameCell && clientNameCell.textContent || '').trim();
|
||||
@@ -367,8 +386,8 @@
|
||||
if (elements.connectionInput) {
|
||||
elements.connectionInput.value = connectionId;
|
||||
}
|
||||
if (elements.deviceInput) {
|
||||
elements.deviceInput.value = deviceId;
|
||||
if (elements.clientIdInput) {
|
||||
elements.clientIdInput.value = clientId;
|
||||
}
|
||||
if (elements.clientNameInput) {
|
||||
elements.clientNameInput.value = clientName;
|
||||
@@ -687,11 +706,6 @@
|
||||
&& typeof tbody.removeChild === 'function'
|
||||
&& typeof document.createElement === 'function';
|
||||
|
||||
if (!visibleClients.length) {
|
||||
tbody.innerHTML = '<tr><td colspan="' + (hasActionsColumn ? '7' : '6') + '" class="empty">No connected clients yet.</td></tr>';
|
||||
return;
|
||||
}
|
||||
|
||||
if (!canPatchRows) {
|
||||
tbody.innerHTML = visibleClients.map(function (client) {
|
||||
return renderClientRow(client, hasActionsColumn);
|
||||
@@ -704,8 +718,32 @@
|
||||
existingRows[String(row.getAttribute('data-client-key') || '').trim()] = row;
|
||||
});
|
||||
|
||||
var visibleRowKeys = {};
|
||||
visibleClients.forEach(function (client) {
|
||||
visibleRowKeys[String(getClientRowKey(client) || '').trim()] = true;
|
||||
});
|
||||
|
||||
function getClientIdentity(client) {
|
||||
return [
|
||||
String(client && (client.client_name || client.name || '') || '').trim().toLowerCase(),
|
||||
String(client && (client.player_url || client.playerPublicBaseUrl || '') || '').trim().replace(/\/$/, '').toLowerCase()
|
||||
].join('|');
|
||||
}
|
||||
|
||||
var visibleClientIdentities = {};
|
||||
visibleClients.forEach(function (client) {
|
||||
var identity = getClientIdentity(client);
|
||||
if (identity !== '|') {
|
||||
visibleClientIdentities[identity] = true;
|
||||
}
|
||||
});
|
||||
|
||||
var nextRows = visibleClients.map(function (client) {
|
||||
var rowKey = String(getClientRowKey(client) || '').trim();
|
||||
if (pendingClientRowRemovals[rowKey]) {
|
||||
clearTimeout(pendingClientRowRemovals[rowKey]);
|
||||
delete pendingClientRowRemovals[rowKey];
|
||||
}
|
||||
var row = existingRows[rowKey] || null;
|
||||
|
||||
if (!row) {
|
||||
@@ -719,6 +757,44 @@
|
||||
return Boolean(row);
|
||||
});
|
||||
|
||||
Object.keys(existingRows).forEach(function (rowKey) {
|
||||
if (visibleRowKeys[rowKey]) {
|
||||
return;
|
||||
}
|
||||
|
||||
var existingRow = existingRows[rowKey];
|
||||
var existingNameCell = existingRow.querySelector('td[data-label="Client"] > div');
|
||||
var existingIdentity = [
|
||||
String(existingNameCell && existingNameCell.textContent || '').trim().toLowerCase(),
|
||||
String(existingRow.getAttribute('data-client-player-base-url') || '').trim().replace(/\/$/, '').toLowerCase()
|
||||
].join('|');
|
||||
if (existingIdentity !== '|' && visibleClientIdentities[existingIdentity]) {
|
||||
if (pendingClientRowRemovals[rowKey]) {
|
||||
clearTimeout(pendingClientRowRemovals[rowKey]);
|
||||
delete pendingClientRowRemovals[rowKey];
|
||||
}
|
||||
if (existingRow.parentNode === tbody) {
|
||||
tbody.removeChild(existingRow);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (pendingClientRowRemovals[rowKey]) {
|
||||
return;
|
||||
}
|
||||
|
||||
pendingClientRowRemovals[rowKey] = setTimeout(function () {
|
||||
delete pendingClientRowRemovals[rowKey];
|
||||
var row = existingRows[rowKey];
|
||||
if (row && row.parentNode === tbody && !visibleRowKeys[rowKey]) {
|
||||
tbody.removeChild(row);
|
||||
}
|
||||
if (!tbody.querySelector('tr[data-client-key]')) {
|
||||
tbody.innerHTML = '<tr><td colspan="' + (hasActionsColumn ? '7' : '6') + '" class="empty">No connected clients yet.</td></tr>';
|
||||
}
|
||||
}, CLIENT_ROW_REMOVAL_DELAY_MS);
|
||||
});
|
||||
|
||||
nextRows.forEach(function (row, index) {
|
||||
var referenceNode = tbody.children[index] || null;
|
||||
if (referenceNode !== row) {
|
||||
@@ -726,8 +802,8 @@
|
||||
}
|
||||
});
|
||||
|
||||
while (tbody.children.length > nextRows.length) {
|
||||
tbody.removeChild(tbody.lastElementChild);
|
||||
if (!visibleClients.length && !tbody.querySelector('tr[data-client-key]')) {
|
||||
tbody.innerHTML = '<tr><td colspan="' + (hasActionsColumn ? '7' : '6') + '" class="empty">No connected clients yet.</td></tr>';
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,431 @@
|
||||
(function () {
|
||||
var form = document.getElementById('onboarding-pair-form');
|
||||
var pairingCard = document.getElementById('onboarding-pair-card');
|
||||
var pairingProgress = document.getElementById('onboarding-pair-progress');
|
||||
var message = document.getElementById('onboarding-pair-message');
|
||||
var codeInputsContainer = document.getElementById('onboarding-pair-code-inputs');
|
||||
var codeField = document.getElementById('onboarding-pair-code');
|
||||
var clientIdField = document.getElementById('onboarding-pair-client-id');
|
||||
var anotherButton = document.getElementById('onboarding-pair-another');
|
||||
var anotherButtonLabel = document.getElementById('onboarding-pair-another-label');
|
||||
var manualButton = document.getElementById('onboarding-pair-manual');
|
||||
var connectButton = document.getElementById('onboarding-pair-connect');
|
||||
var clientListButton = document.getElementById('onboarding-pair-client-list');
|
||||
var scanner = document.getElementById('onboarding-scanner');
|
||||
var scannerVideo = document.getElementById('onboarding-scanner-video');
|
||||
var scannerCapture = document.getElementById('onboarding-scanner-capture');
|
||||
var scannerClose = document.getElementById('onboarding-scanner-close');
|
||||
var scannerMessage = document.getElementById('onboarding-scanner-message');
|
||||
var scannerDebugOutput = document.getElementById('onboarding-scanner-debug');
|
||||
var scannerDebug = new URLSearchParams(window.location.search).get('scanner-debug') === '1';
|
||||
if (!form || !message) {
|
||||
return;
|
||||
}
|
||||
function getClientId() {
|
||||
var storageKey = 'pulse-signage-player-client-id';
|
||||
var clientId = clientIdField ? String(clientIdField.value || '').trim() : '';
|
||||
if (!clientId) {
|
||||
try { clientId = String(window.sessionStorage.getItem(storageKey) || '').trim(); } catch (_error) {}
|
||||
}
|
||||
if (!clientId) {
|
||||
clientId = window.crypto && typeof window.crypto.randomUUID === 'function'
|
||||
? window.crypto.randomUUID()
|
||||
: 'client-' + Date.now().toString(36) + '-' + Math.random().toString(36).slice(2, 12);
|
||||
try { window.sessionStorage.setItem(storageKey, clientId); } catch (_error) {}
|
||||
}
|
||||
if (clientIdField) { clientIdField.value = clientId; }
|
||||
return clientId;
|
||||
}
|
||||
getClientId();
|
||||
function traceScanner(value) {
|
||||
if (!scannerDebug || !scannerDebugOutput) { return; }
|
||||
scannerDebugOutput.classList.remove('d-none');
|
||||
var lines = (scannerDebugOutput.textContent ? scannerDebugOutput.textContent.split('\n') : []).filter(Boolean);
|
||||
lines.push(new Date().toISOString().slice(11, 19) + ' ' + value);
|
||||
scannerDebugOutput.textContent = lines.slice(-8).join('\n');
|
||||
}
|
||||
|
||||
var codeInputs = codeInputsContainer ? Array.prototype.slice.call(codeInputsContainer.querySelectorAll('[data-pairing-code-input]')) : [];
|
||||
function normalizeCode(value) {
|
||||
return String(value || '').replace(/[^a-z0-9]/gi, '').toUpperCase().slice(0, codeInputs.length);
|
||||
}
|
||||
function syncCodeField() {
|
||||
if (codeField) {
|
||||
codeField.value = codeInputs.map(function (input) { return input.value; }).join('').toUpperCase();
|
||||
}
|
||||
}
|
||||
function setCode(value, startIndex) {
|
||||
var normalized = normalizeCode(value);
|
||||
var index = startIndex || 0;
|
||||
normalized.split('').forEach(function (character) {
|
||||
if (index < codeInputs.length) {
|
||||
codeInputs[index].value = character;
|
||||
index += 1;
|
||||
}
|
||||
});
|
||||
syncCodeField();
|
||||
if (index < codeInputs.length) {
|
||||
codeInputs[index].focus();
|
||||
} else if (codeInputs.length) {
|
||||
codeInputs[codeInputs.length - 1].focus();
|
||||
}
|
||||
}
|
||||
var scannerStream = null;
|
||||
var scannerFrame = null;
|
||||
var scannerLastDecodeAt = 0;
|
||||
var scannerCanvas = document.createElement('canvas');
|
||||
var scannerWorkCanvas = document.createElement('canvas');
|
||||
var scannerDecodeCanvas = document.createElement('canvas');
|
||||
|
||||
function decodeQrCanvas(canvas) {
|
||||
if (typeof window.jsQR !== 'function') { return null; }
|
||||
var workCanvas = canvas;
|
||||
var maximumDimension = 1024;
|
||||
if (Math.max(canvas.width, canvas.height) > maximumDimension) {
|
||||
var scale = maximumDimension / Math.max(canvas.width, canvas.height);
|
||||
scannerWorkCanvas.width = Math.round(canvas.width * scale);
|
||||
scannerWorkCanvas.height = Math.round(canvas.height * scale);
|
||||
scannerWorkCanvas.getContext('2d').drawImage(canvas, 0, 0, scannerWorkCanvas.width, scannerWorkCanvas.height);
|
||||
workCanvas = scannerWorkCanvas;
|
||||
}
|
||||
var context = workCanvas.getContext('2d', { willReadFrequently: true });
|
||||
var imageData = context.getImageData(0, 0, workCanvas.width, workCanvas.height);
|
||||
var result = window.jsQR(imageData.data, workCanvas.width, workCanvas.height, { inversionAttempts: 'attemptBoth' });
|
||||
if (result) { return result; }
|
||||
|
||||
scannerDecodeCanvas.width = workCanvas.width;
|
||||
scannerDecodeCanvas.height = workCanvas.height;
|
||||
var decodeContext = scannerDecodeCanvas.getContext('2d', { willReadFrequently: true });
|
||||
var source = new Uint8ClampedArray(imageData.data);
|
||||
var cleaned = new Uint8ClampedArray(source.length);
|
||||
for (var cleanY = 0; cleanY < workCanvas.height; cleanY += 1) {
|
||||
for (var cleanX = 0; cleanX < workCanvas.width; cleanX += 1) {
|
||||
var cleanSamples = [];
|
||||
for (var cleanOffsetY = -1; cleanOffsetY <= 1; cleanOffsetY += 1) {
|
||||
for (var cleanOffsetX = -1; cleanOffsetX <= 1; cleanOffsetX += 1) {
|
||||
var cleanSampleX = Math.max(0, Math.min(workCanvas.width - 1, cleanX + cleanOffsetX));
|
||||
var cleanSampleY = Math.max(0, Math.min(workCanvas.height - 1, cleanY + cleanOffsetY));
|
||||
cleanSamples.push(source[(cleanSampleY * workCanvas.width + cleanSampleX) * 4]);
|
||||
}
|
||||
}
|
||||
cleanSamples.sort(function (left, right) { return left - right; });
|
||||
var cleanIndex = (cleanY * workCanvas.width + cleanX) * 4;
|
||||
cleaned[cleanIndex] = cleaned[cleanIndex + 1] = cleaned[cleanIndex + 2] = cleanSamples[4];
|
||||
cleaned[cleanIndex + 3] = 255;
|
||||
}
|
||||
}
|
||||
source = cleaned;
|
||||
var expanded = decodeContext.createImageData(workCanvas.width, workCanvas.height);
|
||||
var radius = Math.max(3, Math.round(workCanvas.width / 205));
|
||||
for (var y = 0; y < workCanvas.height; y += 1) {
|
||||
for (var x = 0; x < workCanvas.width; x += 1) {
|
||||
var samples = [];
|
||||
for (var offsetY = -radius; offsetY <= radius; offsetY += 2) {
|
||||
for (var offsetX = -radius; offsetX <= radius; offsetX += 2) {
|
||||
var sampleX = Math.max(0, Math.min(workCanvas.width - 1, x + offsetX));
|
||||
var sampleY = Math.max(0, Math.min(workCanvas.height - 1, y + offsetY));
|
||||
samples.push(source[(sampleY * workCanvas.width + sampleX) * 4]);
|
||||
}
|
||||
}
|
||||
var maximum = Math.max.apply(null, samples);
|
||||
var index = (y * workCanvas.width + x) * 4;
|
||||
expanded.data[index] = maximum;
|
||||
expanded.data[index + 1] = maximum;
|
||||
expanded.data[index + 2] = maximum;
|
||||
expanded.data[index + 3] = 255;
|
||||
}
|
||||
}
|
||||
decodeContext.putImageData(expanded, 0, 0);
|
||||
return window.jsQR(expanded.data, workCanvas.width, workCanvas.height, { inversionAttempts: 'attemptBoth' });
|
||||
}
|
||||
function closeScanner() {
|
||||
if (scannerFrame) { window.cancelAnimationFrame(scannerFrame); scannerFrame = null; }
|
||||
if (scannerStream) {
|
||||
scannerStream.getTracks().forEach(function (track) { track.stop(); });
|
||||
scannerStream = null;
|
||||
}
|
||||
if (scannerVideo) {
|
||||
scannerVideo.srcObject = null;
|
||||
scannerVideo.classList.remove('d-none');
|
||||
}
|
||||
if (scannerCapture) { scannerCapture.value = ''; }
|
||||
if (scanner) {
|
||||
scanner.classList.add('d-none');
|
||||
scanner.setAttribute('aria-hidden', 'true');
|
||||
}
|
||||
}
|
||||
function resetPairingForm() {
|
||||
form.reset();
|
||||
codeInputsContainer.setAttribute('data-pairing-code', '');
|
||||
codeInputs.forEach(function (input) { input.value = ''; input.disabled = false; });
|
||||
if (codeField) { codeField.value = ''; }
|
||||
Array.prototype.slice.call(form.elements).forEach(function (element) {
|
||||
if (element !== anotherButton && element !== manualButton) { element.disabled = false; }
|
||||
});
|
||||
if (connectButton) { connectButton.classList.remove('d-none'); }
|
||||
if (clientListButton) { clientListButton.classList.add('d-none'); }
|
||||
if (pairingCard) { pairingCard.classList.remove('is-pairing'); }
|
||||
if (pairingProgress) { pairingProgress.classList.add('d-none'); }
|
||||
message.className = 'alert d-none';
|
||||
message.textContent = '';
|
||||
if (anotherButton) {
|
||||
anotherButton.classList.remove('btn-secondary');
|
||||
anotherButton.classList.add('btn-outline-secondary');
|
||||
anotherButton.setAttribute('aria-label', 'Scan player QR code');
|
||||
anotherButton.setAttribute('title', 'Scan player QR code');
|
||||
}
|
||||
if (anotherButtonLabel) { anotherButtonLabel.classList.add('d-none'); }
|
||||
if (manualButton) { manualButton.classList.add('d-none'); }
|
||||
}
|
||||
function codeFromScan(rawValue) {
|
||||
try {
|
||||
var scannedUrl = new URL(String(rawValue || ''), window.location.origin);
|
||||
if (scannedUrl.pathname === '/pairing') {
|
||||
return normalizeCode(scannedUrl.searchParams.get('code'));
|
||||
}
|
||||
} catch (_error) {}
|
||||
return normalizeCode(rawValue);
|
||||
}
|
||||
function applyScannedCode(rawValue) {
|
||||
var code = codeFromScan(rawValue);
|
||||
if (code.length !== codeInputs.length) {
|
||||
scannerMessage.textContent = 'That QR code is not a Pulse Signage pairing code.';
|
||||
return false;
|
||||
}
|
||||
closeScanner();
|
||||
resetPairingForm();
|
||||
setCode(code, 0);
|
||||
return true;
|
||||
}
|
||||
function scanFrame() {
|
||||
if (!scannerVideo || !scannerStream) { return; }
|
||||
if (Date.now() - scannerLastDecodeAt < 400) {
|
||||
scannerFrame = window.requestAnimationFrame(scanFrame);
|
||||
return;
|
||||
}
|
||||
scannerLastDecodeAt = Date.now();
|
||||
if (!window.BarcodeDetector && typeof window.jsQR === 'function') {
|
||||
var width = scannerVideo.videoWidth;
|
||||
var height = scannerVideo.videoHeight;
|
||||
if (width && height) {
|
||||
traceScanner('frame ' + width + 'x' + height);
|
||||
scannerCanvas.width = width;
|
||||
scannerCanvas.height = height;
|
||||
var context = scannerCanvas.getContext('2d', { willReadFrequently: true });
|
||||
context.drawImage(scannerVideo, 0, 0, width, height);
|
||||
var result = decodeQrCanvas(scannerCanvas);
|
||||
traceScanner(result ? 'decoded' : 'no QR');
|
||||
if (result && applyScannedCode(result.data)) { return; }
|
||||
scannerFrame = window.requestAnimationFrame(scanFrame);
|
||||
return;
|
||||
}
|
||||
scannerFrame = window.requestAnimationFrame(scanFrame);
|
||||
return;
|
||||
}
|
||||
scannerVideo.__pairingBarcodeDetector.detect(scannerVideo).then(function (barcodes) {
|
||||
if (barcodes.length) {
|
||||
if (applyScannedCode(barcodes[0].rawValue)) { return; }
|
||||
}
|
||||
scannerFrame = window.requestAnimationFrame(scanFrame);
|
||||
}).catch(function () {
|
||||
scannerFrame = window.requestAnimationFrame(scanFrame);
|
||||
});
|
||||
}
|
||||
function openScanner() {
|
||||
if (!scanner || !scannerVideo || (!window.BarcodeDetector && typeof window.jsQR !== 'function')) {
|
||||
message.className = 'alert alert-warning';
|
||||
message.textContent = 'Camera scanning is not supported by this browser.';
|
||||
return;
|
||||
}
|
||||
scannerMessage.textContent = 'Point your camera at the player QR code.';
|
||||
traceScanner('open BarcodeDetector=' + Boolean(window.BarcodeDetector) + ' jsQR=' + (typeof window.jsQR === 'function'));
|
||||
scannerVideo.classList.remove('d-none');
|
||||
scanner.classList.remove('d-none');
|
||||
scanner.setAttribute('aria-hidden', 'false');
|
||||
if (window.BarcodeDetector) {
|
||||
scannerVideo.__pairingBarcodeDetector = new window.BarcodeDetector({ formats: ['qr_code'] });
|
||||
}
|
||||
if (!navigator.mediaDevices || !navigator.mediaDevices.getUserMedia) {
|
||||
traceScanner('getUserMedia unavailable');
|
||||
scannerVideo.classList.add('d-none');
|
||||
scannerMessage.textContent = 'Use the camera to capture the player QR code.';
|
||||
if (scannerCapture) { scannerCapture.click(); }
|
||||
return;
|
||||
}
|
||||
navigator.mediaDevices.getUserMedia({
|
||||
video: {
|
||||
facingMode: { ideal: 'environment' },
|
||||
width: { ideal: 1280 },
|
||||
height: { ideal: 720 },
|
||||
focusMode: { ideal: 'continuous' }
|
||||
},
|
||||
audio: false
|
||||
}).then(function (stream) {
|
||||
scannerStream = stream;
|
||||
traceScanner('stream opened');
|
||||
traceScanner(JSON.stringify(stream.getVideoTracks()[0].getSettings ? stream.getVideoTracks()[0].getSettings() : {}));
|
||||
scannerVideo.srcObject = stream;
|
||||
return scannerVideo.play();
|
||||
}).then(function () {
|
||||
scannerFrame = window.requestAnimationFrame(scanFrame);
|
||||
}).catch(function () {
|
||||
traceScanner('stream failed');
|
||||
scannerVideo.classList.add('d-none');
|
||||
scannerMessage.textContent = 'Use the camera to capture the player QR code.';
|
||||
if (scannerCapture) { scannerCapture.click(); }
|
||||
});
|
||||
}
|
||||
if (codeInputs.length) {
|
||||
setCode(codeInputsContainer.getAttribute('data-pairing-code'), 0);
|
||||
codeInputs.forEach(function (input, index) {
|
||||
input.addEventListener('input', function () {
|
||||
var value = normalizeCode(input.value);
|
||||
input.value = value.slice(0, 1);
|
||||
if (value.length > 1) {
|
||||
setCode(value, index);
|
||||
} else {
|
||||
syncCodeField();
|
||||
if (input.value && index < codeInputs.length - 1) {
|
||||
codeInputs[index + 1].focus();
|
||||
}
|
||||
}
|
||||
});
|
||||
input.addEventListener('paste', function (event) {
|
||||
event.preventDefault();
|
||||
setCode(event.clipboardData ? event.clipboardData.getData('text') : '', index);
|
||||
});
|
||||
input.addEventListener('keydown', function (event) {
|
||||
if (event.key === 'Backspace' && !input.value && index > 0) {
|
||||
codeInputs[index - 1].focus();
|
||||
codeInputs[index - 1].value = '';
|
||||
syncCodeField();
|
||||
} else if (event.key === 'ArrowLeft' && index > 0) {
|
||||
event.preventDefault();
|
||||
codeInputs[index - 1].focus();
|
||||
} else if (event.key === 'ArrowRight' && index < codeInputs.length - 1) {
|
||||
event.preventDefault();
|
||||
codeInputs[index + 1].focus();
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
form.addEventListener('submit', function (event) {
|
||||
event.preventDefault();
|
||||
getClientId();
|
||||
syncCodeField();
|
||||
if (!codeField || codeField.value.length !== codeInputs.length) {
|
||||
message.className = 'alert alert-warning';
|
||||
message.textContent = 'Enter the complete six-character pairing code.';
|
||||
var firstEmptyInput = codeInputs.filter(function (input) { return !input.value; })[0] || codeInputs[0];
|
||||
if (firstEmptyInput) { firstEmptyInput.focus(); }
|
||||
return;
|
||||
}
|
||||
message.className = 'alert alert-info';
|
||||
message.textContent = 'Pairing player...';
|
||||
var pairingBody = new URLSearchParams(new FormData(form)).toString();
|
||||
if (pairingCard) { pairingCard.classList.add('is-pairing'); }
|
||||
if (pairingProgress) { pairingProgress.classList.remove('d-none'); }
|
||||
Array.prototype.slice.call(form.elements).forEach(function (element) {
|
||||
if (element !== anotherButton && element !== manualButton) { element.disabled = true; }
|
||||
});
|
||||
fetch(form.action, {
|
||||
method: 'POST',
|
||||
headers: { 'Accept': 'application/json', 'Content-Type': 'application/x-www-form-urlencoded;charset=UTF-8' },
|
||||
body: pairingBody
|
||||
}).then(function (response) {
|
||||
return response.text().then(function (text) {
|
||||
var payload = null;
|
||||
try { payload = JSON.parse(text); } catch (_error) {}
|
||||
if (!response.ok) {
|
||||
throw new Error(payload && payload.error ? payload.error : 'Unable to pair player.');
|
||||
}
|
||||
if (payload && payload.queued) {
|
||||
message.className = 'alert alert-info';
|
||||
message.textContent = 'Pairing is still being completed. Keep this page open and wait for confirmation.';
|
||||
return;
|
||||
}
|
||||
message.className = 'alert alert-success';
|
||||
message.textContent = 'Pairing saved. The player is loading your screen.';
|
||||
if (pairingCard) { pairingCard.classList.remove('is-pairing'); }
|
||||
if (pairingProgress) { pairingProgress.classList.add('d-none'); }
|
||||
Array.prototype.slice.call(form.elements).forEach(function (element) {
|
||||
if (element !== anotherButton && element !== manualButton) { element.disabled = true; }
|
||||
});
|
||||
if (connectButton) { connectButton.classList.add('d-none'); }
|
||||
if (clientListButton) { clientListButton.classList.remove('d-none'); }
|
||||
if (anotherButton) {
|
||||
anotherButton.classList.remove('d-none');
|
||||
anotherButton.classList.remove('btn-outline-secondary');
|
||||
anotherButton.classList.add('btn-secondary');
|
||||
anotherButton.setAttribute('aria-label', 'Pair another screen');
|
||||
anotherButton.setAttribute('title', 'Pair another screen');
|
||||
}
|
||||
if (anotherButtonLabel) { anotherButtonLabel.classList.remove('d-none'); }
|
||||
if (manualButton) { manualButton.classList.remove('d-none'); }
|
||||
});
|
||||
}).catch(function (error) {
|
||||
if (pairingCard) { pairingCard.classList.remove('is-pairing'); }
|
||||
if (pairingProgress) { pairingProgress.classList.add('d-none'); }
|
||||
Array.prototype.slice.call(form.elements).forEach(function (element) {
|
||||
if (element !== anotherButton && element !== manualButton) { element.disabled = false; }
|
||||
});
|
||||
message.className = 'alert alert-danger';
|
||||
message.textContent = error && error.message ? error.message : 'Unable to pair player.';
|
||||
});
|
||||
});
|
||||
|
||||
if (anotherButton) { anotherButton.addEventListener('click', openScanner); }
|
||||
if (manualButton) {
|
||||
manualButton.addEventListener('click', function () {
|
||||
resetPairingForm();
|
||||
if (codeInputs[0]) { codeInputs[0].focus(); }
|
||||
});
|
||||
}
|
||||
if (scannerClose) { scannerClose.addEventListener('click', closeScanner); }
|
||||
if (scannerCapture) {
|
||||
scannerCapture.addEventListener('change', function () {
|
||||
var file = scannerCapture.files && scannerCapture.files[0];
|
||||
if (!file) { return; }
|
||||
scannerMessage.textContent = 'Reading QR code...';
|
||||
var image = new Image();
|
||||
image.onload = function () {
|
||||
if (window.BarcodeDetector) {
|
||||
new window.BarcodeDetector({ formats: ['qr_code'] }).detect(image).then(function (barcodes) {
|
||||
if (!barcodes.length || !applyScannedCode(barcodes[0].rawValue)) {
|
||||
scannerMessage.textContent = 'Could not find a Pulse Signage QR code in that image.';
|
||||
}
|
||||
}).catch(function () {
|
||||
scannerMessage.textContent = 'Could not read that QR image. Try again.';
|
||||
});
|
||||
return;
|
||||
}
|
||||
try {
|
||||
var canvas = document.createElement('canvas');
|
||||
canvas.width = image.naturalWidth;
|
||||
canvas.height = image.naturalHeight;
|
||||
var context = canvas.getContext('2d', { willReadFrequently: true });
|
||||
context.drawImage(image, 0, 0);
|
||||
var result = decodeQrCanvas(canvas);
|
||||
if (!result || !applyScannedCode(result.data)) {
|
||||
scannerMessage.textContent = 'Could not find a Pulse Signage QR code in that image.';
|
||||
}
|
||||
} catch (_error) {
|
||||
scannerMessage.textContent = 'Could not read that QR image. Try again.';
|
||||
}
|
||||
};
|
||||
image.onerror = function () {
|
||||
scannerMessage.textContent = 'Could not read that QR image. Try again.';
|
||||
};
|
||||
var reader = new FileReader();
|
||||
reader.onload = function () { image.src = reader.result; };
|
||||
reader.onerror = function () { scannerMessage.textContent = 'Could not read that QR image. Try again.'; };
|
||||
reader.readAsDataURL(file);
|
||||
});
|
||||
}
|
||||
if (scanner) {
|
||||
scanner.addEventListener('click', function (event) {
|
||||
if (event.target === scanner) { closeScanner(); }
|
||||
});
|
||||
}
|
||||
}());
|
||||
@@ -87,7 +87,7 @@ import { createSlideFormPreviewHelpers } from '/assets/js/slides/slide-form-prev
|
||||
return templates.find(function (item) { return Number(item.id) === Number(id); }) || null;
|
||||
}
|
||||
|
||||
function applyBackdropStyle(element, backgroundColor, backgroundImagePath) {
|
||||
function applyBackdropStyle(element, backgroundColor, backgroundImagePath, backgroundGradient) {
|
||||
if (!element) {
|
||||
return;
|
||||
}
|
||||
@@ -107,7 +107,21 @@ import { createSlideFormPreviewHelpers } from '/assets/js/slides/slide-form-prev
|
||||
|
||||
var color = String(backgroundColor || '#111111').trim() || '#111111';
|
||||
element.style.backgroundColor = color;
|
||||
element.style.backgroundImage = 'none';
|
||||
var gradient = '';
|
||||
try {
|
||||
var gradientData = typeof backgroundGradient === 'string' ? JSON.parse(backgroundGradient) : backgroundGradient;
|
||||
if (gradientData && gradientData.type === 'linear' && Array.isArray(gradientData.colors) && gradientData.colors.length >= 2) {
|
||||
var stops = Array.isArray(gradientData.stops) ? gradientData.stops : (gradientData.colors || []).map(function (color, index, colors) { return { color: color, position: colors.length > 1 ? Math.round(index * 100 / (colors.length - 1)) : 0 }; });
|
||||
stops = stops.filter(function (stop) { return stop && /^#[0-9a-fA-F]{3,8}$/.test(String(stop.color || '').trim()); }).slice(0, 12);
|
||||
if (stops.length >= 2) {
|
||||
var angle = Number(gradientData.angle);
|
||||
gradient = 'linear-gradient(' + (Number.isFinite(angle) ? Math.max(0, Math.min(360, angle)) : 90) + 'deg,' + stops.map(function (stop) { return stop.color + ' ' + Math.max(0, Math.min(100, Number(stop.position) || 0)) + '%'; }).join(',') + ')';
|
||||
}
|
||||
}
|
||||
} catch (_error) {
|
||||
gradient = '';
|
||||
}
|
||||
element.style.backgroundImage = gradient || 'none';
|
||||
element.style.backgroundPosition = 'center';
|
||||
element.style.backgroundSize = '100% 100%';
|
||||
element.style.backgroundRepeat = 'no-repeat';
|
||||
@@ -471,6 +485,7 @@ import { createSlideFormPreviewHelpers } from '/assets/js/slides/slide-form-prev
|
||||
canvasHeight: currentPreviewCanvasHeight || Math.max(1, Number(template && template.canvas_size_height ? template.canvas_size_height : 1080)),
|
||||
backgroundColor: template && template.background_color ? String(template.background_color) : '#111111',
|
||||
backgroundImagePath: template && template.background_image_path ? String(template.background_image_path) : '',
|
||||
backgroundGradient: template && template.background_gradient ? String(template.background_gradient) : '',
|
||||
fontStylesheetHref: fontStylesheetHref
|
||||
};
|
||||
}
|
||||
@@ -591,7 +606,7 @@ import { createSlideFormPreviewHelpers } from '/assets/js/slides/slide-form-prev
|
||||
currentPreviewCanvasWidth = canvasWidth;
|
||||
currentPreviewCanvasHeight = canvasHeight;
|
||||
slidePreviewStage.style.aspectRatio = canvasWidth + ' / ' + canvasHeight;
|
||||
applyBackdropStyle(slidePreviewStage, template.background_color || '#111111', template.background_image_path);
|
||||
applyBackdropStyle(slidePreviewStage, template.background_color || '#111111', template.background_image_path, template.background_gradient);
|
||||
slidePreviewMeta.textContent = canvasWidth + 'x' + canvasHeight;
|
||||
if (!(template.regions || []).length) {
|
||||
slidePreviewEmpty.style.display = 'flex';
|
||||
|
||||
@@ -33,6 +33,18 @@
|
||||
var canvasHeightInput = document.getElementById('canvas-height');
|
||||
var backgroundInput = document.getElementById('background-image');
|
||||
var backgroundColorInput = document.getElementById('background-color');
|
||||
var backgroundGradientEnabled = document.getElementById('background-gradient-enabled');
|
||||
var backgroundGradientInput = document.getElementById('background-gradient');
|
||||
var backgroundGradientOptions = document.getElementById('background-gradient-options');
|
||||
var backgroundGradientAngle = document.getElementById('background-gradient-angle');
|
||||
var backgroundGradientAngleOutput = document.getElementById('background-gradient-angle-output');
|
||||
var backgroundGradientBar = document.getElementById('background-gradient-bar');
|
||||
var backgroundGradientBarHandles = document.getElementById('background-gradient-bar-handles');
|
||||
var backgroundGradientStops = document.getElementById('background-gradient-stops');
|
||||
var backgroundGradientAddStop = document.getElementById('background-gradient-add-stop');
|
||||
var draggedGradientStopIndex = -1;
|
||||
var draggedGradientStopRow = null;
|
||||
var draggedGradientStopHandle = null;
|
||||
var backgroundPreview = document.getElementById('background-preview');
|
||||
var backgroundEmpty = document.getElementById('background-empty');
|
||||
var removeBackgroundButton = document.getElementById('remove-background-image');
|
||||
@@ -1117,6 +1129,65 @@
|
||||
return;
|
||||
}
|
||||
stage.style.backgroundColor = backgroundColorInput && backgroundColorInput.value ? backgroundColorInput.value : '#111111';
|
||||
if (backgroundGradientEnabled && backgroundGradientEnabled.checked) {
|
||||
var gradientStops = Array.prototype.map.call(backgroundGradientStops.querySelectorAll('[data-gradient-stop]'), function (stop) {
|
||||
return { color: stop.querySelector('[data-gradient-stop-color]').value, position: Number(stop.querySelector('[data-gradient-stop-position]').value || 0) };
|
||||
});
|
||||
var gradient = { type: 'linear', stops: gradientStops, angle: Number(backgroundGradientAngle.value || 90) };
|
||||
backgroundGradientInput.value = JSON.stringify(gradient);
|
||||
stage.style.backgroundImage = 'linear-gradient(' + gradient.angle + 'deg,' + gradient.stops.map(function (stop) { return stop.color + ' ' + stop.position + '%'; }).join(',') + ')';
|
||||
var gradientBarTrack = backgroundGradientBar && backgroundGradientBar.querySelector('.gradient-stop-bar-track');
|
||||
if (gradientBarTrack) gradientBarTrack.style.background = stage.style.backgroundImage;
|
||||
backgroundGradientOptions.hidden = false;
|
||||
} else {
|
||||
backgroundGradientInput.value = '';
|
||||
stage.style.backgroundImage = 'none';
|
||||
var emptyGradientBarTrack = backgroundGradientBar && backgroundGradientBar.querySelector('.gradient-stop-bar-track');
|
||||
if (emptyGradientBarTrack) emptyGradientBarTrack.style.background = 'var(--bs-secondary-bg)';
|
||||
backgroundGradientOptions.hidden = true;
|
||||
}
|
||||
if (backgroundGradientAngleOutput) backgroundGradientAngleOutput.value = String(backgroundGradientAngle.value || 90);
|
||||
}
|
||||
|
||||
function renderBackgroundGradientStops(stops) {
|
||||
if (!backgroundGradientStops) return;
|
||||
var normalizedStops = Array.isArray(stops) && stops.length >= 2 ? stops : [{ color: '#111111', position: 0 }, { color: '#334455', position: 100 }];
|
||||
if (backgroundGradientBarHandles) {
|
||||
backgroundGradientBarHandles.innerHTML = normalizedStops.map(function (stop, index) {
|
||||
return '<button type="button" class="gradient-stop-handle" data-gradient-bar-index="' + index + '" style="left:' + Math.max(0, Math.min(100, Number(stop.position) || 0)) + '%;background:' + escapeHtml(stop.color || '#111111') + ';" aria-label="Gradient stop ' + (index + 1) + '"></button>';
|
||||
}).join('');
|
||||
if (backgroundGradientBar) backgroundGradientBar.style.background = 'transparent';
|
||||
}
|
||||
backgroundGradientStops.innerHTML = normalizedStops.map(function (stop) {
|
||||
return '<div class="d-flex align-items-end gap-2" data-gradient-stop><label class="flex-grow-1">Colour<input type="color" class="form-control form-control-color w-100" data-gradient-stop-color value="' + escapeHtml(stop.color || '#111111') + '" /></label><label style="width:6rem">Position<input type="number" class="form-control" data-gradient-stop-position min="0" max="100" value="' + Math.max(0, Math.min(100, Number(stop.position) || 0)) + '" /></label><button type="button" class="btn btn-outline-danger" data-gradient-stop-remove aria-label="Remove colour stop">×</button></div>';
|
||||
}).join('');
|
||||
updateGradientStopRemoveButtons();
|
||||
}
|
||||
|
||||
function updateGradientStopRemoveButtons() {
|
||||
if (!backgroundGradientStops) return;
|
||||
var stops = backgroundGradientStops.querySelectorAll('[data-gradient-stop]');
|
||||
Array.prototype.forEach.call(stops, function (stop) { stop.querySelector('[data-gradient-stop-remove]').disabled = stops.length <= 2; });
|
||||
}
|
||||
|
||||
function syncBackgroundGradientBar() {
|
||||
if (!backgroundGradientStops || !backgroundGradientBarHandles) return;
|
||||
var rows = backgroundGradientStops.querySelectorAll('[data-gradient-stop]');
|
||||
if (backgroundGradientBarHandles.children.length !== rows.length) {
|
||||
backgroundGradientBarHandles.innerHTML = Array.prototype.map.call(rows, function (row, index) {
|
||||
var color = row.querySelector('[data-gradient-stop-color]').value || '#111111';
|
||||
var position = Math.max(0, Math.min(100, Number(row.querySelector('[data-gradient-stop-position]').value) || 0));
|
||||
return '<button type="button" class="gradient-stop-handle" data-gradient-bar-index="' + index + '" style="left:' + position + '%;background:' + escapeHtml(color) + ';" aria-label="Gradient stop ' + (index + 1) + '"></button>';
|
||||
}).join('');
|
||||
return;
|
||||
}
|
||||
Array.prototype.forEach.call(rows, function (row, index) {
|
||||
var handle = backgroundGradientBarHandles.children[index];
|
||||
if (!handle) return;
|
||||
handle.style.background = row.querySelector('[data-gradient-stop-color]').value || '#111111';
|
||||
handle.style.left = Math.max(0, Math.min(100, Number(row.querySelector('[data-gradient-stop-position]').value) || 0)) + '%';
|
||||
handle.setAttribute('data-gradient-bar-index', index);
|
||||
});
|
||||
}
|
||||
|
||||
function updateRegionLockBadge(card) {
|
||||
@@ -1757,6 +1828,79 @@
|
||||
if (backgroundColorInput) {
|
||||
backgroundColorInput.addEventListener('input', updateStageBackgroundColor);
|
||||
}
|
||||
if (backgroundGradientEnabled) backgroundGradientEnabled.addEventListener('change', updateStageBackgroundColor);
|
||||
if (backgroundGradientAddStop) backgroundGradientAddStop.addEventListener('click', function () {
|
||||
var stops = Array.prototype.map.call(backgroundGradientStops.querySelectorAll('[data-gradient-stop]'), function (stop) {
|
||||
return { color: stop.querySelector('[data-gradient-stop-color]').value, position: Number(stop.querySelector('[data-gradient-stop-position]').value || 0) };
|
||||
});
|
||||
stops.push({ color: '#ffffff', position: 100 });
|
||||
renderBackgroundGradientStops(stops);
|
||||
updateStageBackgroundColor();
|
||||
});
|
||||
if (backgroundGradientStops) backgroundGradientStops.addEventListener('input', function () {
|
||||
syncBackgroundGradientBar();
|
||||
updateStageBackgroundColor();
|
||||
});
|
||||
if (backgroundGradientStops) backgroundGradientStops.addEventListener('click', function (event) {
|
||||
var removeButton = event.target.closest('[data-gradient-stop-remove]');
|
||||
if (!removeButton || removeButton.disabled) return;
|
||||
removeButton.closest('[data-gradient-stop]').remove();
|
||||
updateGradientStopRemoveButtons();
|
||||
syncBackgroundGradientBar();
|
||||
updateStageBackgroundColor();
|
||||
});
|
||||
if (backgroundGradientBar) backgroundGradientBar.addEventListener('click', function (event) {
|
||||
if (event.target.closest('[data-gradient-bar-index]')) return;
|
||||
var rect = backgroundGradientBar.getBoundingClientRect();
|
||||
var position = Math.max(0, Math.min(100, Math.round(((event.clientX - rect.left) / rect.width) * 100)));
|
||||
var stops = Array.prototype.map.call(backgroundGradientStops.querySelectorAll('[data-gradient-stop]'), function (stop) { return { color: stop.querySelector('[data-gradient-stop-color]').value, position: Number(stop.querySelector('[data-gradient-stop-position]').value || 0) }; });
|
||||
stops.push({ color: '#ffffff', position: position });
|
||||
stops.sort(function (left, right) { return left.position - right.position; });
|
||||
renderBackgroundGradientStops(stops);
|
||||
updateStageBackgroundColor();
|
||||
});
|
||||
if (backgroundGradientBarHandles) backgroundGradientBarHandles.addEventListener('pointerdown', function (event) {
|
||||
var handle = event.target.closest('[data-gradient-bar-index]');
|
||||
if (!handle) return;
|
||||
draggedGradientStopIndex = Number(handle.getAttribute('data-gradient-bar-index'));
|
||||
draggedGradientStopRow = backgroundGradientStops.querySelectorAll('[data-gradient-stop]')[draggedGradientStopIndex] || null;
|
||||
draggedGradientStopHandle = handle;
|
||||
handle.classList.add('is-dragging');
|
||||
handle.setPointerCapture(event.pointerId);
|
||||
event.preventDefault();
|
||||
});
|
||||
document.addEventListener('pointermove', function (event) {
|
||||
if (draggedGradientStopIndex < 0) return;
|
||||
var rect = backgroundGradientBar.getBoundingClientRect();
|
||||
var position = Math.max(0, Math.min(100, Math.round(((event.clientX - rect.left) / rect.width) * 100)));
|
||||
if (draggedGradientStopRow) draggedGradientStopRow.querySelector('[data-gradient-stop-position]').value = position;
|
||||
if (draggedGradientStopHandle) draggedGradientStopHandle.style.left = position + '%';
|
||||
var entries = Array.prototype.map.call(backgroundGradientStops.querySelectorAll('[data-gradient-stop]'), function (row, index) {
|
||||
return { row: row, handle: backgroundGradientBarHandles.children[index], position: Number(row.querySelector('[data-gradient-stop-position]').value || 0), index: index };
|
||||
});
|
||||
entries.sort(function (left, right) { return left.position - right.position || left.index - right.index; });
|
||||
entries.forEach(function (entry, index) {
|
||||
backgroundGradientStops.appendChild(entry.row);
|
||||
backgroundGradientBarHandles.appendChild(entry.handle);
|
||||
entry.handle.setAttribute('data-gradient-bar-index', index);
|
||||
});
|
||||
draggedGradientStopIndex = entries.findIndex(function (entry) { return entry.row === draggedGradientStopRow; });
|
||||
updateStageBackgroundColor();
|
||||
});
|
||||
document.addEventListener('pointerup', function () {
|
||||
if (draggedGradientStopHandle) draggedGradientStopHandle.classList.remove('is-dragging');
|
||||
draggedGradientStopIndex = -1;
|
||||
draggedGradientStopRow = null;
|
||||
draggedGradientStopHandle = null;
|
||||
});
|
||||
document.addEventListener('pointercancel', function () {
|
||||
draggedGradientStopIndex = -1;
|
||||
draggedGradientStopRow = null;
|
||||
draggedGradientStopHandle = null;
|
||||
});
|
||||
[backgroundGradientAngle].forEach(function (input) {
|
||||
if (input) input.addEventListener('input', updateStageBackgroundColor);
|
||||
});
|
||||
canvasSizeSelect.addEventListener('change', function () { syncCanvasSizeSelection(); render(); });
|
||||
canvasWidthInput.addEventListener('input', render);
|
||||
canvasHeightInput.addEventListener('input', render);
|
||||
@@ -1858,6 +2002,19 @@
|
||||
});
|
||||
}
|
||||
|
||||
if (backgroundGradientInput && backgroundGradientInput.value) {
|
||||
try {
|
||||
var initialGradient = JSON.parse(backgroundGradientInput.value);
|
||||
if (initialGradient) {
|
||||
var initialStops = Array.isArray(initialGradient.stops) ? initialGradient.stops : (initialGradient.colors || []).map(function (color, index, colors) { return { color: color, position: colors.length > 1 ? Math.round(index * 100 / (colors.length - 1)) : 0 }; });
|
||||
renderBackgroundGradientStops(initialStops);
|
||||
if (backgroundGradientAngle && Number.isFinite(Number(initialGradient.angle))) backgroundGradientAngle.value = String(Math.max(0, Math.min(360, Number(initialGradient.angle))));
|
||||
}
|
||||
} catch (_error) {
|
||||
backgroundGradientInput.value = '';
|
||||
}
|
||||
}
|
||||
if (backgroundGradientStops && !backgroundGradientStops.children.length) renderBackgroundGradientStops();
|
||||
renderRegionList(existingRegions);
|
||||
syncCanvasSizeSelection();
|
||||
updateStageBackgroundColor();
|
||||
|
||||
@@ -133,12 +133,7 @@
|
||||
}
|
||||
|
||||
function getClientRowKey(client) {
|
||||
var clientName = String(client && client.client_name ? client.client_name : '').trim();
|
||||
if (clientName) {
|
||||
return clientName;
|
||||
}
|
||||
|
||||
return String(client && client.screen_slug ? client.screen_slug : client && client.deviceId ? client.deviceId : client && client.id ? client.id : client && client.clientId ? client.clientId : '').trim();
|
||||
return String(client && client.id ? client.id : client && client.clientId ? client.clientId : client && client.screen_slug ? client.screen_slug : client && client.deviceId ? client.deviceId : '').trim();
|
||||
}
|
||||
|
||||
function getClientDisplayName(client) {
|
||||
|
||||
Reference in New Issue
Block a user