Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[MWPW-153363] Countdown Timer implementation based on page metadata #2928

Merged
merged 19 commits into from
Sep 23, 2024
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@ -259,5 +259,11 @@ export default async function init(el) {
}
});
decorateTextOverrides(el, ['-heading', '-body', '-detail'], mainCopy);

if (el.classList.contains('countdown-timer')) {
rahulgupta999 marked this conversation as resolved.
Show resolved Hide resolved
const { default: initCDT } = await import('../../features/cdt/cdt.js');
await initCDT(copy, el.classList);
}

await Promise.all(promiseArr);
}
5 changes: 5 additions & 0 deletions libs/blocks/marquee/marquee.js
Original file line number Diff line number Diff line change
Expand Up @@ -136,4 +136,9 @@ export default async function init(el) {
if (el.classList.contains('mnemonic-list') && foreground) {
await loadMnemonicList(foreground);
rahulgupta999 marked this conversation as resolved.
Show resolved Hide resolved
}

if (el.classList.contains('countdown-timer')) {
const { default: initCDT } = await import('../../features/cdt/cdt.js');
await initCDT(text, el.classList);
}
}
7 changes: 6 additions & 1 deletion libs/blocks/media/media.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,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 +105,9 @@ 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')) {
const { default: initCDT } = await import('../../features/cdt/cdt.js');
await initCDT(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 {
rahulgupta999 marked this conversation as resolved.
Show resolved Hide resolved
color: #000;
}

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

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

.timer-block {
display: flex;
}

.horizontal .timer-block {
margin-left: 10px;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what about RTL support ? In RTL there's currently no space between text and timer
Screenshot 2024-09-23 at 15 20 04

}

.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;
}
134 changes: 134 additions & 0 deletions libs/features/cdt/cdt.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
import { getMetadata, getConfig, loadStyle, createTag } from '../../utils/utils.js';
import { replaceKey } from '../placeholders.js';

const replacePlaceholder = async (key) => replaceKey(key, getConfig());
rahulgupta999 marked this conversation as resolved.
Show resolved Hide resolved

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

function createTimerBox(value) {
const unitContainer = createTag('div', { class: 'timer-unit-container' });
const tensBox = createTag('div', { class: 'timer-box' }, Math.floor(value / 10).toString());
const onesBox = createTag('div', { class: 'timer-box' }, (value % 10).toString());

unitContainer.appendChild(tensBox);
unitContainer.appendChild(onesBox);
return unitContainer;
}

function createTimerFragment(value, label) {
const fragment = createTag('div', { class: 'timer-fragment' });
const unitContainer = createTimerBox(value);
const unitLabel = createTag('div', { class: 'timer-unit-label' }, label);

fragment.appendChild(unitContainer);
fragment.appendChild(unitLabel);
return fragment;
}

function appendLabel(parent, label) {
const labelElement = createTag('div', { class: 'timer-label' }, label);
parent.appendChild(labelElement);
}

function appendTimerBlock(parent) {
const timerBlock = createTag('div', { class: 'timer-block' });
parent.appendChild(timerBlock);
return timerBlock;
}

function appendTimerFragment(parent, timeValue, label) {
const fragment = createTimerFragment(timeValue, label);
parent.appendChild(fragment);
}

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

function removeCountdown() {
container.innerHTML = '';
rahulgupta999 marked this conversation as resolved.
Show resolved Hide resolved
}

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

removeCountdown();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why always remove the countdown ? Can you not just update the timer label?


appendLabel(container, cdtLabel);
const timerBlock = appendTimerBlock(container);

appendTimerFragment(timerBlock, daysLeft, cdtDays);
appendSeparator(timerBlock);
appendTimerFragment(timerBlock, hoursLeft, cdtHours);
appendSeparator(timerBlock);
appendTimerFragment(timerBlock, minutesLeft, cdtMins);
}

function updateCountdown() {
const currentTime = Date.now();
rahulgupta999 marked this conversation as resolved.
Show resolved Hide resolved

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();

Check warning on line 97 in libs/features/cdt/cdt.js

View check run for this annotation

Codecov / codecov/patch

libs/features/cdt/cdt.js#L94-L97

Added lines #L94 - L97 were not covered by tests
}

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

startCountdown();
}

const isMobileDevice = () => /Android|webOS|iPhone|iPad|iPod/i.test(navigator.userAgent);
rahulgupta999 marked this conversation as resolved.
Show resolved Hide resolved

export default async function initCDT(el, classList) {
try {
const { miloLibs, codeRoot } = getConfig();
loadStyle(`${miloLibs || codeRoot}/features/cdt/cdt.css`);
rahulgupta999 marked this conversation as resolved.
Show resolved Hide resolved

const placeholders = ['cdt-ends-in', 'cdt-days', 'cdt-hours', 'cdt-mins'];
const [cdtLabel, cdtDays, cdtHours, cdtMins] = await Promise.all(
placeholders.map(replacePlaceholder),
);

const cdtRange = (getMetadata('countdown-timer')).split(',');
const timeRangesEpoch = cdtRange.map((time) => Date.parse(time?.trim()));

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

loadCountdownTimer(cdtDiv, cdtLabel, cdtDays, cdtHours, cdtMins, timeRangesEpoch);
} catch (error) {
window.lana?.log(`Failed to load countdown timer module: ${error}`, { tags: 'countdown-timer' });
rahulgupta999 marked this conversation as resolved.
Show resolved Hide resolved
}
}
7 changes: 7 additions & 0 deletions test/blocks/hero-marquee/hero-marquee.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ setConfig(conf);
describe('Hero Marquee', () => {
before(async () => {
document.body.innerHTML = await readFile({ path: './mocks/body.html' });
const meta = Object.assign(document.createElement('meta'), { name: 'countdown-timer', content: '2024-08-26 12:00:00 PST,2026-08-30 00:00:00 PST' });
document.head.appendChild(meta);
const { default: init } = await import('../../../libs/blocks/hero-marquee/hero-marquee.js');
const marquees = document.querySelectorAll('.hero-marquee');
marquees.forEach(async (marquee) => {
Expand All @@ -35,4 +37,9 @@ describe('Hero Marquee', () => {
const hr = await waitForElement('.has-divider');
expect(hr).to.exist;
});

it('Embedding countdown-timer inside hero-marquee', async () => {
const marquee = document.getElementById('hero-cdt');
expect(marquee.getElementsByClassName('timer-label')).to.exist;
});
});
41 changes: 41 additions & 0 deletions test/blocks/hero-marquee/mocks/body.html
Original file line number Diff line number Diff line change
Expand Up @@ -117,3 +117,44 @@ <h1 id="row-cell---text-right-2">Hero w/ Adobe.tv link</h1>
<div><a href="https://video.tv.adobe.com/v/3427744">https://video.tv.adobe.com/v/3427744</a></div>
</div>
</div>

<div id="hero-cdt" class="hero-marquee xxxl-heading countdown-timer">
<div>
<div>
<picture>
<img loading="lazy" alt="" src="./" width="100" height="100">
</picture>
</div>
</div>
<div>
<div>
<p><picture><img loading="lazy" src="./"></picture> After Effects</p>
<p>DETAIL TEXT</p>
<h2 id="this-hero-has-all-row-types">This Hero has all row types</h2>
<p>lockup, list, qrcode, text, background</p>
<p><em><a href="#the-bg-image-is-on-top-for-mobiletablet"><span class="icon icon-checkmark-circle"></span>See more</a></em> <strong><a href="#bg-mobile">Other options you say?</a></strong></p>
</div>
</div>
<div>
<div data-valign="middle">con-block-row-list (max-width-6-tablet)</div>
<div>
<ul>
<li><span class="icon icon-checkmark"></span>Small</li>
<li><span class="icon icon-checkmark"></span>Medium length text</li>
<li><span class="icon icon-checkmark"></span>Long length text that may break onto a new line, what will happen, keep it going so this is even longer and really wraps?</li>
<li><span class="icon icon-checkmark"></span>Another list</li>
</ul>
</div>
</div>
<div>
<div>--- white</div>
</div>
<div>
<div>con-block-row-text (xs-body, m-button)</div>
<div>See plans for <a href="#teach">students and teachers</a> or <a href="#biz">small and medium business.</a></div>
</div>
<div>
<div>con-block-row-text (body-m)</div>
<div>Text with no button class</div>
</div>
</div>
24 changes: 24 additions & 0 deletions test/blocks/hero-marquee/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"
}
7 changes: 7 additions & 0 deletions test/blocks/marquee/marquee.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ const video = await readFile({ path: './mocks/video.html' });
const multipleIcons = await readFile({ path: './mocks/multiple-icons.html' });

describe('marquee', () => {
const meta = Object.assign(document.createElement('meta'), { name: 'countdown-timer', content: '2024-08-26 12:00:00 PST,2026-08-30 00:00:00 PST' });
document.head.appendChild(meta);
const marquees = document.querySelectorAll('.marquee');
marquees.forEach((marquee) => {
init(marquee);
Expand Down Expand Up @@ -163,4 +165,9 @@ describe('marquee', () => {
expect(log.calledOnceWith(`Failed to load mnemonic marquee module: ${error}`)).to.be.false;
});
});

describe('Embedding countdown-timer inside marquee', () => {
const marquee = document.getElementById('countdown-timer');
expect(marquee.getElementsByClassName('timer-label')).to.exist;
});
});
14 changes: 14 additions & 0 deletions test/blocks/marquee/mocks/body.html
Original file line number Diff line number Diff line change
Expand Up @@ -381,3 +381,17 @@ <h2>Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod temp
</div>
</div>
</div>
<h2>Catalog Marquee with Countdown timer</h2>
<div class="marquee quiet light center countdown-timer" id="countdown-timer">
<div>
<div data-valign="middle">linear-gradient(90deg, rgba(226,106,96,1) 0%, rgba(228,170,166,1) 51%, rgba(163,243,120,1) 100%)</div>
</div>
<div>
<div data-valign="middle">
<h2>Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation.</h2>
<p>Body M Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation.</p>
<p><br><strong>Includes:</strong></p>
<p><picture><img loading="lazy" src="/assets/img/product-icons/svg/acrobat-pro-40.svg" alt="Acrobat Pro"></picture> <strong>Acrobat</strong></p>
</div>
</div>
</div>
Loading
Loading