Files @ 8e0ae92d918d
Branch filter:

Location: light9/web/timeline/TimeZoomed.coffee - annotation

drewp@bigasterisk.com
metrics link on home
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
4556eebe5d73
log = debug('timeline')
debug.enable('*')

Drawing = window.Drawing
ROW_COUNT = 7


# 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.
@customElement('light9-timeline-time-zoomed')
class TimeZoomed extends LitElement
  @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
    imageSamples: { type: Array, value: [] }
  @getter_observers: [
    '_onGraph(graph, setAdjuster, song, viewState, project)',
    'onZoom(viewState)',
    '_onViewState(viewState)',
  ]
  constructor: ->
    super()
    @numRows = 6
    @noteByUriStr = new Map()
    @stage = new PIXI.Container()
    @stage.interactive=true

    @renderer = PIXI.autoDetectRenderer({
      backgroundColor: 0x606060,
      antialias: true,
      forceCanvas: true,
    })
    @bg = new PIXI.Container()
    @stage.addChild(@bg)

    @dirty = _.debounce(@_repaint.bind(@), 10)

  ready: ->
    super.ready()

    @imageSamples = ['one']

    @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())

    @dirty()

  _onGraph: (graph, setAdjuster, song, viewState, project)->
    return unless @song # polymer will call again
    @graph.runHandler(@gatherNotes.bind(@), 'zoom notes')

  _onViewState: (viewState) ->
    @brickLayout = new BrickLayout(@viewState, @numRows)

  noteDirty: -> @dirty()
    
  onZoom: ->
    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'))

    toRemove = new Set(@noteByUriStr.keys())
    
    for uri in @graph.sortedUris(songNotes)
      had = toRemove.delete(uri.value)
      if not had
        @_addNote(uri)

    toRemove.forEach @_delNote.bind(@)

    @dirty()

  isActiveNote: (note) -> @noteByUriStr.has(note.value)

  _repaint: ->
    @_drawGrid()  
    @renderer.render(@stage)

  _drawGrid: ->
    # maybe someday this has snappable timing markers too
    @bg.removeChildren()
    gfx = new PIXI.Graphics()
    @bg.addChild(gfx)

    gfx.lineStyle(1, 0x222222, 1)
    for row in [0...@numRows]
      y = @brickLayout.rowBottom(row)
      gfx.moveTo(0, y)
      gfx.lineTo(@clientWidth, y)

  _addNote: (uri) ->
    U = (x) => @graph.Uri(x)
    
    con = new PIXI.Container()
    con.interactive=true
    @stage.addChild(con)
    
    note = new Note(@, con, @project, @graph, @selection, uri, @setAdjuster, U(@song), @viewState, @brickLayout)
    # this must come before the first Note.draw
    @noteByUriStr.set(uri.value, note)
    @brickLayout.addNote(note, note.onRowChange.bind(note))
    note.initWatchers()

  _delNote: (uriStr) ->
    n = @noteByUriStr.get(uriStr)
    @brickLayout.delNote(n)
    @stage.removeChild(n.container)
    n.destroy()
    @noteByUriStr.delete(uriStr)
            
  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
        log("drop #{effect} is not an effect")
        return

    dropTime = @viewState.zoomInX.invert(pos.e(1))

    desiredWidthX = @offsetWidth * .3
    desiredWidthT = @viewState.zoomInX.invert(desiredWidthX) - @viewState.zoomInX.invert(0)
    desiredWidthT = Math.min(desiredWidthT, @viewState.zoomSpec.duration() - dropTime)
    @project.makeNewNote(U(@song), U(effect), dropTime, desiredWidthT)

  updateInlineAttrs: (note, config) ->
    if not config?
      index = 0
      for c in @inlineAttrConfigs
        if c.uri.equals(note)
          @splice('inlineAttrConfigs', index)
          return
        index += 1
    else
      index = 0
      for c in @inlineAttrConfigs
        if c.uri.equals(note)
          @splice('inlineAttrConfigs', index, 1, config)
          return
        index += 1
      @push('inlineAttrConfigs', config)