rpd
Version:
RPD is a minimal framework for building Node-Based User Interfaces, powered by Reactive Programming
331 lines (292 loc) • 12.5 kB
JavaScript
;(function(global) {
"use strict";
var Rpd = global.Rpd;
if (typeof Rpd === "undefined" && typeof require !== "undefined") {
Rpd = require('rpd');
}
var δ = Rpd.Render.data;
Rpd.channelrenderer('util/boolean', 'html', {
/* show: function(target, value) { }, */
/* edit: function(target, inlet, valueIn) {
var valInput = document.createElement('input');
valInput.type = 'checkbox';
valueIn.onValue(function(val) {
valInput.checked = val ? true : false;
});
target.appendChild(valInput);
return Kefir.fromEvents(valInput, 'change')
.map(function() {
return valInput.checked;
}).toProperty(function() { return false; });
} */
});
Rpd.channelrenderer('util/number', 'html', {
/* show: function(target, value) { }, */
edit: function(target, inlet, valueIn) {
var valInput = document.createElement('input');
valInput.type = 'number';
valueIn.onValue(function(val) {
valInput.value = val;
});
target.appendChild(valInput);
return Kefir.fromEvents(valInput, 'change')
.map(function() { return valInput.value; });
}
});
Rpd.channelrenderer('util/wholenumber', 'html', Rpd.allChannelRenderers['util/number']['html']);
Rpd.noderenderer('util/number', 'html', {
first: function(bodyElm) {
var valInput = document.createElement('input');
valInput.style.display = 'block';
valInput.type = 'number';
valInput.min = 0;
valInput.max = 1000;
bodyElm.appendChild(valInput);
return { 'user-value':
{ default: function() { valInput.value = 0; return 0; },
valueOut: Kefir.fromEvents(valInput, 'change')
.map(function() { return valInput.value; })
}
};
}
});
Rpd.noderenderer('util/bounded-number', 'html', function() {
var spinnerElm, spinner;
return {
size: { width: 25 },
first: function(bodyElm) {
spinnerElm = document.createElement('span');
spinnerElm.classList.add('rpd-util-spinner');
spinner = new Spinner(spinnerElm);
var changes = spinner.getChangesStream();
bodyElm.appendChild(spinnerElm);
return {
'spinner': { valueOut: changes.map(function(val) {
return parseFloat(val);
}) }
};
},
always: function(bodyElm, inlets) {
spinner.updateBounds(inlets.min, inlets.max);
spinnerElm.innerText = spinnerElm.textContent = spinner.setValue(inlets.spinner);
}
}
});
Rpd.noderenderer('util/comment', 'html', function() {
var textElm;
return {
size: { width: 100, height: 150 },
first: function(bodyElm) {
textElm = document.createElement('span');
bodyElm.appendChild(textElm);
},
always: function(bodyElm, inlets, outlets) {
if (inlets.width) bodyElm.style.width = inlets.width + 'px';
textElm.textContent = textElm.innerText = inlets.text || '<empty>';
}
}
});
Rpd.noderenderer('util/log', 'html', function() {
var textElm;
var capacity = 5;
var savedValues = [];
return {
size: { width: 130, height: 30 },
first: function(bodyElm) {
textElm = document.createElement('span');
bodyElm.appendChild(textElm);
},
always: function(bodyElm, inlets, outlets) {
if (inlets.what) {
if (savedValues.length > capacity) savedValues.shift();
savedValues.push(inlets.what);
}
textElm.textContent = textElm.innerText = (savedValues.length > 0) ? ('...' + savedValues.join(', ') + '.') : '...';
}
}
});
Rpd.noderenderer('util/sum-of-three', 'html', {
size: { width: null, height: 80 },
always: function(bodyElm, inlets, outlets) {
bodyElm.innerHTML = '∑ (' + (inlets.a || '?') + ', '
+ (inlets.b || '?') + ', '
+ (inlets.c || '?') + ') = ' + (outlets.sum || '?');
}
});
var toHexColor = RpdUtils.toHexColor;
Rpd.noderenderer('util/color', 'html', function() {
var colorElm;
return {
size: { width: 30, height: 30 },
first: function(bodyElm) {
colorElm = document.createElement('span');
colorElm.classList.add('rpd-util-color-display');
bodyElm.appendChild(colorElm);
},
always: function(bodyElm, inlets, outlets) {
colorElm.style.backgroundColor = toHexColor(outlets.color);
}
}
});
Rpd.noderenderer('util/bang', 'html', function() {
var bangElm;
return {
size: { width: 30, height: 25 },
first: function(bodyElm) {
bangElm = document.createElement('span');
var bangClicks = Kefir.fromEvents(bangElm, 'click');
bodyElm.appendChild(bangElm);
bangClicks.onValue(function() {
bangElm.classList.add('rpd-util-bang-fresh');
});
bangClicks.delay(500).onValue(function() {
bangElm.classList.remove('rpd-util-bang-fresh');
});
return { 'trigger':
{ valueOut: bangClicks.map(function() { return {}; }) }
};
}
}
});
Rpd.noderenderer('util/metro', 'html', function() {
var metroElm;
return {
size: { width: 30, height: 25 },
first: function(bodyElm) {
metroElm = document.createElement('span');
bodyElm.appendChild(metroElm);
},
always: function(bodyElm, inlets, outlets) {
if (outlets.bang) {
outlets.bang.onValue(function() {
metroElm.classList.add('rpd-util-metro-fresh');
}).delay(500).onValue(function() {
metroElm.classList.remove('rpd-util-metro-fresh');
});
}
}
}
});
var NodeList = RpdUtils.NodeList;
var getNodeTypesByToolkit = RpdUtils.getNodeTypesByToolkit;
Rpd.noderenderer('util/nodelist', 'html', {
first: function(bodyElm) {
var patch = this.patch;
var nodeTypes = Rpd.allNodeTypes,
nodeDescriptions = Rpd.allNodeDescriptions,
toolkitIcons = Rpd.allToolkitIcons,
nodeTypeIcons = Rpd.allNodeTypeIcons;
var nodeTypesByToolkit = getNodeTypesByToolkit(nodeTypes);
var nodeList = new NodeList({
getPatch: function() { return patch; },
buildList: function() {
var listElements = [];
// build the list html structure
d3.select(bodyElm)
.append('dl')
.call(function(dl) {
Object.keys(nodeTypesByToolkit).forEach(function(toolkit) {
dl.append('dt')
.call(function(dt) {
if (toolkitIcons[toolkit]) dt.append('span').attr('class', 'rpd-nodelist-toolkit-icon').text(toolkitIcons[toolkit]);
dt.append('span').attr('class', 'rpd-nodelist-toolkit-name').text(toolkit)
});
dl.append('dd')
.append('ul')
.call(function(ul) {
nodeTypesByToolkit[toolkit].types.forEach(function(nodeTypeDef) {
var nodeType = nodeTypeDef.fullName;
ul.append('li')
.call(function(li) {
var elmData = { def: nodeTypeDef,
element: li };
δ(li, elmData);
li.append('span').attr('class', 'rpd-nodelist-icon').text(nodeTypeIcons[nodeType] || String.fromCharCode(160));
li.append('span').attr('class', 'rpd-nodelist-fulltypename')
.call(function(span) {
span.append('span').attr('class', 'rpd-nodelist-toolkit').text(nodeTypeDef.toolkit);
span.append('span').attr('class', 'rpd-nodelist-separator').text('/');
span.append('span').attr('class', 'rpd-nodelist-typename').text(nodeTypeDef.name);
})
if (nodeDescriptions[nodeType]) {
li.append('span').attr('class', 'rpd-nodelist-description')
.attr('title', nodeDescriptions[nodeType])
.text(nodeDescriptions[nodeType]);
}
listElements.push(elmData);
})
});
});
});
});
return listElements;
},
createSearchInput: function() {
return d3.select(bodyElm).append('input').attr('type', 'text');
},
createClearSearchButton: function() {
return d3.select(bodyElm).append('a').attr('href', '#').text('x');
},
clearSearchInput: function(searchInput) { searchInput.node().value = ''; },
recalculateSize: function() {},
markSelected: function(elmData) { elmData.element.classed('rpd-nodelist-selected', true); },
markDeselected: function(elmData) { elmData.element.classed('rpd-nodelist-selected', false); },
markAdding: function(elmData) { elmData.element.classed('rpd-nodelist-add-effect', true); },
markAdded: function(elmData) { elmData.element.classed('rpd-nodelist-add-effect', false); },
setVisible: function(elmData) { elmData.element.style('display', 'list-item'); },
setInvisible: function(elmData) { elmData.element.style('display', 'none'); }
});
nodeList.addOnClick();
nodeList.addSearch();
nodeList.addCtrlSpaceAndArrows();
}
});
function extractPos(evt) { return { x: evt.clientX,
y: evt.clientY }; };
function Spinner(element, min, max) {
this.element = element;
this.min = min || 0;
this.max = isNaN(max) ? Infinity : max;
this.value = this.min;
var spinner = this;
this.incoming = Kefir.emitter();
/*changes.onValue(function(value) {
spinner.value = val;
});*/
Kefir.fromEvents(element, 'mousedown')
.map(extractPos)
.flatMap(function(startPos) {
var start = spinner.value;
return Kefir.fromEvents(document.body, 'mousemove')
.map(extractPos)
.takeUntilBy(Kefir.fromEvents(document.body, 'mouseup'))
.map(function(newPos) { return start + (newPos.x - startPos.x); })
.onValue(function(num) { spinner.incoming.emit(num); })
}).onEnd(function() {});
this.changes = this.incoming.map(function(value) {
return spinner.setValue(value); // returns value updated to bounds
});
//this.changes.onValue(function() {});
}
Spinner.prototype.setValue = function(value) {
this.value = value;
return this.checkValue();
}
Spinner.prototype.checkValue = function() {
if (isNaN(this.value) || (this.value < this.min)) {
this.value = this.min; this.incoming.emit(this.min);
}
if (this.value > this.max) {
this.value = this.max; this.incoming.emit(this.max);
}
return this.value;
}
Spinner.prototype.updateBounds = function(min, max) {
this.min = min || 0;
this.max = isNaN(max) ? Infinity : max;
return this.checkValue();
}
Spinner.prototype.getChangesStream = function() {
return this.changes;
}
})(this);