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