UNPKG

rpd

Version:

RPD is a minimal framework for building Node-Based User Interfaces, powered by Reactive Programming

223 lines (186 loc) 9.04 kB
;(function(global) { "use strict"; var RpdUtils = (function() { function adaptToState(state, value) { return Math.floor((state.min + ((state.max - state.min) * value)) * 100) / 100; } function numberToHex(num) { return (num > 15) ? num.toString(16) : '0' + num.toString(16); } function toHexColor(color) { return '#' + numberToHex(color.r || 0) + numberToHex(color.g || 0) + numberToHex(color.b || 0); } function getNodeTypesByToolkit(nodeTypes) { return Object.keys(nodeTypes).reduce(function(byToolkit, nodeType) { var slashPos = nodeType.indexOf('/'); var toolkit = (slashPos < 0) ? toolkit : nodeType.substring(0, slashPos); var typeName = (slashPos < 0) ? '' : nodeType.substring(slashPos + 1); if (!byToolkit[toolkit]) byToolkit[toolkit] = { icon: '', types: [] }; byToolkit[toolkit].types.push({ toolkit: toolkit, fullName: nodeType, name: typeName, data: nodeTypes[nodeType] }); return byToolkit; }, {}) } function NodeList(conf) { this.listElements = []; this.selected = null; this.markSelected = conf.markSelected; this.markDeselected = conf.markDeselected; this.markAdding = conf.markAdding; this.markAdded = conf.markAdded; this.recalculateSize = conf.recalculateSize; this.setVisible = conf.setVisible; this.setInvisible = conf.setInvisible; this.getPatch = conf.getPatch; // create text input, search field, to let user filter results this.searchInput = conf.createSearchInput(); // create a button which is able to clear this field this.clearSearchButton = conf.createClearSearchButton(); var listElements = conf.buildList(); var search = this.searchInput, clearSearch = this.clearSearchButton; this.clearingEvents = Kefir.fromEvents(this.clearSearchButton.node(), 'click') .onValue(function() { this.selectNothing(); conf.clearSearchInput(this.searchInput); }.bind(this)); // make the list of elements double-linked and looped, // so easy navigation with up/down arrow keys will be possible for (var i = 0; i < listElements.length; i++) { listElements[i].visible = true; listElements[i].prev = (i > 0) ? listElements[i - 1] : listElements[listElements.length - 1]; listElements[i].next = (i < listElements.length - 1) ? listElements[i + 1] : listElements[0]; } this.listElements = listElements; this.currentlyVisible = listElements.length; } NodeList.prototype.select = function(elmData) { if (this.selected) this.markDeselected(this.selected); this.selected = elmData; if (this.selected) this.markSelected(elmData); } NodeList.prototype.selectNothing = function() { this.select(null); } NodeList.prototype.addOnClick = function() { var nodeList = this; var listElements = this.listElements; listElements.forEach(function(elmData) { var li = elmData.element; Kefir.fromEvents(li.node(), 'click') .onValue(function() { // add the node when corresponding element was clicked with mouse nodeList.select(elmData); nodeList.addNode(elmData); }); }); } NodeList.prototype.addNode = function(elmData) { this.markAdding(elmData); setTimeout(function() { this.markAdded(elmData); }.bind(this), 1000); this.getPatch().addNode(elmData.def.fullName); } NodeList.prototype.addSearch = function() { var nodeList = this; var search = this.searchInput; // make seach field hide filtered results when user changes search request Kefir.fromEvents(search.node(), 'input') .merge(this.clearingEvents) .throttle(500) .map(function() { return search.node().value; }) .onValue(function(searchString) { var visibleBefore = nodeList.currentlyVisible; nodeList.currentlyVisible = 0; nodeList.listElements.forEach(function(elmData) { var index = elmData.def.fullName.indexOf(searchString); if (index >= 0) { nodeList.setVisible(elmData); } else { nodeList.setInvisible(elmData); }; elmData.visible = (index >= 0); if (elmData.visible) nodeList.currentlyVisible++; }); nodeList.recalculateSize(nodeList.listElements, visibleBefore, nodeList.currentlyVisible); }); } NodeList.prototype.addCtrlSpaceAndArrows = function() { var nodeList = this; var listElements = this.listElements; var search = this.searchInput, clearSearch = this.clearSearchButton; var clearingEvents = this.clearingEvents; // ctrl+space should focus on the search field and up/down arrows should // work for selection Kefir.merge([ Kefir.fromEvents(document.body, 'keyup') .filter(function(evt) { // (control / alt / cmd) + space // TODO: double space (#63)? return (evt.which == 32 || evt.keyCode == 32) && (evt.altKey || evt.metaKey || evt.ctrlKey); }), Kefir.fromEvents(search.node(), 'click') ]).flatMap(function(switchedOn) { //console.log('start!'); search.node().focus(); if (listElements.length > 0) nodeList.select(listElements[0]); return Kefir.fromEvents(document.body, 'keyup') .map(function(evt) { return evt.which || evt.keyCode; }) .filter(function(key) { return (key === 38) || (key === 40); }) .map(function(key) { return (key === 38) ? 'up' : 'down'; }) .takeUntilBy(Kefir.merge([ Kefir.fromEvents(document.body, 'keyup') .filter(function(evt) { return (evt.which == 13 || evt.keyCode == 13); // key == enter }).map(function() { return true; }), Kefir.fromEvents(document.body, 'keyup').filter(function(evt) { return (evt.which == 27 || evt.keyCode == 27); // key === escape }).map(function() { return false; }), Kefir.fromEvents(document.body, 'click').filter(function(evt) { return evt.target !== search.node(); }).map(function() { return false; }), nodeList.clearingEvents.map(function() { return false; })/*, Kefir.fromEvents(search.node(), 'click')*/ ]).take(1).onValue(function(doAdd) { if (doAdd && nodeList.selected) { nodeList.addNode(nodeList.selected); } search.node().blur(); nodeList.selectNothing(); })) .onValue(function(key) { if (nodeList.currentlyVisible == 0) return; search.node().blur(); if (key === 'up') { var current = nodeList.selected ? nodeList.selected.prev : listElements[listElements.length - 1]; while (current && !current.visible) { current = current.prev; } } else if (key === 'down') { var current = nodeList.selected ? nodeList.selected.next : listElements[0]; while (current && !current.visible) { current = current.next; } } if (current) nodeList.select(current); }); }).onValue(function() {}); } return { 'adaptToState': adaptToState, 'numberToHex': numberToHex, 'toHexColor': toHexColor, 'getNodeTypesByToolkit': getNodeTypesByToolkit, 'NodeList': NodeList }; })(); if (typeof define === 'function' && define.amd) { define([], function() { return RpdUtils; }); global.RpdUtils = RpdUtils; } else if (typeof module === 'object' && typeof exports === 'object') { module.exports = RpdUtils; if (typeof Rpd !== 'undefined') Rpd.RpdUtils = RpdUtils; } else { global.RpdUtils = RpdUtils; } }(this));