Skip to content

Instantly share code, notes, and snippets.

@gregglind
Last active August 29, 2015 14:09
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save gregglind/d29ef74ec5201758f2cd to your computer and use it in GitHub Desktop.
Save gregglind/d29ef74ec5201758f2cd to your computer and use it in GitHub Desktop.
Example of Heartbeat widgets, using new an old style notifications
/* 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