Fix async failing test in sample app

Fix async failing test in sample app

--- app/packages/tinytests/tests/Tinytests.coffee
+++ app/packages/tinytests/tests/Tinytests.coffee
@@ -25,5 +25,18 @@
 Tinytest.addAsync 'async throwing test', (test, done)->
   console.log 'async throwing test'
   Meteor.defer ->
-    throw new Error('async throwing test message')
-    done()
+    try
+      throw new Error('async throwing test message')
+      done()
+    catch err
+      test.exception(err)
+
+
+Tinytest.addAsync 'async throwing test2', (test, done)->
+  console.log 'async throwing test'
+  Meteor.defer ->
+    try
+      throw new Error('async throwing test message')
+      done()
+    catch err
+      test.exception(err)


Update src/_spine/list.coffee

Update src/_spine/list.coffee

Removed some old completed TODO comments.

--- src/_spine/list.coffee
+++ src/_spine/list.coffee
@@ -1,16 +1,5 @@
 class List extends Exo.Spine.Controller

-	###
-
-		TODO:
-		* Add option to auto select any item on render.
-		* Allow the items to be Exo.Controller instances
-			* Allow each item to have it's own in/out transitions.
-			* Allow the entire list to have a single in/out transition.
-		* Allow the items to be simple rendered views.
-
-	###
-
 	#debug: true

 	constructor: (opts={}) ->
@@ -93,4 +82,4 @@
 		@trigger('select', item, $(e.currentTarget))
 		true

-Exo.Spine.List = List+Exo.Spine.List = List


Get scope objects from the state.

Get scope objects from the state.

--- app/javascripts/controllers.coffee
+++ app/javascripts/controllers.coffee
@@ -2,25 +2,6 @@

 # The controller helper methods.
 ctlrs.factory 'ControllerHelper', ['$location', ($location) ->
-  # Fetches a parent object's detail and apply the given
-  # action function.
-  getDetail: (obj, resource, action) ->
-    resource.detail(id: obj.detail).$promise
-      .then (detail) ->
-        if action
-          action(detail, obj)
-        obj
-
-  # Copies the given source object properties into the
-  # destination object.
-  copyContent: (source, dest) ->
-    # Copy the detail properties into the parent object.
-    srcProps = Object.getOwnPropertyNames(source)
-    destProps = Object.getOwnPropertyNames(dest)
-    fields = _.difference(srcProps, destProps)
-    for field in fields
-      dest[field] = source[field]
-
   # Replaces the browser URL search parameters to include at most
   # a non-default project, since all other search parameters are
   # redundant.
@@ -50,50 +31,34 @@
 ]

-ctlrs.controller 'SubjectListCtrl', ['$rootScope', '$scope', '$state', '$stateParams', 'Subject',
-  ($rootScope, $scope, $state, $stateParams, Subject) ->
-    $rootScope.project = $stateParams.project
-    project = $rootScope.project or 'QIN'
-    params = project: project
-
-    # Export the deferred subjects REST promise to the scope.
-    $scope.subjects = Subject.query(params)
-
-    # When the subjects are loaded. then export the collections
-    # to the scope.
-    $scope.subjects.$promise
-      .then (subjects) ->
-        # The unique subject collections.
-        $scope.collections = _.uniq _.map(
-          $scope.subjects,
-          (subject) -> subject.collection
-        )
-]
-
-
-#
-# TODO - get subject from state resolve.
-#
-ctlrs.controller 'SubjectDetailCtrl', ['$rootScope', '$scope', '$stateParams',
-  'ControllerHelper', 'Subject', 'Helpers',
-  ($rootScope, $scope, $stateParams, ControllerHelper, Subject, Helpers) ->
-    $rootScope.project = $stateParams.project
-    # Compose a subject from the route parameters.
-    subject =
-      project: $stateParams.project or 'QIN'
-      collection: _.str.capitalize($stateParams.collection)
-      number: parseInt($stateParams.subject)
-      detail: $stateParams.detail
+ctlrs.controller 'SubjectListCtrl', ['$rootScope', '$scope', 'project',
+  'subjects', 'collections',
+  ($rootScope, $scope, project, subjects, collections) ->
+    # Capture the current project.
+    $rootScope.project = project
+    # Place the subjects and collections in the scope.
+    $scope.subjects = subjects
+    $scope.collections = collections
+]
+
+
+ctlrs.controller 'SubjectDetailCtrl', ['$rootScope', '$scope', 'subject',
+  'detail', 'ControllerHelper', 'Helpers',
+  ($rootScope, $scope, subject, detail, ControllerHelper, Helpers) ->
+    # Capture the current project.
+    $rootScope.project = subject.project

     $scope.toggleModelingFormat = ->
       if $scope.modelingFormat == 'Chart'
-        $scope.modelingFormat = 'Table'
-      else if $scope.modelingFormat == 'Table'
+        $scope.modelingFormat = 'List'
+      else if $scope.modelingFormat == 'List'
         $scope.modelingFormat ='Chart'
       else
         throw "Modeling format is not recognized: " + $scope.modelingFormat

-    # Adds the session links above the line.
+    # Adds the session hyperlinks above the timeline.
+    #
+    # @param chart the timeline chart
     $scope.addSessionDetailLinks = (chart) ->
       # @returns the link to detail page for the given session
       sessionDetailLink = (session) ->
@@ -125,35 +90,40 @@
       # TODO - draw the x axis line.
       line = svg.append('line')

-    # Adds the fetched subject detail into the given subject.
-    addDetail = (detail, subject) ->
-      # The default modeling format is Chart for more than
-      # one session, Table otherwise.
+    # Adds the subject content as follows:
+    # * Converts the subject detail session and encounter
+    #   dates into moments, if necessary
+    # * copies the detail properties into the subject
+    # * sets the subject multiSession flag
+    # * sets the modelingFormat to 'Chart' if there is more
+    #   than one session, 'List' otherwise
+    #
+    # @param session the session object
+    # @param detail the detail object
+    addDetail = (subject, detail) ->
+      # The default modeling format is 'Chart' for more than
+      # one session, 'List' otherwise.
       defaultModelingFormat = (subject) ->
-        if subject.sessions.length > 1 then 'Chart' else 'Table'
+        if subject.sessions.length > 1 then 'Chart' else 'List'

       # Fix the session and encounter dates.
       Helpers.fixDate(sess, 'acquisition_date') for sess in detail.sessions
       Helpers.fixDate(enc, 'date') for enc in detail.encounters
+
       # Copy the detail content into the subject.
-      ControllerHelper.copyContent(detail, subject)
+      Helpers.copyContent(detail, subject)
+
       # Flag indicating whether there is more than one session.
       subject.multiSession = subject.sessions.length > 1
-      # The modeling display format, Chart or Table.
+
+      # The modeling display format, Chart or List.
       $scope.modelingFormat = defaultModelingFormat(subject)
-      # Place the subject in scope.
-      $scope.subject = subject
-      # End of the addDetail function.
-
-    # If there is a detail id, then fetch the detail.
-    # Otherwise, fetch the subject and then the detail.
-    if subject.detail
-      ControllerHelper.getDetail(subject, Subject, addDetail)
-    else
-      Subject.get(subject).$promise
-        .then (fetched) ->
-          subject.detail = fetched.detail
-          ControllerHelper.getDetail(subject, Subject, addDetail)
+
+    # Add the detail to the subject.
+    addDetail(subject, detail)
+
+    # Place the subject in the scope.
+    $scope.subject = subject

     ControllerHelper.cleanBrowserUrl(subject.project)
 ]
@@ -164,21 +134,11 @@
 ]

-ctlrs.controller 'SessionDetailCtrl', ['$rootScope', '$scope', '$stateParams',
-  'ControllerHelper', 'Subject', 'Session', 'Image',
-  ($rootScope, $scope, $stateParams, ControllerHelper, Subject, Session, Image) ->
-    $rootScope.project = $stateParams.project
-    # Compose a subject from the route parameters.
-    subject =
-      project: $stateParams.project or 'QIN'
-      collection: _.str.capitalize($stateParams.collection)
-      number: parseInt($stateParams.subject)
-
-    # Compose a session from the route parameters.
-    session =
-      subject: subject
-      number: parseInt($stateParams.session)
-      detail: $stateParams.detail
+ctlrs.controller 'SessionDetailCtrl', ['$rootScope', '$scope',
+  'session', 'detail', 'Image', 'ControllerHelper', 'Helpers',
+  ($rootScope, $scope, session, detail, Image, ControllerHelper, Helpers) ->
+    # Capture the current project.
+    $rootScope.project = session.subject.project

     # TODO - refactor the chart formatting cruft below into a
     # directive a la the modeling charts.
@@ -206,7 +166,11 @@
         value.toFixed(2)

     # Highlights the bolus arrival tick mark.
+    #
+    # @param chart the intensity chart
     $scope.highlightBolusArrival = (chart) ->
+      # TODO - move this function to a directive.
+
       # Select the SVG element.
       svg = d3.select(chart.container)
       # The x axis element.
@@ -247,12 +211,20 @@
       bolusLegend.attr('filter', 'url(#boluslegendbackground)')
       bolusLegend.text('Bolus Arrival')

+    # Opens the series image display page.
+    #
+    # @param image the Image object
     $scope.openImage = (image) ->
       # TODO - Route to the image open page.
       window.alert("Image open is not yet supported.")

-    # Fetches the detail into the given session.
-    addDetail = (detail, session) ->
+    # Adds the following session content:
+    # * the detail properties are copied into the session
+    # * sets the session graph parameters
+    #
+    # @param session the session object
+    # @param detail the detail object
+    addDetail = (session, detail) ->
       # Makes the graph data parameters for the given
       # label: values associative array. The value arrays must be
       # the same length. The graph x axis labels are the
@@ -273,10 +245,12 @@
         formatItem(key, data[key]) for key in _.keys(data)

       # Copy the fetched detail into the session.
-      ControllerHelper.copyContent(detail, session)
+      Helpers.copyContent(detail, session)
+
       # Add the registration.
       # TODO - handle more than one registration?
       session.registration = session.registrations[0]
+
       # Add the intensity graph parameters.
       session.graphData = intensityGraphData(
         Scan: session.scan.intensity.intensities
@@ -288,114 +262,24 @@
       # Encapsulate the image files.
       for obj in [session.scan, session.registration]
         obj.images = Image.imagesFor(obj)
-      # Place the session in the scope.
-      $scope.session = session
-      # End of the addDetail function.
-
-    # If there is a detail id, then fetch the detail.
-    # Otherwise, fetch the session and then the detail.
-    if session.detail
-      ControllerHelper.getDetail(session, Session, addDetail)
-    else
-      # Fetch the subject...
-      Subject.get(subject).$promise
-        .then (fetched) ->
-          # ...then fetch the subject detail...
-          Subject.detail(id: fetched.detail).$promise
-        .then (detail) ->
-          # ...find the session in the session list...
-          for sess in detail.sessions
-            if sess.number == session.number
-              # ...and fetch the session detail.
-              session.detail = sess.detail_id
-          # If the session was not found, then complain.
-          if not session.detail
-            throw "Subject #{ subject } does not have a session #{ session.number }"
-          # Fill in the session detail.
-          ControllerHelper.getDetail(session, Session, addDetail)
-
-    ControllerHelper.cleanBrowserUrl(subject.project)
-]
-
-
-ctlrs.controller 'SeriesDetailCtrl', ['$rootScope', '$scope', '$stateParams',
-  'ControllerHelper', 'Subject', 'Session', 'Image',
-  ($rootScope, $scope, $stateParams, ControllerHelper, Subject, Session, Image) ->
-    $rootScope.project = $stateParams.project
-    # Compose a subject from the route parameters.
-    subject =
-      project: $stateParams.project or 'QIN'
-      collection: _.str.capitalize($stateParams.collection)
-      number: parseInt($stateParams.subject)
-
-    # Compose a session from the route parameters.
-    session =
-      subject: subject
-      number: parseInt($stateParams.session)
-      detail: $stateParams.detail
-
-    # Fetches the detail into the given session.
-    addDetail = (detail, session) ->
-      # Copy the fetched detail into the session.
-      ControllerHelper.copyContent(detail, session)
-      # Add the registration.
-      # TODO - handle more than one registration?
-      session.registration = session.registrations[0]
-
-      # The series numbers.
-      session.seriesNumbers = [1..session.scan.intensity.intensities.length]
-      # Encapsulate the image files.
-      for obj in [session.scan, session.registration]
-        obj.images = Image.imagesFor(obj)
-      # Place the session in the scope.
-      $scope.session = session
-      # End of the addDetail function.
-
-    # If there is a detail id, then fetch the detail.
-    # Otherwise, fetch the session and then the detail.
-    if session.detail
-      deferred = ControllerHelper.getDetail(session, Session, addDetail)
-    else
-      # Fetch the subject...
-      deferred = Subject.get(subject).$promise
-        .then (fetched) ->
-          # ...then fetch the subject detail...
-          Subject.detail(id: fetched.detail).$promise
-        .then (detail) ->
-          # ...find the session in the session list...
-          for sess in detail.sessions
-            if sess.number == session.number
-              # ...and fetch the session detail.
-              session.detail = sess.detail_id
-          # If the session was not found, then complain.
-          if not session.detail
-            throw "Subject #{ subject } does not have a session #{ session.number }"
-          # Fill in the session detail.
-          ControllerHelper.getDetail(session, Session, addDetail)
-
-    # Compose an Image from the route parameters.
-    deferred
-      .then (fetched) ->
-        # The scan or registration image container object.
-        container = fetched[$stateParams.image_container]
-        if not container
-          throw "Unrecognized #{ subject.collection } Subject #{ subject.number } Session #{ session.number } image container: #{ container }"
-        # The series image.
-        imgNdx = parseInt($stateParams.series) - 1
-        image = container.images[imgNdx]
-        if not image
-          throw "Unrecognized #{ subject.collection } Subject #{ subject.number } Session #{ session.number } series: #{ $stateParams.series }"
-        # Add the series container to the image object.
-        image.container =
-          type: $stateParams.image_container
-          series:
-            session: session
-            number: $stateParams.series
-        # Place the image object in scope.
-        $scope.image = image
-        # Load the image binary data, if necessary.
-        if not image.data
-          image.load()
-
-    ControllerHelper.cleanBrowserUrl(subject.project)
-]
+
+    # Add the detail to the session.
+    addDetail(session, detail)
+
+    # Place the session in the scope.
+    $scope.session = session
+
+    ControllerHelper.cleanBrowserUrl(session.subject.project)
+]
+
+
+ctlrs.controller 'SeriesDetailCtrl', ['$rootScope', '$scope', 'series',
+  ($rootScope, $scope, series) ->
+    # Capture the current project.
+    $rootScope.project = series.session.subject.project
+
+    # Place the series in the scope.
+    $scope.series = series
+
+    ControllerHelper.cleanBrowserUrl(series.session.subject.project)
+]


Only take into account succesfull deployments

--- src/application.coffee
+++ src/application.coffee
@@ -23,36 +23,59 @@
     env in @environments

   fetchStatus: (env, cb) ->
-    repoPath = "/repos/#{@repository}"
     client = @api()
     res = new Response(@name, env, @data)
-    client.get repoPath, {}, (err, status, repository) ->
+    client.get "/repos/#{@repository}", {}, (err, status, repository) =>
       return cb(err) if err?
       res.repository = repository
-      params =
-        environment: env
-        per_page: 1
-        page: 1
-      client.get repoPath + "/deployments", params, (err, status, deployments) ->
+      @lastSuccessfullDeployment env, (err, deployment) =>
         return cb(err) if err?
-        deployment = res.deployment = deployments[0]
-        if !deployment?
-          cb(null, res)
-        else
-          client.get repoPath + "/compare/#{deployment.sha}...#{repository.default_branch}", {}, (err, status, compare) ->
-            return cb(err) if err?
-            res.compare = compare
-            if res.isAhead() || res.isDiverged()
-              # Compare api does not give commits that are ahead of compare, need to fetch the other way around
-              client.get repoPath + "/compare/#{repository.default_branch}...#{deployment.sha}", {}, (err, status, reverseCompare) ->
-                res.reverseCompare = reverseCompare
-                cb(null, res)
-            else
+        return cb(null, res) unless deployment?
+        res.deployment = deployment
+        client.get "/repos/#{@repository}/compare/#{deployment.sha}...#{repository.default_branch}", {}, (err, statusCode, compare) =>
+          return cb(err) if err?
+          res.compare = compare
+          if res.isAhead() || res.isDiverged()
+            # Compare api does not give commits that are ahead of compare, need to fetch the other way around
+            client.get "/repos/#{@repository}/compare/#{repository.default_branch}...#{deployment.sha}", {}, (err, statusCode, reverseCompare) ->
+              return cb(err) if err?
+              res.reverseCompare = reverseCompare
               cb(null, res)
+          else
+            cb(null, res)

   api: ->
     api = Octonode.client(@token)
     api.requestDefaults.headers['Accept'] = 'application/vnd.github.cannonball-preview+json'
     api

+  # private
+  lastSuccessfullDeployment: (env, cb, page = 1) ->
+    client = @api()
+    params =
+      environment: env
+      per_page: 10
+      page: page
+    client.get "/repos/#{@repository}/deployments", params, (err, statusCode, deployments) =>
+      return cb(err) if err?
+      return cb(null, null) if deployments.length == 0
+      @fetchDeploymentStatuses deployments, (err, deployment) ->
+        return cb(err) if err?
+        return cb(null, deployment) if deployment?
+        @lastSuccessfullDeployment(env, cb, page + 1)
+
+  fetchDeploymentStatuses: (deployments, cb) ->
+    deployment = deployments.shift()
+    return cb(null, null) unless deployment?
+    client = @api()
+    client.get "/repos/#{@repository}/deployments/#{deployment.id}/statuses", {}, (err, statusCode, statuses) =>
+      return cb(err) if err?
+      success = false
+      for status in statuses
+        success = true if status.state == 'success'
+      if success
+        cb(null, deployment)
+      else
+        @fetchDeploymentStatuses(deployments, cb)
+
 module.exports = Application


Show currency with two decimal places.

Show currency with two decimal places.

--- app/assets/javascripts/lib/models/metric.coffee
+++ app/assets/javascripts/lib/models/metric.coffee
@@ -112,16 +112,18 @@
     scale     = @power_of_thousand(x)
     value     = abs_value / Math.pow(1000, scale)
     suffix    = ''
+    rounded   = @round_number(value, 2).toString()
+
     if unit_suffix and abs_value >= 1000
       suffix = I18n.t('units.currency.' + @power_of_thousand_to_string(scale))
-    rounded = @round_number(value, 1).toString()
+
     # If the number is < 1000, and has decimal places, make sure that the
     # number isn't truncated to something like 5.4, but instead returns 5.40.
-    if abs_value < 1000 && rounded.match(/\./)
-      rounded = rounded.split('.')
-      if (rounded[1] && rounded[1].length == 1)
-        rounded[1] += '0'
-      rounded = rounded.join('.')
+    if abs_value >= 1000 && rounded.match(/\./)
+      # Trim unnecessary decimal places.
+      rounded = rounded.replace(/0+$/, '')
+      rounded = rounded[0..-2] if rounded.slice(-1) is '.'
+
     "#{prefix}€#{rounded} #{suffix}"

   # support methods


OX-698 change key

OX-698 change key

--- app/assets/javascripts/oxalis/controller.coffee
+++ app/assets/javascripts/oxalis/controller.coffee
@@ -176,7 +176,7 @@
         else
           @setMode(constants.MODE_PLANE_TRACING)

-      "b" : =>
+      "shift + alt + b" : =>
         @model.cellTracing.benchmark( 5, 1000 )
         Toast.success("Benchmark scenario finished")


Update mini-course-content.coffee

Update mini-course-content.coffee

--- app/lib/mini-course-content.coffee
+++ app/lib/mini-course-content.coffee
@@ -30,7 +30,7 @@
           <img style=\"width: 75px; padding: 1px\" src=\"./images/mini-course/transit-depth.png\"/>
         </div>
       """
-      figure: "./images/mini-course/lightcurve-transit-depth.png.png"
+      figure: "./images/mini-course/lightcurve-transit-depth.png"
       figure_credits: ""
   }
   {


Make sure `routeChanging = false` when route change blocked

--- app/assets/javascripts/sprangular/module.coffee
+++ app/assets/javascripts/sprangular/module.coffee
@@ -76,27 +76,29 @@
     requirements = next.requires || {}
     Status.routeChanging = true
     Status.meta = {}
+    redirectPath = null

     if requirements.user && !Account.isLogged
       Status.requestedPath = next.$$route.originalPath
       Flash.error('app.sign_in_or_register')
-      $location.path('/sign-in')
-      event.preventDefault()
+      redirectPath = '/sign-in'

     else if requirements.guest && !Account.isLogged && !Account.isGuest
       Status.requestedPath = next.$$route.originalPath
       Flash.error('app.sign_in_or_register')
-      $location.path('/sign-in')
-      event.preventDefault()
+      redirectPath = '/sign-in'

     else if requirements.anonymous && Account.isLogged
       Flash.error('app.must_be_logged_out')
-      $location.path('/')
-      event.preventDefault()
+      redirectPath = '/'

     else if requirements.cart && Cart.current.items.length == 0
       Flash.error('app.no_items_in_cart')
-      $location.path('/')
+      redirectPath = '/'
+
+    if redirectPath
+      $location.path(redirectPath)
+      Status.routeChanging = false
       event.preventDefault()

   $rootScope.$on '$routeChangeSuccess', ->


Update mini-course-content.coffee

Update mini-course-content.coffee

Reduced padding of equations from 20px to 10px

--- app/lib/mini-course-content.coffee
+++ app/lib/mini-course-content.coffee
@@ -27,7 +27,7 @@
           A lightcurve can tell us a lot about a planetary system. Firstly, it gives us is an estimate of the size of the planet. The larger the planet is in relation to the star, the more light is blocked by the planet and the deeper the transit. So the transit depth, d, is proportional to the radius of the planet, Rp:

-
+

           A planet the size of Jupiter will cause a 1% dip in the brightness of a star the size of our Sun. An Earth-sized planet would only cause a 0.1% dip, making it much harder to detect! However, if the star is much smaller than our Sun, which the majority of the stars in our galaxy are, the dip in light will be larger.
@@ -45,7 +45,7 @@
           If we observe multiple transits at equally spaced intervals then we know they are caused by a single planet and we have a measurement of its orbital period, as it is just the time interval between each transit. Astronomer Johannes Kepler showed that orbital period,P, is proportional to orbital separation, a:

-
+

This means that by observing multiple transits of the same planet we can determine how far away it is from its host star.

         """


Exports still come back as an array

Exports still come back as an array

--- app/pages/lab/project-details.cjsx
+++ app/pages/lab/project-details.cjsx
@@ -87,7 +87,7 @@
       .catch ->
         ''

-    @classificationsExportsGet ?= @props.project.get 'classifications_export'
+    @classificationsExportGet ?= @props.project.get 'classifications_export'
       .catch ->
         []

@@ -155,15 +155,19 @@

           Data export

-          <button type="button" disabled={@state.exportRequested} onClick={@requestDataExport}>Request data export</button>{' '}
+          <button type="button" disabled={@state.exportRequested} onClick={@requestDataExport}>Request new data export</button>{' '}

-            CSV format available.{' '}
-            <PromiseRenderer promise={@classificationsExportsGet} then={(mostRecent) =>
+            CSV format.{' '}
+            <PromiseRenderer promise={@classificationsExportGet}>{([mostRecent]) =>
               if mostRecent?
-                Most recent request was made {moment(mostRecent.created_at).fromNow()}.
+
+                  Most recent data available requested{' '}
+                  {moment(mostRecent.created_at).fromNow()}.
+
               else
                 Never requested.
-            } />

+            }</PromiseRenderer>
+

           {if @state.exportError?
@@ -204,7 +208,7 @@
     @setState exportError: null
     apiClient.post @props.project._getURL('classifications_export'), media: content_type: 'text/csv'
       .then =>
-        @classificationsExportsGet = null
+        @classificationsExportGet = null
         @setState exportRequested: true
       .catch (error) =>
         @setState exportError: error


Fill in good image guidelines

Fill in good image guidelines

--- app/pages/lab/project-details.cjsx
+++ app/pages/lab/project-details.cjsx
@@ -96,7 +96,7 @@
           {if @state.avatarError

{@state.avatarError.toString()}
}

-          Pick an avatar for your project. This will represent your project on the Zooniverse home page. It can also be used as your project’s brand. It’s best if it’s recognizable even as a small icon. To add an image, either drag and drop or right click to open your file viewer. For best results, use a square image of not more than X pixels on a side.
+          Pick an avatar for your project. This will represent your project on the Zooniverse home page. It can also be used as your project’s brand. It’s best if it’s recognizable even as a small icon. To add an image, either drag and drop or right click to open your file viewer. For best results, use a square image no larger than 50 KB.

@@ -108,7 +108,7 @@
           {if @state.backgroundError

{@state.backgroundError.toString()}
}

-          This image will be the background for all of your project pages, including your project’s front page, which newcomers will see first. It should be relatively high resolution and you should be able to read text written across it. To add an image, either drag and drop or right click to open your file viewer. For best results, use images of at least [X by Y] pixels.
+          This image will be the background for all of your project pages, including your project’s front page, which newcomers will see first. It should be relatively high resolution and you should be able to read text written across it. To add an image, either drag and drop or right click to open your file viewer. For best results, use photographic images of at least one megapixel, no larger than 256 KB.


Factor out `parseKeyPattern` from `keyEventMatchesPattern` method

--- src/atom/window.coffee
+++ src/atom/window.coffee
@@ -38,6 +38,10 @@
   bindMenuItems: ->
     @bindMenuItem "File > Save", => @editor.save()

+  bindMenuItemAndKey: (path, pattern, action) ->
+    @bindMenuItem path, action
+    @bindKey pattern, action
+
   bindMenuItem: (path, action) ->
     @menuItemActions[path] = action

@@ -45,18 +49,22 @@
     @keyBindings[pattern] = action

   keyEventMatchesPattern: (event, pattern) ->
+    keys = @parseKeyPattern pattern
+
+    keys.ctrlKey == event.ctrlKey and
+      keys.altKey == event.altKey and
+      keys.shiftKey == event.shiftKey and
+      keys.metaKey == event.metaKey and
+      event.which == keys.key.toUpperCase().charCodeAt 0
+
+  parseKeyPattern: (pattern) ->
     [modifiers..., key] = pattern.split '+'
-    patternModifiers =
-      ctrlKey: 'ctrl' in modifiers
-      altKey: 'alt' in modifiers
-      shiftKey: 'shift' in modifiers
-      metaKey: 'meta' in modifiers

-    patternModifiers.ctrlKey == event.ctrlKey and
-      patternModifiers.altKey == event.altKey and
-      patternModifiers.shiftKey == event.shiftKey and
-      patternModifiers.metaKey == event.metaKey and
-      event.which == key.toUpperCase().charCodeAt 0
+    ctrlKey: 'ctrl' in modifiers
+    altKey: 'alt' in modifiers
+    shiftKey: 'shift' in modifiers
+    metaKey: 'meta' in modifiers
+    key: key

   registerEventHandlers: ->
     $(document).bind 'keydown', (event) =>


First Steps Towards Real Game

First Steps Towards Real Game

I already hate the EDSL I designed.

--- src/coffee/game.coffee
+++ src/coffee/game.coffee
@@ -1,27 +1,43 @@
+locations =
+  home:      "in the residential district"
+  market:    "in the market"
+  slums:     "in the slums"
+  palace:    "in the royal palace"
+  outskirts: "in the outskirts"
+
 angular.module 'gameDefinition', ['qbn.edsl']
   .run (qbnEdsl) ->
     {quality, qualityType} = qbnEdsl

-    quality 'punchiness', qualityType.stat,
-      'Punchiness'
-      'One\'s capability for punching.'
-      value: 2
+    quality 'hours', qualityType.item,
+      'Hours Remaining'
+      'After this, there will be no stopping the Duke.'
+      value: 72

-    quality 'deathWish', qualityType.unique,
-      'a Death Wish'
-      'Has existence become a burden?'
+    quality 'stealth', qualityType.stat,
+      'Stealth'
+      'Hiding, Sneaking, Smuggling'
+      value: 1
+
+    quality 'sorcery', qualityType.stat,
+      'Sorcery'
+      'Casting, Blasting, Conjuring'
+      value: 1
+
+    quality 'strength', qualityType.stat,
+      'Strength'
+      'Action, Power, Combat'
+      value: 1
+
+    quality 'discovered', qualityType.unique,
+      'Been Discovered'
+      'That\'s not good.'
       value: false

-    quality 'unsingableSong', qualityType.item,
-      'Unsingable Song'
-      'Because it is terrible or because it is forbidden?'
-      value: 3
-
-    quality 'luck', qualityType.stat,
-      'Luck'
-      'This text should never display.'
-      value: -2
-      visible: false
+    quality 'location', qualityType.string,
+      '!!ERROR!!'
+      undefined
+      value: null

     return

@@ -34,6 +50,23 @@

     onwards choice 'onwards',
       'The story continues…'
+
+    ## Intro
+
+    front choice 'lets-begin',
+      'Begin the Story'
+
+    storylet 'lets-begin',
+      'August 14th'
+      '''
+
+      '''
+      choices: [
+        choice 'lets-begin2'
+          'OK, and then what?'
+      ]
+
+    return

     front choice 'body-building',
       'Bodybuilding Class'


Altera o elemento em que é inserido o conteúdo do modal

--- src/coffee/portal-template-as-modal.coffee
+++ src/coffee/portal-template-as-modal.coffee
@@ -41,7 +41,7 @@
 	$(document).on 'vtex.modal.hide', hideModal

 	$.get(templateURL).done (content) ->
-		$container.find('.boxPopUp2-content').html $(content)
+		$container.find('.skuWrap_').html $(content)

 $(document).ready(doBind)
 $(document).on('ajaxComplete', doBind)


minor

minor

--- client/app/Applications/Environments.kdapplication/views/DomainRegisterModalFormView.coffee
+++ client/app/Applications/Environments.kdapplication/views/DomainRegisterModalFormView.coffee
@@ -84,8 +84,8 @@
       @modalTabs.showPaneByIndex "2"

       @emit "DomainSaved"
-
-
+
+
   loadSearchDomainPane:->
     @modalTabs.showPaneByIndex 1
     @setData {}
@@ -364,7 +364,7 @@
           # callback                    : => @loadSearchDomainPane()
           buttons                     :
             Next                      :
-              title                   : "Create Domain"
+              title                   : "Add Domain"
               style                   : "cupid-green"
               type                    : "submit"
             cancel                    :


stubby looks into proper directory for tls key/cert now

--- src/console/cli.coffee
+++ src/console/cli.coffee
@@ -71,8 +71,8 @@
       tls: 7443
       location: 'localhost'
       data: null
-      key: fs.readFileSync "#{process.cwd()}/tls/key.pem", 'utf8'
-      cert: fs.readFileSync "#{process.cwd()}/tls/cert.pem", 'utf8'
+      key: fs.readFileSync "#{__dirname}/../../tls/key.pem", 'utf8'
+      cert: fs.readFileSync "#{__dirname}/../../tls/cert.pem", 'utf8'
       pfx: null

    help: ->


Don't pad an empty help

Don't pad an empty help

--- help/troubleshoot/all.coffee
+++ help/troubleshoot/all.coffee
@@ -25,6 +25,6 @@
       return iter()
     ts (help) ->
       if help?
-        help  += '\n'         if help[-1..] isnt '\n'
-        out   += "\n\n#{help}"
+        help  += '\n'           if help[-1..] isnt '\n'
+        out   += "\n\n#{help}"  if out isnt ''
       iter()


Only the precision is still needed.

Only the precision is still needed.

--- app/javascripts/chart.coffee
+++ app/javascripts/chart.coffee
@@ -8,201 +8,38 @@
   chart = ng.module 'qiprofile.chart', []

   chart.factory 'Chart', ->
-    # Builds the nvd3 chart format for the given input resource
-    # objects and configuration.
+    # Returns the smallest number of significant decimals that
+    # captures at least one non-zero digit for all non-zero values,
+    # e.g.:
+    # > Chart.minPrecision(1.373, 0.00542, -4, 0.0, 0.04)
+    # > 3
     #
-    # The input data is the resource objects which contain the
-    # values to graph. The chart values are obtained by calling
-    # the input format x-axis accessor for each chart data series.
-    #
-    # The input dataSpec contains the following x and y attributes:
-    # * x is the x-axis {label, accessor}
-    # * y is the y-axis {[{label, color, accessor}, ...], precision},
-    # where:
-    # * label is the data series chart label
-    # * color is the data series chart color
-    # * accessor is the data value access function
-    # * precision is the decimal precision to use for all data series
-    #   values
-    #
-    # The result configuration object contains the following properties:
-    # * data - the nvd3 chart data
-    # * yLabel - the nvd3 yAxisLabel value
-    # * yFormat - the nvd3 yAxisTickFormat function
-    # * yMaxMin - the nvd3 forcey range
-    #
-    # @param data the resource objects to graph
-    # @param dataSpec the data access specification
-    # @returns the chart configuration
-    configure: (data, dataSpec) ->
-      # Note: nvd3 is undergoing a major version 2 rewrite.
-      # Hopefully, version 2 will address the anomalies noted in
-      # the modeling-discrete-chart template.
-      # TODO - Adapt this function for version 2 when it is
-      # completed.
+    # @param values the values to chart
+    # @returns the smallest number of significant decimals
+    #   that captures at least one non-zero digit for all values
+    minPrecision: (values) ->
+      # Determine the minimum value precision as follows:
+      # * If the value is undefined, null, zero or greater then
+      #   one, then zero
+      # * Otherwise, the number of significant decimal digits
+      #
+      # @param value the numeric value
+      # @returns the number of decimals to display for the
+      #   given value
+      minValuePrecision = (value) ->
+        if not value or Math.abs(value) > 1
+          0
+        else
+          1 + minValuePrecision(value * 10)

-      # @returns the y values for the given resource
-      resourceYValues = (resource, dataSpec) ->
-        ds.accessor(resource) for ds in dataSpec.y.data
-
-      # @param data the graphed objects
-      # @param dataSpec the y axis dataSpec
-      # @returns the y axis {max, min} object
-      getYValueRange = (data, dataSpec) ->
-        # @param data the resource objects to graph
-        # @param dataSpec the data access specification described
-        #   in *configure*
-        # @returns the result of first calling the accessor in the
-        #   data specification to the given resoure, and then applying
-        #   the given function
-        applyToAllResourcesYValues = (data, dataSpec, fn) ->
-          # @returns the result of applying the given function to the
-          #   resource values of the given object
-          applyToAResourceYValues = (resource, dataSpec, fn) ->
-            values = resourceYValues(resource, dataSpec)
-            fn(values)
-
-          applyToAResourceYValues(rsc, dataSpec, fn) for rsc in data
-
-        # Return the {max, min} range object.
-        max: _.max(applyToAllResourcesYValues(data, dataSpec, _.max))
-        min: _.min(applyToAllResourcesYValues(data, dataSpec, _.min))
-
-      # @param data the input resource objects
-      # @param dataSpec the dataSpec described in *configure*
-      # @returns the [{key, values, color}] nvd3 configuration
-      configureAllDataSeries = (data, dataSpec) ->
-        # @param data the input resource objects
-        # @param x the x-xaxis {accessor: function} object
-        # @param y the y-axis  {accessor: function} object
-        # @returns the {key, values, color} object
-        configureADataSeries = (data, x, y) ->
-          # @returns the graph [x, y] coordinates
-          coordinates = (data, xAccessor, yAccessor) ->
-            [xAccessor(rsc), yAccessor(rsc)] for rsc in data
-
-          key: y.label
-          values: coordinates(data, x.accessor, y.accessor)
-          color: y.color
-
-        (configureADataSeries(data, dataSpec.x, y) for y in dataSpec.y.data)
-
-      # Adds padding to the give value range as follows:
-      # * the the chart max is the next higher significant
-      #   tick mark
-      # * if the value min is zero, then that is the chart
-      #   min. Otherwise the chart min is the next lower
-      #   significant tick mark
-      #
-      # @param range the chart values {max, min} range object
-      # @param returns the chart axis {max, min} range object
-      padRange = (range, precision) ->
-        # The factor is 10**precision.
-        factor = Math.pow(10, precision)
-
-        ceiling = Math.ceil(range.max * factor)
-        # Pad the graph max with the next higher significant tick mark.
-        upperInt = if ceiling == 0 then ceiling else ceiling + 1
-        upper = upperInt / factor
-
-        # Round the minimum down to the nearest precision decimal.
-        floor = Math.floor(range.min * factor)
-        # If the floor is not at the origin, then pad the graph min with
-        # the next lower significant tick mark.
-        lowerInt = if floor == 0 then floor else floor - 1
-        lower = lowerInt / factor
-
-        # Return the chart{max, min} range object.
-        max: upper
-        min: lower
-
-      # @param data the resource objects
-      # @param dataSpec the data access specification
-      # @returns the number of decimals to display on the y-axis
-      defaultYPrecision = (data, dataSpec) ->
-        # @param resource the resource object
-        # @param dataSpec the data access specification
-        defaultResourcePrecision = (resource, dataSpec) ->
-          # Determine the default value precision as follows:
-          # * If the value is zero or greater then one, then zero
-          # * Otherwise, the number of significant decimal digits
-          #
-          # @param value the numeric value
-          # @returns the number of decimals to display for the
-          #   given value
-          defaultValuePrecision = (value) ->
-            if not value or value > 1
-              0
-            else
-              1 + defaultValuePrecision(value * 10)
-
-          # The resource data series y values.
-          values = resourceYValues(resource, dataSpec)
-          # The precision for each value.
-          precisions =
-            defaultValuePrecision(value) for value in values
-          # Return the largest precision.
-          _.max(precisions)
-
-        # The precision for each resource.
-        precisions =
-          defaultResourcePrecision(rsc, dataSpec) for rsc in data
-        # Return the largest precision.
-        _.max(precisions)
-
-      # This function is a work-around for the following missing
-      # nvd3 feature:
-      # * allow different dataSpecs for y ticks and y tooltip
-      #   values
-      # This function allows for displaying the y-axis values
-      # with two more precision digits than the y-axis ticks.
-      #
-      # @returns a function which formats the y axis
-      #   ticks differently than the y tooltip values
-      decimalFormatter = (precision) ->
-        # The multiplicative factor.
-        factor = Math.pow(10, precision)
-        # The y-axis tick format.
-        formatTick = d3.format('.' + precision + 'f')
-        # The y-axis value format allows for more precision than
-        # the tick mark.
-        formatValue = d3.format('.' + (precision + 2) + 'f')
-
-        # @param value the value to format
-        # @returns the d3 formatter function
-        (value) ->
-          # Shift the value to the left.
-          shifted = value * factor
-          # If there are no significant decimal digits in
-          # the shifted value, then format the value as a
-          # tick mark. Otherwise, format the value as a data
-          # series value.
-          if shifted - Math.floor(shifted) < .001
-            formatTick(value)
-          else
-            formatValue(value)
-
-      # The y-axis value range.
-      valueYRange = getYValueRange(data, dataSpec)
-
-      # Get the default precision, if necessary.
-      if dataSpec.y.precision
-        precision = dataSpec.y.precision
-      else
-        precision = defaultYPrecision(data, dataSpec)
-
-      # The chart y-axis range.
-      chartYRange = padRange(valueYRange, precision)
-
-      # Return the graph configuration.
-      data: configureAllDataSeries(data, dataSpec)
-      yFormat: decimalFormatter(precision)
-      yLabel: dataSpec.y.label
-      yMaxMin: _.values(chartYRange)
+      # The precision for each value.
+      precisions = (minValuePrecision(value) for value in values)
+      # Return the largest of the minimum value precision.
+      _.max(precisions)

     # @param date the moment date integer
     # @returns the formatted date
-    dateFormat: (date) ->
+    formatDate: (date) ->
       moment(date).format('MM/DD/YYYY')

     # Replaces the given text element with a ui-router ui-sref