Changeset - 3c997bc6d380
[Not reviewed]
default
0 11 1
Drew Perttula - 7 years ago 2018-05-24 06:54:58
drewp@bigasterisk.com
fix some coffee lint
Ignore-this: 1a988be1598dda76dd2fefca3b499aa0
12 files changed with 296 insertions and 153 deletions:
0 comments (0 inline, 0 general)
coffeelint.json
Show inline comments
 
new file 100644
 
{
 
  "arrow_spacing": {
 
    "level": "ignore"
 
  },
 
  "braces_spacing": {
 
    "level": "ignore",
 
    "spaces": 0,
 
    "empty_object_spaces": 0
 
  },
 
  "camel_case_classes": {
 
    "level": "error"
 
  },
 
  "coffeescript_error": {
 
    "level": "error"
 
  },
 
  "colon_assignment_spacing": {
 
    "level": "ignore",
 
    "spacing": {
 
      "left": 0,
 
      "right": 0
 
    }
 
  },
 
  "cyclomatic_complexity": {
 
    "level": "ignore",
 
    "value": 10
 
  },
 
  "duplicate_key": {
 
    "level": "error"
 
  },
 
  "empty_constructor_needs_parens": {
 
    "level": "ignore"
 
  },
 
  "ensure_comprehensions": {
 
    "level": "warn"
 
  },
 
  "eol_last": {
 
    "level": "ignore"
 
  },
 
  "indentation": {
 
    "value": 2,
 
    "level": "error"
 
  },
 
  "line_endings": {
 
    "level": "ignore",
 
    "value": "unix"
 
  },
 
  "max_line_length": {
 
    "value": 80,
 
    "level": "error",
 
    "limitComments": true
 
  },
 
  "missing_fat_arrows": {
 
    "level": "ignore",
 
    "is_strict": false
 
  },
 
  "newlines_after_classes": {
 
    "value": 3,
 
    "level": "ignore"
 
  },
 
  "no_backticks": {
 
    "level": "warn"
 
  },
 
  "no_debugger": {
 
    "level": "warn",
 
    "console": false
 
  },
 
  "no_empty_functions": {
 
    "level": "ignore"
 
  },
 
  "no_empty_param_list": {
 
    "level": "ignore"
 
  },
 
  "no_implicit_braces": {
 
    "level": "ignore",
 
    "strict": true
 
  },
 
  "no_implicit_parens": {
 
    "level": "ignore",
 
    "strict": true
 
  },
 
  "no_interpolation_in_single_quotes": {
 
    "level": "ignore"
 
  },
 
  "no_nested_string_interpolation": {
 
    "level": "warn"
 
  },
 
  "no_plusplus": {
 
    "level": "ignore"
 
  },
 
  "no_private_function_fat_arrows": {
 
    "level": "warn"
 
  },
 
  "no_stand_alone_at": {
 
    "level": "ignore"
 
  },
 
  "no_tabs": {
 
    "level": "error"
 
  },
 
  "no_this": {
 
    "level": "ignore"
 
  },
 
  "no_throwing_strings": {
 
    "level": "error"
 
  },
 
  "no_trailing_semicolons": {
 
    "level": "error"
 
  },
 
  "no_trailing_whitespace": {
 
    "level": "error",
 
    "allowed_in_comments": false,
 
    "allowed_in_empty_lines": true
 
  },
 
  "no_unnecessary_double_quotes": {
 
    "level": "ignore"
 
  },
 
  "no_unnecessary_fat_arrows": {
 
    "level": "warn"
 
  },
 
  "non_empty_constructor_needs_parens": {
 
    "level": "ignore"
 
  },
 
  "prefer_english_operator": {
 
    "level": "ignore",
 
    "doubleNotLevel": "ignore"
 
  },
 
  "space_operators": {
 
    "level": "ignore"
 
  },
 
  "spacing_after_comma": {
 
    "level": "ignore"
 
  },
 
  "transform_messes_up_line_numbers": {
 
    "level": "warn"
 
  }
 
}
light9/web/coffee_element.coffee
Show inline comments
 
# Polymer seems to need static getters for 'observers' and
 
# 'properties', not just static attributes, though I don't know how it
 
# can even tell the difference.
 
#
 
# This workaround is to use names like '@getter_properties' in the
 
# class then register with this function that fixes them.
 
# 
 
#
 
# Also see http://coffeescript.org/#unsupported-get-set
 
window.coffeeElementSetup = (cls) ->
 
  for attr in ['properties', 'observers']
 
    val = cls['getter_' + attr]
 
    if val?
 
      do (val) ->
 
        Object.defineProperty(cls, attr, {get: ( -> val)})  
 
        Object.defineProperty(cls, attr, {get: ( -> val)})
 
  customElements.define(cls.is, cls)
light9/web/edit-choice.coffee
Show inline comments
 
@@ -34,28 +34,28 @@ window.setupDrop = (senseElem, highlight
 
      else
 
        null
 

	
 
    try
 
      onDrop(uri, pos)
 
    catch e
 
      console.log(e)
 
    unhighlight()
 

	
 

	
 

	
 
coffeeElementSetup(class EditChoice extends Polymer.Element
 
    @is: "edit-choice",
 
    @getter_properties:
 
        graph: {type: Object, notify: true},
 
        uri: {type: String, notify: true},
 
  @is: "edit-choice",
 
  @getter_properties:
 
    graph: {type: Object, notify: true},
 
    uri: {type: String, notify: true},
 

	
 
    _setUri: (u) ->
 
      @uri = u
 
      @dispatchEvent(new CustomEvent('edited'))
 
  _setUri: (u) ->
 
    @uri = u
 
    @dispatchEvent(new CustomEvent('edited'))
 

	
 
    connectedCallback: ->
 
      super.connectedCallback()
 
      setupDrop(@$.box, @$.box, null, @_setUri.bind(@))
 
  connectedCallback: ->
 
    super.connectedCallback()
 
    setupDrop(@$.box, @$.box, null, @_setUri.bind(@))
 

	
 
    unlink: ->
 
      @_setUri(null)
 
  unlink: ->
 
    @_setUri(null)
 
)
light9/web/graph.coffee
Show inline comments
 
@@ -19,25 +19,27 @@ patchSizeSummary = (patch) ->
 

	
 
# (sloppily shared to rdfdbclient.coffee too)
 
window.patchSizeSummary = patchSizeSummary
 

	
 
class Handler
 
  # a function and the quad patterns it cared about
 
  constructor: (@func, @label) ->
 
    @patterns = [] # s,p,o,g quads that should trigger the next run
 
    @innerHandlers = [] # Handlers requested while this one was running
 
  
 
class AutoDependencies
 
  constructor: () ->
 
    @handlers = new Handler(null) # tree of all known Handlers (at least those with non-empty patterns). Top node is not a handler.
 
    # tree of all known Handlers (at least those with non-empty
 
    # patterns). Top node is not a handler.
 
    @handlers = new Handler(null)
 
    @handlerStack = [@handlers] # currently running
 
    
 
  runHandler: (func, label) ->
 
    # what if we have this func already? duplicate is safe?
 

	
 
    if not label?
 
      throw new Error("missing label")
 

	
 
    h = new Handler(func, label)
 
    tailChildren = @handlerStack[@handlerStack.length - 1].innerHandlers
 
    matchingLabel = _.filter(tailChildren, ((c) -> c.label == label)).length
 
    # ohno, something depends on some handlers getting run twice :(
 
@@ -118,49 +120,49 @@ class AutoDependencies
 
    # quads in the given pattern.
 
    current = @handlerStack[@handlerStack.length - 1]
 
    if current? and current != @handlers
 
      current.patterns.push([s, p, o, g])
 
      #log('push', s,p,o,g)
 
    #else
 
    #  console.trace('read outside runHandler')
 

	
 
class window.SyncedGraph
 
  # Main graph object for a browser to use. Syncs both ways with
 
  # rdfdb. Meant to hide the choice of RDF lib, so we can change it
 
  # later.
 
  # 
 
  #
 
  # Note that _applyPatch is the only method to write to the graph, so
 
  # it can fire subscriptions.
 

	
 
  constructor: (@patchSenderUrl, @prefixes, @setStatus) ->
 
    # patchSenderUrl is the /syncedGraph path of an rdfdb server.
 
    # prefixes can be used in Uri(curie) calls.
 
    @_autoDeps = new AutoDependencies() # replaces GraphWatchers
 
    @clearGraph()
 

	
 
    if @patchSenderUrl
 
      @_client = new RdfDbClient(@patchSenderUrl,
 
                                 @_clearGraphOnNewConnection.bind(@),
 
                                 @_applyPatch.bind(@),
 
                                 @setStatus)
 
    
 
  clearGraph: ->
 
    # just deletes the statements; watchers are unaffected.
 
    if @graph?
 
      @_applyPatch({addQuads: [], delQuads: @graph.getQuads()})
 

	
 
    # if we had a Store already, this lets N3.Store free all its indices/etc
 
    @graph = N3.Store()
 
    @_addPrefixes(@prefixes)
 
    @cachedFloatValues = new Map();
 
    @cachedFloatValues = new Map()
 

	
 
  _clearGraphOnNewConnection: -> # must not send a patch to the server!
 
    log('graph: clearGraphOnNewConnection')
 
    @clearGraph()
 
    log('graph: clearGraphOnNewConnection done')
 
      
 
  _addPrefixes: (prefixes) ->
 
    for k in (prefixes or {})
 
      @prefixes[k] = prefixes[k]
 
    @prefixFuncs = N3.Util.prefixes(@prefixes)
 
        
 
  Uri: (curie) ->
 
@@ -179,32 +181,32 @@ class window.SyncedGraph
 
                          @Uri("http://www.w3.org/2001/XMLSchema#double"))
 

	
 
  Quad: (s, p, o, g) -> N3.DataFactory.quad(s, p, o, g)
 

	
 
  toJs: (literal) ->
 
    # incomplete
 
    parseFloat(literal.value)
 

	
 
  loadTrig: (trig, cb) -> # for debugging
 
    patch = {delQuads: [], addQuads: []}
 
    parser = N3.Parser()
 
    parser.parse trig, (error, quad, prefixes) =>
 
                  if error
 
                    throw new Error(error)
 
                  if (quad)
 
                    patch.addQuads.push(quad)
 
                  else
 
                    @_applyPatch(patch)
 
                    @_addPrefixes(prefixes)
 
                    cb() if cb
 
      if error
 
        throw new Error(error)
 
      if (quad)
 
        patch.addQuads.push(quad)
 
      else
 
        @_applyPatch(patch)
 
        @_addPrefixes(prefixes)
 
        cb() if cb
 
                    
 
  quads: () -> # for debugging
 
    [q.subject, q.predicate, q.object, q.graph] for q in @graph.getQuads()
 

	
 
  applyAndSendPatch: (patch) ->
 
    console.time('applyAndSendPatch')
 
    if !Array.isArray(patch.addQuads) || !Array.isArray(patch.delQuads)
 
      console.timeEnd('applyAndSendPatch')
 
      log('corrupt patch')
 
      throw new Error("corrupt patch: #{JSON.stringify(patch)}")
 

	
 
    @_validatePatch(patch)
 
@@ -214,29 +216,29 @@ class window.SyncedGraph
 
    console.timeEnd('applyAndSendPatch')
 

	
 
  _validatePatch: (patch) ->
 
    for qs in [patch.addQuads, patch.delQuads]
 
      for q in qs
 
        if not q.equals
 
          throw new Error("doesn't look like a proper Quad")
 
        if not q.subject.id or not q.graph.id? or not q.predicate.id?
 
          throw new Error("corrupt patch: #{JSON.stringify(q)}")
 
    
 
  _applyPatch: (patch) ->
 
    # In most cases you want applyAndSendPatch.
 
    # 
 
    #
 
    # This is the only method that writes to @graph!
 
    @cachedFloatValues.clear()
 
    for quad in patch.delQuads
 
      #log("remove #{JSON.stringify(quad)}")      
 
      #log("remove #{JSON.stringify(quad)}")
 
      did = @graph.removeQuad(quad)
 
      #log("removed: #{did}")
 
    for quad in patch.addQuads
 
      @graph.addQuad(quad)
 
    #log('applied patch locally', patchSizeSummary(patch))
 
    @_autoDeps.graphChanged(patch)
 

	
 
  getObjectPatch: (s, p, newObject, g) ->
 
    # make a patch which removes existing values for (s,p,*,c) and
 
    # adds (s,p,newObject,c). Values in other graphs are not affected.
 
    existing = @graph.getQuads(s, p, null, g)
 
    return {
 
@@ -314,48 +316,50 @@ class window.SyncedGraph
 
  items: (list) ->
 
    out = []
 
    current = list
 
    while true
 
      if current == RDF + 'nil'
 
        break
 
        
 
      @_autoDeps.askedFor(current, null, null, null) # a little loose
 

	
 
      firsts = @graph.getQuads(current, RDF + 'first', null)
 
      rests = @graph.getQuads(current, RDF + 'rest', null)
 
      if firsts.length != 1
 
        throw new Error("list node #{current} has #{firsts.length} rdf:first edges")
 
        throw new Error(
 
          "list node #{current} has #{firsts.length} rdf:first edges")
 
      out.push(firsts[0].object)
 

	
 
      if rests.length != 1
 
        throw new Error("list node #{current} has #{rests.length} rdf:rest edges")
 
        throw new Error(
 
          "list node #{current} has #{rests.length} rdf:rest edges")
 
      current = rests[0].object
 
    
 
    return out
 

	
 
  contains: (s, p, o) ->
 
    @_autoDeps.askedFor(s, p, o, null)
 
    return @graph.getQuads(s, p, o).length > 0
 

	
 
  nextNumberedResources: (base, howMany) ->
 
    # base is NamedNode or string
 
    base = base.id if base.id
 
    results = []
 
    # we could cache [base,lastSerial]
 
    for serial in [0..1000]
 
      uri = @Uri("#{base}#{serial}")
 
      if not @contains(uri, null, null)
 
        results.push(uri)
 
        if results.length >= howMany
 
          return results
 
    throw new Error("can't make sequential uri with base #{base}")
 

	
 
  nextNumberedResource: (base) ->
 
    @nextNumberedResources(base, 1)[0]       
 
    @nextNumberedResources(base, 1)[0]
 

	
 
  contextsWithPattern: (s, p, o) ->
 
    @_autoDeps.askedFor(s, p, o, null)
 
    ctxs = []
 
    for q in @graph.getQuads(s, p, o)
 
      ctxs.push(q.graph)
 
    return _.unique(ctxs)
 

	
light9/web/graph_test.coffee
Show inline comments
 
@@ -41,25 +41,25 @@ describe 'SyncedGraph', ->
 
        called++
 
      graph.runHandler(hand, 'run')
 
      assert.equal(1, called)
 
      
 
    it 'calls a handler a 2nd time if the graph is patched with relevant data', ->
 
      called = 0
 
      hand = ->
 
        called++
 
        graph.uriValue(A1, A2)
 
      graph.runHandler(hand, 'run')
 
      graph.applyAndSendPatch({
 
        delQuads: [quad(A1, A2, A3)], addQuads: [quad(A1, A2, A4)]})
 
      assert.equal(2, called) 
 
      assert.equal(2, called)
 

	
 
    it 'notices new queries a handler makes upon rerun', ->
 
      called = 0
 
      objsFound = []
 
      hand = ->
 
        called++
 
        graph.uriValue(A1, A2)
 
        if called > 1
 
          objsFound.push(graph.objects(A1, A3))
 
      graph.runHandler(hand, 'run')
 
      # first run looked up A1,A2,*
 
      graph.applyAndSendPatch({
 
@@ -71,25 +71,25 @@ describe 'SyncedGraph', ->
 
      assert.equal(3, called)
 
      assert.deepEqual([[], [A4]], objsFound)
 

	
 
    it 'calls a handler again even if the handler throws an error', ->
 
      called = 0
 
      hand = ->
 
        called++
 
        graph.uriValue(A1, A2)
 
        throw new Error('this test handler throws an error')
 
      graph.runHandler(hand, 'run')
 
      graph.applyAndSendPatch({
 
        delQuads: [quad(A1, A2, A3)], addQuads: [quad(A1, A2, A4)]})
 
      assert.equal(2, called) 
 
      assert.equal(2, called)
 

	
 
    describe 'works with nested handlers', ->
 

	
 
      innerResults = []
 
      inner = ->
 
        console.log('\nninnerfetch')
 
        innerResults.push(graph.uriValue(A1, A2))
 
        console.log("innerResults #{JSON.stringify(innerResults)}\n")
 

	
 
      outerResults = []
 
      doRunInner = true
 
      outer = ->
 
@@ -191,45 +191,45 @@ describe 'SyncedGraph', ->
 
        hand = -> values.push(graph.subjects(rdfType, U('Type1')))
 
        graph.runHandler(hand, 'run')
 
        graph.applyAndSendPatch(
 
          {delQuads: [], addQuads: [quad(A4, rdfType, U('Type1'))]})
 
        expect(values[0]).to.deep.have.members([A2, A3])
 
        expect(values[1]).to.deep.have.members([A2, A3, A4])
 

	
 
      describe 'items', ->
 
        it 'when the list order changes', (done) ->
 
          values = []
 
          successes = 0
 
          hand = ->
 
            try 
 
            try
 
              head = graph.uriValue(U('x'), U('y'))
 
            catch
 
              # graph goes empty between clearGraph and loadTrig
 
              return
 
            values.push(graph.items(head))
 
            successes++
 
          graph.clearGraph()
 
          graph.loadTrig "
 
            @prefix : <http://example.com/> .
 
            :ctx { :x :y (:a1 :a2 :a3) } .
 
          ", () ->
 
            graph.runHandler(hand, 'run')
 
            graph.clearGraph()
 
            graph.loadTrig "
 
             graph.runHandler(hand, 'run')
 
             graph.clearGraph()
 
             graph.loadTrig "
 
              @prefix : <http://example.com/> .
 
              :ctx { :x :y (:a1 :a3 :a2) } .
 
            ", () ->
 
              assert.deepEqual([[A1, A2, A3], [A1, A3, A2]], values)
 
              assert.equal(2, successes)
 
              done()
 
               assert.deepEqual([[A1, A2, A3], [A1, A3, A2]], values)
 
               assert.equal(2, successes)
 
               done()
 
  
 
      describe 'contains', ->
 
        it 'when a new triple is added', ->
 
          values = []
 
          hand = -> values.push(graph.contains(A1, A1, A1))
 
          graph.runHandler(hand, 'run')
 
          graph.applyAndSendPatch(
 
            {delQuads: [], addQuads: [quad(A1, A1, A1)]})
 
          assert.deepEqual([false, true], values)
 

	
 
        it 'when a relevant triple is removed', ->
 
          values = []
light9/web/rdfdbclient.coffee
Show inline comments
 
@@ -16,52 +16,53 @@ toJsonPatch = (jsPatch, cb) ->
 
    writer.end((err, result) ->
 
      out.patch.deletes = result
 
      cb())
 

	
 
  writeAdds = (cb) ->
 
    writer = N3.Writer({ format: 'N-Quads' })
 
    writer.addQuads(jsPatch.addQuads)
 
    writer.end((err, result) ->
 
      out.patch.adds = result
 
      cb())
 
    
 
  async.parallel([writeDels, writeAdds], (err) ->
 
      cb(JSON.stringify(out))
 
    )
 
    cb(JSON.stringify(out))
 
  )
 

	
 
parseJsonPatch = (jsonPatch, cb) ->
 
  # note response cb doesn't have an error arg.
 
  input = JSON.parse(jsonPatch)
 
  patch = {delQuads: [], addQuads: []}
 

	
 
  parseAdds = (cb) =>
 
    parser = N3.Parser()
 
    parser.parse input.patch.adds, (error, quad, prefixes) =>
 
                  if (quad)
 
                    patch.addQuads.push(quad)
 
                  else
 
                    cb()
 
      if (quad)
 
        patch.addQuads.push(quad)
 
      else
 
        cb()
 
  parseDels = (cb) =>
 
    parser = N3.Parser()
 
    parser.parse input.patch.deletes, (error, quad, prefixes) =>
 
                  if (quad)
 
                    patch.delQuads.push(quad)
 
                  else
 
                    cb()
 
      if (quad)
 
        patch.delQuads.push(quad)
 
      else
 
        cb()
 

	
 
  async.parallel([parseAdds, parseDels], ((err) => cb(patch)))
 

	
 
class window.RdfDbClient
 
  # Send and receive patches from rdfdb
 
  constructor: (@patchSenderUrl, @clearGraphOnNewConnection, @applyPatch, @setStatus) ->
 
  constructor: (@patchSenderUrl, @clearGraphOnNewConnection, @applyPatch,
 
                @setStatus) ->
 
    @_patchesToSend = []
 
    @_lastPingMs = -1
 
    @_patchesReceived = 0
 
    @_patchesSent = 0
 

	
 
    @_reconnectionTimeout = null
 
    @_newConnection()
 

	
 
  _updateStatus: ->
 
    ws = (if not @ws? then 'no' else switch @ws.readyState
 
      when @ws.CONNECTING then 'connecting'
 
      when @ws.OPEN then 'open'
 
@@ -71,25 +72,25 @@ class window.RdfDbClient
 

	
 
    ping = if @_lastPingMs > 0 then @_lastPingMs else '...'
 
    @setStatus("#{ws};
 
      #{@_patchesReceived} recv;
 
      #{@_patchesSent} sent;
 
      #{@_patchesToSend.length} pending;
 
      #{ping}ms")
 
 
 
  sendPatch: (patch) ->
 
    console.log('rdfdbclient: queue patch to server ', patchSizeSummary(patch))
 
    @_patchesToSend.push(patch)
 
    @_updateStatus()
 
    @_continueSending()           
 
    @_continueSending()
 

	
 
  _newConnection: ->
 
    wsOrWss = window.location.protocol.replace('http', 'ws')
 
    fullUrl = wsOrWss + '//' + window.location.host + @patchSenderUrl
 
    @ws.close() if @ws?
 
    @ws = new WebSocket(fullUrl)
 

	
 
    @ws.onopen = =>
 
      log('rdfdbclient: connected to', fullUrl)
 
      @_updateStatus()
 
      @clearGraphOnNewConnection()
 
      @_pingLoop()
 
@@ -124,24 +125,24 @@ class window.RdfDbClient
 
    @_patchesReceived++
 
    @_updateStatus()
 

	
 
  _continueSending: ->
 
    if @ws.readyState != @ws.OPEN
 
      setTimeout(@_continueSending.bind(@), 500)
 
      return
 

	
 
    # we could call this less often and coalesce patches together to optimize
 
    # the dragging cases.
 

	
 
    sendOne = (patch, cb) =>
 
        toJsonPatch(patch, (json) =>
 
          log('rdfdbclient: send patch to server, ' + json.length + ' bytes')
 
          @ws.send(json)
 
          @_patchesSent++
 
          @_updateStatus()
 
          cb(null)
 
      toJsonPatch(patch, (json) =>
 
        log('rdfdbclient: send patch to server, ' + json.length + ' bytes')
 
        @ws.send(json)
 
        @_patchesSent++
 
        @_updateStatus()
 
        cb(null)
 
      )
 

	
 
    async.eachSeries(@_patchesToSend, sendOne, () =>
 
        @_patchesToSend = []
 
        @_updateStatus()
 
      )
 
      @_patchesToSend = []
 
      @_updateStatus()
 
    )
light9/web/timeline/adjustable.coffee
Show inline comments
 
@@ -9,90 +9,90 @@ class Adjustable
 
  # widget*, wherever it is, but your drag is moving the *target*. The
 
  # adj will travel around too, but it may do extra moves to not bump
 
  # into stuff or to get out from under your finger.
 

	
 
  constructor: (@config) ->
 
    @ctor2()
 

	
 
  ctor2: () ->
 
    # config has:
 
    #   getTarget -> vec2 of current target position
 
    #   getSuggestedTargetOffset -> vec2 pixel offset from target
 
    #   emptyBox -> true if you want no value display
 
    
 

	
 
    # updated later by layout algoritm
 
    @centerOffset = $V([0, 0])
 

	
 
  getDisplayValue: () ->
 
    return '' if @config.emptyBox
 
    defaultFormat = d3.format(".4g")(@_getValue())
 
    if @config.getDisplayValue?
 
      return @config.getDisplayValue(@_getValue(), defaultFormat) 
 
      return @config.getDisplayValue(@_getValue(), defaultFormat)
 
    defaultFormat
 

	
 
  getSuggestedCenter: () ->
 
    @getTarget().add(@config.getSuggestedTargetOffset())
 

	
 
  getCenter: () -> # vec2 of pixels
 
    @getTarget().add(@centerOffset)
 

	
 
  getTarget: () -> # vec2 of pixels
 
    @config.getTarget()
 
            
 

	
 
  subscribe: (onChange) ->
 
    # change could be displayValue or center or target. This likely
 
    # calls onChange right away if there's any data yet.
 
    throw new Error('not implemented')
 

	
 
  startDrag: () ->
 
    @initialTarget = @getTarget()
 

	
 
  continueDrag: (pos) ->
 
    ## pos is vec2 of pixels relative to the drag start
 
    @targetDraggedTo = pos.add(@initialTarget)
 
    
 

	
 
  endDrag: () ->
 
    # override
 

	
 
  _editorCoordinates: () -> # vec2 of mouse relative to <l9-t-editor>
 
    return @targetDraggedTo
 
    ev = d3.event.sourceEvent
 

	
 
    if ev.target.tagName == "LIGHT9-TIMELINE-EDITOR"
 
      rootElem = ev.target
 
    else
 
      rootElem = ev.target.closest('light9-timeline-editor')
 

	
 
    if ev.touches?.length
 
      ev = ev.touches[0]
 
      
 

	
 
    # storing root on the object to remember it across calls in case
 
    # you drag outside the editor.
 
    @root = rootElem.getBoundingClientRect() if rootElem
 
    offsetParentPos = $V([ev.pageX - @root.left, ev.pageY - @root.top])
 

	
 
    return offsetParentPos 
 
    return offsetParentPos
 

	
 
class window.AdjustableFloatObservable extends Adjustable
 
  constructor: (@config) ->
 
    # config also has:
 
    #   observable -> ko.observable we will read and write
 
    #   getValueForPos(pos) -> what should we set to if the user
 
    #                          moves target to this coord?
 
    super()
 
    @ctor2()
 

	
 
  _getValue: () ->
 
    @config.observable()
 
    
 

	
 
  continueDrag: (pos) ->
 
    # pos is vec2 of pixels relative to the drag start.
 
    super(pos)
 
    epos = @_editorCoordinates()
 
    newValue = @config.getValueForPos(epos)
 
    @config.observable(newValue)
 

	
 
  subscribe: (onChange) ->
 
    log('AdjustableFloatObservable subscribe', @config)
 
    ko.computed =>
 
      @config.observable()
 
      onChange()
 
@@ -101,75 +101,76 @@ class window.AdjustableFloatObject exten
 
  constructor: (@config) ->
 
    # config also has:
 
    #   graph
 
    #   subj
 
    #   pred
 
    #   ctx
 
    #   getTargetPosForValue(value) -> getTarget result for value
 
    #   getValueForPos
 
    super()
 
    @ctor2()
 
    if not @config.ctx?
 
      throw new Error("missing ctx")
 
    @config.graph.runHandler(@_syncValue.bind(@), "adj sync #{@config.subj.value}")
 
    @config.graph.runHandler(@_syncValue.bind(@),
 
                             "adj sync #{@config.subj.value}")
 

	
 
  _syncValue: () ->
 
    @_currentValue = @config.graph.floatValue(@config.subj, @config.pred)
 
    @_onChange() if @_onChange
 
    
 

	
 
  _getValue: () ->
 
    # this is a big speedup- callers use _getValue about 4x as much as
 
    # the graph changes and graph.floatValue is slow
 
    @_currentValue
 

	
 
  getTarget: () ->
 
    @config.getTargetPosForValue(@_getValue())
 
    
 

	
 
  subscribe: (onChange) ->
 
    # only works on one subscription at a time
 
    throw new Error('multi subscribe not implemented') if @_onChange
 
    @_onChange = onChange
 

	
 
  continueDrag: (pos) ->
 
    # pos is vec2 of pixels relative to the drag start
 
    super(pos)
 
    newValue = @config.getValueForPos(@_editorCoordinates())
 
    
 

	
 
    @config.graph.patchObject(@config.subj, @config.pred,
 
                              @config.graph.LiteralRoundedFloat(newValue),
 
                              @config.ctx)
 
                              
 

	
 
class window.AdjustableFade extends Adjustable
 
  constructor: (@yForV, @i0, @i1, @note, offset, ctx) ->
 
    super()
 
    @config = {
 
      getSuggestedTargetOffset: -> offset
 
      getTarget: @getTarget.bind(@)
 
      ctx: ctx
 
    }
 
    @ctor2()
 

	
 
  getTarget: ->
 
    mid = @note.worldPts[@i0].x(.5).add(@note.worldPts[@i1].x(.5))
 
    $V([@note.zoomInX(mid.e(1)), @yForV(mid.e(2))])
 

	
 
  _getValue: ->
 
    mid = @note.worldPts[@i0].x(.5).add(@note.worldPts[@i1].x(.5))
 
    mid.e(1)
 

	
 
   continueDrag: (pos) ->
 
  continueDrag: (pos) ->
 
    # pos is vec2 of pixels relative to the drag start
 
    super(pos)
 
    graph = @note.graph
 
    U = (x) => graph.Uri(x)
 
    U = (x) -> graph.Uri(x)
 

	
 
    goalCenterSec = @note.zoomInX.invert(@initialTarget.e(1) + pos.e(1))
 

	
 
    diamSec = @note.worldPts[@i1].e(1) - @note.worldPts[@i0].e(1)
 
    newSec0 = goalCenterSec - diamSec / 2
 
    newSec1 = goalCenterSec + diamSec / 2
 

	
 
    originSec = graph.floatValue(@note.uri, U(':originTime'))
 

	
 
    p0 = @_makePatch(graph, @i0, newSec0, originSec, @config.ctx)
 
    p1 = @_makePatch(graph, @i1, newSec1, originSec, @config.ctx)
 

	
light9/web/timeline/adjusters.coffee
Show inline comments
 
@@ -31,25 +31,26 @@ coffeeElementSetup(class AdjustersCanvas
 
    if ev.buttons == 1
 
      start = $V([ev.x, ev.y])
 
      adj = @_adjAtPoint(start)
 
      if adj
 
        ev.stopPropagation()
 
        @currentDrag = {start: start, adj: adj}
 
        adj.startDrag()
 

	
 
  onMove: (ev) ->
 
    pos = $V([ev.x, ev.y])
 
    if @currentDrag
 
      @currentDrag.cur = pos
 
      @currentDrag.adj.continueDrag(@currentDrag.cur.subtract(@currentDrag.start))
 
      @currentDrag.adj.continueDrag(
 
        @currentDrag.cur.subtract(@currentDrag.start))
 

	
 
  onUp: (ev) ->
 
    return unless @currentDrag
 
    @currentDrag.adj.endDrag()
 
    @currentDrag = null
 
    
 
  _setAdjuster: (adjId, makeAdjustable) ->
 
    # callers register/unregister the Adjustables they want us to make
 
    # adjuster elements for. Caller invents adjId.  makeAdjustable is
 
    # a function returning the Adjustable or it is null to clear any
 
    # adjusters with this id.
 
    if not @adjs[adjId] or not makeAdjustable?
 
@@ -150,17 +151,17 @@ coffeeElementSetup(class AdjustersCanvas
 
    @ctx.lineWidth = 2
 
    @ctx.setLineDash([3, 3])
 
    @ctx.beginPath()
 
    Drawing.roundRect(@ctx, x1, y1, x2, y2, radius)
 
    @ctx.stroke()
 
    @ctx.setLineDash([])
 

	
 
    @ctx.font = "12px sans"
 
    @ctx.fillStyle = '#000'
 
    @ctx.fillText(label, x1 + 5, y2 - 5, x2 - x1 - 10)
 

	
 
    # coords from a center that's passed in
 
    # # special layout for the thaeter ones with middinh 
 
    # # special layout for the thaeter ones with middinh
 
    # l/r arrows
 
    # mouse arrow cursor upon hover, and accent the hovered adjuster
 
    # connector
 
)
 
\ No newline at end of file
light9/web/timeline/drawing.coffee
Show inline comments
 
@@ -10,26 +10,26 @@ window.Drawing.svgPathFromPoints = (pts)
 
    else
 
      out += 'L '
 
    out += '' + p[0] + ',' + p[1] + ' '
 
    return
 
  out
 

	
 
window.Drawing.line = (ctx, p1, p2) ->
 
  ctx.moveTo(p1.e(1), p1.e(2))
 
  ctx.lineTo(p2.e(1), p2.e(2))
 

	
 
# http://stackoverflow.com/a/4959890
 
window.Drawing.roundRect = (ctx, sx,sy,ex,ey,r) ->
 
    d2r = Math.PI/180
 
    r = ( ( ex - sx ) / 2 ) if ( ex - sx ) - ( 2 * r ) < 0 # ensure that the radius isn't too large for x
 
    r = ( ( ey - sy ) / 2 ) if ( ey - sy ) - ( 2 * r ) < 0 # ensure that the radius isn't too large for y
 
    ctx.beginPath();
 
    ctx.moveTo(sx+r,sy);
 
    ctx.lineTo(ex-r,sy);
 
    ctx.arc(ex-r,sy+r,r,d2r*270,d2r*360,false);
 
    ctx.lineTo(ex,ey-r);
 
    ctx.arc(ex-r,ey-r,r,d2r*0,d2r*90,false);
 
    ctx.lineTo(sx+r,ey);
 
    ctx.arc(sx+r,ey-r,r,d2r*90,d2r*180,false);
 
    ctx.lineTo(sx,sy+r);
 
    ctx.arc(sx+r,sy+r,r,d2r*180,d2r*270,false);
 
    ctx.closePath();
 
  d2r = Math.PI/180
 
  r = ( ( ex - sx ) / 2 ) if ( ex - sx ) - ( 2 * r ) < 0 # ensure that the radius isn't too large for x
 
  r = ( ( ey - sy ) / 2 ) if ( ey - sy ) - ( 2 * r ) < 0 # ensure that the radius isn't too large for y
 
  ctx.beginPath()
 
  ctx.moveTo(sx+r,sy)
 
  ctx.lineTo(ex-r,sy)
 
  ctx.arc(ex-r,sy+r,r,d2r*270,d2r*360,false)
 
  ctx.lineTo(ex,ey-r)
 
  ctx.arc(ex-r,ey-r,r,d2r*0,d2r*90,false)
 
  ctx.lineTo(sx+r,ey)
 
  ctx.arc(sx+r,ey-r,r,d2r*90,d2r*180,false)
 
  ctx.lineTo(sx,sy+r)
 
  ctx.arc(sx+r,sy+r,r,d2r*180,d2r*270,false)
 
  ctx.closePath()
light9/web/timeline/timeline.coffee
Show inline comments
 
log = console.log
 
Drawing = window.Drawing
 
ROW_COUNT = 7
 

	
 
class Project
 
  constructor: (@graph) ->
 

	
 
  makeEffect: (uri) ->
 
    U = (x) => @graph.Uri(x)
 
    effect = U(uri.value + '/effect')
 
    quad = (s, p, o) => @graph.Quad(s, p, o, effect)
 
    
 

	
 
    quads = [
 
      quad(effect, U('rdf:type'), U(':Effect')),
 
      quad(effect, U(':copiedFrom'), uri),
 
      quad(effect, U('rdfs:label'), @graph.Literal(uri.replace(/.*capture\//, ''))),
 
      quad(effect, U(':publishAttr'), U(':strength')),
 
      ]
 

	
 
    fromSettings = @graph.objects(uri, U(':setting'))
 

	
 
    toSettings = @graph.nextNumberedResources(effect + '_set', fromSettings.length)
 
      
 

	
 
    for fs in fromSettings
 
      ts = toSettings.pop()
 
      # full copies of these since I may have to delete captures
 
      quads.push(quad(effect, U(':setting'), ts))
 
      quads.push(quad(ts, U(':device'), @graph.uriValue(fs, U(':device'))))
 
      quads.push(quad(ts, U(':deviceAttr'), @graph.uriValue(fs, U(':deviceAttr'))))
 
      try
 
        quads.push(quad(ts, U(':value'), @graph.uriValue(fs, U(':value'))))
 
      catch
 
        quads.push(quad(ts, U(':scaledValue'), @graph.uriValue(fs, U(':scaledValue'))))
 

	
 
    @graph.applyAndSendPatch({delQuads: [], addQuads: quads})
 
    return effect
 

	
 
  makeNewNote: (song, effect, dropTime, desiredWidthT) ->
 
    U = (x) => @graph.Uri(x)
 
    quad = (s, p, o) => @graph.Quad(s, p, o, song)
 
      
 

	
 
    newNote = @graph.nextNumberedResource("#{song.value}/n")
 
    newCurve = @graph.nextNumberedResource("#{newNote.value}c")
 
    points = @graph.nextNumberedResources("#{newCurve.value}p", 4)
 

	
 
    curveQuads = [
 
        quad(song, U(':note'), newNote)
 
        quad(newNote, U('rdf:type'), U(':Note'))
 
        quad(newNote, U(':originTime'), @graph.LiteralRoundedFloat(dropTime))
 
        quad(newNote, U(':effectClass'), effect)
 
        quad(newNote, U(':curve'), newCurve)
 
        quad(newCurve, U('rdf:type'), U(':Curve'))
 
        quad(newCurve, U(':attr'), U(':strength'))
 
      ]        
 
      ]
 
    pointQuads = []
 

	
 
   
 
    
 

	
 

	
 
    for i in [0...4]
 
      pt = points[i]
 
      pointQuads.push(quad(newCurve, U(':point'), pt))
 
      pointQuads.push(quad(pt, U(':time'), @graph.LiteralRoundedFloat(i/3 * desiredWidthT)))
 
      pointQuads.push(quad(pt, U(':value'), @graph.LiteralRoundedFloat(i == 1 or i == 2)))
 

	
 
    patch = {
 
      delQuads: []
 
      addQuads: curveQuads.concat(pointQuads)
 
      }
 
    @graph.applyAndSendPatch(patch)
 
    
 

	
 
  getCurvePoints: (curve, xOffset) ->
 
    worldPts = []
 
    uris = @graph.objects(curve, @graph.Uri(':point'))
 
    for pt in uris
 
      tm = @graph.floatValue(pt, @graph.Uri(':time'))
 
      val = @graph.floatValue(pt, @graph.Uri(':value'))
 
      v = $V([xOffset + tm, val])
 
      v.uri = pt
 
      worldPts.push(v)
 
    worldPts.sort((a,b) -> a.e(1) > b.e(1))
 
    return [uris, worldPts]
 

	
 
  curveWidth: (worldPts) ->
 
    tMin = @graph.floatValue(worldPts[0].uri, @graph.Uri(':time'))
 
    tMax = @graph.floatValue(worldPts[3].uri, @graph.Uri(':time'))
 
    tMax - tMin
 
      
 

	
 
  deleteNote: (song, note, selection) ->
 
    patch = {delQuads: [@graph.Quad(song, graph.Uri(':note'), note, song)], addQuads: []}
 
    @graph.applyAndSendPatch(patch)
 
    if note in selection.selected()
 
      selection.selected(_.without(selection.selected(), note))
 

	
 
    
 

	
 
coffeeElementSetup(class TimelineEditor extends Polymer.mixinBehaviors([Polymer.IronResizableBehavior], Polymer.Element)
 
  @is: 'light9-timeline-editor'
 
  @getter_properties:
 
    viewState: { type: Object }
 
    debug: {type: String}
 
    graph: {type: Object, notify: true}
 
    project: {type: Object}
 
    setAdjuster: {type: Function, notify: true}
 
    playerSong: {type: String, notify: true}
 
    followPlayerSong: {type: Boolean, notify: true, value: true}
 
    song: {type: String, notify: true}
 
    show: {type: String, notify: true}
 
@@ -110,96 +110,96 @@ coffeeElementSetup(class TimelineEditor 
 
    selection: {type: Object, notify: true}
 
  @getter_observers: [
 
    '_onSong(playerSong, followPlayerSong)',
 
    '_onGraph(graph)',
 
    '_onSongDuration(songDuration, viewState)',
 
    '_onSongTime(songTime, viewState)',
 
    '_onSetAdjuster(setAdjuster)',
 
  ]
 
  constructor: ->
 
    super()
 
    @viewState = new ViewState()
 
    window.viewState = @viewState
 
    
 

	
 
  ready: ->
 
    super.ready()
 
    @addEventListener 'mousedown', (ev) => @$.adjustersCanvas.onDown(ev)
 
    @addEventListener 'mousemove', (ev) => @$.adjustersCanvas.onMove(ev)
 
    @addEventListener 'mouseup', (ev) => @$.adjustersCanvas.onUp(ev)
 

	
 
    ko.options.deferUpdates = true
 
    
 

	
 
    @selection = {hover: ko.observable(null), selected: ko.observable([])}
 

	
 
    window.debug_zoomOrLayoutChangedCount = 0
 
    window.debug_adjUpdateDisplay = 0
 
    
 

	
 
    ko.computed(@zoomOrLayoutChanged.bind(@))
 

	
 
    @trackMouse()
 
    @bindKeys()
 
    @bindWheelZoom(@)
 

	
 
    setInterval(@updateDebugSummary.bind(@), 100)
 

	
 
    @addEventListener('iron-resize', @_onIronResize.bind(@))
 
    Polymer.RenderStatus.afterNextRender(this, @_onIronResize.bind(@))
 

	
 
    Polymer.RenderStatus.afterNextRender this, =>
 
      setupDrop(@$.zoomed.$.rows, @$.zoomed.$.rows, @, @$.zoomed.onDrop.bind(@$.zoomed))
 
            
 

	
 
  _onIronResize: ->
 
    @viewState.setWidth(@offsetWidth)
 
    @viewState.coveredByDiagramTop(@$.coveredByDiagram.offsetTop)
 
    @viewState.rowsY(@$.zoomed.$.rows.offsetTop) if @$.zoomed?.$?.rows?
 
    @viewState.audioY(@$.audio.offsetTop)
 
    @viewState.audioH(@$.audio.offsetHeight)
 
    if @$.zoomed?.$?.time?
 
      @viewState.zoomedTimeY(@$.zoomed.$.time.offsetTop)
 
      @viewState.zoomedTimeH(@$.zoomed.$.time.offsetHeight)
 
    
 

	
 
  _onSongTime: (t) ->
 
    @viewState.cursor.t(t)
 
    
 

	
 
  _onSongDuration: (d) ->
 
    d = 700 if d < 1 # bug is that asco isn't giving duration, but 0 makes the scale corrupt
 
    @viewState.zoomSpec.duration(d)
 
    
 

	
 
  _onSong: (s) ->
 
    @song = @playerSong if @followPlayerSong
 
    
 

	
 
  _onGraph: (graph) ->
 
    @project = new Project(graph)
 
    @show = 'http://light9.bigasterisk.com/show/dance2017'
 

	
 
  _onSetAdjuster: () ->
 
    @makeZoomAdjs()
 
    
 

	
 
  updateDebugSummary: ->
 
    elemCount = (tag) -> document.getElementsByTagName(tag).length
 
    @debug = "#{window.debug_zoomOrLayoutChangedCount} layout change,
 
     #{elemCount('light9-timeline-note')} notes,
 
     #{@selection.selected().length} selected
 
     #{elemCount('light9-timeline-graph-row')} rows,
 
     #{window.debug_adjsCount} adjuster items registered,
 
     #{window.debug_adjUpdateDisplay} adjuster updateDisplay calls,
 
    "
 

	
 
  zoomOrLayoutChanged: ->
 
    vs = @viewState
 
    dependOn = [vs.zoomSpec.t1(), vs.zoomSpec.t2(), vs.width()]
 

	
 
    # shouldn't need this- deps should get it
 
    @$.zoomed.gatherNotes() if @$.zoomed?.gatherNotes?
 
  
 

	
 
    # todo: these run a lot of work purely for a time change
 
    if @$.zoomed?.$?.audio?
 
      #@dia.setTimeAxis(vs.width(), @$.zoomed.$.audio.offsetTop, vs.zoomInX)
 
      @$.adjustersCanvas.updateAllCoords()
 

	
 
  trackMouse: ->
 
    # not just for show- we use the mouse pos sometimes
 
    for evName in ['mousemove', 'touchmove']
 
      @addEventListener evName, (ev) =>
 
        ev.preventDefault()
 

	
 
        # todo: consolidate with _editorCoordinates version
 
@@ -228,150 +228,150 @@ coffeeElementSetup(class TimelineEditor 
 
      @$.music.seekPlayOrPause(@viewState.latestMouseTime())
 
    shortcut.add "Ctrl+Escape", => @viewState.frameAll()
 
    shortcut.add "Shift+Escape", => @viewState.frameToEnd()
 
    shortcut.add "Escape", => @viewState.frameCursor()
 
    shortcut.add "L", =>
 
      @$.adjustersCanvas.updateAllCoords()
 
    shortcut.add 'Delete', =>
 
      for note in @selection.selected()
 
        @project.deleteNote(@graph.Uri(@song), note, @selection)
 

	
 
  makeZoomAdjs: ->
 
    yMid = => @$.audio.offsetTop + @$.audio.offsetHeight / 2
 
    
 

	
 
    valForPos = (pos) =>
 
        x = pos.e(1)
 
        t = @viewState.fullZoomX.invert(x)
 
      x = pos.e(1)
 
      t = @viewState.fullZoomX.invert(x)
 
    @setAdjuster('zoom-left', => new AdjustableFloatObservable({
 
      observable: @viewState.zoomSpec.t1,
 
      getTarget: () =>
 
        $V([@viewState.fullZoomX(@viewState.zoomSpec.t1()), yMid()])
 
      getSuggestedTargetOffset: () => $V([50, 0])
 
      getValueForPos: valForPos
 
    }))
 

	
 
    @setAdjuster('zoom-right', => new AdjustableFloatObservable({
 
      observable: @viewState.zoomSpec.t2,
 
      getTarget: () =>
 
        $V([@viewState.fullZoomX(@viewState.zoomSpec.t2()), yMid()])
 
      getSuggestedTargetOffset: () => $V([-50, 0])
 
      getValueForPos: valForPos
 
    }))
 

	
 
    panObs = ko.pureComputed({
 
        read: () =>
 
          (@viewState.zoomSpec.t1() + @viewState.zoomSpec.t2()) / 2
 
        write: (value) =>
 
          zs = @viewState.zoomSpec
 
          span = zs.t2() - zs.t1()
 
          zs.t1(value - span / 2)
 
          zs.t2(value + span / 2)
 
      })
 
      read: () =>
 
        (@viewState.zoomSpec.t1() + @viewState.zoomSpec.t2()) / 2
 
      write: (value) =>
 
        zs = @viewState.zoomSpec
 
        span = zs.t2() - zs.t1()
 
        zs.t1(value - span / 2)
 
        zs.t2(value + span / 2)
 
    })
 

	
 
    @setAdjuster('zoom-pan', => new AdjustableFloatObservable({
 
      observable: panObs
 
      emptyBox: true
 
      # fullzoom is not right- the sides shouldn't be able to go
 
      # offscreen
 
      getTarget: () => $V([@viewState.fullZoomX(panObs()), yMid()])
 
      getSuggestedTargetOffset: () => $V([0, 0])
 
      getValueForPos: valForPos
 
      }))
 
    }))
 
)
 

	
 

	
 
# plan: in here, turn all the notes into simple js objects with all
 
# their timing data and whatever's needed for adjusters. From that, do
 
# the brick layout. update only changing adjusters.
 
coffeeElementSetup(class TimeZoomed extends Polymer.mixinBehaviors([Polymer.IronResizableBehavior], Polymer.Element)
 
  @is: 'light9-timeline-time-zoomed'
 
  @getter_properties:
 
    graph: { type: Object, notify: true }
 
    project: { type: Object }
 
    selection: { type: Object, notify: true }
 
    song: { type: String, notify: true }
 
    viewState: { type: Object, notify: true }
 
    inlineAttrConfigs: { type: Array, value: [] } # only for inlineattrs that should be displayed
 
  @getter_observers: [
 
    '_onGraph(graph, setAdjuster, song, viewState, project)',
 
    'onZoom(viewState)',
 
  ]
 
  constructor: ->
 
    super()
 
    @notes = []
 
    @stage = new PIXI.Container()
 
    @stage.interactive=true
 
    
 

	
 
    @renderer = PIXI.autoDetectRenderer({
 
        backgroundColor: 0x606060,
 
        antialias: true,
 
        forceCanvas: true,
 
      backgroundColor: 0x606060,
 
      antialias: true,
 
      forceCanvas: true,
 
    })
 
     
 

	
 
  ready: ->
 
    super.ready()
 

	
 
    @addEventListener('iron-resize', @_onResize.bind(@))
 
    Polymer.RenderStatus.afterNextRender(this, @_onResize.bind(@))
 
    
 

	
 
    @$.rows.appendChild(@renderer.view)
 

	
 
    # This works for display, but pixi hit events didn't correctly
 
    # move with the objects, so as a workaround, I extended the top of
 
    # the canvas in _onResize.
 
    # 
 
    #
 
    #ko.computed =>
 
    #  @stage.setTransform(0, -(@viewState.rowsY()), 1, 1, 0, 0, 0, 0, 0)
 
      
 

	
 
  _onResize: ->
 
    @$.rows.firstChild.style.position = 'relative'
 
    @$.rows.firstChild.style.top = -@viewState.rowsY() + 'px'
 

	
 
    @renderer.resize(@clientWidth, @clientHeight + @viewState.rowsY())
 
    
 

	
 
    @renderer.render(@stage)
 
  
 

	
 
  _onGraph: (graph, setAdjuster, song, viewState, project)->
 
    return unless @song # polymer will call again
 
    @graph.runHandler(@gatherNotes.bind(@), 'zoom notes')
 
  onZoom: ->
 
    updateZoomFlattened = ->
 
      log('updateZoomFlattened')
 
      @zoomFlattened = ko.toJS(@viewState.zoomSpec)
 
    ko.computed(updateZoomFlattened.bind(@))
 

	
 
  gatherNotes: ->
 
    U = (x) => @graph.Uri(x)
 
    return unless @song?
 

	
 
    songNotes = @graph.objects(U(@song), U(':note'))
 
    
 

	
 
    @stage.removeChildren()
 
    n.destroy() for n in @notes
 
    @notes = []
 
    
 

	
 
    noteNum = 0
 
    for uri in _.sortBy(songNotes, 'id')
 
      con = new PIXI.Container()
 
      con.interactive=true
 
      @stage.addChild(con)
 
      
 

	
 
      row = noteNum % 6
 
      rowTop = @viewState.rowsY() + 20 + 150 * row
 
      note = new Note(@, con, @project, @graph, @selection, uri, @setAdjuster, U(@song), @viewState, rowTop, rowTop + 140)
 
      @notes.push(note)
 
      noteNum = noteNum + 1
 
 
 

	
 
    @renderer.render(@stage)
 
    
 

	
 
  onDrop: (effect, pos) ->
 
    U = (x) => @graph.Uri(x)
 

	
 
    return unless effect and effect.match(/^http/)
 

	
 
    # we could probably accept some initial overrides right on the
 
    # effect uri, maybe as query params
 

	
 
    if not @graph.contains(effect, U('rdf:type'), U(':Effect'))
 
      if @graph.contains(effect, U('rdf:type'), U(':LightSample'))
 
        effect = @project.makeEffect(effect)
 
      else
 
@@ -438,122 +438,122 @@ class Note
 
    U = (x) => @graph.Uri(x)
 
    originTime = @graph.floatValue(subj, U(':originTime'))
 

	
 
    for curve in @graph.objects(subj, U(':curve'))
 
      if @graph.uriValue(curve, U(':attr')).equals(curveAttr)
 
        return @project.getCurvePoints(curve, originTime)
 
    throw new Error("curve #{@uri.value} has no attr #{curveAttr.value}")
 

	
 
  draw: ->
 
    U = (x) => @graph.Uri(x)
 
    [pointUris, worldPts] = @getCurvePoints(@uri, U(':strength'))
 
    effect = @graph.uriValue(@uri, U(':effectClass'))
 
    
 

	
 
    yForV = (v) => @rowBotY + (@rowTopY - @rowBotY) * v
 
    dependOn = [@viewState.zoomSpec.t1(), @viewState.zoomSpec.t2(), @viewState.width()]
 
    screenPts = (new PIXI.Point(@viewState.zoomInX(pt.e(1)), yForV(pt.e(2))) for pt in worldPts)
 

	
 
    @container.removeChildren()    
 
    @container.removeChildren()
 
    graphics = new PIXI.Graphics({nativeLines: false})
 
    graphics.interactive = true
 
    @container.addChild(graphics)
 

	
 
    shape = new PIXI.Polygon(screenPts)
 
    graphics.beginFill(@_noteColor(effect), .313)
 
    graphics.drawShape(shape)
 
    graphics.endFill()
 

	
 
    # stroke should vary with @selection.hover() == @uri and with @uri in @selection.selected()
 
    # 
 
    #
 
    # #notes > path.hover {stroke-width: 1.5; stroke: #888;}
 
    # #notes > path.selected {stroke-width: 5; stroke: red;}
 
    graphics.lineStyle(2, 0xffd900, 1)
 
    graphics.moveTo(screenPts[0].x, screenPts[0].y)
 
    for p in screenPts.slice(1)
 
      graphics.lineTo(p.x, p.y)
 

	
 
    graphics.on 'mousedown', (ev) =>
 
      log('down gfx', @uri.value)
 
      @_onMouseDown(ev)
 

	
 
    graphics.on 'mouseover', =>
 
      log('hover', @uri.value)
 
      @selection.hover(@uri)
 

	
 
    graphics.on 'mouseout', =>
 
      log('hoverout', @uri.value)
 
      @selection.hover(null)
 

	
 
    @graphics = graphics
 
    curveWidthCalc = () => @project.curveWidth(worldPts)
 
    @_updateAdjusters(screenPts, worldPts, curveWidthCalc, yForV, @song)
 
    @_updateInlineAttrs(screenPts)
 
    
 

	
 
  onUri: ->
 
    @graph.runHandler(@update.bind(@), "note updates #{@uri}")
 

	
 
  patchCouldAffectMe: (patch) ->
 
    if patch and patch.addQuads # sometimes patch is a polymer-sent value. @update is used as a listener too
 
      if patch.addQuads.length == patch.delQuads.length == 1
 
        add = patch.addQuads[0]
 
        del = patch.delQuads[0]
 
        if (add.predicate.equals(del.predicate) and del.predicate.equals(@graph.Uri(':time')) and add.subject.equals(del.subject))
 
          timeEditFor = add.subject
 
          if @worldPts and timeEditFor not in @pointUris
 
            return false
 
    return true
 
            
 

	
 
  update: (patch) ->
 
    # update our note DOM and SVG elements based on the graph
 
    if not @patchCouldAffectMe(patch)
 
      # as autodep still fires all handlers on all patches, we just
 
      # need any single dep to cause another callback. (without this,
 
      # we would no longer be registered at all)
 
      @graph.subjects(@uri, @uri, @uri)
 
      return
 
    if @isDetached?
 
      return
 

	
 
    @_updateDisplay()
 

	
 
  _updateAdjusters: (screenPts, worldPts, curveWidthCalc, yForV, ctx) ->
 
    if screenPts[screenPts.length - 1].x - screenPts[0].x < 100
 
      @clearAdjusters()
 
    else
 
      @_makeOffsetAdjuster(yForV, curveWidthCalc, ctx)
 
      @_makeCurvePointAdjusters(yForV, worldPts, ctx)
 
      @_makeFadeAdjusters(yForV, ctx, worldPts)
 

	
 
  _updateInlineAttrs: (screenPts) ->
 
    w = 280
 
    
 

	
 
    leftX = Math.max(2, screenPts[Math.min(1, screenPts.length - 1)].x + 5)
 
    rightX = screenPts[Math.min(2, screenPts.length - 1)].x - 5
 
    if screenPts.length < 3
 
      rightX = leftX + w
 

	
 
    if rightX - leftX < w or rightX < w or leftX > @parentElem.offsetWidth
 
      @parentElem.updateInlineAttrs(@uri, null)
 
      return
 

	
 
    config = {
 
      uri: @uri,
 
      left: leftX,
 
      top: @rowTopY + 5,
 
      width: w,
 
      height: @rowBotY - @rowTopY - 15,
 
      }
 

	
 
    @parentElem.updateInlineAttrs(@uri, config)
 
    
 

	
 
  _makeCurvePointAdjusters: (yForV, worldPts, ctx) ->
 
    for pointNum in [0...worldPts.length]
 
      @_makePointAdjuster(yForV, worldPts, pointNum, ctx)
 

	
 
  _makePointAdjuster: (yForV, worldPts, pointNum, ctx) ->
 
    U = (x) => @graph.Uri(x)
 

	
 
    adjId = @uri.value + '/p' + pointNum
 
    @adjusterIds[adjId] = true
 
    @setAdjuster adjId, =>
 
      adj = new AdjustableFloatObject({
 
        graph: @graph
 
@@ -570,25 +570,25 @@ class Note
 
      adj._getValue = (=>
 
        # note: don't use originTime from the closure- we need the
 
        # graph dependency
 
        adj._currentValue + @graph.floatValue(@uri, U(':originTime'))
 
        )
 
      adj
 

	
 
  _makeOffsetAdjuster: (yForV, curveWidthCalc, ctx) ->
 
    U = (x) => @graph.Uri(x)
 

	
 
    adjId = @uri.value + '/offset'
 
    @adjusterIds[adjId] = true
 
    @setAdjuster adjId, => 
 
    @setAdjuster adjId, =>
 
      adj = new AdjustableFloatObject({
 
        graph: @graph
 
        subj: @uri
 
        pred: U(':originTime')
 
        ctx: ctx
 
        getDisplayValue: (v, dv) => "o=#{dv}"
 
        getTargetPosForValue: (value) =>
 
          # display bug: should be working from pt[0].t, not from origin
 
          $V([@viewState.zoomInX(value + curveWidthCalc() / 2), yForV(.5)])
 
        getValueForPos: (pos) =>
 
          @viewState.zoomInX.invert(pos.e(1)) - curveWidthCalc() / 2
 
        getSuggestedTargetOffset: () => $V([-10, 0])
 
@@ -596,49 +596,49 @@ class Note
 
      adj
 

	
 
  _makeFadeAdjusters: (yForV, ctx, worldPts) ->
 
    U = (x) => @graph.Uri(x)
 
    @_makeFadeAdjuster(yForV, ctx, @uri.value + '/fadeIn', 0, 1, $V([-50, -10]))
 
    n = worldPts.length
 
    @_makeFadeAdjuster(yForV, ctx, @uri.value + '/fadeOut', n - 2, n - 1, $V([50, -10]))
 

	
 
  _makeFadeAdjuster: (yForV, ctx, adjId, i0, i1, offset) ->
 
    return # not ready- AdjustableFade looks in Note object
 
    @adjusterIds[adjId] = true
 
    @setAdjuster adjId, => new AdjustableFade(yForV, i0, i1, @, offset, ctx)
 
    
 

	
 
  _suggestedOffset: (pt) ->
 
    if pt.e(2) > .5
 
      $V([0, 30])
 
    else
 
      $V([0, -30])
 
  
 

	
 
  _onMouseDown: (ev) ->
 
    sel = @selection.selected()
 
    if ev.data.originalEvent.ctrlKey
 
      if @uri in sel
 
        sel = _.without(sel, @uri)
 
      else
 
        sel.push(@uri)
 
    else
 
      sel = [@uri]
 
    @selection.selected(sel)
 

	
 
  _noteColor: (effect) ->
 
    effect = effect.value
 
    if effect in ['http://light9.bigasterisk.com/effect/blacklight',
 
      'http://light9.bigasterisk.com/effect/strobewarm']
 
                  'http://light9.bigasterisk.com/effect/strobewarm']
 
      hue = 0
 
      sat = 100
 
    else        
 
    else
 
      hash = 0
 
      for i in [(effect.length-10)...effect.length]
 
        hash += effect.charCodeAt(i)
 
      hue = (hash * 8) % 360
 
      sat = 40 + (hash % 20) # don't conceal colorscale too much
 

	
 
    return parseInt(tinycolor.fromRatio({h: hue / 360, s: sat / 100, l: .58}).toHex(), 16)
 

	
 
    #elem = @getOrCreateElem(uri+'/label', 'noteLabels', 'text', {style: "font-size:13px;line-height:125%;font-family:'Verana Sans';text-align:start;text-anchor:start;fill:#000000;"})
 
    #elem.setAttribute('x', curvePts[0].e(1)+20)
 
    #elem.setAttribute('y', curvePts[0].e(2)-10)
 
    #elem.innerHTML = effectLabel;
 
    #elem.innerHTML = effectLabel
light9/web/timeline/viewstate.coffee
Show inline comments
 
@@ -14,25 +14,25 @@ class window.ViewState
 
    # all these are relative to #coveredByDiagram:
 
    @audioY = ko.observable(0)
 
    @audioH = ko.observable(0)
 
    @zoomedTimeY = ko.observable(0)
 
    @zoomedTimeH = ko.observable(0)
 
    @rowsY = ko.observable(0)
 
      
 
    @fullZoomX = d3.scaleLinear()
 
    @zoomInX = d3.scaleLinear()
 

	
 
    @zoomAnimSec = .1
 

	
 
    ko.computed(@maintainZoomLimitsAndScales.bind(@))    
 
    ko.computed(@maintainZoomLimitsAndScales.bind(@))
 
 
 
  setWidth: (w) ->
 
    @width(w)
 
    @maintainZoomLimitsAndScales() # before other handlers run
 
    
 
  maintainZoomLimitsAndScales: () ->
 
    log('maintainZoomLimitsAndScales')
 
    # not for cursor updates
 

	
 
    if @zoomSpec.t1() < 0
 
      @zoomSpec.t1(0)
 
    if @zoomSpec.duration() and @zoomSpec.t2() > @zoomSpec.duration()
 
@@ -78,15 +78,15 @@ class window.ViewState
 
    oldT2 = @zoomSpec.t2()
 
    lastTime = 0
 
    for step in [0..secs * fps]
 
      frac = step / (secs * fps)
 
      do (frac) =>
 
        gotoStep = =>
 
          @zoomSpec.t1((1 - frac) * oldT1 + frac * newT1)
 
          @zoomSpec.t2((1 - frac) * oldT2 + frac * newT2)
 
        delay = frac * secs * 1000
 
        setTimeout(gotoStep, delay)
 
        lastTime = delay
 
    setTimeout(=>
 
        @zoomSpec.t1(newT1)
 
        @zoomSpec.t2(newT2)
 
      , lastTime + 10)  
 
      @zoomSpec.t1(newT1)
 
      @zoomSpec.t2(newT2)
 
    , lastTime + 10)
package.json
Show inline comments
 
@@ -5,24 +5,25 @@
 
  "license": "MIT",
 
  "description": "Mini instructions:",
 
  "main": "index.js",
 
  "directories": {
 
    "test": "test"
 
  },
 
  "dependencies": {
 
    "@webcomponents/shadycss": "^1.1.3",
 
    "@webcomponents/webcomponentsjs": "^1.2.0",
 
    "bower": "^1.8.4",
 
    "browserify": "^16.2.0",
 
    "chai": "^3.5.0",
 
    "coffeelint": "^2.1.0",
 
    "coffeescript": "^2.3.0",
 
    "d3": "^5.1.0",
 
    "mocha": "^2.5.3",
 
    "n3": "^1.0.0-alpha",
 
    "pixi.js": "^4.7.3",
 
    "tinycolor2": "^1.4.1"
 
  },
 
  "devDependencies": {
 
    "mocha": "^2.5.3"
 
  },
 
  "scripts": {
 
    "test": "mocha"
0 comments (0 inline, 0 general)