Changeset - 8406a9fbeb9d
[Not reviewed]
default
0 3 0
Drew Perttula - 9 years ago 2016-06-04 09:27:38
drewp@bigasterisk.com
note shape can be edited
Ignore-this: 1d4957c88191336d7b61a7c782897f59
3 files changed with 40 insertions and 17 deletions:
0 comments (0 inline, 0 general)
light9/web/graph.coffee
Show inline comments
 
# Patch is {addQuads: <quads>, delQuads: <quads>}
 
# <quads> is [{subject: s, ...}, ...]
 

	
 
# partial port of autodepgraphapi.py
 
class GraphWatchers
 
  constructor: ->
 
    @handlersSp = {} # {s: {p: [handlers]}}
 
  subscribe: (s, p, o, onChange) -> # return subscription handle
 
    if o? then throw Error('not implemented')
 
    if not @handlersSp[s]
 
      @handlersSp[s] = {}
 
    if not @handlersSp[s][p]
 
      @handlersSp[s][p] = []
 
    @handlersSp[s][p].push(onChange)
 
    
 
  unsubscribe: (subscription) ->
 
    throw Error('not implemented')
 

	
 
  matchingHandlers: (quad) ->
 
    matches = []
 
    for subjDict in [@handlersSp[quad.subject] || {}, @handlersSp[null] || {}]
 
      for subjPredMatches in [subjDict[quad.predicate] || [], subjDict[null] || []]
 
        matches = matches.concat(subjPredMatches)
 
    return matches
 
    
 
  graphChanged: (patch) ->
 
    for quad in patch.delQuads
 
      for cb in ((@handlersSp[quad.subject] || {})[quad.predicate] || [])
 
      for cb in @matchingHandlers(quad)
 
        # currently calls multiple times, which is ok, but we might
 
        # group things into fewer patches
 
        cb({delQuads: [quad], addQuads: []})
 
    for quad in patch.addQuads
 
      for cb in ((@handlersSp[quad.subject] || {})[quad.predicate] || [])
 
      for cb in @matchingHandlers(quad)
 
        cb({delQuads: [], addQuads: [quad]})
 

	
 

	
 
class window.SyncedGraph
 
  # Note that applyPatch is the only method to write to the graph, so
 
  # it can fire subscriptions.
 

	
 
  constructor: (patchSenderUrl, prefixes) ->
 
    @graph = N3.Store()
 
    @_addPrefixes(prefixes)
 
    @_watchers = new GraphWatchers()
 

	
 
  _addPrefixes: (prefixes) ->
 
    @graph.addPrefixes(prefixes)
 
        
 
  Uri: (curie) ->
 
    N3.Util.expandPrefixedName(curie, @graph._prefixes)
 

	
 
  Literal: (jsValue) ->
 
    N3.Util.createLiteral(jsValue)
 

	
 
  toJs: (literal) ->
 
    # incomplete
 
    parseFloat(N3.Util.getLiteralValue(literal))
light9/web/timeline-elements.html
Show inline comments
 
@@ -26,94 +26,96 @@
 
         flex-grow: 1;
 
     }
 
     light9-timeline-diagram-layer, light9-timeline-adjusters {
 
         position: absolute;
 
         left: 0; top: 0; right: 0; bottom: 0;
 
     }
 
     #debug {
 
         position: fixed;
 
         right: 0;
 
         bottom: 0;
 
     }
 
    </style>
 
    <div>
 
      <rdfdb-synced-graph graph="{{graph}}"></rdfdb-synced-graph>
 
      timeline editor: song [uri] <button>unlink</button>
 
      <label><input type="checkbox"> follow player song choice</label>
 
    </div>
 
    <!--
 
         Old zoom menu:
 
         See current time .. esc
 
         See from current time -> end .. shift+esc
 
         Zoom all .. ctrl+esc
 
       -->
 
    <light9-timeline-audio id="audio"></light9-timeline-audio>
 
    <light9-timeline-time-zoomed id="zoomed" zoom="{{viewState.zoomSpec}}"></light9-timeline-time-zoomed>
 
    <light9-timeline-time-zoomed id="zoomed" graph="{{graph}}" zoom="{{viewState.zoomSpec}}" zoom-in-x="{{zoomInX}}"></light9-timeline-time-zoomed>
 
    <light9-timeline-diagram-layer id="dia"></light9-timeline-diagram-layer>
 
    <light9-timeline-adjusters id="adjusters" dia="{{dia}}" adjs="{{adjs}}"></light9-timeline-adjusters>
 
    <div id="debug">[[debug]]</div>
 
  </template>
 
 
 
</dom-module>
 

	
 

	
 
<!-- the whole section that pans/zooms in time (most of the editor) -->
 
<dom-module id="light9-timeline-time-zoomed">
 
  <template>
 
    <style>
 
     :host {
 
         display: flex;
 
         height: 100%;
 
     }
 
     div {
 
         display: flex;
 
         flex-direction: column;
 
         height: 100%;
 
     }
 
     light9-timeline-audio {
 
         width: 100%;
 
         height: 90px;
 
     }
 
     light9-timeline-graph-row {
 
         flex-grow: 1;
 
     }
 
    </style>
 
    <div>
 
      <light9-timeline-time-axis id="time"></light9-timeline-time-axis>
 
      <light9-timeline-audio id="audio" zoom="{{zoomFlattened}}"></light9-timeline-audio>
 
      <template is="dom-repeat" items="{{rows}}">
 
        <light9-timeline-graph-row></light9-timeline-graph-row>
 
        <light9-timeline-graph-row graph="{{graph}}" zoom-in-x="{{zoomInX}}"></light9-timeline-graph-row>
 
      </template>
 
    </div>
 
  </template>
 
  <script>
 
   Polymer({
 
       is: "light9-timeline-time-zoomed",
 
       behaviors: [
 
           Polymer.IronResizableBehavior
 
       ],
 
       properties: {
 
           rows: {value: [0, 1, 2, 3]},
 
           graph: { type: Object, notify: true },
 
           zoomInX: { type: Object, notify: true },
 
           rows: {value: [0]},
 
           zoom: {type: Object, notify: true, observer: 'onZoom'},
 
           zoomFlattened: {type: Object, notify: true}
 
       },
 
       onZoom: function() {
 
           ko.computed(function() {
 
               this.zoomFlattened = ko.toJS(this.zoom);
 
           }.bind(this));
 
       }
 
   });
 
  </script>
 
</dom-module>
 

	
 

	
 
<!--
 
     SVG or canvas that draws these:
 
       - background grids
 
       - zoom arcs
 
       - notes
 
       - annotations on notes
 
       - connectors between adjusters and their targets
 
     
 
     This element is not responsible for any hit detection. Things you click (rows,
 
     notes, adjusters, etc) are caught on their respective elements. (But is that
 
     wrong in the case of notes?)
 
@@ -157,108 +159,111 @@
 

	
 

	
 
<!-- seconds labels -->
 
<dom-module id="light9-timeline-time-axis">
 
  <template>
 
    <style>
 
     div {
 
         width: 100%;
 
         height: 31px;
 
     }
 
    </style>
 
    <div></div>
 
  </template>
 
</dom-module>
 

	
 
<!-- one row of notes -->
 
<dom-module id="light9-timeline-graph-row">
 
  <template>
 
    <style>
 
     :host {
 
         border-top: 1px solid black;
 
         display: flex;
 
     }
 
    </style>
 
    <template is="dom-repeat" items="[1,2,3]">
 
      <light9-timeline-note></light9-timeline-note>
 
    <template is="dom-repeat" items="[1]">
 
      <light9-timeline-note graph="{{graph}}" zoom-in-x="{{zoomInX}}"></light9-timeline-note>
 
    </template>
 
  </template>
 
</dom-module>
 

	
 
<!-- One trapezoid note shape in a row.
 
     This element has the right Y coords.
 
     We compute X coords from the zoom setting.
 
     diagram-layer draws the note body. -->
 
<dom-module id="light9-timeline-note">
 
  <template>
 
    <style>
 
     :host {
 
         display: block;
 
         background: green;
 
         outline: 2px solid red;
 
         /* outline: 2px solid red; */
 
     }
 
    </style>
 
    <light9-timeline-note-inline-attrs></light9-timeline-note-inline-attrs>
 
  </template>
 
</dom-module>
 

	
 
<!-- All the adjusters you can edit or select.
 
     This element manages their layout and suppresion.
 
     Owns the selection.
 
     Maybe includes selecting things that don't even have adjusters.
 
     Maybe manages the layout of other labels and text too, to avoid overlaps.
 
   -->
 
<dom-module id="light9-timeline-adjusters">
 
  <template>
 
    <style>
 
    </style>
 
    <template is="dom-repeat" items="{{adjs}}">
 
      <light9-timeline-adjuster dia="{{dia}}" adj="{{item}}"></light9-timeline-adjuster>
 
    </template>
 
  </template>
 
</dom-module>
 

	
 
<!-- Yellow dotted handle that you can adjust to edit something.
 
     Knows an attribute to edit and the true screen location, even if
 
     parent <light9-timeline-adjusters> has offset us a bit to avoid a
 
     text overlap.
 
     Draws affordance arrows and a connector line if we're far
 
     away from the point that we edit.
 
     May grow to a bigger editor when you click or drag.
 
   -->
 
<dom-module id="light9-timeline-adjuster">
 
  <template>
 
    <style>
 
     /* 
 
     #top {outline: 2px solid rgba(255, 0, 0, 0.25);}
 
     table {outline: 2px solid rgba(0, 0, 255, 0.19);}
 
     */
 
     
 
     #top {
 
         position: absolute;
 
         display: inline-block;
 
         outline: 2px solid rgba(255, 0, 0, 0.25);
 
     }
 
     table {
 
         position: relative;
 
         left: -50%;
 
         top: -40px; /* percent had no effect */
 
         outline: 2px solid rgba(0, 0, 255, 0.19);
 
         z-index: 2;
 
         border-collapse: collapse;
 
     }
 
     td {
 
         text-align: center;
 
         font-size: 20px;
 
     }
 
     span {
 
         font-size: 16px;
 
         background: rgba(255, 255, 0, 0.5);
 
         border: 3px yellow dotted;
 
         border-radius: 8px;
 
         padding: 5px;
 

	
 
         cursor: ew-resize;
 
         -webkit-user-select: none;
 
     }
 
     span.empty {
 
         width: 30px; /* todo: supposed to fill the whole visible section*/
 
         height: 13px;
 
         display: inline-block;
 
         background: rgba(0,0,0,0);
 
     }
 
    </style>
light9/web/timeline.coffee
Show inline comments
 
@@ -35,60 +35,60 @@ Polymer
 
    animCursor = () => 
 
      @viewState.cursor.t = 130 + 20 * Math.sin(Date.now() / 2000)
 
      @$.dia.setCursor(@$.audio.offsetTop, @$.audio.offsetHeight,
 
                       @$.zoomed.$.time.offsetTop,
 
                       @$.zoomed.$.time.offsetHeight,
 
                       @fullZoomX, @zoomInX, @viewState.cursor)
 

	
 
      #@viewState.zoomSpec.t1(80 + 10 * Math.sin(Date.now() / 3000))
 
      
 
    setInterval(animCursor, 50)
 

	
 
    @adjs = @makeZoomAdjs().concat(@persistDemo())
 

	
 

	
 
  persistDemo: ->
 
    ctx = @graph.Uri('http://example.com/')
 
    adjs = []
 
    for n in [0..7]
 
      subj = @graph.Uri(':demoResource'+n)
 
      adjs.push(new AdjustableFloatObject({
 
        graph: @graph
 
        subj: subj
 
        pred: @graph.Uri(':startTime')
 
        ctx: ctx
 
        getTargetTransform: (value) => $V([@zoomInX(value), 300])
 
        getTargetTransform: (value) => $V([@zoomInX(value), 600])
 
        getValueForPos: (pos) => @zoomInX.invert(pos.e(1))
 
        getSuggestedTargetOffset: () => $V([-30, 80])
 
        getSuggestedTargetOffset: () => $V([0, -80])
 
      }))
 
      adjs.push(new AdjustableFloatObject({
 
        graph: @graph
 
        subj: subj
 
        pred: @graph.Uri(':endTime')
 
        ctx: ctx
 
        getTargetTransform: (value) => $V([@zoomInX(value), 300])
 
        getTargetTransform: (value) => $V([@zoomInX(value), 600])
 
        getValueForPos: (pos) => @zoomInX.invert(pos.e(1))
 
        getSuggestedTargetOffset: () => $V([30, 100])
 
        getSuggestedTargetOffset: () => $V([0, -80])
 
      }))
 
    return adjs
 

	
 
  makeZoomAdjs: ->
 
    yMid = @$.audio.offsetTop + @$.audio.offsetHeight / 2
 
    dur = @viewState.zoomSpec.duration
 
    
 
    valForPos = (pos) =>
 
        x = pos.e(1)
 
        t = @fullZoomX.invert(x)
 
    left = new AdjustableFloatObservable({
 
      observable: @viewState.zoomSpec.t1,
 
      getTarget: () =>
 
        $V([@fullZoomX(@viewState.zoomSpec.t1()), yMid])
 
      getSuggestedTargetOffset: () => $V([-50, 0])
 
      getValueForPos: valForPos
 
    })
 

	
 
    right = new AdjustableFloatObservable({
 
      observable: @viewState.zoomSpec.t2,
 
      getTarget: () =>
 
        $V([@fullZoomX(@viewState.zoomSpec.t2()), yMid])
 
      getSuggestedTargetOffset: () => $V([50, 0])
 
      getValueForPos: valForPos
 
@@ -103,59 +103,70 @@ Polymer
 
          zs.t1(value - span / 2)
 
          zs.t2(value + span / 2)
 
      })
 

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

	
 
Polymer
 
  is: "light9-timeline-time-axis",
 
  # for now since it's just one line calling dia,
 
  # light9-timeline-editor does our drawing work.
 

	
 
Polymer
 
  is: 'light9-timeline-graph-row'
 
  behaviors: [ Polymer.IronResizableBehavior ]
 
  properties: {}
 
  properties:
 
    graph: { type: Object, notify: true }
 
    zoomInX: { type: Object, notify: true }
 

	
 
  
 
window.xserial = 0
 
Polymer
 
  is: 'light9-timeline-note'
 
  behaviors: [ Polymer.IronResizableBehavior ]
 
  listeners: 'iron-resize': '_onIronResize'
 
  properties: {}
 
  properties:
 
    graph: { type: Object, notify: true }
 
    zoomInX: { type: Object, notify: true, observer: '_onIronResize' }
 
  ready: ->
 
    @graph.subscribe("http://light9.bigasterisk.com/demoResource6", null, null, @_onIronResize.bind(@))
 
  _onIronResize: ->
 
    setNote 'myuri', 60 + 150 * window.xserial++, 180, @offsetTop, @offsetTop + @offsetHeight
 
    return if !@zoomInX
 
    subj = "http://light9.bigasterisk.com/demoResource6"
 
    setNote(subj,
 
            @zoomInX(@graph.floatValue(subj, @graph.Uri(':startTime'))),
 
            @zoomInX(@graph.floatValue(subj, @graph.Uri(':endTime'))),
 
            @offsetTop, @offsetTop + @offsetHeight)
 

	
 
Polymer
 
  is: "light9-timeline-adjusters"
 
  properties:
 
    adjs: { type: Array },
 
    dia: { type: Object }
 
  updateAllCoords: ->
 
    for elem in @querySelectorAll('light9-timeline-adjuster')
 
      elem.updateDisplay()
 
    
 

	
 
_adjusterSerial = 0
 

	
 
Polymer
 
  is: 'light9-timeline-adjuster'
 
  properties:
 
    adj:
 
      type: Object
 
      notify: true
 
      observer: 'onAdj'
 
    target:
 
      type: Object
 
      notify: true
 
    displayValue:
0 comments (0 inline, 0 general)