Add onboarding weather and template gradients
Publish Docker Image / build-and-push-existing-registry (./build/Dockerfile, web, pulse-signage-web) (push) Successful in 1m53s
Publish Docker Image / build-and-push-existing-registry (./build/Dockerfile.player, player, pulse-signage-player) (push) Successful in 34s

This commit is contained in:
lzstealth committed 2026-08-28 20:05:23 +01:00
1 parent aa07a78912
commit 3960931ebe
80 files changed
+12750 -519

No files matched your search

@@ -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">&times;</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();