Updating help videos

Updating help videos

Moving dungeons-of-kithgard to Vimeo
Adding original style for haunted-kithmaze

--- app/lib/LevelOptions.coffee
+++ app/lib/LevelOptions.coffee
@@ -2,7 +2,7 @@
   'dungeons-of-kithgard':
     disableSpaces: true
     helpVideos: [
-      {style: 'original', URL: '//www.youtube.com/embed/bHaeKdMPZrA'}
+      {style: 'original', URL: '//player.vimeo.com/video/114921603'}
       {style: 'scripted', URL: '//player.vimeo.com/video/114729726'}
       {style: 'eccentric', URL: '//player.vimeo.com/video/114729725'}
       {style: 'edited', URL: '//player.vimeo.com/video/114729724'}
@@ -118,6 +118,7 @@
     requiredCode: ['loop']
   'haunted-kithmaze':
     helpVideos: [
+      {style: 'original', URL: '//player.vimeo.com/video/114921605'}
       {style: 'scripted', URL: '//player.vimeo.com/video/114730074'}
       {style: 'eccentric', URL: '//player.vimeo.com/video/114729727'}
       {style: 'edited', URL: '//player.vimeo.com/video/114729723'}


extract event listeners to seperate funcs

--- app/lib/canvas-graph.coffee
+++ app/lib/canvas-graph.coffee
@@ -93,13 +93,16 @@
 class Mark
   constructor: (e, @canvasGraph) ->
     #TODO: make an active state
+
+    @canvas = @canvasGraph.canvas
+
     @element = document.createElement('div')
     @element.className = "mark"

     @element.style.left = e.x + "px"
     @element.style.position =  'absolute'
     @element.style.top = e.target.offsetTop + "px"
-    @element.style.height = @canvasGraph.canvas.height - 13 + 'px' # subtract border height
+    @element.style.height = @canvas.height - 13 + 'px' # subtract border height
     @element.style.backgroundColor = 'rgba(255,0,0,.5)'
     @element.style.border =  '2px solid red'
     @element.style.borderTop =  '13px solid red'
@@ -107,34 +110,11 @@
     @startingPoint = e.x
     @dragging = false

-    @element.addEventListener 'mousedown', (e) =>
-      # resizing
-      if (Math.abs e.layerX - (@domXMax-@domXMin)) < 12
-        @startingPoint = @domXMin
-        @dragging = true
-      else if e.layerX < 12
-        @startingPoint = @domXMax
-        @dragging = true
-      else if e.layerY > 15
-        @moving = true
-        @movingStart = e.x
-
-    @element.addEventListener 'mousemove', (e) =>
-      @draw(e) if @dragging
-      @move(e) if @moving
-
-    @element.addEventListener 'mouseup', (e) =>
-      if @dragging
-        @canvasGraph.marks.add(@)
-        document.getElementById('points').innerHTML += "x1: #{@dataXMin}, x2: #{@dataXMax}
"
-        console.log "Marks", @canvasGraph.marks
-        @dragging = false
-      else if @moving
-        @save(@domXMin, @domXMax)
-        @moving = false
-
-    @element.addEventListener 'click', (e) =>
-      @canvasGraph.marks.remove(@) if e.layerY < 15
+    @element.addEventListener 'mouseover', (e) => @hovering = true
+    @element.addEventListener 'mousedown', (e) => @onMouseDown(e)
+    @element.addEventListener 'mousemove', (e) => @onMouseMove(e)
+    @element.addEventListener 'mouseup', (e) => @onMouseUp(e)
+    @element.addEventListener 'click', (e) => @canvasGraph.marks.remove(@) if e.layerY < 15

   draw: (e) ->
     markLeftX = Math.min @startingPoint, e.x
@@ -166,4 +146,40 @@
     @dataXMin = @canvasGraph.toDataXCoord(@canvasXMin)
     @dataXMax = @canvasGraph.toDataXCoord(@canvasXMax)

+  updateCursor: (e) ->
+    if ((Math.abs e.layerX - (@domXMax-@domXMin)) < 10) || e.layerX < 10
+      @element.style.cursor = "ew-resize"
+    else if e.layerY < 15
+      @element.style.cursor = "pointer"
+    else
+      @element.style.cursor = "move"
+
+  onMouseMove: (e) ->
+    @draw(e) if @dragging
+    @move(e) if @moving
+    @updateCursor(e) if @hovering
+
+  onMouseDown: (e) ->
+    if (Math.abs e.layerX - (@domXMax-@domXMin)) < 10
+      @startingPoint = @domXMin
+      @dragging = true
+    else if e.layerX < 10
+      @startingPoint = @domXMax
+      @dragging = true
+    else if e.layerY > 15
+      @moving = true
+      @movingStart = e.x
+
+  onMouseUp: (e) ->
+    if @dragging
+      @canvasGraph.marks.add(@)
+      document.getElementById('points').innerHTML += "x1: #{@dataXMin}, x2: #{@dataXMax}
"
+      console.log "Marks", @canvasGraph.marks
+      @dragging = false
+    else if @moving
+      console.log "OLD SP", @startingPoint
+      @save(@domXMin, @domXMax)
+      console.log "NEW SP", @startingPoint
+      @moving = false
+
 module?.exports = CanvasGraph: CanvasGraph, Marks: Marks, Mark: Mark


Added sayLabelOptions groundLayer:boolean to put texts on the game layer (#6537)

--- app/lib/surface/Lank.coffee
+++ app/lib/surface/Lank.coffee
@@ -654,7 +654,8 @@
       d.updatePosition()

   addLabel: (name, style, labelOptions={}) ->
-    @labels[name] ?= new Label sprite: @, camera: @options.camera, layer: @options.textLayer, style: style, labelOptions: labelOptions
+    layer = if labelOptions.groundLayer then @options.groundLayer else @options.textLayer
+    @labels[name] ?= new Label sprite: @, camera: @options.camera, layer: layer, style: style, labelOptions: labelOptions
     @labels[name]

   addMark: (name, layer, thangType=null) ->


better select by slug

better select by slug

--- app/models/Institute.coffee
+++ app/models/Institute.coffee
@@ -24,15 +24,15 @@
       Institute.trigger("refresh")

-
   setupArchives:(archives)=>
     console.log "setting up archives ", archives
     for archive in archives
       @archives().create(archive)

   @findBySlug:(slug)=>
-    @select (institute)=>
+    result =@select (institute)=>
       institute.slug() is slug
+    result[0]

   slug:->
     @name.replace /\s/g, "_"


Remove extra class

Remove extra class

--- app/pages/user-profile.cjsx
+++ app/pages/user-profile.cjsx
@@ -6,27 +6,6 @@
 {Link} = ChildRouter
 Markdown = require '../components/markdown'
 LoadingIndicator = require '../components/loading-indicator'
-
-UserHeader = React.createClass
-  displayName: 'UserHeader'
-
-  render: ->
-    if @props.user?
-
User OK

-    else
-
User not found

-
-  OLD_RENDER: ->
-
-UserDetails = React.createClass
-  displayName: 'UserDetails'
-
-  mixins: [
-    usersStore.mixInto -> user: usersStore.get @props.login
-  ]
-
-  render: ->
-

 module.exports = React.createClass
   displayName: 'UserProfilePage'


Add Request factory

Add Request factory

--- app/scripts/services.coffee
+++ app/scripts/services.coffee
@@ -16,6 +16,8 @@
     apiResource $resource, 'waterpoints'
   .factory 'Facility', ($resource) ->
     apiResource $resource, 'facilities'
+  .factory 'Request', ($resource) ->
+    apiResource $resource, 'requests'
   .factory 'Service', ($resource) ->
     apiResource $resource, 'services'
   .factory 'Map', (Waterpoint) ->


Add update method using PUT to resource factory

--- app/scripts/services.coffee
+++ app/scripts/services.coffee
@@ -8,6 +8,8 @@
     query:
       method: 'GET'
       isArray: false
+    update:
+      method: 'PUT'

 angular.module('taarifaWaterpointsApp')
   .factory 'Waterpoint', ($resource) ->


Add link to submit request to map marker

--- app/scripts/services.coffee
+++ app/scripts/services.coffee
@@ -35,7 +35,10 @@
           # group: p.district
           lat: p.latitude
           lng: p.longitude
-          message: "#{p.wpt_code}
Status: #{p.status}
edit"
+          message: "#{p.wpt_code}
" +
+            "Status: #{p.status}
" +
+            "edit
" +
+            "submit request"
       # This would keep loading further waterpoints as long as there are any.
       # Disabled for performance reasons
       # if waterpoints._links.next


This should be a major migration.

This should be a major migration.

--- server/migrations/0005-created-rename.coffee
+++ server/migrations/0005-created-rename.coffee
@@ -1,4 +1,4 @@
-class Migration extends Document.MinorMigration
+class Migration extends Document.MajorMigration
   name: "Renaming created and updated fields to createdAt and updatedAt"

   forward: (db, collectionName, currentSchema, newSchema, callback) =>


this is not atom

this is not atom

--- src/ui/views/menu.coffee
+++ src/ui/views/menu.coffee
@@ -8,7 +8,7 @@
         { type: 'separator' }
         # { label: 'Preferences...', accelerator: 'Command+,', click: => delegate.openConfig() }
         { type: 'separator' }
-        { label: 'Hide Atom', accelerator: 'Command+H', selector: 'hide:' }
+        { label: 'Hide YakYak', accelerator: 'Command+H', selector: 'hide:' }
         { label: 'Hide Others', accelerator: 'Command+Shift+H', selector: 'hideOtherApplications:' }
         { label: 'Show All', selector: 'unhideAllApplications:' }
         { type: 'separator' }


fixes children values not appearing in bar tooltips

--- src/viz/types/bar.coffee
+++ src/viz/types/bar.coffee
@@ -108,6 +108,17 @@
   unless vars.axes.discrete
     axis = if vars.time.value is vars.y.value then "y" else "x"
     vars.self[axis] scale: "discrete"
+
+  y    = vars[vars.axes.opposite]
+  size = vars.size
+
+  if (not y.value and size.value) or
+     (size.changed and size.previous is y.value)
+    vars.self[vars.axes.opposite] size.value
+  else if (not size.value and y.value) or
+          (y.changed and y.previous is size.value)
+    vars.self.size y.value
+
 bar.shapes       = ["square"]

 module.exports = bar


Fix failing unit tests

Fix failing unit tests

--- services/web/test/unit/coffee/History/HistoryControllerTests.coffee
+++ services/web/test/unit/coffee/History/HistoryControllerTests.coffee
@@ -122,7 +122,7 @@
 			@res =
 				json: sinon.stub()
 			@next = sinon.stub()
-			@request.yields(null, {}, @data = "mock-data")
+			@request.yields(null, {statusCode: 200}, @data = "mock-data")
 			@HistoryManager.injectUserDetails = sinon.stub().yields(null, @data_with_users = "mock-injected-data")

 		describe "for a project with the project history flag", ->


equation builder adds ability for "fractions" to be evaluated

--- javascripts/lib/math/expression_evaluation.coffee
+++ javascripts/lib/math/expression_evaluation.coffee
@@ -87,6 +87,19 @@
             throw new MalformedExpressionError("Invalid Expression")

       });
+
+    refinement.forType(comps.classes.fraction,
+      {
+        eval: (evaluation, pass)->
+          return if pass != "multiplication"
+          num = refinement.refine(@numerator()).eval().toCalculable()
+          denom = refinement.refine(@denominator()).eval().toCalculable()
+          if num && denom
+            comps.build_number(value: precise.div(num, denom))
+          else
+            throw new MalformedExpressionError("Invalid Expression")
+      }
+    )

     @refinement_val = refinement


Refactor: rename.

Refactor: rename.

--- init.coffee
+++ init.coffee
@@ -120,11 +120,11 @@

 info = () ->
 	mysqlConnection = createAnyDBConnection(config.MYSQL_DATABASE_URL)
-	result = utils.migration.getCurrentRevision.sync(null, mysqlConnection)
+	current = utils.migration.getCurrentRevision.sync(null, mysqlConnection)
 	newest = utils.migration.getNewestRevision()
-	status = if result < newest then 'needs update' else 'up to date'
+	status = if current < newest then 'needs update' else 'up to date'
 	console.log "init.js with #{newest + 1} revisions on board."
-	console.log "Current revision is #{result} (#{status})."
+	console.log "Current revision is #{current} (#{status})."
 	process.exit 0


Core#send

Core#send

--- test/core_object.coffee
+++ test/core_object.coffee
@@ -74,6 +74,8 @@
   equals o.test(), "my_val"

 test "#send", ->
+  o = Core()

+  o.

 module undefined


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- test/flickerable.coffee
+++ test/flickerable.coffee
@@ -8,6 +8,7 @@

   equals player.I.alpha, 0.9

-
+  player.flicker
+    durat

 module()


Add spec for default value

Add spec for default value

--- spec/InPort.coffee
+++ spec/InPort.coffee
@@ -35,3 +35,21 @@
       chai.expect(p.isConnected()).to.equal false
     it 'should not contain a socket initially', ->
       chai.expect(p.sockets.length).to.equal 0
+
+  describe 'with default value', ->
+    p = s = null
+    beforeEach ->
+      p = new inport
+        default: 'default-value'
+      s = new socket
+      p.attach s
+    it 'should send the default value as a packet, though on next tick after initialization', (done) ->
+      p.config.on 'data', (data) ->
+        chai.expect(data).toEqual 'default-value'
+        done()
+    it 'should send the default value before IIP', (done) ->
+      received = ['default-value', 'some-iip']
+      p.config.on 'data', (data) ->
+        chai.expect(data).toEqual received.shift()
+        done() if received.length is 0
+      s.send 'some-iip'


Troubleshoot: Troubleshoot modal items are changed with TroubleshootItemView instances

--- client/Main/avatararea/avatarareaiconmenu.coffee
+++ client/Main/avatararea/avatarareaiconmenu.coffee
@@ -127,24 +127,30 @@
     @update status

   update: (response) ->
-    @destroySubViews()
-    @bongo = new KDCustomHTMLView
-      partial : "Bongo Server Status : #{response?.bongo.status}"
+    @bongo = new TroubleshootItemView
+      title: "Bongo"
+    , @getData()["bongo"]

-    @broker = new KDCustomHTMLView
-      partial : "Broker Status : #{response?.broker.status}"
+    @broker = new TroubleshootItemView
+      title : "Broker"
+    , @getData()["broker"]

-    @kiteBroker = new KDCustomHTMLView
-      partial : "Kite-Broker Status : #{response?.kiteBroker.status}"
+    @kiteBroker = new TroubleshootItemView
+      title : "Kite-Broker"
+    , @getData()["kiteBroker"]

-    @osKite = new KDCustomHTMLView
-      partial : "OS-Kite Status : #{response?.osKite.status}"
+    @osKite = new TroubleshootItemView
+      title : "OS-Kite"
+    , @getData()["osKite"]

-    @webServer = new KDCustomHTMLView
-      partial : "Webserver Status : #{response?.webServer?.status}"
+    @webServer = new TroubleshootItemView
+      title : "Webserver"
+    , @getData()["webServer"]

-    @connection = new KDCustomHTMLView
-      partial : "Internet Connection Status : #{response?.connection?.status}"
+    @connection = new TroubleshootItemView
+      title : "Internet Connection"
+    , @getData()["connection"]
+

     @addSubView @bongo
     @addSubView @broker


ContextMenu: Add all possible items into context menu

--- client/Social/Activity/views/settingsview.coffee
+++ client/Social/Activity/views/settingsview.coffee
@@ -23,51 +23,41 @@
   settingsMenu:(post)->
     account        = KD.whoami()
     activityController = KD.getSingleton("activityController")
+    menu = {}
     if post.originId is account.getId()
-      menu =
-        'Edit Post'           :
-          callback       : =>
-            @emit 'ActivityEditIsClicked'
-        'Delete'         :
-          callback       : =>
-            @confirmDeletePost post
+        menu['Edit Post'] =
+          callback: => @emit 'ActivityEditIsClicked'
+        menu['Delete Post'] =
+          callback: => @confirmDeletePost post

       if KD.checkFlag("super-admin") or KD.hasAccess("delete posts")
         menu['Add System Tag'] =
-        callback : =>
-          @selectSystemTag post
-
-      return menu
+          callback : => @selectSystemTag post

     if KD.checkFlag("super-admin") or KD.hasAccess("delete posts")
       if KD.checkFlag 'exempt', account
-        menu =
-          'Unmark User as Troll' :
+        menu['Unmark User as Troll'] =
             callback             : ->
               activityController.emit "ActivityItemUnMarkUserAsTrollClicked", post
       else
-        menu =
-          'Mark User as Troll' :
+        menu['Mark User as Troll'] =
             callback           : ->
               activityController.emit "ActivityItemMarkUserAsTrollClicked", post

       menu['Delete Post'] =
-        callback : =>
-          @confirmDeletePost post
+        callback : => @confirmDeletePost post

       menu['Edit Post'] =
-        callback : =>
-          @emit 'ActivityEditIsClicked'
+        callback : => @emit 'ActivityEditIsClicked'

       menu['Block User'] =
         callback : ->
           activityController.emit "ActivityItemBlockUserClicked", post.originId

       menu['Add System Tag'] =
-        callback : =>
-          @selectSystemTag post
+        callback : => @selectSystemTag post

-      return menu
+    return menu

   viewAppended: -> @addSubView @settings


updateembedbox: fix logic depending on embed existence

--- client/activity/lib/mixins/updateembedbox.coffee
+++ client/activity/lib/mixins/updateembedbox.coffee
@@ -8,17 +8,17 @@
 module.exports = updateEmbedBoxMixin = ->

   data     = @getData()
-  embedBox = if data.link?
-
+  embedBox = null
+  if hasEmbed data
     { link_embed } = data.link
     embedType      = (getEmbedType link_embed?.type) or 'link'
     isEmptyLink    = embedType is 'link' and not link_embed?.description

-    if isEmptyLink
+    embedBox = if isEmptyLink
     then new KDCustomHTMLView
     else new EmbedBox @embedOptions, data.link

-  else new KDCustomHTMLView
+  else embedBox = new KDCustomHTMLView

   @embedBoxWrapper.destroySubViews()
   @embedBoxWrapper.addSubView embedBox


Refactor "IDELayoutManager"

Refactor "IDELayoutManager"

--- client/ide/lib/workspace/idelayoutmanager.coffee
+++ client/ide/lib/workspace/idelayoutmanager.coffee
@@ -46,7 +46,7 @@
     @layout.push
       type      : 'split',
       direction : if parent.vertical is true then 'vertical' else 'horizontal'
-      views     : @getSubLevels parent
+      views     : @drillDown parent

   ###*
@@ -54,95 +54,91 @@
    *
    * @param {KDSplitViewPanel} splitViewPanel
   ###
-  getSubLevels: (splitViewPanel) ->
-
-    subViews = []
-
-    ###*
-     * Create a split view object item.
-     *
-     * @param {string} direction
-     * @param {boolean} isFirst
-     * @param {Object} parentView
-    ###
-    createSplitView = (panel, isFirst = no, parentView) ->
-
-      item =
-        type      : 'split'
-        direction : if panel.vertical then 'vertical' else 'horizontal'
-        isFirst   : isFirst
-        views     : []
-
-      if parentView
-      then parentView.views.push item
-      else subViews.push item
-
-      getSubLevel panel
-
-
-    ###*
-     * Get/find last split object in data structure.
-     *
-     * <Recursive>
-     * @param {Object} items
-    ###
-    findLastSplitView = (items = subViews) ->
-
-      return  if _.isEmpty items
-      return  if items.last.context
-
-      lastViews = items.last.views
-
-      if lastViews.length is 0 or (lastViews.length > 0 and lastViews.last.context)
-        return items.last
+  drillDown: (splitViewPanel) ->
+
+    @subViews = []
+    @getSubLevel splitViewPanel
+    return @subViews
+
+
+  ###*
+   * Get/find last split object in data structure.
+   *
+   * <Recursive>
+   * @param {Object} items
+  ###
+  findLastSplitView: (items) ->
+
+    return  if _.isEmpty(items) or items.last.context
+
+    lastViews = items.last.views
+
+    if lastViews.length is 0 or (lastViews.length > 0 and lastViews.last.context)
+      return items.last
+    else
+      @findLastSplitView lastViews
+
+
+  ###*
+   * Search in views and create sub levels.
+   *
+   * <Recursive>
+   * @param {(KDSplitViewPanel|KDSplitView|IDEView|KDTabPaneView|IDEApplicationTabView)} target
+   * @return {Array} subViews
+  ###
+  getSubLevel: (target) ->
+
+    if target instanceof KDSplitViewPanel
+      @getSubLevel target.getSubViews().first
+
+    else if target instanceof KDSplitView
+
+      { panels } = target
+
+      if panels.length is 2
+        splitView = @findLastSplitView @subViews
+
+        @createSplitView panels.first, splitView, yes
+        @createSplitView panels.last,  splitView
       else
-        findLastSplitView lastViews
-
-
-    ###*
-     * Search in views and create sub levels.
-     *
-     * <Recursive>
-     * @param {(KDSplitViewPanel|KDSplitView|IDEView|KDTabPaneView|IDEApplicationTabView)} target
-     * @param {KDSplitViewPanel} nextSplitViewPanel
-     * @return {Array} subViews
-    ###
-    getSubLevel = (target) ->
-
-      if target instanceof KDSplitViewPanel
-        getSubLevel target.getSubViews().first
-
-      else if target instanceof KDSplitView
-
-        { panels } = target
-
-        if panels.length is 2
-          splitView = findLastSplitView()
-
-          createSplitView panels.first, yes,  splitView
-          createSplitView panels.last,  no,   splitView
-        else
-          getSubLevel panels.first
-
-      else if target instanceof IDEView
-
-        for pane in target.tabView.panes
-          getSubLevel pane
-
-      else if target instanceof KDTabPaneView
-        return  unless target.view.serialize
-
-        pane = context : target.view.serialize()
-        last = findLastSplitView()
-
-        if last
-        then last.views.push pane
-        else subViews.push pane
-
-
-    getSubLevel splitViewPanel
-
-    return subViews
+        @getSubLevel panels.first
+
+    else if target instanceof IDEView
+
+      for pane in target.tabView.panes
+        @getSubLevel pane
+
+    else if target instanceof KDTabPaneView
+      return  unless target.view.serialize
+
+      pane = context : target.view.serialize()
+      last = @findLastSplitView @subViews
+
+      if last
+      then last.views.push pane
+      else @subViews.push pane
+
+
+  ###*
+   * Create a split view object item.
+   *
+   * @param {string} direction
+   * @param {Object} parentView
+   * @param {boolean} isFirst
+  ###
+  createSplitView: (panel, parentView, isFirst = no) ->
+
+    item =
+      type      : 'split'
+      direction : if panel.vertical then 'vertical' else 'horizontal'
+      isFirst   : isFirst
+      views     : []
+
+    if parentView
+    then parentView.views.push item
+    else @subViews.push item
+
+    @getSubLevel panel

   ###*
@@ -152,29 +148,33 @@
   ###
   resurrectSnapshot: (snapshot) ->

+    delegate = @getDelegate()
+
     # if has the fake view
-    @delegate.mergeSplitView()  if @delegate.ideViews.length > 1
-
-    @delegate.splitTabView snapshot[1].direction  if snapshot[1]
+    delegate.mergeSplitView()  if delegate.ideViews.length > 1
+
+    delegate.splitTabView snapshot[1].direction  if snapshot[1]

     for key, value of snapshot
-      tabView = @delegate.ideViews[key]?.tabView
+      tabView = delegate.ideViews[key]?.tabView
       @resurrectPanes_ value.views, tabView

-    @delegate.isLocalSnapshotRestored = yes
+    delegate.isLocalSnapshotRestored = yes

   resurrectPanes_: (items, tabView) ->

+    delegate = @getDelegate()
+
     for key, value of items

-      @delegate.setActiveTabView tabView
+      delegate.setActiveTabView tabView

       if value.type is 'split'

         if value.isFirst isnt yes
-          @delegate.splitTabView value.direction
-          tabView = @delegate.ideViews.last.tabView
+          delegate.splitTabView value.direction
+          tabView = delegate.ideViews.last.tabView

         if value.views.length
           do (value, tabView) =>
@@ -184,7 +184,7 @@
         # Don't use `active tab view` logic for new pane creation.
         # Because `The Editors` (saved editors) are loading async.
         value.targetTabView = tabView  if value.context.paneType is 'editor'
-        @delegate.createPaneFromChange value, yes
+        delegate.createPaneFromChange value, yes

   ###*


MachineStateModal: make stateLabel as a separate getter

--- client/ide/views/modals/machinestatemodal.coffee
+++ client/ide/views/modals/machinestatemodal.coffee
@@ -132,7 +132,7 @@
     @createError()

-  createStateLabel: (customState)->
+  getStateLabel:->

     stateTexts       =
       Stopped        : 'is turned off.'
@@ -149,10 +149,14 @@
       NotFound       : 'This machine does not exist.' # additional class level state to show a modal for unknown routes.

     stateText = "#{@machineName or ''} #{stateTexts[@state]}"
+    return "#{stateText}"
+
+
+  createStateLabel: (customState)->

     @label     = new KDCustomHTMLView
       tagName  : 'p'
-      partial  : customState or "#{stateText}"
+      partial  : customState or @getStateLabel()
       cssClass : "state-label #{@state.toLowerCase()}"

     @container.addSubView @label


add tests for #120

add tests for #120

--- test/select.test.coffee
+++ test/select.test.coffee
@@ -265,12 +265,34 @@
         assert.same @inst.toParam(), { "text": "SELECT * FROM schools INNER JOIN (SELECT * FROM (SELECT * FROM students WHERE (age = ?))) `meh` ON (meh.ID = ID) WHERE (school_type = ?)", "values": [6,'junior'] }
         assert.same @inst.toParam({ "numberedParametersStartAt": 1}), { "text": "SELECT * FROM schools INNER JOIN (SELECT * FROM (SELECT * FROM students WHERE (age = $1))) `meh` ON (meh.ID = ID) WHERE (school_type = $2)", "values": [6,'junior'] }

-  'cloning': ->
-    newinst = @inst.from('students').limit(10).clone()
-    newinst.limit(20)
-
-    assert.same 'SELECT * FROM students LIMIT 10', @inst.toString()
-    assert.same 'SELECT * FROM students LIMIT 20', newinst.toString()
+  'cloning':
+    'basic': ->
+      newinst = @inst.from('students').limit(10).clone()
+      newinst.limit(20)
+
+      assert.same 'SELECT * FROM students LIMIT 10', @inst.toString()
+      assert.same 'SELECT * FROM students LIMIT 20', newinst.toString()
+
+    'with expressions (ticket #120)': ->
+      expr = squel.expr().and('a = 1')
+      newinst = @inst.from('table').left_join('table_2', 't', expr)
+        .clone()
+        .where('c = 1')
+
+      expr.and('b = 2')
+
+      assert.same 'SELECT * FROM table LEFT JOIN table_2 `t` ON (a = 1 AND b = 2)', @inst.toString()
+      assert.same 'SELECT * FROM table LEFT JOIN table_2 `t` ON (a = 1) WHERE (c = 1)', newinst.toString()
+
+    'with sub-queries (ticket #120)': ->
+      newinst = @inst.from(squel.select().from('students')).limit(30)
+        .clone()
+        .where('c = 1')
+        .limit(35)
+
+      assert.same 'SELECT * FROM (SELECT * FROM students) LIMIT 30', @inst.toString()
+      assert.same 'SELECT * FROM (SELECT * FROM students) WHERE (c = 1) LIMIT 35', newinst.toString()
+

   'is nestable': ->
     assert.same true, @inst.isNestable()


Update fields that are shown

Update fields that are shown

--- client/skr/components/CustomerProjectFinder.cjsx
+++ client/skr/components/CustomerProjectFinder.cjsx
@@ -15,11 +15,14 @@
             new Lanes.Models.Query({
                 syncOptions:
                     include: [ 'customer', 'sku' ]
+                    with: ['with_details']
+                title: 'Customer Project'
                 src: Skr.Models.CustomerProject, fields: [
-                    {id:'id', visible: false}
+                    { id:'id', visible: false     }
                     { id: 'code', fixedWidth: 120 }
-                    { id: 'description', flex: 1 }
-                    { id: 'notes', flex: 1.5 }
+                    { id: 'po_num', flex: 1       }
+                    { id: 'description', flex: 2  }
+                    { id: 'customer_code', fixedWidth: 120 }
                 ]
             })


Rewortk the coffeescript to handle vanilla geojson messages

--- client/src/main/coffee/javascripts/simple.coffee
+++ client/src/main/coffee/javascripts/simple.coffee
@@ -46,14 +46,14 @@
 handleGeoJsonPacket = (map, v) ->
   #console.log v
   color = 'green'
-  if( v.type == 'Person' )
+  if( v.properties.resource_type == 'Person' )
     color = 'blue'
-  else if( v.type == 'Slip-On' )
+  else if( v.properties.resource_type == 'Slip-On' )
     color = 'yellow'
-  else if( v.type == 'Tanker' )
+  else if( v.properties.resource_type == 'Tanker' )
     color = 'red'
   current_time = new Date().getTime()
-  feature_time = new Date(v.updated_at).getTime()
+  feature_time = new Date(v.properties.updated_at).getTime()
   time_diff_in_minutes = Math.ceil((current_time - feature_time)/1000/60)
   time_diff_in_hours = Math.floor((current_time - feature_time)/1000/60/60)
   time_diff_in_days = Math.floor((current_time - feature_time)/1000/60/60/24)
@@ -63,7 +63,7 @@
     time_diff = time_diff_in_hours + " hours ago"
     if time_diff_in_hours == 0
       time_diff = time_diff_in_minutes + " minutes ago"
-  layer = L.geoJson(v.geo.features[0], {
+  layer = L.geoJson(v, {
     pointToLayer: (feature, latlon) ->
       markerIcon = L.icon
         iconUrl: 'images/marker-icon.png'
@@ -72,7 +72,7 @@
     style: (feature) ->
       return {color: color};
     onEachFeature: (feature, layer) ->
-      layer.bindPopup v.type + " - " + v.title + "
(#{time_diff})"
+      layer.bindPopup v.properties.resource_type + " - " + v.properties.title + "
(#{time_diff})"
   })
   if( !TopLayers[v.id] )
     TopLayers[v.id] = []
@@ -88,7 +88,7 @@
   handleDownloadCallback = (response) ->
     $('div.messages').empty()

-    response.forEach (v) ->
+    response.features.forEach (v) ->
       handleGeoJsonPacket(map, v)
   $.get '/fgis/api/resource', {}, handleDownloadCallback, 'json'


Test server: handle <message/> stanzas

Test server: handle <message/> stanzas

--- test/test_server.coffee
+++ test/test_server.coffee
@@ -298,5 +298,18 @@
                         iq = @makeIq("result", stanza.attrs.to, stanza.attrs.from, id)
                             .cnode(queryEl)
                         @emit "stanza", iq.root()
+
+            when "message"
+                stanza.attrs.should.have.property "from"
+                stanza.attrs.should.have.property "to"
+
+                to = stanza.attrs.to
+                if to of @messages
+                    @messages[to].push stanza
+                else
+                    @messages[to] = [stanza]
+
+                @emit "got-message-#{to}", stanza
+
             else
                 @emit "got-stanza", stanza


payment: implement billing history

payment: implement billing history

--- client/landing/app/Applications/Account.kdapplication/views/paymenthistory.coffee
+++ client/landing/app/Applications/Account.kdapplication/views/paymenthistory.coffee
@@ -14,20 +14,31 @@
     @removeAllItems()
     @showLazyLoader no

-    transactions = []
-    JPayment.fetchTransactions (err, trans=[]) =>
+    items = []
+    JPayment.fetchTransactions (err, transactions) =>
       warn err  if err

-      for t in trans when t.amount + t.tax > 0
-        {status, createdAt, card, cardType, cardNumber, owner, refundable} = t
-        amount = @utils.formatMoney (t.amount + t.tax) / 100
-        transactions.push {
-          status, cardType, cardNumber, owner, refundable, amount
-          currency : 'USD'
-          paidVia  : card or ''
-        }
+      for own paymentMethodId, transactionList of transactions
+        for transaction in transactionList when transaction.amount > 0

-      @instantiateListItems transactions
+          { status, createdAt, card, cardType, invoice
+            cardNumber, owner, refundable, type } = transaction
+
+          amount = @utils.formatMoney (transaction.amount + transaction.tax) / 100
+
+          items.push {
+            status
+            cardType
+            cardNumber
+            owner
+            refundable
+            amount
+            invoice
+            currency : 'USD'
+            paidVia  : card or ''
+          }
+
+      @instantiateListItems items
       @hideLazyLoader()

   loadView:->
@@ -67,6 +78,7 @@

   viewAppended:->
     super
+
     @addSubView editLink = new KDCustomHTMLView
       tagName      : 'a'
       partial      : 'View invoice'
@@ -91,6 +103,6 @@
       #{data.status.toUpperCase()}

-      ...#{data.cardNumber.split("...")[1]}
+      ...#{data.cardNumber}

     """


fullpieagent testing

fullpieagent testing

--- widgets/fullpie_agent/fullpie_agent.coffee
+++ widgets/fullpie_agent/fullpie_agent.coffee
@@ -192,7 +192,7 @@
         if !data
           return

-        piedata = [data[0]].label
+        piedata = data[0].[label]
         console.log("update pie", piedata)

         $(@node).children(".title").text($(@node).attr("data-title"))


vm: fix variable collision

vm: fix variable collision

--- workers/social/lib/social/models/vm.coffee
+++ workers/social/lib/social/models/vm.coffee
@@ -626,8 +626,11 @@
         return callback err  if err

         subscription = freeSubscription = null
-        for subscription in subscriptions
-          freeSubscription = subscription  if "nosync" in subscription.tags
+        for item in subscriptions
+          if "nosync" in item.tags
+            freeSubscription = item
+          else
+            subscription = item

         kallback subscription or freeSubscription
     else


payment: pack choice form should signal when activated

--- client/app/MainApp/payment/packchoiceform.coffee
+++ client/app/MainApp/payment/packchoiceform.coffee
@@ -15,6 +15,9 @@

     super()

+  activate: (activator) ->
+    @emit 'Activated', activator
+
   setContents: (contents) ->
     @listController.instantiateListItems contents


Resurrection: Return 1 if a or b is undefined or not set

--- client/ide/lib/workspace/idelayoutmanager.coffee
+++ client/ide/lib/workspace/idelayoutmanager.coffee
@@ -419,7 +419,7 @@

   getRatio: (a, b) ->

-    return 0  if not a or not b
+    return 1  if not a or not b
     return 1  if a is b
     return (a / b)


[module] it seems that shallow cloning is enough to prevent leaking in Nunjucks `config()`

--- modules/nunjucks-extensions.coffee
+++ modules/nunjucks-extensions.coffee
@@ -93,9 +93,8 @@
     if typeof value != 'object'
       return

-    # prevent leaking when merging Objects or Arrays
-    value = _.cloneDeep(value)
-    value = _.isArray(value) and _.union(value, ctxValue) or _.merge(value, ctxValue)
+    # Shallow cloning prevents leaking when merging
+    value = _.isArray(value) and _.union([], value, ctxValue) or _.merge({}, value, ctxValue)

     _.set(@ctx, prop, value)
     return


Renaming variable.

Renaming variable.

--- client/publication.coffee
+++ client/publication.coffee
@@ -586,19 +586,19 @@
     return # Make sure CoffeeScript does not return anything

 Template.publicationAnnotations.rendered = ->
-  $annotation = $(@findAll '.annotations-list')
-
-  $annotation.scrollLock()
+  $annotations = $(@findAll '.annotations-list')
+
+  $annotations.scrollLock()

   # We have to reset current left edge when re-rendering
-  $annotation.css
+  $annotations.css
     # To not crop the shadow of annotations we move the left edge
     # for 5px to the left and then add 5px (in fact 6px, so that
     # it looks better with our 1px shadow border) left margin to each
     # annotation. Same value is used in the _viewer.styl as well.
     left: $('.annotations-control').position().left - 5

-  $annotation.find('.invite .body, .local .body').balanceText()
+  $annotations.find('.invite .body, .local .body').balanceText()

   # If we leave z-index constant for all meta menu items
   # then because of the DOM order those later in the DOM
@@ -611,7 +611,7 @@
   BASE_Z_INDEX = 200
   # We have to search for meta menus globally to have
   # access to other meta menus of other annotations
-  $metaMenus = $annotation.find('.meta-menu')
+  $metaMenus = $annotations.find('.meta-menu')
   $metaMenus.each (i, metaMenu) =>
     $(metaMenu).css
       zIndex: BASE_Z_INDEX + $metaMenus.length - i


support .removeChild(int), not just (node)

--- dom.coffee
+++ dom.coffee
@@ -201,6 +201,8 @@
 			node._private.childIndex = @childNodes.length
 			@childNodes.push node
 	removeChild: (node) ->
+		if( typeof node is "number" )
+			node = @childNodes[node]
 		i = node._private.childIndex
 		if i > -1 and node.parentNode is @
 			node._private.parentNode = null


WebVR: Remove unpredict hack.

WebVR: Remove unpredict hack.

--- engine/webvr.coffee
+++ engine/webvr.coffee
@@ -8,9 +8,8 @@
             throw Error "There's a VR screen already"
         {
             use_room_scale_parent=true
-            use_unpredict=true
-            head
             mirror_zoom=1.3
+            head0
         } = options
         if use_room_scale_parent and not @scene.active_camera.parent?
             throw Error "use_room_scale_parent requires the camera to have a
@@ -33,7 +32,7 @@
         @left_orientation = quat.create()
         @right_orientation = quat.create()
         @last_time = performance.now()
-        @use_unpredict = use_unpredict and @HMD.displayName == 'OpenVR HMD'
+        console.log 'HMD is', @HMD.displayName
         if not /Oculus/.test @HMD.displayName
             @is_wmr = true # we'll set it to false when we detect velocity
         @sst = mat4.create()
@@ -168,17 +167,11 @@
             has_av = (avx or avy or avz)
             if has_av
                 @is_wmr = false
-            # WMR headsets seem to have zero velocity through SteamVR
-            # and shouldn't have prediction correction
-            use_unpredict = @use_unpredict and has_av
             m4 = mat4.create()
             m3 = mat3.create()
             {sst_inverse} = this
             time = performance.now()
             delta_frames = Math.min 10, (time - @last_time)/11.11111111
-            # it tries to predict one frame ahead using the difference between
-            # the last two frames, so we substract the prediction
-            unpredict = 1-(1/(delta_frames+1))

             mat4.copyArray m4, leftViewMatrix
             # mat4.rotateX m4, m4, Math.PI/2
@@ -186,8 +179,6 @@
             mat4.invert m4, m4
             mat3.fromMat4 m3, m4
             quat.fromMat3 r0, m3
-            if use_unpredict
-                quat.slerp r0, @left_orientation, r0, unpredict
             quat.copy @left_orientation, r0
             # TODO: Test with other headsets.
             @head_is_tracking =
@@ -200,8 +191,6 @@
             mat4.invert m4, m4
             mat3.fromMat4 m3, m4
             quat.fromMat3 r1, m3
-            if use_unpredict
-                quat.slerp r1, @right_orientation, r1, unpredict
             quat.copy @right_orientation, r1
             vec3.set p1, m4.m12, m4.m13, m4.m14


remove duplicated line

remove duplicated line

--- app/coffee/app.coffee
+++ app/coffee/app.coffee
@@ -619,8 +619,6 @@
             navigationBarService.disableHeader()
         else
             navigationBarService.enableHeader()
-
-pluginsWithModule = _.filter(@.taigaContribPlugins, (plugin) -> plugin.module)

 pluginsWithModule = _.filter(@.taigaContribPlugins, (plugin) -> plugin.module)


Fixing not found urls

Fixing not found urls

--- app/coffee/app.coffee
+++ app/coffee/app.coffee
@@ -62,7 +62,7 @@
             projectLoaded: ["$q", "tgProjectService", "$route", ($q, projectService, $route) ->
                 deferred = $q.defer()

-                projectService.setSection($route.current.$$route.section)
+                projectService.setSection($route.current.$$route?.section)

                 if $route.current.params.pslug
                     projectService.setProjectBySlug($route.current.params.pslug).then(deferred.resolve)


Chrome extension: include more story information

--- src/chromeExtension/components/020-story.cjsx
+++ src/chromeExtension/components/020-story.cjsx
@@ -14,8 +14,10 @@

   #-----------------------------------------------------
   render: ->
-    {fWrapper, title, records} = @props.story
+    {fWrapper, title, fServer, fOpen, records} = @props.story
     if fWrapper then return @_renderRecords records
+    title += if fServer then ' (server)' else ' (client)'
+    if not fOpen then title += ' - CLOSED'

       <ColoredText text={title}/>
       {@_renderRecords records}
@@ -27,8 +29,9 @@

   _renderRecord: (record, idx) ->
-    {id, msg} = record
+    {id, msg, fServer} = record
     if record.fStory then return <Story key={id} story={record}/>
+    msg += if fServer then ' (server)' else ' (client)'

       <ColoredText text={msg}/>


Get widgets from collection

Get widgets from collection

--- src/coffee/widget/rs-widget-collection.coffee
+++ src/coffee/widget/rs-widget-collection.coffee
@@ -68,6 +68,15 @@

       @

+    getWidget: () ->
+      if @widget.length > 0
+        return @widgets[0]
+
+      throw new Error('Widgets list is empty')
+
+    getWidgets: () ->
+      @widgets
+
     validate: () ->
       result = {}


ContextMenu: fixed the issue overriding width when max width was passed

--- src/components/contextmenu/contextmenu.coffee
+++ src/components/contextmenu/contextmenu.coffee
@@ -108,8 +108,10 @@
     mainHeight  = mainView.getHeight()
     mainWidth   = mainView.getWidth()

-    menuHeight  = @getHeight()
-    menuWidth   = @getWidth()
+    { menuWidth, menuHeight, menuMaxWidth, menuMinWidth } = options
+
+    menuHeight ?= @getHeight()
+    menuWidth  ?= @getWidth()

     top         = (options.y or event.pageY or 0) + options.offset.top
     left        = (options.x or event.pageX or 0) + options.offset.left
@@ -126,7 +128,6 @@
     @topMargin  = expectedTop  - top
     @leftMargin = expectedLeft - left

-    {menuWidth, menuMaxWidth, menuMinWidth} = options

     style   =
       width : "#{menuWidth}px"
@@ -134,7 +135,6 @@
       left  : left

     if menuMaxWidth
-      style.width    = "auto"
       style.maxWidth = "#{menuMaxWidth}px"

     style.minWidth = "#{menuMinWidth}px"  if menuMinWidth


Preventing circular loop in defaultKey handling

--- js/foundation/eventmanager/eventmanager.coffee
+++ js/foundation/eventmanager/eventmanager.coffee
@@ -1123,13 +1123,17 @@
   # that responds true to _methodName. If _ctrl is undefined, use
   # a special default rule of auto-selecting the active control and
   # checking all of its siblings before traversing.
-  defaultKey: (_methodName,_ctrl)->
+  defaultKey: (_methodName,_ctrl,_testedIds)->
     return true if _ctrl? and _ctrl[_methodName]? and _ctrl[_methodName]() == true
     return null unless @_listeners.active
-    _ctrl = @_views[@_listeners.active[0]]
+    _ctrl = @_views[@_listeners.active[0]] unless _ctrl?
+    _ctrlId = _ctrl.viewId
+    return null if ~_testedIds.indexOf(_ctrlId)
     return true if _ctrl[_methodName]? and _ctrl[_methodName]() == true
     _stop = null
+    _testedIds.push(_ctrlId)
     for _viewId in _ctrl.parent.views
+      continue if ~_testedIds.indexOf(_viewId)
       continue if _ctrl.viewId == _viewId
       _ctrl = @_views[_viewId]
       if _ctrl[_methodName]?
@@ -1137,7 +1141,7 @@
         if _stopStatus == false or _stopStatus == true
           _stop = _stopStatus unless _stop
       return _stop if _stop != null
-    return true if _ctrl.parent? and @defaultKey(_methodName,_ctrl.parent) == true
+    return true if _ctrl.parent? and @defaultKey(_methodName,_ctrl.parent,_testedIds) == true
     null
   #
   # Handles the keyDown event
@@ -1168,7 +1172,7 @@
     # to instances of HControl, but any parent object will work.
     if not _repeating and @_defaultKeyActions[_keyCode.toString()]
       _defaultKeyMethod = @_defaultKeyActions[_keyCode.toString()]
-      _stop = true if @defaultKey(_defaultKeyMethod)
+      _stop = true if @defaultKey(_defaultKeyMethod,null,[])
     @_lastKeyDown = _keyCode
     Event.stop(e) if _stop
   keyUp: (e)->


Fix typo in previous commit

Fix typo in previous commit

--- engine/framebuffer.coffee
+++ engine/framebuffer.coffee
@@ -4,7 +4,7 @@

 class Framebuffer

-    constructor: (@render_manager, size_x, size_y, tex_type=@render_manager.gl.FLOAT, tex_format=@render_manager.gl.RGBA) ->
+    constructor: (@render_manager, size_x, size_y, @tex_type=@render_manager.gl.FLOAT, tex_format=@render_manager.gl.RGBA) ->
         @context = @render_manager.context
         gl = @render_manager.gl
         @size_x = size_x


Update# improve google analytics tracking

--- coffeescripts/main.coffee
+++ coffeescripts/main.coffee
@@ -39,6 +39,8 @@
 setMenuActiveItem = (index) ->
   resetMenuActiveItem()
   $menuItem[index].addClass "active"
+  hash = $menuItem[index].children('a').attr('href')
+  ga('send', 'pageview', "/2014/#{hash}")

 $window.on 'resize', ()->
   screenWidth = $window.width()
@@ -51,6 +53,7 @@
 $menu.on "click", "a", (e) ->
   targetScrollTop = $sections[e.target.hash].offset().top
   $dom.scrollTo(targetScrollTop - 58, 1000)
+  ga('send', 'pageview', "/2014/#{e.target.hash}")
   e.preventDefault()

 ###


[modularization] Modularize close position strategy. Display return starting from 1. Add drawback calculation.

--- routes/process.coffee
+++ routes/process.coffee
@@ -1,10 +1,7 @@
-# should = require('chai').should()
 fs = require 'fs'
 path = require 'path'
 moment = require 'moment'
 _ = require 'underscore'
-
-# TODO pass between function a status object? Or calculate everyone's own status

 openPositionStrategyForSessionFactory = (rollback, cutoff) ->
   extreme = do ->
@@ -15,7 +12,7 @@
       max = if max < n or max == undefined then n else max
       min = if n < min or min == undefined then n else min
       ret
-  rollbackQueue = new Array rollback
+  rollbackQueue = new Array(rollback)
   (price, timeIndex) ->
     rollbackQueue.push price
     [max, min] = extreme rollbackQueue.shift()
@@ -24,6 +21,30 @@
         1
       else if price <= min
         -1
+    else
+      0
+
+closePositionStrategyForSessionFactory = (maxAF, stepSize, pos, price) ->
+  last =
+    position: pos
+    extreme: price
+    af: 0
+    sar: price
+  (price) ->
+    now =
+      position: last.position
+      extreme: if 0 < (price - last.extreme) * last.position
+      then price else last.extreme
+      af: 0
+      sar: 0
+    now.af = do =>
+      t = last.af + (now.extreme isnt last.extreme) * stepSize
+      if t < maxAF then t else maxAF
+    now.sar = last.sar + (now.extreme - last.sar) * now.af
+    last = now
+    now.sar
+    if 0 < (now.sar - price) * last.position
+      -last.position
     else
       0

@@ -38,21 +59,15 @@
     last =
       trade: 0
       position: 0
-      extreme: 0
-      af: 0
-      sar: 0
       price: 0
       openPrice: 0
       return: ret
     # Data here should be of one day
-    openPositionStrategy = openPositionStrategyForSessionFactory(@rollback, @cutoff)
+    openPositionStrategy = openPositionStrategyForSessionFactory @rollback, @cutoff
     for i in [0..data.length - 1]
       now =
         trade: 0
         position: last.position
-        extreme: 0
-        af: 0
-        sar: last.sar
         price: parseFloat data[i][1]
         openPrice: last.openPrice
         return: last.return
@@ -61,36 +76,54 @@
         if i == data.length - 1 # Force cover at the end of day
           now.trade = -last.position
         else
-          now.extreme = if 0 < (now.price - last.extreme) * last.position
-          then now.price else last.extreme
-          now.af = do =>
-            t = last.af + (now.extreme isnt last.extreme) * @stepSize
-            if t < @maxAF then t else @maxAF
-          now.sar = last.sar + (now.extreme - last.sar) * now.af
-          now.trade = if 0 < (now.sar - now.price) * last.position
-          then -last.position else 0
+          now.trade = closePositionStrategy now.price
         if now.trade # Trade
           now.position = last.position + now.trade
           now.return =
-          (1 + last.return) *
-          (1 - now.trade * (now.price / now.openPrice - 1)) - 1
+          last.return *
+          (1 - now.trade * (now.price / now.openPrice - 1))
           now.openPrice = 0
       else # Consider opening a position
         now.trade = openPosition
         if now.trade # Open a position
           now.position = now.trade
-          now.extreme = now.price
-          now.sar = now.price
+          closePositionStrategy = closePositionStrategyForSessionFactory @maxAF, @stepSize, now.trade, now.price
           now.openPrice = if now.trade then now.price else 0
       last = now
       retList.push last.return
     return retList

+maxDrawback = (data) ->
+  len = data.length
+  curMin = data[len - 1]
+  drawback = []
+  for i in [data.length - 2..0]
+    curMin = if data[i] < curMin then data[i] else curMin
+    drawback.push (data[i] - curMin) / data[i]
+  _.max drawback
+
+# sharpeRatio = (raw, data) ->
+#   # TODO Not done
+#   time = (moment(i[0]) for i in raw)
+#   timeDiff = _.map time, (i) ->
+#     i.diff time[0], 'years', true
+#   # Calculate return for every time point
+#   ret = []
+#   for i in [0..data.length - 1]
+#     if timeDiff[i] == 0
+#       ret.push 0
+#     else
+#       ret.push Math.log(data[i]) / timeDiff[i]
+#   sum = _.reduce ret, (a, b) ->
+#     a + b
+#   mean = sum / data.length
+#   console.log mean
+
 predict = (data) ->
   a = _.groupBy data, (i) ->
     i[0][..9]
   transaction = new Transaction 4, 8, 0.1, 0.01
-  currentReturn = 0
+  currentReturn = 1
   retList = []
   for i, j of a
     retList = retList.concat transaction.processDay(j, currentReturn)
@@ -101,7 +134,8 @@
   data = fs.readFileSync path.join(__dirname, '../data/stock.fmt'), 'ascii'
   raw = JSON.parse data
   result = predict raw
-  ret = [['Time', 'Close', 'Return']]
+  ret = []
   for i in [0..raw.length - 1] by Math.floor(raw.length / 400)
     ret.push [raw[i][0], raw[i][1], result[i]]
+  ret.unshift ['Time', 'Close', 'Return']
   return ret


Ignore gracefully if task in request cookie doesn't exist

--- acs.coffee
+++ acs.coffee
@@ -251,7 +251,9 @@
         nextTask(cookies)
       else
         db.getTask(taskId, (task) ->
-          if reqParams.fault?
+          if not task
+            nextTask(cookies)
+          else if reqParams.fault?
             util.log("#{currentRequest.deviceId}: Fault response for task #{task._id}")
             task.fault = reqParams.fault
             db.saveTask(task, (err) ->


fullpieagent testing

fullpieagent testing

--- widgets/fullpie_agent/fullpie_agent.coffee
+++ widgets/fullpie_agent/fullpie_agent.coffee
@@ -1,224 +1,221 @@
 class Dashing.FullpieAgent extends Dashing.Widget
-
-    @accessor 'data'
-
-    onData: (data) ->
-        #$(@node).fadeOut().fadeIn()
-        @update(data.data)
-
-    update: (dataSet) ->
-
-        if !dataSet
-            dataSet = @get("data")
-        if !dataSet
-            return
-        $(@node).children("svg").remove();
-        width = 750
-        height = 400
-        radius = Math.min(width, height) / 2
-        radiuso = 130
-        radiusi = 65
-        color = d3.scale.category20()
-        pie = d3.layout.pie().value((d) -> d.value)
-        arc = d3.svg.arc().innerRadius(radiusi).outerRadius(radiuso)
-        svg = d3.select(@node).append('svg').attr('width', width).attr('height', height).append('g').attr('transform', 'translate(' + width / 2 + ',' + height / 2 + ')')
-        console.log 'update pie', dataSet
-        #that = this
-        piedata = pie(dataSet)
-        # Remove zero values
-        piedata = piedata.filter (pd) -> pd.value isnt 0
-        console.log 'update pie', piedata
-        #create a marker element if it doesn't already exist
-        defs = svg.select('defs')
-        if defs.empty()
-            defs = svg.append('defs')
-        marker = defs.select('marker#circ')
-        if marker.empty()
-            defs.append('marker').attr('id', 'circ').attr('markerWidth', 6).attr('markerHeight', 6).attr('refX', 3).attr('refY', 3).append('circle').attr('cx', 3).attr('cy', 3).attr 'r', 3
-        #Create/select <g> elements to hold the different types of graphics
-        #and keep them in the correct drawing order
-        pathGroup = svg.select('g.piePaths')
-        if pathGroup.empty()
-            pathGroup = svg.append('g').attr('class', 'piePaths')
-        pointerGroup = svg.select('g.pointers')
-        if pointerGroup.empty()
-            pointerGroup = svg.append('g').attr('class', 'pointers')
-        labelGroup = svg.select('g.labels')
-        if labelGroup.empty()
-            labelGroup = svg.append('g').attr('class', 'labels')
-        path = pathGroup.selectAll('path.pie').data(piedata)
-        #console.log("path",this.path.empty())
-        path.enter().append('path').attr('class', 'pie').attr 'fill', (d, i) ->
-            color i
-        #path.attr('d',arc)
-        #    .exit()
-        path.transition().duration(1500).attrTween 'd', (d,i) ->
+  #@accessor 'value'
+  @accessor 'data'
+  onData: (data) ->
+    #$(@node).fadeOut().fadeIn()
+    @update(data.data)
+  update: (dataSet) ->
+    if !dataSet
+        dataSet = @get("data")
+    if !dataSet
+        return
+    $(@node).children("svg").remove();
+    width = 750
+    height = 400
+    radius = Math.min(width, height) / 2
+    radiuso = 130
+    radiusi = 65
+    color = d3.scale.category20()
+    pie = d3.layout.pie().value((d) -> d.value)
+    arc = d3.svg.arc().innerRadius(radiusi).outerRadius(radiuso)
+    svg = d3.select(@node).append('svg').attr('width', width).attr('height', height).append('g').attr('transform', 'translate(' + width / 2 + ',' + height / 2 + ')')
+    console.log 'update pie', dataSet
+    #that = this
+    piedata = pie(dataSet)
+    # Remove zero values
+    piedata = piedata.filter (pd) -> pd.value isnt 0
+    console.log 'update pie', piedata
+    #create a marker element if it doesn't already exist
+    defs = svg.select('defs')
+    if defs.empty()
+      defs = svg.append('defs')
+    marker = defs.select('marker#circ')
+    if marker.empty()
+      defs.append('marker').attr('id', 'circ').attr('markerWidth', 6).attr('markerHeight', 6).attr('refX', 3).attr('refY', 3).append('circle').attr('cx', 3).attr('cy', 3).attr 'r', 3
+    #Create/select <g> elements to hold the different types of graphics
+    #and keep them in the correct drawing order
+    pathGroup = svg.select('g.piePaths')
+    if pathGroup.empty()
+      pathGroup = svg.append('g').attr('class', 'piePaths')
+    pointerGroup = svg.select('g.pointers')
+    if pointerGroup.empty()
+      pointerGroup = svg.append('g').attr('class', 'pointers')
+    labelGroup = svg.select('g.labels')
+    if labelGroup.empty()
+      labelGroup = svg.append('g').attr('class', 'labels')
+    path = pathGroup.selectAll('path.pie').data(piedata)
+    #console.log("path",this.path.empty())
+    path.enter().append('path').attr('class', 'pie').attr 'fill', (d, i) ->
+      color i
+    #path.attr('d',arc)
+    #    .exit()
+    path.transition().duration(1500).attrTween 'd', (d,i) ->
+        `var i`
+        theOldDataInPie = oldPieData
+        # Interpolate the arcs in data space
+        s0 = undefined
+        e0 = undefined
+        if theOldDataInPie[i]
+          s0 = theOldDataInPie[i].startAngle
+          e0 = theOldDataInPie[i].endAngle
+        else if !theOldDataInPie[i] and theOldDataInPie[i - 1]
+          s0 = theOldDataInPie[i - 1].endAngle
+          e0 = theOldDataInPie[i - 1].endAngle
+        else if !theOldDataInPie[i - 1] and theOldDataInPie.length > 0
+          s0 = theOldDataInPie[theOldDataInPie.length - 1].endAngle
+          e0 = theOldDataInPie[theOldDataInPie.length - 1].endAngle
+        else
+          s0 = 0
+          e0 = 0
+        i = d3.interpolate({
+          startAngle: 0
+          endAngle: 0
+        },
+          startAngle: d.startAngle
+          endAngle: d.endAngle)
+        (t) ->
+          b = i(t)
+          return arc b
+    path.exit()
+        .transition()
+        .duration(300)
+        .attrTween("d", (d,i) ->
             `var i`
-            theOldDataInPie = oldPieData
-            # Interpolate the arcs in data space
-            s0 = undefined
-            e0 = undefined
-            if theOldDataInPie[i]
-                s0 = theOldDataInPie[i].startAngle
-                e0 = theOldDataInPie[i].endAngle
-            else if !theOldDataInPie[i] and theOldDataInPie[i - 1]
-                s0 = theOldDataInPie[i - 1].endAngle
-                e0 = theOldDataInPie[i - 1].endAngle
-            else if !theOldDataInPie[i - 1] and theOldDataInPie.length > 0
-                s0 = theOldDataInPie[theOldDataInPie.length - 1].endAngle
-                e0 = theOldDataInPie[theOldDataInPie.length - 1].endAngle
-            else
-                s0 = 0
-                e0 = 0
+            s0 = 2 * Math.PI
+            e0 = 2 * Math.PI
             i = d3.interpolate({
-                startAngle: 0
-                endAngle: 0
+              startAngle: d.startAngle
+              endAngle: d.endAngle
             },
-                startAngle: d.startAngle
-                endAngle: d.endAngle)
+              startAngle: s0
+              endAngle: e0)
             (t) ->
-                b = i(t)
-                return arc b
-        path.exit()
-            .transition()
-            .duration(300)
-            .attrTween("d", (d,i) ->
-                `var i`
-                s0 = 2 * Math.PI
-                e0 = 2 * Math.PI
-                i = d3.interpolate({
-                    startAngle: d.startAngle
-                    endAngle: d.endAngle
-                },
-                    startAngle: s0
-                    endAngle: e0)
-                (t) ->
-                    b = i(t)
-                    return arc b
-            )
-            .remove()
-        labels = labelGroup.selectAll('text').data(piedata.sort((p1, p2) ->
-            p1.startAngle - p2.startAngle
-        ))
-        labels.enter().append('text').attr 'text-anchor', 'middle'
-        labels.exit().remove()
-        labelLayout = d3.geom.quadtree().extent([
-            [
-            -width
-            -height
-            ]
-            [
-            width
-            height
-            ]
-        ]).x((d) ->
-            d.x
-        ).y((d) ->
-            d.y
-        )([])
-        #create an empty quadtree to hold label positions
-        maxLabelWidth = 0
-        maxLabelHeight = 0
-        labels.text((d) ->
-            # Set the text *first*, so we can query the size
-            # of the label with .getBBox()
-            #d.value
-            console.log 'ts: d', d
-            d.data.label
-        ).each((d, i) ->
-            # Move all calculations into the each function.
-            # Position values are stored in the data object
-            # so can be accessed later when drawing the line
-            ### calculate the position of the center marker ###
-            a = (d.startAngle + d.endAngle) / 2
-            #trig functions adjusted to use the angle relative
-            #to the "12 o'clock" vector:
-            #console.log 'ts: a', a
-            #d.cx = Math.sin(a) * (radius - 75)
-            #d.cy = -Math.cos(a) * (radius - 75)
-            d.cx = Math.sin(a) * (radiuso - (radiuso - radiusi) / 2)
-            d.cy = -Math.cos(a) * (radiuso - (radiuso - radiusi) / 2)
-            ### calculate the default position for the label,
-                so that the middle of the label is centered in the arc
-            ###
-            bbox = @getBBox()
-            #bbox.width and bbox.height will
-            #describe the size of the label text
-            labelRadius = radius - 20
-            #labelRadius = radiuso * 1.3
-            d.x = Math.sin(a) * labelRadius
-            d.l = d.x - bbox.width / 2 - 2
-            d.r = d.x + bbox.width / 2 + 2
-            #d.y = -Math.cos(a) * (radius - 20)
-            d.y = -Math.cos(a) * (labelRadius)
-            d.b = d.oy = d.y + 5
-            d.t = d.y - bbox.height - 5
-            #console.log 'ts: d', d
-            ### check whether the default position
-                overlaps any other labels
-            ###
-            conflicts = []
-            labelLayout.visit (node, x1, y1, x2, y2) ->
-            #recurse down the tree, adding any overlapping
-            #node is the node in the quadtree,
-            #node.point is the value that we added to the tree
-            #x1,y1,x2,y2 are the bounds of the rectangle that
-            #this node covers
-            if x1 > d.r + maxLabelWidth / 2 or x2 < d.l - maxLabelWidth / 2 or y1 > d.b + maxLabelHeight / 2 or y2 < d.t - maxLabelHeight / 2
-                return true #don't bother visiting children or checking this node
-            p = node.point
-            v = false
-            h = false
-            if p
-                #p is defined, i.e., there is a value stored in this node
-                h = p.l > d.l and p.l <= d.r or p.r > d.l and p.r <= d.r or p.l < d.l and p.r >= d.r
-                #horizontal conflict
-                v = p.t > d.t and p.t <= d.b or p.b > d.t and p.b <= d.b or p.t < d.t and p.b >= d.b
-                #vertical conflict
-                if h and v
-                conflicts.push p
-                #add to conflict list
-            return
-            if conflicts.length
-            console.log d, ' conflicts with ', conflicts
-            rightEdge = d3.max(conflicts, (d2) ->
-                `var maxLabelHeight`
-                `var maxLabelWidth`
-                d2.r
-            )
-            d.l = rightEdge
-            d.x = d.l + bbox.width / 2 + 5
-            d.r = d.l + bbox.width + 10
-            else
-            console.log 'no conflicts for ', d
-            ### add this label to the quadtree, so it will show up as a conflict
-                for future labels.
-            ###
-            labelLayout.add d
-            maxLabelWidth = Math.max(maxLabelWidth, bbox.width + 10)
-            maxLabelHeight = Math.max(maxLabelHeight, bbox.height + 10)
-            return
+              b = i(t)
+              return arc b
         )
-        .transition()
-        .attr('x', (d) ->
-            d.x
-        ).attr 'y', (d) ->
-            d.y
-        pointers = pointerGroup.selectAll('path.pointer').data(piedata)
-        pointers.enter()
-            .append('path')
-            .attr('class', 'pointer')
-            .style('fill', 'none')
-            .style('stroke', 'black')
-            .attr('marker-end', 'url(#circ)')
-        pointers.exit().remove()
-        pointers.transition().attr 'd', (d) ->
-            console.log 'ts: d', d
-            if d.cx > d.l
-            console.log 'if d.cx > d.l','M' + (d.l + 2) + ',' + d.b + 'L' + (d.r - 2) + ',' + d.b + ' ' + d.cx + ',' + d.cy
-            'M' + (d.l + 2) + ',' + d.b + 'L' + (d.r - 2) + ',' + d.b + ' ' + d.cx + ',' + d.cy
-            else
-            console.log 'else', 'M' + (d.r - 2) + ',' + d.b + 'L' + (d.l + 2) + ',' + d.b + ' ' + d.cx + ',' + d.cy
-            'M' + (d.r - 2) + ',' + d.b + 'L' + (d.l + 2) + ',' + d.b + ' ' + d.cx + ',' + d.cy
-        oldPieData = piedata
+        .remove()
+    labels = labelGroup.selectAll('text').data(piedata.sort((p1, p2) ->
+      p1.startAngle - p2.startAngle
+    ))
+    labels.enter().append('text').attr 'text-anchor', 'middle'
+    labels.exit().remove()
+    labelLayout = d3.geom.quadtree().extent([
+      [
+        -width
+        -height
+      ]
+      [
+        width
+        height
+      ]
+    ]).x((d) ->
+      d.x
+    ).y((d) ->
+      d.y
+    )([])
+    #create an empty quadtree to hold label positions
+    maxLabelWidth = 0
+    maxLabelHeight = 0
+    labels.text((d) ->
+      # Set the text *first*, so we can query the size
+      # of the label with .getBBox()
+      #d.value
+      console.log 'ts: d', d
+      d.data.label
+    ).each((d, i) ->
+      # Move all calculations into the each function.
+      # Position values are stored in the data object
+      # so can be accessed later when drawing the line
+      ### calculate the position of the center marker ###
+      a = (d.startAngle + d.endAngle) / 2
+      #trig functions adjusted to use the angle relative
+      #to the "12 o'clock" vector:
+      #console.log 'ts: a', a
+      #d.cx = Math.sin(a) * (radius - 75)
+      #d.cy = -Math.cos(a) * (radius - 75)
+      d.cx = Math.sin(a) * (radiuso - (radiuso - radiusi) / 2)
+      d.cy = -Math.cos(a) * (radiuso - (radiuso - radiusi) / 2)
+      ### calculate the default position for the label,
+         so that the middle of the label is centered in the arc
+      ###
+      bbox = @getBBox()
+      #bbox.width and bbox.height will
+      #describe the size of the label text
+      labelRadius = radius - 20
+      #labelRadius = radiuso * 1.3
+      d.x = Math.sin(a) * labelRadius
+      d.l = d.x - bbox.width / 2 - 2
+      d.r = d.x + bbox.width / 2 + 2
+      #d.y = -Math.cos(a) * (radius - 20)
+      d.y = -Math.cos(a) * (labelRadius)
+      d.b = d.oy = d.y + 5
+      d.t = d.y - bbox.height - 5
+      #console.log 'ts: d', d
+      ### check whether the default position
+         overlaps any other labels
+      ###
+      conflicts = []
+      labelLayout.visit (node, x1, y1, x2, y2) ->
+        #recurse down the tree, adding any overlapping
+        #node is the node in the quadtree,
+        #node.point is the value that we added to the tree
+        #x1,y1,x2,y2 are the bounds of the rectangle that
+        #this node covers
+        if x1 > d.r + maxLabelWidth / 2 or x2 < d.l - maxLabelWidth / 2 or y1 > d.b + maxLabelHeight / 2 or y2 < d.t - maxLabelHeight / 2
+          return true #don't bother visiting children or checking this node
+        p = node.point
+        v = false
+        h = false
+        if p
+          #p is defined, i.e., there is a value stored in this node
+          h = p.l > d.l and p.l <= d.r or p.r > d.l and p.r <= d.r or p.l < d.l and p.r >= d.r
+          #horizontal conflict
+          v = p.t > d.t and p.t <= d.b or p.b > d.t and p.b <= d.b or p.t < d.t and p.b >= d.b
+          #vertical conflict
+          if h and v
+            conflicts.push p
+          #add to conflict list
         return
+      if conflicts.length
+        console.log d, ' conflicts with ', conflicts
+        rightEdge = d3.max(conflicts, (d2) ->
+          `var maxLabelHeight`
+          `var maxLabelWidth`
+          d2.r
+        )
+        d.l = rightEdge
+        d.x = d.l + bbox.width / 2 + 5
+        d.r = d.l + bbox.width + 10
+      else
+        console.log 'no conflicts for ', d
+      ### add this label to the quadtree, so it will show up as a conflict
+         for future labels.
+      ###
+      labelLayout.add d
+      maxLabelWidth = Math.max(maxLabelWidth, bbox.width + 10)
+      maxLabelHeight = Math.max(maxLabelHeight, bbox.height + 10)
+      return
+    )
+    .transition()
+    .attr('x', (d) ->
+      d.x
+    ).attr 'y', (d) ->
+      d.y
+    pointers = pointerGroup.selectAll('path.pointer').data(piedata)
+    pointers.enter()
+        .append('path')
+        .attr('class', 'pointer')
+        .style('fill', 'none')
+        .style('stroke', 'black')
+        .attr('marker-end', 'url(#circ)')
+    pointers.exit().remove()
+    pointers.transition().attr 'd', (d) ->
+      console.log 'ts: d', d
+      if d.cx > d.l
+        console.log 'if d.cx > d.l','M' + (d.l + 2) + ',' + d.b + 'L' + (d.r - 2) + ',' + d.b + ' ' + d.cx + ',' + d.cy
+        'M' + (d.l + 2) + ',' + d.b + 'L' + (d.r - 2) + ',' + d.b + ' ' + d.cx + ',' + d.cy
+      else
+        console.log 'else', 'M' + (d.r - 2) + ',' + d.b + 'L' + (d.l + 2) + ',' + d.b + ' ' + d.cx + ',' + d.cy
+        'M' + (d.r - 2) + ',' + d.b + 'L' + (d.l + 2) + ',' + d.b + ' ' + d.cx + ',' + d.cy
+    oldPieData = piedata
+    return


fullpieagent testing

fullpieagent testing

--- widgets/fullpie_agent/fullpie_agent.coffee
+++ widgets/fullpie_agent/fullpie_agent.coffee
@@ -133,28 +133,20 @@
             p1.startAngle - p2.startAngle
         ))
         #labels.enter().append('text').attr 'text-anchor', 'middle'
-        #labels.enter().append('text').attr('text-anchor', 'middle').attr("filter","url(#dropshadow)")
-        labels.enter().append('text')
-            .attr('text-anchor', (d) ->
-                rads = ((d.endAngle - d.startAngle) / 2) + d.startAngle
-                #if (rads > 7 * Math.PI / 4 && rads < Math.PI / 4) || (rads > 3 * Math.PI / 4 && rads < 5 * Math.PI / 4)
+        labels.enter().append('text').attr('text-anchor', 'middle').attr("filter","url(#dropshadow)")
+        #labels.enter().append('text')
+            #.attr('text-anchor', (d) ->
+                #rads = ((d.endAngle - d.startAngle) / 2) + d.startAngle
+                #if (rads > 3 * Math.PI / 4 && rads < 5 * Math.PI / 4)
                 #    return "middle"
-                #else if (rads >= Math.PI / 4 && rads <= 3 * Math.PI / 4)
+                #else if (rads >= 0 && rads <= 3 * Math.PI / 4)
                 #    return "start"
-                #else if (rads >= 5 * Math.PI / 4 && rads <= 7 * Math.PI / 4)
+                #else if (rads >= 5 * Math.PI / 4 && rads <= 8 * Math.PI / 4)
                 #    return "end"
                 #else
                 #    return "middle"
-                if (rads > 3 * Math.PI / 4 && rads < 5 * Math.PI / 4)
-                    return "middle"
-                else if (rads >= 0 && rads <= 3 * Math.PI / 4)
-                    return "start"
-                else if (rads >= 5 * Math.PI / 4 && rads <= 8 * Math.PI / 4)
-                    return "end"
-                else
-                    return "middle"
-            )
-            .attr("filter","url(#dropshadow)")
+            #)
+            #.attr("filter","url(#dropshadow)")
         labels.exit().remove()
         labelLayout = d3.geom.quadtree().extent([
             [


fullpieagent testing

fullpieagent testing

--- widgets/fullpie_agent/fullpie_agent.coffee
+++ widgets/fullpie_agent/fullpie_agent.coffee
@@ -272,12 +272,12 @@
         if labelGroup.empty()
             labelGroup = svg.append("g").attr("class", "labels")

-        path = pathGroup.selectAll("path.pie")
-            .data(piedata)
-
-        path.enter().append("path")
-            .attr("class", "pie")
-            .attr("fill",(d, i) -> return color i)
+        #path = pathGroup.selectAll("path.pie")
+        #    .data(piedata)
+
+        #path.enter().append("path")
+        #    .attr("class", "pie")
+        #    .attr("fill",(d, i) -> return color i)
         #    .data(piedata)

         arcs = svg.selectAll("g.slice")


fullpieagent testing

fullpieagent testing

--- widgets/fullpie_agent/fullpie_agent.coffee
+++ widgets/fullpie_agent/fullpie_agent.coffee
@@ -222,7 +222,7 @@
       maxLabelHeight = Math.max(maxLabelHeight, bbox.height + 10)
       return
     )
-    #.transition()
+    .transition()
     .attr('x', (d) ->
       d.x
     ).attr 'y', (d) ->


Agent pie: start/end angle pie transition work

--- widgets/fullpie_agent/fullpie_agent.coffee
+++ widgets/fullpie_agent/fullpie_agent.coffee
@@ -160,6 +160,7 @@
         path.transition().duration(1000).attrTween('d', (d,i) ->
             theOldDataInPie = @oldPieData ? piedata
             console.log('@oldPieData in the tween',@oldPieData)
+            @testData = @testData + 1000
             tD = @testData + 1000
             console.log('tD in the tween',tD)
             # Interpolate the arcs in data space


Do not try to createDomains or adding vm to group if vm.save fails.

--- workers/social/lib/social/models/vm.coffee
+++ workers/social/lib/social/models/vm.coffee
@@ -19,6 +19,8 @@

   @set
     softDelete          : yes
+    indexes             :
+      hostnameAlias     : 'unique'
     permissions         :
       'sudoer'          : []
       'create vms'      : ['member','moderator']
@@ -32,8 +34,6 @@
                            'count', #'calculateUsage'
                           ]
       instance          : []
-    indexes             :
-      hostnameAlias     : 'unique'
     schema              :
       ip                :
         type            : String
@@ -168,7 +168,11 @@
           }

           vm.save (err) =>
-            return callback err  if err
+
+            handleError err
+            if err
+              return console.warn "Failed to create VM for ", \
+                                   {users, groups, hostnameAliases}

             JVM.createDomains hostnameAliases, hostnameAliases[0]

@@ -414,6 +418,12 @@
       }

       vm.save (err)->
+
+        handleError err
+        if err
+          return console.warn "Failed to create VM for ", \
+                               {users, groups, hostnameAliases}
+
         JVM.createDomains hostnameAliases, hostnameAliases[0]
         target.addVm vm, handleError


Add descriptions to AsyncAction spec.

Add descriptions to AsyncAction spec.

--- src/Witness/Scripts/specs/AsyncAction.coffee
+++ src/Witness/Scripts/specs/AsyncAction.coffee
@@ -3,12 +3,12 @@

 describe "AsyncAction",
 {
-	given: ->
+	"given an AsyncAction where the function throws before going async": ->
 		functionThatThrowsBeforeAsync = -> throw new Error "failed"
 		@action = new Witness.AsyncAction functionThatThrowsBeforeAsync, []

-	when: async ->
-		testDone = @done;
+	"when the action is run": async ->
+		testDone = @done
 		@action.run {}, (=> @doneCalled = true; testDone()), ((error) => @error = error; testDone())

 	then:
@@ -16,13 +16,13 @@
 		error: message: should.be "failed"
 },
 {
-	given: ->
+	"given an AsyncAction where the function does nothing and timeout is 10 milliseconds": ->
 		functionThatDoesNothing = (->)
 		timeToWait = 10 # milliseconds
 		@action = new Witness.AsyncAction functionThatDoesNothing, [], null, timeToWait

-	when: async ->
-		testDone = @done;
+	"when the action is run": async ->
+		testDone = @done
 		@action.run {}, (=> @doneCalled = true; testDone()), ((error) => @error = error; testDone())

 	then:
@@ -30,12 +30,12 @@
 		error: should.beInstanceof Witness.TimeoutError
 },
 {
-	given: ->
+	"given an AsyncAction where the function calls done after a 100 millisecond delay": ->
 		functionThatCallsDone = -> setTimeout (=> this.done()), 100
 		@action = new Witness.AsyncAction functionThatCallsDone

-	when: async ->
-		testDone = @done;
+	"when the action is run": async ->
+		testDone = @done
 		@action.run {}, (=> @doneCalled = true; testDone()), (=> @failCalled = true; testDone())

 	then:
@@ -43,12 +43,12 @@
 		failCalled: should.be undefined
 },
 {
-	given: ->
+	"given an AsyncAction where the function calls fail": ->
 		functionThatCallsFail = -> setTimeout (=> this.fail "failed"), 10
 		@action = new Witness.AsyncAction functionThatCallsFail

-	when: async ->
-		testDone = @done;
+	"when the action is run": async ->
+		testDone = @done
 		@action.run {}, (=> @doneCalled = true; testDone()), ((error) => @error = error; testDone())

 	then:


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- test/controllable.coffee
+++ test/controllable.coffee
@@ -98,6 +98,8 @@

 test "should stay in place if two different directions are held", ->
   window.keydown.left = true
-  window.keydown.rig
+  window.keydown.right = true
+
+

 module()


Modified in browser at pixie.strd6.com

Modified in browser at pixie.strd6.com

--- test/controllable.coffee
+++ test/controllable.coffee
@@ -41,7 +41,7 @@

   window.keydown.right = false

-  equals player.I.x, 1
+  equals player.I.x, 12

 test "should move player up while key is pressed", ->
   ;


adding test modules

adding test modules

--- test/power_canvas.coffee
+++ test/power_canvas.coffee
@@ -3,5 +3,5 @@
 test "exists", ->
   ok $.fn.powerCanvas

-module undefined
+module()


Test: simulate server-side preview of a comment

--- test/save-restore.coffee
+++ test/save-restore.coffee
@@ -33,10 +33,22 @@
 	# Abort if we accidentally used a camelcase name.
 	['hostName', 'pathName'].forEach (key) ->
 		if location.hasOwnProperty(key)
-			throw "makeLocation: Invalid location key: #{key}"
+			throw new Error("makeLocation: Invalid location key: #{key}")
+
 	location['protocol'] ||= "https:"
 	location['hostname'] ||= "www.metafilter.com"
 	location['pathname'] ||= "/"
+	if !/^\//.test(location['pathname'])
+		throw new Error("makeLocation: pathname needs to start with a slash: " +
+			location['pathname'])
+	if /#/.test(location['pathname'])
+		throw new Error("makeLocation: Get that hash out of the pathname")
+
+	location['hash']	   ||= ''
+	if !/(^$)|^#/.test(location['hash'])
+		throw new Error("makeLocation: Hash needs to be empty string or start with #: " +
+			location['hash'])
+
 	location['href']     ||= location.protocol + location.hostname + location.pathname
 	return location

@@ -77,7 +89,7 @@
 		mdCommentText: "A comment"
 		mdExtendedText: "More inside"
 		linkId: null # no linkId for an FPP
-		formSubmitUrl: "post_preview.mefi"
+		formSubmitUrl: "/post_preview.mefi"
 	sr.saveMarkdownForPreview saveOptions

 	test.equals(sessionStorage.length, 1, "Data saved to session storage")
@@ -86,7 +98,7 @@
 	sr = saveRestore.makeSaveRestore
 		sessionStorage: sessionStorage
 		location: makeLocation
-			pathname: "post_preview.mefi"
+			pathname: "/post_preview.mefi"

 	savedDataJson = sr.restoreMarkdown
 		linkId: saveOptions.linkId
@@ -117,7 +129,7 @@
 		mdCommentText: mdCommentText
 		mdExtendedText: mdExtendedText
 		linkId: null # no link ID for new post page
-		formSubmitUrl: "post_preview.mefi"
+		formSubmitUrl: "/post_preview.mefi"

 	test.equals(sessionStorage.length, 1, "Comment was saved")
@@ -125,22 +137,67 @@
 	# Simulate a failure to delete when reloading the page, for whatever reason.
 	# So don't call getSessionStorageKey.

-
 	# deleteMarkdownForPreview is called when posting the FPP
 	sr = saveRestore.makeSaveRestore
 		sessionStorage: sessionStorage
 		location: makeLocation
-			pathname: "post_preview.mefi"
+			pathname: "/post_preview.mefi"

 	sr.deleteMarkdownForPreview
 		linkId: null
-		formSubmitUrl: "post_preview.mefi"
+		formSubmitUrl: "/post_preview.mefi"

 	test.equals(sessionStorage.length, 0, "Comment was deleted")
 	test.done()

-
+# Simulate previewing a comment on the server side
+#
+# URL we're commenting on: https://fanfare.metafilter.com/2212/Forever-Hitler-on-the-Half-Shell
+exports['simulate server-side preview of a comment'] = (test) ->
+	sessionStorage = makeSessionStorage()
+
+	sr = saveRestore.makeSaveRestore
+		sessionStorage: sessionStorage
+		location: makeLocation
+			hostname: 'fanfare.metafilter.com'
+			pathname: '/2212/Forever-Hitler-on-the-Half-Shell'
+
+	# Simulate clicking the preview button
+	mdCommentText = "I really liked this show and/or movie!"
+	linkId = 2212
+	sr.saveMarkdownForPreview
+		mdCommentText: mdCommentText
+		mdExtendedText: null
+		linkId: linkId
+		formSubmitUrl: '/contribute/post_comment_preview.mefi#commentpreview'
+
+	test.equal(sessionStorage.length, 1, "Comment was saved")
+
+
+	# Load the markdown on the preview page
+	sr = saveRestore.makeSaveRestore
+		sessionStorage: sessionStorage
+		location: makeLocation
+			hostname: 'fanfare.metafilter.com'
+			pathname: '/contribute/post_comment_preview.mefi'
+			hash:     '#commentpreview'
+
+
+	savedDataJson = sr.restoreMarkdown
+		linkId: linkId
+		formSubmitUrl: 'post_comment_preview.mefi#commentpreview'
+
+
+	test.equals(mdCommentText, savedDataJson.comment, "comment was restored correctly")
+	test.equals(null,          savedDataJson.extended, "extended was restored correctly")
+
+	storageKey = sr.getSessionStorageKey linkId, 'post_comment_preview.mefi#commentpreview'
+	keyValue = sessionStorage.getItem(storageKey)
+	test.equals(typeof(keyValue), "undefined", "Data was removed from session storage")
+	test.equals(sessionStorage.length, 0, "Nothing left in session storage")
+
+	test.done()

 # Tests to write:
@@ -151,7 +208,7 @@
 # - Stale markdown: If the page gave us HTML, do we have markdown to match it?
 #   + Looks like I accidentally deleted the implementation, with some Git foolishness
 # - Nothing happens when calling from unsupported URLs
-
-
-
-
+# - @@@ Shouldn't the getSessionStorageKey() tests fail if the URL ends in a hash? Test that more robustly.
+
+
+


check for finiteness and add matcher toApproxUpper()

--- matcher.coffee
+++ matcher.coffee
@@ -1,11 +1,23 @@
+
+toApprox = (expected, actual, upper, maxEps = 0) ->
+  if not expected.isSameSize(actual)
+    return false
+  for c in [0...expected.width] by 1
+    for r in [0...(if upper then c+1 else expected.height)] by 1
+      dif = Math.abs actual.get(r, c) - expected.get(r, c)
+      if not isFinite(dif) or dif > maxEps
+        return false
+  true
+
+
 module.exports =

   # checks if actual matrix has the same size and all element number values are similar
-  toApprox: (expected, maxEps = 0) ->
-    if not expected.isSameSize(@actual)
-      return false
-    for i in [0...expected.width] by 1
-      for j in [0...expected.height] by 1
-        if (Math.abs @actual.get(j, i) - expected.get(j, i)) > maxEps
-          return false
-    true
+  toApprox: (expected, maxEps) ->
+    toApprox expected, @actual, false, maxEps
+
+  toApproxUpper: (expected, maxEps) ->
+    toApprox expected, @actual, true, maxEps
+
+
+


3d: show both sides of faces, so material is visible from inside

--- salvus/page/3d.coffee
+++ salvus/page/3d.coffee
@@ -506,6 +506,7 @@

             push_face3 = (a,b,c) =>
                 geometry.faces.push(new THREE.Face3(a-1,b-1,c-1))
+                geometry.faces.push(new THREE.Face3(b-1,a-1,c-1))   # both sides of faces, so material is visible from inside

             # include all faces defined by 3 vertices (triangles)
             for k in [0...face3.length] by 3


storage: ability to specify a prefered host or hosts

--- salvus/storage.coffee
+++ salvus/storage.coffee
@@ -335,6 +335,7 @@
         project_id : required
         base_url   : ''
         exclude    : undefined  # if project not currently opened, won't open on any host in the list exclude
+        prefer     : undefined  # string or array; if given prefer these hosts first, irregardless of their newness.
         cb         : required   # cb(err, host used)

     dbg = (m) -> winston.debug("open_project_somewhere(#{opts.project_id}): #{m}")
@@ -393,6 +394,12 @@

                         if opts.exclude?
                             hosts = (x for x in hosts when opts.exclude.indexOf(x) == -1)
+
+                        if opts.prefer?
+                            if typeof(opts.prefer) == 'string'
+                                opts.prefer = [opts.prefer]
+                            # move any hosts in the prefer list to the front of the line.
+                            hosts = (x for x in hosts when opts.prefer.indexOf(x) != -1).concat( (x for x in hosts when opts.prefer.indexOf(x) == -1) )

                         ## TODO: FOR TESTING -- restrict to Google
                         ##hosts = (x for x in hosts when x.slice(0,4) == '10.3')


scoring unit tests: bruteforce cardinality, crack time, 0-5 score

--- test/test-scoring.coffee
+++ test/test-scoring.coffee
@@ -36,11 +36,11 @@
     t.equal lg(n), result, "lg(#{n}) == #{result}"
   n = 17
   p = 4
-  approx_equal t, lg(n * p), lg(n) + lg(p), "logarithm product rule"
-  approx_equal t, lg(n / p), lg(n) - lg(p), "logarithm quotient rule"
-  approx_equal t, lg(10), 1 / Math.log10(2), "logarithm base switch rule"
-  approx_equal t, lg(Math.pow(n, p)), p * lg(n), "logarithm power rule"
-  approx_equal t, lg(n), Math.log(n) / Math.log(2), "logarithm base change rule"
+  approx_equal t, lg(n * p), lg(n) + lg(p), "product rule"
+  approx_equal t, lg(n / p), lg(n) - lg(p), "quotient rule"
+  approx_equal t, lg(10), 1 / Math.log10(2), "base switch rule"
+  approx_equal t, lg(Math.pow(n, p)), p * lg(n), "power rule"
+  approx_equal t, lg(n), Math.log(n) / Math.log(2), "base change rule"
   t.end()

 test 'minimum entropy search', (t) ->
@@ -116,3 +116,49 @@
   t.deepEqual result.match_sequence, [m1, m2]

   t.end()
+
+test 'entropy to crack time', (t) ->
+  times = [e0, e1, e2, e3] = (scoring.entropy_to_crack_time(n) for n in [0,1,7,60])
+  t.ok e0 < e1 < e2 < e3, "monotonically increasing"
+  t.ok e > 0, "always positive" for e in times
+  t.end()
+
+test 'crack time to score', (t) ->
+  for [seconds, score] in [
+    [0,  0]
+    [10, 0]
+    [Math.pow(10, 9), 4]
+    ]
+    msg = "crack time of #{seconds} seconds has score of #{score}"
+    t.equal scoring.crack_time_to_score(seconds), score, msg
+  t.end()
+
+test 'bruteforce cardinality', (t) ->
+  for [str, cardinality] in [
+    # beginning / middle / end of lowers range
+    ['a', 26]
+    ['h', 26]
+    ['z', 26]
+    # sample from each other character group
+    ['Q', 26]
+    ['0', 10]
+    ['9', 10]
+    ['$', 33]
+    ['£', 64]
+    ['å', 64]
+    # unicode
+    ['α', 40]
+    ['αβ', 40]
+    ['Ϫα', 58]
+    ['好', 40]
+    ['信息论', 100]
+    # combinations
+    ['a$', 59]
+    ['aQ£', 116]
+    ['9Z9Z', 36]
+    ['«信息论»', 164]
+    ]
+    msg = "cardinality of #{str} is #{cardinality}"
+    t.equal scoring.calc_bruteforce_cardinality(str), cardinality, msg
+  t.end()
+


clear cron schedule if quartz schedule is given

--- SingularityUI/app/views/requestFormBase.coffee
+++ SingularityUI/app/views/requestFormBase.coffee
@@ -109,6 +109,9 @@
             expectedRuntime = parseInt @$('#scheduled-expected-runtime').val()

             requestObject[scheduleType] = schedule if schedule
+            if scheduleType is 'quartzSchedule'
+                requestObject.schedule = ''
+
             requestObject.numRetriesOnFailure = retries if retries
             requestObject.scheduledExpectedRuntimeMillis = expectedRuntime if expectedRuntime


Use nested params in quick add url

Use nested params in quick add url

Don't force wrap_parameters to make a copy of it because it could be a
large data URI.

--- app/assets/javascripts/quick_add_url.js.coffee
+++ app/assets/javascripts/quick_add_url.js.coffee
@@ -6,7 +6,10 @@
     type: 'post'
     contentType: 'application/json'
     dataType: 'json'
-    data: JSON.stringify(url: url)
+    data: JSON.stringify(
+      src_image:
+        url: url
+    )
     success: (data) ->
       callbacks.submit_success()
       count = 0


JCredential:share: implemented, it supports both modifications for sharing as user or owner

it can be used like this:

  JCredentialInstance.share { user: yes, owner: no, target: "gokmen"}, cb

--- workers/social/lib/social/models/computeproviders/credential.coffee
+++ workers/social/lib/social/models/computeproviders/credential.coffee
@@ -1,12 +1,15 @@

 jraphical       = require 'jraphical'
 JCredentialData = require './credentialdata'
+JName           = require '../name'
+JUser           = require '../user'
+JGroup          = require '../group'

 module.exports = class JCredential extends jraphical.Module

   KodingError        = require '../../error'

-  {secure, ObjectId, signature} = require 'bongo'
+  {secure, ObjectId, signature, daisy} = require 'bongo'
   {Relationship}     = jraphical
   {permit}           = require '../group/permissionset'
   Validators         = require '../group/validators'
@@ -38,9 +41,9 @@
       instance        :
         delete        :
           (signature Function)
-        # assign        :
-        #   (signature Object, Function)
-        # unassign      :
+        share         :
+          (signature Object, Function)
+        # withold       :
         #   (signature Object, Function)
         # update        :
         #   (signature Object, Function)
@@ -133,11 +136,65 @@
       options ?= { limit : 10 }

       {delegate} = client.connection
-      delegate.fetchCredentials {},
+      delegate.fetchCredentials as: "user",
         targetOptions : {
           selector, options
         }, callback

+
+  # .share can be used like this:
+  #
+  # JCredentialInstance.share { user: yes, owner: no, target: "gokmen"}, cb
+
+  share: permit
+
+    advanced: [
+      { permission: 'update credential', validateWith: Validators.own }
+    ]
+
+    success: (client, options, callback)->
+
+      {target, owner, user} = options
+
+      setPermissionFor = (target, callback)=>
+
+        method = (x)-> if x then 'addCredential' else 'removeCredential'
+
+        daisy queue = [
+          =>
+            if user?
+              target[method(user)]  this, as: 'user', queue.next
+            else
+              queue.next()
+        ,
+          =>
+            if owner?
+              target[method(owner)] this, as: 'owner', queue.next
+            else
+              queue.next()
+        ,
+          -> callback null
+        ]
+
+      JName.fetchModels target, (err, result)=>
+
+        if err or not result
+          return callback new KodingError "Target not found."
+
+        { models } = result
+        target = models[0]
+
+        if target instanceof JUser
+          target.fetchOwnAccount (err, account)=>
+            if err or not account
+              return callback new KodingError "Failed to fetch account."
+            setPermissionFor account, callback
+
+        else if target instanceof JGroup
+          setPermissionFor target, callback
+
+        else
+          callback new KodingError "Target does not support credentials."

   delete: permit


updated groups config

updated groups config

--- config/main.groups.coffee
+++ config/main.groups.coffee
@@ -69,7 +69,6 @@
   authWorker    :
     login       : 'authWorker'
     queueName   : socialQueueName+'auth'
-    authResourceName: 'auth'
     numberOfWorkers: 1
     watch       : yes
   social        :
@@ -91,6 +90,7 @@
   client        :
     version     : version
     watch       : yes
+    watchDuration: 250
     includesPath: 'client'
     websitePath : 'website'
     js          : "js/kd.#{version}.js"
@@ -112,12 +112,16 @@
       sourceUri : 'http://localhost:1337'
   mq            :
     host        : 'web-prod.in.koding.com'
+    port        : 5672
+    apiAddress  : "web-prod.in.koding.com"
+    apiPort     : 55672
     login       : 'PROD-k5it50s4676pO9O'
     componentUser: "prod-<component>"
     password    : 'Dtxym6fRJXx4GJz'
     heartbeat   : 10
     vhost       : 'new'
   broker        :
+    ip          : ""
     port        : 8008
     certFile    : ""
     keyFile     : ""
@@ -133,6 +137,8 @@
     cronDaily   : '0 10 0 * * *'
     run         : no
     defaultRecepient : undefined
+  emailSender   :
+    run         : no
   guests        :
     # define this to limit the number of guset accounts
     # to be cleaned up per collection cycle.


cookies are now dasherized

cookies are now dasherized

--- addon/services/user-session.coffee
+++ addon/services/user-session.coffee
@@ -1,14 +1,21 @@
 `import Ember from 'ember'`
 `import Errors from '../utils/errors'`

-{computed, Service, RSVP, isBlank, isPresent, Evented} = Ember
+{computed, String, Service, RSVP, isBlank, isPresent, Evented} = Ember
 {alias, notEmpty, equal, and: present, or: ifAny} = computed

 volatile = ->
   computed(arguments...).volatile()

 cookieSetter = (key, value) ->
-  if value? then Cookies.set(key, value, expires: 365) else Cookies.remove(key)
+  k = String.dasherize key
+  if value?
+    Cookies.set(k, value, expires: 365)
+  else
+    Cookies.remove(k)
+
+cookieGetter = (key) ->
+  Cookies.get String.dasherize key

 SessionStates = ["uncertain", "login-success", "login-failed", "logout-success"]
 UserSession = Service.extend Evented,
@@ -25,13 +32,11 @@
   p: computed -> RSVP.Promise.resolve @
   simwmsAccountSession: volatile
     set: cookieSetter
-    get: ->
-      Cookies.get("simwmsAccountSession")
+    get: cookieGetter

   simwmsUserSession: volatile
     set: cookieSetter
-    get: ->
-      Cookies.get("simwmsUserSession")
+    get: cookieGetter

   logout: ->
     return @get("p") if isBlank @get "session"


Fixed merge conflicts.

Fixed merge conflicts.

--- src/command.coffee
+++ src/command.coffee
@@ -333,12 +333,20 @@
         args = args.concat ['--hash', opts.hash]

       p = spawn "#{__dirname}/../bin/muffin", args, {stdio: 'inherit'}
-      p.on 'close', done
+      p.on 'exit', (code) ->
+        if code isnt 0
+          process.exit(code)
+        else
+          done(null)

     # Minify
     (done) ->
       p =  spawn "#{__dirname}/../bin/muffin", ['optimize', '-e', 'production'], {stdio: 'inherit'}
-      p.on 'close', done
+      p.on 'exit', (code) ->
+        if code isnt 0
+          process.exit(code)
+        else
+          done(null)

     # Remove temp directories
     (done) ->


Better message when removing build directory.

--- src/command.coffee
+++ src/command.coffee
@@ -143,7 +143,7 @@
 task 'new', 'create a new project', ->
   projectName = opts.arguments[1]
   utils.fatal "Must supply a name for the new project" unless projectName
-  projectDir = sysPath.join cwd, projectName
+  projectDir = sysPath.join process.cwd(), projectName
   utils.fatal "The application #{projectName} already exists." if fs.existsSync(projectDir)

   # Copy skeleton files
@@ -370,9 +370,10 @@
   ]

 # Task - remove the `public/` directory
-task 'clean', 'remove the public/ directory', ->
+task 'clean', 'remove the build directory', ->
   fs.removeSync publicDir
-  logging.warn 'Removed the public/ directory.'
+  relativePath = sysPath.relative(process.cwd(), publicDir)
+  logging.warn "Removed the build directory at #{relativePath}."

 # Task - run tests
 task 'test', 'run tests', ->


Add try-catch -block positionList. Log thrown errors to Sentry with address information.

--- src/control.coffee
+++ src/control.coffee
@@ -324,23 +324,32 @@
                 slug = "#{municipality}/#{street}/#{numberPart}"
                 positionList = models.PositionList.fromSlug municipality, street, numberPart
                 @listenTo positionList, 'sync', (p) =>
-                    if p.length == 0
-                        throw new Error 'Address slug not found'
-                    else if p.length == 1
-                        position = p.pop()
-                    else if p.length > 1
-                        exactMatch = p.filter (pos) ->
-                            numberParts = numberPart.split SEPARATOR
-                            letter = pos.get 'letter'
-                            number_end = pos.get 'number_end'
-                            if numberParts.length == 1
-                                return letter == null and number_end == null
-                            letterMatch = -> letter and letter.toLowerCase() == numberParts[1].toLowerCase()
-                            numberEndMatch = -> number_end and number_end == numberParts[1]
-                            return letterMatch() or numberEndMatch()
-                        if exactMatch.length != 1
-                            position = exactMatch.shift()
-                    @selectPosition position
+                    try
+                        if p.length == 0
+                            throw new Error 'Address slug not found', slug
+                        else if p.length == 1
+                            position = p.pop()
+                        else if p.length > 1
+                            exactMatch = p.filter (pos) ->
+                                numberParts = numberPart.split SEPARATOR
+                                letter = pos.get 'letter'
+                                number_end = pos.get 'number_end'
+                                if numberParts.length == 1
+                                    return letter == null and number_end == null
+                                letterMatch = -> letter and letter.toLowerCase() == numberParts[1].toLowerCase()
+                                numberEndMatch = -> number_end and number_end == numberParts[1]
+                                return letterMatch() or numberEndMatch()
+                            if exactMatch.length != 1
+                                throw new Error 'Too many address matches'
+                        @selectPosition position
+
+                    catch err
+
+                        addressInfo =
+                            address: slug
+
+                        Raven.captureException err, {extra: addressInfo}
+
                     deferred.resolve
                         afterMapInit: =>
                             if level != 'none'


Pack OpenSSL if installed

Pack OpenSSL if installed

--- webpack/dist/sfx.coffee
+++ webpack/dist/sfx.coffee
@@ -16,6 +16,18 @@
 console.log "Creating sfx at", dst = path.resolve 'tmp/sfx'
 rm dst
 mkdirp dst
+
+install2 = path.join process.env.APPDATA, PACKAGE.mingzi
+
+openssl = (at)->
+  exe = path.join at, cli = 'openssl-cli.exe'
+  fs.stat exe, (err)->
+    return if err
+    console.log "Copying OpenSSL"
+    fs.createReadStream exe
+    .pipe fs.createWriteStream path.join dst, cli
+
+openssl install2

 nodes = require './ls'
 nodesII = nodes.slice()
@@ -55,7 +67,7 @@
   unless local = nodes.pop()
     do zipped
     return
-  zipTree local, path.join process.env.APPDATA, PACKAGE.mingzi, local
+  zipTree local, path.join install2, local

 # Everything is zipped. Pack using iexpress
 zipped = ->


Updated reference (window -> root), Added AMD definition

--- src/detectr.coffee
+++ src/detectr.coffee
@@ -1,5 +1,7 @@
-do (window = @, document) ->
-  "use strict"
+"use strict"
+
+do (root = exports ? this) ->
+  document = root.document

   ###
     String helper functions
@@ -32,9 +34,9 @@
       linux:
         run: -> contains detectr.Browser.platform.name(), 'linux'
         result: 'linux'
-      windows:
-        run: -> contains detectr.Browser.platform.name(), 'windows'
-        result: 'windows'
+      roots:
+        run: -> contains detectr.Browser.platform.name(), 'roots'
+        result: 'roots'
       android:
         run: -> contains detectr.Browser.get(), 'android'
         result: 'android'
@@ -57,7 +59,7 @@
         run: -> contains detectr.Browser.get(), 'webos'
         result: 'webos'
       wp7:
-        run: -> contains detectr.Browser.get(), 'windows phone os'
+        run: -> contains detectr.Browser.get(), 'roots phone os'
         result: 'wp7'
       blackberry:
         run: -> (contains detectr.Browser.get(), 'rim') or (contains detectr.Browser.get(), 'blackberry')
@@ -142,8 +144,8 @@
     parsedPlatform = uaString.match(/(.*?)\s(.*?)\((.*?);\s(.*?)\)/)

     detectr.Browser or=
-      width: -> window.outerWidth
-      height: -> window.outerHeight
+      width: -> root.outerWidth
+      height: -> root.outerHeight
       get: -> uaString
       name: -> uaAppName
       version: -> uaAppVersion
@@ -158,10 +160,10 @@
     document.documentElement.setAttribute 'lang', detectr.Browser.language()

     detectr.Display or=
-      width: -> window.screen.width
-      height: -> window.screen.height
-      pageWidth: -> window.innerWidth
-      pageHeight: -> window.innerHeight
+      width: -> root.screen.width
+      height: -> root.screen.height
+      pageWidth: -> root.innerWidth
+      pageHeight: -> root.innerHeight

     detectr.clear()

@@ -205,9 +207,9 @@

       detectr.Display.orientation = detectResultCache['landscape'] or detectResultCache['portrait']

-    window.addEventListener 'resize', (-> doOrientationChange(-> detectr.Display.pageWidth() >= detectr.Display.pageHeight())), false
-
-    window.addEventListener 'orientationchange', (-> doOrientationChange(-> Math.abs(window.orientation) is 90)), false
+    root.addEventListener 'resize', (-> doOrientationChange(-> detectr.Display.pageWidth() >= detectr.Display.pageHeight())), false
+
+    root.addEventListener 'orientationchange', (-> doOrientationChange(-> Math.abs(root.orientation) is 90)), false

     detectr.Display.orientation = detectResultCache['landscape'] or detectResultCache['portrait']

@@ -292,4 +294,8 @@
     Call detectr constructor with default configuration
     and set the reference to the detectr object
   ###
-  window.detectr = detectr defaultTests+  root.detectr = detectr defaultTests
+
+  # AMDs are awesome :)
+  if root.define and not exports?
+    root.define 'detectr', [], -> root.detectr