rpd
Version:
RPD is a minimal framework for building Node-Based User Interfaces, powered by Reactive Programming
972 lines (831 loc) • 35.3 kB
JavaScript
;(function(global) {
"use strict";
var Kefir = global.Kefir;
if ((typeof Kefir === 'undefined') &&
(typeof require !== 'undefined')) Kefir = require('kefir');
if (!Kefir) throw new Error('Kefir.js (https://github.com/rpominov/kefir) is required for Rpd to work');
var VERSION = 'v3.0.0-alpha';
var Rpd = (function() {
injectKefirEmitter();
// Rpd.NOTHING, Rpd.ID_LENGTH, ...
//var PATCH_PROPS = [ 'title', '*handle' ];
var nodetypes = {}; var NODE_PROPS = [ 'title', '*inlets', '*outlets', 'prepare', 'process', 'tune', '*handle' ];
var channeltypes = {}; var INLET_PROPS = [ 'label', 'default', 'hidden', 'cold', 'readonly', 'allow', 'accept', 'adapt', 'tune', 'show', '*handle' ];
var OUTLET_PROPS = [ 'label', 'tune', 'show', '*handle' ];
var CHANNEL_PROPS = INLET_PROPS;
var noderenderers = {}; var NODE_RENDERER_PROPS = [ 'prepare', 'size', 'first', 'always' ];
var channelrenderers = {}; var CHANNEL_RENDERER_PROPS = [ 'prepare', 'show', 'edit' ];
var nodedescriptions = {};
var styles = {};
var nodetypeicons = {};
var toolkiticons = {};
var renderer_registry = {};
var event_types = { 'network/add-patch': [ 'patch' ] };
var rpdEvent = create_event_map(event_types);
var rpdEvents = create_events_stream(event_types, rpdEvent, 'network', Rpd);
rpdEvent['network/add-patch'].onValue(function(patch) { rpdEvents.plug(patch.events); });
var rendering;
function ƒ(v) { return function() { return v; } }
function create_rendering_stream() {
var rendering = Kefir.emitter();
rendering.map(function(rule) {
return {
rule: rule,
func: function(patch) {
patch.render(rule.aliases, rule.targets, rule.config)
}
}
}).scan(function(prev, curr) {
if (prev) rpdEvent['network/add-patch'].offValue(prev.func);
rpdEvent['network/add-patch'].onValue(curr.func);
return curr;
}, null).last().onValue(function(last) {
rpdEvent['network/add-patch'].offValue(last.func);
});
return rendering;
}
function /*Rpd.*/renderNext(aliases, targets, conf) {
if (!rendering) rendering = create_rendering_stream();
rendering.emit({ aliases: aliases, targets: targets, config: conf });
}
function /*Rpd.*/stopRendering() {
if (rendering) {
rendering.end();
rendering = null;
}
}
function /*Rpd.*/addPatch(arg0, arg1, arg2) {
return addClosedPatch(arg0, arg1).open(arg2);
}
function /*Rpd.*/addClosedPatch(arg0, arg1) {
var name = !is_object(arg0) ? arg0 : undefined; var def = arg1 || arg0;
var instance = new Patch(name, def);
rpdEvent['network/add-patch'].emit(instance);
return instance;
}
// =============================================================================
// ================================== Patch ====================================
// =============================================================================
function Patch(name, def) {
this.id = short_uid();
this.name = name;
this.def = def || {};
var patch = this;
var event_types = {
'patch/is-ready': [ ],
'patch/open': [ 'parent' ],
'patch/close': [ ],
'patch/move-canvas': [ 'position' ],
'patch/resize-canvas': [ 'size' ],
'patch/set-inputs': [ 'inputs' ],
'patch/set-outputs': [ 'outputs' ],
'patch/project': [ 'node', 'target' ],
'patch/refer': [ 'node', 'target' ],
'patch/add-node': [ 'node' ],
'patch/remove-node': [ 'node' ]
};
this.event = create_event_map(event_types);
this.events = create_events_stream(event_types, this.event, 'patch', this);
if (this.def.handle) subscribe(this.events, this.def.handle);
// this stream controls the way patch events reach the assigned renderer
this.renderQueue = Kefir.emitter();
var renderStream = Kefir.combine([ this.events ],
[ this.renderQueue.scan(function(storage, rule) {
var alias = rule.alias, target = rule.target, configuration = rule.config;
var renderer = storage[alias];
if (!renderer) {
renderer = {};
renderer.produce = renderer_registry[alias](patch);
renderer.handlers = [];
storage[alias] = renderer;
}
if (renderer.produce) {
var handler = renderer.produce(target, configuration);
if (handler) {
renderer.handlers.push(
(typeof handler === 'function') ? handler
: function(event) {
if (handler[event.type]) handler[event.type](event);
} );
}
}
return storage;
}, {}) ]);
// we need to wait for first renderer and then push there events happened before, then proceed
renderStream = renderStream.bufferBy(this.renderQueue).take(1).flatten().concat(renderStream);
renderStream.onValue(function(value) {
var event = value[0], renderers = value[1];
var aliases = Object.keys(renderers);
var renderer, handlers;
for (var i = 0, il = aliases.length; i < il; i++) {
renderer = renderers[aliases[i]]; handlers = renderer.handlers;
for (var j = 0, jl = handlers.length; j < jl; j++) {
handlers[j](inject_render(clone_obj(event), aliases[i]));
}
}
});
// projections are connections between different patches; patch inlets looking in the outer
// world are called "inputs" here, and outlets looking in the outer world are, correspondingly,
// called "outputs"
this.projections = Kefir.emitter();
Kefir.combine(
[ this.projections ],
[ this.event['patch/set-inputs'],
this.event['patch/set-outputs'] ]
).onValue(function(value) {
var node = value[0], inputs = value[1], outputs = value[2];
var inlet, outlet, input, output;
for (var i = 0; i < inputs.length; i++) {
inlet = node.addInlet(inputs[i].type, inputs[i].alias, inputs[i].def, inputs[i].render);
inlet.event['inlet/update'].onValue((function(input) {
return function(value) { input.receive(value); };
})(inputs[i]));
} // use inlet.onUpdate?
for (i = 0; i < outputs.length; i++) {
outlet = node.addOutlet(outputs[i].type, outputs[i].alias, outputs[i].def, outputs[i].render);
outputs[i].event['outlet/update'].onValue((function(outlet) {
return function(value) { outlet.send(value); };
})(outlet));
} // use output.onUpdate?
patch.event['patch/project'].emit({ node: node, target: node.patch });
node.patch.event['patch/refer'].emit({ node: node, target: patch });
});
this.nodesToRemove = Kefir.emitter();
this.event['patch/is-ready'].emit();
}
Patch.prototype.render = function(aliases, targets, config) {
aliases = Array.isArray(aliases) ? aliases : [ aliases ];
targets = Array.isArray(targets) ? targets : [ targets ];
for (var i = 0, il = aliases.length, alias; i < il; i++) {
for (var j = 0, jl = targets.length, target; j < jl; j++) {
alias = aliases[i]; target = targets[j];
if (!renderer_registry[alias]) report_system_error(this, 'patch', 'Renderer \'' + alias + '\' is not registered');
this.renderQueue.emit({ alias: alias, target: target, config: config });
}
}
return this;
}
Patch.prototype.addNode = function(type, arg1, arg2, arg3) {
var patch = this;
var def = arg2 ? arg2 : (is_object(arg1) ? (arg1 || {}) : {});
var title = is_object(arg1) ? undefined : arg1;
if (title) def.title = title;
var render = (arg1 && is_object(arg1)) ? arg2 : arg3;
var node = new Node(type, this, def, render, function(node) {
patch.events.plug(node.events);
patch.event['patch/add-node'].emit(node);
node.turnOn();
});
return node;
}
Patch.prototype.removeNode = function(node) {
node.turnOff();
this.event['patch/remove-node'].emit(node);
this.events.unplug(node.events);
return this;
}
Patch.prototype.open = function(parent) {
this.event['patch/open'].emit(parent);
return this;
}
Patch.prototype.close = function() {
this.event['patch/close'].emit();
return this;
}
Patch.prototype.inputs = function(list) {
this.event['patch/set-inputs'].emit(list);
return this;
}
Patch.prototype.outputs = function(list) {
this.event['patch/set-outputs'].emit(list);
return this;
}
Patch.prototype.project = function(node) {
this.projections.emit(node);
return this;
}
Patch.prototype.moveCanvas = function(x, y) {
this.event['patch/move-canvas'].emit([x, y]);
return this;
}
Patch.prototype.resizeCanvas = function(width, height) {
this.event['patch/resize-canvas'].emit([width, height]);
return this;
}
// =============================================================================
// ================================= Node ======================================
// =============================================================================
function Node(type, patch, def, render, callback) {
this.type = type || 'core/basic';
this.toolkit = extract_toolkit(type);
this.id = short_uid();
this.patch = patch;
var event_types = {
'node/turn-on': [ ],
'node/is-ready': [ ],
'node/process': [ 'inlets', 'outlets' ],
'node/turn-off': [ ],
'node/add-inlet': [ 'inlet' ],
'node/remove-inlet': [ 'inlet' ],
'node/add-outlet': [ 'outlet' ],
'node/remove-outlet': [ 'outlet' ],
'node/move': [ 'position' ],
'node/configure': [ 'props' ]
};
this.event = create_event_map(event_types);
this.events = create_events_stream(event_types, this.event, 'node', this);
var type_def = adapt_to_obj(nodetypes[this.type], this);
if (!type_def) report_system_error(this, 'node', 'Node type \'' + this.type + '\' is not registered!');
this.def = join_definitions(NODE_PROPS, def, type_def);
this.render = join_render_definitions(NODE_RENDERER_PROPS, render,
prepare_render_obj(noderenderers[this.type], this));
if (callback) callback(this);
var node = this;
if (this.def.handle) subscribe(this.events, this.def.handle);
if (this.def.process) {
var process_f = this.def.process;
var process = Kefir.combine([
// when new inlet was added, start monitoring its updates
// as an active stream
this.event['node/add-inlet'].flatMap(function(inlet) {
var updates = inlet.event['inlet/update'].map(function(value) {
return { inlet: inlet, value: value };
});;
if (node.def.tune) updates = node.def.tune.bind(node)(updates);
return updates;
})
],
[
// collect all the existing outlets aliases as a passive stream
this.event['node/add-outlet'].scan(function(storage, outlet) {
storage[outlet.alias] = outlet;
return storage;
}, {})
])
// do not fire any event until node is ready, then immediately fire them one by one, if any occured;
// later events are fired after node/is-ready, corresponding to their time of firing, as usual
process = process.bufferBy(this.event['node/is-ready']).take(1).flatten().concat(process);
process = process.scan(function(storage, update) {
// update[0] is inlet value update, update[1] is a list of outlets
var inlet = update[0].inlet;
var alias = inlet.alias;
storage.inlets.prev[alias] = storage.inlets.cur[alias];
storage.inlets.cur[alias] = update[0].value;
storage.outlets = update[1];
storage.source = inlet;
return storage;
}, { inlets: { prev: {}, cur: {} }, outlets: {} }).changes();
// filter cold inlets, so the update data will be stored, but process event won't fire
process = process.filter(function(data) { return !data.source.def.cold; });
process.onValue(function(data) {
// call a node/process event using collected inlet values
var outlets_vals = process_f.bind(node)(data.inlets.cur, data.inlets.prev);
node.event['node/process'].emit({ inlets: data.inlets.cur, outlets: outlets_vals });
// send the values provided from a `process` function to corresponding outlets
var outlets = data.outlets;
for (var outlet_name in outlets_vals) {
if (outlets[outlet_name]) {
if (outlets_vals[outlet_name] instanceof Kefir.Stream) {
outlets[outlet_name].stream(outlets_vals[outlet_name]);
} else {
outlets[outlet_name].send(outlets_vals[outlet_name]);
}
};
}
});
}
// only inlets / outlets described in type definition are stored inside
// (since they are constructed once), it lets user easily connect to them
if (this.def.inlets) {
this.inlets = {};
for (var alias in this.def.inlets) {
var conf = this.def.inlets[alias];
var inlet = this.addInlet(conf.type, alias, conf);
this.inlets[alias] = inlet;
}
}
if (this.def.outlets) {
this.outlets = {};
for (var alias in this.def.outlets) {
var conf = this.def.outlets[alias];
var outlet = this.addOutlet(conf.type, alias, conf);
this.outlets[alias] = outlet;
}
}
if (this.def.prepare) this.def.prepare.bind(this)(this.inlets, this.outlets);
this.event['node/is-ready'].emit();
}
Node.prototype.turnOn = function() {
this.event['node/turn-on'].emit();
return this;
}
Node.prototype.turnOff = function() {
this.event['node/turn-off'].emit();
return this;
}
Node.prototype.addInlet = function(type, alias, arg2, arg3, arg4) {
var def = arg3 ? arg3 : (is_object(arg2) ? (arg2 || {}) : {});
var label = is_object(arg2) ? undefined : arg2;
if (label) def.label = label;
var render = (arg2 && is_object(arg2)) ? arg3 : arg4;
var inlet = new Inlet(type, this, alias, def, render);
this.events.plug(inlet.events);
this.event['node/add-inlet'].emit(inlet);
inlet.toDefault();
return inlet;
}
Node.prototype.addOutlet = function(type, alias, arg2, arg3, arg4) {
var def = arg3 ? arg3 : (is_object(arg2) ? (arg2 || {}) : {});
var label = is_object(arg2) ? undefined : arg2;
if (label) def.label = label;
var render = (arg2 && is_object(arg2)) ? arg3 : arg4;
var outlet = new Outlet(type, this, alias, def, render);
this.events.plug(outlet.events);
this.event['node/add-outlet'].emit(outlet);
outlet.toDefault();
return outlet;
}
Node.prototype.removeInlet = function(inlet) {
this.event['node/remove-inlet'].emit(inlet);
this.events.unplug(inlet.events);
return this;
}
Node.prototype.removeOutlet = function(outlet) {
this.event['node/remove-outlet'].emit(outlet);
this.events.unplug(outlet.events);
return this;
}
Node.prototype.move = function(x, y) {
this.event['node/move'].emit([ x, y ]);
return this;
}
Node.prototype.configure = function(props) {
this.event['node/configure'].emit(props);
return this;
}
// =============================================================================
// ================================== Inlet ====================================
// =============================================================================
function Inlet(type, node, alias, def, render) {
this.type = type || 'core/any';
this.toolkit = extract_toolkit(type);
this.id = short_uid();
this.alias = alias;
this.node = node;
var type_def = adapt_to_obj(channeltypes[this.type], this);
if (!type_def) report_system_error(this, 'inlet', 'Channel type \'' + this.type + '\' is not registered!');
this.def = join_definitions(INLET_PROPS, def, type_def);
if (!this.alias) report_error(this, 'inlet', 'Inlet should have an alias');
this.value = Kefir.pool();
this.render = join_render_definitions(CHANNEL_RENDERER_PROPS, render,
prepare_render_obj(channelrenderers[this.type], this));
var event_types = {
'inlet/update': [ 'value' ]
};
this.event = create_event_map(event_types);
var orig_updates = this.event['inlet/update'];
var updates = orig_updates.merge(this.value);
if (this.def.tune) updates = this.def.tune.bind(this)(updates);
if (this.def.accept) updates = updates.flatten(function(v) {
if (this.def.accept(v)) { return [v]; } else {
orig_updates.error(make_silent_error(this, 'inlet')); return [];
}
}.bind(this));
if (this.def.adapt) updates = updates.map(this.def.adapt);
// rewrite with the modified stream
this.event['inlet/update'] = updates.onValue(function(){});
this.events = create_events_stream(event_types, this.event, 'inlet', this);
if (this.def.handle) subscribe(this.events, this.def.handle);
}
Inlet.prototype.receive = function(value) {
this.value.plug(Kefir.constant(value));
return this;
}
Inlet.prototype.stream = function(stream) {
this.value.plug(stream);
return this;
}
Inlet.prototype.toDefault = function() {
if (is_defined(this.def.default)) {
if (this.def.default instanceof Kefir.Stream) {
this.stream(this.def.default);
} else this.receive(this.def.default);
}
return this;
}
Inlet.prototype.allows = function(outlet) {
if (this.type === 'core/any') return true;
if (outlet.type === this.type) return true;
if (!this.def.allow && (outlet.type !== this.type)) return false;
if (this.def.allow) {
var matched = false;
this.def.allow.forEach(function(allowedType) {
if (outlet.type === allowedType) { matched = true; };
});
return matched;
}
return true;
}
// =============================================================================
// ================================= Outlet ====================================
// =============================================================================
function Outlet(type, node, alias, def, render) {
this.type = type || 'core/any';
this.toolkit = extract_toolkit(type);
this.id = short_uid();
this.alias = alias;
this.node = node;
var type_def = adapt_to_obj(channeltypes[this.type], this);
if (!type_def) report_system_error(this, 'outlet', 'Channel type \'' + this.type + '\' is not registered!');
this.def = join_definitions(OUTLET_PROPS, def, type_def);
if (!this.alias) report_error(this, 'outlet', 'Outlet should have an alias');
this.value = Kefir.pool();
this.render = join_render_definitions(CHANNEL_RENDERER_PROPS, render,
prepare_render_obj(channelrenderers[this.type], this));
// outlets values are not editable
var event_types = {
'outlet/update': [ 'value' ],
'outlet/connect': [ 'link', 'inlet' ],
'outlet/disconnect': [ 'link' ]
};
this.event = create_event_map(event_types);
var orig_updates = this.event['outlet/update'];
var updates = orig_updates.merge(this.value);
// rewrite with the modified stream
this.event['outlet/update'] = updates.onValue(function(v){});
this.events = create_events_stream(event_types, this.event, 'outlet', this);
if (this.def.handle) subscribe(this.events, this.def.handle);
// re-send last value on connection
var outlet = this;
Kefir.combine([ this.event['outlet/connect'] ],
[ this.event['outlet/update'] ])
.onValue(function(update) {
outlet.value.plug(Kefir.constant(update[1]));
});
}
Outlet.prototype.connect = function(inlet) {
if (!inlet.allows(this)) {
report_error(this, 'outlet', 'Outlet of type \'' + this.type + '\' is not allowed to connect to inlet of type \'' + inlet.type + '\'');
}
var link = new Link(this, inlet);
this.events.plug(link.events);
this.value.onValue(link.receiver);
this.event['outlet/connect'].emit({ link: link, inlet: inlet });
return link;
}
Outlet.prototype.disconnect = function(link) {
this.event['outlet/disconnect'].emit(link);
this.value.offValue(link.receiver);
this.events.unplug(link.events);
return this;
}
Outlet.prototype.send = function(value) {
this.value.plug(Kefir.constant(value));
return this;
}
Outlet.prototype.stream = function(stream) {
this.value.plug(stream);
return this;
}
Outlet.prototype.toDefault = function() {
if (is_defined(this.def.default)) {
if (this.def.default instanceof Kefir.Stream) {
this.stream(this.def.default);
} else this.send(this.def.default);
}
return this;
}
// =============================================================================
// ================================= Link ======================================
// =============================================================================
function Link(outlet, inlet, label) {
this.id = short_uid();
this.name = label || '';
this.outlet = outlet;
this.inlet = inlet;
this.value = Kefir.emitter();
var link = this;
this.receiver = (outlet.node.id !== inlet.node.id)
? function(x) { link.pass(x); }
: function(x) {
// this avoids stack overflow on recursive connections
setTimeout(function() { link.pass(x); }, 0);
};
var event_types = {
'link/enable': [ ],
'link/disable': [ ],
'link/pass': [ 'value' ]
};
this.event = create_event_map(event_types);
var orig_updates = this.event['link/pass'];
var updates = orig_updates.merge(this.value);
// rewrite with the modified stream
this.event['link/pass'] = updates.onValue(function(v){});
this.events = create_events_stream(event_types, this.event, 'link', this);
this.enabled = Kefir.merge([ this.event['link/disable'].map(ƒ(false)),
this.event['link/enable'].map(ƒ(true)) ]).toProperty(ƒ(true));
this.event['link/pass'].filterBy(this.enabled).onValue(function(value) {
inlet.receive(value);
});
// re-send last value on enable
Kefir.combine([ this.event['link/enable'] ],
[ this.event['link/pass'] ])
.onValue(function(event) {
link.pass(event[1]);
});
}
Link.prototype.pass = function(value) {
this.value.emit(value);
return this;
}
Link.prototype.enable = function() {
this.event['link/enable'].emit();
return this;
}
Link.prototype.disable = function() {
this.event['link/disable'].emit();
return this;
}
Link.prototype.disconnect = function() {
this.outlet.disconnect(this);
return this;
}
// =============================================================================
// ================================== utils ====================================
// =============================================================================
function injectKefirEmitter() {
Kefir.emitter = function() {
var e, stream = Kefir.stream(function(_e) {
e = _e; return function() { e = undefined; }
});
stream.emit = function(x) { e && e.emit(x); return this; }
stream.error = function(x) { e && e.error(x); return this; }
stream.end = function() { e && e.end(); return this; }
stream.emitEvent = function(x) { e && e.emitEvent(x); return this; }
return stream.setName('emitter');
}
}
function join_definitions(keys, src1, src2) {
var trg = {}; src1 = src1 || {}; src2 = src2 || {};
var key;
for (var i = 0, il = keys.length; i < il; i++) {
key = keys[i];
if (key[0] !== '*') {
if (!(key in src1) && !(key in src2)) continue;
trg[key] = is_defined(src1[key]) ? src1[key] : src2[key];
} else {
key = key.slice(1);
if (!(key in src1) && !(key in src2)) continue;
trg[key] = {};
var src2_keys = src2[key] ? Object.keys(src2[key]) : [];
for (var j = 0, jl = src2_keys.length; j < jl; j++) {
trg[key][src2_keys[j]] = src2[key][src2_keys[j]];
}
var src1_keys = src1[key] ? Object.keys(src1[key]) : [];
for (var j = 0, jl = src1_keys.length; j < jl; j++) {
trg[key][src1_keys[j]] = src1[key][src1_keys[j]];
}
}
}
return trg;
}
function clone_obj(src) {
// this way is not a deep-copy and actually not cloning at all, but that's ok,
// since we use it few times for events, which are simple objects and the objects they
// pass, should be the same objects they got; just events by themselves should be different.
var res = {}; var keys = Object.keys(src);
for (var i = 0, il = keys.length; i < il; i++) {
res[keys[i]] = src[keys[i]];
}
return res;
}
function is_object(val) {
return (typeof val === 'object');
}
function is_defined(val) {
return (typeof val !== 'undefined');
}
function adapt_to_obj(val, subj) {
if (!val) return null;
if (typeof val === 'function') return val(subj);
return val;
}
function prepare_render_obj(template, subj) {
if (!template) return {};
var render_obj = {};
for (var render_type in template) {
render_obj[render_type] = adapt_to_obj(template[render_type], subj);
}
return render_obj;
}
function join_render_definitions(keys, user_render, type_render) {
if (!user_render) return type_render;
var result = {};
for (var render_type in type_render) {
result[render_type] = join_definitions(keys, user_render[render_type], type_render[render_type]);
}
for (var render_type in user_render) {
if (!result[render_type] && !type_render[render_type]) result[render_type] = user_render[render_type];
}
return result;
}
function create_event_map(conf) {
var map = {}; var types = Object.keys(conf);
for (var i = 0, type; i < types.length; i++) {
type = types[i];
map[type] = Kefir.emitter();
}
return map;
}
function adapt_events(type, spec) {
if (spec.length === 0) return function() { return { type: type } };
if (spec.length === 1) return function(value) { var evt = {}; evt.type = type; evt[spec[0]] = value; return evt; };
if (spec.length > 1) return function(event) { event = clone_obj(event); event.type = type; return event; };
}
function create_events_stream(conf, event_map, subj_as, subj) {
var stream = Kefir.pool(); var types = Object.keys(conf);
for (var i = 0; i < types.length; i++) {
stream.plug(event_map[types[i]]
.map(adapt_events(types[i], conf[types[i]]))
.map(function(evt) {
evt[subj_as] = subj;
return evt;
}));
}
return stream;
}
function subscribe(events, handlers) {
events.filter(function(event) { return handlers[event.type]; })
.onValue(function(event) {
handlers[event.type](event);
});
}
function make_silent_error(subject, subject_name) {
return make_error(subject, subject_name, null, false, true);
}
function make_error(subject, subject_name, message, is_system, is_silent) {
return { type: subject_name + '/error', system: is_system || false,
subject: subject, message: message, silent: is_silent || false };
}
function report_error(subject, subject_name, message, is_system) {
rpdEvents.plug(Kefir.constantError(make_error(subject, subject_name, message, is_system)));
}
function report_system_error(subject, subject_name, message) {
report_error(subject, subject_name, message, true);
}
function short_uid() {
return ("0000" + (Math.random() * Math.pow(36,4) << 0).toString(36)).slice(-4);
}
function inject_render(update, alias) {
var type = update.type;
if ((type === 'patch/add-node') || (type === 'node/process')) {
update.render = update.node.render[alias] || {};
} else if ((type === 'node/add-inlet') || (type === 'inlet/update')) {
update.render = update.inlet.render[alias] || {};
} else if ((type === 'node/add-outlet') || (type === 'outlet/update')) {
update.render = update.outlet.render[alias] || {};
}
return update;
}
function get_style(name, renderer) {
if (!name) report_system_error(null, 'network', 'Unknown style requested: \'' + name + '\'');
if (!styles[name]) report_system_error(null, 'network', 'Style \'' + name + '\' is not registered');
var style = styles[name][renderer];
if (!style) report_system_error(null, 'network', 'Style \'' + name + '\' has no definition for \'' + renderer + '\' renderer');
return style;
}
function extract_toolkit(type) {
var slashPos = type.indexOf('/');
return (slashPos >= 0) ? type.substring(0, slashPos) : '';
}
// =============================================================================
// =========================== registration ====================================
// =============================================================================
function nodetype(type, def) {
nodetypes[type] = def || {};
}
function channeltype(type, def) {
channeltypes[type] = def || {};
}
function renderer(alias, f) {
renderer_registry[alias] = f;
}
function noderenderer(type, alias, data) {
if (!nodetypes[type]) report_system_error(null, 'network', 'Node type \'' + type + '\' is not registered');
if (!noderenderers[type]) noderenderers[type] = {};
noderenderers[type][alias] = data;
}
function channelrenderer(type, alias, data) {
if (!channeltypes[type]) report_system_error(null, 'network', 'Channel type \'' + type + '\' is not registered');
if (!channelrenderers[type]) channelrenderers[type] = {};
channelrenderers[type][alias] = data;
}
function nodedescription(type, description) {
nodedescriptions[type] = description;
}
function style(name, renderer, func) {
if (!styles[name]) styles[name] = {};
styles[name][renderer] = func;
}
function toolkiticon(toolkit, icon) {
toolkiticons[toolkit] = icon;
}
function nodetypeicon(type, icon) {
nodetypeicons[type] = icon;
}
nodetype('core/basic', {});
nodetype('core/reference', {});
channeltype('core/any', {});
// =============================================================================
// ========================== stringification ==================================
// =============================================================================
var stringify = {};
stringify.patch = function(patch) {
return '[ Patch' + (patch.name ? (' \'' + patch.name + '\'') : ' <Unnamed>') + ' ]';
};
stringify.node = function(node) {
return '[ Node (' + node.type + ')' +
(node.def ? (node.def.title ? (' \'' + node.def.title + '\'') : ' <Untitled>') : ' <Unprepared>') +
' #' + node.id +
' ]';
};
stringify.outlet = function(outlet) {
return '[ Outlet (' + outlet.type + ')' +
(outlet.alias ? (' \'' + outlet.alias + '\'') : ' <Unaliased>') +
(outlet.def ? (outlet.def.label ? (' \'' + outlet.def.label + '\'') : ' <Unlabeled>') : ' <Unprepared>') +
' #' + outlet.id +
' ]';
};
stringify.inlet = function(inlet) {
return '[ Inlet (' + inlet.type + ')' +
(inlet.alias ? (' \'' + inlet.alias + '\'') : ' <Unaliased>') +
(inlet.def ? (inlet.def.label ? (' \'' + inlet.def.label + '\'') : ' <Unlabeled>') : ' <Unprepared>') +
' #' + inlet.id +
(inlet.def.hidden ? ' (hidden)' : '') +
(inlet.def.cold ? ' (cold)' : '') +
' ]';
};
stringify.link = function(link) {
return '[ Link (' + link.type + ')' +
' \'' + link.name + '\'' +
' #' + link.id +
' ' + stringify.outlet(link.outlet) + ' ->' +
' ' + stringify.inlet(link.inlet) +
' ]';
};
function autoStringify(value) {
if (value instanceof Patch) { return stringify.patch(value); }
if (value instanceof Node) { return stringify.node(value); }
if (value instanceof Inlet) { return stringify.inlet(value); }
if (value instanceof Outlet) { return stringify.outlet(value); }
if (value instanceof Link) { return stringify.link(value); }
return '<?> ' + value;
}
// =============================================================================
// =============================== export ======================================
// =============================================================================
return {
'VERSION': VERSION,
'_': { 'Patch': Patch, 'Node': Node, 'Inlet': Inlet, 'Outlet': Outlet, 'Link': Link },
'unit': ƒ,
'not': function(value) { return !value; },
'event': rpdEvent,
'events': rpdEvents,
'addPatch': addPatch,
'addClosedPatch': addClosedPatch,
'renderNext': renderNext,
'stopRendering': stopRendering,
'nodetype': nodetype,
'channeltype': channeltype,
'nodedescription': nodedescription,
'renderer': renderer, 'styles': styles, 'style': style,
'noderenderer': noderenderer,
'channelrenderer': channelrenderer,
'toolkiticon': toolkiticon,
'nodetypeicon': nodetypeicon,
'import': {}, 'export': {},
'allNodeTypes': nodetypes,
'allChannelTypes': channeltypes,
'allNodeRenderers': noderenderers,
'allChannelRenderers': channelrenderers,
'allNodeDescriptions': nodedescriptions,
'allNodeTypeIcons': nodetypeicons,
'allToolkitIcons': toolkiticons,
'getStyle': get_style,
'reportError': report_error,
'reportSystemError': report_system_error,
'short_uid': short_uid,
'stringify': stringify,
'autoStringify': autoStringify
}
})();
if (typeof define === 'function' && define.amd) {
define([], function() { return Rpd; });
global.Rpd = Rpd;
} else if (typeof module === 'object' && typeof exports === 'object') {
module.exports = Rpd;
Rpd.Rpd = Rpd;
} else {
global.Rpd = Rpd;
}
}(this));