Mercurial > code > home > repos > light9
changeset 1788:8b9b8584ee42
rewrite /live to edit a real effect, not push its own values to collector
Ignore-this: 295983a53720204b5cd12524190959f5
author | Drew Perttula <drewp@bigasterisk.com> |
---|---|
date | Thu, 07 Jun 2018 17:40:29 +0000 |
parents | 4bd88d5fcaf8 |
children | d8ae388a8076 |
files | light9/web/live/elements.html light9/web/live/live.coffee |
diffstat | 2 files changed, 215 insertions(+), 85 deletions(-) [+] |
line wrap: on
line diff
--- a/light9/web/live/elements.html Thu Jun 07 17:39:41 2018 +0000 +++ b/light9/web/live/elements.html Thu Jun 07 17:40:29 2018 +0000 @@ -4,7 +4,6 @@ <link rel="import" href="/lib/paper-listbox/paper-listbox.html"> <link rel="import" href="/lib/paper-item/paper-item.html"> <link rel="import" href="/lib/iron-ajax/iron-ajax.html"> -<link rel="import" href="../light9-collector-client.html"> <link rel="import" href="../rdfdb-synced-graph.html"> <link rel="import" href="../resource-display.html"> @@ -113,24 +112,24 @@ </style> - <template is="dom-if" if="{{deviceAttr.useSlider}}"> + <template is="dom-if" if="{{deviceAttrRow.useSlider}}"> <paper-slider min="0" - max="{{deviceAttr.max}}" + max="{{deviceAttrRow.max}}" step=".001" editable content-type="application/json" value="{{sliderWriteValue}}" immediate-value="{{immediateSlider}}"></paper-slider> </template> - <template is="dom-if" if="{{deviceAttr.useColor}}"> + <template is="dom-if" if="{{deviceAttrRow.useColor}}"> <div id="colorControls"> <button on-click="goBlack">0.0</button> <light9-color-picker color="{{value}}"></light9-color-picker> </div> </template> - <template is="dom-if" if="{{deviceAttr.useChoice}}"> - <light9-listbox choices="{{deviceAttr.choices}}" value="{{value}}"> + <template is="dom-if" if="{{deviceAttrRow.useChoice}}"> + <light9-listbox choices="{{deviceAttrRow.choices}}" value="{{value}}"> </light9-listbox> </template> @@ -186,7 +185,13 @@ <template is="dom-repeat" items="{{deviceAttrs}}" as="dattr"> <div class="deviceAttr"> <span>attr <resource-display minor graph="{{graph}}" uri="{{dattr.uri}}"></resource-display></span> - <light9-live-control device="{{uri}}" device-attr="{{dattr}}"></light9-live-control> + <light9-live-control + graph="{{graph}}" + device="{{uri}}" + device-attr-row="{{dattr}}" + effect="{{effect}}" + graph-to-controls="{{graphToControls}}" + ></light9-live-control> </div> </template> </div> @@ -219,9 +224,7 @@ </style> <rdfdb-synced-graph graph="{{graph}}"></rdfdb-synced-graph> - <light9-collector-client self="{{client}}"></light9-collector-client> <h1>device control - <button on-click="resendAll">resend all</button> <button on-click="clearAll">clear all</button></h1> <div id="save"> @@ -235,7 +238,12 @@ <div id="deviceControls"> <template is="dom-repeat" items="{{devices}}" as="device"> - <light9-live-device-control graph="{{graph}}" uri="{{device.uri}}"></light9-live-device-control> + <light9-live-device-control + graph="{{graph}}" + uri="{{device.uri}}" + effect="{{effect}}" + graph-to-controls="{{graphToControls}}" + ></light9-live-device-control> </template> </div>
--- a/light9/web/live/live.coffee Thu Jun 07 17:39:41 2018 +0000 +++ b/light9/web/live/live.coffee Thu Jun 07 17:40:29 2018 +0000 @@ -1,32 +1,52 @@ log = console.log +valuePred = (graph, attr) -> + U = (x) -> graph.Uri(x) + scaledAttributeTypes = [U(':color'), U(':brightness'), U(':uv')] + if _.some(scaledAttributeTypes, + (x) -> attr.equals(x)) then U(':scaledValue') else U(':value') + coffeeElementSetup(class Light9LiveControl extends Polymer.Element @is: 'light9-live-control' @getter_properties: - device: { type: String } - deviceAttr: { type: Object } - max: { type: Number, value: 1 } + graph: { type: Object, notify: true } + device: { type: Object } + deviceAttrRow: { type: Object } # object returned from attrRow, below value: { type: Object, notify: true } immediateSlider: { notify: true, observer: 'onSlider' } sliderWriteValue: { type: Number } + pickedChoice: { observer: 'onChange' } + graphToControls: { type: Object } @getter_observers: [ 'onChange(value)' + 'onGraphToControls(graphToControls)' ] - ready: -> - super.ready() + constructor: -> + super() + @enableChange = false # until 1st graph read onSlider: -> @value = @immediateSlider goBlack: -> @value = "#000000" + onGraphToControls: (gtc) -> + gtc.register(@device, @deviceAttrRow.uri, @graphValueChanged.bind(@)) + @enableChange = true + + graphValueChanged: (v) -> + log('change: control gets', v) + @enableChange = false + @value = v + @sliderWriteValue = v if @deviceAttrRow.useSlider + @enableChange = true onChange: (value) -> - @lastSent = [[@device, @deviceAttr.uri, value]] - @resend() - resend: -> - window.gather(@lastSent) + return unless @graphToControls? and @enableChange + log('change: control tells graph', @deviceAttrRow.uri.value, value) + @graphToControls.controlChanged(@device, @deviceAttrRow.uri, value) + clear: -> @pickedChoice = null @sliderWriteValue = 0 - if @deviceAttr.useColor + if @deviceAttrRow.useColor @value = '#000000' else @value = @immediateSlider = 0 @@ -37,8 +57,10 @@ @getter_properties: graph: { type: Object, notify: true } uri: { type: String, notify: true } + effect: { type: String } deviceClass: { type: String, notify: true } deviceAttrs: { type: Array, notify: true } + graphToControls: { type: Object } bgStyle: { type: String, computed: '_bgStyle(deviceClass)' } @getter_observers: [ 'onGraph(graph)' @@ -47,93 +69,203 @@ hash = 0 deviceClass = deviceClass.value for i in [(deviceClass.length-10)...deviceClass.length] - hash += deviceClass.charCodeAt(i) + hash += deviceClass.charCodeAt(i) hue = (hash * 8) % 360 accent = "hsl(#{hue}, 49%, 22%)" "background: linear-gradient(to right, rgba(31,31,31,0) 50%, #{accent} 100%);" + onGraph: -> @graph.runHandler(@update.bind(@), "#{@uri.value} update") - update: -> + + update: (patch) -> U = (x) => @graph.Uri(x) - + return if patch? and not SyncedGraph.patchContainsPreds( + patch, [U('rdf:type'), U(':deviceAttr'), U(':dataType'), U(':choice')]) @deviceClass = @graph.uriValue(@uri, U('rdf:type')) - @deviceAttrs = [] for da in _.unique(@graph.sortedUris(@graph.objects(@deviceClass, U(':deviceAttr')))) - dataType = @graph.uriValue(da, U(':dataType')) - daRow = { - uri: da - dataType: dataType - showColorPicker: dataType.equals(U(':color')) - } - if dataType.equals(U(':color')) - daRow.useColor = true + @push('deviceAttrs', @attrRow(da)) - else if dataType.equals(U(':choice')) - daRow.useChoice = true - choiceUris = @graph.sortedUris(@graph.objects(da, U(':choice'))) - daRow.choices = ({uri: x, label: @graph.labelOrTail(x)} for x in choiceUris) - daRow.choiceSize = Math.min(choiceUris.length + 1, 10) - else - daRow.useSlider = true + attrRow: (devAttr) -> + U = (x) => @graph.Uri(x) + dataType = @graph.uriValue(devAttr, U(':dataType')) + daRow = { + uri: devAttr + dataType: dataType + showColorPicker: dataType.equals(U(':color')) + } + if dataType.equals(U(':color')) + daRow.useColor = true + 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.choiceSize = Math.min(choiceUris.length + 1, 10) + else + daRow.useSlider = true + daRow.max = 1 + if dataType.equals(U(':angle')) + # varies daRow.max = 1 - if dataType.equals(U(':angle')) - # varies - daRow.max = 1 - - @push('deviceAttrs', daRow) + return daRow + clear: -> for lc in @shadowRoot.querySelectorAll("light9-live-control") lc.clear() ) + +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) -> + @currentSettings = {} # {dev: {attr: value}} + @onChanged = {} + @effect = @graph.Uri('http://light9.bigasterisk.com/effect/pool_r') + @ctx = @graph.Uri('http://light9.bigasterisk.com/show/dance2017/effect/pool_r') + @graph.runHandler(@syncFromGraph.bind(@), 'GraphToControls sync') + + syncFromGraph: -> + U = (x) => @graph.Uri(x) + @currentSettings = {} + return if not @effect + for s in @graph.objects(@effect, U(':setting')) + dev = @graph.uriValue(s, U(':device')) + devAttr = @graph.uriValue(s, U(':deviceAttr')) + + pred = valuePred(@graph, devAttr) + try + value = @graph.floatValue(s, pred) + catch + value = @graph.stringValue(s, pred) + log('change: graph contains', devAttr, value) + + oc = @onChanged[dev.value + " " + devAttr.value] + if oc? + log('change: gtc tells control') + oc(value) + + # currentSettings is no longer what we need- the point is to + # optimize effectSettingLookup by knowing the :setting nodes for + # every dev+devAttr + + # this is a bug for zoom=0, since collector will default it to + # stick at the last setting if we don't explicitly send the + # 0. rx/ry similar though not the exact same deal because of + # their remap. + if value == 0 or value == '#000000' or value == null or value == undefined + delete @currentSettings[dev.value][devAttr.value] if @currentSettings[dev.value]? + else + @currentSettings[dev.value] = {} unless @currentSettings[dev.value]? + @currentSettings[dev.value][devAttr.value] = value + + effectSettingLookup: (device, attr) -> + U = (x) => @graph.Uri(x) + for s in @graph.objects(@effect, U(':setting')) + if @graph.uriValue(s, U(':device')).equals(device) and @graph.uriValue(s, U(':deviceAttr')).equals(attr) + return s + return null + + # faster one: + d = @currentSettings[device.value] + if d? + da = d[attr.value] + # ... + return null + + preview: -> + JSON.stringify(@currentSettings) + + register: (device, deviceAttr, graphValueChanged) -> + log('change: registering', device, deviceAttr) + @onChanged[device.value + " " + deviceAttr.value] = graphValueChanged + da = @currentSettings[device.value] + if da? + v = da[deviceAttr.value] + if v? + log('change: gtc tells change at reg time', v) + graphValueChanged(v) + # no unregister yet + + shouldBeStored: (deviceAttr, value) -> + # this is a bug for zoom=0, since collector will default it to + # stick at the last setting if we don't explicitly send the + # 0. rx/ry similar though not the exact same deal because of + # their remap. + return value != 0 and value != '#000000' + + controlChanged: (device, deviceAttr, value) -> + effectSetting = @effectSettingLookup(device, deviceAttr) + if @shouldBeStored(deviceAttr, value) + if not effectSetting? + @_addEffectSetting(device, deviceAttr, value) + else + @_patchExistingEffectSetting(effectSetting, deviceAttr, value) + else + @_removeEffectSetting(effectSetting) + + _addEffectSetting: (device, deviceAttr, value) -> + U = (x) => @graph.Uri(x) + quad = (s, p, o) => @graph.Quad(s, p, o, @ctx) + effectSetting = @graph.nextNumberedResource(@effect.value + '_set') + addQuads = [ + quad(@effect, U(':setting'), effectSetting), + quad(effectSetting, U(':device'), device), + quad(effectSetting, U(':deviceAttr'), deviceAttr), + quad(effectSetting, valuePred(@graph, deviceAttr), @graph.prettyLiteral(value)) + ] + patch = {addQuads: addQuads, delQuads: []} + log('save', patch) + @graph.applyAndSendPatch(patch) + + _patchExistingEffectSetting: (effectSetting, deviceAttr, value) -> + log('patch existing', effectSetting.value) + @graph.patchObject(effectSetting, valuePred(@graph, deviceAttr), @graph.prettyLiteral(value), @ctx) + + _removeEffectSetting: (effectSetting) -> + U = (x) => @graph.Uri(x) + if effectSetting? + toDel = [quad(@effect, U(':setting'), effectSetting, @ctx)] + for q in @graph.graph.getQuads(effectSetting) + toDel.push(q) + @graph.applyAndSendPatch({delQuads: toDel, addQuads: []}) + coffeeElementSetup(class Light9LiveControls extends Polymer.Element @is: "light9-live-controls" @getter_properties: graph: { type: Object, notify: true } - client: { type: Object, notify: true } devices: { type: Array, notify: true } - currentSettings: { type: Object, notify: true } # dev+attr: [dev, attr, value] effectPreview: { type: String, notify: true } newEffectName: { type: String, notify: true } effect: { type: String, notify: true } # the one being edited, if any + graphToControls: { type: Object } @getter_observers: [ 'onGraph(graph)' 'onEffect(effect)' ] + + constructor: -> + super() + @graphToControls = null ready: -> super.ready() @currentSettings = {} @effectPreview = JSON.stringify({}) - @sendAllThrottled = _.throttle(@sendAll.bind(@), 30) - - window.gather = (sent) => - [dev, devAttr, value] = sent[0] - key = dev.value + " " + devAttr.value - # this is a bug for zoom=0, since collector will default it to - # stick at the last setting if we don't explicitly send the - # 0. rx/ry similar though not the exact same deal because of - # their remap. - if value == 0 or value == '#000000' or value == null or value == undefined - delete @currentSettings[key] - else - @currentSettings[key] = [dev, devAttr, value] - @effectPreview = JSON.stringify(v for k,v of @currentSettings) + onGraph: -> + @graphToControls = new GraphToControls(@graph) + @effect = @graphToControls.effect.value + log('set my @graphtocontrols') + @graph.runHandler(@update.bind(@), 'Light9LiveControls update') - @sendAllThrottled() - - currentSettingsList: -> (v for k,v of @currentSettings) + effectSettingLookup: (device, attr) -> + if @graphToControls == null + throw new Error('not ready') + # optimization for getting the :setting node + return @graphToControls.effectSettingLookup(device, attr) - sendAll: -> - @client.send(@currentSettingsList()) - - valuePred: (attr) -> - U = (x) => @graph.Uri(x) - scaledAttributeTypes = [U(':color'), U(':brightness'), U(':uv')] - if [attr.equals(x) for x in scaledAttributeTypes].length then U(':scaledValue') else U(':value') - onEffect: -> U = (x) => @graph.Uri(x) return unless @effect @@ -142,7 +274,7 @@ dev = @graph.uriValue(s, U(':device')) devAttr = @graph.uriValue(s, U(':deviceAttr')) - pred = @valuePred(devAttr) + pred = valuePred(@graph, devAttr) try value = @graph.floatValue(s, pred) catch @@ -172,24 +304,14 @@ addQuads.push(quad(@effect, U(':setting'), setting)) addQuads.push(quad(setting, U(':device'), row[0])) addQuads.push(quad(setting, U(':deviceAttr'), row[1])) - value = if typeof(row[2]) == 'number' - @graph.LiteralRoundedFloat(row[2]) - else - @graph.Literal(row[2]) - addQuads.push(quad(setting, @valuePred(row[1]), value)) + + addQuads.push(quad(setting, valuePred(@graph, row[1]), value)) patch = {addQuads: addQuads, delQuads: []} log('save', patch) @graph.applyAndSendPatch(patch) @newEffectName = '' - - onGraph: -> - @graph.runHandler(@update.bind(@), 'controls') - resendAll: -> - for llc in @getElementsByTagName("light9-live-control") - llc.resend() - clearAll: -> for dc in @shadowRoot.querySelectorAll("light9-live-device-control") dc.clear() @@ -209,7 +331,7 @@ # Tried columnize- fails in jquery maybe from weird elements. # not sure how to get this run after the children are created - setTimeout((() => $('#deviceControls').isotope({ + setTimeout((() -> $('#deviceControls').isotope({ # fitColumns would be nice, but it doesn't scroll vertically layoutMode: 'masonry', containerStyle: null