Release 2.21.2: fix animation timing
Publish Docker Image / build-and-push-existing-registry (./build/Dockerfile, web, pulse-signage-web) (push) Successful in 1m21s
Publish Docker Image / build-and-push-existing-registry (./build/Dockerfile.player, player, pulse-signage-player) (push) Successful in 32s

This commit is contained in:
lzstealth committed 2026-10-26 22:41:07 +00:00
1 parent c923dcc1c5
commit 554678b3c4
13 files changed
+151 -54

No files matched your search

@@ -144,6 +144,7 @@ function clearRegionAnimationClasses(root) {
element.style.removeProperty('--animate-duration');
element.style.removeProperty('--animate-delay');
element.style.removeProperty('--animate-repeat');
element.style.removeProperty('animation-delay');
});
}
@@ -161,13 +162,16 @@ function applyAnimationStep(element, step, phase) {
return;
}
element.classList.add('animate__animated', 'animate__' + preset);
element.style.setProperty('--animate-duration', String(Math.max(1, Number(step.duration_ms || 0) || 1000)) + 'ms');
if (Number(step.delay_ms || 0) > 0) {
element.style.setProperty('--animate-delay', String(Math.max(0, Number(step.delay_ms || 0))) + 'ms');
var delayMs = phase === 'outro' ? 0 : Math.max(0, Number(step.delay_ms || 0));
if (delayMs > 0) {
element.style.setProperty('--animate-delay', String(delayMs) + 'ms');
element.style.setProperty('animation-delay', String(delayMs) + 'ms');
} else {
element.style.removeProperty('--animate-delay');
element.style.removeProperty('animation-delay');
}
element.classList.add('animate__animated', 'animate__' + preset);
if (phase === 'loop') {
var repeatCount = Number(step.iterations);
@@ -76,8 +76,22 @@
});
return normalize(config);
}
function clampAdvancedField(input) {
if (!input || !input.dataset) return;
var field = input.dataset.animationModalField;
var value = String(input.value || '').trim();
if (!value) return;
var number = Number(value);
if (!Number.isFinite(number)) return;
var normalized;
if (field === 'duration_ms') normalized = Math.min(200, Math.max(1, Math.round(number)));
else if (field === 'iterations' && input.dataset.animationModalStep === 'loop') normalized = Math.min(999, Math.max(1, Math.round(number)));
else return;
if (String(normalized) !== String(input.value)) input.value = String(normalized);
}
window.pulseRegionAnimationControls = {
read: read, write: write, setPreset: setPreset, bind: bind,
populateAdvanced: populateAdvanced, readAdvanced: readAdvanced
populateAdvanced: populateAdvanced, readAdvanced: readAdvanced,
clampAdvancedField: clampAdvancedField
};
})();
@@ -141,7 +141,7 @@ export function createSlideAnimationEditor(options) {
element.className.split(/\s+/).forEach(function (className) {
if (className.indexOf('animate__') === 0) element.classList.remove(className);
});
['--animate-duration', '--animate-delay', '--animate-repeat'].forEach(function (property) { element.style.removeProperty(property); });
['--animate-duration', '--animate-delay', '--animate-repeat', 'animation-delay'].forEach(function (property) { element.style.removeProperty(property); });
});
updatePreviewButtons();
}
@@ -168,16 +168,18 @@ export function createSlideAnimationEditor(options) {
previewMode = mode;
var token = previewToken;
var longest = 0;
getPreviewElements().forEach(function (element, index) {
getPreviewElements().forEach(function (element) {
var config = window.templateDesignerUtils.normalizeAnimationConfig(getConfig(element.dataset.regionKey));
var step = config[mode] || {};
var preset = window.templateAnimationPresets.normalize(step.preset);
if (preset === 'none') return;
var delay = Math.max(0, Number(step.delay_ms) || 0) + index * 90;
var configuredDelay = mode === 'outro' ? 0 : Math.max(0, Number(step.delay_ms) || 0);
var delay = configuredDelay;
var duration = Math.max(1, Number(step.duration_ms) || 1000);
var iterations = mode === 'loop' ? Math.max(1, Number(step.iterations) || 1) : 1;
element.style.setProperty('--animate-duration', duration + 'ms');
element.style.setProperty('--animate-delay', delay + 'ms');
element.style.setProperty('animation-delay', delay + 'ms');
element.style.setProperty('--animate-repeat', String(iterations));
element.classList.remove('animate__animated', 'animate__' + preset, 'animate__repeat-1');
void element.offsetWidth;
@@ -266,6 +268,9 @@ export function createSlideAnimationEditor(options) {
advanced.addEventListener('shown.bs.modal', function () {
advanced.querySelector('[data-animation-modal-field]').focus();
});
advanced.addEventListener('input', function (event) {
controls.clampAdvancedField(event.target);
});
advanced.addEventListener('hidden.bs.modal', function () {
window.pulseModal.getOrCreate(advanced)._scrollBar = advancedScrollBar;
advancedScrollBar = null;
@@ -363,6 +363,7 @@
rect.style.removeProperty('--animate-duration');
rect.style.removeProperty('--animate-delay');
rect.style.removeProperty('--animate-repeat');
rect.style.removeProperty('animation-delay');
rect.style.opacity = '';
rect.style.transform = '';
rect.style.filter = '';
@@ -442,15 +443,20 @@
var durationMs = Number(step && step.duration_ms);
var delayMs = Number(step && step.delay_ms);
var repeatCount = Number(step && step.iterations);
if (mode === 'out') delayMs = 0;
var isAttentionSeeker = preset === 'bounce' || preset === 'flash' || preset === 'pulse' || preset === 'rubberBand' || preset === 'shakeX' || preset === 'shakeY' || preset === 'headShake' || preset === 'swing' || preset === 'tada' || preset === 'wobble' || preset === 'jello' || preset === 'heartBeat';
rect.classList.remove('animate__animated', 'animate__infinite');
rect.style.removeProperty('--animate-duration');
rect.style.removeProperty('--animate-delay');
rect.style.removeProperty('--animate-repeat');
rect.style.removeProperty('animation-delay');
rect.style.setProperty('--animate-duration', Math.max(0, Number.isFinite(durationMs) && durationMs > 0 ? durationMs : 1000) + 'ms');
if (Number.isFinite(delayMs) && delayMs > 0) {
rect.style.setProperty('--animate-delay', Math.max(0, delayMs) + 'ms');
rect.style.setProperty('animation-delay', Math.max(0, delayMs) + 'ms');
} else {
rect.style.removeProperty('animation-delay');
}
rect.classList.add('animate__animated', animateClass);
if (isAttentionSeeker) {
@@ -475,6 +481,9 @@
if (!Number.isFinite(delayMs) || delayMs < 0) {
delayMs = 0;
}
if (normalizedMode === 'out') {
delayMs = 0;
}
if (!Number.isFinite(iterations) || iterations < 1) {
iterations = 1;
}
@@ -595,39 +604,6 @@
window.pulseRegionAnimationControls.populateAdvanced(animationAdvancedModal, config);
}
function readNumberField(value) {
var raw = String(value || '').trim();
if (!raw) {
return null;
}
var next = Number(raw);
return Number.isFinite(next) ? Math.round(next) : null;
}
function clampAnimationModalField(field) {
if (!field || !field.matches) {
return;
}
var isLoopIterationsField = field.matches('[data-animation-modal-step="loop"][data-animation-modal-field="iterations"]');
var isDurationField = field.matches('[data-animation-modal-field="duration_ms"]');
if (!isLoopIterationsField && !isDurationField) {
return;
}
var fieldValue = readNumberField(field.value);
if (fieldValue === null) {
return;
}
var normalizedValue = isLoopIterationsField
? Math.min(MAX_ANIMATION_REPEAT_COUNT, Math.max(1, fieldValue))
: Math.min(200, Math.max(1, fieldValue));
if (String(normalizedValue) !== String(field.value)) {
field.value = String(normalizedValue);
}
}
function readAnimationModalConfig() {
if (!animationAdvancedModal) {
return null;
@@ -732,7 +708,6 @@
function animatePreviewRects(mode, regions) {
var rects = getPreviewRects();
var token = previewState.token;
var stagger = 90;
var duration = 900;
var overlayRect = getOverlayRect();
var canvasMovement = Math.max(12, Math.round(overlayRect.height * 0.02));
@@ -740,11 +715,11 @@
previewState.animations = [];
rects.forEach(function (rect, index) {
rects.forEach(function (rect) {
var region = regions && regions[index] ? regions[index] : null;
var step = getPreviewRegionAnimationStep(region, mode);
var preset = normalizePreviewAnimation(step && step.preset);
var delay = index * stagger;
var delay = mode === 'out' ? 0 : Math.max(0, Number(step && step.delay_ms) || 0);
var animation = null;
var animateCssMode = isAnimateCssPreset(preset);
var timingMs = delay + getPreviewAnimationStepTimingMs(step, mode, animateCssMode);
@@ -1785,7 +1760,7 @@
activeAnimationCard = null;
});
animationAdvancedModal.addEventListener('input', function (event) {
clampAnimationModalField(event.target);
window.pulseRegionAnimationControls.clampAdvancedField(event.target);
markTemplateFormDirty();
});
animationAdvancedModal.addEventListener('change', function () {
@@ -15,7 +15,7 @@
<div class="card-body d-grid gap-3 align-content-start">
<label class="d-grid gap-1">Preset<select class="form-select" data-animation-modal-step="outro" data-animation-modal-field="preset">{{#each animationPresets.advanced.outro}}<option value="{{value}}">{{label}}</option>{{/each}}</select></label>
<label class="d-grid gap-1">Duration %<input class="form-control" type="number" min="1" max="200" step="1" data-animation-modal-step="outro" data-animation-modal-field="duration_ms" placeholder="100" /></label>
<label class="d-grid gap-1">Delay ms<input class="form-control" type="number" min="0" step="1" data-animation-modal-step="outro" data-animation-modal-field="delay_ms" placeholder="0" /></label>
<label class="d-grid gap-1">Finish before transition (ms)<input class="form-control" type="number" min="0" step="1" data-animation-modal-step="outro" data-animation-modal-field="delay_ms" placeholder="0" /></label>
</div>
</div>
</div>