From 1641f5b07e5b82031243e6223ea78d6b8bd675ab Mon Sep 17 00:00:00 2001 From: STEINNI Date: Sun, 21 Sep 2025 20:08:12 +0000 Subject: [PATCH] snaptobus starts to work... --- app/thirdparty/Snaptobus/snaptobus.js | 128 +++++++++++++++++++--- app/views/dashboards/MainDashboardView.js | 105 ++++++++++++++---- test.js | 117 ++++++++++++++++++++ 3 files changed, 309 insertions(+), 41 deletions(-) create mode 100644 test.js diff --git a/app/thirdparty/Snaptobus/snaptobus.js b/app/thirdparty/Snaptobus/snaptobus.js index 5298075..314257a 100644 --- a/app/thirdparty/Snaptobus/snaptobus.js +++ b/app/thirdparty/Snaptobus/snaptobus.js @@ -5,10 +5,69 @@ */ class Snaptobus{ - constructor(config){ - this.config = this.observeObject(config, this.configChange.bind(this)) + constructor(options){ + this.spriteDefs = options.spriteDefs + this._curBusConfig = [] + this._stagedBusConfig = options.busConfig + this.snap = options.snap + this.commitConfig() } + get busConfig() { return this._stagedBusConfig } + get liveBusConfig() { return this._curBusConfig } + set busConfig(newConfig) { this._stagedBusConfig = newConfig } + + async commitConfig(){ + const chansToAdd = [] + const chansToKeep = [] + for(const chanObj of this._stagedBusConfig){ + console.log('staged chan:',chanObj.chan,' current ones:', this._curBusConfig.map(item => item.chan)) + if(this._curBusConfig.map(item => item.chan).includes(chanObj.chan)) chansToKeep.push(chanObj) + else chansToAdd.push(chanObj) + } + const chansToDel = this._curBusConfig.filter(item => (!chansToKeep.map(c=>c.chan).includes(item.chan) && !chansToAdd.map(c=>c.chan).includes(item.chan))) + await app.MessageBus.subscribe(chansToAdd.map(item => item.chan)) + await app.MessageBus.unSubscribe(chansToDel.map(item => item.chan)) + // console.log('subscribe:', chansToAdd.map(item => item.chan)) + // console.log('unSubscribe:', chansToDel) + + const eventsToAdd = chansToAdd.flatMap(item => item.events.map(ev => ({ chan:item.chan, eventName:ev.eventName }))) + let eventsToDel = []//= chansToDel.flatMap(item => item.events.map(ev => ({ chan:item.chan, eventName:ev.eventName }))) + for(const oldChan of this._curBusConfig){ + for(const oldEvent of oldChan.events){ + for(const keepChan of chansToKeep){ + if(!keepChan.events.map(item=>item.eventName).includes(oldEvent.eventName)) eventsToDel.push({chan: oldChan.chan, eventName: oldEvent.eventName}) + } + } + } + + // console.log('eventsToAdd:', eventsToAdd) + // console.log('eventsToDel:', eventsToDel) + for(const eventToAdd of eventsToAdd){ + app.MessageBus.addBusListener(eventToAdd.eventName, [eventToAdd.chan], this.processBusEvent.bind(this, eventToAdd.eventName,), 'snaptobus') + } + for(const eventToDel of eventsToDel){ + app.MessageBus.removeBusListener(eventToDel.eventName, this.processBusEvent.bind(this, eventToDel.eventName), 'snaptobus') + } + + this._curBusConfig = this.deepClone(this._stagedBusConfig) + } + + deepClone(obj) { // Needed because structuredClone doesn't take functions (and we have transformers) + if (obj === null || typeof obj !== 'object') { + return obj + } + if (Array.isArray(obj)) { + return obj.map((el => this.deepClone(el))) + } + const clone = {} + for (const key in obj) { + clone[key] = this.deepClone(obj[key]) + } + return clone + } + + observeObject(obj, onChange) { const wrap = (value) => (value && typeof value === 'object') @@ -37,31 +96,49 @@ class Snaptobus{ return new Proxy(obj, handler) } - - configChange(prop, newval, oldval){ - //TODO subscribe new chans - //TODO unsubscribe removed chans - //TODO update event filters - // - console.log(prop, oldval, newval) + + + processBusEvent(eventType, chan, payload, userId, x){ + console.log('processBusEvent====>',eventType, chan, payload, userId) + const chanObj = this._curBusConfig.find(item => item.chan==chan) + if(!chanObj) return + console.log('processBusEvent====>chanObj', chanObj) + const eventObj = chanObj.events.find(item => item.eventName==eventType) + if(!eventObj) return + console.log('processBusEvent====>eventObj', eventObj) + // assign attributes in payload to all snap objects for that eventType + // each snap is an assignation of attributes via a selector + for(const snapDef of eventObj.snaps){ + const selector = snapDef.selector.replace(/\$\{(\w+)\}/g, (_, key) => this.getValueByPath(payload, key)) + console.log('Will look for snap:', selector) + const snaps = this.snap.selectAll(selector) + console.log(`found ${snaps.length} snaps`) + snaps.forEach(snapEl => { + const newAttr = this.assignFromConfig(payload, snapDef.assign) + console.log('newAttr:',newAttr) + snapEl.attr(newAttr) + }) + } } - getValueByPath(obj, path) { - return(path.split('.').reduce((acc, key) => acc?.[key], obj)) - } - - assignFromConfig(chan, event) { + assignFromConfig(data, replaceDef) { + console.log('assignFromConfig', data, replaceDef) const result = {} - for (const [key, rule] of Object.entries(this.config.assign)) { + for (const [key, rule] of Object.entries(replaceDef)) { if (typeof rule === 'string') { // plain path - result[key] = this.getValueByPath(event, rule) + result[key] = this.getValueByPath(data, rule) } else if((typeof(rule) == 'object') && (typeof(rule.transformer) == 'function')) { // transformer - const fnargs = (rule.arguments || []).map(arg => this.getValueByPath(event,arg)) + const fnargs = (rule.arguments || []).map(arg => this.getValueByPath(data,arg)) result[key] = rule.transformer(...fnargs) } } return result } + + getValueByPath(obj, path) { + return(path.split('.').reduce((acc, key) => acc?.[key], obj)) + } + } /* const s2bConfig = [ @@ -81,6 +158,17 @@ const s2bConfig = [ } ] }, + { eventName: 'rotating', + snaps: [ + { + selector: '#${aid}', + assign: { + r: 'rotangle' + }, + animate: true + } + ] + }, ] }, { chan: 'agent:*', // wildcards allowed @@ -102,3 +190,9 @@ const s2bConfig = [ }, ] */ + +/* +payload = +{} + eventType +*/ diff --git a/app/views/dashboards/MainDashboardView.js b/app/views/dashboards/MainDashboardView.js index 6730053..ed76ba2 100644 --- a/app/views/dashboards/MainDashboardView.js +++ b/app/views/dashboards/MainDashboardView.js @@ -1,10 +1,33 @@ class MainDashboardView extends EICDomContent { + agentTypes = { + molecule1:{ + type: 'circle', + attrs: { + r: 10, + fill: '#BFB', + stroke: "#0A0", + strokeWidth: 2, + } + }, + molecule2:{ + type: 'circle', + attrs: { + r: 10, + fill: '#BBF', + stroke: "#00A", + strokeWidth: 2, + } + } + + } + constructor() { super() Object.assign(this, app.helpers.activeAttributes) //this.tileMarkup = app.Assets.Store.html['/app/assets/html/mailing/tile.html'] this.snap = null + this.snaptobus = null } DOMContentLoaded(options) { @@ -12,15 +35,38 @@ class MainDashboardView extends EICDomContent { const components = ui.eicfy(this.el) this.setupTriggers(components) this.setupRefs(components) - this.snap = Snap("svg.stb"); - var agent = this.snap.circle(150, 150, 20); - agent.attr({ - id: 'agent42', - fill: "#BFB", - stroke: "#0A0", - strokeWidth: 2 - }); - setTimeout(this.moveit.bind(this), 3000); + this.snaptobus = new Snaptobus({ + snap: Snap("svg.stb"), + spriteDefs: this.agentTypes, + busConfig: [ + { + chan: 'gps:agents', // What to subscribe to + events: [ // What to select on this chan + { eventName: 'moving', + snaps: [ + { + selector: '#${aid}', + assign: { + cx: 'attrs.x', + cy: 'attrs.y', + }, + animate: true + } + ] + }, + ] + }, + ] + }) + // var agent = this.snap.circle(150, 150, 20); + // agent.attr({ + // id: 'agent42', + // fill: "#BFB", + // stroke: "#0A0", + // strokeWidth: 2 + // }); + this.createAgent('molecule2', 'agent42', 100,100) + //setTimeout(this.moveit.bind(this), 3000); } DOMContentFocused(options) { @@ -33,22 +79,33 @@ class MainDashboardView extends EICDomContent { DOMContentBlured(options) { this.wasBlured = true } - moveit(){ - var myCircle = this.snap.select('#agent42') - - var newx = parseInt(myCircle.attr('cx')) + 600 - var newy = parseInt(myCircle.attr('cy')) + 200 - - // animate translate - myCircle.animate( - { - cx: newx, - cy: newy, - }, - 1000, // duration in ms - mina.linear // easing - ) + createAgent(agentType, id, x, y){ + if(!Object.keys(this.agentTypes).includes(agentType)) return + this.agentTypes[agentType] + const svgAgent = this.snaptobus.snap[this.agentTypes[agentType].type]().attr(this.agentTypes[agentType].attrs) + svgAgent.attr({ + id: id, + cx: x, + cy:y, + }) } + + // moveit(){ + // var myCircle = this.snap.select('#agent42') + + // var newx = parseInt(myCircle.attr('cx')) + 600 + // var newy = parseInt(myCircle.attr('cy')) + 200 + + // // animate translate + // myCircle.animate( + // { + // cx: newx, + // cy: newy, + // }, + // 1000, // duration in ms + // mina.linear // easing + // ) + // } } app.registerClass('MainDashboardView', MainDashboardView) diff --git a/test.js b/test.js new file mode 100644 index 0000000..daca351 --- /dev/null +++ b/test.js @@ -0,0 +1,117 @@ +class SnapToBus{ + constructor(config){ + this._curBusConfig = [] + this._stagedBusConfig = config // null Means nothing uncommitted + this.commitConfig() + } + + get busConfig() { return this._stagedBusConfig } + get liveBusConfig() { return this._curBusConfig } + set busConfig(newConfig) { this._stagedBusConfig = newConfig } + + async commitConfig(){ + const chansToAdd = [] + const chansToKeep = [] + console.log('=======>_stagedBusConfig', this._stagedBusConfig) + for(const chanObj of this._stagedBusConfig){ + console.log('staged chan:',chanObj.chan,' current ones:', this._curBusConfig.map(item => item.chan)) + if(this._curBusConfig.map(item => item.chan).includes(chanObj.chan)) chansToKeep.push(chanObj) + else chansToAdd.push(chanObj) + } + const chansToDel = this._curBusConfig.filter(item => (!chansToKeep.map(c=>c.chan).includes(item.chan) && !chansToAdd.map(c=>c.chan).includes(item.chan))) + app.MessageBus.subscribe(chansToAdd.map(item => item.chan)) + app.MessageBus.unSubscribe(chansToDel.map(item => item.chan)) + // console.log('subscribe:', chansToAdd.map(item => item.chan)) + // console.log('unSubscribe:', chansToDel) + + const eventsToAdd = chansToAdd.flatMap(item => item.events.map(ev => ({ chan:item.chan, eventName:ev.eventName }))) + let eventsToDel = []//= chansToDel.flatMap(item => item.events.map(ev => ({ chan:item.chan, eventName:ev.eventName }))) + for(const oldChan of this._curBusConfig){ + for(const oldEvent of oldChan.events){ + for(const keepChan of chansToKeep){ + if(!keepChan.events.map(item=>item.eventName).includes(oldEvent.eventName)) eventsToDel.push({chan: oldChan.chan, eventName: oldEvent.eventName}) + } + } + } + + // console.log('eventsToAdd:', eventsToAdd) + // console.log('eventsToDel:', eventsToDel) + for(const eventToAdd of eventsToAdd){ + app.MessageBus.addBusListener(eventToAdd.eventName, [eventToAdd.chan], this.processBus.bind(this), 'snaptobus') + } + for(const eventToDel of eventsToDel){ + app.MessageBus.removeBusListener(eventToDel.eventName, this.processBus.bind(this), 'snaptobus') + } + + this._curBusConfig = this.deepClone(this._stagedBusConfig) + } + + deepClone(obj) { // Needed because structuredClone doesn't take functions (and we have transformers) + if (obj === null || typeof obj !== 'object') { + return obj + } + if (Array.isArray(obj)) { + return obj.map((el => this.deepClone(el))) + } + const clone = {} + for (const key in obj) { + clone[key] = this.deepClone(obj[key]) + } + return clone + } +} +const s2bConfig = [ + { chan: 'gps:agents', // What to subscribe to + events: [ // What to select on this chan + { eventName: 'moving', + snaps: [ + { + // selector will be used as css selector for a snap element / group, + // with LAST MINUTE template resolving of event properties (with eventual dots) + selector: '#${aid}', + assign: { + x: 'coords.x', // type string: event property, eventual dots to go down object + y: 'coords.y', + }, + animate: true + } + ] + }, + { eventName: 'rotating', + snaps: [ + { + selector: '#${aid}', + assign: { + r: 'rotangle' + }, + animate: true + } + ] + }, + ] + }, + { chan: 'agent:*', // wildcards allowed + events: [ + { eventName: 'aging', + snaps: [ + { + selector: '#{aid}', + assign: { + fill: { // transformer function + arguments: [ 'age' ], // What to give from the event as function's params + transformer: i => `rgb(${Math.round(255 * i / 10)},0,${Math.round(255 * (1 - i / 10))})` + }, + }, + } + ] + }, + ] + }, +] + +t = new SnapToBus(s2bConfig) +console.log('----------------------------------------') +t.busConfig.splice(1, 1) +t.busConfig[0].events.splice(1, 1) +t.commitConfig() +