Wait for full animation preview duration
Publish Docker Image / build-and-push-existing-registry (./build/Dockerfile, web, pulse-signage-web) (push) Successful in 1m32s
Publish Docker Image / build-and-push-existing-registry (./build/Dockerfile.player, player, pulse-signage-player) (push) Successful in 36s

This commit is contained in:
lzstealth committed 2026-10-26 23:02:09 +00:00
1 parent b5301f3f16
commit 8ee59e97d3
3 files changed
+22 -4

No files matched your search

+1 -1
View File
@@ -6,7 +6,7 @@ All notable changes to this project will be documented in this file.
### Fixed ### Fixed
- Animation delays now work in slide and template previews and player playback. Outro timing follows each preset's computed Animate.css duration, is configured as time before transition, and preview-only region staggering is removed; slide animation controls enforce the same advanced limits as templates. - Animation delays now work in slide and template previews and player playback. Outro timing follows each preset's computed Animate.css duration and is configured as time before transition; preview cleanup waits for each preset's computed runtime, preview-only region staggering is removed, and slide animation controls enforce the same advanced limits as templates.
## 2.21.1 - 2026-10-26 ## 2.21.1 - 2026-10-26
@@ -132,6 +132,17 @@ export function createSlideAnimationEditor(options) {
function getPreviewElements() { function getPreviewElements() {
return Array.prototype.slice.call(previewHost.querySelectorAll('.slide-preview-region[data-region-key]')); return Array.prototype.slice.call(previewHost.querySelectorAll('.slide-preview-region[data-region-key]'));
} }
function getAnimationDurationMs(element, fallbackDurationMs) {
if (typeof window.getComputedStyle !== 'function') return fallbackDurationMs;
var durations = String(window.getComputedStyle(element).animationDuration || '').split(',');
var longestDuration = durations.reduce(function (longest, value) {
var match = value.trim().match(/^([\d.]+)\s*(ms|s)$/i);
if (!match) return longest;
var duration = Number(match[1]) * (match[2].toLowerCase() === 's' ? 1000 : 1);
return Number.isFinite(duration) ? Math.max(longest, duration) : longest;
}, 0);
return longestDuration || fallbackDurationMs;
}
function stopPreview() { function stopPreview() {
if (previewTimer) window.clearTimeout(previewTimer); if (previewTimer) window.clearTimeout(previewTimer);
previewTimer = 0; previewTimer = 0;
@@ -185,7 +196,7 @@ export function createSlideAnimationEditor(options) {
void element.offsetWidth; void element.offsetWidth;
element.classList.add('animate__animated', 'animate__' + preset); element.classList.add('animate__animated', 'animate__' + preset);
if (mode === 'loop') element.classList.add('animate__repeat-1'); if (mode === 'loop') element.classList.add('animate__repeat-1');
longest = Math.max(longest, delay + duration * iterations); longest = Math.max(longest, delay + getAnimationDurationMs(element, duration) * iterations);
}); });
updatePreviewButtons(); updatePreviewButtons();
previewTimer = window.setTimeout(function () { previewTimer = window.setTimeout(function () {
+9 -2
View File
@@ -383,7 +383,13 @@ test('slide advanced popup applies changes, cancels drafts and preserves the par
}; };
const modalInstances = new Map(); const modalInstances = new Map();
const scrollBarCalls = []; const scrollBarCalls = [];
const context = { window: { innerWidth: 1200, setTimeout() { return 1; }, clearTimeout() {}, pulseModal: { const previewTimers = [];
const context = { window: { innerWidth: 1200,
setTimeout(_callback, delay) { previewTimers.push(delay); return previewTimers.length; }, clearTimeout() {},
getComputedStyle() {
return { animationDuration: '400ms' };
},
pulseModal: {
show(modal) { this.getOrCreate(modal)._scrollBar.hide(); modal.shown = true; }, show(modal) { this.getOrCreate(modal)._scrollBar.hide(); modal.shown = true; },
hide(modal) { this.getOrCreate(modal)._scrollBar.reset(); modal.shown = false; modal.handlers['hidden.bs.modal'](); }, hide(modal) { this.getOrCreate(modal)._scrollBar.reset(); modal.shown = false; modal.handlers['hidden.bs.modal'](); },
getOrCreate(modal) { getOrCreate(modal) {
@@ -410,7 +416,7 @@ test('slide advanced popup applies changes, cancels drafts and preserves the par
getTemplate() { return { regions: [ getTemplate() { return { regions: [
{ region_key: 'hero', animation_json: { intro: { preset: 'bounceIn' }, outro: { preset: 'fadeOut' } } }, { region_key: 'hero', animation_json: { intro: { preset: 'bounceIn' }, outro: { preset: 'fadeOut' } } },
{ region_key: 'fixed_brand', read_only: true, animation_json: { { region_key: 'fixed_brand', read_only: true, animation_json: {
outro: { preset: 'fadeOut', duration_ms: 200, delay_ms: 500 }, outro: { preset: 'hinge', duration_ms: 200, delay_ms: 500 },
loop: { preset: 'pulse', duration_ms: 200, delay_ms: 240, iterations: 1 } loop: { preset: 'pulse', duration_ms: 200, delay_ms: 240, iterations: 1 }
} }, } },
{ region_key: 'fixed_brand_second', read_only: true, animation_json: { loop: { preset: 'pulse', duration_ms: 200, iterations: 1 } } } { region_key: 'fixed_brand_second', read_only: true, animation_json: { loop: { preset: 'pulse', duration_ms: 200, iterations: 1 } } }
@@ -432,6 +438,7 @@ test('slide advanced popup applies changes, cancels drafts and preserves the par
animationPreviewButtons[1].handlers.click(); animationPreviewButtons[1].handlers.click();
assert.equal(readonlyPreviewRegion.style.values['animation-delay'], '0ms'); assert.equal(readonlyPreviewRegion.style.values['animation-delay'], '0ms');
animationPreviewButtons[1].handlers.click(); animationPreviewButtons[1].handlers.click();
assert.equal(previewTimers[previewTimers.length - 1], 580);
const resetRegion = ids['slide-animation-reset-region']; const resetRegion = ids['slide-animation-reset-region'];
const resetAll = ids['slide-animation-reset-all']; const resetAll = ids['slide-animation-reset-all'];
assert.equal(resetRegion.disabled, true); assert.equal(resetRegion.disabled, true);