snaptobus starts to work...
This commit is contained in:
+111
-17
@@ -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
|
||||
*/
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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()
|
||||
|
||||
Reference in New Issue
Block a user