Skip to content

Commit

Permalink
[MWPW-153363] Countdown Timer implementation based on page metadata (#…
Browse files Browse the repository at this point in the history
…2928)

* CDT Approach based on metadata

* Add unit tests for CDT

* self code review

* removed comments

* Add support for CDT in media block

* Code Coverage fix

* code coverage

* code coverage

* Handling Review Comments

* Review comments

* add unit test for code coverage

* added cdt feature tests

* increase code coverage

* NIT for one line coverage

* review comments for parallel load

* code coverage

* Fix UTC

* Support instant query param

* Fix code coverage
  • Loading branch information
rahulgupta999 authored Sep 23, 2024
1 parent 7d7eb63 commit c5e352f
Show file tree
Hide file tree
Showing 13 changed files with 351 additions and 9 deletions.
6 changes: 6 additions & 0 deletions libs/blocks/hero-marquee/hero-marquee.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {
decorateTextOverrides,
decorateButtons,
handleObjectFit,
loadCDT,
} from '../../utils/decorate.js';
import { createTag, loadStyle, getConfig } from '../../utils/utils.js';

Expand Down Expand Up @@ -259,5 +260,10 @@ export default async function init(el) {
}
});
decorateTextOverrides(el, ['-heading', '-body', '-detail'], mainCopy);

if (el.classList.contains('countdown-timer')) {
promiseArr.push(loadCDT(copy, el.classList));
}

await Promise.all(promiseArr);
}
12 changes: 10 additions & 2 deletions libs/blocks/marquee/marquee.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
* Marquee - v6.0
*/

import { decorateButtons, getBlockSize, decorateBlockBg } from '../../utils/decorate.js';
import { decorateButtons, getBlockSize, decorateBlockBg, loadCDT } from '../../utils/decorate.js';
import { createTag, getConfig, loadStyle } from '../../utils/utils.js';

// [headingSize, bodySize, detailSize]
Expand Down Expand Up @@ -133,7 +133,15 @@ export default async function init(el) {
if (iconArea?.childElementCount > 1) decorateMultipleIconArea(iconArea);
extendButtonsClass(text);
if (el.classList.contains('split')) decorateSplit(el, foreground, media);

const promiseArr = [];
if (el.classList.contains('mnemonic-list') && foreground) {
await loadMnemonicList(foreground);
promiseArr.push(loadMnemonicList(foreground));
}

if (el.classList.contains('countdown-timer')) {
promiseArr.push(loadCDT(text, el.classList));
}

await Promise.all(promiseArr);
}
15 changes: 13 additions & 2 deletions libs/blocks/media/media.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,13 @@
/* media - consonant v6 */

import { decorateBlockBg, decorateBlockText, getBlockSize, decorateTextOverrides, applyHoverPlay } from '../../utils/decorate.js';
import {
decorateBlockBg,
decorateBlockText,
getBlockSize,
decorateTextOverrides,
applyHoverPlay,
loadCDT,
} from '../../utils/decorate.js';
import { createTag, loadStyle, getConfig } from '../../utils/utils.js';

const blockTypeSizes = {
Expand Down Expand Up @@ -33,7 +40,7 @@ function decorateQr(el) {
qrImage.classList.add('qr-code-img');
}

export default function init(el) {
export default async function init(el) {
if (el.className.includes('rounded-corners')) {
const { miloLibs, codeRoot } = getConfig();
const base = miloLibs || codeRoot;
Expand Down Expand Up @@ -105,4 +112,8 @@ export default function init(el) {
const mediaRowReversed = el.querySelector(':scope > .foreground > .media-row > div').classList.contains('text');
if (mediaRowReversed) el.classList.add('media-reverse-mobile');
decorateTextOverrides(el);

if (el.classList.contains('countdown-timer')) {
await loadCDT(container, el.classList);
}
}
86 changes: 86 additions & 0 deletions libs/features/cdt/cdt.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
.horizontal,
.vertical {
display: flex;
padding: 20px 0;
}

.vertical {
flex-direction: column;
}

.center {
align-items: center;
justify-content: center;
}

.timer-label {
font-size: var(--type-body-s-size);
font-weight: 700;
height: 27px;
}

.light .timer-label {
color: #000;
}

.dark .timer-label {
color: #FFF;
}

.horizontal .timer-label {
margin: 0 2px 27px;
}

.timer-block {
display: flex;
}

.horizontal .timer-block {
margin-left: 10px;
}

.timer-fragment {
display: flex;
flex-direction: column;
align-items: center;
}

.timer-box {
padding: 0 9px;
width: 10px;
border-radius: 5px;
font-size: var(--type-body-m-size);
font-weight: 700;
text-align: center;
}

.light .timer-box {
background-color: #222;
color: #FFF;
}

.dark .timer-box {
background-color: #EBEBEB;
color: #1D1D1D;
}

.timer-unit-container {
display: flex;
column-gap: 2px;
align-items: center;
}

.timer-unit-label {
width: 100%;
font-size: var(--type-body-xs-size);
font-weight: 400;
text-align: start;
}

.light .timer-unit-label {
color: #464646;
}

.dark .timer-unit-label {
color: #D1D1D1;
}
121 changes: 121 additions & 0 deletions libs/features/cdt/cdt.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
import { getMetadata, getConfig, createTag } from '../../utils/utils.js';
import { replaceKey } from '../placeholders.js';

function loadCountdownTimer(
container,
cdtLabel,
cdtDays,
cdtHours,
cdtMins,
timeRangesEpoch,
) {
let isVisible = false;
let interval;

function appendTimerBox(parent, value, label) {
const fragment = createTag('div', { class: 'timer-fragment' }, null, { parent });
const unitContainer = createTag('div', { class: 'timer-unit-container' }, null, { parent: fragment });
createTag('div', { class: 'timer-unit-label' }, label, { parent: fragment });

createTag('div', { class: 'timer-box' }, Math.floor(value / 10).toString(), { parent: unitContainer });
createTag('div', { class: 'timer-box' }, (value % 10).toString(), { parent: unitContainer });
}

function appendSeparator(parent) {
createTag('div', { class: 'timer-separator' }, ':', { parent });
}

function appendTimerBlock(parent, daysLeft, hoursLeft, minutesLeft) {
const timerBlock = createTag('div', { class: 'timer-block' }, null, { parent });
appendTimerBox(timerBlock, daysLeft, cdtDays);
appendSeparator(timerBlock);
appendTimerBox(timerBlock, hoursLeft, cdtHours);
appendSeparator(timerBlock);
appendTimerBox(timerBlock, minutesLeft, cdtMins);
}

function appendTimerLabel(parent, label) {
createTag('div', { class: 'timer-label' }, label, { parent });
}

function removeCountdown() {
container.replaceChildren();
}

function render(daysLeft, hoursLeft, minutesLeft) {
if (!isVisible) return;

removeCountdown();

appendTimerLabel(container, cdtLabel);
appendTimerBlock(container, daysLeft, hoursLeft, minutesLeft);
}

function updateCountdown() {
const instant = new URL(window.location.href)?.searchParams?.get('instant');
const currentTime = instant ? new Date(instant) : Date.now();

for (let i = 0; i < timeRangesEpoch.length; i += 2) {
const startTime = timeRangesEpoch[i];
const endTime = timeRangesEpoch[i + 1];

if (currentTime >= startTime && currentTime <= endTime) {
isVisible = true;
const diffTime = endTime - currentTime;
const daysLeft = Math.floor(diffTime / (1000 * 60 * 60 * 24));
const hoursLeft = Math.floor((diffTime % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60));
const minutesLeft = Math.floor((diffTime % (1000 * 60 * 60)) / (1000 * 60));
render(daysLeft, hoursLeft, minutesLeft);
return;
}
}

isVisible = false;
clearInterval(interval);
removeCountdown();
}

function startCountdown() {
const oneMinuteinMs = 60000;
updateCountdown();
interval = setInterval(updateCountdown, oneMinuteinMs);
}

startCountdown();
}

function isMobile() {
return window.matchMedia('(max-width: 767px)').matches;
}

export default async function initCDT(el, classList) {
const placeholders = ['cdt-ends-in', 'cdt-days', 'cdt-hours', 'cdt-mins'];
const [cdtLabel, cdtDays, cdtHours, cdtMins] = await Promise.all(
placeholders.map((placeholder) => replaceKey(placeholder, getConfig())),
);

const cdtMetadata = getMetadata('countdown-timer');
if (cdtMetadata === null) {
throw new Error('Metadata for countdown-timer is not available');
}

const cdtRange = cdtMetadata.split(',');
if (cdtRange.length % 2 !== 0) {
throw new Error('Invalid countdown timer range');
}

const timeRangesEpoch = cdtRange.map((time) => {
const parsedTime = Date.parse(time?.trim());
return Number.isNaN(parsedTime) ? null : parsedTime;
});
if (timeRangesEpoch.includes(null)) {
throw new Error('Invalid format for countdown timer range');
}

const cdtDiv = createTag('div', { class: 'countdown-timer' }, null, { parent: el });
cdtDiv.classList.add(isMobile() ? 'vertical' : 'horizontal');
cdtDiv.classList.add(classList.contains('dark') ? 'dark' : 'light');
if (classList.contains('center')) cdtDiv.classList.add('center');

loadCountdownTimer(cdtDiv, cdtLabel, cdtDays, cdtHours, cdtMins, timeRangesEpoch);
}
16 changes: 15 additions & 1 deletion libs/utils/decorate.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import { createTag } from './utils.js';
import { createTag, loadStyle, getConfig } from './utils.js';

const { miloLibs, codeRoot } = getConfig();

export function decorateButtons(el, size) {
const buttons = el.querySelectorAll('em a, strong a, p > a strong');
Expand Down Expand Up @@ -309,3 +311,15 @@ export function decorateMultiViewport(el) {
}
return foreground;
}

export async function loadCDT(el, classList) {
try {
await Promise.all([
loadStyle(`${miloLibs || codeRoot}/features/cdt/cdt.css`),
import('../features/cdt/cdt.js')
.then(({ default: initCDT }) => initCDT(el, classList)),
]);
} catch (error) {
window.lana?.log(`Failed to load countdown timer module: ${error}`, { tags: 'countdown-timer' });
}
}
2 changes: 1 addition & 1 deletion test/blocks/hero-marquee/mocks/body.html
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ <h1 id="row-cell---text-right-2">2-Row Cell - text right</h1>
</div>
</div>

<div id="hero-w-adobetv" class="hero-marquee">
<div id="hero-w-adobetv" class="hero-marquee countdown-timer">
<div>
<div>#ffffff</div>
</div>
Expand Down
2 changes: 1 addition & 1 deletion test/blocks/marquee/mocks/body.html
Original file line number Diff line number Diff line change
Expand Up @@ -184,7 +184,7 @@ <h2 id="marquee-inline">Marquee inline</h2>

<h2>Marquee (split)</h2>
<p>small</p>
<div class="marquee split small" id="media-credit-text">
<div class="marquee split small countdown-timer" id="media-credit-text">
<div>
<div>#000000</div>
</div>
Expand Down
11 changes: 10 additions & 1 deletion test/blocks/media/media.test.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,13 @@
import { readFile, setViewport } from '@web/test-runner-commands';
import { expect } from '@esm-bundle/chai';
import { setConfig, getConfig } from '../../../libs/utils/utils.js';

document.head.innerHTML = "<link rel='stylesheet' href='../../../libs/blocks/media/media.css'>";
const locales = { '': { ietf: 'en-US', tk: 'hah7vzn.css' } };
const conf = { locales, miloLibs: 'http://localhost:2000/libs' };
setConfig(conf);
getConfig().locale.contentRoot = '/test/blocks/media/mocks';

document.head.innerHTML = '<link rel="stylesheet" href="../../../libs/blocks/media/media.css"><meta name="countdown-timer" content="2024-08-26 12:00:00 PST,2026-08-30 00:00:00 PST">';
document.body.innerHTML = await readFile({ path: './mocks/body.html' });
const { default: init } = await import('../../../libs/blocks/media/media.js');
describe('media', () => {
Expand Down Expand Up @@ -132,5 +138,8 @@ describe('media', () => {
const detail = medias[8].querySelector('.detail-l');
expect(detail).to.exist;
});
it('has a cdt', () => {
expect(medias[8].querySelectorAll('.timer-label')).to.have.lengthOf(1);
});
});
});
2 changes: 1 addition & 1 deletion test/blocks/media/mocks/body.html
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,7 @@ <h2 id="heading-xs-1822-media-merch-small"><strong>Heading XS 18/22 Media (merch
</div>
</div>
</div>
<div class="media medium-compact">
<div class="media medium-compact countdown-timer">
<div>
<div data-valign="middle">
<picture>
Expand Down
24 changes: 24 additions & 0 deletions test/blocks/media/mocks/placeholders.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
{
"total": 21,
"offset": 0,
"limit": 21,
"data": [
{
"key": "cdt-ends-in",
"value": "ENDS IN"
},
{
"key": "cdt-days",
"value": "days"
},
{
"key": "cdt-hours",
"value": "hours"
},
{
"key": "cdt-mins",
"value": "mins"
}
],
":type": "sheet"
}
Loading

0 comments on commit c5e352f

Please sign in to comment.