Fix for #482

Fix for #482

 - Thanks to @daorren

--- server.coffee
+++ server.coffee
@@ -7,6 +7,7 @@
 @summary Require NPM packages
 ###
 `import fs       from 'fs-extra'`
+`import nodeQs   from 'querystring'`
 `import events   from 'events'`
 `import request  from 'request'`
 `import Throttle from 'throttle'`
@@ -400,10 +401,10 @@
           uris = uri.split '/'
           if uris.length is 3
             params =
-              query: if request._parsedUrl.query then JSON.parse('{"' + decodeURI(request._parsedUrl.query).replace(/"/g, '\\"').replace(/&/g, '","').replace(/=/g,'":"') + '"}') else {}
+              query: if request._parsedUrl.query then nodeQs.parse(request._parsedUrl.query) else {}
               _id: uris[0]
               version: uris[1]
-              name: uris[2]
+              name: uris[2].split('?')[0]
             http = {request, response, params}
             self.download http, uris[1], self.collection.findOne(uris[0]) if self._checkAccess http
           else
@@ -427,7 +428,7 @@
               _file   = _file.split('?')[0]

             params =
-              query: if request._parsedUrl.query then JSON.parse('{"' + decodeURI(request._parsedUrl.query).replace(/"/g, '\\"').replace(/&/g, '","').replace(/=/g,'":"') + '"}') else {}
+              query: if request._parsedUrl.query then nodeQs.parse(request._parsedUrl.query) else {}
               file: _file
               _id: _file.split('.')[0]
               version: version


server side decks retrieval

server side decks retrieval

--- server.coffee
+++ server.coffee
@@ -99,6 +99,31 @@
     else
       res.send {message: 'OK'}, 200

+app.get '/messages/:channel', (req, res) ->
+  db.collection('messages').find({channel: req.params.channel}).sort(date: 1).limit(50).toArray (err, data) ->
+    throw err if err
+    res.json(200, data)
+
+app.get '/data/decks', (req, res) ->
+  db.collection('decks').find({username: req.user.username}).toArray (err, data) ->
+    throw err if err
+    res.json(200, data)
+
+app.post '/data/decks', (req, res) ->
+  deck = req.body
+  if req.user
+    deck.username = req.user.username
+    if deck._id
+      db.collection('decks').update {_id: deck._id}, deck, (err) ->
+        console.log(err) if err
+        res.send {message: 'OK'}, 200
+    else
+      db.collection('decks').insert deck, (err) ->
+        console.log(err) if err
+        res.send {message: 'OK'}, 200
+  else
+    res.send {message: 'Unauthorized'}, 401
+
 app.get '/data/:collection', (req, res) ->
   db.collection(req.params.collection).find().sort(_id: 1).toArray (err, data) ->
     throw err if err
@@ -112,26 +137,6 @@
     console.error(err) if err
     delete d._id for d in data
     res.json(200, data)
-
-app.get '/messages/:channel', (req, res) ->
-  db.collection('messages').find({channel: req.params.channel}).sort(date: 1).limit(50).toArray (err, data) ->
-    throw err if err
-    res.json(200, data)
-
-app.post '/data/decks/', (req, res) ->
-  deck = req.body
-  if req.user
-    deck.user = req.user
-    if deck._id
-      db.collection('decks').update {_id: deck._id}, deck, (err) ->
-        console.log(err) if err
-        res.send {message: 'OK'}, 200
-    else
-      db.collection('decks').insert deck, (err) ->
-        console.log(err) if err
-        res.send {message: 'OK'}, 200
-  else
-    res.send {message: 'Unauthorized'}, 401

 app.configure 'development', ->
   console.log "Dev environment"


make config global from the start

make config global from the start

--- server.coffee
+++ server.coffee
@@ -8,9 +8,9 @@

 ## Load config
 try
-    config = JSON.parse fs.readFileSync './config.json', 'utf8'
+    global.config = JSON.parse fs.readFileSync './config.json', 'utf8'
 catch e
-    config = {}
+    global.config = {}

 florinda = require './system'
 brain    = require './brains'
@@ -29,7 +29,6 @@

 ## Make main objects global
 # yeah, I could keep passing stuff around...
-global.config   = config
 global.program  = program
 global.florinda = florinda
 global.brain    = brain


Changed port for nodester

Changed port for nodester

--- server.coffee
+++ server.coffee
@@ -5,7 +5,7 @@
 server = websocket.createServer()

 HOST = null
-PORT = 5000
+PORT = 10464

 STAGE_WIDTH = 49
 STAGE_HEIGHT = 49


brain must be loaded after commander otherwise it spits loaded patterns before --help

--- server.coffee
+++ server.coffee
@@ -2,10 +2,20 @@
 qs       = require 'querystring'
 url      = require 'url'
 http     = require 'http'
-brain    = require './brains'
 request  = require 'request'
 { exec } = require 'child_process'
 program  = require 'commander'
+
+program
+    .version('0.0.1')
+    .option('-n, --nochat', "Don't send messages")
+    .option('-s, --silent', "Don't send greeting on start")
+    .option('-u, --user [name]', 'Username for CLI/testing', 'John')
+    .parse process.argv
+
+#####
+
+brain = require './brains'

 try
     config = JSON.parse fs.readFileSync './config.json', 'utf8'
@@ -13,13 +23,6 @@
     config = {}

 #####
-
-program
-  .version('0.0.1')
-  .option('-n, --nochat', "Don't send messages")
-  .option('-s, --silent', 'Start quietly')
-  .option('-u, --user [name]', 'Username for CLI/testing', 'John')
-  .parse process.argv

 sendChatMessage = (message) ->


Don't call toJSON if the method does not exist

--- src/template.coffee
+++ src/template.coffee
@@ -20,7 +20,7 @@
     # otherwise handlebars can't handle them
     for key, val of options
       if val instanceof Array
-        options[key] = (a.toJSON() for a in val)
+        options[key] = (if a instanceof Salad.Model then a.toJSON() else a for a in val)
       if val?.toJSON
         options[key] = val.toJSON()

@@ -36,4 +36,4 @@
   @_render: (file, options) ->
     Salad.Template.Handlebars.render file, options

-require "./templates/handlebars"+require "./templates/handlebars"


remove trailing whitespace

remove trailing whitespace

--- pace.coffee
+++ pace.coffee
@@ -144,7 +144,7 @@

   watch: (request) ->
     tracker = new RequestTracker(request)
-
+
     @elements.push tracker

 class RequestTracker
@@ -183,7 +183,7 @@

     for set in selectors
       @elements.push new ElementTracker set
-
+
 class ElementTracker
   constructor: (selectors) ->
     @progress = 0
@@ -237,7 +237,7 @@

       @progress = 100 * (3 / (avg + 3))
     , 50
-
+
 class Scaler
   constructor: (@source) ->
     @last = @sinceLastUpdate = 0


Browser compat fixes

Browser compat fixes

--- pace.coffee
+++ pace.coffee
@@ -28,6 +28,18 @@

 now = ->
   performance?.now?() ? +new Date
+
+requestAnimationFrame = window.requestAnimationFrame or window.mozRequestAnimationFrame or
+                        window.webkitRequestAnimationFrame or window.msRequestAnimationFrame
+
+cancelAnimationFrame = window.cancelAnimationFrame or window.mozCancelAnimationFrame
+
+if not requestAnimationFrame?
+  requestAnimationFrame = (fn) ->
+    setTimeout fn, 50
+
+  cancelAnimationFrame = (id) ->
+    clearTimeout id

 runAnimation = (fn) ->
   last = now()
@@ -46,13 +58,6 @@
   else
     obj[key]

-avgKey = (arr, key, args...) ->
-  sum = 0
-  for item in arr
-    sum += result(item, key, args...)
-
-  sum / arr.length
-
 class Bar
   constructor: ->
     @progress = 0
@@ -151,31 +156,43 @@
   constructor: (request) ->
     @progress = 0

-    size = null
-    request.onprogress = =>
-      try
-        headers = request.getAllResponseHeaders()
-
-        for name, val of headers
-          if name.toLowerCase() is 'content-length'
-            size = +val
-            break
-
-      catch e
-
-      if size?
-        # This is not perfect, as size is in bytes, length is in chars
+    if request.onprogress isnt undefined
+      # It will be null, not undefined, on browsers which don't support it
+
+      size = null
+      _onprogress = request.onprogress
+      request.onprogress = =>
         try
-          @progress = request.responseText.length / size
+          headers = request.getAllResponseHeaders()
+
+          for name, val of headers
+            if name.toLowerCase() is 'content-length'
+              size = +val
+              break
+
         catch e
-      else
-        # If it's chunked encoding, we have no way of knowing the total length of the
-        # response, all we can do is incrememnt the progress with backoff such that we
-        # never hit 100% until it's done.
-        @progress = @progress + (100 - @progress) / 2
-
-    request.onload = request.onerror = request.ontimeout = request.onabort = =>
-      @progress = 100
+
+        if size?
+          # This is not perfect, as size is in bytes, length is in chars
+          try
+            @progress = request.responseText.length / size
+          catch e
+        else
+          # If it's chunked encoding, we have no way of knowing the total length of the
+          # response, all we can do is increment the progress with backoff such that we
+          # never hit 100% until it's done.
+          @progress = @progress + (100 - @progress) / 2
+
+      _onprogress?(arguments...)
+
+    _onreadystatechange = request.onreadystatechange
+    request.onreadystatechange = =>
+      if request.readyState in [0, 4]
+        @progress = 100
+      else if not request.onprogress? and request.readyState is 3
+        @progress = 50
+
+      _onreadystatechange?(arguments...)

 class ElementMonitor
   constructor: (selectors...) ->
@@ -185,25 +202,22 @@
       @elements.push new ElementTracker set

 class ElementTracker
-  constructor: (selectors) ->
+  constructor: (@selectors) ->
     @progress = 0

-    if typeof selectors is 'string'
-      @selector = selectors
-    else
-      @selector = selectors.join(',')
+    if typeof @selectors is 'string'
+      @selectors = [@selectors]

     @check()

   check: ->
-    if document.querySelector(@selector)
-      @done()
+    matches = document.querySelectorAll(@selectors.join(','))
+
+    if matches.length
+      @progress = 100 * matches.length / @selectors.length
     else
       setTimeout (=> @check()),
         ELEMENT_CHECK_INTERVAL
-
-  done: ->
-    @progress = 100

 class DocumentMonitor
   states:
@@ -214,9 +228,12 @@
   constructor: ->
     @progress = 0

+    _onreadystatechange = document.onreadystatechange
     document.onreadystatechange = =>
       if @states[document.readyState]?
         @progress = @states[document.readyState]
+
+      _onreadystatechange?(arguments...)

 class EventLagMonitor
   constructor: ->
@@ -290,7 +307,9 @@
 bar = null
 uniScaler = null
 animation = null
-
+cancelAnimation = null
+
+# We reset the bar whenever it looks like an ajax navigation has occured.
 if window.pushState?
   _pushState = window.pushState
   window.pushState = ->
@@ -307,16 +326,24 @@

 do init = ->
   sources = [new AjaxMonitor, new ElementMonitor('body'), new DocumentMonitor, new EventLagMonitor]
+
+  bar = new Bar
+
+  # Each source of progress data has it's own scaler to smooth its output
   scalers = []

-  bar = new Bar
+  # We have an extra scaler for the final output to keep things looking nice as we add and
+  # remove sources
   uniScaler = new Scaler

 reset = ->
   bar.destroy()

+  # Not all browsers support cancelAnimationFrame
+  cancelAnimation = true
+
   if animation?
-    cancelAnimationFrame animation
+    cancelAnimationFrame? animation
     animation = null

   init()
@@ -328,6 +355,7 @@
 go = ->
   bar.render()

+  cancelAnimation = false
   animation = runAnimation (frameTime, enqueueNextFrame) ->
     # Every source gives us a progress number from 0 - 100
     # It's up to us to figure out how to turn that into a smoothly moving bar
@@ -362,7 +390,7 @@
     bar.update uniScaler.tick(frameTime, avg)

     start = now()
-    if bar.done() or done
+    if bar.done() or done or cancelAnimation
       bar.update 100

       setTimeout ->
@@ -374,6 +402,7 @@
 do check = ->
   bar.render()

+  # It's usually possible to render a bit before the document declares itself ready
   if not document.querySelector('.pace')
     setTimeout check, 50
   else


Send entire config object into sidekiq

Send entire config object into sidekiq

--- plugins/email/index.coffee
+++ plugins/email/index.coffee
@@ -5,7 +5,10 @@
   SIDEKIQ_QUEUE = "error_emails"

   @receiveEvent: (config, event, callback) ->
-    config.sidekiq.enqueue SIDEKIQ_WORKER, [event.trigger.type, event.error.id, config?.includeMetadata],
+    sidekiq = config.sidekiq
+    delete config.sidekiq
+
+    sidekiq.enqueue SIDEKIQ_WORKER, [event.trigger.type, event.error.id, config],
       retry: false
       queue: SIDEKIQ_QUEUE


Use a key function when setting data

Use a key function when setting data

--- js/views/VizView.coffee
+++ js/views/VizView.coffee
@@ -111,14 +111,13 @@
         .style('top', '3px')

   _drawFromLayout: (data) ->
-    texts = @texts.selectAll('text').data(data)
+    texts = @texts.selectAll('text').data(data, (d) -> d[0])

     # Modify existing <text> elements
     texts.transition()
       .duration(AnimationDuration)
       .attr('transform', (d) -> "translate(#{d.x},#{d.y}),rotate(#{d.rotate})")
       .style('font-size', (d) -> "#{d.size}px")
-      .text((d) -> d.text)

     # Add new <text> elements and animate their opacity
     texts.enter().append('text')


sending all events to particles

sending all events to particles

--- src/timed_events.coffee
+++ src/timed_events.coffee
@@ -30,5 +30,5 @@
   @param {Function} fn Code to execute after n frames has passed
   ###
   every: (n, fn) ->
-    if I.age.mod(n) is 0
+    while I.
       fn()


levels and default game states

levels and default game states

--- src/title_screen.coffee
+++ src/title_screen.coffee
@@ -5,6 +5,6 @@
     engine.nextLevel() if justPressed.any

   self.bind "overlay", (canvas) ->
-    self.centerText canvas, window.title, "Press any key to start"
+    self.centerText canvas, App.name, "Press any key to start"

   return self


DockController: Use KDCustomScrollView for dock container

--- client/Main/maincontroller/dockcontroller.coffee
+++ client/Main/maincontroller/dockcontroller.coffee
@@ -150,7 +150,10 @@

     @ready =>

-      dock.addSubView @navController.getView()
+      @scrollView = new KDCustomScrollView
+
+      dock.addSubView @scrollView
+      @scrollView.wrapper.addSubView @navController.getView()

       # Listen appManager to update dock items states
       {appManager, kodingAppsController} = KD.singletons


Add defensive check

Add defensive check

--- client/ide/lib/workspace/idelayoutmanager.coffee
+++ client/ide/lib/workspace/idelayoutmanager.coffee
@@ -86,7 +86,7 @@
       @createParentSplitViews splitViews
     else

-      for panel in splitViews.panels
+      for panel in splitViews.panels when panel
         @createParentSplitViews panel

     return @map


Fixed an indentation error

Fixed an indentation error

--- src/ui/draggable.coffee
+++ src/ui/draggable.coffee
@@ -75,7 +75,7 @@
 			t: event.timeStamp

 		@view.x = @_start.x + correctedDelta.x - @_offset.x
- 		@view.y = @_start.y + correctedDelta.y - @_offset.y
+		@view.y = @_start.y + correctedDelta.y - @_offset.y

 		@_deltas.push correctedDelta


spreadsheet: fix slot release edit initial value

--- web/volume/spreadsheet.coffee
+++ web/volume/spreadsheet.coffee
@@ -1022,7 +1022,7 @@
                 return if info.slot?.top && (mi == 'date' || mi == 'release')
                 v = info.slot?[mi]
                 v = !!v if mi == 'top' && info.slot
-                v = v+'' if mi == 'release'
+                v = (v ? -1)+'' if mi == 'release'
               else if info.c == 'asset' # not reached
                 v = info.asset[mi]
               else


instead of redefining the whole Backbone.View.prototype.constructor method, lets just call it

This will help us prevent strange bugs when the
Backbone.View.prototype.constructor method signature changes in the
future

--- core/app/backbone/modules/memory_profiler.coffee
+++ core/app/backbone/modules/memory_profiler.coffee
@@ -7,13 +7,10 @@

       MemoryProfiler.view_count_collection = new ViewCountCollection

+      old_constructor = Backbone.View.prototype.constructor
       Backbone.View.prototype.constructor = (options)->
-        @cid = _.uniqueId('view')
+        old_constructor.apply @, arguments
         openView(this)
-        @_configure(options || {})
-        @_ensureElement();
-        @initialize.apply(this, arguments)
-        @delegateEvents()
         @on 'close', => closeView(this)

     class MemoryView extends Marionette.ItemView


Add more logging and don't delete temp files

--- services/filestore/app/coffee/S3PersistorManager.coffee
+++ services/filestore/app/coffee/S3PersistorManager.coffee
@@ -49,9 +49,9 @@
 			if res.statusCode != 200
 				logger.err bucketName:bucketName, key:key, fsPath:fsPath, "non 200 response from s3 putting file"
 				return callback("non 200 response from s3 on put file")
-			LocalFileWriter.deleteFile fsPath, (err)->
-				logger.log res:res,  bucketName:bucketName, key:key, fsPath:fsPath,"file uploaded to s3"
-				callback(err)
+			#LocalFileWriter.deleteFile fsPath, (err)->
+			logger.log res:res,  bucketName:bucketName, key:key, fsPath:fsPath,"file uploaded to s3"
+			callback(err)
 		putEventEmiter.on "error", (err)->
 			logger.err err:err,  bucketName:bucketName, key:key, fsPath:fsPath, "error emmited on put of file"
 			callback err
@@ -100,7 +100,12 @@
 				logger.err err:err, res:res, bucketName:bucketName, key:key, "something went wrong deleting file in aws"
 			callback(err)

-	deleteDirectory: (bucketName, key, callback)->
+	deleteDirectory: (bucketName, key, _callback)->
+		callback = (args...) ->
+			logger.log key: key, bucketName: bucketName, "calling delete callback"
+			_callback(args...)
+
+		logger.log key: key, bucketName: bucketName, "deleting directory"
 		s3Client = knox.createClient
 			key: settings.filestore.s3.key
 			secret: settings.filestore.s3.secret


fix null case

fix null case

--- services/track-changes/app/coffee/UpdatesManager.coffee
+++ services/track-changes/app/coffee/UpdatesManager.coffee
@@ -36,7 +36,7 @@
 			MongoManager.insertCompressedUpdates project_id, doc_id, compressedUpdates, temporary,(error) ->
 				return callback(error) if error?
 				logger.log project_id: project_id, doc_id: doc_id, rawUpdatesLength: length, compressedUpdatesLength: compressedUpdates.length, "compressed doc updates"
-				if lastCompressedUpdate.inS3?
+				if lastCompressedUpdate?.inS3?
 					MongoManager.remarkDocHistoryAsArchived doc_id, callback
 				else
 					callback()


fix for acceptance test

fix for acceptance test

--- services/track-changes/app/coffee/UpdatesManager.coffee
+++ services/track-changes/app/coffee/UpdatesManager.coffee
@@ -205,6 +205,8 @@
 				for user_id in user_ids
 					if UpdatesManager._validUserId(user_id)
 						update.meta.users.push fetchedUserInfo[user_id]
+					else
+						update.meta.users.push null
 			callback null, updates

 	_validUserId: (user_id) ->


NEW ScrollElement onScroll prop: childrenOffsetDelta

--- source/Art/Engine/Elements/Widgets/ScrollElement.coffee
+++ source/Art/Engine/Elements/Widgets/ScrollElement.coffee
@@ -200,9 +200,9 @@
     else
       @_spMinusTp = _scrollPosition - @trackingPositionFromPendingGeometry

-    @_updateOnScreenInfo oldChildrenOffset != @_childrenOffset
-
-  _updateOnScreenInfo: (childrenOffsetChanged)->
+    @_updateOnScreenInfo @_childrenOffset - oldChildrenOffset
+
+  _updateOnScreenInfo: (childrenOffsetDelta)->
     {isVertical, windowSize} = @

     focusedChild = @_pendingState._focusedChild
@@ -230,7 +230,7 @@
     firstOnScreenChildIndex = -1 if firstOnScreenChildIndex == children.length

     if (
-        childrenOffsetChanged ||
+        childrenOffsetDelta != 0 ||
         firstOnScreenChildIndex != @_firstOnScreenChildIndex ||
         lastOnScreenChildIndex  != @_lastOnScreenChildIndex ||
         focusedChildIndex       != @_focusedChildIndex ||
@@ -241,6 +241,7 @@
           @scrollRatio
           @childrenOffset
           @childrenSize
+          childrenOffsetDelta
           windowSize
           focusedChild
           firstOnScreenChildIndex


add a comment for the spec's simulated click event

--- test/concept-coach/modal.spec.cjsx
+++ test/concept-coach/modal.spec.cjsx
@@ -10,7 +10,9 @@
         true
       children: <button id="click-me"/>

-
+    # simulate click manually.
+    # The event listener for this is outside of React's simulated event system,
+    # because it needs to be the first check for all click and focus events on `document`.
     @click = (el, onClick) ->
       ev = document.createEvent("MouseEvent")
       clickArguments = [


Updated DynamoDB unit test to prevent timeout errors

--- test/services/dynamodb.spec.coffee
+++ test/services/dynamodb.spec.coffee
@@ -27,8 +27,9 @@
   describe 'retryDelays', ->

     it 'has a custom backoff function', ->
+      client = ddb()
       expectedDelays = [ 0, 50, 100, 200, 400, 800, 1600, 3200, 6400, 12800 ]
-      actualDelays = (ddb().retryDelays(i) for i in [0..ddb().numRetries()-1])
+      actualDelays = (client.retryDelays(i) for i in [0..client.numRetries()-1])
       expect(actualDelays).to.eql(expectedDelays)

   describe 'CRC32 check', ->


Fix tests

Fix tests

--- test/signup-controller-spec.coffee
+++ test/signup-controller-spec.coffee
@@ -12,6 +12,7 @@
         AuthenticatorService: @AuthenticatorService
         $routeParams: @routeParams
         $window: @window
+        $scope: @rootScope.$new()

   it 'should exist', ->


update data

update data

--- app/assets/javascripts/bank_train/demo.coffee
+++ app/assets/javascripts/bank_train/demo.coffee
@@ -52,6 +52,9 @@

   to_page: (page_id, direction = 'toright')->
+    @play = false
+    @current_idx = null
+
     current_page_id = @$elm.find(".page-flow .page.active").data('num')
     return if page_id is current_page_id

@@ -80,6 +83,7 @@
     @play = false

   play_demo: (screen)->
+    return if @play is true
     @play = true
     @current_idx ||= 0
     if @current_idx is 0
@@ -138,6 +142,7 @@
           else
             @current_idx = null
             console.log '播放完毕'
+            @play = false
         # , 1500

   show_tip_text: ($tip, func)->


Category bar uses custom precision for axis only

Leave the default precision for the tooltips

Ref quintel/etmodel#3841

--- app/assets/javascripts/d3/category_bar.coffee
+++ app/assets/javascripts/d3/category_bar.coffee
@@ -275,7 +275,7 @@
         .ticks(7)
         .tickSize(-@width, 10, 0)
         .orient("right")
-        .tickFormat(@main_formatter())
+        .tickFormat(@y_axis_formatter())

       @svg.append("svg:g")
         .attr("class", "y_axis inner_grid")
@@ -317,7 +317,7 @@
         .attr('x', 0 - @margins.left)
         .attr('y', (d) => @series_height - @y(d))

-      @y_axis.tickFormat(@main_formatter())
+      @y_axis.tickFormat(@y_axis_formatter())

       # Animate the y-axis.
       @svg.selectAll(".y_axis").transition().call(@y_axis.scale(@inverted_y))
@@ -443,8 +443,8 @@
         }
       )

-    main_formatter: (opts = {}) ->
+    y_axis_formatter: (opts = {}) ->
       if @model.get('config') && @model.get('config').y_precision != undefined
-        super(Object.assign({ precision: @model.get('config').y_precision }, opts))
+        @main_formatter(Object.assign({ precision: @model.get('config').y_precision }, opts))
       else
-        super()
+        @main_formatter()


gridDrawObject and gridDrawShip events remove Utils.Operation

--- app/assets/javascripts/pirates/grid.js.coffee
+++ app/assets/javascripts/pirates/grid.js.coffee
@@ -1,14 +1,19 @@
 class @Grid
   @initialize = (@canvas) ->
     Grid.GridControls.initialize()
-    @size = 32 # pixel per cell
+    @size = 32 # pixel per cell, overriden by load
+
     @ctx = canvas.getContext "2d"
     @canvasWidth = @ctx.canvas.width
     @canvasHeight = @ctx.canvas.height

+    # load the default grid when the user visits the site for the first time.
     @defaultData = $.parseJSON $('#gridData').html()

+    # stores the cell coordinates that are highlighted because of the mouse
     @activeCell = null
+
+    # highlighted cell because of look operation
     @look = null
     @mousePosition = null

@@ -25,11 +30,14 @@

     @mousePressedOnShip = false
     @mousePressed = false
-    @_smoothingStep = 0 #TODO
+    @_smoothingStep = 0 # help for smooth animation

   @loadDefault = () =>
     @load @defaultData

+  ###
+    loads a serialized grid object
+  ###
   @load = (obj) =>
     @width = obj.width || 22
     @height = obj.height || 10


fix uglified component mounting

fix uglified component mounting

fixes: CNVS-34655

test plan:
 - set the following env vars:
   - RAILS_LOAD_ALL_LOCALES=true
   - USE_OPTIMIZED_JS=true
 - compile assets with: `rake canvas:compile_assets`
 - go to gradezilla enabled gradebook
 - the page loads without the following JS console error:
   - "Uncaught Error: _registerComponent(...): Target container is not a
   DOM element."

Change-Id: Ib9722608da0e156129162662d639718c7cb041e1
Reviewed-on: https://gerrit.instructure.com/100834
Reviewed-by: Keith T. Garner <a1a80143a840c140f31c3495ebbd9ec906e09fed@instructure.com>
Reviewed-by: Shahbaz Javeed <6692d7be3fa215e5483a121d6fb314def6049877@instructure.com>
Tested-by: Jenkins
QA-Review: Anju Reddy <851202533fb1f0b615e4547ba226c76996d060dd@instructure.com>
Product-Review: Keith T. Garner <a1a80143a840c140f31c3495ebbd9ec906e09fed@instructure.com>

--- app/coffeescripts/gradezilla/Gradebook.coffee
+++ app/coffeescripts/gradezilla/Gradebook.coffee
@@ -1084,7 +1084,7 @@

     initViewOptionsMenu: () =>
       component = React.createElement(ViewOptionsMenu, {}, null)
-      mountPoint = document.querySelectorAll("[data-component='#{component.type.name}']")[0]
+      mountPoint = document.querySelectorAll("[data-component='ViewOptionsMenu']")[0]
       ReactDOM.render(component, mountPoint)

     initAssignmentColumnHeader: (obj) =>


Tests: Added postMessageWithLink test.

Tests: Added postMessageWithLink test.

--- tests/src/activity/activity.coffee
+++ tests/src/activity/activity.coffee
@@ -248,5 +248,19 @@
     browser.waitForElementVisible activitySelector' + .activity-content-wrapper .embed-image-view img', image # Assertion
     browser.end()

-    browser.end()
-
+
+  postMessageWithLink: (browser) ->
+
+    helpers.beginTest(browser)
+
+    link = 'http://nightwatchjs.org/' # last '/' is the trick!
+    linkSelector = activitySelector + ' .activity-content-wrapper article a'
+
+    helpers.doPostActivity(browser, link)
+
+    browser.getAttribute linkSelector, 'href', (result) ->
+      href = result.value
+      assert.equal(link, href)
+
+    browser.end()
+


movable velocity and acceleration getters

--- test/movable.coffee
+++ test/movable.coffee
@@ -34,8 +34,8 @@

 test "should increase velocity according to acceleration", ->
   particle = GameObject
-    velocity: Point(0, 1)
-    acceleration: Point(0, -60)
+    velocity: Point(0, 120)
+    acceleration: Point(0, 60)

   60.times ->
     particle.update(1/60)


Don't rely on multiline in other tests

Don't rely on multiline in other tests

--- test/strings.coffee
+++ test/strings.coffee
@@ -131,10 +131,7 @@
 eq ''' line 0
   should not be relevant
     to the indent level
-''', ' line 0\n\
-      should not be relevant\n  \
-      to the indent level
-'
+''', ' line 0\nshould not be relevant\n  to the indent level'

 eq ''' '\\\' ''', " '\\' "
 eq """ "\\\" """, ' "\\" '


Fixed tests

Fixed tests

--- test/KernelTests.coffee
+++ test/KernelTests.coffee
@@ -1,5 +1,5 @@
 chai = require "chai"
-chai.should()
+should = chai.should()
 require "../src/cms/System/Kernel"

 describe "Kernel", ->
@@ -48,13 +48,15 @@
       Kernel.fireHookable "Hello.set", val
       @msg = val
   """
-  page404 = "\n  \n    <title>#{@getSiteName()} - Error 404</title>\n  \n  \n
Error 404: Not found
\n
The Application or Page at #{url} could not be found.
\n  \n"
+  page404 = "\n  \n    <title>A CoffeeNode CMS site - Error 404</title>\n  \n  \n
Error 404: Not found
\n
The Application or Page at /doesnotexistorifitdoesthatsweird could not be found.
\n  \n"

-  it "should exist", ->
-    should.exist Kernel
+  it "should be an object", ->
+    Kernel.should.be.an "object"

   it "should be able to preprocess code", ->
     Kernel.preprocess(code, appConfig).should.equal codePreProcessed

   it "should return a 404 page for non-existant pages", ->
-    Kernel.getPage("/doesnotexistorifitdoesthatsweird").should.equal page404
+    result = Kernel.getPage "/doesnotexistorifitdoesthatsweird"
+    result[0].should.equal 404
+    result[1].should.equal page404


Allow Coauthor within <iframe>

Allow Coauthor within <iframe>

--- server/security.coffee
+++ server/security.coffee
@@ -8,3 +8,6 @@

 ## Allow blob source for images, as needed by pdf.js
 BrowserPolicy.content.allowImageOrigin 'blob:'
+
+## Allow <iframe>ing Coauthor, for Comingle
+BrowserPolicy.framing.allowAll()


Test update for c99e17c7f3027cb7b9df54e101caa9aa845d88a5 coffeescript helpers changes.

--- tests/assets.coffee
+++ tests/assets.coffee
@@ -34,7 +34,7 @@

     c = t.client(zapp.server)
     c.get '/coffee.js', (err, res) ->
-      t.equal 1, res.body, ';var __slice = Array.prototype.slice;var __hasProp = Object.prototype.hasOwnProperty;var __bind = function(fn, me){  return function(){ return fn.apply(me, arguments); };};var __extends = function(child, parent) {  for (var key in parent) {    if (__hasProp.call(parent, key)) child[key] = parent[key];  }  function ctor() { this.constructor = child; }  ctor.prototype = parent.prototype;  child.prototype = new ctor;  child.__super__ = parent.prototype;  return child;};var __indexOf = Array.prototype.indexOf || function(item) {  for (var i = 0, l = this.length; i < l; i++) {    if (this[i] === item) return i;  } return -1; };(function () {\n            return alert(\'hi\');\n          })();'
+      t.equal 1, res.body, ';var __slice = [].slice;var __hasProp = {}.hasOwnProperty;var __bind = function(fn, me){  return function(){ return fn.apply(me, arguments); };};var __extends = function(child, parent) {  for (var key in parent) {    if (__hasProp.call(parent, key)) child[key] = parent[key];  }  function ctor() { this.constructor = child; }  ctor.prototype = parent.prototype;  child.prototype = new ctor();  child.__super__ = parent.prototype;  return child;};var __indexOf = [].indexOf || function(item) {  for (var i = 0, l = this.length; i < l; i++) {    if (i in this && this[i] === item) return i;  } return -1; };var __modulo = function(a, b) { return (+a % (b = +b) + b) % b; };(function () {\n            return alert(\'hi\');\n          })();'
       t.equal 2, res.headers['content-type'], JS_TYPE

   js: (t) ->
@@ -257,7 +257,7 @@
     c.get '/shared.js', (err, res) ->
       t.equal 'shared', res.headers['content-length'], '91'
     c.get '/coffee.js', (err, res) ->
-      t.equal 'coffee', res.headers['content-length'], '475'
+      t.equal 'coffee', res.headers['content-length'], '492'
     c.get '/js.js', (err, res) ->
       t.equal 'js', res.headers['content-length'], '13'


clean up deps

clean up deps

--- app/lib/analytics.coffee
+++ app/lib/analytics.coffee
@@ -1,3 +1,6 @@
+querystring = require 'querystring'
+http = require 'http'
+
 ###
 This will log a user interaction using the analytics.zooniverse.org API.
 eventData should be of the format:
@@ -46,7 +49,4 @@
   req.write "{\"events\":[" + dataString + "]}"
   req.end()
   return
-
-querystring = require("querystring")
-http = require("http")
-exports.logEvent = logEvent
+module.exports = logEvent


Updated comment from last commit

Updated comment from last commit

--- app/lib/sdk/model.coffee
+++ app/lib/sdk/model.coffee
@@ -1,5 +1,5 @@
 # @todo this is old and needs to be removed; am just keeping it
-#       around for now until I can decouple it from the url class
+# around for now until I can decouple it from the url class

 {EventEmitter} = require "events"
 {XML} = require "./xml"


add failing test case showing iframe GET requests' referrer headers are not being set correctly

--- test/iframe_test.coffee
+++ test/iframe_test.coffee
@@ -108,6 +108,28 @@
     it "should pass messages back and forth", ->
       browser.assert.text "title", "pong http://example.com"

+  describe "referer", ->
+    before (done)->
+      brains.get "/iframe/show-referer", (req, res)->
+        res.send "<title>#{req.headers["referer"]}</title>"
+
+      brains.get "/iframe/referer", (req, res)->
+        res.send """
+
+
+
+
+
+
+        """
+      brains.ready =>
+        browser.visit "/iframe/referer", done
+
+    it "should be the parent's URL", ->
+      assert.equal browser.window.frames["child"].title, "http://example.com/iframe/referer"
+
+    after ->
+      browser.close()

   describe "link target", ->
     before (done)->


finish integration test

finish integration test

--- test/integration.coffee
+++ test/integration.coffee
@@ -8,6 +8,7 @@
 module.exports =

   'systems are started and stopped in the most efficient order': (test) ->
+    test.expect 28

     ###################################################################################
     # services
@@ -15,10 +16,10 @@
     services =
       redisOne:
         start: ->
-          test.ok starting.isEqual(
-            new Set(['redisOne', 'redisTwo', 'postgres', 'elasticSearch', 'mailAPI'])
-          )
-          test.ok running.isEqual(
+          test.ok starting.equals(
+            new Set('redisOne', 'redisTwo', 'postgres', 'elasticSearch', 'mailAPI')
+          )
+          test.ok running.equals(
             new Set
           )
           Promise.delay(10)
@@ -26,10 +27,10 @@
           Promise.delay(100)
       redisTwo:
         start: ->
-          test.ok starting.isEqual(
-            new Set(['redisOne', 'redisTwo', 'postgres', 'elasticSearch', 'mailAPI'])
-          )
-          test.ok running.isEqual(
+          test.ok starting.equals(
+            new Set('redisOne', 'redisTwo', 'postgres', 'elasticSearch', 'mailAPI')
+          )
+          test.ok running.equals(
             new Set
           )
           Promise.delay(20)
@@ -37,76 +38,123 @@
           Promise.delay(100)
       serverOne:
         start: ->
-          Promise.delay(100)
+          test.ok starting.equals(
+            new Set('mailAPI', 'serverOne', 'serverTwo', 'serverThree')
+          )
+          test.ok running.equals(
+            new Set('redisOne', 'redisTwo', 'postgres', 'elasticSearch', 'cache')
+          )
+          Promise.delay(5)
         stop: ->
           Promise.delay(100)
       serverTwo:
         start: ->
-          Promise.delay(100)
+          test.ok starting.equals(
+            new Set(['mailAPI', 'serverOne', 'serverTwo', 'serverThree'])
+          )
+          test.ok running.equals(
+            new Set(['redisOne', 'redisTwo', 'postgres', 'elasticSearch', 'cache'])
+          )
+          Promise.delay(30)
         stop: ->
           Promise.delay(100)
       serverThree:
         start: ->
-          Promise.delay(100)
+          test.ok starting.equals(
+            new Set(['mailAPI', 'serverOne', 'serverTwo', 'serverThree'])
+          )
+          test.ok running.equals(
+            new Set(['redisOne', 'redisTwo', 'postgres', 'elasticSearch', 'cache'])
+          )
+          Promise.delay(60)
         stop: ->
           Promise.delay(100)
       elasticSearch:
         start: ->
-          test.ok starting.isEqual(
-            new Set(['redisOne', 'redisTwo', 'postgres', 'elasticSearch', 'mailAPI'])
-          )
-          test.ok running.isEqual(
-            new Set
-          )
-          Promise.delay(100)
+          test.ok starting.equals(
+            new Set('redisOne', 'redisTwo', 'postgres', 'elasticSearch', 'mailAPI')
+          )
+          test.ok running.equals(
+            new Set
+          )
+          Promise.delay(25)
         stop: ->
           Promise.delay(100)
       mailAPI:
         start: ->
-          test.ok starting.isEqual(
-            new Set(['redisOne', 'redisTwo', 'postgres', 'elasticSearch', 'mailAPI'])
-          )
-          test.ok running.isEqual(
-            new Set
-          )
-          Promise.delay(100)
+          test.ok starting.equals(
+            new Set('redisOne', 'redisTwo', 'postgres', 'elasticSearch', 'mailAPI')
+          )
+          test.ok running.equals(
+            new Set
+          )
+          Promise.delay(60)
         stop: ->
           Promise.delay(100)
       cache:
         start: ->
-          test.ok starting.isEqual(
-            new Set(['postgres', 'elasticSearch', 'mailAPI', 'cache'])
-          )
-          test.ok running.isEqual(
-            new Set(['redisOne', 'redisTwo'])
-          )
-          Promise.delay(100)
+          test.ok starting.equals(
+            new Set('elasticSearch', 'mailAPI', 'cache')
+          )
+          test.ok running.equals(
+            new Set('redisOne', 'redisTwo', 'postgres')
+          )
+          Promise.delay(30)
         stop: ->
           Promise.delay(100)
       postgres:
         start: ->
-          test.ok starting.isEqual(
-            new Set(['redisOne', 'redisTwo', 'postgres', 'elasticSearch', 'mailAPI'])
-          )
-          test.ok running.isEqual(
-            new Set
-          )
-          Promise.delay(100)
+          test.ok starting.equals(
+            new Set('redisOne', 'redisTwo', 'postgres', 'elasticSearch', 'mailAPI')
+          )
+          test.ok running.equals(
+            new Set
+          )
+          Promise.delay(15)
         stop: ->
           Promise.delay(100)
       loadBalancer:
         start: ->
-          Promise.delay(100)
+          test.ok starting.equals(
+            new Set('workerTwo', 'loadBalancer')
+          )
+          test.ok running.equals(
+            new Set(
+              'redisOne',
+              'redisTwo',
+              'postgres',
+              'elasticSearch',
+              'cache',
+              'serverOne',
+              'serverTwo',
+              'serverThree',
+              'mailAPI',
+              'workerOne'
+            )
+          )
+          Promise.delay(20)
         stop: ->
           Promise.delay(100)
       workerOne:
         start: ->
-          Promise.delay(100)
+          test.ok starting.equals(
+            new Set('serverTwo', 'serverThree', 'workerOne', 'workerTwo')
+          )
+          test.ok running.equals(
+            new Set('redisOne', 'redisTwo', 'postgres', 'elasticSearch', 'cache', 'serverOne', 'mailAPI')
+          )
+          Promise.delay(10)
         stop: ->
           Promise.delay(100)
       workerTwo:
         start: ->
-          Promise.delay(100)
+          test.ok starting.equals(
+            new Set('serverTwo', 'serverThree', 'workerOne', 'workerTwo')
+          )
+          test.ok running.equals(
+            new Set('redisOne', 'redisTwo', 'postgres', 'elasticSearch', 'cache', 'serverOne', 'mailAPI')
+          )
+          Promise.delay(60)
         stop: ->
           Promise.delay(100)

@@ -151,45 +199,41 @@
     stopped = new Set

     start = (names) ->
-      console.log('starting:', names)
       starting.add names
       Promise.all names.map (name) ->
         callback = services[name].start
         promise = Promise.resolve(callback())
         promise.then ->
-          console.log('running:  ' + name)
-          starting.remove name
+          starting.delete name
           running.add name
           # start all we can start now that have not been started
-          toStart = new Set(dag.whereAllParentsIn(running.elements()))
-            .remove(starting)
-            .remove(running)
-            .elements()
+          toStart = new Set(dag.whereAllParentsIn(running.keys()))
+            .delete(starting)
+            .delete(running)
+            .keys()
           start toStart

     stop = (names) ->
-      console.log('stopping: ', names)
       stopping.add names
       return Promise.all names.map (name) ->
         callback = services[name].stop
         promise = Promise.resolve callback()
         promise.then ->
-          console.log('stopped:  ' + name)
-          stopping.remove name
+          stopping.delete name
           stopped.add name
           # stop all we can stop now that have not been stopped
-          toStop = new Set(dag.whereAllChildrenIn(stopped.elements()))
-            .remove(stopping)
-            .remove(stopped)
-            .elements()
+          toStop = new Set(dag.whereAllChildrenIn(stopped.keys()))
+            .delete(stopping)
+            .delete(stopped)
+            .keys()
           stop toStop

     start(dag.parentless())
       .then ->
-        test.ok running.isEqual new Set Object.keys(services)
-        test.ok starting.isEqual new Set
+        test.ok running.equals new Set Object.keys(services)
+        test.ok starting.equals new Set
         stop dag.childless()
       .then ->
-        test.ok stopped.isEqual new Set Object.keys(services)
-        test.ok stopping.isEqual new Set
+        test.ok stopped.equals new Set Object.keys(services)
+        test.ok stopping.equals new Set
         test.done()


add comment

add comment

--- test/integration.coffee
+++ test/integration.coffee
@@ -305,8 +305,22 @@
         else
           # use the normal bravo factory when required from other factories
           result = inner query
-          # this would usually be cached
-          # but dont cache it as that would
+
+          # by default this will return a factory and then
+          # the cached value on subsequent resolutions.
+          # requires originating from bravo_charlie are not cached (nocache above).
+          # requires from other factories are cached by default.
+
+          # the under construction is a problem !!!
+          # if bravo is already under construction by alpha_bravo
+          # for example which reaches this code branch
+          # then bravo_charlies special factory returned from this resolver
+          # is ignored and bravo_charlie resolves to the same value as
+          # alpha_charlie.
+          # we can prevent this by disabling caching for all requires of bravo
+
+          # under construction should key both based on the path and the factory!!!
+
           result.nocache = true
           result
       else
@@ -327,7 +341,6 @@
     hinoki.get(
       container
       ['alpha_bravo', 'bravo_charlie', 'alpha_charlie']
-      # (x) -> console.log util.inspect x
     )
       .spread (alpha_bravo, bravo_charlie, alpha_charlie) ->
         test.equal alpha_bravo, 'alpha_bravo'


tweaking leaderboard logic

tweaking leaderboard logic

--- app/models/person.coffee
+++ app/models/person.coffee
@@ -26,7 +26,7 @@
     # if totalGames == 0
     #   percentage = -99
     if losses < 1
-      percentage = wins + 1
+      percentage = (wins * (wins + 1)) + 1
     else
       percentage = (wins / losses) * ( @get('totalGames') )
     Math.round(percentage * 100) / 100


Fix bug in LinearColorMapper.build_palette.

Fixes #2801, by correctly converting non-number values for the last
value of the passed in palette when assigning to the last entry in
the new palette.

--- bokehjs/src/coffee/mapper/linear_color_mapper.coffee
+++ bokehjs/src/coffee/mapper/linear_color_mapper.coffee
@@ -70,12 +70,14 @@

   _build_palette: (palette) ->
     new_palette = new Uint32Array(palette.length+1)
+    _convert = (value) ->
+      if _.isNumber(value)
+        return value
+      else
+        return parseInt(value.slice(1), 16)
     for i in [0...palette.length]
-      if _.isNumber(palette[i])
-        new_palette[i] = palette[i]
-      else
-        new_palette[i] = parseInt(palette[i].slice(1), 16)
-    new_palette[new_palette.length-1] = palette[palette.length-1]
+      new_palette[i] = _convert(palette[i])
+    new_palette[new_palette.length-1] = _convert(palette[palette.length-1])
     return new_palette

 module.exports =


And so?

And so?

--- test/appveyor.coffee
+++ test/appveyor.coffee
@@ -16,7 +16,7 @@
   uri = url.parse api
   data = JSON.stringify data
   uri.method = 'POST'
-  uri.path = path
+  uri.pathname = path
   uri.headers =
     'Content-Type': 'application/json'
     'Content-Length': data.length


More robust handling of finally/always

More robust handling of finally/always

--- lib/BehaviorTree.coffee
+++ lib/BehaviorTree.coffee
@@ -224,6 +224,7 @@
     localPath = choice.toString()
     dests = @findDestinations node, choice
     dests.forEach (d) =>
+      return if d.type in ['always', 'finally'] and dests.length > 1
       destChoice = d.choices[localPath]
       if destChoice and destChoice.state isnt State.PENDING
         return
@@ -270,10 +271,6 @@
         source = @nodes[source.promiseSource]
         continue

-      if choice.type in ['always', 'finally'] and source.destinations.length
-        source = @nodes[source.promiseSource]
-        continue
-
       # Add edge between source and node
       source.destinations.push choice if source.destinations.indexOf(choice) is -1
       choice.sources.push source if choice.sources.indexOf(source) is -1


ensure setRandomSeed can take one arg as well

--- test/src/math.coffee
+++ test/src/math.coffee
@@ -124,6 +124,12 @@
         random()

       expect(result_set_a).to.eql(result_set_b)
+
+    it 'should accept a single argument', ->
+      math.setRandomSeed(100)
+      [low, high] = math.getRandomSeed()
+      expect(low).to.equal(100)
+      expect(high).to.equal(0)

   describe '.getRandomSeed', ->
     random = null


Clearer intent when declaring criteria

Clearer intent when declaring criteria

--- apps/shows/routes.coffee
+++ apps/shows/routes.coffee
@@ -21,13 +21,6 @@
   currentPage = parseInt req.query.page or 1
   pageSize = 18

-  criteria = (status) ->
-    near: city.coords.toString()
-    published_with_eligible_artworks: true
-    status: status
-    sort: '-start_at'
-    size: pageSize
-
   upcoming = new PartnerShows
   upcoming.comparator = (show) -> Date.parse(show.get('start_at'))
   current = new PartnerShows [], state: currentPage: currentPage, pageSize: pageSize
@@ -35,10 +28,16 @@
   past = new PartnerShows
   past.comparator = (show) -> -(Date.parse(show.get('end_at')))

+  criteria =
+    near: city.coords.toString()
+    published_with_eligible_artworks: true
+    sort: '-start_at'
+    size: pageSize
+
   Q.allSettled([
-    upcoming.fetch(cache: true, data: criteria('upcoming'))
-    current.fetch(cache: true, data: _.extend({}, criteria('running'), total_count: true, sort: 'end_at'))
-    past.fetch(cache: true, data: criteria('closed'))
+    upcoming.fetch(cache: true, data: _.defaults(status: 'upcoming', criteria))
+    current.fetch(cache: true, data: _.defaults(status: 'running', total_count: true, sort: 'end_at', criteria))
+    past.fetch(cache: true, data: _.defaults(status: 'closed', criteria))
   ]).then(->
     opening = upcoming.groupBy (show) -> show.openingThisWeek()


:guardsman: Filters null marker state on restoration

--- lib/color-buffer.coffee
+++ lib/color-buffer.coffee
@@ -54,7 +54,9 @@
     @initializePromise

   restoreMarkersState: (variableMarkers, colorMarkers) ->
-    @variableMarkers = variableMarkers.map (state) =>
+    @variableMarkers = variableMarkers
+    .filter (state) -> state?
+    .map (state) =>
       bufferRange = Range.fromObject(state.bufferRange)
       marker = @editor.getMarker(state.markerId) ? @editor.markBufferRange(bufferRange, {
         type: 'pigments-variable'
@@ -64,7 +66,9 @@
       variable.bufferRange ?= bufferRange
       @variableMarkersByMarkerId[marker.id] = new VariableMarker {marker, variable}

-    @colorMarkers = colorMarkers.map (state) =>
+    @colorMarkers = colorMarkers
+    .filter (state) -> state?
+    .map (state) =>
       marker = @editor.getMarker(state.markerId) ? @editor.markBufferRange(state.bufferRange, {
         type: 'pigments-color'
         invalidate: 'touch'


Prevent unnecessary rerendering

Prevent unnecessary rerendering

--- lib/linter-views.coffee
+++ lib/linter-views.coffee
@@ -108,7 +108,7 @@
     unless @tabs[@state.scope]?.visibility
       @state.scope = @tabPriority.filter((key) => @tabs[key].visibility)[0]

-    @changeTab(@state.scope)
+    @changeTab(@state.scope, false)

   # consumed in editor-linter, renderPanel
   updateBubble: (point) ->
@@ -159,7 +159,7 @@
       item: @bottomStatus,
       priority: -999

-  changeTab: (tabName) ->
+  changeTab: (tabName, render = true) ->
     @showPanel = not @tabs[tabName]?.active
     if not @showPanel
       for key, tab of @tabs
@@ -168,7 +168,7 @@
       @state.scope = tabName
       for key, tab of @tabs
         tab.active = tabName is key
-      @renderPanelMessages()
+      @renderPanelMessages() if render
     @setShowPanel @showPanel

   removeBubble: ->


:bug: Fix message rendering for non-editor views

--- lib/linter-views.coffee
+++ lib/linter-views.coffee
@@ -170,12 +170,13 @@
   # This method is called in render, and classifies the messages according to scope
   _extractMessages: (Gen, counts) ->
     isProject = @_scope is 'project'
-    activeFile = atom.workspace.getActiveTextEditor()?.getPath?()
+    activeEditor = atom.workspace.getActiveTextEditor()
+    activeFile = activeEditor?.getPath?()
     Gen.forEach (Entry) =>
       # Entry === Array<Messages>
       Entry.forEach (message) =>
         # If there's no file prop on message and the panel scope is file then count is as current
-        if (not message.filePath and not isProject) or message.filePath is activeFile
+        if activeEditor and ((not message.filePath and not isProject) or message.filePath is activeFile)
           counts.file++
           counts.project++
           message.currentFile = true


Update minimap whenever theme is reloaded

Fixes the case where the minimap is positioned incorrectly if you have something in your `styles.less` that alters the tab bar height. It also fixes a similar possible case where changing the theme would do the same.

--- lib/minimap-view.coffee
+++ lib/minimap-view.coffee
@@ -125,6 +125,11 @@
     config = childList: true
     @observer.observe @paneView.element, config

+    # Update the minimap whenever theme is reloaded
+    @subscriptions.add atom.themes.onDidReloadAll =>
+      @adjustTopPosition()
+      @updateMinimapView()
+
     # The resize:end event is dispatched at the end of an animated resize
     # to not flood the cpu with updates.
     @subscribe $(window), 'resize:end', @onScrollViewResized


Don’t override opacity when setting fill or stroke color

Closes #329.

--- lib/mixins/color.coffee
+++ lib/mixins/color.coffee
@@ -6,20 +6,20 @@
     @_opacityRegistry = {}
     @_opacityCount = 0
     @_gradCount = 0
-
+
   _normalizeColor: (color) ->
     if color instanceof PDFGradient
       return color
-
+
     if typeof color is 'string'
       if color.charAt(0) is '#'
         color = color.replace(/#([0-9A-F])([0-9A-F])([0-9A-F])/i, "#$1$1$2$2$3$3") if color.length is 4
         hex = parseInt(color.slice(1), 16)
         color = [hex >> 16, hex >> 8 & 0xff, hex & 0xff]
-
+
       else if namedColors[color]
         color = namedColors[color]
-
+
     if Array.isArray color
       # RGB
       if color.length is 3
@@ -27,10 +27,10 @@

       # CMYK
       else if color.length is 4
-        color = (part / 100 for part in color)
-
+        color = (part / 100 for part in color)
+
       return color
-
+
     return null

   _setColor: (color, stroke) ->
@@ -45,34 +45,34 @@
     else
       space = if color.length is 4 then 'DeviceCMYK' else 'DeviceRGB'
       @_setColorSpace space, stroke
-
+
       color = color.join ' '
       @addContent "#{color} #{op}"
-
+
     return yes
-
+
   _setColorSpace: (space, stroke) ->
     op = if stroke then 'CS' else 'cs'
     @addContent "/#{space} #{op}"

-  fillColor: (color, opacity = 1) ->
+  fillColor: (color, opacity) ->
     set = @_setColor color, no
     @fillOpacity opacity if set
-
-    # save this for text wrapper, which needs to reset
+
+    # save this for text wrapper, which needs to reset
     # the fill color on new pages
     @_fillColor = [color, opacity]
     return this

-  strokeColor: (color, opacity = 1) ->
+  strokeColor: (color, opacity) ->
     set = @_setColor color, yes
     @strokeOpacity opacity if set
     return this
-
+
   opacity: (opacity) ->
      @_doOpacity opacity, opacity
      return this
-
+
   fillOpacity: (opacity) ->
      @_doOpacity opacity, null
      return this
@@ -91,7 +91,7 @@
      if @_opacityRegistry[key]
        [dictionary, name] = @_opacityRegistry[key]
      else
-       dictionary =
+       dictionary =
          Type: 'ExtGState'

        dictionary.ca = fillOpacity if fillOpacity?
@@ -105,13 +105,13 @@

      @page.ext_gstates[name] = dictionary
      @addContent "/#{name} gs"
-
+
   linearGradient: (x1, y1, x2, y2) ->
     return new PDFLinearGradient(this, x1, y1, x2, y2)
-
+
   radialGradient: (x1, y1, r1, x2, y2, r2) ->
     return new PDFRadialGradient(this, x1, y1, r1, x2, y2, r2)
-
+
 namedColors =
   aliceblue: [240, 248, 255]
   antiquewhite: [250, 235, 215]


Dock: persistent apps reordered

Dock: persistent apps reordered

--- client/Main/maincontroller/dockcontroller.coffee
+++ client/Main/maincontroller/dockcontroller.coffee
@@ -4,12 +4,11 @@

   defaultItems = [
     { title : 'Activity',  path : '/Activity', order : 10, type :'persistent' }
-    { title : 'IDE',       path : '/IDE',      order : 40, type :'persistent' }
-    { title : 'Editor',    path : '/Ace',      order : 41, type :'persistent' }
-    { title : 'Terminal',  path : '/Terminal', order : 30, type :'persistent' }
-    { title : 'Teamwork',  path : '/Teamwork', order : 20, type :'persistent' }
-    { title : 'Apps',      path : '/Apps',     order : 50, type :'persistent' }
-    { title : 'DevTools',  path : '/DevTools', order : 60, type :'persistent' }
+    { title : 'IDE',       path : '/IDE',      order : 20, type :'persistent' }
+    { title : 'Teamwork',  path : '/Teamwork', order : 30, type :'persistent' }
+    { title : 'Apps',      path : '/Apps',     order : 40, type :'persistent' }
+    { title : 'DevTools',  path : '/DevTools', order : 50, type :'persistent' }
+    # { title : 'Environments',  path : '/Environments', order : 60, type :'persistent' }
   ]

@@ -56,9 +55,11 @@
       title        : 'vms'
       items        : []

-
-    {mainController} = KD.singletons
-
+    @vmsList.getListView().on 'VMCogClicked', (vm)->
+      {mainView} = KD.singletons
+      mainView.openVMModal vm
+
+    # {mainController} = KD.singletons
     # mainController.ready @bound 'accountChanged'

     @trackStateTransitions()
@@ -216,16 +217,18 @@
     @setNavItems defaultItems
     @emit 'ready'

-    {vmController} = KD.singletons
-
-    vmController.fetchVMs no, (err, vms) =>
-      if err
-        ErrorLog.create 'terminal: Couldn\'t fetch vms', reason : err
-        return new KDNotificationView title : 'Couldn\'t fetch your VMs'
-
-      vms.sort (a,b) -> a.hostnameAlias > b.hostnameAlias
-
-      @listVMs vms
+    if KD.userVMs.length
+    then @listVMs KD.userVMs
+    else
+      {vmController} = KD.singletons
+      vmController.fetchVMs no, (err, vms) =>
+        if err
+          ErrorLog.create 'terminal: Couldn\'t fetch vms', reason : err
+          return new KDNotificationView title : 'Couldn\'t fetch your VMs'
+
+        vms.sort (a,b) -> a.hostnameAlias > b.hostnameAlias
+
+        @listVMs vms

     @ready =>


Main: revive account if it is not JAccount

--- client/Main/maincontroller/maincontroller.coffee
+++ client/Main/maincontroller/maincontroller.coffee
@@ -63,6 +63,7 @@
     @forwardEvents KD.remote, ['disconnected', 'reconnected']

   accountChanged:(account, firstLoad = no)->
+    account = KD.remote.revive account  unless account instanceof KD.remote.api.JAccount
     @userAccount             = account
     connectedState.connected = yes


show options option default to false

show options option default to false

--- heck.coffee
+++ heck.coffee
@@ -24,24 +24,24 @@
       # So that the pages can say more about what went wrong.
       res.writeHead data.code, "Content-Type": "text/html"
       map = plates.Map()
-      if data.options.debug
-        debugHTML  = data.options.debugBlock.start
-        debugHTML += data.stack
-        debugHTML += data.options.debugBlock.close
-        debugHTML += '
'
-        delete data.stack
-        debugHTML += data.options.debugBlock.start
-        debugHTML += JSON.stringify data, null, '  '
-        debugHTML += data.options.debugBlock.close
-        map.where('class').is(data.options.debugClass).partial debugHTML
       for item in data.options.debugLess.classes
         map.class(item).remove()
+      debugClass = data.options.debugClass
+      start = data.options.debugBlock.start
+      close = data.options.debugBlock.close
+      if data.options.debug
+        debugHTML = start + data.stack + close + '
'
+        delete data.stack
+        delete data.options unless data.show
+        debugHTML += start + (JSON.stringify data, null, '  ') + close
+        map.where('class').is(debugClass).partial debugHTML
       res.end (plates.bind plate.toString(), data, map)

 # The options: defaults and how they can be overridden.
 options = (input = {}) ->
   defaults =
+    show: false # these options when debug is true
     debug: if process.env.NODE_ENV is 'development' then true else false
     debugLess:
       classes: []


activity: add methods to switch between kd and react panes

--- client/activity/lib/activityappview.coffee
+++ client/activity/lib/activityappview.coffee
@@ -84,6 +84,20 @@
       pane?.refreshContent? path

+  switchToReactivityContainer: ->
+
+    @tabs.hide()
+    @setClass 'Reactivity'
+    @reactivityContainer.show()
+
+
+  switchToTabs: ->
+
+    @reactivityContainer.hide()
+    @unsetClass 'Reactivity'
+    @tabs.show()
+
+
   viewAppended: ->

     # see above for the terrible hack note - SY


client: remove migratefromsolo app

client: remove migratefromsolo app

--- client/app/lib/kodingappscontroller.coffee
+++ client/app/lib/kodingappscontroller.coffee
@@ -24,7 +24,6 @@
   kites: require 'kites'
   legal: require 'legal'
   members: require 'members'
-  migratefromsolo: require 'migratefromsolo'
   pricing: require 'pricing'
   showcase: require 'showcase'
   'stack-editor': require 'stack-editor'


Can update application in other branch than master without new version

--- client/app/views/config_application.coffee
+++ client/app/views/config_application.coffee
@@ -82,7 +82,9 @@
                 @startStopBtn.displayGrey t 'start this app'

         @updateIcon.toggle @model.get 'needsUpdate'
-        @$(".update-app").hide() unless @model.get('needsUpdate')
+        if (not @model.get("branch")? or @model.get 'branch' is 'master') and
+            not(@model.get('needsUpdate'))
+                @$(".update-app").hide()

         bool = @model.get 'isStoppable'
         @$('.app-stoppable').attr 'checked', bool


Update Price import create update method, remove reference resolution. Add summary method.

--- lib/price-import.coffee
+++ lib/price-import.coffee
@@ -4,6 +4,7 @@
 Promise = require 'bluebird'
 slugify = require 'underscore.string/slugify'
 {SphereClient, ProductSync} = require 'sphere-node-sdk'
+{Repeater} = require 'sphere-node-utils'
 ProductImport = require './product-import'

 class PriceImport extends ProductImport
@@ -12,6 +13,26 @@
     super @logger, options
     @skuOfPerformedPrices = []
     @sync.config [{type: 'prices', group: 'white'}].concat(['base', 'references', 'attributes', 'images', 'variants', 'metaAttributes'].map (type) -> {type, group: 'black'})
+    @repeater = new Repeater
+
+  _resetSummary: ->
+    @_summary =
+      unknownSKUCount: 0
+      created: 0
+      updated: 0
+
+  summaryReport: (filename) ->
+    if @_summary.created is 0 and @_summary.updated is 0
+      message = 'Summary: nothing to do, everything is fine'
+    else
+      message = "Summary: there were #{@_summary.created + @_summary.updated} imported prices " +
+        "(#{@_summary.created} were new and #{@_summary.updated} were updates)"
+
+    if @_summary.unknownSKUCount > 0
+      message += "\nFound #{@_summary.unknownSKUCount} unknown SKUs from file input"
+      message += " '#{filename}'" if filename
+
+    message

   _processBatches: (prices) ->
     batchedList = _.batchList(prices, 30) # max parallel elements to process
@@ -36,6 +57,39 @@
               when 200 then @_summary.updated++
           Promise.resolve(@_summary)
     ,{concurrency: 1}
+
+  _createOrUpdate: (productsToProcess, existingProducts) ->
+    debug 'Products to process: %j', {toProcess: productsToProcess, existing: existingProducts}
+
+    posts = _.map productsToProcess, (prodToProcess) =>
+      existingProduct = @_isExistingEntry(prodToProcess, existingProducts)
+      if existingProduct?
+        synced = @sync.buildActions(prodToProcess, existingProduct)
+        if synced.shouldUpdate()
+          updateTask = (payload) =>
+            @client.products.byId(synced.getUpdateId()).update(payload)
+          @repeater.execute ->
+            updateTask(synced.getUpdatePayload())
+          , (e) =>
+            if e.statusCode is 409
+              debug 'retrying to update %s because of 409', synced.getUpdateId()
+              newTask = =>
+                @client.productProjections.staged(true)
+                .byId(synced.getUpdateId())
+                .fetch()
+                .then (result) ->
+                  newPayload = _.extend {}, synced.getUpdatePayload(), {version: result.body.version}
+                  updateTask(newPayload)
+              Promise.resolve(newTask)
+            else Promise.reject(e) # do not retry in this case
+        else
+          Promise.resolve statusCode: 304
+      else
+        @_summary.unknownSKUCount++
+        Promise.resolve statusCode: 404
+
+    debug 'About to send %s requests', _.size(posts)
+    Promise.all(posts)

   _extractUniqueSkus: (prices) ->
     _.map prices, (p) ->


mixpanel: be a proper poor man

mixpanel: be a proper poor man

--- client/landing/app/MainApp/mixpanel.coffee
+++ client/landing/app/MainApp/mixpanel.coffee
@@ -130,5 +130,5 @@
         "$email"        : email
         "$created"      : createdAt
         "Status"        : type
-        "Randomizer"    : Math.floor((Math.random()*4)+1)
+        "Randomizer"    : KD.utils.getRandomNumber 4
       mixpanel.name_tag "#{nickname}.kd.io"


Always put mapper note at the top

Always put mapper note at the top

--- resources/assets/coffee/react/beatmap-discussions/discussions.coffee
+++ resources/assets/coffee/react/beatmap-discussions/discussions.coffee
@@ -187,8 +187,19 @@

   sortedDisussions: ->
-    discussions = @props.currentDiscussions[@props.mode].slice(0)
-    discussions.sort sortPresets[@currentSort()].sort
+    @props.currentDiscussions[@props.mode].slice().sort (a, b) =>
+      mapperNoteCompare =
+        # no sticky for timeline sort
+        @currentSort() != 'timeline' &&
+        # stick the mapper note
+        'mapper_note' in [a.message_type, b.message_type] &&
+        # but if both are mapper note, do base comparison
+        a.message_type != b.message_type
+
+      if mapperNoteCompare
+        if a.message_type == 'mapper_note' then -1 else 1
+      else
+        sortPresets[@currentSort()].sort(a, b)

   timelineCircle: =>


typed arrays do not support slicing, must be copied differently

--- subtree/bokehjs/static/coffee/common/plot.coffee
+++ subtree/bokehjs/static/coffee/common/plot.coffee
@@ -183,8 +183,16 @@

   map_to_screen : (x, x_units, y, y_units, units) ->
     if x_units == 'screen'
-      sx = x[..]
-      sy = y[..]
+      if _.isArray(x)
+        sx = x[..]
+      else
+        sx = new Float32Array(x.length)
+        sx.set(x)
+      if _.isArray(y)
+        sy = y[..]
+      else
+        sy = new Float32Array(y.length)
+        sy.set(y)
     else
       [sx, sy] = @mapper.v_map_to_target(x, y)

@@ -194,8 +202,18 @@
     return [sx, sy]

   map_from_screen : (sx, sy, units) ->
-    sx = @view_state.v_device_to_sx(sx[..])
-    sy = @view_state.v_device_to_sy(sy[..])
+    if _.isArray(sx)
+      dx = x[..]
+    else
+      dx = new Float32Array(sx.length)
+      sd.set(x)
+    if _.isArray(sy)
+      dy = y[..]
+    else
+      dy = new Float32Array(sy.length)
+      dy.set(y)
+    sx = @view_state.v_device_to_sx(dx)
+    sy = @view_state.v_device_to_sy(dy)

     if units == 'screen'
       x = sx
@@ -330,7 +348,7 @@
     super()
     window.render_count +=1
     #console.log("render #{@plotnum}", window.render_count)
-
+
     #newtime = new Date()
     # if @last_render
     #   console.log(newtime - @last_render)
@@ -507,4 +525,4 @@
     plot10.update_range({sdx:1, sdy:2, xr:{end:20, start:0}, yr:{start:-3, end:30}})), 600)
   end_time = new Date()
   console.log(end_time - start_time, "ms")
-
+


flushOriginSnapshot for JComments

flushOriginSnapshot for JComments

  when a JComment liked we need to flush the activity which comments belong to

--- workers/social/lib/social/traits/likeable.coffee
+++ workers/social/lib/social/traits/likeable.coffee
@@ -56,6 +56,7 @@
                     likesCount    : count
                     relationship  : docs[0]
                   }
+                @flushOriginSnapshot constructor
           else
             @removeLikedBy delegate, respondWithCount: yes, (err, count)=>
               if err
@@ -65,3 +66,15 @@
                 @update ($set: 'meta.likes': count), callback
                 delegate.update ($inc: 'counts.likes': -1), (err)->
                   console.log err if err
+                @flushOriginSnapshot constructor
+
+  flushOriginSnapshot:(constructor)->
+    if constructor.name is 'JComment'
+      Relationship.one
+        targetId: @getId()
+        as: 'reply'
+      , (err, rel)->
+        if not err and rel
+          rel.fetchSource (err, source)->
+            if not err and source
+              source.flushSnapshot?()


Activity: fixed topic pane message addition order

--- client/Social/Activity/views/topicmessagepane.coffee
+++ client/Social/Activity/views/topicmessagepane.coffee
@@ -5,11 +5,11 @@
     super

     KD.singletons.socialapi
-      .on 'MessageAdded', @bound 'addMessage'
+      .on 'MessageAdded',   @bound 'prependMessage'
       .on 'MessageRemoved', @bound 'removeMessage'

-  addMessage: (message) ->
+  prependMessage: (message) ->

     {name} = @getData()
     return  unless message.body.match ///##{name}///


Client: Activity: Updated onFollowChannel to add accountId post parametes

--- client/activity/lib/components/postpane/index.coffee
+++ client/activity/lib/components/postpane/index.coffee
@@ -1,5 +1,6 @@
 kd           = require 'kd'
 React        = require 'kd-react'
+whoami       = require 'app/util/whoami'
 immutable    = require 'immutable'
 ActivityFlux = require 'activity/flux'
 ChatPane     = require 'activity/components/chatpane'
@@ -42,7 +43,7 @@

   onFollowChannel: ->

-    ActivityFlux.actions.channel.followChannel @channel 'id'
+    ActivityFlux.actions.channel.followChannel @channel('id'), whoami()._id

   onClose: -> kd.singletons.router.handleRoute "/Channels/#{@channel 'name'}"


activity/flux: load message when popular message loaded

--- client/activity/lib/flux/stores/messagesstore.coffee
+++ client/activity/lib/flux/stores/messagesstore.coffee
@@ -29,6 +29,8 @@
   initialize: ->

     @on actions.LOAD_MESSAGE_SUCCESS, @handleLoadMessageSuccess
+    @on actions.LOAD_POPULAR_MESSAGE_SUCCESS, @handleLoadMessageSuccess
+
     @on actions.CREATE_MESSAGE_BEGIN, @handleCreateMessageBegin
     @on actions.CREATE_MESSAGE_SUCCESS, @handleCreateMessageSuccess
     @on actions.CREATE_MESSAGE_FAIL, @handleCreateMessageFail


closing over loop variable considered harmful

--- assets/js/teacher.coffee
+++ assets/js/teacher.coffee
@@ -37,15 +37,16 @@
             current_row.append $("

#{nick}

-                                       <textarea rows=10 class='span3' id='text-#{id}'></textarea>
+                                       <textarea rows=10 class='span12' id='text-#{id}'></textarea>

#{id}

                                     #{prog}

")
-            $("#nick-#{id}").toggle () ->
-                $("#text-container-#{id}").show("explode", 1000);
+            ((closed_id) ->
+              $("#nick-#{closed_id}").toggle () ->
+                $("#text-container-#{closed_id}").show("explode", 1000);
             , () ->
-                $("#text-container-#{id}").hide("explode", 1000);
+                $("#text-container-#{closed_id}").hide("explode", 1000);)(id);
     #also, delete any ids that are still client side but have disconnected from the server
      _.map $('.container').find('.student-box'),
           (el) ->


registration now considers if registration has been done from a group context, if the group is public it will automatically add the user to that group, if not it will create an invitation/access request

--- client/landing/app/MainApp/login/registerform.coffee
+++ client/landing/app/MainApp/login/registerform.coffee
@@ -178,6 +178,12 @@
           messages    :
             required  : "Please enter your invitation code."

+    @entryPoint = new LoginInputView
+      inputOptions    :
+        name          : 'entryPoint'
+        type          : 'hidden'
+        defaultValue  : KD.config.groupEntryPoint
+
     @on "SubmitFailed", (msg)=>
       if msg is "Wrong password"
         @passwordConfirm.input.setValue ''
@@ -331,4 +337,5 @@
     </div>
     <div>{{> @button}}</div>
     {{> @disabledNotice}}
+    {{> @entryPoint}}
     """


tip jar for dao.consider.it

tip jar for dao.consider.it

--- @client/customizations.coffee
+++ @client/customizations.coffee
@@ -3510,7 +3510,7 @@
                   fontSize: 46
                   fontWeight: 200
                   color: 'white'
-                  marginTop: 40
+                  marginTop: 20

                 'Deliberate Proposals about The DAO'
@@ -3519,13 +3519,12 @@
               DIV
                 style:
                   backgroundColor: 'rgba(255,255,255,.2)'
-                  marginTop: 5
-                  marginBottom: 20
+                  marginTop: 10
+                  marginBottom: 16
                   padding: '4px 12px'
                   float: 'right'
                   fontSize: 18
                   color: 'white'
-                  display: 'inline-block'

                 SPAN
                   style:
@@ -3542,10 +3541,56 @@

                   "#dao_consider_it"

+
               DIV
                 style:
                   clear: 'both'
-                  marginBottom: 120
+
+              DIV
+                style:
+                  float: 'right'
+                  fontSize: 12
+                  color: 'white'
+                  opacity: .9
+                  padding: '0px 10px'
+                  position: 'relative'
+
+                "Donate ETH to fuel our work evolving consider.it to meet The DAO’s needs."
+
+                # A
+                #   href: 'https://thedao.slack.com/messages/dao_consider_it/'
+                #   target: '_blank'
+                #   style:
+                #     #textDecoration: 'underline'
+                #     color: dao_yellow
+                #     fontWeight: 600
+
+                #   "#dao_consider_it"
+
+              DIV
+                style:
+                  clear: 'both'
+
+              DIV
+                style:
+                  #backgroundColor: 'rgba(255,255,255,.2)'
+                  #marginBottom: 20
+                  padding: '0px 10px'
+                  float: 'right'
+                  fontSize: 15
+                  fontWeight: 500
+                  #color: 'white'
+                  color: dao_yellow
+                  #border: "1px solid #{dao_yellow}"
+                  opacity: .8
+                  fontFamily: '"Courier New",Courier,"Lucida Sans Typewriter","Lucida Typewriter",monospace'
+                "0xc7e165ebdad9eeb8e5f5d94eef3e96ea9739fdb2"
+
+
+              DIV
+                style:
+                  clear: 'both'
+                  marginBottom: 70

               DIV


pep8 integration

pep8 integration

--- atom.symlink/config.cson
+++ atom.symlink/config.cson
@@ -49,6 +49,10 @@
     useGlobalEslint: true
   "linter-jshint":
     disableWhenNoJshintrcFileInPath: true
+  "linter-pep8":
+    ignoreErrorCodes: [
+      "E501"
+    ]
   metrics:
     userId: "f0927718331c072990cb750aafe7bbb7c24695ca"
   minimap:


atom config bump

atom config bump

--- atom.symlink/config.cson
+++ atom.symlink/config.cson
@@ -3,6 +3,7 @@
     'showIndentGuide': true
     'fontFamily': 'Menlo'
     'fontSize': 15
+    'invisibles': {}
   'core':
     'ignoredNames': [
       '.bundle'
@@ -32,4 +33,3 @@
     'showOnStartup': false
   'release-notes':
     'viewedVersion': '0.89.0'
-  'go-plus': {}


remove atom-prettier from config

remove atom-prettier from config

--- atom.symlink/config.cson
+++ atom.symlink/config.cson
@@ -30,17 +30,7 @@
     userId: "c48ac152-7102-ae12-c9d6-2aafd0240b51"
   "linter-ui-default":
     panelHeight: 81
-  "prettier-atom":
-    formatOnSaveOptions:
-      showInStatusBar: true
-      whitelistedGlobs: [
-        "*.js"
-      ]
-    prettierOptions:
-      printWidth: 100
-      singleQuote: true
-      trailingComma: "es5"
-    useEslint: true
+  mprettier: {}
   "reason-refmt":
     useLinter: false
   "tree-view":


updating atom config

updating atom config

--- atom.symlink/config.cson
+++ atom.symlink/config.cson
@@ -1,7 +1,7 @@
 "*":
   editor:
     fontFamily: "Menlo"
-    fontSize: 12
+    fontSize: 10
     showIndentGuide: true
     tabLength: 4
     invisibles: {}
@@ -19,9 +19,7 @@
       "background-tips"
       "exception-reporting"
       "metrics"
-      "linter-tidy"
-      "linter"
-      "merge-conflicts"
+      "omnisharp-atom"
     ]
     themes: [
       "unity-ui"
@@ -51,3 +49,7 @@
   "atom-beautify":
     _analyticsUserId: "6fe7c5c7-8c61-4c6c-ac3b-a4bd3fe33475"
   "terminal-status": {}
+  "autocomplete-plus": {}
+  "omnisharp-atom":
+    enhancedHighlighting: true
+    useLeftLabelColumnForSuggestions: true


Switching theme & font

Switching theme & font

--- atom.symlink/config.cson
+++ atom.symlink/config.cson
@@ -6,15 +6,15 @@
   'showOnStartup': false
 'metrics': {}
 'editor':
-  'fontFamily': 'Droid Sans Mono'
+  'fontFamily': 'Input Mono Narrow'
   'showIndentGuide': true
   'showInvisibles': true
   'lineHeight': 1.5
   'fontSize': 13
 'core':
   'themes': [
-    'atom-dark-ui'
-    'base16-tomorrow-dark-theme'
+    'seti-ui'
+    'seti-syntax'
   ]
   'projectHome': '~/Sites'
   'disabledPackages': [
@@ -35,6 +35,7 @@
     'archive-view'
     'feedback'
     'sublime-tabs'
+    'atom-color-highlight'
   ]
 'autosave': {}
 'spell-check':


Add keyboard shortcut for sort lines

Add keyboard shortcut for sort lines

--- atom.symlink/keymap.cson
+++ atom.symlink/keymap.cson
@@ -23,3 +23,6 @@
 # This file uses CoffeeScript Object Notation (CSON).
 # If you are unfamiliar with CSON, you can read more about it here:
 # https://github.com/bevry/cson#what-is-cson
+
+'atom-text-editor:not([mini])':
+  'alt-s': 'sort-lines:sort'


mapper_type attribute added to switch to appropriate mapper for ticker

--- bokehjs/src/coffee/common/plot.coffee
+++ bokehjs/src/coffee/common/plot.coffee
@@ -10,11 +10,12 @@
   "./has_parent",
   "./view_state",
   "mapper/1d/linear_mapper",
+  "mapper/1d/log_mapper",
   "mapper/1d/categorical_mapper",
   "mapper/2d/grid_mapper",
   "renderer/properties",
   "tool/active_tool_manager",
-], (_, Backbone, require, build_views, safebind, bulk_save, ContinuumView, HasParent, ViewState, LinearMapper, CategoricalMapper, GridMapper, Properties, ActiveToolManager) ->
+], (_, Backbone, require, build_views, safebind, bulk_save, ContinuumView, HasParent, ViewState, LinearMapper, LogMapper, CategoricalMapper, GridMapper, Properties, ActiveToolManager) ->

   line_properties = Properties.line_properties
   text_properties = Properties.text_properties
@@ -126,21 +127,41 @@
       @x_range = options.x_range ? @mget_obj('x_range')
       @y_range = options.y_range ? @mget_obj('y_range')

-      xmapper_type = LinearMapper.Model
-      if @x_range.type == "FactorRange"
-        xmapper_type = CategoricalMapper.Model
-      @xmapper = new xmapper_type({
-        source_range: @x_range
-        target_range: @view_state.get('inner_range_horizontal')
-      })
-
-      ymapper_type = LinearMapper.Model
-      if @y_range.type == "FactorRange"
-        ymapper_type = CategoricalMapper.Model
-      @ymapper = new ymapper_type({
-        source_range: @y_range
-        target_range: @view_state.get('inner_range_vertical')
-      })
+      xmt = @mget('x_mapper_type')
+      if xmt == 'auto'
+        xmapper_type = LinearMapper.Model
+        if @x_range.type == "FactorRange"
+          xmapper_type = CategoricalMapper.Model
+        @xmapper = new xmapper_type({
+          source_range: @x_range
+          target_range: @view_state.get('inner_range_horizontal')
+        })
+      else
+        if xmt == 'log'
+          xmapper_type = LogMapper.Model
+        @xmapper = new xmapper_type({
+          source_range: @x_range
+          target_range: @view_state.get('inner_range_horizontal')
+        })
+
+
+      ymt = @mget('y_mapper_type')
+      if ymt == 'auto'
+        ymapper_type = LinearMapper.Model
+        if @y_range.type == "FactorRange"
+          ymapper_type = CategoricalMapper.Model
+        @ymapper = new ymapper_type({
+          source_range: @y_range
+          target_range: @view_state.get('inner_range_vertical')
+        })
+      else
+        if ymt == 'log'
+          ymapper_type = LogMapper.Model
+
+        @ymapper = new ymapper_type({
+          source_range: @y_range
+          target_range: @view_state.get('inner_range_vertical')
+        })

       @mapper = new GridMapper.Model({
         domain_mapper: @xmapper
@@ -467,6 +488,8 @@
         renderers: [],
         tools: [],
         title: 'Plot',
+        x_mapper_type: 'auto',
+        y_mapper_type: 'auto'
       }

     display_defaults: () ->


whatever

whatever

--- index.coffee
+++ index.coffee
@@ -75,7 +75,7 @@
     message.match = match
     runCLI(spawnedBot, message)
   else
-    spawnedBot.replyPrivate(message, "Usage: `#{CLI_HELP}`")
+    spawnedBot.reply(message, "Usage: `#{CLI_HELP}`")

 controller.hears [QUERY_REGEX], ['direct_mention'], (bot, message) ->
   runCLI(bot, message)
@@ -93,7 +93,6 @@
   context = new ReplyContext(looker, bot, message)
   runner = new CLIQueryRunner(context, query, type)
   runner.start()
-

 checkMessage = (bot, message) ->
   return if !message.text || message.subtype == "bot_message"


fix entry point

fix entry point

--- index.coffee
+++ index.coffee
@@ -1,5 +1,4 @@
 sync = require("./lib/sync")

-console.log("SYNC", sync)
-
-sync(process.cwd(), process.argv[process.argv.length - 1])
+repo = process.argv[process.argv.length - 1]
+sync(process.cwd(), repo)


Enable texture atlas (now with greedy meshing support). Closes GH-19

Massive performance increase!

Requires https://github.com/deathcap/voxel-texture/commit/a58b2878b23ad6d42d166cacb0f3a64f56bae073

--- index.coffee
+++ index.coffee
@@ -49,7 +49,7 @@
       exposeGlobal: true  # for debugging
       lightsDisabled: true
       arrayType: Uint16Array
-      useAtlas: false
+      useAtlas: true
       generateChunks: false
       chunkDistance: 2
       materials: []  # added dynamically later


added method setContentProvider

added method setContentProvider

--- index.coffee
+++ index.coffee
@@ -10,6 +10,10 @@

     if optionMap.contentProvider
       @contentProvider = @__validateContentProvider optionMap.contentProvider
+
+
+  setContentProvider: (contentProvider)->
+    @contentProvider = @__validateContentProvider contentProvider

   __validateContentProvider: (contentProvider)->


Whitespace

Whitespace

--- index.coffee
+++ index.coffee
@@ -5,22 +5,22 @@
 # Support toggling of global disabled state
 disabled = false
 export disable = -> disabled = true
-export enable = ->
+export enable = ->
 	disabled = false
 	update instance for id, instance of instances

 # Create instance after the element has been added to DOM
 startObserving = (el, binding) ->
-
+
 	# If an indvidual instance is disabled, just add the in viewport classes
 	# to reveal the element
 	if binding?.value?.disabled || directive.defaults.disabled || disabled
 		el.classList.add.apply el.classList, [ 'in-viewport' ]
 		return

-	# Create the instance object
+	# Create the instance object
 	instance = observer: makeObserver el, binding
-
+
 	# Generate a unique id that will be store in a data value on the element
 	id = 'i' + counter++
 	el.setAttribute 'data-in-viewport', id
@@ -28,34 +28,34 @@

 # Make the instance
 makeObserver = (el, { value = {}, modifiers }) ->
-
+
 	# Make the default root
 	root = value.root || directive.defaults.root
 	root = switch typeof root
 		when 'function' then root()
 		when 'string' then document.querySelector root
 		when 'object' then root # Expects to be a DOMElement
-
+
 	# Make the default margin
 	margin = if typeof value == 'string' then value
 	else value.margin || directive.defaults.margin
-
+
 	# Make the observer callback
 	callback = ([entry]) -> update { el, entry, modifiers }
-
+
 	# Make the observer instance
 	observer = new IntersectionObserver callback,
 		root: root
 		rootMargin: margin
 		threshold: [0,1]
-
+
 	# Start observing the element and return the observer
 	observer.observe el
 	return observer

 # Update element classes based on current intersection state
 update = ({ el, entry, modifiers }) ->
-
+
 	# Destructure the entry to just what's needed
 	{ boundingClientRect: target, rootBounds: root } = entry


Adds support for the Squirrels no MSI option

--- index.coffee
+++ index.coffee
@@ -136,6 +136,9 @@
           setupIconPath = path.resolve(config.setupIcon)
           args.push '--setupIcon'
           args.push setupIconPath
+
+        if config.noMsi
+          args.push '--no-msi'

         exec {cmd, args}, (error) ->
           return done(error) if error?


Begin implementing detecting config file and boot

Signed-off-by: Wyatt Calandro <96804f3875bc364d25340989c9f5960c99a00fd9@arm1stice.com>

--- index.coffee
+++ index.coffee
@@ -1,17 +1,29 @@
 # Import all the stuffs
 e = require 'electron'
 app = e.app
+fs = require 'fs'
+pathExists = require 'path-exists'
+path = require 'path'
 BrowserWindow = e.BrowserWindow

 # Here we goooo
 app.once 'ready', ->
-  console.log "ready"
-  mainWindow = new BrowserWindow {
-    width: 100
-    height: 100
-    show: false
-  }
-  console.log __dirname
-  mainWindow.loadURL "file://#{__dirname}/views/index.html"
-  mainWindow.webContents.once 'did-finish-load', ->
-    mainWindow.show()
+  mainWindow = null;
+  ###
+    Initial run - We have to copy the default config file
+  ###
+  if not pathExists.sync path.join __dirname, "../../../config.json"
+    #fs.createReadStream(path.join __dirname, "../../../config.default.json").pipe(fs.createWriteStream(path.join __dirname, "../../../config.json"))
+    mainWindow = new BrowserWindow {
+      width: 500
+      height: 150
+      show: false
+      resizable: false
+      title: "Setup LP-Tracker"
+    }
+    mainWindow.loadURL "file://#{__dirname}/views/initial.html"
+    mainWindow.webContents.once 'did-finish-load', ->
+      mainWindow.show()
+  else
+    # In this scenario, the api key is already set and the application is set up already, so we just open the main window
+    console.log 'exists'


just root :-/

just root :-/

--- index.coffee
+++ index.coffee
@@ -67,10 +67,10 @@
   app.post opts.twitterLastStepPath, submitTwitterLastStep
   app.use headerLogin
   app.use addLocals
-  app.get '*', ensureEmailFromTwitterSignup
+  app.get '/', ensureEmailFromTwitterSignup

 ensureEmailFromTwitterSignup = (req, res, next) ->
-  return next() if not req.user? or req.url is opts.twitterLastStepPath
+  return next() unless req.user?
   tmpSuffix = _.last(opts.twitterSignupTempEmail('').split('@'))
   if req.user.get('email').match tmpSuffix
     res.redirect opts.twitterLastStepPath


Add measure calculation (in progress)

Add measure calculation (in progress)

--- app/assets/javascripts/map_editor.js.coffee
+++ app/assets/javascripts/map_editor.js.coffee
@@ -11,6 +11,271 @@
 #    $(this._getInnerContentContainer()).find('.modal-body > :first-child').replaceWith($content)
     $(this._getInnerContentContainer()).find('.modal-body').append($(content))
     this.update()
+
+  # Portage from leaflet1.0.0-rc1: https://github.com/Leaflet/Leaflet/blob/master/src/layer/vector/Polygon.js
+  # Return true centroid
+  L.Polygon.include
+
+    __getCenter: ->
+
+      @projectLatlngs()
+      @__project()
+      points = @_rings[0]
+      len = points.length
+      if !len
+        return null
+      # polygon centroid algorithm; only uses the first ring if there are multiple
+      area = x = y = 0
+      i = 0
+      j = len - 1
+      while i < len
+        p1 = points[i]
+        p2 = points[j]
+        f = p1.y * p2.x - (p2.y * p1.x)
+        x += (p1.x + p2.x) * f
+        y += (p1.y + p2.y) * f
+        area += f * 3
+        j = i++
+      if area == 0
+        # Polygon is so small that all points are on same pixel.
+        center = points[0]
+      else
+        center = [
+          x / area
+          y / area
+        ]
+      @_map.layerPointToLatLng center
+
+  L.Polyline.include
+    getLatLngsAsArray: ->
+      arr = []
+      for latlng in @_latlngs
+        arr.push [latlng.lat, latlng.lng]
+      arr
+    # Portage from leaflet1.0.0-rc1: https://github.com/Leaflet/Leaflet/blob/master/src/layer/vector/Polyline.js
+    # Return true centroid
+    __getCenter: ->
+      @projectLatlngs()
+      @__project()
+      i = undefined
+      halfDist = undefined
+      segDist = undefined
+      dist = undefined
+      p1 = undefined
+      p2 = undefined
+      ratio = undefined
+      points = @_rings[0]
+      len = points.length
+      if !len
+        return null
+      # polyline centroid algorithm; only uses the first ring if there are multiple
+      i = 0
+      halfDist = 0
+      while i < len - 1
+        halfDist += points[i].distanceTo(points[i + 1]) / 2
+        i++
+      # The line is so small in the current view that all points are on the same pixel.
+      if halfDist == 0
+        return @_map.layerPointToLatLng(points[0])
+      i = 0
+      dist = 0
+      while i < len - 1
+        p1 = points[i]
+        p2 = points[i + 1]
+        segDist = p1.distanceTo(p2)
+        dist += segDist
+        if dist > halfDist
+          ratio = (dist - halfDist) / segDist
+          return @_map.layerPointToLatLng([
+            p2.x - (ratio * (p2.x - (p1.x)))
+            p2.y - (ratio * (p2.y - (p1.y)))
+          ])
+        i++
+      return
+
+    __project: ->
+      pxBounds = new (L.Bounds)
+      @_rings = []
+      @__projectLatlngs @_latlngs, @_rings, pxBounds
+      w = @__clickTolerance()
+      p = new (L.Point)(w, w)
+      if @getBounds().isValid() and pxBounds.isValid()
+        pxBounds.min._subtract p
+        pxBounds.max._add p
+        @_pxBounds = pxBounds
+      return
+
+    # recursively turns latlngs into a set of rings with projected coordinates
+    __projectLatlngs: (latlngs, result, projectedBounds) ->
+      flat = latlngs[0] instanceof L.LatLng
+      len = latlngs.length
+      i = undefined
+      ring = undefined
+      if flat
+        ring = []
+        i = 0
+        while i < len
+          ring[i] = @_map.latLngToLayerPoint(latlngs[i])
+          projectedBounds.extend ring[i]
+          i++
+        result.push ring
+      else
+        i = 0
+        while i < len
+          @__projectLatlngs latlngs[i], result, projectedBounds
+          i++
+      return
+
+    __clickTolerance: ->
+      # used when doing hit detection for Canvas layers
+      (@options.stroke ? @options.weight / 2 : 0) + (L.Browser.touch ? 10 : 0)
+
+  L.Draw.Polyline.include
+    __addHooks: L.Draw.Polyline.prototype.addHooks
+    __removeHooks: L.Draw.Polyline.prototype.removeHooks
+
+    __onMouseMove: (e) ->
+      return unless @_markers.length > 0
+      newPos = @_map.mouseEventToLayerPoint(e.originalEvent)
+      mouseLatLng = @_map.layerPointToLatLng(newPos)
+
+      latLngArray = []
+      for latLng in @_poly.getLatLngs()
+        latLngArray.push latLng
+      latLngArray.push mouseLatLng
+
+      # draw a polyline
+      if @_markers.length == 1
+        clone = L.polyline(latLngArray)
+
+      # draw a polygon
+      if @_markers.length >= 2
+
+#        console.log("The distance is " + L.GeographicUtil.distance(lastLatLng, mouseLatLng.toArray()) + " m.")
+
+        clone = L.polygon(latLngArray)
+
+      clone._map = @_map
+      center = clone.__getCenter()
+
+      g = new L.GeographicUtil.Polygon(clone.getLatLngsAsArray())
+
+      measure = {}
+      measure['perimeter'] = g.perimeter()
+      measure['area'] = g.area()
+      console.log measure
+
+      @__updateTooltipMeasure center, measure
+
+
+    addHooks: () ->
+      @__addHooks.apply this, arguments
+      @__tooltipMeasure = new L.Tooltip @_map, onTop: true
+      @_map.on 'mousemove', @__onMouseMove, this
+      return
+
+    removeHooks: () ->
+      @_map.off 'mousemove'
+      @__removeHooks.apply this, arguments
+      return
+
+    __updateTooltipMeasure: (latLng, measure = {}) ->
+      showLength = @options.showLength
+      labelText =
+        text: ''
+      distanceStr = undefined
+      # TODO: use L.drawLocal to i18n tooltip
+      ###if @_markers.length == 0
+        labelText = text: L.drawLocal.draw.handlers.polyline.tooltip.start
+      else
+        distanceStr = if showLength then @_getMeasurementString() else ''
+        if @_markers.length == 1
+          labelText =
+            text: L.drawLocal.draw.handlers.polyline.tooltip.cont
+            subtext: distanceStr
+        else
+          labelText =
+            text: L.drawLocal.draw.handlers.polyline.tooltip.end
+            subtext: distanceStr###
+      if measure['perimeter']
+        labelText['text'] += "#{measure['perimeter']}"
+        console.log labelText
+
+
+      #TODO clean html with class
+      if measure['area']
+        labelText['text']  += "#{measure['area']}"
+
+        console.log labelText
+
+      if latLng
+        @__tooltipMeasure.updatePosition latLng
+#      if !@_errorShown
+        @__tooltipMeasure.updateContent labelText
+      return
+
+  L.Edit.Poly.include
+    __addHooks: L.Edit.Poly.prototype.addHooks
+
+    __onHandlerDrag: (e) ->
+      new_poly = e.target
+      latLngs = new_poly.getLatLngs()
+      geod = GeographicLib.Geodesic.WGS84
+
+      poly = geod.Polygon(false)
+      for latlng in latLngs
+        poly.AddPoint latlng['lat'], latlng['lng']
+
+      poly = poly.Compute(false, true)
+      console.log 'Perimeter/area of polygon are ' + poly.perimeter.toFixed(3) + ' m / ' + poly.area.toFixed(1) + ' m^2.'
+
+    addHooks: () ->
+      @__addHooks.apply this, arguments
+      if @options.reactiveMeasure
+        this._poly.on 'editdrag', @__onHandlerDrag, this
+
+
+  L.Edit.Poly.mergeOptions
+    reactiveMeasure: true
+
+  L.LatLng.prototype.toArray = ->
+    [@lat, @lng]
+
+  L.Tooltip.include
+    __initialize: L.Tooltip.prototype.initialize
+
+    initialize: (map,options = {}) ->
+      @__initialize.apply this, arguments
+
+      if options.onTop
+        L.DomUtil.addClass(@_container, 'leaflet-draw-tooltip-top')
+
+
+
+  L.GeographicUtil = L.extend L.GeographicUtil || {},
+    geod: GeographicLib.Geodesic.WGS84
+
+# Use Karney distance formula
+# ([lat, lng], [lat, lng]) -> Number (in meters)
+    distance: (a, b) ->
+      r = @geod.Inverse(a[0], a[1], b[0], b[1])
+      r.s12.toFixed(3)
+
+    Polygon: (points) -> # (Array of [lat,lng] pair)
+      @geod = GeographicLib.Geodesic.WGS84
+      @poly = @geod.Polygon(false)
+      for point in points
+        @poly.AddPoint point[0], point[1]
+
+      @poly = @poly.Compute(false, true)
+      return
+
+  L.GeographicUtil.Polygon.prototype =
+    perimeter: ->
+      @poly.perimeter
+    area: ->
+      @poly.area
+

   $.widget "ui.mapeditor",
@@ -54,7 +319,7 @@
             circle: false
             polygon:
               allowIntersection: false
-              showArea: true
+              showArea: false
         zoom:
           position: "topleft"
           zoomInText: ""
@@ -117,6 +382,8 @@

       this.counter = 1

+      @geod = GeographicLib.Geodesic.WGS84
+
       this.map.on "draw:created", (e) =>
         #Attempt to add a geojson feature
         try
@@ -140,6 +407,19 @@
           widget.element.trigger 'mapeditor:feature_delete', layer.feature

         widget.update()
+
+      this.map.on "draw:drawvertex", (e) =>
+        console.log 'draw', e.layers.getLayers()
+        poly = @geod.Polygon(false)
+        e.layers.eachLayer (layer) =>
+          latlng = layer.getLatLng()
+          poly.AddPoint latlng['lat'], latlng['lng']
+
+        poly = poly.Compute(false, true)
+        console.log 'Perimeter/area of polygon are ' + poly.perimeter.toFixed(3) + ' m / ' + poly.area.toFixed(1) + ' m^2.'
+
+      this.map.on "draw:editvertex", (e) ->
+        console.log 'edit', e.layers

       this._resize()
       # console.log "resized"
@@ -538,6 +818,7 @@
         this.controls.fullscreen = new L.Control.FullScreen(this.options.controls.fullscreen)
         this.map.addControl this.controls.fullscreen
       if this.edition?
+        console.log this.options.controls.draw
         this.controls.draw = new L.Control.Draw($.extend(true, {}, this.options.controls.draw, {edit: {featureGroup: this.edition}}))
         this.map.addControl this.controls.draw
       unless this.options.controls.scale is false


Verbose init migration

Verbose init migration

--- init.coffee
+++ init.coffee
@@ -189,7 +189,7 @@

 migrateTables = ->
 	dbConnection = createAnyDBConnection(config.DATABASE_URL)
-	lib.migration.migrate.sync null, dbConnection
+	lib.migration.migrate.sync null, dbConnection, {verbose: true}

 optimize = ->


fix jnorthpole script bug

fix jnorthpole script bug

--- spec/examples/json.northpole.ro/public/javascripts/jnorthpole.coffee
+++ spec/examples/json.northpole.ro/public/javascripts/jnorthpole.coffee
@@ -21,7 +21,7 @@

     r.onreadystatechange = ->
       return if r.readyState != 4
-      if status == 200
+      if r.status == 200
         responseHandler(JSON.parse(r.responseText), r.status)
       else
         errorHandler(JSON.parse(r.responseText), r.status)
@@ -34,18 +34,22 @@
     @genericRequest(jsonObj, 'POST', 'user', success, failure)
     return

+  getUser: (jsonObj, responseHandler, errorHandler) ->
+    @genericRequest(jsonObj, 'SEARCH', 'user', responseHandler, errorHandler)
+    return
+
   createStorage: (jsonObj, responseHandler, errorHandler) ->
     @genericRequest(jsonObj, 'POST', 'storage', responseHandler, errorHandler)
+    return
+
+  getStorage: (jsonObj, responseHandler, errorHandler) ->
+    @genericRequest(jsonObj, 'SEARCH', 'storage', responseHandler, errorHandler)
     return

   putStorage: (jsonObj, responseHandler, errorHandler) ->
     @genericRequest(jsonObj, 'PUT', 'storage', responseHandler, errorHandler)
     return

-  getStorage: (jsonObj, responseHandler, errorHandler) ->
-    @genericRequest(jsonObj, 'SEARCH', 'storage', responseHandler, errorHandler)
-    return
-
   deleteStorage: (jsonObj, responseHandler, errorHandler) ->
     @genericRequest(jsonObj, 'DELETE', 'storage', responseHandler, errorHandler)
     return


dialog if email already taken + clear form after submit

--- app/components/admin-page/admin-page.coffee
+++ app/components/admin-page/admin-page.coffee
@@ -79,9 +79,16 @@

     $scope.newGroupAdmin = Records.users.build()
     $scope.inviteGroup = ->
-      $scope.newGroupAdmin.save().then ->
-        Dialog.alert
-          title: 'Invite sent!'
+      $scope.newGroupAdmin.save()
+        .then ->
+          Dialog.alert
+            title: 'Success!'
+            content: "Your invite was sent."
+        .catch ->
+          Dialog.alert
+            title: 'Error!'
+            content: 'That email is already taken.'
+      $scope.newGroupAdmin = Records.users.build()

     $scope.uploadPathForGroup = (groupId) ->
       "#{config.apiPrefix}/allocations/upload?group_id=#{groupId}"


bumping version to 1.1

bumping version to 1.1

--- src/sc.coffee
+++ src/sc.coffee
@@ -1,5 +1,5 @@
 window.SC = SC.Helper.merge SC || {},
-  _version: "1.0.13"
+  _version: "1.1.0"
   options:
     baseUrl: "http://connect.soundcloud.com"
     site: "soundcloud.com"


fix formula for calculating marker positions

--- src/ui.coffee
+++ src/ui.coffee
@@ -68,7 +68,7 @@
     for segment in segments
       length = turf.lineDistance(segment, 'kilometers')
       for n in d3.range(0, d3.round(length / interval))
-        point = turf.along(segment, n * interval + 0.5, 'kilometers')
+        point = turf.along(segment, interval * (n + 0.5), 'kilometers')
         [x, y] = projection(point.geometry.coordinates)
         if x > 0 and x < width and y > 0 and y < height
           symbols.push(


:fire: Remove jQuery in drag-and-drop listeners

--- lib/tab-bar-view.coffee
+++ lib/tab-bar-view.coffee
@@ -42,11 +42,11 @@
       'tabs:split-left': => @splitTab('splitLeft')
       'tabs:split-right': => @splitTab('splitRight')

-    @on 'dragstart', '.sortable', @onDragStart
-    @on 'dragend', '.sortable', @onDragEnd
-    @on 'dragleave', @onDragLeave
-    @on 'dragover', @onDragOver
-    @on 'drop', @onDrop
+    @element.addEventListener "dragstart", @onDragStart
+    @element.addEventListener "dragend", @onDragEnd
+    @element.addEventListener "dragleave", @onDragLeave
+    @element.addEventListener "dragover", @onDragOver
+    @element.addEventListener "drop", @onDrop

     @paneContainer = @pane.getContainer()
     @addTabForItem(item) for item in @pane.getItems()
@@ -235,6 +235,8 @@
     (@paneContainer.getPanes().length > 1) or (@pane.getItems().length > 1)

   onDragStart: (event) =>
+    return unless event.target.classList.contains('sortable')
+
     event.originalEvent.dataTransfer.setData 'atom-event', 'true'

     element = $(event.target).closest('.sortable')
@@ -279,6 +281,8 @@
     @removePlaceholder()

   onDragEnd: (event) =>
+    return unless event.target.classList.contains('sortable')
+
     @clearDropTarget()

   onDragOver: (event) =>