Last active
August 29, 2015 14:09
-
-
Save gregglind/d29ef74ec5201758f2cd to your computer and use it in GitHub Desktop.
Example of Heartbeat widgets, using new an old style notifications
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* jshint forin:true, noarg:false, noempty:true, eqeqeq:true, bitwise:true, | |
strict:true, undef:true, curly:false, browser:true, | |
unused:true, | |
indent:2, maxerr:50, devel:true, node:true, boss:true, white:true, | |
globalstrict:true, nomen:false, newcap:true, esnext: true, moz: true */ | |
/*global exports, require */ | |
// "use strict"; | |
/** USAGE | |
* | |
* 1. open a chrome page, such as "about:addons" | |
* 2. open dev tools (tools > web developer > dev console) | |
* 3. Cut and past this entire file. | |
* | |
* Exposes: `makeNotice(which, overrides)` | |
* examples: | |
* | |
* let o = makeNotice("stars") // or "nps" | |
* let o = makeNotice("nps", {mikestyle: true}) | |
* let o = makeNotice("stars", {mikestyle: true, msg: "Is Firefox Awesome, or the MOST AWESOME?", minus: "just awesome", plus: "most awesome"}) | |
* let o = makeNotice("stars", {mikestyle: true, msg: "Is Firefox Awesome, or the MOST AWESOME?", minus: "just awesome", plus: "most awesome", outmsg="Who rules? YOU DO! Thanks.", delay: 2000}) | |
*/ | |
let SOURCES = [ | |
"mxr.mozilla.org/mozilla-central/source/toolkit/content/widgets/notification.xml", | |
"https://developer.mozilla.org/en-US/docs/Mozilla/Tech/XUL/notificationbox", | |
"http://mxr.mozilla.org/mozilla-central/source/toolkit/content/xul.css#183", | |
"chrome://global/skin/notification.css" | |
]; | |
// https://www.surveymonkey.com/blog/en/net-promoter-score-survey-template/ | |
// http://en.wikipedia.org/wiki/Net_Promoter | |
// http://en.wikipedia.org/wiki/Net_Promoter#cite_note-16 | |
// http://people.mozilla.org/~mmaslaney/prompt/Prompt-spec.png | |
// utils | |
let hoverize = function (el, fhover, foff) { | |
el.addEventListener("mouseover", fhover); | |
el.addEventListener("mouseout", foff); | |
}; | |
function fragmentFromString (strHTML) { | |
var frag = document.createDocumentFragment(); | |
//var html = '<div>x</div><span>y</span>'; | |
var holder = document.createElement("div"); | |
holder.innerHTML = strHTML; | |
var ii; | |
for (ii = 0; ii < holder.childElementCount; ii++) { | |
////console.log(ii); | |
////console.log(holder.children[ii]); | |
frag.appendChild(holder.children[ii]); | |
} | |
return (frag); | |
} | |
function styleEl (el, styles, unset ) { | |
for (let k in styles) { | |
let ans = unset ? 'inherit' : styles[k]; | |
//console.log("styling", k, ans); | |
el.style[k] = ans; | |
} | |
}; | |
/* | |
function update () { | |
let out = {}; | |
// makes a new object with elements form all, right overriding left. | |
Array.prototype | |
return out; | |
}*/ | |
// should be const | |
let BROWSER = 'navigator:browser'; | |
let WM = Cc['@mozilla.org/appshell/window-mediator;1']. | |
getService(Ci.nsIWindowMediator); | |
function getMostRecentWindow(type) { | |
return WM.getMostRecentWindow(type); | |
} | |
function getMostRecentBrowserWindow() { | |
return getMostRecentWindow(BROWSER); | |
} | |
/* super gross plan | |
## GROSS GROSS, probably have to have a temporarily visible non widgeted bar, | |
then hack around it? | |
# append a blank (no buttons), then append on bbox with stuff? | |
*/ | |
// do the bar | |
let starchar = "★"; | |
let makeStarElString = function (n) { | |
let out = []; | |
for (let ii = 0; ii < n; ii++) { | |
let j = ii+1; | |
out.push(`<span class="star-x" data-score="`+ j +`" id="star`+ j +`">`+ starchar +`</span>`); | |
} | |
return out; | |
}; | |
let makeNpsString = function () { | |
let n = 11; | |
let out = []; | |
for (let ii = 0; ii < n; ii++) { | |
let j = ii; | |
// out.push(`<span class="star-x" style="background-color:rgba(0, 0, 255,.1); margin: 0px 3px; width:30px; display: block; text-align: center;" id="star`+ j +`">`+ ii +`</span>`); | |
out.push(`<span class="star-x" data-score="`+ j +`" style="box-shadow: 0px 0px 0px 2px rgba(255,255,255,.1); border-radius: 15px; margin: 0px 3px; width:30px; display: block; text-align: center;" id="star`+ j +`">`+ ii +`</span>`); | |
} | |
return out; | |
}; | |
//let CONTEXT = "stars"; | |
let stuff = { | |
"stars": { | |
"msg": "Rate Firefox", | |
plus: "+", // nice plus | |
minus: "−", // nice minus | |
// should this be a fn? | |
widgetstring: makeStarElString(nstars=5).join("\n"), | |
widgetLitStyles: { | |
color: 'orange' | |
}, | |
widgetUnlitStyles: { | |
}, | |
_v: "stars.v1" | |
}, | |
"nps": { | |
"msg": "How likely are you to recommend Firefox to others?", // msg, | |
plus: "extremely", // nice plus | |
minus:"not at all", // nice minus | |
widgetstring: makeNpsString().join("\n"), | |
widgetLitStyles: { | |
//color: 'white', | |
backgroundColor: "orange", | |
boxShadow: "0px 0px 0px 2px rgba(255,255,255,1)" // white | |
}, | |
widgetUnlitStyles: { | |
//color: 'white', | |
boxShadow: "0px 0px 0px 2px rgba(255,255,255,.1)" | |
}, | |
_v: "nps.v1" | |
} | |
}; | |
// http://people.mozilla.org/~mmaslaney/prompt/Prompt-spec.png | |
// partial impl. | |
let mikestyle = { | |
// osx | |
notice: { | |
color: '#333333', | |
fontWeight: "normal", | |
fontFamily: "Lucida Grande", | |
//lineHeight: "16px", | |
background: "#F1F1F1", // no image! | |
boxShadow: "0px 1px 0px 0px rgba(0,0,0,0.35)" | |
}, | |
messageText: { | |
fontWeight: "normal", | |
fontFamily: "Lucida Grande", | |
//fontSize: "11.5px", | |
color: '#333333', | |
//lineHeight: "16px", | |
}, | |
exit: { | |
marginLeft: "10px" | |
}, | |
messageImage: { | |
marginRight: "10px" | |
} | |
}; | |
// unclear what arity and args should be here. | |
let makeNotice = function (which, overrides) { | |
if (overrides === undefined) overrides = {}; | |
// should we mixin the overrides over the conf? | |
// TODO, maybe after one more :) | |
let conf = stuff[which]; | |
let icons = { | |
question: "chrome://global/skin/icons/question-large.png", | |
heart: "https://raw.githubusercontent.com/gregglind/firefox-pulse/master/pulse-response-rate-ui-test/data/icons/icon-b.png", | |
fx: "https://raw.githubusercontent.com/raymak/contextualfeaturerecommender/156b4fd7003691b5019cbccc30a10d372b7984d5/phase1/addon/data/ui/icons/firefox-highres.png", | |
}; | |
icon = icons.fx; | |
if (overrides.icon) { | |
icon = icons[overrides.icon] || overrides.icon // url path! | |
} | |
let options = [ | |
overrides.msg || conf.msg, // msg, | |
null, // id, | |
icon, // icon, | |
1, // priority, | |
null, // buttons, | |
null, // callback | |
]; | |
let win = getMostRecentBrowserWindow(); | |
// noficication | |
let box = win.document.getElementById("high-priority-global-notificationbox"); | |
box.removeAllNotifications(); | |
let notice = box.appendNotification.apply(box,options); | |
let messageImage = document.getAnonymousElementByAttribute(notice, "class", "messageImage"); | |
let messageText = document.getAnonymousElementByAttribute(notice, "class", "messageText"); | |
let closeButton = document.getAnonymousElementByAttribute(notice, "class", "messageCloseButton close-icon tabbable"); | |
styleEl(messageImage, {width: "64px", height: "64px"}); | |
let starstring = `<span id="star"> ` + conf.widgetstring + `</span>` | |
// put it on! | |
let starry = fragmentFromString(starstring); | |
let moreEl = document.createElement("span"); | |
moreEl.textContent = overrides.plus || conf.plus; | |
let lessEl = document.createElement("span"); | |
lessEl.textContent = overrides.minus || conf.minus; | |
// style more and less | |
[moreEl, lessEl].map((el) => { | |
el.style.padding="0px 5px"; | |
el.style.opacity = '.5'; | |
}) | |
switch (which) { | |
case "nps": | |
notice.style.fontSize="16px"; | |
notice.style.lineHeight="20px"; | |
break; | |
case "stars": | |
notice.style.fontSize="24px"; | |
notice.style.lineHeight="28px"; | |
// shrink plus / minus | |
moreEl.style.fontSize = "16px"; | |
lessEl.style.fontSize = "16px"; | |
break; | |
} | |
// move the flexer | |
let spacer = messageText.nextSibling; | |
messageText.parentElement.appendChild(spacer.cloneNode()); | |
spacer.remove(); // is this destroyed? | |
delete spacer; | |
messageText.style.display = "inline"; // TODO, is this right? | |
messageText.removeAttribute("flex"); // TODO, is this right? | |
messageText.style.paddingRight="20px"; // margin doesn't work here. | |
notice.appendChild(lessEl); | |
notice.appendChild(starry); | |
notice.appendChild(moreEl); | |
// show counter | |
let scoreEl = document.createElement("span"); | |
scoreEl.id="score"; | |
scoreEl.textContent = ''; | |
notice.appendChild(scoreEl); | |
scoreEl.style.width="24px"; | |
scoreEl.style.textAlign = "center"; | |
scoreEl.style.display = "block"; | |
scoreEl.style.margin = "0px 10px"; | |
if (which == "nps") scoreEl.style.display = "none"; | |
// name of this button set | |
let starEl = notice.children.star; | |
//starEl.style.marginRight="24px"; | |
// events. | |
// HOVER sets stars | |
(function (el, litStyles, unlitStyles) { | |
function setStars(min, max){ | |
//console.log('setStars',n); | |
Array.forEach(el.getElementsByClassName("star-x"), function(el){ | |
var i = /star(.*)/.exec(el.id)[1]; | |
i = Number(i, 10); | |
// switch lit and unlit styles | |
if ((min <= i) && (i <= max)) { | |
styleEl(el, unlitStyles, false); | |
styleEl(el, litStyles); | |
//el.classList.add("star-on"); | |
} else { | |
styleEl(el, litStyles, true); | |
styleEl(el, unlitStyles); | |
//el.classList.remove("star-on"); | |
} | |
}); | |
} | |
// hover for each star. | |
Array.forEach(el.getElementsByClassName("star-x"),function(el){ | |
var n = /star(.*)/.exec(el.id)[1]; | |
n = Number(n,10); | |
el.addEventListener("mouseover", function(evt){ | |
////console.log("hover on",evt.target.id); | |
//console.log(n); | |
if (which == "nps") { | |
setStars(n, n); | |
} else { | |
setStars(0,n); | |
} | |
// set score | |
scoreEl.textContent = n; | |
scoreEl.style.opacity = "" + (n / nstars); | |
scoreEl.style.color = "orange"; | |
}); | |
el.addEventListener("mouseout", function(evt){ | |
////console.log("hover on",evt.target.id); | |
setStars(-1, -1); | |
// set score | |
scoreEl.textContent = ""; | |
//scoreEl.style.opacity = "" + (n / nstars); | |
//scoreEl.style.color = "orange"; | |
}); | |
}); | |
})(starEl, conf.widgetLitStyles, conf.widgetUnlitStyles); | |
// hoverize(starEl, (el) => styleEl(starEl, stylesOn), (el) => styleEl(starEl, stylesOn, true)) | |
// click emits and closes the bar. | |
starEl.addEventListener("click", function (evt) { /*weak*/ | |
let s = evt.target.getAttribute("data-score"); | |
console.log('you rated:', s, evt.target.id); | |
// could be set timeout, if we're worried here. | |
// thank you. | |
notice.label = overrides.outmsg || conf.outmsg || "Thank you for making Firefox better!"; | |
// empty it | |
while (notice.firstChild) { | |
notice.removeChild(notice.firstChild); | |
} | |
let delay = overrides.delay || conf.delay || 1000 | |
window.setTimeout(() => {box.removeCurrentNotification()}, delay) | |
}); | |
if (overrides && overrides.mikestyle) { | |
console.log("overriding styles"); | |
styleEl(notice, mikestyle.notice); | |
styleEl(messageText, mikestyle.messageText); | |
styleEl(closeButton, mikestyle.exit); | |
styleEl(messageImage, mikestyle.messageImage); | |
} | |
// final GRL overides | |
messageImage.style.margin = "-10px 10px -20px -12px"; | |
// should return the options / overrides maybe? | |
return { | |
win: win, | |
box: box, | |
scoreEl: scoreEl, | |
notice: notice, | |
which: which, | |
messageImage: messageImage, | |
messageText: messageText, | |
closeButton: closeButton | |
} | |
} | |
let o = makeNotice("stars", {mikestyle: true, msg: "Is Firefox Awesome, or the MOST AWESOME?", minus: "just awesome", plus: "most awesome"}) | |
// other images... o.notice.image = "chrome://branding/content/about-logo@2x.png" | |
// https://raw.githubusercontent.com/raymak/contextualfeaturerecommender/156b4fd7003691b5019cbccc30a10d372b7984d5/phase1/addon/data/ui/icons/firefox-highres.png |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment