Skip to content
Open
Show file tree
Hide file tree
Changes from 5 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). */
export const PIP_PAUSE_ICON_SVG =
'<path d="M8.5 5C8.77614 5 9 5.22386 9 5.5V14.5C9 14.7761 8.77614 15 8.5 15H7.5C7.22386 15 7 14.7761 7 14.5V5.5C7 5.22386 7.22386 5 7.5 5H8.5ZM12.5 5C12.7761 5 13 5.22386 13 5.5V14.5C13 14.7761 12.7761 15 12.5 15H11.5C11.2239 15 11 14.7761 11 14.5V5.5C11 5.22386 11.2239 5 11.5 5H12.5Z" fill="currentColor"></path>'

/** Inner HTML for `#ct-pip-expand svg` when PIP is fullscreen (collapse control). */
export const PIP_COLLAPSE_ICON_SVG =
'<path d="M7.60059 12.0098C7.82855 12.0563 8 12.2583 8 12.5V14.5C8 14.7761 7.77614 15 7.5 15C7.22386 15 7 14.7761 7 14.5V13H5.5C5.22386 13 5 12.7761 5 12.5C5 12.2239 5.22386 12 5.5 12H7.5C7.53443 12 7.56811 12.0031 7.60059 12.0098ZM14.5 12C14.7761 12 15 12.2239 15 12.5C15 12.7761 14.7761 13 14.5 13H13V14.5C13 14.7761 12.7761 15 12.5 15C12.2239 15 12 14.7761 12 14.5V12.5C12 12.4656 12.0031 12.4319 12.0098 12.3994C12.0497 12.2039 12.2039 12.0497 12.3994 12.0098C12.4319 12.0031 12.4656 12 12.5 12H14.5ZM7.5 5C7.77614 5 8 5.22386 8 5.5V7.5C8 7.53443 7.99686 7.56811 7.99023 7.60059C7.95034 7.79608 7.79608 7.95034 7.60059 7.99023C7.56811 7.99686 7.53443 8 7.5 8H5.5C5.22386 8 5 7.77614 5 7.5C5 7.22386 5.22386 7 5.5 7H7V5.5C7 5.22386 7.22386 5 7.5 5ZM12.5 5C12.7761 5 13 5.22386 13 5.5V7H14.5C14.7761 7 15 7.22386 15 7.5C15 7.77614 14.7761 8 14.5 8H12.5C12.4656 8 12.4319 7.99686 12.3994 7.99023C12.2039 7.95034 12.0497 7.79608 12.0098 7.60059C12.0031 7.56811 12 7.53443 12 7.5V5.5C12 5.22386 12.2239 5 12.5 5Z" fill="currentColor"></path>'

/** Inner HTML for `#ct-pip-mute svg` when video is muted (unmute action). Mute glyph comes from `msgContent.html`. */
export const PIP_UNMUTE_ICON_SVG =
'<path fill-rule="evenodd" clip-rule="evenodd" d="M10.3677 5.07572C10.6322 4.88602 11.0012 5.0745 11.0015 5.39994V14.5982C11.0015 14.9239 10.6323 15.1132 10.3677 14.9234L6.39502 12.0738C6.32711 12.0251 6.24518 11.9996 6.16162 11.9995H5.03076C4.80989 11.9995 4.63037 11.82 4.63037 11.5992V8.39896C4.63053 8.17821 4.80999 7.9996 5.03076 7.99955H6.16162C6.24518 7.99951 6.32711 7.97305 6.39502 7.92435L10.3677 5.07572ZM12.4165 6.2681C12.5143 6.0326 12.7859 5.91844 13.0103 6.03959C13.6534 6.3879 14.2045 6.88914 14.6128 7.5015C15.1068 8.24257 15.3705 9.1138 15.3696 10.0044C15.3687 10.8952 15.1036 11.7663 14.6079 12.5064C14.1983 13.1179 13.6463 13.6174 13.0024 13.9644C12.7779 14.0854 12.5062 13.9715 12.4087 13.7359C12.3113 13.5 12.4256 13.232 12.647 13.105C13.1233 12.8318 13.5325 12.4514 13.8403 11.9917C14.2342 11.4035 14.445 10.7114 14.4458 10.0035C14.4465 9.29559 14.2369 8.60319 13.8442 8.0142C13.5375 7.55405 13.1293 7.17315 12.6538 6.89896C12.4328 6.77161 12.3189 6.50366 12.4165 6.2681ZM11.8608 7.68802C11.9662 7.43473 12.2601 7.31195 12.4927 7.45756C12.8512 7.68208 13.1603 7.98042 13.397 8.33549C13.7261 8.82919 13.9015 9.41011 13.9009 10.0035C13.9002 10.5966 13.7232 11.1766 13.3931 11.6695C13.1556 12.024 12.8458 12.3217 12.4868 12.5454C12.2539 12.6906 11.9607 12.5667 11.856 12.313C11.7514 12.0595 11.8772 11.7736 12.0972 11.6099C12.2798 11.4741 12.4389 11.3069 12.5669 11.1158C12.7877 10.7861 12.9063 10.3983 12.9067 10.0015C12.9071 9.60496 12.7897 9.21723 12.5698 8.88724C12.4422 8.69583 12.2835 8.52833 12.1011 8.39213C11.8812 8.22791 11.7555 7.94146 11.8608 7.68802Z" 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