Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

docs(decision-tree-widget): implement Operator Decision Tree widget #1406

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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"
]
}
25 changes: 25 additions & 0 deletions doc/decision-tree-widget/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
{
"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",
"uglify": "uglifyjs dist/decision-tree-widget.js -o dist/decision-tree-widget.min.js",
"build": "npm install && npm run yaml2json && npm run browserify && npm run uglify",
"postbuild": "rm -f src/tree.json"
},
"dependencies": {
"hyperscript-helpers": "^2.1.0",
"snabbdom": "^0.4.2",
"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",
"uglify-js": "^2.6.2"
}
}
163 changes: 163 additions & 0 deletions doc/decision-tree-widget/src/main.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
const snabbdom = require('snabbdom');
const classModule = require('snabbdom/modules/class');
const propsModule = require('snabbdom/modules/props');
const styleModule = require('snabbdom/modules/style');
const attrsModule = require('snabbdom/modules/attributes');
const h = require('snabbdom/h');
const {div, h4, p, li, ul, a, span} = require('hyperscript-helpers')(h);
const tree = require('./tree.json');
const patch = snabbdom.init([classModule, propsModule, styleModule, attrsModule]);

function intent(containerElem) {
const click$ = Rx.Observable.fromEvent(containerElem, 'click');

const chooseOption$ = click$
.filter(ev => ev.target.className === 'option')
.map(ev => ({
type: 'CHOOSE_OPTION',
payload: parseInt(ev.target.dataset.index)
}));

const undo$ = click$
.filter(ev => ev.target.className === 'undo')
.map(() => ({
type: 'UNDO'
}));

const reset$ = click$
.filter(ev => ev.target.className === 'reset')
.map(() => ({
type: 'RESET'
}));

return Rx.Observable.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;
});

return Rx.Observable.merge(selectReducer$, undoReducer$, resetReducer$)
.scan((state, reducer) => reducer(state), initialState)
.startWith(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
].filter(x => x !== 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(containerElem) {
const action$ = intent(containerElem);
const state$ = model(action$);
const displayState$ = viewModel(state$);
const vdom$ = view(displayState$);
return {
DOM: vdom$
};
}

window.addEventListener('load', () => {
const container = document.querySelector('.decision-tree-widget');
const vdom$ = main(container).DOM;
vdom$.startWith(container).pairwise().subscribe(([a,b]) => { patch(a,b); });
});
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