Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
29 changes: 28 additions & 1 deletion src/util/campaignHouseKeeping/commonCampaignUtils.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { renderAdvancedBuilder, renderPopUpImageOnly } from '../campaignRender/webPopup.js'
import { renderAdvancedBuilder, renderPopUpImageOnly, renderPIP } from '../campaignRender/webPopup.js'
import {
addDeliveryPreferenceDetails,
addToLocalProfileMap,
Expand Down Expand Up @@ -26,6 +26,7 @@ import { getNow, getToday } from '../datetime.js'
import { StorageManager, $ct } from '../storage.js'
import RequestDispatcher from '../requestDispatcher.js'
import { CTWebPopupImageOnly } from '../web-popupImageonly/popupImageonly.js'
import { CTWebPopupPIP } from '../web-popupPIP/popupPIP.js'
import {
checkAndRegisterWebInboxElements,
initializeWebInbox,
Expand Down Expand Up @@ -435,6 +436,28 @@ export const commonCampaignUtils = {
return renderPopUpImageOnly(targetingMsgJson, CampaignContext.session)
},

handlePIP (targetingMsgJson) {
const divId = 'wizPIPDiv'
// Skips if frequency limits are exceeded
if (this.doCampHouseKeeping(targetingMsgJson, Logger.getInstance()) === false) {
return
}
// Removes existing popup if spam control is active
if ($ct.dismissSpamControl && document.getElementById(divId) != null) {
const element = document.getElementById(divId)
element.remove()
}
const msgDiv = document.createElement('div')
msgDiv.id = divId
document.body.appendChild(msgDiv)
// Registers custom element for PIP if not already defined
if (customElements.get('ct-web-popup-pip') === undefined) {
customElements.define('ct-web-popup-pip', CTWebPopupPIP)
}
// Renders the PIP
return renderPIP(targetingMsgJson, CampaignContext.session)
},

// Checks if a campaign is already rendered in an iframe
isExistingCampaign (campaignId) {
const testIframe =
Expand Down Expand Up @@ -463,6 +486,10 @@ export const commonCampaignUtils = {
this.handleImageOnlyPopup(targetingMsgJson)
return
}
if (displayObj.layout === WEB_POPUP_TEMPLATES.PIP) {
this.handlePIP(targetingMsgJson)
return
}

// Skips if frequency limits are exceeded
if (this.doCampHouseKeeping(targetingMsgJson, Logger.getInstance()) === false) {
Expand Down
11 changes: 11 additions & 0 deletions src/util/campaignRender/webPopup.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,17 @@ export const renderPopUpImageOnly = (targetingMsgJson, _session) => {
containerEl.appendChild(popupImageOnly)
}

export const renderPIP = (targetingMsgJson, _session) => {
const divId = 'wizPIPDiv'
const pip = document.createElement('ct-web-popup-pip')
pip.session = _session
pip.target = targetingMsgJson
const containerEl = document.getElementById(divId)
containerEl.innerHTML = ''
containerEl.style.visibility = 'hidden'
containerEl.appendChild(pip)
}

const FULLSCREEN_STYLE = `
z-index: 2147483647;
display: block;
Expand Down
15 changes: 14 additions & 1 deletion src/util/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -104,9 +104,22 @@ export const WEB_POPUP_TEMPLATES = {
INTERSTITIAL: 1,
BANNER: 2,
IMAGE_ONLY: 3,
ADVANCED_BUILDER: 4
ADVANCED_BUILDER: 4,
PIP: 5
}

/** Inner HTML for `#ct-pip-play svg` when video is playing (`msgContent.html` only ships play). Path uses viewBox 0 0 48 48. */
export const PIP_PAUSE_ICON_SVG =
'<path d="M21 14C21.5523 14 22 14.4477 22 15V33C22 33.5523 21.5523 34 21 34H19C18.4477 34 18 33.5523 18 33V15C18 14.4477 18.4477 14 19 14H21ZM29 14C29.5523 14 30 14.4477 30 15V33C30 33.5523 29.5523 34 29 34H27C26.4477 34 26 33.5523 26 33V15C26 14.4477 26.4477 14 27 14H29Z" fill="currentColor"></path>'

/** Inner HTML for `#ct-pip-expand svg` when PIP is fullscreen (collapse control). Path uses viewBox 0 0 48 48. */
export const PIP_COLLAPSE_ICON_SVG =
'<path fill-rule="evenodd" clip-rule="evenodd" d="M25.5273 27.5285C25.5274 26.4241 26.4229 25.5285 27.5273 25.5285L32.5263 25.5285C33.0786 25.5285 33.5263 25.9763 33.5263 26.5285C33.5262 27.0807 33.0785 27.5285 32.5263 27.5285L28.9375 27.5285L34.1748 32.7658C34.5652 33.1563 34.5652 33.7894 34.1748 34.1799C33.7843 34.5702 33.1512 34.5703 32.7607 34.1799L27.5273 28.9465L27.5273 32.5275C27.5272 33.0797 27.0796 33.5275 26.5273 33.5275C25.9751 33.5275 25.5274 33.0797 25.5273 32.5275L25.5273 27.5285ZM13.8252 15.2346C13.4349 14.8441 13.4349 14.211 13.8252 13.8205C14.2157 13.4302 14.8488 13.4302 15.2392 13.8205L20.4726 19.0539L20.4726 15.4728C20.4728 14.9207 20.9204 14.4729 21.4726 14.4728C22.0248 14.4728 22.4725 14.9207 22.4726 15.4728L22.4726 20.4719C22.4724 21.5761 21.577 22.4719 20.4726 22.4719L15.4736 22.4719C14.9215 22.4718 14.4739 22.0239 14.4736 21.4719C14.4738 20.9198 14.9215 20.4719 15.4736 20.4719L19.0625 20.4719L13.8252 15.2346Z" fill="currentColor"></path>'

/** Inner HTML for `#ct-pip-mute svg` when video is muted (unmute action). Path uses viewBox 0 0 48 48; mute glyph comes from `msgContent.html`. */
export const PIP_UNMUTE_ICON_SVG =
'<path fill-rule="evenodd" clip-rule="evenodd" d="M24.7349 14.1514C25.2641 13.7718 26.0013 14.1496 26.0015 14.8008V33.1973C26.0015 33.8487 25.2642 34.2273 24.7349 33.8477L16.7905 28.1504C16.6547 28.0531 16.4918 28.0011 16.3247 28.001H14.061C13.6194 28.0009 13.2615 27.6427 13.2612 27.2012V20.8008C13.2612 20.359 13.6193 20.0011 14.061 20.001H16.3208C16.4879 20.0009 16.6508 19.948 16.7866 19.8506L24.7349 14.1514ZM28.8325 16.5371C29.0285 16.066 29.5723 15.839 30.021 16.082C31.3075 16.7788 32.4094 17.7808 33.2261 19.0059C34.2142 20.4882 34.7407 22.2303 34.7388 24.0117C34.7369 25.7932 34.2067 27.5345 33.2153 29.0147C32.3961 30.2378 31.2922 31.2377 30.0044 31.9317C29.5552 32.1737 29.0119 31.9451 28.8169 31.4736C28.6221 31.002 28.8498 30.4659 29.2925 30.2119C30.2453 29.6655 31.0634 28.9048 31.6792 27.9854C32.467 26.809 32.8886 25.4256 32.8901 24.0098C32.8917 22.5939 32.4734 21.2094 31.688 20.0313C31.0742 19.1105 30.2577 18.3483 29.3062 17.7998C28.864 17.545 28.6367 17.0084 28.8325 16.5371ZM27.7212 19.377C27.9319 18.8703 28.5198 18.6238 28.9849 18.9151C29.7019 19.364 30.319 19.9618 30.7925 20.6719C31.4507 21.6593 31.8016 22.8202 31.8003 24.0068C31.799 25.1934 31.4459 26.3529 30.7856 27.3389C30.3106 28.0482 29.6914 28.6452 28.9731 29.0928C28.5075 29.3826 27.921 29.1348 27.7114 28.6279C27.5019 28.1206 27.7543 27.5482 28.1948 27.2207C28.5601 26.9491 28.8783 26.6156 29.1343 26.2334C29.5758 25.5741 29.8121 24.7984 29.813 24.0049C29.8138 23.2113 29.5793 22.4347 29.1392 21.7744C28.8841 21.3919 28.5662 21.0585 28.2017 20.7861C27.7619 20.4577 27.5106 19.8838 27.7212 19.377Z" fill="currentColor"></path>'

export const CAMPAIGN_TYPES = {
EXIT_INTENT: 1, /* Deprecated */
WEB_NATIVE_DISPLAY: 2,
Expand Down
181 changes: 181 additions & 0 deletions src/util/web-popupPIP/pipPopupUtils.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
import { ACTION_TYPES } from '../constants'
import { invokeExternalJs } from '../campaignRender/utilities'

export const PIP_DRAG_CONTROL_SELECTOR =
'#ct-pip-close, #ct-pip-expand, #ct-pip-play, #ct-pip-mute'

/** Fullscreen expand: letterbox video with native aspect ratio (object-fit: contain). */
export const PIP_EXPAND_RUNTIME_CSS = `
.ct-pip-overlay.ct-pip--expanded {
align-items: center !important;
justify-content: center !important;
padding: 0 !important;
pointer-events: auto !important;
background: rgba(0, 0, 0, 0.88);
}
.ct-pip-overlay.ct-pip--expanded .ct-pip-container {
position: relative !important;
max-width: none !important;
width: 100vw !important;
height: 100vh !important;
display: flex !important;
align-items: center !important;
justify-content: center !important;
border-radius: 0 !important;
}
.ct-pip-overlay.ct-pip--expanded .ct-pip-media {
width: auto !important;
height: auto !important;
max-width: 100vw !important;
max-height: 100vh !important;
object-fit: contain !important;
}
`

/** Nearest anchor id -> flex placement on `.ct-pip-overlay` (row: justify = horizontal, align = vertical). */
export const PIP_ANCHOR_FLEX = {
center: { alignItems: 'center', justifyContent: 'center' },
'top-right': { alignItems: 'flex-start', justifyContent: 'flex-end' },
'top-left': { alignItems: 'flex-start', justifyContent: 'flex-start' },
'bottom-right': { alignItems: 'flex-end', justifyContent: 'flex-end' },
'bottom-left': { alignItems: 'flex-end', justifyContent: 'flex-start' },
top: { alignItems: 'flex-start', justifyContent: 'center' },
bottom: { alignItems: 'flex-end', justifyContent: 'center' },
left: { alignItems: 'center', justifyContent: 'flex-start' },
right: { alignItems: 'center', justifyContent: 'flex-end' }
}

/**
* `display.pip.onClick` / `display.mobile.pip.onClick` (nested payload).
* @param {Record<string, unknown>} pipConfig — result of desktop/mobile `pip` slice
* @returns {Record<string, unknown>}
*/
export function getPipOnClickConfig (pipConfig) {
const oc = pipConfig?.onClick
return oc && typeof oc === 'object' ? oc : {}
}

/** @param {Record<string, unknown>} pipConfig */
export function getPipOnClickAction (pipConfig) {
const t = getPipOnClickConfig(pipConfig).type
if (t != null) {
const s = String(t).trim()
if (s !== '') return s
}
const legacy = pipConfig?.onClickAction
return legacy != null && legacy !== '' ? legacy : ''
}

/** @param {Record<string, unknown>} pipConfig */
export function getPipOnClickUrl (pipConfig, display) {
const oc = getPipOnClickConfig(pipConfig)
return (
oc.webUrl ||
oc.url ||
pipConfig?.onClickUrl ||
display?.onClickUrl ||
''
)
}

/** Used only for {@link ACTION_TYPES.OPEN_LINK} (`url`). */
export function getPipOpenLinkUsesNewTab (pipConfig, display) {
if (typeof pipConfig?.window === 'boolean') return pipConfig.window
return !!display?.window
}

/** @param {Record<string, unknown>} pipConfig */
export function getPipOnClickJsName (pipConfig) {
const oc = getPipOnClickConfig(pipConfig)
if (typeof oc.js === 'string' && oc.js) return oc.js
if (typeof oc.jsName === 'string' && oc.jsName) return oc.jsName
if (oc.js && typeof oc.js === 'object' && oc.js?.name) return oc.js.name
return pipConfig?.onClickJs || ''
}

export function buildPipNotificationClickedPayload (msgId, pivotId, pipConfig) {
const oc = getPipOnClickConfig(pipConfig)
const kv = oc.kv
const payload = { msgId, pivotId }
if (kv && typeof kv === 'object' && Object.keys(kv).length > 0) {
payload.kv = kv
}
return payload
}

function navigateOpenWebUrl (url, oc, closeTemplate) {
const openInNewTab = oc.openInNewTab === true
const closeOnClick = oc.closeOnClick === true
if (openInNewTab) {
window.open(url, '_blank', 'noopener')
if (closeOnClick) closeTemplate()
} else {
if (closeOnClick) closeTemplate()
window.location.href = url
}
}

/**
* @param {object} params
* @param {Record<string, unknown>} params.pipConfig — `getPipDisplayConfig()` slice
* @param {Record<string, unknown>} params.display — `target.display`
* @param {Record<string, unknown>} params.targetingMsgJson — campaign / `target`
* @param {boolean} [params.preview]
* @param {() => void} params.closeTemplate
*/
export function runPipClickAction ({
pipConfig,
display,
targetingMsgJson,
preview,
closeTemplate
}) {
const action = getPipOnClickAction(pipConfig)
if (!action) return

const fireClicked = () => {
if (!preview) {
window.clevertap.renderNotificationClicked(
buildPipNotificationClickedPayload(
targetingMsgJson.wzrk_id,
targetingMsgJson.wzrk_pivot,
pipConfig
)
)
}
}

switch (action) {
case ACTION_TYPES.OPEN_LINK: {
const url = getPipOnClickUrl(pipConfig, display)
if (!url) return
fireClicked()
if (getPipOpenLinkUsesNewTab(pipConfig, display)) {
window.open(url, '_blank', 'noopener')
} else {
window.parent.location.href = url
}
break
}
case ACTION_TYPES.OPEN_WEB_URL: {
const url = getPipOnClickUrl(pipConfig, display)
if (!url) return
fireClicked()
navigateOpenWebUrl(url, getPipOnClickConfig(pipConfig), closeTemplate)
break
}
case ACTION_TYPES.SOFT_PROMPT:
fireClicked()
window.clevertap.notifications.push({ skipDialog: true })
break
case ACTION_TYPES.RUN_JS: {
const jsName = getPipOnClickJsName(pipConfig)
if (!jsName) return
fireClicked()
invokeExternalJs(jsName, targetingMsgJson)
break
}
default:
break
}
}
Loading
Loading