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