203 lines
7.5 KiB
JavaScript
203 lines
7.5 KiB
JavaScript
/**
|
|
* @classdesc The main Snaptobus class
|
|
* @author Nike
|
|
* @version 1.0
|
|
*/
|
|
|
|
class Snaptobus{
|
|
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')
|
|
? this.observeObject(value, onChange)
|
|
: value
|
|
|
|
const handler = {
|
|
set: (target, prop, value) => {
|
|
const oldValue = target[prop]
|
|
target[prop] = wrap(value)
|
|
onChange(prop, value, oldValue, target)
|
|
return true
|
|
},
|
|
deleteProperty: (target, prop) => {
|
|
const oldValue = target[prop]
|
|
delete target[prop]
|
|
onChange(prop, undefined, oldValue, target)
|
|
return true
|
|
}
|
|
}
|
|
|
|
// Walk initial keys/elements (construction time)
|
|
for (const key of Object.keys(obj)) {
|
|
obj[key] = wrap(obj[key])
|
|
}
|
|
|
|
return new Proxy(obj, handler)
|
|
}
|
|
|
|
|
|
processBusEvent(eventType, chan, payload, userId, x){
|
|
const chanObj = this._curBusConfig.find(item => app.MessageBus.chanMatch(chan, item.chan))
|
|
if(!chanObj) return
|
|
const eventObj = chanObj.events.find(item => item.eventName==eventType)
|
|
if(!eventObj) return
|
|
// 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)
|
|
if(snapDef.animate){
|
|
snapEl.animate(
|
|
newAttr,
|
|
400,
|
|
mina.linear
|
|
)
|
|
} else {
|
|
snapEl.attr(newAttr)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
assignFromConfig(data, replaceDef) {
|
|
console.log('assignFromConfig', data, replaceDef)
|
|
const result = {}
|
|
for (const [key, rule] of Object.entries(replaceDef)) {
|
|
if (typeof rule === 'string') { // plain path
|
|
result[key] = this.getValueByPath(data, rule)
|
|
} else if((typeof(rule) == 'object') && (typeof(rule.transformer) == 'function')) { // transformer
|
|
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 = [
|
|
{ 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))})`
|
|
},
|
|
},
|
|
}
|
|
]
|
|
},
|
|
]
|
|
},
|
|
]
|
|
*/
|
|
|
|
/*
|
|
payload =
|
|
{}
|
|
eventType
|
|
*/
|