From e7fdf76120df0c52ad5edbf65058ba0375c99884 Mon Sep 17 00:00:00 2001 From: Danny Coates Date: Tue, 7 Nov 2017 15:54:42 -0800 Subject: [PATCH] Added experiment for firefox download promo --- app/experiments.js | 28 +++++++++++++++-- app/main.js | 2 +- app/metrics.js | 8 ++++- app/routes/index.js | 46 +++++++++++++++++++++------ app/templates/blank.js | 5 +-- app/templates/completed.js | 4 +++ app/templates/download.js | 6 +++- app/templates/footer.js | 31 ++++++++++++++++++ app/templates/fxPromo.js | 44 ++++++++++++++++++++++++++ app/templates/header.js | 21 +++++++++++++ app/templates/legal.js | 4 --- app/templates/notFound.js | 4 --- app/templates/preview.js | 6 ++-- app/templates/unsupported.js | 4 --- app/templates/welcome.js | 6 ++-- app/utils.js | 9 +++--- assets/firefox_logo-only.svg | 2 +- assets/main.css | 34 +++++++++++++++++--- package.json | 3 ++ server/layout.js | 61 +++--------------------------------- 20 files changed, 222 insertions(+), 106 deletions(-) create mode 100644 app/templates/footer.js create mode 100644 app/templates/fxPromo.js create mode 100644 app/templates/header.js diff --git a/app/experiments.js b/app/experiments.js index 0072f93d..37bce4e5 100644 --- a/app/experiments.js +++ b/app/experiments.js @@ -1,6 +1,28 @@ import hash from 'string-hash'; -const experiments = {}; +const experiments = { + 'SyI-hI7gT9agiH-f3f0BYg': { + id: 'SyI-hI7gT9agiH-f3f0BYg', + run: function(variant, state, emitter) { + state.promo = variant === 1 ? 'body' : 'header'; + emitter.emit('render'); + }, + eligible: function() { + return ( + !/firefox/i.test(navigator.userAgent) && + document.querySelector('html').lang === 'en-US' + ); + }, + variant: function(state) { + return this.luckyNumber(state) > 0.5 ? 1 : 0; + }, + luckyNumber: function(state) { + return luckyNumber( + `${this.id}:${state.storage.get('testpilot_ga__cid')}` + ); + } + } +}; //Returns a number between 0 and 1 // eslint-disable-next-line no-unused-vars @@ -32,12 +54,12 @@ export default function initialize(state, emitter) { checkExperiments(state, emitter); }); } else { - const enrolled = state.storage.enrolled; - enrolled.forEach(([id, variant]) => { + const enrolled = state.storage.enrolled.filter(([id, variant]) => { const xp = experiments[id]; if (xp) { xp.run(variant, state, emitter); } + return !!xp; }); // single experiment per session for now if (enrolled.length === 0) { diff --git a/app/main.js b/app/main.js index 7e47e13a..2c55e0ed 100644 --- a/app/main.js +++ b/app/main.js @@ -47,4 +47,4 @@ app.use(fileManager); app.use(dragManager); app.use(experiments); -app.mount('#page-one'); +app.mount('body'); diff --git a/app/metrics.js b/app/metrics.js index 186b7c93..3f3adf4e 100644 --- a/app/metrics.js +++ b/app/metrics.js @@ -20,7 +20,7 @@ let experiment = null; export default function initialize(state, emitter) { appState = state; emitter.on('DOMContentLoaded', () => { - addExitHandlers(); + // addExitHandlers(); experiment = storage.enrolled[0]; sendEvent(category(), 'visit', { cm5: storage.totalUploads, @@ -29,6 +29,9 @@ export default function initialize(state, emitter) { }); //TODO restart handlers... somewhere }); + emitter.on('exit', evt => { + exitEvent(evt); + }); } function category() { @@ -81,6 +84,8 @@ function urlToMetric(url) { case 'https://testpilot.firefox.com/': case 'https://testpilot.firefox.com/experiments/send': return 'testpilot'; + case 'https://www.mozilla.org/firefox/new/?utm_campaign=send-acquisition&utm_medium=referral&utm_source=send.firefox.com': + return 'promo'; default: return 'other'; } @@ -244,6 +249,7 @@ function exitEvent(target) { }); } +// eslint-disable-next-line no-unused-vars function addExitHandlers() { const links = Array.from(document.querySelectorAll('a')); links.forEach(l => { diff --git a/app/routes/index.js b/app/routes/index.js index 08b84705..12b33c1d 100644 --- a/app/routes/index.js +++ b/app/routes/index.js @@ -1,17 +1,43 @@ const choo = require('choo'); +const html = require('choo/html'); const download = require('./download'); +const header = require('../templates/header'); +const footer = require('../templates/footer'); +const fxPromo = require('../templates/fxPromo'); const app = choo(); -app.route('/', require('./home')); -app.route('/share/:id', require('../templates/share')); -app.route('/download/:id', download); -app.route('/download/:id/:key', download); -app.route('/completed', require('../templates/completed')); -app.route('/unsupported/:reason', require('../templates/unsupported')); -app.route('/legal', require('../templates/legal')); -app.route('/error', require('../templates/error')); -app.route('/blank', require('../templates/blank')); -app.route('*', require('../templates/notFound')); +function body(template) { + return function(state, emit) { + const b = html` + ${state.promo === 'header' ? fxPromo(state, emit) : ''} + ${header(state)} +
+ + ${template(state, emit)} +
+ ${footer(state)} + `; + if (state.layout) { + return state.layout(state, b); + } + return b; + }; +} + +app.route('/', body(require('./home'))); +app.route('/share/:id', body(require('../templates/share'))); +app.route('/download/:id', body(download)); +app.route('/download/:id/:key', body(download)); +app.route('/completed', body(require('../templates/completed'))); +app.route('/unsupported/:reason', body(require('../templates/unsupported'))); +app.route('/legal', body(require('../templates/legal'))); +app.route('/error', body(require('../templates/error'))); +app.route('/blank', body(require('../templates/blank'))); +app.route('*', body(require('../templates/notFound'))); module.exports = app; diff --git a/app/templates/blank.js b/app/templates/blank.js index 377b9817..080a3232 100644 --- a/app/templates/blank.js +++ b/app/templates/blank.js @@ -1,9 +1,6 @@ const html = require('choo/html'); -module.exports = function(state) { +module.exports = function() { const div = html`
`; - if (state.layout) { - return state.layout(state, div); - } return div; }; diff --git a/app/templates/completed.js b/app/templates/completed.js index 18d0ce13..751b9de7 100644 --- a/app/templates/completed.js +++ b/app/templates/completed.js @@ -1,9 +1,11 @@ const html = require('choo/html'); const progress = require('./progress'); const { fadeOut } = require('../utils'); +const fxPromo = require('./fxPromo'); module.exports = function(state, emit) { const div = html` +
${state.translate( @@ -19,6 +21,8 @@ module.exports = function(state, emit) { sendNew }>${state.translate('sendYourFilesLink')}
+ ${state.promo === 'body' ? fxPromo(state, emit) : ''} +
`; async function sendNew(e) { diff --git a/app/templates/download.js b/app/templates/download.js index 38fff942..7551eb5f 100644 --- a/app/templates/download.js +++ b/app/templates/download.js @@ -1,10 +1,12 @@ const html = require('choo/html'); const progress = require('./progress'); const { bytes } = require('../utils'); +const fxPromo = require('./fxPromo'); -module.exports = function(state) { +module.exports = function(state, emit) { const transfer = state.transfer; const div = html` +
${state.translate( 'downloadingPageProgress', @@ -21,6 +23,8 @@ module.exports = function(state) { transfer.sizes )}
+
+ ${state.promo === 'body' ? fxPromo(state, emit) : ''}
`; diff --git a/app/templates/footer.js b/app/templates/footer.js new file mode 100644 index 00000000..795cdfda --- /dev/null +++ b/app/templates/footer.js @@ -0,0 +1,31 @@ +const html = require('choo/html'); +const assets = require('../../common/assets'); + +module.exports = function(state) { + return html``; +}; diff --git a/app/templates/fxPromo.js b/app/templates/fxPromo.js new file mode 100644 index 00000000..5098314d --- /dev/null +++ b/app/templates/fxPromo.js @@ -0,0 +1,44 @@ +const html = require('choo/html'); +const assets = require('../../common/assets'); + +// function replaceLinks(str, urls) { +// let i = -1; +// const s = str.replace(/([^<]+)<\/a>/g, (m, v) => { +// i++; +// return `${v}`; +// }); +// return [`${s}`]; +// } + +module.exports = function(state, emit) { + // function close() { + // document.querySelector('.banner').remove(); + // } + + function clicked(evt) { + emit('exit', evt); + } + + return html` + `; +}; + +/* + +*/ diff --git a/app/templates/header.js b/app/templates/header.js new file mode 100644 index 00000000..edd39d7a --- /dev/null +++ b/app/templates/header.js @@ -0,0 +1,21 @@ +const html = require('choo/html'); +const assets = require('../../common/assets'); + +module.exports = function(state) { + return html`
+ + +
`; +}; diff --git a/app/templates/legal.js b/app/templates/legal.js index 9fa3f0f5..af196ab9 100644 --- a/app/templates/legal.js +++ b/app/templates/legal.js @@ -30,9 +30,5 @@ module.exports = function(state) {
`; - - if (state.layout) { - return state.layout(state, div); - } return div; }; diff --git a/app/templates/notFound.js b/app/templates/notFound.js index 5dc8fba3..c29bf194 100644 --- a/app/templates/notFound.js +++ b/app/templates/notFound.js @@ -17,9 +17,5 @@ module.exports = function(state) { )} `; - - if (state.layout) { - return state.layout(state, div); - } return div; }; diff --git a/app/templates/preview.js b/app/templates/preview.js index 93129d1b..cb5352ac 100644 --- a/app/templates/preview.js +++ b/app/templates/preview.js @@ -3,6 +3,7 @@ const assets = require('../../common/assets'); const notFound = require('./notFound'); const downloadPassword = require('./downloadPassword'); const { bytes } = require('../utils'); +const fxPromo = require('./fxPromo'); function getFileFromDOM() { const el = document.getElementById('dl-file'); @@ -61,6 +62,7 @@ module.exports = function(state, emit) { ${state.translate('sendYourFilesLink')} + ${state.promo === 'body' ? fxPromo(state, emit) : ''} `; @@ -68,9 +70,5 @@ module.exports = function(state, emit) { event.preventDefault(); emit('download', fileInfo); } - - if (state.layout) { - return state.layout(state, div); - } return div; }; diff --git a/app/templates/unsupported.js b/app/templates/unsupported.js index a4a7a60d..f37c9bd5 100644 --- a/app/templates/unsupported.js +++ b/app/templates/unsupported.js @@ -42,9 +42,5 @@ module.exports = function(state) { )} `; const div = html`
${msg}
`; - - if (state.layout) { - return state.layout(state, div); - } return div; }; diff --git a/app/templates/welcome.js b/app/templates/welcome.js index 3bac7f75..0f004608 100644 --- a/app/templates/welcome.js +++ b/app/templates/welcome.js @@ -1,6 +1,7 @@ const html = require('choo/html'); const assets = require('../../common/assets'); const fileList = require('./fileList'); +const fxPromo = require('./fxPromo'); const { fadeOut } = require('../utils'); module.exports = function(state, emit) { @@ -35,6 +36,7 @@ module.exports = function(state, emit) { title="${state.translate('uploadPageBrowseButton1')}"> ${state.translate('uploadPageBrowseButton1')} + ${state.promo === 'body' ? fxPromo(state, emit) : ''} ${fileList(state, emit)} `; @@ -67,9 +69,5 @@ module.exports = function(state, emit) { await fadeOut('page-one'); emit('upload', { file, type: 'click' }); } - - if (state.layout) { - return state.layout(state, div); - } return div; }; diff --git a/app/utils.js b/app/utils.js index ca261322..06f480f9 100644 --- a/app/utils.js +++ b/app/utils.js @@ -108,7 +108,8 @@ function bytes(num) { let nStr = n.toFixed(1); if (LOCALIZE_NUMBERS) { try { - nStr = n.toLocaleString(navigator.languages.map(l => l.split(';')[0]), { + const locale = document.querySelector('html').lang; + nStr = n.toLocaleString(locale, { minimumFractionDigits: 1, maximumFractionDigits: 1 }); @@ -122,10 +123,8 @@ function bytes(num) { function percent(ratio) { if (LOCALIZE_NUMBERS) { try { - return ratio.toLocaleString( - navigator.languages.map(l => l.split(';')[0]), - { style: 'percent' } - ); + const locale = document.querySelector('html').lang; + return ratio.toLocaleString(locale, { style: 'percent' }); } catch (e) { // fall through } diff --git a/assets/firefox_logo-only.svg b/assets/firefox_logo-only.svg index 2fc8eedd..4b330869 100644 --- a/assets/firefox_logo-only.svg +++ b/assets/firefox_logo-only.svg @@ -1 +1 @@ - \ No newline at end of file +firefox-logo \ No newline at end of file diff --git a/assets/main.css b/assets/main.css index 12d4af57..6971a32d 100644 --- a/assets/main.css +++ b/assets/main.css @@ -8,7 +8,6 @@ html { background-repeat: no-repeat; background-position: center top; height: 100%; - max-width: 1440px; margin: auto; } @@ -130,7 +129,7 @@ body { display: flex; flex-direction: column; justify-content: flex-start; - max-width: 630px; + max-width: 650px; margin: 0 auto; padding: 0 20px; box-sizing: border-box; @@ -213,7 +212,7 @@ a { .upload-window { border: 3px dashed rgba(0, 148, 251, 0.5); - margin: 0 auto; + margin: 0 auto 10px; height: 255px; border-radius: 4px; display: flex; @@ -709,6 +708,10 @@ tbody { width: 70px; } +.firefox-logo-small { + width: 24px; +} + #dl-firefox, #update-firefox { margin-bottom: 181px; @@ -766,7 +769,7 @@ tbody { } #download { - margin: 0 auto; + margin: 0 auto 30px; display: flex; justify-content: center; align-items: center; @@ -955,6 +958,29 @@ tbody { background-position: 2px 1px; } +.banner { + padding: 0 15px; + height: 48px; + background-color: #efeff1; + color: #4a4a4f; + font-size: 13px; + display: flex; + flex-direction: row; + align-content: center; + align-items: center; + justify-content: center; +} + +.banner > div { + display: flex; + align-items: center; + margin: 0 auto; +} + +.banner > div > span { + margin-left: 10px; +} + @media (max-device-width: 992px), (max-width: 992px) { .popup .popuptext { left: auto; diff --git a/package.json b/package.json index ea7a9776..7235fb8e 100644 --- a/package.json +++ b/package.json @@ -112,6 +112,7 @@ "en-US", "ast", "az", + "bs", "ca", "cak", "cs", @@ -132,6 +133,7 @@ "id", "it", "ja", + "ka", "kab", "ko", "ms", @@ -145,6 +147,7 @@ "sl", "sr", "sv-SE", + "tl", "tr", "uk", "vi", diff --git a/server/layout.js b/server/layout.js index 0c5e65b6..18bef3cd 100644 --- a/server/layout.js +++ b/server/layout.js @@ -8,7 +8,7 @@ module.exports = function(state, body = '') { : ''; return html` - + @@ -31,7 +31,7 @@ module.exports = function(state, body = '') { ${state.title} - + @@ -62,8 +62,8 @@ module.exports = function(state, body = '') { - - + + ${firaTag} @@ -71,58 +71,7 @@ module.exports = function(state, body = '') { - -
- - -
-
- - ${body} -
- - + ${body} `; };