Mini Kabibi Habibi
/**
* ADOBE CONFIDENTIAL
* _________________
* Copyright 2016 Adobe Systems Incorporated
* All Rights Reserved.
*
* NOTICE: All information contained herein is, and remains
* the property of Adobe Systems Incorporated and its suppliers,
* if any. The intellectual and technical concepts contained
* herein are proprietary to Adobe Systems Incorporated and its
* suppliers and are protected by all applicable intellectual property
* laws, including trade secret and copyright laws.
* Dissemination of this information or reproduction of this material
* is strictly forbidden unless prior written permission is obtained
* from Adobe Systems Incorporated.
*/
/*************************************************************************
* ADOBE CONFIDENTIAL
* ___________________
*
* Copyright 2015 Adobe Systems Incorporated
* All Rights Reserved.
*
* NOTICE: All information contained herein is, and remains
* the property of Adobe Systems Incorporated and its suppliers,
* if any. The intellectual and technical concepts contained
* herein are proprietary to Adobe Systems Incorporated and its
* suppliers and are protected by all applicable intellectual property
* laws, including trade secret and copyright laws.
* Dissemination of this information or reproduction of this material
* is strictly forbidden unless prior written permission is obtained
* from Adobe Systems Incorporated.
**************************************************************************/
/*
Service helper that holds the objects wanting to run something when the app starts.
The functions you should have attached to your objects correspond to PHASES.
eg if you want to listen for when everything has been loaded, make sure you have a function called "whenReady".
Example usage:
var startupObj = {
whenHostReady: function() {
// your code here
}
};
iaw.startup.add(startupObj);
*/
var iaw = iaw || {};
iaw.startup = {
PHASES: {
Doc: 'whenDocReady', // after the document finishes loading, before host data is loaded
Host: 'whenHostReady', // after host data has been loaded
Done: 'whenReady' // after everything has been loaded
},
_objs: [],
/*
Pass in the object that holds the (public) function(s) to be called during the startup sequence.
*/
add: function(startupObj) {
var haveCB = false;
for (var p in this.PHASES) {
if (startupObj[this.PHASES[p]]) {
haveCB = true;
break;
}
}
if (!haveCB) {
throw new Error('[iaw.init] Add at least one init callback before adding to startup sequence.');
}
this._objs.push(startupObj);
},
remove: function(startupObj) {
var i = this._objs.indexOf(startupObj);
if (i === -1) return;
this._objs.splice(i, 1);
console.log(this._objs);
},
/*
Cycles through all objects to call their functions if they exist. No arguments are passed.
@private
*/
run: function(phase) {
var i, o;
// console.log('[Startup] Running '+phase);
for (i = 0; i < this._objs.length; i++) {
o = this._objs[i];
if (o[phase]) {
o[phase].call(o);
}
}
}
};
//===================================================================================
//
// ADOBE CONFIDENTIAL
//
// Copyright © 2015 Adobe Systems Incorporated
// All Rights Reserved.
//
// NOTICE: All information contained herein is, and remains
// the property of Adobe Systems Incorporated and its suppliers,
// if any. The intellectual and technical concepts contained
// herein are proprietary to Adobe Systems Incorporated and its
// suppliers and are protected by all applicable intellectual property
// laws, including trade secret and copyright laws.
//
// Dissemination of this information or reproduction of this material
// is strictly forbidden unless prior written permission is obtained
// from Adobe Systems Incorporated.
//
//===================================================================================
/**
* Simple singleton style utility container so accessibility related
* stuff has a centralized wrapper.
*/
var iaw = iaw || {};
iaw.a11y = function() {
var KEYS_OF_INTEREST_MAC = [
{
/* Enter */
keyCode: 36
},
{
/* Tab */
keyCode: 48
},
{
/* Shift+Tab */
keyCode: 48,
shiftKey: true
},
{
/* Space */
keyCode: 49
},
{
/* Esc */
keyCode: 53
},
{
/* Home */
keyCode: 115
},
{
/* Page Up */
keyCode: 116
},
{
/* End */
keyCode: 119
},
{
/* Page Down */
keyCode: 121
},
{
/* Left Arrow */
keyCode: 123
},
{
/* Right Arrow */
keyCode: 124
},
{
/* Down Arrow */
keyCode: 125
},
{
/* Up Arrow */
keyCode: 126
},
{
/* A */
keyCode: 0
},
{
/* B */
keyCode: 11
},
{
/* C */
keyCode: 8
},
{
/* D */
keyCode: 2
},
{
/* E */
keyCode: 14
},
{
/* F */
keyCode: 3
},
{
/* G */
keyCode: 5
},
{
/* H */
keyCode: 4
},
{
/* I */
keyCode: 34
},
{
/* J */
keyCode: 38
},
{
/* K */
keyCode: 40
},
{
/* L */
keyCode: 37
},
{
/* M */
keyCode: 46
},
{
/* N */
keyCode: 45
},
{
/* O */
keyCode: 31
},
{
/* P */
keyCode: 35
},
{
/* Q */
keyCode: 12
},
{
/* R */
keyCode: 15
},
{
/* S */
keyCode: 1
},
{
/* T */
keyCode: 17
},
{
/* U */
keyCode: 32
},
{
/* V */
keyCode: 9
},
{
/* W */
keyCode: 13
},
{
/* X */
keyCode: 7
},
{
/* Y */
keyCode: 16
},
{
/* Z */
keyCode: 6
},
{
/* ` */
keyCode: 50
},
{
/* 1 */
keyCode: 18
},
{
/* 2 */
keyCode: 19
},
{
/* 3 */
keyCode: 20
},
{
/* 4 */
keyCode: 21
},
{
/* 5 */
keyCode: 23
},
{
/* 6 */
keyCode: 22
},
{
/* 7 */
keyCode: 26
},
{
/* 8 */
keyCode: 28
},
{
/* 9 */
keyCode: 25
},
{
/* 0 */
keyCode: 29
},
{
/* - */
keyCode: 27
},
{
/* = */
keyCode: 24
},
{
/* [ */
keyCode: 33
},
{
/* ] */
keyCode: 30
},
{
/* \ */
keyCode: 42
},
{
/* ; */
keyCode: 41
},
{
/* ' */
keyCode: 39
},
{
/* , */
keyCode: 43
},
{
/* . */
keyCode: 47
},
{
/* / */
keyCode: 44
},
{
/* A */
keyCode: 0,
shiftKey: true
},
{
/* B */
keyCode: 11,
shiftKey: true
},
{
/* C */
keyCode: 8,
shiftKey: true
},
{
/* D */
keyCode: 2,
shiftKey: true
},
{
/* E */
keyCode: 14,
shiftKey: true
},
{
/* F */
keyCode: 3,
shiftKey: true
},
{
/* G */
keyCode: 5,
shiftKey: true
},
{
/* H */
keyCode: 4,
shiftKey: true
},
{
/* I */
keyCode: 34,
shiftKey: true
},
{
/* J */
keyCode: 38,
shiftKey: true
},
{
/* K */
keyCode: 40,
shiftKey: true
},
{
/* L */
keyCode: 37,
shiftKey: true
},
{
/* M */
keyCode: 46,
shiftKey: true
},
{
/* N */
keyCode: 45,
shiftKey: true
},
{
/* O */
keyCode: 31,
shiftKey: true
},
{
/* P */
keyCode: 35,
shiftKey: true
},
{
/* Q */
keyCode: 12,
shiftKey: true
},
{
/* R */
keyCode: 15,
shiftKey: true
},
{
/* S */
keyCode: 1,
shiftKey: true
},
{
/* T */
keyCode: 17,
shiftKey: true
},
{
/* U */
keyCode: 32,
shiftKey: true
},
{
/* V */
keyCode: 9,
shiftKey: true
},
{
/* W */
keyCode: 13,
shiftKey: true
},
{
/* X */
keyCode: 7,
shiftKey: true
},
{
/* Y */
keyCode: 16,
shiftKey: true
},
{
/* Z */
keyCode: 6,
shiftKey: true
},
{
/* ` */
keyCode: 50,
shiftKey: true
},
{
/* 1 */
keyCode: 18,
shiftKey: true
},
{
/* 2 */
keyCode: 19,
shiftKey: true
},
{
/* 3 */
keyCode: 20,
shiftKey: true
},
{
/* 4 */
keyCode: 21,
shiftKey: true
},
{
/* 5 */
keyCode: 23,
shiftKey: true
},
{
/* 6 */
keyCode: 22,
shiftKey: true
},
{
/* 7 */
keyCode: 26,
shiftKey: true
},
{
/* 8 */
keyCode: 28,
shiftKey: true
},
{
/* 9 */
keyCode: 25,
shiftKey: true
},
{
/* 0 */
keyCode: 29,
shiftKey: true
},
{
/* - */
keyCode: 27,
shiftKey: true
},
{
/* = */
keyCode: 24,
shiftKey: true
},
{
/* [ */
keyCode: 33,
shiftKey: true
},
{
/* ] */
keyCode: 30,
shiftKey: true
},
{
/* \ */
keyCode: 42,
shiftKey: true
},
{
/* ; */
keyCode: 41,
shiftKey: true
},
{
/* ' */
keyCode: 39,
shiftKey: true
},
{
/* , */
keyCode: 43,
shiftKey: true
},
{
/* . */
keyCode: 47,
shiftKey: true
},
{
/* / */
keyCode: 44,
shiftKey: true
},
{
/* / */
keyCode: 75
},
{
/* * */
keyCode: 67
},
{
/* - */
keyCode: 78
},
{
/* + */
keyCode: 69
},
{
/* ⌫ */
keyCode: 51
}
];
var KEYS_OF_INTEREST_WIN = [
{
/* Enter */
keyCode: 0x0D
},
{
/* Tab */
keyCode: 0x09
},
{
/* Shift+Tab */
keyCode: 0x09,
shiftKey: true
},
{
/* Space */
keyCode: 0x20
},
{
/* Esc */
keyCode: 0x1B
},
{
/* Home */
keyCode: 0x24
},
{
/* Page Up */
keyCode: 0x21
},
{
/* End */
keyCode: 0x23
},
{
/* Page Down */
keyCode: 0x22
},
{
/* Left Arrow */
keyCode: 0x25
},
{
/* Right Arrow */
keyCode: 0x27
},
{
/* Down Arrow */
keyCode: 0x28
},
{
/* Up Arrow */
keyCode: 0x26
},
{
/* A */
keyCode: 0x41
},
{
/* B */
keyCode: 0x42
},
{
/* C */
keyCode: 0x43
},
{
/* D */
keyCode: 0x44
},
{
/* E */
keyCode: 0x45
},
{
/* F */
keyCode: 0x46
},
{
/* G */
keyCode: 0x47
},
{
/* H */
keyCode: 0x48
},
{
/* I */
keyCode: 0x49
},
{
/* J */
keyCode: 0x4A
},
{
/* K */
keyCode: 0x4B
},
{
/* L */
keyCode: 0x4C
},
{
/* M */
keyCode: 0x4D
},
{
/* N */
keyCode: 0x4E
},
{
/* O */
keyCode: 0x4F
},
{
/* P */
keyCode: 0x50
},
{
/* Q */
keyCode: 0x51
},
{
/* R */
keyCode: 0x52
},
{
/* S */
keyCode: 0x53
},
{
/* T */
keyCode: 0x54
},
{
/* U */
keyCode: 0x55
},
{
/* V */
keyCode: 0x56
},
{
/* W */
keyCode: 0x57
},
{
/* X */
keyCode: 0x58
},
{
/* Y */
keyCode: 0x59
},
{
/* Z */
keyCode: 0x5A
},
{
/* ` */
keyCode: 0xC0
},
{
/* 1 */
keyCode: 0x31
},
{
/* 2 */
keyCode: 0x32
},
{
/* 3 */
keyCode: 0x33
},
{
/* 4 */
keyCode: 0x34
},
{
/* 5 */
keyCode: 0x35
},
{
/* 6 */
keyCode: 0x36
},
{
/* 7 */
keyCode: 0x37
},
{
/* 8 */
keyCode: 0x38
},
{
/* 9 */
keyCode: 0x39
},
{
/* 0 */
keyCode: 0x30
},
{
/* - */
keyCode: 0xBD
},
{
/* = */
keyCode: 0xBB
},
{
/* [ */
keyCode: 0xDB
},
{
/* ] */
keyCode: 0xDD
},
{
/* \ */
keyCode: 0xDC
},
{
/* ; */
keyCode: 0xBA
},
{
/* ' */
keyCode: 0xDE
},
{
/* , */
keyCode: 0xBC
},
{
/* . */
keyCode: 0xBE
},
{
/* / */
keyCode: 0xBF
},
{
/* A */
keyCode: 0x41,
shiftKey: true
},
{
/* B */
keyCode: 0x42,
shiftKey: true
},
{
/* C */
keyCode: 0x43,
shiftKey: true
},
{
/* D */
keyCode: 0x44,
shiftKey: true
},
{
/* E */
keyCode: 0x45,
shiftKey: true
},
{
/* F */
keyCode: 0x46,
shiftKey: true
},
{
/* G */
keyCode: 0x47,
shiftKey: true
},
{
/* H */
keyCode: 0x48,
shiftKey: true
},
{
/* I */
keyCode: 0x49,
shiftKey: true
},
{
/* J */
keyCode: 0x4A,
shiftKey: true
},
{
/* K */
keyCode: 0x4B,
shiftKey: true
},
{
/* L */
keyCode: 0x4C,
shiftKey: true
},
{
/* M */
keyCode: 0x4D,
shiftKey: true
},
{
/* N */
keyCode: 0x4E,
shiftKey: true
},
{
/* O */
keyCode: 0x4F,
shiftKey: true
},
{
/* P */
keyCode: 0x50,
shiftKey: true
},
{
/* Q */
keyCode: 0x51,
shiftKey: true
},
{
/* R */
keyCode: 0x52,
shiftKey: true
},
{
/* S */
keyCode: 0x53,
shiftKey: true
},
{
/* T */
keyCode: 0x54,
shiftKey: true
},
{
/* U */
keyCode: 0x55,
shiftKey: true
},
{
/* V */
keyCode: 0x56,
shiftKey: true
},
{
/* W */
keyCode: 0x57,
shiftKey: true
},
{
/* X */
keyCode: 0x58,
shiftKey: true
},
{
/* Y */
keyCode: 0x59,
shiftKey: true
},
{
/* Z */
keyCode: 0x5A,
shiftKey: true
},
{
/* ` */
keyCode: 0xC0,
shiftKey: true
},
{
/* 1 */
keyCode: 0x31,
shiftKey: true
},
{
/* 2 */
keyCode: 0x32,
shiftKey: true
},
{
/* 3 */
keyCode: 0x33,
shiftKey: true
},
{
/* 4 */
keyCode: 0x34,
shiftKey: true
},
{
/* 5 */
keyCode: 0x35,
shiftKey: true
},
{
/* 6 */
keyCode: 0x36,
shiftKey: true
},
{
/* 7 */
keyCode: 0x37,
shiftKey: true
},
{
/* 8 */
keyCode: 0x38,
shiftKey: true
},
{
/* 9 */
keyCode: 0x39,
shiftKey: true
},
{
/* 0 */
keyCode: 0x30,
shiftKey: true
},
{
/* - */
keyCode: 0xBD,
shiftKey: true
},
{
/* = */
keyCode: 0xBB,
shiftKey: true
},
{
/* [ */
keyCode: 0xDB,
shiftKey: true
},
{
/* ] */
keyCode: 0xDD,
shiftKey: true
},
{
/* \ */
keyCode: 0xDC,
shiftKey: true
},
{
/* ; */
keyCode: 0xBA,
shiftKey: true
},
{
/* ' */
keyCode: 0xDE,
shiftKey: true
},
{
/* , */
keyCode: 0xBC,
shiftKey: true
},
{
/* . */
keyCode: 0xBE,
shiftKey: true
},
{
/* / */
keyCode: 0xBF,
shiftKey: true
},
{
/* / */
keyCode: 0x6F
},
{
/* * */
keyCode: 0x6A
},
{
/* - */
keyCode: 0x6D
},
{
/* + */
keyCode: 0x6B
},
{
/* ⌫ */
keyCode: 0x08
}
];
var FOCUSABLE_SELECTOR = ['input:not([type="hidden"]):not(:disabled)', ' select:not(:disabled)', 'a[href]', 'textarea:not(:disabled)', 'button:not(:disabled)', '[tabindex]'].join(',');
// variables for alphanumeric seach
var searchString = '';
var searchStringDelay = 800;
var searchTimeout;
var whitespaceRegex = /\s+/g;
var regexRegex = /[\-\[\]{}()*+?.,\\\^$|#\s]/g;
// Utility method to evaluate whether an node or any of its parents is hidden.
var nodeCache = {}, cacheIndex = 1;
function isHidden(node) {
if (node === document.documentElement) {
return false;
}
// No need to test node if we already have tested its display/visibility.
if (node.focusableCacheIndex) {
return nodeCache[node.focusableCacheIndex];
}
var result = false,
style = window.getComputedStyle(node);
if (style.visibility === 'hidden' || style.display === 'none') {
result = true;
}
else if (node.parentNode) {
result = isHidden(node.parentNode);
}
// Once node has been tested store result.
node.focusableCacheIndex = cacheIndex;
nodeCache[node.focusableCacheIndex] = result;
cacheIndex++;
return result;
}
function onLoadEvent() {
console.log(); // this is a hack to force this function to run; don't remove
var hadKeyboardEvent = false,
keyboardModalityWhitelist = ['input:not([type])', 'input[type=text]', 'input[type=checkbox]', 'input[type=radio]', 'input[type=number]', 'input[type=date]', 'input[type=time]', 'input[type=datetime]', 'textarea', '[role=textbox]', 'select', '[supports-modality=keyboard]'].join(',');
disableFocusRingByDefault();
document.body.addEventListener('keydown', function(evt) {
hadKeyboardEvent = true;
setTimeout(function() {
hadKeyboardEvent = false;
}, 0);
}, true);
document.body.addEventListener('focus', function(evt) {
if (hadKeyboardEvent || focusTriggersKeyboardModality(evt.target)) {
document.body.setAttribute('modality', 'keyboard');
}
}, true);
document.body.addEventListener('blur', function(evt) {
document.body.removeAttribute('modality');
}, true);
function disableFocusRingByDefault() {
var css = 'body:not([modality=keyboard]) :focus { outline: none; }',
head = document.head || document.getElementsByTagName('head')[0],
style = document.createElement('style');
style.type = 'text/css';
style.id = 'disable-focus-ring';
if (style.styleSheet) {
style.styleSheet.cssText = css;
}
else {
style.appendChild(document.createTextNode(css));
}
head.appendChild(style);
}
function focusTriggersKeyboardModality(el) {
return el.matches(keyboardModalityWhitelist);
}
}
// there is no event that works consistently across start and fnft, so we utilize startup
iaw.startup.add({
whenDocReady: onLoadEvent.bind(this)
});
return {
/**
* Static keycode constants.
*/
Keys: {
TAB: 9,
ENTER: 13,
ESC: 27,
SPACE: 32,
PAGEUP: 33,
PAGEDOWN: 34,
END: 35,
HOME: 36,
LEFT: 37,
UP: 38,
RIGHT: 39,
DOWN: 40
},
/**
* Register keys that the host application should allow to pass through to be handled by the CEP web view.
*/
registerKeyEventsInterest: function() {
var keyEventsInterest = iaw.util.isWindowsOS() ? KEYS_OF_INTEREST_WIN : KEYS_OF_INTEREST_MAC;
if (iaw.cepUtil.csInterface) {
iaw.cepUtil.csInterface.registerKeyEventsInterest(JSON.stringify(keyEventsInterest));
}
},
/**
* Returns true when a given element can receive keyboard or mouse focus.
*
* @param el HTMLElement An HTML element.
* @return Boolean true when element can receive keyboard or mouse focus.
*/
isFocusable: function(el) {
return el.matches(FOCUSABLE_SELECTOR) && !isHidden(el);
},
/**
* Returns an array of focusable descendants of a given element ordered with elements having tabIndex > 0 coming before elements with tabIndex <= 0.
*
* @param el Object Parent node or selector
* @param tabbable Boolean Return only tabbable children by excluding elements with tabIndex < 0
* @param includeEl Boolean Include parent element in result if it is focusable.
* @return Array Ordered array of focusable elements
*/
focusable: function(el, tabbable, includeEl) {
if (typeof el === 'string') {
el = document.querySelector(el);
}
var basicFocusables = [],
orderedFocusables = [],
candidateNodelist = el.querySelectorAll(FOCUSABLE_SELECTOR),
candidates = Array.prototype.slice.call(candidateNodelist),
candidate, candidateTabIndex;
if (includeEl) {
candidates.unshift(el);
}
for (var i = 0, l = candidates.length; i < l; i++) {
candidate = candidates[i];
candidateTabIndex = candidate.tabIndex;
if ((candidateTabIndex < 0 && tabbable) || isHidden(candidate)) {
continue;
}
if (candidateTabIndex <= 0) {
basicFocusables.push(candidate);
}
else {
orderedFocusables.push({
tabIndex: candidateTabIndex,
node: candidate
});
}
}
orderedFocusables = orderedFocusables
.sort(function(a, b) {
return a.tabIndex - b.tabIndex;
})
.map(function(a) {
return a.node;
});
Array.prototype.push.apply(orderedFocusables, basicFocusables);
return orderedFocusables;
},
/**
* Returns an array of tabbable descendants of a given element ordered with elements having
* tabIndex > 0 coming before elements with tabIndex <= 0.
*
* @param el HtmlElement Parent node
* @param includeEl Boolean Include parent element in result if it is tabbable
* @return Array Ordered array of tabbable elements
*/
tabbable: function(el, includeEl) {
if (typeof el === 'string') {
el = document.querySelector(el);
}
return iaw.a11y.focusable(el, true, includeEl);
},
/**
* Returns true if a given element or one of its descendants has focus.
*
* @param el Object Element or selector
* @return Boolean Whether the element or one of its descendant has focus.
*/
descendantHasFocus: function(el) {
if (typeof el === 'string') {
el = document.querySelector(el);
}
return el === document.activeElement || el.contains(document.activeElement);
},
/**
* Returns next item from array of items by alphanumeric search
*
* @param evt Event Keypress event object
* @param items Array An array of HTMLElements
* @param currentItem HTMLElement Current item within the items array.
* @return HTMLElement An element with text content that starts with an alphanumeric string
*/
alphanumericSearch: function(evt, items, currentItem) {
var charCode = evt.charCode,
stringFromCharCode,
index = -1;
if (charCode <= 32 || evt.ctrlKey || evt.metaKey || evt.altKey) return;
stringFromCharCode = String.fromCharCode(charCode);
if (stringFromCharCode !== searchString) {
searchString += String.fromCharCode(charCode);
}
clearTimeout(searchTimeout);
searchTimeout = setTimeout(function() {
searchString = '';
}, searchStringDelay);
items = items.filter(function(el) {
var textContent = el.textContent.replace(whitespaceRegex, ' ').trim();
return new RegExp('^' + searchString.replace(regexRegex, '\\$&'), 'i').test(textContent);
});
if (items.length) {
index = items.indexOf(currentItem);
if (index === -1) {
return items[0];
}
else if (searchString.length > 1) {
return items;
}
else if (index === items.length - 1) {
return items[0];
}
else {
return items[index + 1];
}
}
return;
},
/**
* Returns next item from a grid/card layout in vertical direction
*
* @param items Array An array of HTMLElements
* @param currentItem HTMLElement Current item within the items array.
* @param up Boolean Search in the reverse or 'up' direction
* @return HTMLElement An element adjacent to the current item vertically
*/
getAdjacentElementVertically: function(items, currentItem, up) {
var increment = up ? -1 : 1,
i = items.indexOf(currentItem),
rect = currentItem.getBoundingClientRect(),
rowTop = rect.top,
colLeft = rect.left,
colRight = rect.right,
prevOrNext = items[i + increment],
nextRowTop = null,
candidates = [], filtered;
i += increment;
while (prevOrNext) {
rect = prevOrNext.getBoundingClientRect();
// determine start of next row
if (nextRowTop === null && rowTop !== rect.top) {
nextRowTop = rect.top;
}
// in next row,
if (nextRowTop) {
// add candidates until
if (nextRowTop === rect.top) {
candidates.push({item: prevOrNext, rect: rect});
}
// we reach the first card in the following row.
else {
break;
}
}
i += increment;
prevOrNext = items[i];
}
// sort candidates from left to right
// @todo We may need to be aware of right-to-left languages when sorting.
candidates = candidates.sort(function(a, b) {
return a.rect.left > b.rect.left;
});
// filter candidates based whether they overlap with the current card
filtered = candidates.filter(function(candidate) {
rect = candidate.rect;
return (colLeft === rect.left || colRight === rect.right ||
(colLeft > rect.left && colRight < rect.right));
});
// Return the first candidate that overlaps with the current card,
if (filtered.length) {
return filtered[0].item;
}
// or return the last item in the next row.
else if (candidates.length) {
return candidates[candidates.length - 1].item;
}
return;
},
/**
* TrapFocus is used to trap keyboard focus within a DOM node.
* @returns Class iaw.a11y.TrapFocus class instance
*/
TrapFocus: function() {
var trap,
tabbables,
lastFocused,
isActive,
config;
/**
* Activate traps keyboard focus within given DOM node or selector.
* <p>
* Options:
* <ul>
* <li><b>initialFocus:</b> By default, focus will be set to the
* first element in the element's tab order, but by declaring the
* <b>initialFocus</b> you can specify which specific element or
* selector will receive focus.</li>
* <li><b>onDeactivate:</b> A callback method to be executed when
* the focus trap is deactivated</li>
* </ul>
*
* @param Object el HTMLselector
* @param Object Options object containing intialFocus and/or onDeactivate callback method.
*/
function activate(el, options) {
// There can be only one focus trap at a time
if (isActive) deactivate();
isActive = true;
trap = (typeof el === 'string') ? document.querySelector(el) : el;
config = options || {};
lastFocused = document.activeElement;
updateTabbables();
focus(initialFocus());
document.addEventListener('focus', onFocus, true);
document.addEventListener('click', onClick, true);
document.addEventListener('keydown', onKeyDown, true);
setModal(trap, true);
}
/**
* Returns the HTMLElement that should receive initial focus when
* the focus trap has been activated.
*
* @return HTMLElement Element to receive initial focus
*/
function initialFocus() {
var node;
if (!config.initialFocus) {
node = tabbables[0];
if (!node) {
throw new Error('You can\'t have a focus-trap without at least one focusable element');
}
return node;
}
if (typeof config.initialFocus === 'string') {
node = document.querySelector(config.initialFocus);
}
else {
node = config.initialFocus;
}
if (!node) {
throw new Error('The `initialFocus` selector you passed refers to no known node');
}
return node;
}
/**
* Deactivate the focus trap, call onDeactivate callback method,
* and restore focus to the last element that had focus before
* the trap was activated if the focus hasn't been shifted
* to some other element.
*/
function deactivate() {
if (!isActive) return;
isActive = false;
document.removeEventListener('focus', onFocus, true);
document.removeEventListener('click', onClick, true);
document.removeEventListener('keydown', onKeyDown, true);
setModal(trap, false);
if (config.onDeactivate) {
config.onDeactivate();
}
setTimeout(function() {
if (iaw.a11y.descendantHasFocus(trap) ||
(!document.activeElement || document.body === document.activeElement)) {
focus(lastFocused);
}
}, 0);
}
/**
* Handle click event when trap is active, to ensure that elements
* outside focus trap do not respond to click events.
* @param MouseEvent evt Click event
*/
function onClick(evt) {
if (trap.contains(evt.target)) return;
evt.preventDefault();
evt.stopImmediatePropagation();
}
/**
* Handle focus event when trap is active, to ensure that elements
* outside focus trap do not receive focus.
* @param FocusEvent evt Focus event
*/
function onFocus(evt) {
updateTabbables();
if (trap.contains(evt.target)) return;
focus(tabbables[0]);
}
/**
* Handle key down event when trap is active to respond to the TAB
* key or the ESC key, which should deactivate the focus trap.
* @param KeyboardEvent evt Keyboard event
*/
function onKeyDown(evt) {
if (evt.keyCode === iaw.a11y.Keys.TAB) {
handleTabKey(evt);
}
if (evt.keyCode === iaw.a11y.Keys.ESC) {
deactivate();
}
}
/**
* Handle TAB key when trap is active to ensure that focus stays
* within the focus trap.
* @param KeyboardEvent evt Keyboard event
*/
function handleTabKey(evt) {
evt.preventDefault();
updateTabbables();
var targ = evt.target;
var index = tabbables.indexOf(targ);
var last = tabbables[tabbables.length - 1];
var first = tabbables[0];
if (evt.shiftKey) {
if (evt.target === first) {
focus(last);
return;
}
focus(tabbables[index - 1]);
return;
}
if (targ === last) {
focus(first);
return;
}
focus(tabbables[index + 1]);
}
/**
* Update array of tabbable elements within the focus trap.
*/
function updateTabbables() {
tabbables = iaw.a11y.tabbable(trap, true);
}
/**
* Set focus to an HTMLElement node.
* @param HTMLElementnode HTMLElement node to receive focus.
*/
function focus(node) {
if (!node || !node.focus) return;
if (node === initialFocus() && !iaw.a11y.isFocusable(node)) {
node.tabIndex = -1;
node.addEventListener('blur', function blurred() {
node.removeAttribute('tabIndex');
node.removeEventListener('blur', blurred);
}, true);
}
node.focus();
if (node.tagName.toLowerCase() === 'input') {
node.select();
}
}
/**
* Hide siblings of an HTMLElement node from assistive technology
* using `aria-hidden`, so that it is not possible to read elements
* outside of the given node with a screen reader.
* @param HTMLElement node HTMLElement node to receive focus.
*/
function setModal(node, bool) {
if (!node) return;
var parentNode = node.parentNode,
siblings, sibling, cachedAriaHidden;
while (parentNode !== document.documentElement) {
siblings = Array.prototype.slice.call(parentNode.children);
Array.prototype.splice.call(siblings, siblings.indexOf(node), 1);
for (var i = 0, l = siblings.length; i < l; i++) {
sibling = siblings[i];
if (sibling.tagName !== 'AREA' &&
sibling.tagName !== 'BASE' &&
sibling.tagName !== 'BASEFONT' &&
sibling.tagName !== 'BR' &&
sibling.tagName !== 'COL' &&
sibling.tagName !== 'LINK' &&
sibling.tagName !== 'META' &&
sibling.tagName !== 'PARAM' &&
sibling.tagName !== 'SCRIPT' &&
sibling.tagName !== 'STYLE') {
if (bool) {
cachedAriaHidden = sibling.getAttribute('aria-hidden');
if (cachedAriaHidden) {
sibling.setAttribute('data-aria-hidden', cachedAriaHidden);
}
sibling.setAttribute('aria-hidden', 'true');
}
else {
cachedAriaHidden = sibling.getAttribute('data-aria-hidden');
if (cachedAriaHidden) {
sibling.setAttribute('aria-hidden', cachedAriaHidden);
sibling.removeAttribute('data-aria-hidden');
}
else {
sibling.removeAttribute('aria-hidden');
}
}
}
}
node = parentNode;
parentNode = parentNode.parentNode;
}
}
return {
activate: activate,
deactivate: deactivate
};
}()
};
}();
/*************************************************************************
* ADOBE CONFIDENTIAL
* ___________________
*
* Copyright 2015 Adobe Systems Incorporated
* All Rights Reserved.
*
* NOTICE: All information contained herein is, and remains
* the property of Adobe Systems Incorporated and its suppliers,
* if any. The intellectual and technical concepts contained
* herein are proprietary to Adobe Systems Incorporated and its
* suppliers and are protected by all applicable intellectual property
* laws, including trade secret and copyright laws.
* Dissemination of this information or reproduction of this material
* is strictly forbidden unless prior written permission is obtained
* from Adobe Systems Incorporated.
**************************************************************************/
/*global Ingest*/
/**
* Simple singleton style container so all analytics related stuff has a centralized point.
*/
var iaw = iaw||{};
iaw.analytics = {
/**
* Override to turn analytics on or off
*/
enabled: false,
pipQuery: {
//Events
category: 'Mobile Creations',
pipAware: {
subCategory: 'See Start Screen',
eventName: 'See Start Screen'
},
pipTry: {
subCategory: 'Click Mobile Creations',
eventName : 'Click Mobile Creations'
},
pipSucceedProject: {
subCategory: 'Open Mobile Creations',
eventName: 'Open Mobile Creations'
},
pipSucceedProjectFile: {
subCategory: 'Open Mobile Creations',
eventName : 'Open App Page'
},
pipNoAssetUI: {
subCategory : 'No Mobile Creations',
eventName : 'See No Asset UI'
},
pipGetApp: {
subCategory : 'No Mobile Creations',
eventName : 'Click on Get App'
},
pipTimeOutOnMCClick: {
subCategory: 'TimeOutOnMCClick',
eventName: 'TimeOutOnMCClick'
},
pipTimeOutOnProjectClick: {
subCategory: 'TimeOutOnProjectClick',
eventName: 'TimeOutOnProjectClick'
},
pipTimeOutOnFileClick: {
subCategory: 'TimeOutOnFileClick',
eventName: 'TimeOutOnFileClick'
},
pipTryAgainOnMCClick: {
subCategory: 'TryAgainOnMCClick',
eventName: 'TryAgainOnMCClick'
},
pipTryAgainOnProjectClick: {
subCategory: 'TryAgainOnProjectClick',
eventName: 'TryAgainOnProjectClick'
},
pipTryAgainOnFileClick: {
subCategory: 'TryAgainOnFileClick',
eventName: 'TryAgainOnFileClick'
}
},
/**
* Configure analytics for future analytics calls.
*
* @param client client ID string
* @param hostData data from the host
* @param psdkData data fron the PSDK
* @param mode string indicating the server mode (prod, stage, or dev)
*/
config: function(client, hostData, psdkData, mode) {
this.enabled = hostData.userTrackingEnabled;
this.ingest.configure(client, hostData, psdkData, mode);
},
/**
* Compose the passed URL for proper analytics logging - this means
* adding in analytic query params and moving any deep links to the end.
* This is primarily for Adobe Learn.
*
* @param url original URL to add analytic params into
* @return String containing the URL properly ordered with analytics query arguments.
*/
composeURLWithAnalyticsQueryString: function(url) {
var composedURL = url;
if (composedURL) {
var analyticsQuery = this.getAnalyticsQueryString();
// check for deep links
var deepLinkIndex = composedURL.indexOf('#');
var deepLink = '';
if (deepLinkIndex >= 0) {
deepLink = composedURL.substr(deepLinkIndex);
composedURL = composedURL.substr(0, deepLinkIndex);
}
// check for existing query which it could have
var querySep = ((composedURL.indexOf('?') < 0) ? '?' : '&');
composedURL += (querySep + analyticsQuery + deepLink);
}
return composedURL;
},
/**
* Build the passthrough analytics string for
*
* @return String containing the query arguments.
*/
getAnalyticsQueryString: function() {
var hostData = iaw.store.get('host');
return 'locale='+hostData.language+
'&x-product='+hostData.hostID+'%2F'+hostData.appVersion+
'&x-product-location=CCXStart-'+hostData.displayMode+'%2F'+hostData.radarVersion+
'&x-radarSession='+hostData.radarSessionGUID+
'&x-appSession='+hostData.sessionGUID+
'&ute='+hostData.userTrackingEnabled;
},
/**
* AdobePIP/Highbeam Analytics
*/
pip: {
kCategory: 'CCXStart',
fnftCategory: 'CCXFNFT',
/**
* Log Highbeam events to the hosting application.
*
* @param pipCategory Highbeam category classification string
* @param pipSubCategory Highbeam sub-category classification string
* @param pipEvent Highbeam event name string
*/
logEvent: function( pipCategory, pipSubCategory, pipEvent ) {
if ( iaw.analytics.enabled ) {
if ( pipEvent ) {
// Highbeam events are limited to 128 characters
pipEvent = (pipEvent.length < 128) ? pipEvent: pipEvent.substr(0, 127);
}
var pipJSO = {
dataType: 'event',
category: pipCategory,
subcategory: pipSubCategory,
eventname: pipEvent
};
var pipJSON = JSON.stringify( pipJSO );
if ( iaw.cepUtil ) {
iaw.cepUtil.sendEvent( iaw.cepUtil.events.LOGPIP, pipJSON );
}
}
},
/**
* Log Highbeam group data to the hosting application.
*
* @param pipGroupName Highbeam group name
* @param data Highbeam group data
* @param category Highbeam group category
* @param subcategory Highbeam group subcategory
*/
logDataGroupEvent: function( pipGroupName, data, category, subcategory ) {
if (iaw.analytics.enabled) {
var group_Data_Array = [];
if (data) {
Object.keys(data).forEach(function(key) {
group_Data_Array.push({
'columnname': key,
'value': data[key].toString()
});
});
}
var pipJSON = {
dataType: 'group',
category: category,
subcategory: subcategory,
groupname: pipGroupName,
Group_Data_Array: group_Data_Array
};
pipJSON = JSON.stringify( pipJSON );
if ( iaw.cepUtil ) {
iaw.cepUtil.sendEvent( iaw.cepUtil.events.LOGPIP, pipJSON );
}
}
},
/**
* Log Highbeam group data to the hosting application for FNFT. For PHXS only so far.
*
*/
logFNFTDataGroupEvent: function( pipGroupName, data ) {
// For PHXS only so far.
if ('PHXS' === iaw.store.get(['host', 'hostID'])) {
this.logDataGroupEvent(pipGroupName, data, this.fnftCategory, 'Interaction');
}
},
/**
* Utility method to log an 'Interaction' event.
*
* @param interactionEvent event to log
*/
logInteractionEvent: function( interactionEvent ) {
this.logEvent( this.kCategory, 'Interaction', interactionEvent );
},
/**
* Utility method to log an 'Interaction' event for FNFT. For PHXS only so far.
*
* @param interactionEvent event to log
*/
logFNFTInteractionEvent: function( interactionEvent ) {
if ('PHXS' === iaw.store.get(['host', 'hostID'])) {
this.logEvent( this.fnftCategory, 'Interaction', interactionEvent );
}
},
/**
* Utility method to log an 'Failure' event.
*
* @param failEvent event to log
*/
logFailureEvent: function( failEvent ) {
this.logEvent( this.kCategory, 'Failure', failEvent );
}
}, // end of pip container
/**
* Ingest Analytics
*/
ingest: {
/**
* Ingest configuration objects
*/
imsToken: null,
ingestLibReady: false,
instance: null,
psdkData: null,
basePayloadData: {
eventType: null,
eventGUID: null,
dts: null,
userGUID: null,
subscriptionStatus: null,
productName: null,
productVersion: null,
productLanguage: null,
displayCount: 0,
radarVersion: null,
userAgent: navigator.userAgent,
radarSessionGUID: null,
appSessionGUID: null
},
renderedCards: {}, // map of rendered card events
// since many things load asynchronously, some event actions can happen before
// the Ingest lib scripts have completed - therefore we pre-queue some events
// that get processed once the lib is ready
prequeuedEvents: [],
/**
* Configure Ingest for future analytics calls.
*
* @param client client ID string
* @param hostData data from the host
* @param psdkData data fron the PSDK
* @param mode string indicating the server mode (prod, stage, or dev)
*/
configure: function( client, hostData, psdkData, mode ) {
var self = this;
mode = mode || 'prod';
this.psdkData = psdkData;
// Ingest.js library configuration
var options = {
ENVIRONMENT: mode,
ANALYTICS_API_KEY: client,
ANALYTICS_X_PRODUCT: hostData.hostID+'%2F'+hostData.appVersion,
ANALYTICS_PROJECT: 'ccx-cmdn-service',
ANALYTICS_USER_REGION: hostData.countryCode,
ANALYTICS_INGEST_TYPE: 'dunamis',
ANALYTICS_MAX_QUEUED_EVENTS: 50,
ANALYTICS_DEBOUNCE: 0,
TIMESTAMP_PROPERTY_NAME: 'event.dts_end'
};
var dependencies = {
log: function(msg) {
iaw.log.console(msg);
},
getAccessToken: function(callback) {
if (self.imsToken) {
callback(null, self.imsToken);
}
else { // handle case when no IMS token is available
iaw.cepUtil.getIMSAccessToken(function( token ) {
self.imsToken = token;
callback(null, token);
});
}
},
clearAccessToken: function() {
self.imsToken = null;
iaw.cepUtil.clearAccessToken();
}
};
// Get Ingest instance (Ingest supports multiple instances since v1.0)
this.instance = Ingest.createInstance(dependencies, options);
this.instance.enable(iaw.analytics.enabled);
// configure basic payload
this.basePayloadData.userGUID = hostData.adobeGUID;
this.basePayloadData.subscriptionStatus = hostData.accountStatus;
this.basePayloadData.productName = hostData.hostID;
this.basePayloadData.productVersion = hostData.appVersion;
this.basePayloadData.productLanguage = hostData.language;
this.basePayloadData.displayCount = hostData.displayCount;
this.basePayloadData.radarVersion = hostData.radarVersion;
this.basePayloadData.modeID = hostData.displayMode;
this.basePayloadData.radarSessionGUID = hostData.radarSessionGUID;
this.basePayloadData.appSessionGUID = hostData.sessionGUID;
if (hostData.displayMode !== 'fnft') {
this.basePayloadData.appLaunchCount = hostData.launchCount;
}
this.basePayloadData.AUMSegments = null;
this.basePayloadData.hvaFlow = null;
this.basePayloadData.displayMode = null;
if (psdkData) {
this.basePayloadData.AUMSegments = psdkData.segmentID || 'none';
this.basePayloadData.hvaFlow = psdkData.hvaFlow || 'none';
// find the control card section for this display mode
for (var index = 0; index < psdkData.cardControl.length && !this.basePayloadData.displayMode; index++) {
if ( psdkData.cardControl[index].modeID === hostData.displayMode ) {
this.basePayloadData.displayMode = psdkData.cardControl[index].cardOrder.toString();
}
}
}
// we only want to add the trialEndDts parameter if we are a trial account
if (hostData.accountStatus !== 'paid' && hostData.secondsLeftInTrial !== undefined) {
this.basePayloadData.trialEndDts = iaw.util.getTrialEndDate(hostData.secondsLeftInTrial);
}
iaw.log.console('Ingest configured ('+mode+')');
this.ingestLibReady = true;
// now handle any prequeued events
this.processPrequeuedEvents();
},
/**
* Create a clone of the Ingest base payload data.
*
* @param needsAEMData boolean flag to indicated that AEM card data is needed in the payload
* @return A new object based off the preset payload data.
*/
createPayload: function(needsAEMData) {
var payload = JSON.parse(JSON.stringify(this.basePayloadData));
payload.eventGUID = iaw.util.generateGUID();
payload['source.client_id'] = iaw.cepUtil.getIMSClientId();
if (!needsAEMData) {
delete payload.AUMSegments;
delete payload.hvaFlow;
delete payload.displayMode;
}
return payload;
},
/**
* If the Ingest library is not loaded/configured prior to calling a postEvent,
* then the event gets pre-queued. So once the library is ready then we process
* all the events.
*/
processPrequeuedEvents: function() {
var self = this;
this.prequeuedEvents.forEach(function(payload) {
// update base payload properties - dont use iaw.util.assign here
// as it would overwrite properties we dont want
for (var prop in self.basePayloadData) {
if (!payload[prop] && self.basePayloadData[prop]) {
payload[prop] = self.basePayloadData[prop];
}
}
self.postEvent(payload);
});
},
/**
* Post the analytics call to the Ingest data server.
*
* @param payload payload data to send
*/
postEvent: function( payload ) {
if (this.ingestLibReady) {
if (payload.debugIt) {
window.alert(JSON.stringify(payload));
}
else {
this.instance.postEvent(payload);
}
}
else {
this.prequeuedEvents.push(payload);
}
},
/**
* Flush the queued events to the Ingest data server.
*
*/
flushEvent: function() {
if (this.ingestLibReady) {
this.instance.flush(true);
}
},
/**
* Log a screen state event.
*
* @param mode values: 'open', 'close-manual', 'close-auto', 'do-not-show'
*/
logScreenStateEvent: function( mode ) {
var ingestPayloadData = this.createPayload();
ingestPayloadData.eventType = 'screen-state';
ingestPayloadData.welcomeScreenState = mode;
this.postEvent(ingestPayloadData);
},
/**
* Log a Ingest event on the PSDK engagement card.
*
* @param type type of event
* @param cardData card data from the PSDK
*/
logEngagementCardEvent: function( type, cardData ) {
if ((type === 'rendered' && (!this.renderedCards[cardData.cardID])) ||
(type !== 'rendered')) {
var ingestPayloadData = this.createPayload(true);
switch (type) {
case 'rendered':
ingestPayloadData.eventType = 'eng-card-rendered';
this.renderedCards[cardData.cardID] = true;
break;
case 'clicked':
ingestPayloadData.eventType = 'eng-card-click';
break;
default:
iaw.log.conosle('Invalid Ingest card event');
break;
}
ingestPayloadData.cardTypeID = cardData.cardTypeID;
ingestPayloadData.cardTypeName = cardData.cardType;
ingestPayloadData.cardID = cardData.cardID;
ingestPayloadData.cardName = cardData.cardName;
ingestPayloadData.width = cardData.width;
ingestPayloadData.displayTemplate = cardData.displayTemplate || '';
ingestPayloadData.startDTS = cardData.startDTS || '';
ingestPayloadData.endDTS = cardData.endDTS || '';
ingestPayloadData.actionURL = cardData.actionURL || '';
ingestPayloadData.urlLinkType = cardData.urlLinkType || '';
// invertPresentation is a boolean - so usual logic above doesnt work
ingestPayloadData.invertPresentation = (typeof cardData.invertPresentation !== 'undefined') ? cardData.invertPresentation: 'n/a';
// optional params, some are not implemented yet in AEM,
// so strip them out until they are
if ( cardData.campaignCode ) {
ingestPayloadData.campaignCode = cardData.campaignCode;
}
if ( cardData.recipe ) {
ingestPayloadData.recipe = cardData.recipe;
}
if ( cardData.aumSegments ) {
ingestPayloadData.aumSegments = cardData.aumSegments;
}
// price copy is only present on offer cards
if ( cardData.priceCopy ) {
ingestPayloadData.priceCopy = cardData.priceCopy;
}
// some cards like AdobeStock, may have a promo ID, if so, then log it
if ( cardData.promoID ) {
ingestPayloadData.promoID = cardData.promoID;
}
// only present on AdobeStock cards
if ( cardData.as_query ) {
ingestPayloadData.actionURL = ingestPayloadData.actionURL+'&'+cardData.as_query;
}
// playlists fields present on all engagement stream cards
if (this.psdkData) {
ingestPayloadData.eventParams = {
'persona': this.psdkData.persona || 'none',
'skill': this.psdkData.skill || 'none',
'appLaunchBucket': this.psdkData.appLaunchBucket || 'none',
'entitlement': this.psdkData.entitlement || 'none',
'entitlementType': this.psdkData.entitlementType || 'none',
'marketSegment': this.psdkData.marketSegment || 'none',
'derivedPersona': this.psdkData.derivedPersona || 'none',
'derivedSkill': this.psdkData.derivedSkill || 'none',
'derivedAppLaunchBucket': this.psdkData.derivedAppLaunchBucket || 'none',
'bonusLaunch': this.psdkData.bonusLaunch || 'none',
'ccxVersion': this.psdkData.ccxVersion || 'none',
'radarSessionGUID': this.psdkData.radarSessionGUID || 'none'
};
}
this.postEvent(ingestPayloadData);
}
},
/**
* Log a view change event.
*
* @param viewData view data to log
*/
logViewChangeEvent: function( viewData ) {
var ingestPayloadData = this.createPayload();
ingestPayloadData.eventType = 'uc-section';
ingestPayloadData.sectionView = viewData.sectionView;
ingestPayloadData.sectionType = viewData.sectionType;
this.postEvent(ingestPayloadData);
},
/**
* Log a file/lib/etc. open event.
*
* @param itemData item data to log
*/
logItemOpenedEvent: function( itemData ) {
var ingestPayloadData = this.createPayload();
ingestPayloadData.eventType = 'uc-file-open';
ingestPayloadData.fileType = itemData.fileType;
ingestPayloadData.fileOpenType = itemData.openType;
ingestPayloadData.sectionView = itemData.sectionView;
if (typeof itemData.itemPosition !== 'undefined') {
ingestPayloadData.itemPosition = itemData.itemPosition;
}
if (itemData.ucAction) {
ingestPayloadData.ucAction = itemData.ucAction;
}
if (itemData.cardID) {
ingestPayloadData.cardID = itemData.cardID;
}
if (itemData.eventAction) {
ingestPayloadData.eventAction = itemData.eventAction;
}
this.postEvent(ingestPayloadData);
},
/**
* Log a miscellaneous event.
*
* @param itemData item data to log
*/
logMiscellaneousEvent: function( itemData ) {
var ingestPayloadData = this.createPayload();
ingestPayloadData.eventType = itemData.eventType || 'uc-misc';
for (var key in itemData) {
ingestPayloadData[key] = itemData[key];
}
this.postEvent(ingestPayloadData);
},
/**
* Log a Ingest event on the Mobile Creations item.
*
* @param eventAction render,click, or open
* @param cardTypeName null/project/pages/appcards/appdetail
* @param cardName will be null in most cases, added here for future expansion
*/
logMobileCreationsEvent: function( mcEventData ) {
var ingestPayloadData = this.createPayload();
ingestPayloadData.eventType = 'mobile-creations';
for (var prop in mcEventData) {
if (mcEventData[prop]) {
ingestPayloadData[prop] = mcEventData[prop];
}
}
this.postEvent(ingestPayloadData);
},
/**
* Build up the event structure or the differnt types of Mobile Creations events to Ingest.
*
* @param mode string object indicating the event mode
* @param action string containg type of action 'click' or 'render'
* @param cardData JSON object containing the data to log
*/
constructMobileCreationsEventData: function(mode, action, cardData) {
var mcEventData = {
eventAction: action,
cardTypeName: ''
};
// setup card data
if (mode !== 'item') {
for (var prop in cardData) {
if (cardData) {
mcEventData[prop] = cardData[prop];
}
}
}
// setup mode op
switch (mode) {
case 'project':
mcEventData.cardTypeName = 'project';
break;
case 'pages':
mcEventData.cardTypeName = 'pages';
break;
case 'app':
mcEventData.cardTypeName = 'appcards';
break;
case 'item':
mcEventData.cardTypeName = 'item';
mcEventData.cardName = cardData.syncGroup || null;
mcEventData.cardID = cardData.compositeId || null;
break;
default:
iaw.log.console('Invalid Ingest Mobile Creations '+action+' event');
mcEventData = null;
break;
}
return mcEventData;
},
/**
* Log a user clicked event for Mobile Creations to Ingest.
*
* @param mode string object indicating the event mode
* @param cardData JSON object containing the data to log
*/
logMobileCreationsRenderedEvent: function(mode, cardData) {
var mcEventData = this.constructMobileCreationsEventData(mode, 'render', cardData);
if (mcEventData) {
this.logMobileCreationsEvent(mcEventData);
}
},
/**
* Log a user clicked event for Mobile Creations to Ingest.
*
* @param mode string object indicating the event mode
* @param cardData JSON object containing the data to log
*/
logMobileCreationsClickedEvent: function(mode, cardData) {
var action = (mode !== 'item') ? 'click' : 'open';
var mcEventData = this.constructMobileCreationsEventData(mode, action, cardData);
if (mcEventData) {
this.logMobileCreationsEvent(mcEventData);
}
},
/**
* Log a error event for Mobile Creations to Ingest.
*
* @param mcErrorCode string object indicating the error code
*/
logMobileCreationsErrorEvent: function(mcErrorCode) {
var mcEventData = {
eventAction: 'error',
errorCode : mcErrorCode
};
this.logMobileCreationsEvent(mcEventData);
},
/**
* Log an event from the FNFT dialog.
*
* @param itemData item data to log
*/
logFNFTItemEvent: function( itemData ) {
var ingestPayloadData = this.createPayload();
ingestPayloadData.eventType = 'uc-file-open';
ingestPayloadData.fileOpenType = 'new';
iaw.util.assign(ingestPayloadData, itemData);
this.postEvent(ingestPayloadData);
},
/**
* Log an analytics call for the FNFT grid item render event.
*
* @param itemData grid item's data parameters
* @param section category section ID string
*/
logFNFTItemRenderedEvent: function( itemData ) {
// window.alert(JSON.stringify(itemData));
/*
{
"name":"Custom (1074 x 1394 px @ 72 ppi)",
"tip":"Start a new Custom (1074 x 1394 px @ 72 ppi) document - 1074 x 1394 px",
"group":"",
"width":1074,
"height":1394,
"showInFNFT":false,
"units":"pixelsUnit",
"profile":"sRGB IEC61966-2.1",
"resolution":72,
"resolutionUnits":"inchesUnit",
"depth":8,
"scale":1,
"mode":"RGB",
"fill":"white",
"lastUsedTime":1466528752889,
"isPreset":true,
"id":"",
"title":"Custom (1074 x 1394 px @ 72 ppi)",
"description":"",
"thumbnail_url":"SP_PresetCustom.png",
"mime_type":"image/photoshop",
"price_prompt":"",
"template_category":"recent",
"previews":[],
"uuid":"f707810544c34470a5e048664fcaa5b2"
}
{
"name":"Clipboard",
"tip":"Start a new Clipboard document - 565 x 396 px",
"group":"clipboard",
"width":565,
"height":396,
"showInFNFT":true,
"units":"pixelsUnit",
"profile":"Display",
"resolution":72,
"resolutionUnits":"inchesUnit",
"depth":8,
"scale":1,
"mode":"RGB",
"isPreset":true,
"id":"",
"title":"Clipboard",
"description":"",
"thumbnail_url":"SP_PresetClipboard.png",
"mime_type":"image/photoshop",
"price_prompt":"",
"template_category":"recent",
"previews":[],
"uuid":"e258641a2e264aea9fe9b0692103457a"
}
{
"id":111631092,
"title":"Photo Album Presentation",
"description":"##### Two easy-to-use photo album mockups\r##### What's included:\r* Smart objects\r* Fully editable\r* High resolution\r* Great for presenting book layouts\r\r\r",
"marketing_text":null,
"thumbnail_url":"/Users/mortimer/Library/Caches/Adobe/CCX Welcome/stock/assets/0a529bbf-ce4d-4db3-8d1c-3d851d8ac897.jpeg",
"width":2048,
"height":1424,
"mime_type":"image/vnd.adobe.photoshop.template",
"size":16634446,
"template_category":[
"photo"
],
"previews":[
{
"url":"/Users/mortimer/Library/Caches/Adobe/CCX Welcome/stock/assets/fbc0094f-0e27-4c79-9b76-9e87bc77fb0e.jpeg"
}
],
"units":"inches",
"resolution":"72",
"price_prompt":"free",
"lastUsedTime":0,
"uuid":"a4d794f391274c32a11d02bab2005c75"
}
*/
var itemPayloadData = {
// debugIt: true,
eventAction: 'render',
cardTypeName: itemData.activeFilter,
displayMode: (itemData.displayPosition >= 0) ? itemData.displayPosition : -1,
sectionView: (itemData.group && itemData.group === 'clipboard') ? 'clipboard' : 'blank',
displayTemplate: itemData.isPreset ? 'preset' : 'stock-template',
cardName: itemData.name || itemData.title,
cardID: itemData.id || 'preset'
};
// for templates - add in the price parameter data
if (!itemData.isPreset) {
itemPayloadData.attributes = { price: itemData.price_prompt };
}
this.logFNFTItemEvent(itemPayloadData);
},
/**
* Collected utility methods for setting FNFT sturcture data
*/
fnftUtil: {
/**
* Utility method to set the common parameters related to both Preset and Stock template data.
*
* @param payload target Ingest payload object to update
* @param data source data object
*/
setCommonPayloadParameters: function(payload, data) {
payload.displayMode = data.displayPosition;
payload.cardTypeName = data.activeFilter;
payload.cardName = data.title;
payload.cardID = data.id || 'preset';
},
/**
* Utility method to set the common parameters related to Stock template data.
*
* @param payload target Ingest payload object to update
* @param data source Stock template data object
*/
setCommonStockPayloadParameters: function(payload, data) {
this.setCommonPayloadParameters(payload, data);
payload.displayTemplate = 'stock-template';
payload.attributes = {
price: data.price_prompt
};
},
/**
* Utility method to set the common parameters related to Preset data.
*
* @param payload target Ingest payload object to update
* @param data source preset data object
*/
setCommonPresetPayloadParameters: function(payload, data) {
this.setCommonPayloadParameters(payload, data);
payload.displayTemplate = 'preset';
payload.attributes = { };
this.setAttributesFromPreset(payload.attributes, data);
},
/**
* Utility method to set only the parameters on the 'attributes' object that we care about.
*
* @param attributes target preset attributes object to update
* @param preset source preset data object
*/
setAttributesFromPreset: function(attributes, preset) {
for (var key in preset) {
switch (key) {
case 'width':
case 'height':
case 'mode':
case 'units':
case 'profile':
case 'fill':
case 'resolution':
case 'resolutionUnits':
attributes[key] = preset[key];
break;
default:
break;
}
}
}
},
/**
* Log an analytics call for the FNFT action event.
*
* @param actionData grid item's data parameters
*/
logFNFTActionClickedEvent: function( action, actionData ) {
var actionPayloadData = {
// debugIt: true,
eventAction: 'click',
ucAction: action
};
actionData = actionData || {};
switch (action) {
case 'preset-selected':
this.fnftUtil.setCommonPresetPayloadParameters(actionPayloadData, actionData);
delete actionPayloadData.ucAction;
break;
case 'template-selected':
this.fnftUtil.setCommonStockPayloadParameters(actionPayloadData, actionData);
delete actionPayloadData.ucAction;
break;
case 'stock-search':
actionPayloadData.displayTemplate = 'stock-template';
actionPayloadData.cardTypeName = actionData.activeFilter;
actionPayloadData.actionURL = actionData.actionURL + '&as_content=ccxstart-search';
break;
case 'stock-sidebar':
this.fnftUtil.setCommonStockPayloadParameters(actionPayloadData, actionData);
actionPayloadData.actionURL = actionData.actionURL + '&as_content=ccxstart-sidebar';
break;
case 'render-preview':
actionPayloadData.eventAction = 'render';
actionPayloadData.ucAction = 'preview';
this.fnftUtil.setCommonStockPayloadParameters(actionPayloadData, actionData);
break;
case 'close-template':
actionPayloadData.ucAction = 'close';
this.fnftUtil.setCommonStockPayloadParameters(actionPayloadData, actionData);
break;
case 'open-template':
actionPayloadData.ucAction = 'open';
this.fnftUtil.setCommonStockPayloadParameters(actionPayloadData, actionData);
break;
case 'preview':
case 'preview-close':
case 'preview-back':
case 'license-template':
case 'download-start':
case 'download-end':
case 'download':
this.fnftUtil.setCommonStockPayloadParameters(actionPayloadData, actionData);
break;
case 'too-large-render':
this.fnftUtil.setCommonStockPayloadParameters(actionPayloadData, actionData);
actionPayloadData.ucAction = 'download-large';
actionPayloadData.eventAction = 'render';
break;
case 'close':
this.fnftUtil.setCommonPresetPayloadParameters(actionPayloadData, actionData);
break;
case 'preset-viewmore':
this.fnftUtil.setCommonPresetPayloadParameters(actionPayloadData, actionData);
actionPayloadData.ucAction = 'viewmore';
delete actionPayloadData.attributes;
break;
case 'preset-create':
this.fnftUtil.setCommonPresetPayloadParameters(actionPayloadData, actionData);
actionPayloadData.attributesChanged = false;
actionPayloadData.ucAction = 'create';
// check for the 'settings' data added which indicates a preset MAY have changed
if (actionData.settings && Object.keys(actionData.settings).length !== 0 && actionData.settings.constructor === Object) {
actionPayloadData.attributesChanged = actionData.settings.attributesChanged || false;
// only add changed attributes if there were actual changes
if (actionPayloadData.attributesChanged) {
actionPayloadData.newAttributes = {};
this.fnftUtil.setAttributesFromPreset(actionPayloadData.newAttributes, actionData.settings);
}
}
break;
case 'more-options':
this.fnftUtil.setCommonPresetPayloadParameters(actionPayloadData, actionData);
iaw.util.assign(actionPayloadData, actionData.presetAttributes);
break;
case 'tab-selected':
actionPayloadData.cardTypeName = actionData.activeFilter;
break;
case 'welcome-rendered':
actionPayloadData.displayTemplate = 'welcome';
actionPayloadData.eventAction = 'render';
actionPayloadData.displayCount = actionData.displayCount;
actionPayloadData.cardTypeName = actionData.activeFilter;
actionPayloadData.ucAction = 'render';
break;
case 'welcome-closed':
actionPayloadData.displayTemplate = 'welcome';
actionPayloadData.cardTypeName = actionData.activeFilter;
actionPayloadData.ucAction = 'close';
break;
case 'resize-dialog':
actionPayloadData.eventAction = 'resize';
actionPayloadData.displayTemplate = actionData.displayTemplate;
actionPayloadData.cardName = actionData.cardName;
actionPayloadData.cardTypeName = actionData.activeFilter;
if (actionData.displayMode >= 0) {
actionPayloadData.displayMode = actionData.displayMode;
}
actionPayloadData.attributes = actionData.attributes;
delete actionPayloadData.ucAction;
break;
default:
break;
}
this.logFNFTItemEvent(actionPayloadData);
}
} // end of ingest container
};
/*************************************************************************
* ADOBE CONFIDENTIAL
* ___________________
*
* Copyright 2015 Adobe Systems Incorporated
* All Rights Reserved.
*
* NOTICE: All information contained herein is, and remains
* the property of Adobe Systems Incorporated and its suppliers,
* if any. The intellectual and technical concepts contained
* herein are proprietary to Adobe Systems Incorporated and its
* suppliers and are protected by all applicable intellectual property
* laws, including trade secret and copyright laws.
* Dissemination of this information or reproduction of this material
* is strictly forbidden unless prior written permission is obtained
* from Adobe Systems Incorporated.
**************************************************************************/
/**
* Simple singleton style container for holding the internationalization tables (I18N).
*/
var iaw = iaw || { };
iaw.i18n = {
localizedStringTable: null,
urlLocalizationTable: null,
/**
* Normalize the Adobe locale to a standardized one. Basically converts
* a locale ID like ja_JP to ja-jp.
*
* @param localeID locale ID string to normalize
* @return the normalized locale ID string
*/
normalizeLocaleID: function(localeID) {
return localeID ? localeID.toLowerCase().replace('_', '-') : localeID;
},
/**
* Initialize the table from a file in JSON format.
*
* @param localeID string containing the locale to load
* @param continueFn continuation function callback
*/
addFromLocalLocaleFile: function(localeID, continueFn) {
// Reuse the cached strings if they are available.
var strings = iaw.localstorage.getGlobalItem('localeStrings');
if (strings && strings.locale === localeID && strings.version === window.buildVersion) {
this.localizedStringTable = strings.data;
// update html language tag for accessibility
document.getElementsByTagName('html')[0]
.setAttribute('lang', localeID.toLowerCase().replace(/_/, '-'));
if (continueFn) {
continueFn();
}
}
else {
var self = this;
var xmlhttp = new XMLHttpRequest();
xmlhttp.overrideMimeType('application/json');
xmlhttp.onreadystatechange = function() {
if (xmlhttp.readyState === 4 && xmlhttp.responseText) {
// xmlhttp.status == 200 && // local file will return status code 0
if (xmlhttp.responseText) {
self.localizedStringTable = JSON.parse(xmlhttp.responseText);
iaw.localstorage.setGlobalItem('localeStrings', {
locale: localeID,
data: self.localizedStringTable,
version: window.buildVersion
});
}
// update html language tag for accessibility
document.getElementsByTagName('html')[0]
.setAttribute('lang', localeID.toLowerCase().replace(/_/, '-'));
if (continueFn) {
continueFn();
}
}
};
xmlhttp.open('GET', './locale/' + localeID + '/strings.json', true);
xmlhttp.send();
}
},
/**
* Add a localized string to the object's string table.
*
* @param stringID string representing the index key into the table
* @param value string value to store with associated key
*/
addLocalizedString: function( stringID, value ) {
if ( stringID ) { this.localizedStringTable[stringID] = value; }
},
/**
* Retrieve a localized string to the object's string table. If the string
* is not present, then a standard error string is returned instead.
*
* @param stringID string representing the index key into the table
* @return String value to associated with the key
*/
getLocalizedString: function( stringID ) {
var locStr = this.localizedStringTable && stringID ? this.localizedStringTable[stringID] : null;
var newStr = locStr ? locStr.replace('^n', '<br>') : '';
return newStr;// debug ? 'NON-LOCALIZED:'+stringID : '';
},
/**
* Retrieve a localized string to the object's string table. If the string
* is not present, then a standard error string is returned instead.
*
* @param stringID string representing the index key into the table
* @return String value to associated with the key
*/
getLocalizedSubstitutionString: function( stringID, subArray ) {
var locStr = (this.localizedStringTable && stringID) ? this.localizedStringTable[stringID] : null;
if ( locStr && subArray ) {
for ( var index = 0; index < subArray.length; index++ ) {
locStr = locStr.replace('^'+index, subArray[index] );
}
}
else {
locStr = '';// debug ? 'NON-LOCALIZED:'+stringID : '';
}
return locStr;
},
/**
* Change a document element display string to a translated string.
*
* @param elementID document element ID
* @param stringID translation key-string ID
* @param dontLocalize boolean to indicate not to localize
*/
setDocumentElementString : function( elementID, stringID, dontLocalize ) {
stringID = !stringID ? elementID : stringID;
var elem = document.getElementById( elementID );
if ( elem ) {
elem.innerHTML = !dontLocalize ? this.getLocalizedString( stringID ) : stringID;
}
},
/**
* Initialize the table from a file in JSON format.
*
* @param type string containing the url type.
*/
addFromURLFile: function(type) {
// Reuse the cached strings if they are available.
if (type === 'goURL') {
var strings = iaw.localstorage.getGlobalItem('goURL');
if (strings && strings.version === window.buildVersion) {
this.urlLocalizationTable = strings.data;
return;
}
}
var self = this;
var xmlhttp = new XMLHttpRequest();
xmlhttp.overrideMimeType('application/json');
xmlhttp.onreadystatechange = function() {
if ( xmlhttp.readyState === 4 && xmlhttp.responseText ) {
// xmlhttp.status == 200 && // local files will return status code 0
if ( xmlhttp.responseText ) {
try {
self.urlLocalizationTable = JSON.parse( xmlhttp.responseText );
}
catch (e) {
// window.alert('i18n table parse error: '+e.message);
iaw.log.exception('i18n go URL table parse error: ' + e.message);
return;
}
iaw.localstorage.setGlobalItem('goURL', {
version: window.buildVersion,
data: self.urlLocalizationTable
});
}
}
};
if (type === 'goURL' ) {
xmlhttp.open('GET', './locale/'+'goURL.json', true);
}
xmlhttp.send();
},
/**
* Get language id for help url.
*
* @param appLang string containing language id xx_XX from app, such like, en_US
* @return String value to associated with the app language.
*/
getLangSuffixForHelpURL : function( appLang ) {
var helpURL = this.urlLocalizationTable &&
this.urlLocalizationTable.help &&
appLang ? this.urlLocalizationTable.help[appLang] : null;
return helpURL ? helpURL : 'en';
},
/**
* Get language id for account url.
*
* @param appLang string containing language id xx_XX from app, such like, en_US
* @return String value to associated with the app language.
*/
getLangSuffixForAccountURL : function( appLang ) {
var accountURL = this.urlLocalizationTable &&
this.urlLocalizationTable.account &&
appLang ? this.urlLocalizationTable.account[appLang] : null;
return accountURL ? accountURL : 'en';
},
/**
* Get language id for mobile creations store url.
*
* @param appLang string containing language id xx_XX from app, such like, en_US
* @return String value to associated with the app language.
*/
getLangSuffixForMobileCreationsGoURL : function( appLang ) {
var suffix = this.urlLocalizationTable &&
this.urlLocalizationTable.mobilecreations &&
appLang ? this.urlLocalizationTable.mobilecreations[appLang] : null;
return (suffix && suffix !== 'en') ? '_'+suffix : ''; // for enUS default is just empty
}
};
/*************************************************************************
* ADOBE CONFIDENTIAL
* ___________________
*
* Copyright 2015 Adobe Systems Incorporated
* All Rights Reserved.
*
* NOTICE: All information contained herein is, and remains
* the property of Adobe Systems Incorporated and its suppliers,
* if any. The intellectual and technical concepts contained
* herein are proprietary to Adobe Systems Incorporated and its
* suppliers and are protected by all applicable intellectual property
* laws, including trade secret and copyright laws.
* Dissemination of this information or reproduction of this material
* is strictly forbidden unless prior written permission is obtained
* from Adobe Systems Incorporated.
**************************************************************************/
var iaw = iaw || { };
iaw.json = {
/**
* Read and parse a JSON file from the local file system disk.
*
* @param path string containing the full path to the file to process
* @return Processed JSON as an object or null.
*/
readLocalJSONFile: function(path) {
var obj = null;
if (window.__adobe_cep__ && path) {
var result = window.cep.fs.readFile(path);
if (0 === result.err) {
try {
obj = JSON.parse(result.data);
}
catch (error) {
iaw.log.exception('Failed to read JSON file with path [' + path + '], error=' + error);
obj = null;
}
}
else if (3 === result.err) {
iaw.log.console('Attempt to read JSON file failed, file does not exist [' + path + ']');
}
else {
iaw.log.console('Attempt to read JSON file failed with path [' + path + '], error code=' + result.err);
}
}
return obj;
},
/**
* Preprocess the JSON data common to all CCX extensions.
*
* @param key data key name string
* @param val key's value
* @return A possibly modified 'val' parameter.
*/
commonDataReceiver: function(key, val) {
switch (key) {
case 'userTrackingEnabled':
if (typeof val === 'string') {
val = (val === 'true' || val === '1');
}
iaw.analytics.enabled = val; // enable/disable analytics
break;
case 'fnftEnabled':
if (typeof val === 'string') {
val = (val === 'true' || val === '1');
}
break;
case 'shortcut':
val = val.replace(/Cmd\+/, '⌘');
break;
case 'language':
// special case en_IL & en_AE to remap for en_US
val = (val !== 'en_IL' && val !== 'en_AE') ? val : 'en_US';
// val = 'ja_JP'; // force Japanese
// val = 'fr_FR'; // force French
// val = 'de_DE'; // force German
break;
}
return val;
},
/**
* Preprocess the JSON data specific for CCX-Start.
*
* @param key data key name string
* @param val key's value
* @return A possibly modified 'val' parameter.
*/
startDataReceiver: function(key, val) {
switch (key) {
case 'thumbnailViewEnabled':
case 'listViewEnabled':
if (typeof val === 'string') {
val = (val === 'true' || val === '1');
}
break;
case 'size':
val = parseInt(val);
break;
case 'thumb':
if (val) {
// strip empty thumbnails
if (val === 'data:image/jpeg;base64,') {
val = '';
}
// strip newlines inserted by JSON.parse
else {
val = val.replace(/\n/g, '');
}
}
break;
case 'icon':
if (val.indexOf('SP_Preset') < 0 && val.indexOf('SP_QuickStart') < 0) {
switch (val) {
case 'aep':
case 'pr_proj_primary':
case 'pr_convert_premiere_clip':
val = 'CCX_Start_DefaultThumb_Pr_Ae';
break;
case 'ai':
case 'psd':
case 'id':
case 'id_library':
case 'id_book':
val = 'CCX_Start_DefaultThumb_Ps_Ai_Id';
break;
default:
val = 'CCX_Start_DefaultThumb_other';
break;
}
}
break;
case 'dontShowAgain':
if (typeof val === 'string') {
val = (val === 'true' || val === '1');
}
break;
case 'secondsLeftInTrial':
case 'appStartClockTime':
if (typeof val === 'string') {
val = parseInt(val);
}
break;
default:
val = iaw.json.commonDataReceiver(key, val);
break;
}
return val;
},
/**
* Preprocess the JSON data specific for CCX-FNFT.
*
* @param key data key name string
* @param val key's value
* @return A possibly modified 'val' parameter.
*/
fnftDataReceiver: function(key, val) {
switch (key) {
default:
val = iaw.json.commonDataReceiver(key, val);
break;
}
return val;
}
};
/*************************************************************************
* ADOBE CONFIDENTIAL
* ___________________
*
* Copyright 2016 Adobe Systems Incorporated
* All Rights Reserved.
*
* NOTICE: All information contained herein is, and remains
* the property of Adobe Systems Incorporated and its suppliers,
* if any. The intellectual and technical concepts contained
* herein are proprietary to Adobe Systems Incorporated and its
* suppliers and are protected by all applicable intellectual property
* laws, including trade secret and copyright laws.
* Dissemination of this information or reproduction of this material
* is strictly forbidden unless prior written permission is obtained
* from Adobe Systems Incorporated.
**************************************************************************/
/**
* Simple singleton style container so all Libray related stuff has a centralized point.
*/
var iaw = iaw || { };
iaw.libraryManager = {
// global template lookup map
statusLookupMap: {},
hasSyncListener: false,
// download status
DOWNLOADED: 'downloaded',
DOWNLOADING: 'downloading',
DOWNLOAD_ERR: 'error',
// template type and mime type
TEMPLATE_ELEMENT_TYPE: 'application/vnd.adobe.element.template+dcx',
MIME_TYPE: {
'PHXS': 'image/vnd.adobe.photoshop.template',
'ILST': 'application/illustrator.template',
'IDSN': 'application/vnd.adobe.indesign.template'
},
// others
initialized: false,
libraryCollections: null,
debounce: 300, // 0.3 second
lastSyncTime: new Date().valueOf(),
pendingSyncTimeout: null,
checkRunningTimeout: null,
cclibraryProcessLaunchPending: false, // CCLibrary process launch pending
// cache licensed templates
cachedLicensedTemplates: {},
// __________________________________________________________________________
/**
* Listener for library collections loaded
*/
onLibraryCollectionsLoaded: function() {
clearTimeout(iaw.libraryManager.checkRunningTimeout);
iaw.libraryManager.libraryCollections = ccLibraries && ccLibraries.getLoadedCollections();
if (iaw.cepUtil.getExtensionID() !== 'com.adobe.ccx.fnft' || !iaw.libraryManager.libraryCollections) {
return;
}
// Update status lookup map for fnft dialog
iaw.libraryManager.getLicensedTemplates(iaw.store.get('host').hostID)
.then(function(templates) {
if (!window.__adobe_cep__) {
iaw.fnftFakeLicensedTemplates.forEach(function(template) {
iaw.libraryManager.statusLookupMap[template.id] = {
templateId: template.id,
downloadStatus: iaw.libraryManager.DOWNLOADED,
path: template.url
};
});
return;
}
if (!iaw.fnftFakeLicensedTemplates) {
iaw.libraryManager.cachedLicensedTemplates = {};
}
templates.forEach(function(template) {
iaw.libraryManager.statusLookupMap[template.id] = {
templateId: template.id,
downloadStatus: iaw.libraryManager.DOWNLOADED,
path: template.url,
elementRef: template.elementRef
};
});
})
.catch(function() {
// ignore
});
//TODO: Enable this for ccx start screen when we need to detect cc lib changes there too.
// Listen sync changes
iaw.libraryManager.libraryCollections.forEach(function(libraryCollection) {
(function(libraryCollection) {
libraryCollection.addSyncListener(function() {
iaw.libraryManager.onSync(libraryCollection);
});
})(libraryCollection);
});
},
/**
* Load & init CC library
* @param filters an array of element filters, like ['application/vnd.adobe.element.template+dcx']
*/
init: function(filters) {
filters = filters || '*';
if (!iaw.libraryManager.isProcessInstalled()) {
return iaw.log.console('CC Library Process is not installed.');
}
tophat(['cc-libraries-api.min.js'], function() {
if (typeof ccLibraries === 'undefined') {
return iaw.log.console('Fail to load CC Library javascript file.');
}
// Initialize ccLibraries
var DEPENDENCIES = {
log: function(message) {
iaw.log.console(message);
}
};
var OPTIONS = {
SHARED_LOCAL_STORAGE: true,
ELEMENT_TYPE_FILTERS: filters
};
ccLibraries.configure(DEPENDENCIES, OPTIONS);
// VulcanInterface.isAppRunning is not reliable to know the running status. Sometimes it returns false for the running CC Lib Process on Mac.
// We check if ccLibraries.isConnected in 1 second to determine the running status of CC Lib process. this workaround is suggested by CC Library team.
iaw.libraryManager.checkRunningTimeout = setTimeout(function() {
if ((!iaw.libraryManager.isProcessRunning()) && (!ccLibraries.isConnected())) {
iaw.log.console('Start to launch CCLibrary Process');
iaw.libraryManager.launchCCLibraryProcess();
}
}, 1000);
ccLibraries.addLoadedCollectionsListener(iaw.libraryManager.onLibraryCollectionsLoaded);
});
},
/**
* Get template library
*
* @return [Promise] promise that will get "Stock Templates" library
*/
getTemplateLibrary: function() {
return new Promise(function(resolve, reject) {
if (!iaw.libraryManager.isLibraryCollelctionsLoaded()) {
iaw.log.console('Get template library: Fail to load library collections.');
return reject();
}
var libraryCollection = iaw.libraryManager.libraryCollections[0];
var templateLib;
var libraryName = iaw.i18n.getLocalizedString('stock_template_library_name');
libraryCollection.libraries.forEach(function(library) {
if (!templateLib && library.name === libraryName) {
templateLib = library;
}
});
if (!templateLib) {
templateLib = libraryCollection.createLibrary(libraryName);
}
resolve(templateLib);
});
},
/**
* Util to check if representation is in downloading
* @param [Object] representation CC library element's representation
* @return [Boolean] true or false
*/
isRepresentationDownloadPending: function(representation) {
if (!representation.isExternalLink) {
return false;
}
return !representation.getCachedContentPath() && !representation.getCachedExternalLinkError();
},
/**
* Util to check if element is in downloading
*
* @param [Object] element CC library element
* @return [Boolean] true or false
*/
isElementDownloadPending: function(element) {
return element.representations.some(function(representation) {
return iaw.libraryManager.isRepresentationDownloadPending(representation);
});
},
/**
* Util to retry downloading
*
* @param [Object] element CC library element
*/
retryElementDownload: function(element) {
element.representations.forEach(function(representation) {
if (iaw.libraryManager.isRepresentationDownloadPending(representation)) {
representation.getContentPath(); // This triggers the refetch
}
});
},
/**
* Util to get download progress
*
* @param element CC library element
* @return [Number] download progress like 60
*/
getElementDownloadProgress: function(element) {
var downloadProgress;
element.representations.forEach(function(representation) {
if (downloadProgress === undefined) {
downloadProgress = representation.getExternalLinkDownloadProgress();
}
});
return downloadProgress;
},
/**
* Hook CC library sync listener
*/
onSync: function(libraryCollection) {
if (iaw.libraryManager.pendingSyncTimeout) {
return;
}
var currentTime = new Date().valueOf();
if (currentTime - iaw.libraryManager.lastSyncTime < iaw.libraryManager.debounce) {
(function(libraryCollection) {
iaw.libraryManager.pendingSyncTimeout = setTimeout(function() {
iaw.libraryManager.pendingSyncTimeout = undefined;
iaw.libraryManager.onSync(libraryCollection);
}, iaw.libraryManager.debounce);
})(libraryCollection);
return;
}
iaw.libraryManager.lastSyncTime = currentTime;
var statusLookupMap = iaw.libraryManager.statusLookupMap;
libraryCollection.libraries.forEach(function(library) {
library.getFilteredElements(iaw.libraryManager.TEMPLATE_ELEMENT_TYPE).forEach(function(element) {
if (iaw.stockUtil.getElementStockLicense(element) === undefined) return;
var id = iaw.stockUtil.getElementStockId(element);
var status = statusLookupMap[id] || {templateId: id};
var oldProgress = status.progress;
status.progress = iaw.libraryManager.getElementDownloadProgress(element);
var oldPending = status.downloadPending;
status.downloadPending = iaw.libraryManager.isElementDownloadPending(element);
var logPrefix = 'Stock template [' + status.templateId + '] ';
// TODO: Split to onDownloaded, onDownloading, onDownloadError function
if (status.downloadPending !== oldPending || status.progress !== oldProgress) {
if (status.progress === undefined || (oldPending && !status.downloadPending)) {
// downloaded
// Filter out any odd progress event(s) from Library. Usually, it happens at parallel downloadings.
if (oldProgress && status.progress && status.progress < oldProgress) return;
element.getPrimaryRepresentation().getContentPath(function(err, path) {
if (err || (status.downloadStatus === iaw.libraryManager.DOWNLOADED && status.path === path)) return;
if (status.downloadStatus !== iaw.libraryManager.DOWNLOADED) {
var templateData = iaw.util.getTemplateData(id) || {};
iaw.analytics.ingest.logFNFTActionClickedEvent('download-end', templateData);
iaw.log.console(logPrefix + 'is downloaded.');
// update template data in data store.
if (templateData) {
templateData.licensedTime = element.modified;
templateData.lastUsedTime = iaw.localstorage.getUserItem('templateLUT_' + id) || element.modified;
templateData.template_category = templateData.template_category || [];
if (templateData.template_category.indexOf('saved') === -1) {
templateData.template_category.push('saved');
}
}
}
status.downloadStatus = iaw.libraryManager.DOWNLOADED;
status.path = path;
status.elementRef = element.getReference();
iaw.store.set(['input', 'download-status'], status);
statusLookupMap[id] = status;
});
return;
}
else if (status.progress === -1) {
// download error
if (status.downloadStatus === iaw.libraryManager.DOWNLOAD_ERR) return;
status.downloadStatus = iaw.libraryManager.DOWNLOAD_ERR;
iaw.log.console(logPrefix + 'fail to be downloaded.');
iaw.store.set(['input', 'download-status'], status);
}
else if ((status.progress >= oldProgress) && status.downloadPending) {
// downloading
status.downloadStatus = iaw.libraryManager.DOWNLOADING;
status.licensedTime = element.modified;
status.lastUsedTime = element.modified;
iaw.log.console(logPrefix + 'is being downloaded, progress=' + status.progress);
iaw.store.set(['input', 'download-status'], status);
}
statusLookupMap[id] = status;
}
else if (status.downloadPending && (statusLookupMap[id].downloadStatus !== iaw.libraryManager.DOWNLOADING)) {
iaw.analytics.ingest.logFNFTActionClickedEvent('download-start', iaw.util.getTemplateData(id) || {});
status.downloadStatus = iaw.libraryManager.DOWNLOADING;
status.progress = 0;
statusLookupMap[id] = status;
iaw.store.set(['input', 'download-status'], status);
}
});
});
},
/**
* Build up template object based on CC library element
*
* @param [Object] element cc library element
* @param [Boolean] checkPurchased flag indicating to check purchase/license status or not
* @param [String] mime_type mime type of template
* @return [Promise] promise that will build up template in desired format
*/
buildupTemplate: function(element, checkPurchased, mime_type) {
return new Promise(function(resolve, reject) {
if (!element || (checkPurchased && iaw.stockUtil.getElementStockLicense(element) === undefined) || iaw.libraryManager.isElementDownloadPending(element)) {
return reject();
}
var representation = element.getPrimaryRepresentation();
if (!representation) return reject();
representation.getContentPath(function(err, templatePath) {
if (err) return reject(err);
element.getThumbnailPath(202, function(err, thumbnailPath) {
if (err) {
thumbnailPath = null;
}
var id = iaw.stockUtil.getElementStockId(element);
resolve({
id: id,
mime_type: mime_type,
template_category: ['saved', 'recent'],
title: element.name,
created: element.created,
licensedTime: element.modified,
modified: element.modified,
lastUsedTime: iaw.localstorage.getUserItem('templateLUT_' + id) || element.modified,
description: iaw.i18n.getLocalizedString('newdoc_details_template_default'),
'thumbnail_url': thumbnailPath,
width: representation.width,
height: representation.height,
url: templatePath,
size: representation.contentLength,
elementRef: element.getReference(),
previews: []
});
});
});
});
},
/**
* Util to check if CC library is connected.
*
* @return [Boolean] true or false.
*/
isConnected: function() {
return ccLibraries.isConnected();
},
/**
* Util to get CC library service info.
*
* @return [Object] service info.
*/
getServiceInfo: function() {
return ccLibraries.getServiceInfo();
},
/**
* Util to check if CC library Process is installed
*
* @return [Boolean] true or false.
*/
isProcessInstalled: function() {
return window.__adobe_cep__ ? VulcanInterface.isAppInstalled('cclibraries') : false;
},
// __________________________________________________________________________
/**
* Public API
*/
/**
* Check if CC Library process is running or not.
*
* @return [Boolean] true or false.
*/
isProcessRunning: function() {
return VulcanInterface.isAppRunning('cclibraries');
},
/**
* Launch CC library process.
*/
launchCCLibraryProcess: function() {
if (iaw.libraryManager.cclibraryProcessLaunchPending) {
return;
}
var interval;
var message = 'vulcan.SuiteMessage.cclibraries.service.Initialized';
function started() {
if (interval) {
clearInterval(interval);
}
iaw.libraryManager.cclibraryProcessLaunchPending = false;
ccLibraries.reconnect();
VulcanInterface.removeMessageListener(message, started);
// Wait 1s, then check if we're still connected - if not, need to retry
setTimeout(function() {
if (!iaw.libraryManager.isConnected()) {
iaw.libraryManager.launchCCLibraryProcess();
}
}, 1000);
}
function callLaunch() {
// If we happen to already be connected then abort the interval
if (iaw.libraryManager.isConnected()) {
started();
return;
}
// NOTE: We never try to launch the CC Library process if an update is required (either to the panel or process).
// Unfortunately, Vulcan.isAppRunning always returns false on the Mac, which can mean we keep on trying to start the
// process even though it's already running, and this causes 2 second hangs on the main thread in the desktop products.
var serviceInfo = iaw.libraryManager.getServiceInfo() || {};
if (!serviceInfo.updateRequired && iaw.libraryManager.isProcessInstalled() && !iaw.libraryManager.isProcessRunning()) {
iaw.log.console('Launching CCLibrary Process');
VulcanInterface.launchApp('cclibraries', false);
}
}
VulcanInterface.addMessageListener(message, started);
ccLibraries.reconnect();
iaw.libraryManager.cclibraryProcessLaunchPending = true;
interval = setInterval(callLaunch, 5000);
callLaunch();
},
/**
* Get licensed templates per hostID.
*
* @param [String] hostID host id, like 'PHXS'
* @return [Promise] promise that will resolve licensed templates
*/
getLicensedTemplates: function(hostID) {
return new Promise(function(resolve, reject) {
if (!window.__adobe_cep__) {
return resolve(iaw.fnftFakeLicensedTemplates);
}
function onFinish(templates) {
templates = templates.filter(function(item, pos) {
return templates.indexOf(item) == pos;
});
resolve(templates);
}
if (!iaw.libraryManager.isLibraryCollelctionsLoaded()) {
iaw.log.console('Get licensed templates: Fail to load library collections.');
return reject();
}
var elements = [];
var libraries = [];
iaw.libraryManager.libraryCollections.forEach(function(libraryCollection) {
libraries = libraries.concat(libraryCollection.libraries);
});
libraries.forEach(function(library) {
elements = elements.concat(library.getFilteredElements(iaw.libraryManager.TEMPLATE_ELEMENT_TYPE));
});
// Filter out licensed templates via mime_type.
// For Photoshop: it's application/photoshop.template
// For Illustrator: it's application/illustrator.template'
var mime_type = iaw.libraryManager.MIME_TYPE[hostID];
if (mime_type) {
elements = elements.filter(function(element) {
return element.getPrimaryRepresentation().isCompatibleType(mime_type);
});
}
var callCount = 0;
var templates = [];
if (!elements || elements.length === 0) {
return onFinish([]);
}
// Build up the desired licensed template array.
elements.forEach(function(element) {
// build up template obj.
iaw.libraryManager.buildupTemplate(element, true, mime_type)
.then(function(template) {
templates.push(template);
if (++callCount === elements.length) {
onFinish(templates);
}
})
.catch(function() {
if (++callCount === elements.length) {
onFinish(templates);
}
});
});
});
},
/**
* Get templates per elements reference.
*
* @param [Array] elementRefs an array of elements reference
* @return [Promise] promise that will resolve templates
*/
elementRefsToTemplates: function(elementRefs) {
return new Promise(function(resolve, reject) {
if (!elementRefs) {
return reject();
}
if (!window.__adobe_cep__) {
return resolve(iaw.fnftFakeLicensedTemplates);
}
function onFinish(templates) {
resolve(templates);
}
if (!iaw.libraryManager.isLibraryCollelctionsLoaded()) {
iaw.log.console('ElementRefs to templates: Fail to load library collections.');
return reject();
}
var callCount = 0;
var elements = [];
elementRefs.forEach(function(elementRef) {
if (!elementRef) return;
elements.push(ccLibraries.resolveElementReference(elementRef));
});
var templates = [];
if (!elements || elements.length === 0) {
return onFinish([]);
}
elements.forEach(function(element) {
// build up template obj.
iaw.libraryManager.buildupTemplate(element)
.then(function(template) {
templates.push(template);
if (++callCount === elements.length) {
onFinish(templates);
}
})
.catch(function() {
if (++callCount === elements.length) {
onFinish(templates);
}
});
});
});
},
/**
* Get template path per library Id.
*
* @param [Object] libraryCollection library collection
* @param [String] stockTemplateId stock template id
* @return [Promise] promise that will resolve path of template
*/
getTemplatePath: function(libraryCollection, stockTemplateId) {
return new Promise(function(resolve, reject) {
if (!window.__adobe_cep__) {
var path;
iaw.fnftFakeLicensedTemplates.forEach(function(template) {
if (template.id === stockTemplateId) {
path = template.url;
}
});
return path;
}
var isFound = false;
libraryCollection.libraries.forEach(function(library) {
library.elements.forEach(function(element) {
var id = iaw.stockUtil.getElementStockId(element);
if (id === stockTemplateId && !isFound) {
isFound = true;
element.getPrimaryRepresentation().getContentPath(function(err, path) {
resolve(path);
});
}
});
});
if (!isFound) {
resolve();
}
});
},
/**
* Check if temlate is licensed or not.
*
* @param [String] id template id
* @return [Boolean] true or false.
*/
isTemplateLicensed: function(id) {
if (!window.__adobe_cep__) {
var license = false;
iaw.fnftFakeLicensedTemplates.forEach(function(template) {
if (template.id === id) {
license = true;
}
});
return license;
}
if (iaw.libraryManager.cachedLicensedTemplates && Object.keys(iaw.libraryManager.cachedLicensedTemplates).length > 0) {
if (iaw.libraryManager.cachedLicensedTemplates[id] !== undefined) {
return true;
}
}
return iaw.libraryManager.statusLookupMap[id] && (iaw.libraryManager.statusLookupMap[id].downloadStatus === iaw.libraryManager.DOWNLOADED);
},
/**
* Check if temlate is in downloading.
*
* @param [String] id template id
* @return [Boolean] true or false.
*/
isTemplateDownloading: function(id) {
return iaw.libraryManager.statusLookupMap[id] && (iaw.libraryManager.statusLookupMap[id].downloadStatus === iaw.libraryManager.DOWNLOADING);
},
/**
* Get the status of template.
*
* @param [String] id template id
* @return [Object] template status.
*/
getTemplateStatus: function(id) {
return iaw.libraryManager.statusLookupMap[id];
},
/**
* Set status for template.
*
* @param [String] id template id
* @param [Object] status template status
*/
setTemplateStatus: function(id, status) {
return iaw.libraryManager.statusLookupMap[id] = status;
},
/**
* Get element reference by template Id
*
* @param [String] id template id
* @return [String] element reference
*/
getElementRefById: function(id) {
return iaw.libraryManager.statusLookupMap[id] && iaw.libraryManager.statusLookupMap[id].elementRef;
},
/**
* Get template Id by element reference
*
* @param [String] elementRef element reference
* @return [String] template id
*/
getIdByElementRef: function(elementRef) {
if (typeof ccLibraries !== 'undefined') {
var element = ccLibraries && ccLibraries.resolveElementReference(elementRef);
return element && iaw.stockUtil.getElementStockId(element);
}
return null;
},
/**
* Check if cc library collections get loaded
*
* @return [Boolean] true or false.
*/
isLibraryCollelctionsLoaded: function() {
return (iaw.libraryManager.libraryCollections && iaw.libraryManager.libraryCollections.length > 0);
},
/**
* Get the cached licensed templates using the lookup file.
*
* @param [String] hostID host id, like 'PHXS'
* @return [Promise] promise that will resolve licensed templates
*/
getCachedTemplates: function(hostID, resolve) {
if (iaw.fnftFakeLicensedTemplates) {
var templates = [];
iaw.fnftFakeLicensedTemplates.forEach(function(template) {
if (template['mime_type'] === iaw.libraryManager.MIME_TYPE[hostID]) {
templates.push(template);
iaw.libraryManager.cachedLicensedTemplates[template.id] = template;
}
});
return resolve(templates);
}
var licensedTemplates = [];
var csInterface = new CSInterface();
var lookupFilePath = csInterface.getSystemPath(SystemPath.USER_DATA) + '/Adobe/Creative Cloud Libraries/LIBS/librarylookupfile';
var lookupMapJSON = iaw.json.readLocalJSONFile(lookupFilePath);
if (lookupMapJSON && lookupMapJSON.libraries) {
Object.keys(lookupMapJSON.libraries).forEach(function(libId) {
var lib = lookupMapJSON.libraries[libId];
if (!lib || !lib.elements) return;
Object.keys(lib.elements).forEach(function(elementId) {
var element = lib.elements[elementId];
if (!element || !element.reps) return;
var template = {
id: element.stockId,
template_category: ['saved', 'recent'],
title: element.name,
created: element.mod, // no creation time so set modified time
licensedTime: element.mod,
modified: element.mod,
lastUsedTime: iaw.localstorage.getUserItem('templateLUT_' + element.stockId) || element.mod,
description: iaw.i18n.getLocalizedString('newdoc_details_template_default'),
elementRef: 'cloud-asset://' + lib.domain + lib.path + '/' + libId + ';node=' + elementId,
previews: []
};
var isWindows = iaw.store.get(['host', 'platform']) === 'win' || iaw.store.get(['host', 'platform']) === 'windows' || false;
element.reps.forEach(function(rep) {
if (!rep.path) {
return;
}
if (rep && (rep.type === iaw.libraryManager.MIME_TYPE[hostID])) {
template['mime_type'] = rep.type;
if (isWindows) {
template.url = rep.path.replace(/\\/g, '/');
}
else {
template.url = rep.path;
}
}
else if (rep && (rep.type === 'image/jpeg')) {
if (isWindows) {
template['thumbnail_url'] = rep.path.replace(/\\/g, '/');
}
else {
template['thumbnail_url'] = rep.path;
}
}
});
if (template['mime_type'] && template.url) {
licensedTemplates.push(template);
}
});
});
}
licensedTemplates.forEach(function(template) {
iaw.libraryManager.cachedLicensedTemplates[template.id] = template;
});
resolve(licensedTemplates);
},
/**
* Get template id from elementRef of cached templates.
*
* @param [String] elementRef element reference
* @return [String] template id
*/
getIdByCachedElementRef: function(elementRef) {
var templates = iaw.libraryManager.cachedLicensedTemplates;
var keys = Object.keys(templates);
if (templates && keys.length > 0) {
for (var i=0; i<keys.length; i++) {
var key = keys[i];
if (templates[key].elementRef === elementRef) {
return templates[key].id;
}
}
}
return null;
}
};
/*************************************************************************
* ADOBE CONFIDENTIAL
* ___________________
*
* Copyright 2015 Adobe Systems Incorporated
* All Rights Reserved.
*
* NOTICE: All information contained herein is, and remains
* the property of Adobe Systems Incorporated and its suppliers,
* if any. The intellectual and technical concepts contained
* herein are proprietary to Adobe Systems Incorporated and its
* suppliers and are protected by all applicable intellectual property
* laws, including trade secret and copyright laws.
* Dissemination of this information or reproduction of this material
* is strictly forbidden unless prior written permission is obtained
* from Adobe Systems Incorporated.
**************************************************************************/
/**
* Simple singleton style utility container so all local storage related
* stuff has a centralized wrapper.
*/
var iaw = iaw || { };
iaw.localstorage = {
userID: null,
/**
* Set a value to the local storage for a specified userID.
*
* @param userID user's associated ID value
* @param key local store key
* @param val value to store
*/
setUserItem: function( key, val ) {
if ( this.userID && key ) {
// iaw.log.console('[iaw.localstorage.setUserItem] Setting '+key+' to '+val);
window.localStorage[this.userID+'_'+key] = JSON.stringify(val);
}
},
/**
* Get a value from local storage for a specified userID.
* If the value is not found, a null object is returned.
*
* @param userID user's associated ID value
* @param key local store key
* @return The stored value or null if not present.
*/
getUserItem: function( key ) {
var val = null;
if ( this.userID && key ) {
val = window.localStorage[this.userID+'_'+key] || null;
}
try {
val = JSON.parse(val);
}
catch (err) {
val = null;
}
return val;
},
/**
* Set a value to the local storage.
*
* @param key local store key
* @param val value to store
*/
setGlobalItem: function( key, val ) {
if ( key ) {
window.localStorage[key] = JSON.stringify(val);
}
},
/**
* Get a value from local storage.
* If the value is not found, a null object is returned.
*
* @param key local store key
* @return The stored value or null if not present.
*/
getGlobalItem: function( key ) {
var val = null;
if ( key ) {
val = window.localStorage[key] || null;
}
try {
val = JSON.parse(val);
}
catch (err) {
val = null;
}
return val;
}
};
/*************************************************************************
* ADOBE CONFIDENTIAL
* ___________________
*
* Copyright 2015 Adobe Systems Incorporated
* All Rights Reserved.
*
* NOTICE: All information contained herein is, and remains
* the property of Adobe Systems Incorporated and its suppliers,
* if any. The intellectual and technical concepts contained
* herein are proprietary to Adobe Systems Incorporated and its
* suppliers and are protected by all applicable intellectual property
* laws, including trade secret and copyright laws.
* Dissemination of this information or reproduction of this material
* is strictly forbidden unless prior written permission is obtained
* from Adobe Systems Incorporated.
**************************************************************************/
/**
* Simple singleton style container so all logging related stuff has
* a centeralized wrapper.
*/
var iaw = iaw || { };
iaw.log = {
/**
* Boolean flag to globally enable/disable logging.
*/
enabled: true,
/**
* Default log prefix.
*/
kLogPrefix: 'CCX-Start',
/**
* Send a message to the log. An optional prefix can be passed which will get
* prepended to the message.
*
* @param msg string object containing the log message
* @param optParam optional object containing either a string to prepend
* to the message or an object containing multiple display
* options. the format is:
* { prefix: 'string', indent: number, alert: true, trace: false }
*/
console : function(msg, optParam) {
var kIndentation = ' ';
var prefix = '';
var depth = 0;
var trace = false;
// parse out the optional parameters
if (typeof optParam === 'object') {
prefix = (optParam.prefix) ? ' - ' + optParam.prefix : prefix;
depth = (optParam.indent) ? optParam.indent : depth;
trace = (optParam.trace) ? optParam.trace : trace;
}
else if (typeof optParam === 'string') {
prefix = ' - ' + optParam;
}
// build up the default message
var logmsg = this.kLogPrefix+prefix+' :: ';
// add indentation if requested
for (; depth > 0; depth--) { logmsg += kIndentation; }
logmsg += msg;
// dump to the log file
console.log(logmsg);
// dump the stack so we know what happened
if (trace) console.trace();
},
/**
* Dumps a JavaScript object out to the log. An optional prefix can be passed which will get
* prepended to the message. This only works in the Chrome debugger.
*
* @param msg string object containing the log message
* @param prefix optional object containing a string to prepend to the message
*/
dump : function(obj, prefix) {
console.log('<< '+this.kLogPrefix+' >> object: '+ ((prefix) ? prefix + ' - %o' : '%o'), obj);
},
/**
* Shortcut to add a debugging prefixed message to the log.
*
* @param msg string object containing the log message
*/
debug : function(msg) {
this.console(msg, 'DEBUG');
},
/**
* Place a separator message in the log.
*/
separator : function() {
console.log(this.kLogPrefix + '==========================================================');
},
/**
* Send a separated message out to the log . An optional prefix can be passed which
* will get prepended to the message.
*
* @param msg string object containing the log message
* @param prefix optional object containing a string to prepend to the message
*/
mark : function(msg, prefix) {
this.separator();
this.console('@'+Date()+' '+msg, prefix);
},
/**
* Dumps out an options structure to the log.
*
* @param msg string object containing the log message
* @param obj object to output
* @param step indentation step for recursive call
*/
logObject : function(msg, obj, step) {
this.console(msg);
step = (typeof step !== 'undefined') ? step : 1;
// walk throug the object
if (typeof obj !== 'undefined' && obj !== null) {
var otype = null;
switch (typeof obj) {
case 'object':
for (var member in obj) {
otype = typeof obj[member];
this.console(member +' = '+obj[member]+' ('+otype+')', { indent: step });
}
break;
// process array
case 'array': {
var arraylen = obj.length;
for (var idx = 0; idx < arraylen; idx++) {
otype = typeof obj[idx];
this.console('array['+idx+']= '+obj[idx]+' ('+otype+')', { indent: step });
}
break;
}
default: // atomic type
this.console('('+typeof obj+') : '+obj, { indent: step });
break;
}
}
},
/**
* Dumps out a JSON object structure to the log.
*
* @param msg string object containing the log message
* @param jsonData string or object containing the JSON
*/
logJSON : function(msg, jsonData) {
if (window.__adobe_cep__) {
// CEP log changed and \n is not supported so
// makes the data more difficult to read
this.console(msg+' '+JSON.stringify(jsonData));
}
else {
this.console(msg+'\n'+JSON.stringify(jsonData, null, 4));
}
},
/**
* Dumps out a JSON object structure to the log.
*
* @param msg string object containing the log message
* @param jsonData string or object containing the JSON
*/
logJSONDelayed: function(msg, jsonData) {
setTimeout(function() {
iaw.log.logJSON(msg, jsonData);
}, 1000);
},
/**
* Send an error object to the log. The error contains a message and a call stack list
*
* @param e error object containing the exceptions
*/
exception : function(e) {
console.trace();
console.error(this.kLogPrefix+' EXCEPTION! '+ e.stack);
}
};
/*************************************************************************
* ADOBE CONFIDENTIAL
* ___________________
*
* Copyright 2015 Adobe Systems Incorporated
* All Rights Reserved.
*
* NOTICE: All information contained herein is, and remains
* the property of Adobe Systems Incorporated and its suppliers,
* if any. The intellectual and technical concepts contained
* herein are proprietary to Adobe Systems Incorporated and its
* suppliers and are protected by all applicable intellectual property
* laws, including trade secret and copyright laws.
* Dissemination of this information or reproduction of this material
* is strictly forbidden unless prior written permission is obtained
* from Adobe Systems Incorporated.
**************************************************************************/
/*
Only place in the app that should have a resize event handler. Add a callback to this module to receive resize events.
Defers updates so callbacks are only called once per frame (using requestAnimationFrame), and will turn itself off after a number of frames following the last resize event.
*/
var iaw = iaw || { };
iaw.motor = {
_running: false,
_steps: 0,
_cycle: 30, // number of frames to step for
_throttleSwitch: false,
_pistons: [],
_stopped: false,
start: function() {
iaw.motor._running = false;
this._stopped = false;
iaw.motor._steps = 0;
window.addEventListener('resize', iaw.motor.kick.bind(this), false);
},
stop: function() {
this._stopped = true;
},
update: function() {
if (this._stopped) return;
if (iaw.motor._steps++ > iaw.motor._cycle) {
// only run for a stride, then automatically shut off
iaw.motor._running = false;
return;
}
iaw.motor._throttleSwitch = !iaw.motor._throttleSwitch;
if (iaw.motor._throttleSwitch) {
// only run the pistons every other frame (30 fps throttle) because any more than that is overkill
for (var i = 0; i < iaw.motor._pistons.length; i++) {
var o = iaw.motor._pistons[i];
o.func.call(o.scope || window, iaw.motor._cycle - iaw.motor._steps);
}
}
window.requestAnimationFrame(iaw.motor.update);
},
// force an update cycle
kick: function() {
if (this._stopped) return;
iaw.motor._steps = 0;
if (iaw.motor._running) {
return;
}
iaw.motor._running = true;
iaw.motor.update();
},
// in order to be able to ID functions we have to hash them to generate unique-ish keys for us to find them with later
// if we don't do this, we won't be able to remove callbacks that were bound and save us from binding callbacks multiple times all over the place
add: function(cb, scope) {
if (!cb) console.error('[iaw.motor.add] Pass in a valid function');
var k = iaw.util.hashStr(cb.toString());
var h = iaw.motor.has(k, scope);
if (h === -1) {
iaw.motor._pistons.push({
func: cb,
scope: scope,
key: k
});
}
},
remove: function(cb) {
var k = iaw.util.hashStr(cb.toString());
var i = iaw.motor.has(k);
if (i !== -1) {
iaw.motor._pistons.splice(i, 1);
}
},
// check if the handler already has iaw.motor particular callback
has: function(k, scope) {
var n = -1;
var s = null;
var i;
for (i = 0; i < iaw.motor._pistons.length; i++) {
n = iaw.motor._pistons[i].key;
s = iaw.motor._pistons[i].scope;
if (n === k && s === scope) {
return i;
}
}
return -1;
}
/*
Nothing to do with motor but this has a RAF, so in order to keep track of all the RAF things, let's put them in this module.
scrollTarget Element The element to scroll to
[scrollDuration] Number How long the animation takes in milliseconds. Defaults to 500.
*/
/*scrollToTarget: function(scrollTarget, scrollContainer, scrollDuration) {
}*/
};
/*************************************************************************
* ADOBE CONFIDENTIAL
* ___________________
*
* Copyright 2015 Adobe Systems Incorporated
* All Rights Reserved.
*
* NOTICE: All information contained herein is, and remains
* the property of Adobe Systems Incorporated and its suppliers,
* if any. The intellectual and technical concepts contained
* herein are proprietary to Adobe Systems Incorporated and its
* suppliers and are protected by all applicable intellectual property
* laws, including trade secret and copyright laws.
* Dissemination of this information or reproduction of this material
* is strictly forbidden unless prior written permission is obtained
* from Adobe Systems Incorporated.
**************************************************************************/
/**
* Simple singleton style utility container so all utility related
* stuff has a centralized wrapper.
*/
var iaw = iaw || { };
iaw.profile = {
/**
* Configuration
*/
configurations: {
stage: {
url: 'https://cc-collab-stage.adobe.io/profile',
expirationDays: 1
},
prod: {
url: 'https://cc-collab.adobe.io/profile',
expirationDays: 1
}
},
config: null,
retryAttempt: 0,
/**
* Fetch profile image.
* @param userGUID user GUID
* @param token access token
*/
fetchImage: function(userGUID, token, callback) {
var self = this;
var url;
iaw.util.promise('GET', self.config.url, token)
.then(function(response) {
switch (typeof response) {
case 'string':
url = JSON.parse(response).user.avatar;
break;
case 'object':
url = response.user.avatar;
break;
}
if (!url) return callback(null);
iaw.util.downLoadImage( url, function(blob) {
if (!blob) {
if (self.callback) {
self.callback( null );
}
return;
}
var reader = new window.FileReader();
reader.readAsDataURL( blob );
reader.onloadend = function() {
var imageBase64 = reader.result;
iaw.localstorage.setUserItem('profile_image', imageBase64 );
iaw.localstorage.setUserItem('profile_image_timestamp', new Date().getTime() );
iaw.log.debug( '[' + url + '] Downloaded profile image for user [' + userGUID + ']' );
callback(imageBase64);
};
});
self.retryAttempt = 0;
})
.catch(function(err) {
iaw.log.debug( 'Fail to download profile image with retryAttempt=' + self.retryAttempt);
if (self.retryAttempt === 0) {
// access token is expired. Clear access token and retry once.
iaw.cepUtil.clearAccessToken();
self.getProfilePicture( userGUID, callback, ++self.retryAttempt );
}
});
},
/**
* Check to see if we have a profile picture and make sure
* it is not expired.
*
* @param userGUID user GUID
* @return true if available from cache, false otherwise
*/
isProfilePictureAvailable: function( userGUID ) {
var isAvailable = false;
// make sure we have the configuration setup - if not
// set the default to the production configuration
var expirationDays = this.config.expirationDays;
// fetch image from local storage if it was cached within expirationDays
var imageBase64 = iaw.localstorage.getUserItem('profile_image');
var timestamp = iaw.localstorage.getUserItem('profile_image_timestamp');
if ( imageBase64 && timestamp ) {
var days = ( new Date().getTime() - timestamp ) / ( 24 * 3600 * 1000 );
isAvailable = ( timestamp && days < expirationDays );
}
return isAvailable;
},
/**
* Public API
*/
/**
* Get Profile picture.
*
* @param userGUID user GUID
* @param callback completion callback method
* @param retryAttempt retry times once token is expired
*/
getProfilePicture: function( userGUID, callback, retryAttempt ) {
this.retryAttempt = retryAttempt || 0;
this.config = this.config || (iaw.cepUtil.usingStageAuthentication() ? this.configurations.stage : this.configurations.prod);
iaw.log.debug('Start to get profile Picture, for user [' + userGUID + ']');
// check to see if we already have one cached
if ( this.isProfilePictureAvailable( userGUID ) ) {
iaw.log.debug( 'Found cached profile image for user [' + userGUID + ']');
var imageBase64 = iaw.localstorage.getUserItem('profile_image');
if ( callback ) { callback( imageBase64 ); }
return;
}
// no cached profile image available, then access profile api to get a new one
iaw.cepUtil.getIMSAccessToken( function( token ) {
iaw.profile.fetchImage( userGUID, token, callback );
});
}
};
/*************************************************************************
* ADOBE CONFIDENTIAL
* ___________________
*
* Copyright 2016 Adobe Systems Incorporated
* All Rights Reserved.
*
* NOTICE: All information contained herein is, and remains
* the property of Adobe Systems Incorporated and its suppliers,
* if any. The intellectual and technical concepts contained
* herein are proprietary to Adobe Systems Incorporated and its
* suppliers and are protected by all applicable intellectual property
* laws, including trade secret and copyright laws.
* Dissemination of this information or reproduction of this material
* is strictly forbidden unless prior written permission is obtained
* from Adobe Systems Incorporated.
**************************************************************************/
/**
* Simple singleton style container so all Stock related stuff has a centralized point.
*/
var iaw = iaw || { };
iaw.stockUtil = {
STOCK_NAMESPACE: 'adobestock',
STOCK_DATA_KEY: 'trackingdata',
STOCK_STATE_PURCHASED: 'purchased',
STOCK_STATE_NOT_PURCHASED: 'not_purchased',
STOCK_URL_LICENSE: 'https://www.adobe.com/go/stocklicense_',
STOCK_URL: 'https://stock.adobe.com',
STOCK_URL_SEARCH: 'https://stock.adobe.com/Search',
STOCK_URL_STAGE: 'https://primary.stock.stage.adobe.com',
STOCK_URL_SEARCH_STAGE: 'https://primary.stock.stage.adobe.com/Search',
/**
* Get Stock data from representation.
*
* @param [Object] representation representation of cc library element
* @return [Object] Stock data
*/
getStockData: function(representation) {
return representation && representation.getValue(this.STOCK_NAMESPACE, this.STOCK_DATA_KEY);
},
/**
* Get Stock data from cc library element.
*
* @param [Object] element cc library element
* @param [Number] index index of representation in cc library element
* @return [Object] Stock data
*/
getStockDataForElement: function(element, index) {
// First try the primary representation
var representation = index === undefined ? element.getPrimaryRepresentation() : element.representations[index];
var stockData = this.getStockData(representation);
if (stockData && representation.isExternalLink && representation.getCachedExternalLinkError()) {
// It's an external link, but the external link isn't valid: Try the next representation
return iaw.stockUtil.getStockDataForElement(element, index === undefined ? 0 : index + 1);
}
return stockData;
},
/**
* Get Stock Id from cc library element.
*
* @param [Object] element cc library element
* @return [String] Stock Id
*/
getElementStockId: function(element) {
var stockData = this.getStockDataForElement(element);
if (stockData) {
return Number(stockData.content_id);
}
return undefined;
},
/**
* Get Stock license status from cc library element.
*
* @param [Object] element cc library element
* @return [String] Stock license status
*/
getElementStockLicense: function(element) {
var stockData = this.getStockDataForElement(element);
if (stockData) {
if (stockData.state === this.STOCK_STATE_PURCHASED) {
return String(stockData.license);
}
}
return undefined;
},
/**
* License Stock template.
*
* @param [String] imageId content Id of template
* @param [String] licenseType license type of template
* @return [Promise] promise that will resolve the result to license template
*/
licenseTemplate: function(imageId, licenseType) {
return new Promise(function(resolve, reject) {
iaw.cepUtil.setEnvironment(iaw.cepUtil.usingStageAuthentication() ? 'stage' : 'prod');
iaw.cepUtil.getIMSAccessToken(function(token) {
if (!token) {
reject('Invalid access token.');
return;
}
var url = iaw.cepUtil.usingStageAuthentication() ? 'https://stock-stage.adobe.io/Rest/Libraries/1/Content/License?' : 'https://stock.adobe.ioRest/Libraries/1/Content/License?';
url += 'content_id=' + imageId + '&license=' + licenseType;
iaw.util.promise('GET', url, token).then(function(res) {
if (typeof res === 'string') {
res = JSON.parse(res);
}
resolve(res);
}).catch(function(err) {
reject(err);
});
});
});
},
/**
* Create the URL to open/use for the selected Adobe Stock template.
* https://wiki.corp.adobe.com/display/adobestock/Jump+to+Adobe+Stock+Search+Pages
*
* @param [String] hostID phxs, ilst, etc.
* @param [String] type template type
* @param [String] term search term, if any
* @return [String] containing the URL that was opened
*/
composeStockLink: function( hostID, type, term ) {
var baseURL = iaw.cepUtil.usingStageAuthentication() ? this.STOCK_URL_SEARCH_STAGE : this.STOCK_URL_SEARCH;
var category = -1;
var application = 0;
term = typeof term === 'undefined' ? '' : term;
switch (type) {
case 'recent':
case 'saved':
category = 0;
break;
case 'mobile':
category = 1;
break;
case 'web':
category = 2;
break;
case 'print':
category = 3;
break;
case 'photo':
category = 4;
break;
case 'film':
category = 5;
break;
case 'art':
category = 6;
break;
default:
category = -1; // signify error
break;
}
switch (hostID) {
case 'PHXS':
application = 1;
break;
case 'ILST':
application = 2;
break;
case 'IDSN':
application = 3;
break;
default:
application = 0;
break;
}
term = term ? 'k=' + iaw.util.fixedEncodeURIComponent(term) + '&' : '';
term += iaw.analytics.getAnalyticsQueryString() + '&'; // includes language and other host data
var query = 'filters[content_type:template]=1&filters[template_type_id][]=' + application;
// in order to search all categories, remove the filter
var cat = category === 0 ? '' : '&filters[template_category_id][]=' + category;
var analyticsQuery = '&as_channel=adobe_apps&as_source=app&as_campclass=brand&as_campaign=templates_' + hostID;
var featureFlag = 'ff_4815162342=true&';
return (baseURL + '?' + featureFlag + term + query + cat + analyticsQuery);
},
/**
* Open Adobe Stock URL given the product and template type
* https://wiki.corp.adobe.com/display/adobestock/Jump+to+Adobe+Stock+Search+Pages
*
* @param [String] hostID phxs, ilst, etc.
* @param [String] type template type
* @param [String] term search term, if any
* @return [String] containing the URL that was opened
*/
openStockLink: function( hostID, type, term ) {
var uri = this.composeStockLink(hostID, type, term);
if (window.__adobe_cep__) {
iaw.util.openDefaultBrowserAuthenicated('adobeStock', uri);
}
else {
window.open(uri);
}
return uri;
}
};
/*************************************************************************
* ADOBE CONFIDENTIAL
* ___________________
*
* Copyright 2015 Adobe Systems Incorporated
* All Rights Reserved.
*
* NOTICE: All information contained herein is, and remains
* the property of Adobe Systems Incorporated and its suppliers,
* if any. The intellectual and technical concepts contained
* herein are proprietary to Adobe Systems Incorporated and its
* suppliers and are protected by all applicable intellectual property
* laws, including trade secret and copyright laws.
* Dissemination of this information or reproduction of this material
* is strictly forbidden unless prior written permission is obtained
* from Adobe Systems Incorporated.
**************************************************************************/
/**
* Simple singleton style utility container so all utility related
* stuff has a centralized wrapper.
*/
window.iaw = window.iaw || { };
window.iaw.util = {
currentBreakPoint: 0,
loadingLibrary: false, // flag indicates cc library is in loading or not
libraryCallbacks: [],
ccxProcessLaunchPending: false,
/**
* Get current time
*/
getCurrentTime: function() {
var currentTime = new Date().getTime();
return currentTime;
},
/**
* Determine if the current browser is running on a MS Windows based OS.
*
* @return A boolean value indicating true if the browser is on Windows,
* false otherwise.
*/
isWindowsOS: function() {
return ( navigator.userAgent.indexOf('Mac OS X') === -1 );
},
/**
* Convert a URL parameter string into an object.
*
* @return A object with key/value members created from the URL string.
*/
convertQueryString: function( search ) {
// build out the JS object - note: this does not handle sub-objects (like the 'overrides' one)
var jsObj = { };
if ( search ) {
// remove hash before parsing the query
var idx = search.indexOf('#');
if (idx !== -1) {
search = search.slice(0, idx);
}
var jsData = '{"' + search.replace(/&/g, '","').replace(/=/g, '":"') + '"}';
jsObj = JSON.parse(jsData, function(key, value) {
var val = (key === '' ? value : decodeURIComponent(value));
// check for numeric values & boolean - otherwise assume string
if (isNaN( val )) { // boolean, or possibly a string
if (val !== 'true') {
val = (val !== 'false') ? val /* its a string */ : false;
}
else {
val = true;
}
}
else { // numeric
val = parseFloat(val);
}
return val;
});
}
return jsObj;
},
/**
* Convert query string into an object. New one because the above throws an error and it's easier to write a new one than to debug the old one.
* Usually you'll just want to pass in `location.hash`
*
* @return A object with key/value members created from the URL string.
*/
parseQueryString: function(str) {
var ret = Object.create(null); // object with no proto so it won't break with bad param
if (typeof str !== 'string') {
return ret;
}
str = str.trim().replace(/^(\?|#|&)/, '');
if (!str) {
return ret;
}
str.split('&').forEach(function(param) {
var parts = param.replace(/\+/g, ' ').split('=');
// Firefox (pre 40) decodes `%3D` to `=`
var key = parts.shift();
var val = parts.length > 0 ? parts.join('=') : undefined;
key = decodeURIComponent(key);
// missing `=` should be `null`:
val = val === undefined ? null : decodeURIComponent(val);
if (ret[key] === undefined) {
ret[key] = val;
}
else if (Array.isArray(ret[key])) {
ret[key].push(val);
}
else {
ret[key] = [ret[key], val];
}
});
return ret;
},
/**
* Download image.
* @param url url to download
* @param callback completion callback method with the parameter of blob or null if any failure.
*/
downLoadImage: function( url, callback ) {
iaw.log.debug( 'Downloading image from: ' + url );
var xhr = new XMLHttpRequest( );
xhr.onreadystatechange = function() {
if ( this.readyState === 4 && this.status === 200 ) {
iaw.log.debug( 'Image is downloaded.' );
callback( this.response );
}
};
xhr.onerror = function( error ) {
callback( null );
};
xhr.open( 'GET', url, true );
xhr.responseType = 'blob';
xhr.send( );
},
/**
* Setup ajax request.
* @param url url to download
* @param callback completion callback method with the parameter of blob or null if any failure.
*/
ajax: function( url, callback, scope ) {
var xhr = new XMLHttpRequest();
xhr.onreadystatechange = function() {
if ( this.readyState === 4 && this.status === 200 ) {
callback.call(scope || null, this.response);
}
};
xhr.onerror = function( error ) {
iaw.log.debug('[util.ajax] Error');
callback.call(scope || null, error);
};
xhr.open('GET', url, true);
xhr.send();
},
/**
*
*/
normalize: function(v, min, max) {
return (v - min) / (max - min);
},
/**
* Open a URL in the default browser.
*
* @param url string containing the URL to load
*/
openDefaultBrowser: function( url, isAuthenticated ) {
isAuthenticated = isAuthenticated || false;
// skip the authenticated URL's because they dont mean anything... we want the base
if ( !isAuthenticated ) {
iaw.analytics.pip.logInteractionEvent( 'OpenExtLink:' + url );
}
if ( window.cep.util.openURLInDefaultBrowser ) {
window.cep.util.openURLInDefaultBrowser(url);
}
else {
window.open(url, '_blank');
}
},
/**
* Similar to openDefaultBrowser but uses creates an authenicated
* jump url.
*
* @param clientKey element key that matches entry in iaw.cepUtil.authenticationInfo
* @param url string containing the URL to open
*/
openDefaultBrowserAuthenicated: function( clientKey, url ) {
if ( iaw.cepUtil.imsValid() ) {
var query = url.indexOf('?');
iaw.analytics.pip.logInteractionEvent( 'OpenExtLink:' + url.substr(0, (query > 0) ? query : url.length) );
var callback = function(authenticatedURL) { //eslint-disable-line func-style
iaw.log.console('The generated jumpURL is: ' + authenticatedURL);
iaw.util.openDefaultBrowser( authenticatedURL, true );
};
this.jumpToURL( iaw.cepUtil.authenticationInfo[clientKey].clientID,
iaw.cepUtil.authenticationInfo[clientKey].clientScope,
url, callback );
}
else {
this.openDefaultBrowser( url );
}
},
/**
* Set the UI theme mode.
*
* @param mode color mode, 'light' or 'dark'
*/
setUIThemeMode: function( mode ) {
switch ( mode ) {
case 'light':
document.body.classList.remove('spc--dark');
break;
case 'dark':
document.body.classList.add('spc--dark');
break;
default:
// get the interface color -- if CEP is not present -
// submit to the dark side cause its cooler...
var interfaceColor = ( iaw.cepUtil.csInterface ) ? iaw.cepUtil.getUIThemeColor() : null;
this.setUIThemeMode( (interfaceColor && interfaceColor.red >= 184) ? 'light' : 'dark' );
break;
}
},
/**
* Method to generate a jump url. Only for CEP based applications. This method
* will make a callback to deal with the jump url.
*
* @param clientID SSO client ID
* @param clientScope SSO client scope
* @param url base url
* @param callback callback method which takes the following parameters:
* the jump url to deal with
*/
jumpToURL : function( clientID, clientScope, url, callback) {
if ( url && callback && clientID && clientScope ) {
if (window.__adobe_cep__) {
iaw.cepUtil.getIMSAccessToken( function(token) {
if ( token ) {
iaw.cepUtil.createJumpURL(clientID, clientScope, url, callback);
}
else {
iaw.log.console('No jump URL since access token is invalid.');
callback(url);
}
});
}
else { // no CEP present
/*
$.get('https://'+ getEnvironment().ims +'/ims/jumptoken/v1',
{
bearer_token:accessToken,
target_client_id:'AdobeStockClient1',
target_redirect_uri:url,
target_scope:'AdobeID,openid,creative_cloud,read_organizations,gnav,additional_info.address.mail_to,sao.stock'
})
.success(function(data) {
if (data.jump) {
HelloLog.log('jump url:' + data.jump);
callback(data.jump);
}
})
.error(function(jqXHR, textStatus, errorThrown) {
console.log('jump url error: ' + errorThrown);
callback(url);
});
*/
}
}
},
/**
* Method to append an external script to the head
*
* @param script script path
* @return [Promise] promise that will resolve when the script is loaded
*/
loadExternalScript : function( script ) {
return new Promise(function(resolve, reject) {
var scriptTag = document.createElement('script');
// setup properties so it's interpreted correctly and loads asynchronously
scriptTag.type = 'text/javascript';
scriptTag.charset = 'utf-8';
// ASYNC: load in parallel and execute as soon as possible
scriptTag.async = true;
// DEFER: load in parallel but maintain execution order
scriptTag.defer = false;
scriptTag.src = script;
scriptTag.onload = function() {
resolve(scriptTag);
};
scriptTag.onerror = function(error) {
reject(error);
};
// kick off the load, directly to body so we don't have to traverse to find the head
document.body.appendChild(scriptTag);
});
},
/**
* Format the a Date object in the correct string layout for Radar.
* This method is a percaution in case someone changes the required
* date format, and we won't need to hunt throught the code to change
* all the instances of moment.format
*
* @param [Date] stamp Date time stamp to format
* @return [String] in the date format YYYY-MM-DDTHH:mm:ss.SSS-XXXX
*/
formatTimeStamp: function formatTimeStamp(date) {
function pad(n, count) {
count = count || 2;
var def = '';
for (var i = 0; i < count; ++i) {
def += '0';
}
return String(def + n).slice(-count);
}
var offset = date.getTimezoneOffset();
return (
date.getFullYear() +
'-' +
pad(date.getMonth() + 1) +
'-' +
pad(date.getDate()) +
'T' +
pad(date.getHours()) +
':' +
pad(date.getMinutes()) +
':' +
pad(date.getSeconds()) +
'.' +
pad(date.getMilliseconds(), 3) +
(offset < 0 ? '+' : '-') +
pad(Math.abs(offset) / 0.6, 4)
);
},
/**
* Method to get trial end date.
*
* @param secondsLeftInTrial seconds left in trial
*/
getTrialEndDate : function( secondsLeftInTrial ) {
var trialEndDate = new Date(new Date().getTime() + secondsLeftInTrial * 1000);
return (secondsLeftInTrial >= 0) ? this.formatTimeStamp(trialEndDate) : null;
},
/**
* Generate a rfc4122 version 4 compliant GUID.
* @see http://stackoverflow.com/questions/105034/create-guid-uuid-in-javascript
*
* @return A string containing the GUID
*/
generateGUID : function() {
/*jshint bitwise: false*/
return 'xxxxxxxxxxxx4xxxyxxxxxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
// 'x|0' is a shortcut for 'Math.floor(x)'
var r = Math.random()*16|0;
var v = (c === 'x') ? r : (r&0x3|0x8);
return v.toString(16);
});
},
/**
* Open the Library panel to a specific library
*
* @param The ID of the library you want to show
*/
showLibraryInPanel : function( libraryId ) {
var csInterface = new CSInterface();
var emitEvent = function() { //eslint-disable-line func-style
var setLibEvent = new CSEvent('dlSetCurrentLibrary', 'APPLICATION');
setLibEvent.data = {'libraryId' : libraryId};
csInterface.dispatchEvent(setLibEvent);
};
var handlePanelInitialized = function() { //eslint-disable-line func-style
csInterface.removeEventListener('dlPanelInitialized', handlePanelInitialized);
setTimeout(emitEvent, 200);
};
// Emit the event once incase the panel is already open
emitEvent();
// Emit the event after panel is initialized
csInterface.addEventListener('dlPanelInitialized', handlePanelInitialized);
// Launch the extension incase if it's not already open
csInterface.requestOpenExtension('com.adobe.DesignLibraries.angular', '');
},
/**
* Promise helper function
*
* @param The method you want to use
* @param The URL of the service
* @param More http headers
* @param An array of ignored http status
*/
promise : function( method, url, token, headers, ignoredStatus ) {
return new Promise(function(resolve, reject) {
var request = new XMLHttpRequest();
request.open(method, url, true);
request.onload = function() {
if (request.status === 200 || request.status === 202) {
resolve(request.response);
}
else if (ignoredStatus && ignoredStatus.indexOf(request.status) > -1) {
resolve(request.status);
}
else {
reject(request.statusText);
}
};
request.onerror = function() {
reject(request.statusText || 'Network Error');
};
if (token) {
request.setRequestHeader('x-api-key', 'CCXInAppWelcome');
request.setRequestHeader('Content-Type', 'application/json');
request.setRequestHeader('Authorization', 'Bearer ' + token);
if (headers) {
Object.keys(headers).forEach(function(key) {
if (typeof headers[key] !== 'undefined') {
request.setRequestHeader(key, headers[key]);
}
});
}
}
request.send();
});
},
/**
* Handle status coming back from the host ExtendScript method,
* closes the extension is status is 'true'. Remember callbacks/continuation
* methods from ExtendScript always send params as strings.
*
* @param status string containing 'false' if cancelled/error, 'true' otherwise
*/
closeExtOnStatus: function(status) {
if ((status.toLowerCase() === 'true') && window.__adobe_cep__) {
iaw.analytics.ingest.logScreenStateEvent('close-auto');
iaw.cepUtil.sendEvent(iaw.cepUtil.events.REQUESTHOSTCLOSE, null);
}
},
/**
* To be more stringent in adhering to RFC 3986 (which reserves !, ', (, ), and *),
* even though these characters have no formalized URI delimiting uses.
*
* @param str string to encode
* @return String object encoded for URI
* @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/encodeURIComponent
*/
fixedEncodeURIComponent: function(str) {
var encoded = (str && str.length) ? encodeURIComponent(str).replace(/[!'()*]/g, function(c) {
return '%' + c.charCodeAt(0).toString(16);
}) : str;
return encoded;
},
/**
* Round to the nearest decimal
*
* @param {Number} num
* @param {Number} decimal - how many decimal places you want to round to
* @return {Number} - number rounded to the correct number of decimals
*/
roundDecimal: function(num, decimal) {
return Number(Math.round(Number(num).toFixed(20) + ('e' + decimal)) + ('e-' + decimal));
},
/**
* Using the ceiling of the closest decimal
* Example: ceilDecimal(0.131, 2); // 0.14
*
* @param {Number} num
* @param {Number} decimal - how many decimal places you want to round to
* @return {Number} - number rounded to the correct number of decimals
*/
ceilDecimal: function(num, decimal) {
return Number(Math.ceil(Number(num).toFixed(20) + ('e' + decimal)) + ('e-' + decimal));
},
/**
* Using the floor of the closest decimal
* Example: floorDecimal(0.131, 2); // 0.13
*
* @param {Number} num
* @param {Number} decimal - how many decimal places you want to round to
* @return {Number} - number rounded to the correct number of decimals
*/
floorDecimal: function(num, decimal) {
return Number(Math.floor(Number(num).toFixed(20) + ('e' + decimal)) + ('e-' + decimal));
},
/**
*
*
* @param target If string, will mount the tag to first element matched with querySelector; else, assume it's an element
*
*/
mountTag: function(target, tagName, data, animClassIn, animClassOut) {
data = data || null;
animClassOut = animClassOut || 'anim--fade-out';
animClassIn = animClassIn || 'anim--fade-in';
var parent = target;
if (typeof parent === 'string') {
parent = document.querySelector(target);
if (!parent) {
console.warn('[iaw.util.mountTag] No element found with selector '+target);
return;
}
}
function onEnd(evt) {
// console.log('[mount] onEnd');
parent.removeEventListener('webkitTransitionEnd', onEnd);
parent.removeEventListener('webkitAnimationEnd', onEnd);
parent.removeEventListener('animationend', onEnd);
parent.classList.remove(animClassOut);
parent.classList.add(animClassIn);
riot.mount(parent, tagName, data);
}
// just in case it was there
parent.classList.remove('hidden');
parent.classList.remove(animClassOut);
parent.classList.remove(animClassIn);
if (parent.children.length > 0 || parent.attributes['data-is']) {
// if there's already something in here, fade it out
parent.addEventListener('webkitTransitionEnd', onEnd, false);
parent.addEventListener('webkitAnimationEnd', onEnd, false);
parent.addEventListener('animationend', onEnd, false);
parent.classList.add(animClassOut);
}
else {
// animate it in immediately
parent.classList.add(animClassIn);
riot.mount(parent, tagName, data);
}
},
/**
*
*
* @param tagInstance
*
*/
unmountTag: function(tagInstance, animClassIn, animClassOut) {
animClassOut = animClassOut || 'anim--fade-out';
animClassIn = animClassIn || 'anim--fade-in';
var parent = tagInstance.root;
if (parent.classList.contains(animClassOut)) {
// it's already animating out, so ignore
return;
}
// just in case it was there
parent.classList.remove(animClassIn);
function onEnd(evt) {
// console.log('[unmount] onEnd');
parent.removeEventListener('webkitTransitionEnd', onEnd);
parent.removeEventListener('webkitAnimationEnd', onEnd);
parent.removeEventListener('animationend', onEnd);
tagInstance.unmount(true);
parent.removeAttribute('data-is'); // must be a riot bug, because riot should remove this for us
parent.classList.remove(animClassOut); // clean classList for next time
parent.classList.add('hidden'); // remove from DOM
}
parent.addEventListener('webkitTransitionEnd', onEnd, false);
parent.addEventListener('webkitAnimationEnd', onEnd, false);
parent.addEventListener('animationend', onEnd, false);
parent.classList.add(animClassOut);
},
showElement: function(element, animClassIn, animClassOut) {
animClassOut = animClassOut || 'anim--fade-out';
animClassIn = animClassIn || 'anim--fade-in';
// just in case it was there
element.classList.remove('hidden');
element.classList.remove(animClassOut);
element.classList.remove(animClassIn);
element.classList.add(animClassIn);
},
hideElement: function(element, animClassIn, animClassOut) {
animClassOut = animClassOut || 'anim--fade-out';
animClassIn = animClassIn || 'anim--fade-in';
// just in case it was there
element.classList.remove(animClassOut);
element.classList.remove(animClassIn);
function onEnd(evt) {
element.removeEventListener('webkitTransitionEnd', onEnd);
element.removeEventListener('webkitAnimationEnd', onEnd);
element.removeEventListener('animationend', onEnd);
element.classList.add('hidden'); // remove from DOM
}
element.addEventListener('webkitTransitionEnd', onEnd, false);
element.addEventListener('webkitAnimationEnd', onEnd, false);
element.addEventListener('animationend', onEnd, false);
element.classList.add(animClassOut);
},
/**
* Convert a country code to a shared cloud useable region
*
* @param countryCode country code
* @return two digit region used in shared cloud call
*
*/
getRegionFromCountryCode: function(countryCode) {
return iaw.util.countryCodeMap[countryCode];
},
// keep this outside so it's only created once per app session, not once every time the function is called, or else we'll have a memory problem
countryCodeMap: {'US': 'us', 'CA': 'us', 'AI': 'us', 'AG': 'us', 'AR': 'us', 'AW': 'us', 'BS': 'us', 'BB': 'us', 'BZ': 'us', 'BM': 'us', 'BO': 'us', 'BR': 'us', 'KY': 'us', 'CL': 'us', 'CO': 'us', 'CR': 'us', 'DM': 'us', 'DO': 'us', 'EC': 'us', 'SV': 'us', 'FK': 'us', 'GF': 'us', 'GD': 'us', 'GP': 'us', 'GT': 'us', 'GY': 'us', 'HT': 'us', 'HN': 'us', 'JM': 'us', 'MQ': 'us', 'MX': 'us', 'MS': 'us', 'AN': 'us', 'NI': 'us', 'PA': 'us', 'PY': 'us', 'PE': 'us', 'KN': 'us', 'LC': 'us', 'PM': 'us', 'VC': 'us', 'GS': 'us', 'SR': 'us', 'TT': 'us', 'TC': 'us', 'UM': 'us', 'UY': 'us', 'VE': 'us', 'VG': 'us', 'AS': 'us', 'PR': 'us', 'GB': 'eu', 'AL': 'eu', 'DZ': 'eu', 'AD': 'eu', 'AO': 'eu', 'AM': 'eu', 'AT': 'eu', 'AZ': 'eu', 'BY': 'eu', 'BE': 'eu', 'BJ': 'eu', 'BA': 'eu', 'BW': 'eu', 'IO': 'eu', 'BG': 'eu', 'BF': 'eu', 'BI': 'eu', 'CM': 'eu', 'CV': 'eu', 'CF': 'eu', 'TD': 'eu', 'KM': 'eu', 'CD': 'eu', 'CG': 'eu', 'HR': 'eu', 'CY': 'eu', 'CZ': 'eu', 'DK': 'eu', 'DJ': 'eu', 'EG': 'eu', 'GQ': 'eu', 'ER': 'eu', 'EE': 'eu', 'ET': 'eu', 'FO': 'eu', 'FI': 'eu', 'FR': 'eu', 'GA': 'eu', 'GM': 'eu', 'GE': 'eu', 'DE': 'eu', 'GH': 'eu', 'GI': 'eu', 'GR': 'eu', 'GL': 'eu', 'GN': 'eu', 'GW': 'eu', 'HU': 'eu', 'IS': 'eu', 'IE': 'eu', 'IT': 'eu', 'CI': 'eu', 'KE': 'eu', 'LV': 'eu', 'LS': 'eu', 'LR': 'eu', 'LY': 'eu', 'LI': 'eu', 'LT': 'eu', 'LU': 'eu', 'MK': 'eu', 'MG': 'eu', 'MW': 'eu', 'ML': 'eu', 'MT': 'eu', 'MR': 'eu', 'MU': 'eu', 'YT': 'eu', 'MD': 'eu', 'MC': 'eu', 'ME': 'eu', 'MA': 'eu', 'MZ': 'eu', 'NA': 'eu', 'NL': 'eu', 'NE': 'eu', 'NG': 'eu', 'NO': 'eu', 'PS': 'eu', 'PL': 'eu', 'PT': 'eu', 'RE': 'eu', 'RO': 'eu', 'RW': 'eu', 'SH': 'eu', 'SM': 'eu', 'ST': 'eu', 'SN': 'eu', 'CS': 'eu', 'RS': 'eu', 'SC': 'eu', 'SL': 'eu', 'SK': 'eu', 'SI': 'eu', 'SO': 'eu', 'ZA': 'eu', 'ES': 'eu', 'SJ': 'eu', 'SZ': 'eu', 'SE': 'eu', 'CH': 'eu', 'TZ': 'eu', 'TG': 'eu', 'TN': 'eu', 'UG': 'eu', 'UA': 'eu', 'VA': 'eu', 'EH': 'eu', 'ZM': 'eu', 'ZW': 'eu', 'AF': 'ap', 'AQ': 'ap', 'AU': 'ap', 'BH': 'ap', 'BD': 'ap', 'BT': 'ap', 'BN': 'ap', 'MM': 'ap', 'KH': 'ap', 'CN': 'ap', 'CX': 'ap', 'CC': 'ap', 'CK': 'ap', 'TL': 'ap', 'FJ': 'ap', 'PF': 'ap', 'HK': 'ap', 'IN': 'ap', 'ID': 'ap', 'IQ': 'ap', 'IL': 'ap', 'JP': 'ap', 'JO': 'ap', 'KZ': 'ap', 'KI': 'ap', 'KR': 'ap', 'KW': 'ap', 'KG': 'ap', 'LA': 'ap', 'LB': 'ap', 'MO': 'ap', 'MY': 'ap', 'MV': 'ap', 'MH': 'ap', 'FM': 'ap', 'MN': 'ap', 'NR': 'ap', 'NP': 'ap', 'NC': 'ap', 'NZ': 'ap', 'NU': 'ap', 'NF': 'ap', 'OM': 'ap', 'PK': 'ap', 'PG': 'ap', 'PH': 'ap', 'PN': 'ap', 'QA': 'ap', 'RU': 'ap', 'WS': 'ap', 'SA': 'ap', 'SG': 'ap', 'SB': 'ap', 'LK': 'ap', 'TW': 'ap', 'TJ': 'ap', 'TH': 'ap', 'TK': 'ap', 'TO': 'ap', 'TR': 'ap', 'TM': 'ap', 'TV': 'ap', 'AE': 'ap', 'UZ': 'ap', 'VU': 'ap', 'VN': 'ap', 'WF': 'ap', 'YE': 'ap'},
hashStr: function(str) {
var hash = 0, i, chr, len;
if (str.length === 0) return hash;
for (i = 0, len = str.length; i < len; i++) {
chr = str.charCodeAt(i);
hash = ((hash << 5) - hash) + chr;
hash |= 0; // convert to 32bit integer
}
return hash;
},
/**
* Convert the file size into a formatted string.
*
* @param isize integer value of file size
* @return A string formatted with the size MB/GB/KB etc. specifier
*/
createFileSizeString: function(isize) {
var val = '';
if (isize >= 1099511627776) { // TB
val = ((isize / 1099511627776).toFixed(1)).toString()+iaw.i18n.getLocalizedString('filesize_key_TB');
}
else if (isize >= 1073741824) { // GB
val = ((isize / 1073741824).toFixed(1)).toString()+iaw.i18n.getLocalizedString('filesize_key_GB');
}
else if (isize >= 1048576) { // MB
val = ((isize / 1048576).toFixed(1)).toString()+iaw.i18n.getLocalizedString('filesize_key_MB');
}
else if (isize >= 1024) { // KB
val = ((isize / 1024).toFixed(1)).toString() + iaw.i18n.getLocalizedString('filesize_key_KB');
}
else if (isize > 0) { // Bytes
val = isize.toString()+iaw.i18n.getLocalizedString('filesize_key_B');
}
else {
val = '--';
}
return val.replace('.', iaw.i18n.getLocalizedString('filesize_key_decimal'));
},
/**
* Show license dialog.
*
* @param element element {type: 'application/vnd.adobe.element.template+dcx'}
* @param contentId template Id
* @skipQuotaCheck skip quota check
* @licenseType license type
*/
showLicenseDialog: function(element, contentId, skipQuotaCheck, libraryId, renditionDetails, licenseType) {
var PURCHASE_DIALOG_INIT_EVENT_NAME = 'com.adobe.stock.panel.license.init';
var PURCHASE_DIALOG_PERFORM_EVENT_NAME = 'com.adobe.stock.panel.license.perform';
var PURCHASE_DIALOG_CLOSED_EVENT_NAME = 'com.adobe.stock.panel.license.closed';
var PURCHASE_DIALOG_EXTENSION_ID = 'com.adobe.stock.panel.licensing';
return new Promise(function(resolve, reject) {
var csInterface = iaw.cepUtil.csInterface;
if (!csInterface) {
reject();
return;
}
// Register for event to know when Purchase dialog loads & pass on the required information
function onPurchaseDialogInit(event) {
csInterface.removeEventListener(PURCHASE_DIALOG_INIT_EVENT_NAME, onPurchaseDialogInit);
var csEvent = new CSEvent(PURCHASE_DIALOG_PERFORM_EVENT_NAME, 'APPLICATION', 'CCInAppCmdN', csInterface.getExtensionID());
// TODO: get rid of stockSearchAsset once Stock search is working fine
csEvent.data = {'elementType': element.type, 'contentId': String(contentId), 'skipQuotaCheck': skipQuotaCheck, 'licenseType': licenseType, 'addToLibraryID': libraryId, 'renditionDetails': renditionDetails};
csInterface.dispatchEvent(csEvent);
iaw.log.logJSON('Dispatch CEP event to start purchase dialog: ', csEvent);
}
csInterface.addEventListener(PURCHASE_DIALOG_INIT_EVENT_NAME, onPurchaseDialogInit);
// Register for event to know when Purchase dialog closes & get purchase information
function onPurchaseDialogClosed(event) {
iaw.log.logJSON('Data from purchase dialog: ', event);
csInterface.removeEventListener(PURCHASE_DIALOG_CLOSED_EVENT_NAME, onPurchaseDialogClosed);
csInterface.removeEventListener('com.adobe.csxs.events.ExtensionUnloaded', onPurchaseDialogClosed);
// If this callback is due to ExtensionUnloaded event
if (event.type === 'com.adobe.csxs.events.ExtensionUnloaded') {
var xmlParser = new DOMParser();
var xmlDoc = xmlParser.parseFromString(event.data, 'text/xml');
// Check for the extension that go unloaded
if (xmlDoc.getElementsByTagName('Id')[0].childNodes[0].nodeValue !== PURCHASE_DIALOG_EXTENSION_ID) {
return;
}
}
var eventData = event.data;
if (eventData && eventData.didFinish) {
resolve(eventData.data);
}
else {
reject(eventData && eventData.data);
}
}
csInterface.addEventListener(PURCHASE_DIALOG_CLOSED_EVENT_NAME, onPurchaseDialogClosed);
csInterface.addEventListener('com.adobe.csxs.events.ExtensionUnloaded', onPurchaseDialogClosed);
csInterface.requestOpenExtension(PURCHASE_DIALOG_EXTENSION_ID);
});
},
/**
* Creates a string like "ccx.open recent grid" or "ccx.start dragdrop" to pass to Photoshop
* for Highbeam logging of open actions.
*
* @param context The general context such as recent, cc files, open dialog, etc
* @param contextDetails More details about the context, e.g. grid or list view. Optional.
* @return A string combining the telling part of the extension id and the contet and details.
*/
getPipMethodString : function(context, viewMode) {
var method = iaw.cepUtil.csInterface.getExtensionID().replace('com.adobe.', '') + ' ' + context;
if (viewMode) {
method += ' ' + viewMode;
}
return method;
},
/**
* Object.assing polyfill from https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/assign
*/
assign: function(target) {
'use strict';
if (target === undefined || target === null) {
throw new TypeError('Cannot convert undefined or null to object');
}
var output = Object(target);
for (var index = 1; index < arguments.length; index++) {
var source = arguments[index];
if (source !== undefined && source !== null) {
for (var nextKey in source) {
if (source.hasOwnProperty(nextKey) && source[nextKey] !== undefined) {
output[nextKey] = source[nextKey];
}
}
}
}
return output;
},
/**
* Merges two (or more) objects, giving the last one precedence
* @param {Object} target
* @param {Object} source
*/
merge: function(target, source) {
if (typeof target !== 'object') {
target = {};
}
var sourceProperty;
for (var property in source) {
if (source.hasOwnProperty(property)) {
sourceProperty = source[property];
if (typeof sourceProperty === 'object') {
target[property] = iaw.util.merge(target[property], sourceProperty);
}
target[property] = sourceProperty;
}
}
for (var a = 2, l = arguments.length; a < l; a++) {
iaw.util.merge(target, arguments[a]);
}
return target;
},
/**
* Get template data per template id
* @param templateId template id
* @return A object of template data
*/
getTemplateData: function(templateId) {
var templateData;
var templates = iaw.store.get('templates');
if (templates && templates.length > 0) {
templates.forEach(function(template) {
if (template.id === templateId) {
templateData = template;
}
});
}
return templateData;
},
/**
* Set template data per uuid
* @param templateId template id
* @return A object of template data
*/
setTemplateData: function(uuid, templateData) {
var templateIndex = iaw.store.get(['templateLUT', uuid]);
iaw.store.set(['templates', templateIndex], templateData);
},
/**
* Animate a scrollTo action of the provided element given the provided offset
* TODO this should move to a more common location, perhaps with spectre if this disclosure widget makes it there
*
* @param {DOM.element} element scroll container element
* @param {number} offset positive or negative distance to scroll from current
* @param {number=} duration
* @param {Function=} callback
*/
smoothScroll: function(element, offset, duration, callback) {
function easeInOutQuad(t, b, c, d) {
t /= d/2;
if (t < 1) {
return c/2*t*t + b;
}
t--;
return -c/2 * (t*(t-2) - 1) + b;
}
var start = element.scrollTop,
currentTime = 0,
increment = 20,
val;
duration = (typeof (duration) === 'undefined') ? 500 : duration;
function animateScroll() {
currentTime += increment;
val = easeInOutQuad(currentTime, start, offset, duration);
element.scrollTop = val;
if (currentTime < duration) {
requestAnimationFrame(animateScroll);
}
else if (typeof callback === 'function') {
callback();
}
}
animateScroll();
},
/**
* Launch CCX process if not running.
* callback is a funcion with parameter status code:
* 0: means the process has been running
* 1: indicates the process is just launched and ready
* 2: timeout or failed to launch process, like Thor is upgrading, or it's slow to get CCX Process startup (longer than 2 seconds)
* 3: Thor or ccx process is not installed at all
*/
launchCCXProcess: function(callback) {
if (iaw.util.ccxProcessLaunchPending || !window.__adobe_cep__) {
return;
}
var initializedTimeout;
var initializedMessage = 'vulcan.SuiteMessage.ccxprocess.Initialized';
callback = callback || function() {};
iaw.util.ccxProcessLaunchPending = true;
function onFinished(status) {
callback(status);
iaw.util.ccxProcessLaunchPending = false;
}
function started() {
clearTimeout(initializedTimeout);
iaw.util.ccxProcessLaunchPending = false;
VulcanInterface.removeMessageListener(initializedMessage, started);
onFinished(1);
}
function startLaunchCCXProcess() {
VulcanInterface.addMessageListener(initializedMessage, started);
initializedTimeout = setTimeout(function() {
VulcanInterface.removeMessageListener(initializedMessage, started);
onFinished(2);
},
1000);
VulcanInterface.launchApp('ccxprocess', false);
}
if (VulcanInterface.isAppInstalled('ccxprocess')) {
iaw.util.isCCXProcessRunning(function(isRunning) {
if (!isRunning) {
iaw.log.console('Start to launch CCX Process');
startLaunchCCXProcess();
}
else {
onFinished(0);
}
});
}
else {
onFinished(3);
}
},
/**
* Check if CCX process is installed.
*/
isCCXProcessInstalled: function(callback) {
return VulcanInterface.isAppInstalled('ccxprocess');
},
/**
* Check if CCX process is running.
*/
isCCXProcessRunning: function(callback) {
if (!iaw.util.isWindowsOS() && VulcanInterface.isAppRunning('ccxprocess')) {
// Ignore VulcanInterface.isAppRunning on Windows due to https://jira.corp.adobe.com/browse/CEP-510
return callback(true);
}
// VulcanInterface.isAppRunning is not reliable to know the running status. Sometimes it returns false for the running CCX Process on Mac.
// Workaround is to check if CCX Process is able to respond to a ping/pong message in 200ms.
var pingMessage = 'vulcan.SuiteMessage.ccxprocess.in.request.app.ping';
var pongMessage = 'vulcan.SuiteMessage.ccxprocess.out.response.app.ping';
var pingMessageTimeout;
function pongHanlder() {
clearTimeout(pingMessageTimeout);
VulcanInterface.removeMessageListener(pongMessage, pongHanlder);
callback(true);
}
VulcanInterface.addMessageListener(pongMessage, pongHanlder);
pingMessageTimeout = setTimeout(function() {
VulcanInterface.removeMessageListener(pongMessage, pongHanlder);
callback(false);
}, 200);
var message = new VulcanMessage(pingMessage);
message.setPayload('');
VulcanInterface.dispatchMessage(message);
}
};
/*************************************************************************
* ADOBE CONFIDENTIAL
* ___________________
*
* Copyright 2016 Adobe Systems Incorporated
* All Rights Reserved.
*
* NOTICE: All information contained herein is, and remains
* the property of Adobe Systems Incorporated and its suppliers,
* if any. The intellectual and technical concepts contained
* herein are proprietary to Adobe Systems Incorporated and its
* suppliers and are protected by all applicable intellectual property
* laws, including trade secret and copyright laws.
* Dissemination of this information or reproduction of this material
* is strictly forbidden unless prior written permission is obtained
* from Adobe Systems Incorporated.
**************************************************************************/
(function() {
var fnft = {
data: { },
earlyKeyEventsProcessed: false,
RWD_SMALL: 0,
RWD_LARGE: 1,
NUMBER_OF_ITEMS_ABOVE_FOLD: 12,
LOG_PREFIX: 'CCX-FNFT :: ',
CCXP_VULCAN_SPECIFIER: 'CCXP',
VULCAN_STOCK_TEMPLATE_REQUEST: 'ccxprocess.StockTemplateRequest',
VULCAN_STOCK_TEMPLATE_RESPONSE: 'ccxprocess.StockTemplateResponse',
STOCK_RESPONSE_TIMEOUT: 1000, // 1 seconds
StockTimeoutHandler: null,
/**
* Event handler for updating the calculation data from the host for AI/ID field calculations.
*
* @param evt event data object
*/
handleUpdateCalcData: function(evt) {
var evtData = null;
try {
evtData = (typeof evt.data === 'string') ? JSON.parse(evt.data) : evt.data;
}
catch (err) {
iaw.log.exception('Host calculation update handler exception: '+err.message);
}
if (evtData) {
iaw.store.set(['host-calc-update'], evtData);
}
},
/**
* Event handler for early key events that happened before the dialog initializes (iaw.cepUtil.events.EARLYKEYEVENTS)
* @param evt event data object
*/
handleEarlykeyEvents: function(evt) {
iaw.cepUtil.csInterface.removeEventListener(iaw.cepUtil.events.EARLYKEYEVENTS, iaw.fnft.handleEarlykeyEvents);
var evtData = null;
try {
evtData = (typeof evt.data === 'string') ? JSON.parse(evt.data) : evt.data;
}
catch (err) {
iaw.log.exception('Host early key events handler exception: ' + err.message);
}
if (evtData && evtData.keyCodes.indexOf(iaw.a11y.Keys.ENTER) > -1) {
iaw.fnft.createNewDocumentFromSelectedTemplate(false);
iaw.fnft.closeExtOnStatus('true');
return;
}
if (evtData && evtData.keyCodes.indexOf(iaw.a11y.Keys.ESC) > -1) {
iaw.fnft.closeExtOnStatus('true');
return;
}
// Tell the panel that we are done processing early key events
iaw.fnft.earlyKeyEventsProcessed = true;
},
/**
* Generate a default data set if the Stock pull fails.
*
* @return an object representing the default data set.
*/
generateDefaultStockData: function(hostData) {
var defaultData = {
'nb_results': 0,
'templates': [],
'expirationDTS': '2015-11-01T00:00:00.000+00:00',
'version': hostData.radarVersion || '0.0.0'
};
return defaultData;
},
/**
* Create the filter tab elements for the file new from template nav bar
*
* @param hostID application host ID string
* @return An ordered array of the nav bar filter elements
*/
generateFilters: function(hostID) {
var filters;
switch (hostID) {
case 'ILST':
filters = [
{type: 'recent', name: 'newdoc_filter_recent'},
{type: 'saved', name: 'newdoc_filter_saved'},
{type: 'mobile', name: 'newdoc_filter_mobile'},
{type: 'web', name: 'newdoc_filter_web'},
{type: 'print', name: 'newdoc_filter_print'},
{type: 'film', name: 'newdoc_filter_film'},
{type: 'art', name: 'newdoc_filter_art'}
];
break;
case 'PHXS':
filters = [
{type: 'recent', name: 'newdoc_filter_recent'},
{type: 'saved', name: 'newdoc_filter_saved'},
{type: 'photo', name: 'newdoc_filter_photo'},
{type: 'print', name: 'newdoc_filter_print'},
{type: 'art', name: 'newdoc_filter_art'},
{type: 'web', name: 'newdoc_filter_web'},
{type: 'mobile', name: 'newdoc_filter_mobile'},
{type: 'film', name: 'newdoc_filter_film'}
];
break;
case 'IDSN':
filters = [
{type: 'recent', name: 'newdoc_filter_recent'},
{type: 'saved', name: 'newdoc_filter_saved'},
{type: 'print', name: 'newdoc_filter_print'},
{type: 'web', name: 'newdoc_filter_web'},
{type: 'mobile', name: 'newdoc_filter_mobile'}
];
break;
default:
filters = [
{type: 'recent', name: 'newdoc_filter_recent'},
{type: 'saved', name: 'newdoc_filter_saved'},
{type: 'mobile', name: 'newdoc_filter_mobile'},
{type: 'web', name: 'newdoc_filter_web'},
{type: 'print', name: 'newdoc_filter_print'},
{type: 'photo', name: 'newdoc_filter_photo'},
{type: 'film', name: 'newdoc_filter_film'},
{type: 'art', name: 'newdoc_filter_art'}
];
break;
}
return filters;
},
/**
* Get the icon font element associated with the corresponding ID.
*
* @param elementID unique identifier string of the UI element
*/
getThumbnailIcon: function(elementID) {
var iconMap = {
'recent': 'SP_PresetNewFromPreset.png',
'mobile': 'SP_PresetMobileAppDoc.png',
'web': 'SP_PresetWebDoc.png',
'print': 'SP_PrintDoc.png',
'photo': 'SP_PresetPhotoDoc.png',
'film': 'SP_PresetFilmVideo.png',
'art': 'SP_PresetCustom.png'
};
var icon = iconMap[elementID] || 'CCX_Start_DefaultThumb_other.png';
return 'images/thumbs/'+icon;
},
/**
* Get the JSON data block that matches the selected template.
*
* @param templateUUID unique identifier string of the template
*/
getTemplateData: function(templateUUID) {
var templateData = null;
if (templateUUID) {
var dataIndex = iaw.store.get(['presetLUT', templateUUID]);
if (dataIndex !== undefined ) {
templateData = iaw.store.get('presets', dataIndex);
}
else {
dataIndex = iaw.store.get(['templateLUT', templateUUID]);
if (dataIndex !== undefined ) {
templateData = iaw.store.get('templates', dataIndex);
}
}
}
return templateData;
},
/**
* Handle status coming back from the host ExtendScript method,
* closes the extension is status is 'true'. Remember callbacks/continuation
* methods from ExtendScript always send params as strings.
* For FNFT we have to close 2 extensions - the Start & FNFT.
*
* @param status string containing 'false' if cancelled/error, 'true' otherwise
*/
closeExtOnStatus: function(status) {
if ((status.toLowerCase() === 'true') && window.__adobe_cep__) {
//iaw.analytics.ingest.logScreenStateEvent('close-auto');
window.__adobe_cep__.closeExtension();
}
},
/*
* Create a new Preset from the currently selected document settings
*
* @param name the name of the preset to create
* @return an object representing the created template set.
*/
createPreset: function(name, callback) {
// get the selected template
var templateData = iaw.fnft.getTemplateData(iaw.store.get(['input', 'selected-item']));
if (!templateData) {
try {
templateData = JSON.parse(iaw.store.get('last-saved-template-data') || iaw.store.get('last-deleted-template-data'));
}
catch (e) {
templateData = {isPreset: true};
}
}
iaw.store.set('last-saved-template-data', JSON.stringify(templateData));
var hostID = iaw.store.get(['host', 'hostID']);
var settings = iaw.store.get(['input', 'settings']);
var tempObj = {};
// merge templateData to tempObj
iaw.util.assign(tempObj, templateData);
if (templateData.isPreset && settings) {
// merge custom settings to tempObj
iaw.util.assign(tempObj, settings);
}
tempObj['template_category'] = 'saved';
tempObj.title = name;
tempObj.name = name;
tempObj.group = 'user';
if (hostID === 'PHXS') {
if (templateData.isPreset) {
if (window.__adobe_cep__) {
iaw.cepUtil.evalExtendScriptWithParams('CCXWelcomeXSHost_PHXS.createPreset', tempObj, name, callback);
}
else {
callback(tempObj);
}
}
}
else if (hostID === 'IDSN') {
if (templateData.isPreset) {
if (window.__adobe_cep__) {
iaw.cepUtil.sendEvent( iaw.cepUtil.events.SAVEPRESET, tempObj);
callback(tempObj);
}
}
}
return tempObj;
},
deletePreset: function(name) {
if (!window.__adobe_cep__) return;
var hostID = iaw.store.get(['host', 'hostID']);
var templateData = iaw.fnft.getTemplateData(iaw.store.get(['input', 'selected-item']));
iaw.store.set('last-deleted-template-data', JSON.stringify(templateData));
switch (hostID) {
case 'PHXS':
iaw.cepUtil.evalExtendScriptWithParams('CCXWelcomeXSHost_PHXS.deletePreset', name, iaw.fnft.closeExtOnStatus);
break;
case 'IDSN':
iaw.cepUtil.sendEvent( iaw.cepUtil.events.DELETEPRESET, name);
break;
}
},
/*
* Create a new document from the currently selected template
*
* @param showDialog boolean parameter indicating if the native document
* dialog should be shown or not
*/
createNewDocumentFromSelectedTemplate: function(showDialog) {
var self = this;
// validate required parameter
showDialog = showDialog || false;
// get the selected template
var templateData = iaw.fnft.getTemplateData(iaw.store.get(['input', 'selected-item']));
if (templateData) {
var hostID = iaw.store.get(['host', 'hostID']);
var docName = iaw.store.get(['input', 'doc-name']);
var settings = iaw.store.get(['input', 'settings']);
var tempObj = {};
// set the document name for the new document
tempObj.documentName = docName;
tempObj.showDialog = showDialog;
// merge templateData to tempObj
iaw.util.assign(tempObj, templateData);
if (templateData.isPreset && settings) {
// merge custom settings to tempObj
iaw.util.assign(tempObj, settings);
}
else if (!templateData.url) {
var status = iaw.libraryManager.getTemplateStatus(templateData.id);
if (status) {
tempObj.url = status.path;
tempObj.elementRef = status.elementRef;
}
}
switch (hostID) {
case 'PHXS':
var fnftSettings = iaw.store.get(['host', 'fnftSettings']);
if (fnftSettings.psSupportsDocCreationViaCEPEvents) {
// New approach
tempObj.pipMethod = iaw.util.getPipMethodString('stocktemplate', null);
iaw.log.console(self.LOG_PREFIX + 'Sending data to create new doc: ' + JSON.stringify(tempObj));
var psEventType = templateData.isPreset ? iaw.cepUtil.events.NEWDOCFROMTEMPLATE : iaw.cepUtil.events.PREVIEWDOCFROMTEMPLATE;
iaw.cepUtil.sendEvent(psEventType, JSON.stringify(tempObj));
}
else {
// LEGACY APPROACH - Keep until know will only ship w/ PS that has support for the above (probably PS 19.1)
if (templateData.isPreset) {
iaw.cepUtil.evalExtendScriptWithParams('CCXWelcomeXSHost_PHXS.createDocumentFromTemplate',
tempObj, docName, showDialog, iaw.fnft.closeExtOnStatus);
}
else {
var pipMethod = iaw.util.getPipMethodString('stocktemplate', null);
window.__adobe_cep__.evalScript('CCXWelcomeXSHost_PHXS.openDocumentWithPath("'+tempObj.url+'","'+pipMethod+'",true,"'+templateData.elementRef+'")', iaw.fnft.closeExtOnStatus);
}
}
break;
default:
iaw.log.console(self.LOG_PREFIX + 'Sending data to create new doc: ' + JSON.stringify(tempObj));
var eventType = iaw.cepUtil.events.NEWDOCFROMTEMPLATE;
if (settings.actionTrigger == 'preview') {
eventType = iaw.cepUtil.events.PREVIEWDOCFROMTEMPLATE;
}
iaw.cepUtil.sendEvent(eventType, JSON.stringify(tempObj));
break;
}
}
},
/**
* Get template data from the Adobe Stock service.
* @param hostData host data passed from the application
* @param cb callback method
*/
getStockData: function(hostData, cb) {
if (!hostData.adobeGUID || hostData.adobeGUID.length === 0) {
iaw.log.console(this.LOG_PREFIX + 'No user. Likely FRL or signed out.');
// Offline or FRL case.
cb(null, {});
}
var self = this;
// var startTimestamp = Date.now();
var onFinish = function(err, data) { //eslint-disable-line func-style
if (err) {
iaw.log.console(self.LOG_PREFIX + err);
}
// var durationInMilliSec = Date.now() - startTimestamp;
// iaw.log.debug(self.LOG_PREFIX + 'It costs ' + durationInMilliSec + ' ms to get stock response from CCXProcess.');
cb(data);
};
var uuid = iaw.util.generateGUID();
var responder = {};
responder.handler = function(msg) { //eslint-disable-line func-style
var responseData = VulcanInterface.getPayload(msg);
var stockJSON;
var parsedData;
// parse the JSON response
try {
parsedData = JSON.parse(responseData);
// iaw.log.debug(self.LOG_PREFIX + 'Receiving ' + self.VULCAN_STOCK_TEMPLATE_RESPONSE + ' [' + parsedData.requestId + '], path is ' + parsedData.path);
}
catch (error) {
// iaw.log.debug(self.LOG_PREFIX + 'Invalid JSON string:' + responseData);
return;
}
// validate
if (parsedData && parsedData.requestId === uuid) {
// Clear timeout only if the response has the expected requestid.
clearTimeout(self.StockTimeoutHandler);
var err = null;
if (parsedData.path) {
// read in the locally written JSON data from Adobe Stock
// and adjust image paths to the local file system
stockJSON = iaw.json.readLocalJSONFile(parsedData.path);
if (stockJSON && stockJSON.templates) {
for (var i = 0; i < stockJSON.templates.length; i++) {
var template = stockJSON.templates[i];
var parentFolder = parsedData.path.substring(0, parsedData.path.lastIndexOf('/'));
if (template['thumbnail_url'] ) {
template['thumbnail_url'] = parentFolder && parentFolder + '/' + template['thumbnail_url'];
}
if (template.previews) {
for (var j = 0; j < template.previews.length; j++) {
var preview = template.previews[j];
if (preview && preview.url) {
preview.url = parentFolder && parentFolder + '/' + preview.url;
}
}
}
}
}
}
else {
err = 'path is missing, parsedData=' + JSON.stringify(parsedData);
}
// clear IPC listeners
VulcanInterface.removeMessageListener(VulcanMessage.TYPE_PREFIX + self.VULCAN_STOCK_TEMPLATE_RESPONSE, responder.handler);
// finish out
onFinish(err, stockJSON);
}
};
// Add message lisener for Stock response
VulcanInterface.addMessageListener(VulcanMessage.TYPE_PREFIX + self.VULCAN_STOCK_TEMPLATE_RESPONSE, responder.handler);
// Send Stock request
var stockParams = {
productCode: hostData.hostID,
productVersion: hostData.appVersion,
productLanguage: hostData.language,
countryCode: hostData.countryCode,
subscriptionStatus: hostData.accountStatus,
requestId: uuid,
featureFlag: false,
release: 'CCXStart-1-5'
};
if (stockParams.subscriptionStatus === 'trial') {
stockParams.trialEndDTS = iaw.util.getTrialEndDate(hostData.secondsLeftInTrial);
}
var params = {
stockParams: stockParams,
requestId: uuid,
userTrackingEnabled: hostData.userTrackingEnabled
};
var StockRequestMsg = new VulcanMessage(VulcanMessage.TYPE_PREFIX + self.VULCAN_STOCK_TEMPLATE_REQUEST);
StockRequestMsg.setPayload(JSON.stringify(params));
VulcanInterface.dispatchMessage(StockRequestMsg);
iaw.log.console(self.LOG_PREFIX + 'Sending ' + self.VULCAN_STOCK_TEMPLATE_REQUEST + ' [' + uuid + ']');
var relaunched = false;
// Set timeout
function timeoutHandler() {
// Check and launch CCXProcess
if (!relaunched) {
relaunched = true;
iaw.util.launchCCXProcess();
VulcanInterface.dispatchMessage(StockRequestMsg);
self.StockTimeoutHandler = setTimeout(timeoutHandler, self.STOCK_RESPONSE_TIMEOUT);
return;
}
VulcanInterface.removeMessageListener(VulcanMessage.TYPE_PREFIX + self.VULCAN_STOCK_TEMPLATE_RESPONSE, responder.handler);
onFinish('It is timeout to get stock response from CCX Process.');
}
self.StockTimeoutHandler = setTimeout(timeoutHandler, self.STOCK_RESPONSE_TIMEOUT);
}
};
window.iaw = window.iaw || {};
window.iaw.fnft = fnft;
}());
/*************************************************************************
* ADOBE CONFIDENTIAL
* ___________________
*
* Copyright 2016 Adobe Systems Incorporated
* All Rights Reserved.
*
* NOTICE: All information contained herein is, and remains
* the property of Adobe Systems Incorporated and its suppliers,
* if any. The intellectual and technical concepts contained
* herein are proprietary to Adobe Systems Incorporated and its
* suppliers and are protected by all applicable intellectual property
* laws, including trade secret and copyright laws.
* Dissemination of this information or reproduction of this material
* is strictly forbidden unless prior written permission is obtained
* from Adobe Systems Incorporated.
**************************************************************************/
iaw.fnftEvents = {
CUSTOMIZE_DOC: 'host-more-options',
CREATE_DOC: 'host-create-template-doc',
CANCEL_DOC: 'host-close-fnft',
SCREEN_TEMPLATE: 'host-screen-template',
FILTER: 'filter-templates',
PREVIEW_DOC: 'host-preview-template-doc'
};
/**
* Handle the "Customize" button event
*/
riotctrl.on(iaw.fnftEvents.CUSTOMIZE_DOC, function() {
if (window.__adobe_cep__) {
iaw.fnft.createNewDocumentFromSelectedTemplate(true);
}
});
/**
* Handle the "Create Document" button event.
*/
riotctrl.on(iaw.fnftEvents.CREATE_DOC, (function() {
var isCoolingDown = false;
var coolDownTimer = 7000;
return function() {
if (window.__adobe_cep__) {
if (isCoolingDown) return;
isCoolingDown = true;
setTimeout( function() {
isCoolingDown = false;
}, coolDownTimer);
var template = iaw.fnft.getTemplateData(iaw.store.get(['input', 'selected-item']));
var hostID = iaw.store.get(['host', 'hostID']);
if (hostID == 'IDSN') {
var settings = iaw.store.get(['input', 'settings']);
settings.actionTrigger = 'actionButton';
iaw.store.set(['input', 'settings'], settings);
}
if (template && !template.isPreset) {
if (hostID !== 'PHXS') {
iaw.localstorage.setUserItem('templateLUT_' + template.id, new Date().getTime());
}
iaw.analytics.ingest.logFNFTActionClickedEvent('open-template', template);
iaw.analytics.pip.logFNFTDataGroupEvent('Open Template', {
id: String(template.id),
name: template.title,
templateCategory: template.template_category.toString(),
activeTab: template.activeFilter
});
}
iaw.fnft.createNewDocumentFromSelectedTemplate();
}
};
})());
/**
* Handle the "Close" button event.
*/
riotctrl.on(iaw.fnftEvents.CANCEL_DOC, function() {
if (!window.__adobe_cep__) return;
window.__adobe_cep__.closeExtension();
});
/**
* Handle the download template button event.
*/
riotctrl.on(iaw.fnftEvents.SCREEN_TEMPLATE, function() {
var template = iaw.store.get(['input', 'preview-item']);
iaw.analytics.ingest.logFNFTActionClickedEvent('download', template);
var showScreen = iaw.localstorage.getUserItem('fnft.showFilesizeWarning');
showScreen = typeof showScreen === 'boolean' ? showScreen : true;
if (!showScreen) {
iaw.analytics.ingest.logFNFTActionClickedEvent('license-template', template);
riotctrl.trigger('host-license-template');
return;
}
if (template.size / 1048576 > 100) {
// warn the user that this is pretty big
iaw.analytics.ingest.logFNFTActionClickedEvent('too-large-render', template);
riotctrl.trigger('host-download-template--large');
return;
}
// start the licensing process
iaw.analytics.ingest.logFNFTActionClickedEvent('license-template', template);
riotctrl.trigger('host-license-template');
});
/**
* Handle the preview document event.
**/
riotctrl.on(iaw.fnftEvents.PREVIEW_DOC, function() {
if (window.__adobe_cep__) {
iaw.fnft.createNewDocumentFromSelectedTemplate(true);
}
});
//# sourceMappingURL=fnft-iaw.js.map