Files @ 4bab5bbce195
Branch filter:

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

drewp@bigasterisk.com
show-specific changes
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
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


@customElement('light9-timeline-editor')
class TimelineEditor extends LitElement
  @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}
    songTime: {type: Number, notify: true}
    songDuration: {type: Number, notify: true}
    songPlaying: {type: Boolean, notify: true}
    selection: {type: Object, notify: true}
  @getter_observers: [
    '_onSong(playerSong, followPlayerSong)',
    '_onGraph(graph)',
    '_onSongDuration(songDuration, viewState)',
    '_onSongTime(song, playerSong, 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: (song, playerSong, t) ->
    if song != playerSong
      @viewState.cursor.t(0)
      return
    @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 = showRoot

  _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
        if ev.touches?.length
          ev = ev.touches[0]

        root = @$.cursorCanvas.getBoundingClientRect()
        @viewState.mouse.pos($V([ev.pageX - root.left, ev.pageY - root.top]))

        # should be controlled by a checkbox next to follow-player-song-choice
        @sendMouseToVidref() unless window.location.hash.match(/novidref/)

  sendMouseToVidref: ->
    now = Date.now()
    if (!@$.vidrefLastSent? || @$.vidrefLastSent < now - 200) && !@songPlaying
      @$.vidrefTime.body = {t: @viewState.latestMouseTime(), source: 'timeline', song: @song}
      @$.vidrefTime.generateRequest()
      @$.vidrefLastSent = now

  bindWheelZoom: (elem) ->
    elem.addEventListener 'mousewheel', (ev) =>
      @viewState.onMouseWheel(ev.deltaY)

  bindKeys: ->
    shortcut.add "Ctrl+P", (ev) =>
      @$.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)
    @setAdjuster('zoom-left', => new AdjustableFloatObservable({
      observable: @viewState.zoomSpec.t1,
      getTarget: () =>
        $V([@viewState.fullZoomX(@viewState.zoomSpec.t1()), yMid()])
      getSuggestedTargetOffset: () => $V([-50, 10])
      getValueForPos: valForPos
    }))

    @setAdjuster('zoom-right', => new AdjustableFloatObservable({
      observable: @viewState.zoomSpec.t2,
      getTarget: () =>
        $V([@viewState.fullZoomX(@viewState.zoomSpec.t2()), yMid()])
      getSuggestedTargetOffset: () => $V([50, 10])
      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)
    })

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