Skip to content

Commit

Permalink
docs(decision-tree-widget): implement Operator Decision Tree widget, …
Browse files Browse the repository at this point in the history
…add to operators.md
  • Loading branch information
staltz committed Mar 3, 2016
1 parent d5f1bcd commit e74c9a0
Show file tree
Hide file tree
Showing 10 changed files with 681 additions and 3 deletions.
8 changes: 8 additions & 0 deletions doc/decision-tree-widget/.babelrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"presets": [
"es2015"
],
"plugins": [
"transform-object-rest-spread"
]
}
24 changes: 24 additions & 0 deletions doc/decision-tree-widget/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
{
"name": "decision-tree-widget",
"version": "1.0.0",
"description": "Interactive widget for a decision tree to assist choosing an RxJS operator",
"scripts": {
"yaml2json": "rm -f src/tree.json && node tools/yaml2json tree.yml ./src/tree.json",
"prebrowserify": "mkdir -p dist",
"browserify": "browserify src/main.js -t babelify --outfile dist/decision-tree-widget.js",
"build": "npm run yaml2json && npm run browserify",
"postbuild": "rm -f src/tree.json"
},
"dependencies": {
"@motorcycle/core": "^1.1.0",
"@motorcycle/dom": "^1.2.1",
"most": "^0.18.1",
"yaml-js": "^0.1.3"
},
"devDependencies": {
"babel-plugin-transform-object-rest-spread": "^6.5.0",
"babel-preset-es2015": "^6.5.0",
"babelify": "^7.2.0",
"browserify": "^13.0.0"
}
}
158 changes: 158 additions & 0 deletions doc/decision-tree-widget/src/main.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
import {just, combine, merge} from 'most';
import {run} from '@motorcycle/core';
import {makeDOMDriver, div, h4, p, li, ul, a, span} from '@motorcycle/dom';
const tree = require('./tree.json');

function intent(domSource) {
const chooseOption$ = domSource
.select('.option')
.events('click')
.map(ev => ({
type: 'CHOOSE_OPTION',
payload: parseInt(ev.currentTarget.dataset.index)
}));

const undo$ = domSource
.select('.undo')
.events('click')
.map(() => ({
type: 'UNDO'
}));

const reset$ = domSource
.select('.reset')
.events('click')
.map(() => ({
type: 'RESET'
}));

return merge(chooseOption$, undo$, reset$);
}

function model(action$) {
const initialState = {tree: tree, current: []};

const selectReducer$ = action$
.filter(action => action.type === 'CHOOSE_OPTION')
.map(action => function (state) {
return {
...state,
current: state.current.concat(action.payload)
};
});

const undoReducer$ = action$
.filter(action => action.type === 'UNDO')
.map(() => function (state) {
let newCurrent = state.current.slice();
newCurrent.pop();
return {
...state,
current: newCurrent
};
});

const resetReducer$ = action$
.filter(action => action.type === 'RESET')
.map(() => function (state) {
return initialState;
});

const reducer$ = merge(selectReducer$, undoReducer$, resetReducer$);
return reducer$.scan((state, reducer) => reducer(state), initialState);
}

function viewModel(state$) {
return state$.map(state => {
let previous = [];
let currentTree = state.tree;
for (let i = 0; i < state.current.length; i++) {
previous.push(currentTree.children[state.current[i]].label);
currentTree = currentTree.children[state.current[i]];
}
previous = previous.join(' ');
return {
previous,
options: currentTree.children
}
});
}

function renderCurrentSentence(state) {
const WELCOME_SENTENCE = 'Do you need to find an operator for your problem? ' +
'Start by choosing an option from the list below:';
return p('.current-sentence', [
!state.previous ? WELCOME_SENTENCE : null,
state.previous ? `"${state.previous}${state.options.length === 1 ? '.' : '...'}"` : null,
state.previous ? span('.undo', '\u21A9\u00A0Undo') : null,
state.previous ? span('.reset', 'Or\u00A0reset') : null
]);
}

function renderOption(option, index) {
const endString = option.children.length > 1 ? '...' : '.';
return li('.option',
{attrs: {'data-index': index}},
`${option.label}${endString}`
);
}

const OBSERVABLE_PATH = './class/es6/Observable.js~Observable.html';

function renderStaticDecision(option) {
const label = option.label.replace('Observable.', '');
return h4('.decision', [
'\u00BB You want the static operator ',
a(
{attrs: {href: `${OBSERVABLE_PATH}#static-method-${label}`}},
label
),
'.'
]);
}

function renderInstanceDecision(option) {
return h4('.decision', [
'\u00BB You want the instance operator ',
a(
{attrs: {href: `${OBSERVABLE_PATH}#instance-method-${option.label}`}},
option.label
),
'.'
]);
}

function renderItem(option, index) {
if (option.children) {
return renderOption(option, index);
} else if (option.label.match(/^Observable\./)) {
return renderStaticDecision(option);
} else {
return renderInstanceDecision(option);
}
}

function view(state$) {
return state$.map(state =>
div([
renderCurrentSentence(state),
ul(state.options.map(renderItem))
])
);
}

function main(sources) {
const action$ = intent(sources.DOM);
const state$ = model(action$);
const displayState$ = viewModel(state$);
const vdom$ = view(displayState$);
return {
DOM: vdom$
};
}

window.addEventListener('load', () => {
run(main, {
DOM: makeDOMDriver('.decision-tree-widget')
});
});
7 changes: 7 additions & 0 deletions doc/decision-tree-widget/tools/yaml2json.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
var yaml = require('yaml-js');
var fs = require('fs');
var inFilename = process.argv[2];
var outFilename = process.argv[3];
var yamlContent = fs.readFileSync(inFilename, 'utf8');
var jsonContent = yaml.load(yamlContent);
fs.writeFileSync(outFilename, JSON.stringify(jsonContent, null, ' '), 'utf8');
Loading

0 comments on commit e74c9a0

Please sign in to comment.