changeset 1804:fb8ca0f886fc

rewrite effectSettings code. fix choice attributes. Ignore-this: b1c4e48d4789a735dfd696cf266e20a0
author Drew Perttula <drewp@bigasterisk.com>
date Fri, 08 Jun 2018 09:09:19 +0000
parents 7d68a9db8319
children 4d129dd1752e
files light9/web/live/elements.html light9/web/live/live.coffee
diffstat 2 files changed, 119 insertions(+), 63 deletions(-) [+]
line wrap: on
line diff
--- a/light9/web/live/elements.html	Fri Jun 08 05:53:04 2018 +0000
+++ b/light9/web/live/elements.html	Fri Jun 08 09:09:19 2018 +0000
@@ -30,6 +30,9 @@
              background: #0b1d0b;
          }
      }
+     paper-item.iron-selected {
+         background: #7b7b4a;
+     }
     </style>
     <paper-listbox id="list"
                    selected="{{value}}"
@@ -129,7 +132,7 @@
       </div>
     </template>
     <template is="dom-if" if="{{deviceAttrRow.useChoice}}">
-      <light9-listbox choices="{{deviceAttrRow.choices}}" value="{{value}}">
+      <light9-listbox choices="{{deviceAttrRow.choices}}" value="{{choiceValue}}">
       </light9-listbox>
     </template>
 
--- a/light9/web/live/live.coffee	Fri Jun 08 05:53:04 2018 +0000
+++ b/light9/web/live/live.coffee	Fri Jun 08 09:09:19 2018 +0000
@@ -33,7 +33,8 @@
     graph: { type: Object, notify: true }
     device: { type: Object }
     deviceAttrRow: { type: Object } # object returned from attrRow, below
-    value: { type: Object, notify: true }
+    value: { type: Object, notify: true } # null, Uri, float, str
+    choiceValue: { type: Object }
     
     immediateSlider: { notify: true, observer: 'onSlider' }
     sliderWriteValue: { type: Number }
@@ -43,6 +44,7 @@
   @getter_observers: [
     'onChange(value)'
     'onGraphToControls(graphToControls)'
+    'onChoice(choiceValue)'
     ]
   constructor: ->
     super()
@@ -57,17 +59,27 @@
     log('change: control gets', v)
     @enableChange = false
     if v == null
-      if @deviceAttrRow.useColor
-        v = '#000000'
-      else
-        v = 0
-    @value = v
+      @clear()
+    else
+      @value = v
     @sliderWriteValue = v if @deviceAttrRow.useSlider
+    @choiceValue = (if v == null then v else v.value) if @deviceAttrRow.useChoice
     @enableChange = true
+
+  onChoice: (value) ->
+    return unless @graphToControls? and @enableChange
+    if value?
+      value = @graph.Uri(value)
+    else
+      value = null
+    @graphToControls.controlChanged(@device, @deviceAttrRow.uri, value)
     
   onChange: (value) ->
     return unless @graphToControls? and @enableChange
+    return if typeof value == "number" and isNaN(value) # let onChoice do it
     #log('change: control tells graph', @deviceAttrRow.uri.value, value)
+    if value == undefined
+      value = null
     @graphToControls.controlChanged(@device, @deviceAttrRow.uri, value)
 
   clear: ->
@@ -128,7 +140,7 @@
     else if dataType.equals(U(':choice'))
       daRow.useChoice = true
       choiceUris = @graph.sortedUris(@graph.objects(devAttr, U(':choice')))
-      daRow.choices = ({uri: x, label: @graph.labelOrTail(x)} for x in choiceUris)
+      daRow.choices = ({uri: x.value, label: @graph.labelOrTail(x)} for x in choiceUris)
       daRow.choiceSize = Math.min(choiceUris.length + 1, 10)
     else
       daRow.useSlider = true
@@ -144,20 +156,83 @@
     
 )
 
+
+class ActiveSettings
+  # Maintains the settings on this effect
+  constructor: (@graph) ->
+
+    # The settings we're showing (or would like to but the widget
+    # isn't registered yet):
+    # dev+attr : {setting: Uri, onChangeFunc: f, jsValue: str_or_float}
+    @settings = new Map()
+    @keyForSetting = new Map() # setting uri str -> dev+attr
+
+    # Registered graphValueChanged funcs, by dev+attr. Kept even when
+    # settings are deleted.
+    @onChanged = new Map()
+
+  addSettingsRow: (device, deviceAttr, setting, value) ->
+    key = device.value + " " + deviceAttr.value
+    @settings.set(key, {
+      setting: setting,
+      onChangeFunc: @onChanged[key],
+      jsValue: value
+    })
+    @keyForSetting.set(setting.value, key)
+    if @onChanged[key]?
+      @onChanged[key](value)
+
+  has: (setting) ->
+    @keyForSetting.has(setting.value)
+
+  setValue: (setting, value) ->
+    row = @settings.get(@keyForSetting.get(setting.value))
+    row.jsValue = value
+    row.onChangeFunc(value) if row.onChangeFunc?
+
+  registerWidget: (device, deviceAttr, graphValueChanged) ->
+    key = device.value + " " + deviceAttr.value
+    @onChanged[key] = graphValueChanged
+    
+    if @settings.has(key)
+      row = @settings.get(key)
+      row.onChangeFunc = graphValueChanged
+      row.onChangeFunc(row.jsValue)
+
+  effectSettingLookup: (device, attr) ->
+    key = device.value + " " + attr.value
+    if @settings.has(key)
+      return @settings.get(key).setting
+
+    return null
+
+  deleteSetting: (setting) ->
+    key = @keyForSetting.get(setting.value)
+    row = @settings.get(key)
+    row.onChangeFunc(null) if row?.onChangeFunc?
+    @settings.delete(key)
+    @keyForSetting.delete(setting)
+
+  clear: ->
+    new Map(@settings).forEach (row, key) ->
+      row.onChangeFunc(null)
+    @settings.clear()
+    @keyForSetting.clear()
+
+  forAll: (cb) ->
+    all = Array.from(@keyForSetting.keys())
+    for s in all
+      cb(@graph.Uri(s))
+
+  allSettingsStr: ->
+    @keyForSetting.keys()
+
 class GraphToControls
   # More efficient bridge between liveControl widgets and graph edits,
   # as opposed to letting each widget scan the graph and push lots of
   # tiny patches to it.
   constructor: (@graph) ->
-
-    # Registered graphValueChanged funcs, by dev+attr
-    @onChanged = {}
-
-    # The settings we're showing (or would like to but the widget
-    # isn't registered yet):
-    # dev+attr : {setting: Uri, onChangeFunc: f, jsValue: str_or_float}
-    @settings = new Map()
-
+    @activeSettings = new ActiveSettings(@graph)
     @effect = null
 
   ctxForEffect: (effect) ->
@@ -190,67 +265,45 @@
     @graph.applyAndSendPatch(patch)
     return effect
 
-  addSettingsRow: (device, deviceAttr, setting, value) ->
-    key = device.value + " " + deviceAttr.value
-    @settings.set(key, {
-      setting: setting,
-      onChangeFunc: @onChanged[key],
-      jsValue: value
-    })
-                                                                              
   syncFromGraph: ->
     U = (x) => @graph.Uri(x)
     return if not @effect
     
-    toClear = new Map(@settings)
+    toClear = new Set(@activeSettings.allSettingsStr())
     
     for setting in @graph.objects(@effect, U(':setting'))
       dev = @graph.uriValue(setting, U(':device'))
       devAttr = @graph.uriValue(setting, U(':deviceAttr'))
-      key = dev.value + " " + devAttr.value
 
       pred = valuePred(@graph, devAttr)
       try
-        value = @graph.floatValue(setting, pred)
+        value = @graph.uriValue(setting, pred)
+        if not value.id.match(/^http/)
+          throw new Error("not uri")
       catch
-        value = @graph.stringValue(setting, pred)
+        try
+          value = @graph.floatValue(setting, pred)
+        catch
+          value = @graph.stringValue(setting, pred)
       log('change: graph contains', devAttr, value)
 
-      if @settings.has(key)
-        @settings.get(key).jsValue = value
-        @settings.get(key).onChangeFunc(value)
-        toClear.delete(key)
+      if @activeSettings.has(setting)
+        @activeSettings.setValue(setting, value)
+        toClear.delete(setting.value)
       else
-        @addSettingsRow(dev, devAttr, setting, value)
-        if @onChanged[key]?
-          @onChanged[key](value)
+        @activeSettings.addSettingsRow(dev, devAttr, setting, value)
           
-    for key, row of toClear
-      row.onChangeFunc(null)      
-      @settings.delete(key)
+    for settingStr in Array.from(toClear)
+      @activeSettings.deleteSetting(U(settingStr))
 
   clearSettings: ->
-    @settings.forEach (row, key) =>
-      row.onChangeFunc(null) if row.onChangeFunc?
-
-    @settings.clear()
+    @activeSettings.clear()
 
   effectSettingLookup: (device, attr) ->
-    key = device.value + " " + attr.value
-    if @settings.has(key)
-      return @settings.get(key).setting
-
-    return null
+    @activeSettings.effectSettingLookup(device, attr)
 
   register: (device, deviceAttr, graphValueChanged) ->
-    key = device.value + " " + deviceAttr.value
-
-    @onChanged[key] = graphValueChanged
-
-    if @settings.has(key)
-      row = @settings.get(key)
-      row.onChangeFunc = graphValueChanged
-      row.onChangeFunc(row.jsValue)
+    @activeSettings.registerWidget(device, deviceAttr, graphValueChanged)
 
   shouldBeStored: (deviceAttr, value) ->
     # this is a bug for zoom=0, since collector will default it to
@@ -260,13 +313,11 @@
     return value? and value != 0 and value != '#000000'
 
   emptyEffect: ->
-    new Map(@settings).forEach (row, key) =>
-      row.onChangeFunc(null)
-      @_removeEffectSetting(row.setting)
+    @activeSettings.forAll(@_removeEffectSetting.bind(@))
 
   controlChanged: (device, deviceAttr, value) ->
     # value is float or #color or (Uri or null)
-    if (value == undefined or (typeof value == "number" and isNaN(value)) or (not value.id and typeof value == "object"))
+    if (value == undefined or (typeof value == "number" and isNaN(value)) or (typeof value == "object" and value != null and not value.id))
       throw new Error("controlChanged sent bad value " + value)
     effectSetting = @effectSettingLookup(device, deviceAttr)
     if @shouldBeStored(deviceAttr, value)
@@ -287,7 +338,7 @@
     U = (x) => @graph.Uri(x)
     quad = (s, p, o) => @graph.Quad(s, p, o, @ctx)
     effectSetting = @graph.nextNumberedResource(@effect.value + '_set')
-    @addSettingsRow(device, deviceAttr, effectSetting, value)
+    @activeSettings.addSettingsRow(device, deviceAttr, effectSetting, value)
     addQuads = [
       quad(@effect, U(':setting'), effectSetting),
       quad(effectSetting, U(':device'),  device),
@@ -300,17 +351,19 @@
 
   _patchExistingEffectSetting: (effectSetting, deviceAttr, value) ->
     log('change: patch existing', effectSetting.value)
+    @activeSettings.setValue(effectSetting, value)
     @graph.patchObject(effectSetting, valuePred(@graph, deviceAttr), @_nodeForValue(value), @ctx)
 
   _removeEffectSetting: (effectSetting) ->
-    log('change: _removeEffectSetting', effectSetting.value)
-     U = (x) => @graph.Uri(x)
+    U = (x) => @graph.Uri(x)
     quad = (s, p, o) => @graph.Quad(s, p, o, @ctx)
     if effectSetting?
+      log('change: _removeEffectSetting', effectSetting.value)
       toDel = [quad(@effect, U(':setting'), effectSetting, @ctx)]
       for q in @graph.graph.getQuads(effectSetting)
         toDel.push(q)
       @graph.applyAndSendPatch({delQuads: toDel, addQuads: []})
+      @activeSettings.deleteSetting(effectSetting)
     
     
 coffeeElementSetup(class Light9LiveControls extends Polymer.Element