diff --git a/apps/addons/buttons.py b/apps/addons/buttons.py index c4a528ed527..0f1db754cb6 100644 --- a/apps/addons/buttons.py +++ b/apps/addons/buttons.py @@ -34,7 +34,8 @@ def install_button(context, addon, version=None, show_eula=True, addon.id in request.amo_user.mobile_addons) c = {'button': button, 'addon': addon, 'version': button.version, 'installed': installed} - t = jingo.render_to_string(request, 'addons/button.html', c) + template = 'addons/mobile/button.html' if request.MOBILE else 'addons/button.html' + t = jingo.render_to_string(request, template, c) return jinja2.Markup(t) diff --git a/apps/addons/templates/addons/button.html b/apps/addons/templates/addons/button.html index c64a8623842..9c9966a7bb0 100644 --- a/apps/addons/templates/addons/button.html +++ b/apps/addons/templates/addons/button.html @@ -2,7 +2,7 @@ {% set _obj = version if amo.HAS_COMPAT[addon.type] else addon %} {% set compat = _obj.compatible_apps[APP] if _obj else None %} -
+
+ {% set links = b.links() %} + {% if not links %} + + {{ _('No compatible versions') }} + + {% endif %} + {% for link in links %} + {% set extra = "platform " + link.os.shortname if link.os else "" %} + + {{ _('Add to Firefox') }} + + {% endfor %} +
    + {% if False %} +
  • {{ _('Requires Newer Version of Firefox') }}
  • + {% endif %} + {% if settings.PERF_THRESHOLD and addon.ts_slowness >= settings.PERF_THRESHOLD %} +
  • {{ _('May Slow Down Your Browser') }}
  • + {% endif %} + {% if files and files[0].no_restart %} +
  • {{ _('No Restart Required') }}
  • + {% endif %} +
+ {% if addon.privacy_policy %} + + {{ _('View privacy policy') }} + + {% endif %} +
{# install #} + diff --git a/apps/addons/templates/addons/mobile/details.html b/apps/addons/templates/addons/mobile/details.html index aaa0714c231..b95cd108413 100644 --- a/apps/addons/templates/addons/mobile/details.html +++ b/apps/addons/templates/addons/mobile/details.html @@ -8,10 +8,8 @@

{{ addon.name }}

{# TODO: make this a secret link #}

{{ _('by') }} {{ users_list(addon.listed_authors) }}

-
- Add to Firefox - {# TODO: privacy policy (in button.html right now) #} -
+ {{ big_install_button(addon, show_warning=False) }} + {# TODO: privacy policy (in button.html right now) #}

diff --git a/apps/versions/helpers.py b/apps/versions/helpers.py index 953a39f0a1d..b4a694537b2 100644 --- a/apps/versions/helpers.py +++ b/apps/versions/helpers.py @@ -12,5 +12,6 @@ def version_detail(context, addon, version, src, @jingo.register.inclusion_tag('versions/mobile/version.html') -def mobile_version_detail(addon, version, src): - return locals() +@jinja2.contextfunction +def mobile_version_detail(context, addon, version, src): + return new_context(**locals()) diff --git a/apps/versions/templates/versions/mobile/version.html b/apps/versions/templates/versions/mobile/version.html index 68986938e8e..63fcecf7ee3 100644 --- a/apps/versions/templates/versions/mobile/version.html +++ b/apps/versions/templates/versions/mobile/version.html @@ -35,8 +35,5 @@

{{ _('Works with:') }}

{% endif %}
-
- Add to Firefox - {# TODO: privacy policy (in button.html right now) #} -
+ {{ big_install_button(addon, version=version) }} diff --git a/media/css/zamboni/mobile.css b/media/css/zamboni/mobile.css index a3e698b81c7..419b4332809 100644 --- a/media/css/zamboni/mobile.css +++ b/media/css/zamboni/mobile.css @@ -77,8 +77,15 @@ h1, h2, h3, .moz-menu { font-family: Georgia, serif; } +a:link { + font-family: Georgia, serif; + color: #447bc4; + text-decoration: none; +} .num_ratings, -.review .old-version { +.review .old-version, +.install-wrapper, +.install-wrapper a { font-family: "Droid Sans", sans-serif; } #content h2 { @@ -93,11 +100,6 @@ h1, h2, h3, .copy p { color: #444; } -a:link { - font-family: Georgia, serif; - color: #447bc4; - text-decoration: none; -} /************************************/ /* GRADIENTS */ @@ -836,17 +838,19 @@ td .versions li a { /* BUTTONS */ /************************************/ -.installer { +.install-wrapper { margin: 10px 0 4px; padding-top: 8px; + text-align: center; + font-weight: bold; } -.infobox .installer { +.infobox .install-wrapper { border-top: 2px solid #fff; box-shadow: 0 -1px #ccc; } -.button { +.button, a.button { -moz-transition: -moz-box-shadow 0.3s ease 0s; background-image: -moz-linear-gradient(#669BE1, #5784BF) repeat scroll 0 0 transparent; background-image: -webkit-gradient( @@ -910,7 +914,7 @@ td .versions li a { 0 0 100px rgba(255, 255, 255, 0.2) inset; } -.button.install { +.button.add { background-color: #84C63C; background-image: -moz-linear-gradient(#84C63C, #489615); background-image: -webkit-gradient( @@ -923,7 +927,7 @@ td .versions li a { color: #fff; padding-left: 40px; } -.button.install:before { +.button.add:before { content: " "; display: block; width: 24px; @@ -931,16 +935,79 @@ td .versions li a { position: absolute; left: 14px; top: 10px; - background: url(../../img/zamboni/mobile/install.svg) no-repeat center center; -} - -.button.install.disabled { + background: url(../../img/zamboni/mobile/install.svg) no-repeat top left; +} + +.button.warning { + background-attachment: scroll, scroll; + background-clip: border-box, border-box; + background-color: transparent; + background-image: -moz-linear-gradient(rgba(255, 255, 255, 0.25), rgba(255, 255, 255, 0.25)), + url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAIAAAAC64paAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAGlJREFUeNqc0cEJACEMRFFNARaw/RexFe1dG3AjCBJJYDL/kNtjDqn9lRLUnlrixjd5qVdo6WNQOhiXN05Jg7PyYEJuzMmFaRn+GZG6KrQMlxHpY1A6GJc3TkmDs/JgQm7MyYVpqf0CDABVcj3T2ITzOAAAAABJRU5ErkJggg==); + background-origin: padding-box, padding-box; + background-position: 0 0%, 0 0; + background-repeat: repeat, repeat; + background-size: auto auto, auto auto; + color: #333333; + text-shadow: 0 -1px 0 rgba(255, 255, 255, 0.5); + top: 0; +} +.button.add.warning:before { + background: url(../../img/zamboni/mobile/install.svg) no-repeat 0 -50px; +} +.button.disabled { background: -moz-linear-gradient(#d1d4d7, #c1c5ca); color: #fff; -moz-box-shadow: 0 3px rgba(0, 0, 0, 0.05), 0 -4px rgba(0, 0, 0, 0.05) inset; top: 0; } +.button.add.disabled:before { + background: url(../../img/zamboni/mobile/install.svg) no-repeat top left; +} + +/** Button Badges */ + +.badges { + margin-top: 8px; +} +.badges a, .badges li { + display: block; + color: black; +} +.badges li { + border: 1px solid #ddd; + -moz-border-radius: 4px; + -webkit-border-radius: 4px; + border-radius: 4px; + margin-bottom: 8px; + font-size: .8em; + line-height: 20px; + height: 20px; + background: #fff; +} +.badges .error { + background-color: #edd4d2; + border-color: #ac9a98; +} +.badges .warning { + background-color: #fef9d7; + border-color: #bebaa1; +} +.install .privacy-policy { + display: block; + margin-top: 12px; + -moz-border-radius: 4px; + -webkit-border-radius: 4px; + border-radius: 4px; + height: 27px; + font-size: .9em; + color: black; + line-height: 25px; + border: 1px solid #ccc; + background-color: #e6e6e6; + background-image: -moz-linear-gradient(#e6e6e6, #d3d3d3); +} /************************************/ /* REVIEWS */ diff --git a/media/img/zamboni/mobile/install.svg b/media/img/zamboni/mobile/install.svg index c8f00bfee36..46b0ea44064 100644 --- a/media/img/zamboni/mobile/install.svg +++ b/media/img/zamboni/mobile/install.svg @@ -1,3 +1,5 @@ - + + + diff --git a/media/js/zamboni/init.js b/media/js/zamboni/init.js index a0802b7c011..9bcb65f4803 100644 --- a/media/js/zamboni/init.js +++ b/media/js/zamboni/init.js @@ -1,5 +1,5 @@ /* Global initialization script */ -z = {}; +var z = {}; $(document).ready(function(){ @@ -90,7 +90,8 @@ var escape_ = function(s){ .replace("'", ''').replace('"', '"'); }; - +//TODO(potch): kill underscore dead. until then, fake it on mobile. +if (!('_' in window)) _ = {}; /* is ``key`` in obj? */ _.haskey = function(obj, key) { return typeof obj[key] !== "undefined"; diff --git a/media/js/zamboni/mobile_buttons.js b/media/js/zamboni/mobile_buttons.js new file mode 100644 index 00000000000..c571976289e --- /dev/null +++ b/media/js/zamboni/mobile_buttons.js @@ -0,0 +1,222 @@ +(function() { + /* Call this with something like $('.install').installButton(); */ + z.button = {}; + + /* A library of callbacks that may be run after InstallTrigger succeeds. + * ``this`` will be bound to the .install button. + */ + z.button.after = {'contrib': function(xpi_url, status) { + if (status === 0) { //success + document.location = $(this).attr('data-developers'); + } + }}; + + /* Install an XPI or a JAR (or something like that). + * + * hash and callback are optional. callback is triggered after the + * installation is complete. + */ + z.installAddon = function(name, url, icon, hash, callback) { + var params = {}; + params[name] = { + URL: url, + IconURL: icon, + toString: function() { return url; } + }; + if (hash) { + params[name]['Hash'] = hash; + } + // InstallTrigger is a Gecko API. + InstallTrigger.install(params, callback); + }; + + z.installSearch = function(name, url, icon, hash, callback) { + if (window.external && window.external.AddSearchProvider) { + window.external.AddSearchProvider(url); + callback(); + } else { + // Alert! Deal with it. + alert(gettext('Sorry, you need a Mozilla-based browser (such as Firefox) to install a search plugin.')); + } + }; + + var messages = { + 'tooNew': format(gettext("Not Updated for {0} {1}"), z.appName, z.browserVersion), + 'tooOld': format(gettext("Requires Newer Version of {0}"), z.appName), + 'unreviewed': gettext("Unreviewed"), + 'badApp': format(gettext("Not Available for {0}"), z.appName), + 'experimental': gettext("Experimental") + }; + + function Button(el) { + // the actionQueue holds all the various events that have to happen + // when the user clicks the button. This includes the terminal action, + // such as "install", "purchase", or "add to mobile". + // actions are a tuple of the form [n, cb], where cb is a method that + // is called when the action is executed, and n is the priority of the + // action. The queue is sorted before execution. the callback is + // executed in the function's scope. To resume, call this.nextAction() + // after a user or other blocking action, or return true to + // immediately execute the next action. + this.actionQueue = []; + + var self = this, + attr, classes, + hashes = {}, + errors = [], + warnings = [], + activeInstaller, + currentAction=0, + //setup references to DOM UI. + dom = { + 'self' : $(el), + 'badges' : $(el).find(".badges"), + //Can be multiple buttons in the case of platformers + 'buttons' : $('.button', el), + 'labels' : $('.button span', el) + }; + + // the initializer is called once when the button is created. + this.init = function() { + initFromDom(); + collectHashes(); + + versionPlatformCheck(); + + this.actionQueue.push([0, function() { + var href = activeInstaller.attr('href'); + hash = hashes[href], + attr = self.attr, + install = attr.search ? z.installSearch : z.installAddon; + install(attr.name, href, attr.icon, hash); + return true; + }]); + + for (var i=0; i{0}", messages[errors[i]])); + } + for (i=0; i{0}", messages[warnings[i]])); + } + + // sort the actionQueue by priority + this.actionQueue.sort(function (a, b) {return b[0]-a[0]}); + }; + + function collectHashes() { + dom.self.find('.button[data-hash]').each(function() { + hashes[$(this).attr('href')] = $(this).attr('data-hash'); + }); + } + + function startInstall(e) { + e.preventDefault(); + self.currentAction=0; + activeInstaller = $(this); + nextAction(); + } + + // performs the next action in the queue. + function nextAction() { + if (self.currentAction >= self.actionQueue.length) return; + self.currentAction++; + // execute the next action. + var result = self.actionQueue[0][1].call(this); + // execute the next action if the current action returns true. + if (result === true) { + self.resumeInstall(); + }; + } + this.resumeInstall = function() { + // moving on. + nextAction(); + }; + + //collects all the classes and parameters from the DOM elements. + function initFromDom() { + var b = dom.self; + + self.attr = { + 'addon' : b.attr('data-addon'), + 'min' : b.attr('data-min'), + 'max' : b.attr('data-max'), + 'name' : b.attr('data-name'), + 'icon' : b.attr('data-icon'), + 'after' : b.attr('data-after'), + 'search' : b.hasattr('data-search'), + 'accept_eula' : b.hasClass('accept') + }; + + self.classes = { + 'selfhosted' : b.hasClass('selfhosted'), + 'beta' : b.hasClass('beta'), + 'unreviewed' : b.hasClass('unreviewed'), // && !beta, + 'persona' : b.hasClass('persona'), + 'contrib' : b.hasClass('contrib'), + 'search' : b.hasattr('data-search'), + 'eula' : b.hasClass('eula') + }; + + self.platforms = dom.buttons.filter(".platform").map(function () { + return self.attr("data-platform"); + }) + } + + // Add version and platform warnings and (optionally) popups. This is one + // big function since we merge the messaging when bad platform and version + // occur simultaneously. Returns true if a popup was added. + function versionPlatformCheck(options) { + var b = dom.self, + attr = self.attr, + classes = self.classes, + badPlatform = (b.find('.os').length && + !b.hasClass(z.platform)), + appSupported = z.appMatchesUserAgent && attr.min && attr.max, + olderBrowser, newerBrowser, + canInstall = true; + + // min and max only exist if the add-on is compatible with request[APP]. + if (appSupported) { + // The user *has* an older/newer browser. + self.tooOld = VersionCompare.compareVersions(z.browserVersion, attr.min) < 0; + self.tooNew = VersionCompare.compareVersions(z.browserVersion, attr.max) > 0; + if (self.tooOld || self.tooNew) { + canInstall = false; + } + if (self.tooOld) errors.push("tooOld"); + if (self.tooNew) errors.push("tooNew"); + } else { + errors.push("badApp"); + canInstall = false; + } + + if (classes.beta) warnings.push("experimental"); + if (classes.unreviewed) warnings.push("unreviewed"); + + if (classes.beta || classes.unreviewed) { + dom.buttons.addClass("warning"); + } + + if (!canInstall) { + dom.buttons.addClass("disabled"); + dom.buttons.each(function() { + this.removeAttribute("href"); + }); + } else { + dom.buttons.click(startInstall); + } + }; + + //and of course, initialize the button. + this.init(); + } + + z.b = function() { + new Button(this); + } + + jQuery.fn.installButton = function() { + return this.each(z.b); + }; + +})(); \ No newline at end of file diff --git a/settings.py b/settings.py index 3f56a82e4b1..a339ef3ed37 100644 --- a/settings.py +++ b/settings.py @@ -480,9 +480,11 @@ def JINJA_CONFIG(): 'js/zamboni/jquery-1.5.min.js', 'js/zamboni/jqmobile.js', 'js/zamboni/browser.js', + 'js/zamboni/init.js', + 'js/zamboni/format.js', + 'js/zamboni/mobile_buttons.js', 'js/zamboni/truncation.js', 'js/zamboni/mobile.js', - 'js/zamboni/format.js', ), } } diff --git a/templates/mobile/base.html b/templates/mobile/base.html index cf1910bc4ba..60d010e7a3c 100644 --- a/templates/mobile/base.html +++ b/templates/mobile/base.html @@ -70,6 +70,7 @@

{% endblock %} {# js #} {% block site_js %} + {{ js('zamboni/mobile') }} {% endblock %} {% block js %}{% endblock %}