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 8 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
9 changes: 9 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,14 @@ export default async function init(el) {
}
});
decorateTextOverrides(el, ['-heading', '-body', '-detail'], mainCopy);

if (el.classList.contains('countdown-timer')) {
const { default: initCDT } = await import('../../features/cdt/cdt.js');
const classesToAdd = [];
if (el.classList.contains('center')) classesToAdd.push('center');
classesToAdd.push(el.classList.contains('dark') ? 'dark' : 'light');
await initCDT(copy, classesToAdd);
}

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

if (el.classList.contains('countdown-timer')) {
const { default: initCDT } = await import('../../features/cdt/cdt.js');
const classesToAdd = [];
if (el.classList.contains('center')) classesToAdd.push('center');
classesToAdd.push(el.classList.contains('dark') ? 'dark' : 'light');
await initCDT(text, classesToAdd);
}
}
9 changes: 8 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,11 @@ 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');
const classesToAdd = [];
classesToAdd.push(el.classList.contains('dark') ? 'dark' : 'light');
await initCDT(container, classesToAdd);
}
}
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 45px;
}

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

const replacePlaceholder = async (key) => replaceKey(key, getConfig());

async function loadCountdownTimer(container) {
const cdtLabel = await replacePlaceholder('cdt-ends-in');
const cdtDays = await replacePlaceholder('cdt-days');
const cdtHours = await replacePlaceholder('cdt-hours');
const cdtMins = await replacePlaceholder('cdt-mins');
const cdtRange = (getMetadata('countdown-timer')).split(',');
const timeRangesEpoch = cdtRange.map((time) => Date.parse(time.trim()));
let isVisible = false;
let interval;

const { miloLibs, codeRoot } = getConfig();
await Promise.resolve(loadStyle(`${miloLibs || codeRoot}/features/cdt/cdt.css`));

function createTimerFragment(value, label) {
const fragment = document.createElement('div');
fragment.classList.add('timer-fragment');

const unitContainer = document.createElement('div');
unitContainer.classList.add('timer-unit-container');
fragment.appendChild(unitContainer);

const tensBox = document.createElement('div');
tensBox.classList.add('timer-box');
tensBox.textContent = Math.floor(value / 10);
unitContainer.appendChild(tensBox);

const onesBox = document.createElement('div');
onesBox.classList.add('timer-box');
onesBox.textContent = value % 10;
unitContainer.appendChild(onesBox);

const unitLabel = document.createElement('div');
unitLabel.classList.add('timer-unit-label');
unitLabel.textContent = label;
fragment.appendChild(unitLabel);

return fragment;
}

function removeCountdown() {
container.innerHTML = '';
}

function createSeparator() {
const separator = document.createElement('div');
separator.classList.add('timer-label');
separator.textContent = ':';
return separator;
}

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?


const labelElement = document.createElement('div');
labelElement.classList.add('timer-label');
labelElement.textContent = cdtLabel;
container.appendChild(labelElement);

const timerBlock = document.createElement('div');
timerBlock.classList.add('timer-block');
container.appendChild(timerBlock);

timerBlock.appendChild(createTimerFragment(daysLeft, cdtDays));
timerBlock.appendChild(createSeparator());
timerBlock.appendChild(createTimerFragment(hoursLeft, cdtHours));
timerBlock.appendChild(createSeparator());
timerBlock.appendChild(createTimerFragment(minutesLeft, cdtMins));
}

function updateCountdown() {
const currentTime = 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();
}

const isMobileDevice = () => /Android|webOS|iPhone|iPad|iPod/i.test(navigator.userAgent);

export default async function initCDT(el, classesToAdd) {
try {
const cdtDiv = document.createElement('div');
cdtDiv.classList.add('countdown-timer');
classesToAdd.forEach((className) => {
cdtDiv.classList.add(className);
});
cdtDiv.classList.add(isMobileDevice() ? 'vertical' : 'horizontal');
el.appendChild(cdtDiv);
await loadCountdownTimer(cdtDiv);
} catch (error) {
window.lana?.log(`Failed to load countdown timer module: ${error}`, { tags: 'countdown-timer' });
}
}
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