├── .gitignore ├── Gulpfile.js ├── README.md ├── gol ├── gol │ ├── app.coffee │ ├── dispatcher.coffee │ ├── gol_state.coffee │ ├── index.coffee │ ├── initial_data.coffee │ └── view.coffee └── public │ ├── index.html │ └── js │ └── app.js ├── hello_world ├── hello_world │ ├── app.coffee │ ├── dispatcher.coffee │ ├── index.coffee │ ├── storage.coffee │ └── view.coffee └── public │ ├── index.html │ └── js │ └── app.js ├── hello_world2 ├── hello_world2 │ ├── app.coffee │ ├── dispatcher.coffee │ ├── index.coffee │ ├── storage.coffee │ ├── transport.coffee │ └── view │ │ └── view.coffee └── public │ ├── index.html │ └── js │ └── app.js ├── index.html ├── package.json └── todo ├── public ├── css │ └── app.css ├── index.html ├── js │ └── app.js └── todomvc-common │ ├── base.css │ ├── bg.png │ ├── bower.json │ └── readme.md └── todo ├── app.coffee ├── dispatcher.coffee ├── index.coffee ├── todo_state.coffee └── views ├── todo_item.coffee └── todo_list.coffee /.gitignore: -------------------------------------------------------------------------------- 1 | .* 2 | lib-cov 3 | *.seed 4 | *.log 5 | *.csv 6 | *.dat 7 | *.out 8 | *.pid 9 | *.gz 10 | *.iml 11 | *.ipr 12 | *.iws 13 | 14 | pids 15 | logs 16 | results 17 | node_modules 18 | lib 19 | dist 20 | deploy.sh 21 | 22 | npm-debug.log 23 | 24 | !.gitignore 25 | !.travis.yml 26 | -------------------------------------------------------------------------------- /Gulpfile.js: -------------------------------------------------------------------------------- 1 | var gulp = require('gulp'); 2 | var browserify = require('gulp-browserify'); 3 | var rename = require('gulp-rename'); 4 | var uglify = require('gulp-uglify'); 5 | var filter = require('gulp-filter'); 6 | var deploy = require('gulp-gh-pages'); 7 | var react_render = require('gulp-react-render'); 8 | require('coffee-script/register'); 9 | 10 | 11 | var apps = ['hello_world', 'todo', 'hello_world2', "nested_components", "gol"]; 12 | 13 | 14 | // Build 15 | apps.map(function(app){ 16 | gulp.task(app, function(){ 17 | return gulp.src('./' + app + '/' + app + '/index.coffee', {read: false}) 18 | .pipe(browserify({ 19 | transform: ['coffeeify'], 20 | extensions: ['.coffee']})) 21 | .pipe(rename('app.js')) 22 | .pipe(gulp.dest('./' + app + '/public/js')); 23 | }); 24 | }); 25 | 26 | // Deploy 27 | apps.map(function(app){ 28 | gulp.task('deploy_' + app, function(){ 29 | jsfilter = filter(['**/*.js']) 30 | htmlfilter = filter(['**/*.html']) 31 | 32 | return gulp.src('./' + app + '/public/**/*.*') 33 | .pipe(jsfilter) 34 | .pipe(uglify()) 35 | .pipe(jsfilter.restore()) 36 | .pipe(htmlfilter) 37 | .pipe(react_render()) 38 | .pipe(htmlfilter.restore()) 39 | .pipe(gulp.dest('./dist/' + app + '/public')); 40 | }); 41 | }); 42 | 43 | // Watch 44 | gulp.task('watch', function(){ 45 | apps.map(function(app){ 46 | gulp.watch('./' + app + '/' + app + '/**/*.*', [app]); 47 | }); 48 | }); 49 | 50 | gulp.task('default', apps); 51 | 52 | gulp.task('copy_main_index', function(){ 53 | return gulp.src("index.html") 54 | .pipe(react_render()) 55 | .pipe(gulp.dest('./dist')); 56 | }); 57 | 58 | gulp.task('deploy_apps', ['default'], function() { 59 | return apps.map(function(app){return 'deploy_' + app}); 60 | }); 61 | 62 | 63 | gulp.task('deploy', ['deploy_apps', 'copy_main_index'], function (){ 64 | return gulp.src("./dist/**/*") 65 | .pipe(deploy()); 66 | }); 67 | 68 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # RxReact 2 | 3 | Demo apps: 4 | [Site with demo apps](http://alexmost.github.io/RxReact/) 5 | -------------------------------------------------------------------------------- /gol/gol/app.coffee: -------------------------------------------------------------------------------- 1 | Rx = require 'rx' 2 | React = require 'react' 3 | MainView = React.createFactory(require './view') 4 | {GolState, addCols, addRows, STATUS} = require './gol_state' 5 | dispatchActions = require './dispatcher' 6 | Immutable = require 'immutable' 7 | List = Immutable.List 8 | 9 | initialData = require './initial_data' 10 | 11 | getViewState = (state) -> 12 | cells: state.get("cells") 13 | isPlay: state.get("status") == STATUS.PLAY 14 | 15 | 16 | initApp = (mountNode) -> 17 | eventStream = new Rx.Subject() 18 | 19 | initialState = GolState(cells: Immutable.fromJS(initialData)) 20 | 21 | view = React.render MainView({eventStream}), mountNode 22 | 23 | stateStream = dispatchActions(initialState, eventStream) 24 | 25 | stateStream.subscribe( 26 | (newState) -> view.setProps getViewState(newState) 27 | (err) -> throw new Error err.stack) 28 | 29 | eventStream.onNext {action: "play"} 30 | 31 | 32 | module.exports = initApp -------------------------------------------------------------------------------- /gol/gol/dispatcher.coffee: -------------------------------------------------------------------------------- 1 | Rx = require 'rx' 2 | {calcNewState, addPoint, addRow, addCol, play, stop, delPoint, saveSate} = require './gol_state' 3 | 4 | 5 | dispatchActions = (initialState, eventStream) -> 6 | pauser = new Rx.Subject 7 | 8 | playStream = eventStream.filter(({action}) -> action is "play") 9 | .map(-> play) 10 | .do(-> pauser.onNext(true)) 11 | 12 | stopStream = eventStream.filter(({action}) -> action is "stop") 13 | .map(-> stop) 14 | .do(-> pauser.onNext(false)) 15 | 16 | gameLoopStream = Rx.Observable.interval(50).pausable(pauser).map(-> calcNewState) 17 | 18 | addRowStream = eventStream.filter(({action}) -> action is "add_row") 19 | .map(-> addRow) 20 | 21 | addColStream = eventStream.filter(({action}) -> action is "add_col") 22 | .map(-> addCol) 23 | 24 | mouseDown = eventStream.filter(({action}) -> action is "on_cell_mouse_down") 25 | mouseMove = eventStream.filter(({action}) -> action is "on_hover") 26 | mouseUp = eventStream.filter(({action}) -> action is "on_cell_mouse_up") 27 | 28 | addPointStream = eventStream.filter(({action}) -> action is "add_point") 29 | .map(({point}) -> addPoint(point)) 30 | 31 | delPointStream = eventStream.filter(({action}) -> action is "del_point") 32 | .map(({point}) -> delPoint(point)) 33 | 34 | saveState = eventStream.filter(({action}) -> action is "save") 35 | .map(-> saveSate) 36 | 37 | Rx.Observable.merge( 38 | gameLoopStream, addPointStream, addRowStream, 39 | addColStream, playStream, stopStream, delPointStream, 40 | saveState) 41 | .scan(initialState, (state, action) -> action(state)) 42 | .startWith(initialState) 43 | 44 | 45 | module.exports = dispatchActions -------------------------------------------------------------------------------- /gol/gol/gol_state.coffee: -------------------------------------------------------------------------------- 1 | Immutable = require 'immutable' 2 | Record = Immutable.Record 3 | List = Immutable.List 4 | 5 | STATUS = {PLAY: 0, STOP: 1} 6 | 7 | GolState = Record 8 | cells: List(List()) 9 | status: STATUS.PLAY 10 | saves: List() 11 | 12 | 13 | LIVE = 1 14 | DEAD = 0 15 | 16 | 17 | findNeighbors = ([y, x], matrix) -> 18 | [[y-1, x-1], [y, x-1], [y+1, x-1], [y-1, x], 19 | [y+1, x], [y-1, x+1], [y, x+1], [y+1, x+1]] 20 | .filter(([y, x]) -> y >= 0 and x >= 0) # immutable js returns last element instead of undefined if negative index 21 | .map(([y, x]) -> matrix.get(y)?.get(x)) 22 | .filter((value) -> value?) 23 | 24 | 25 | calcNewState = (state) -> 26 | currentCells = state.get("cells") 27 | currentCells.forEach((row, rowNum) -> 28 | row.forEach((cell, cellNum) -> 29 | neighbors = findNeighbors([rowNum, cellNum], currentCells) 30 | live_count = neighbors.filter((n) -> n is LIVE).length 31 | dead_count = neighbors.length - live_count 32 | 33 | cell_value = if cell is LIVE 34 | if live_count < 2 or live_count > 3 35 | DEAD 36 | else 37 | LIVE 38 | else # dead 39 | if live_count is 3 40 | LIVE 41 | else 42 | DEAD 43 | state = state.setIn(["cells", rowNum, cellNum], cell_value) 44 | ) 45 | ) 46 | state 47 | 48 | 49 | addPoint = ([y, x]) -> (state) -> state.setIn(["cells", y, x], LIVE) 50 | 51 | 52 | delPoint = ([y, x]) -> (state) -> state.setIn(["cells", y, x], DEAD) 53 | 54 | 55 | addRow = (state) -> 56 | currentCells = state.get("cells") 57 | rowLength = currentCells.get(0).size 58 | new_row = List((DEAD for i in [0..rowLength])) 59 | state.set("cells", currentCells.push(new_row)) 60 | 61 | 62 | addCol = (state) -> 63 | currentCells = state.get("cells") 64 | newCells = currentCells.map((row) -> 65 | row.push(DEAD)) 66 | state.set("cells", newCells) 67 | 68 | 69 | addCols = (n) -> (state) -> 70 | [0..n].reduce(((s, i) -> addCol(s)), state) 71 | 72 | 73 | addRows = (n) -> (state) -> 74 | [0..n].reduce(((s, i) -> addRow(s)), state) 75 | 76 | 77 | stop = (state) -> state.set("status", STATUS.STOP) 78 | 79 | 80 | play = (state) -> state.set("status", STATUS.PLAY) 81 | 82 | 83 | saveSate = (state) -> 84 | console.log JSON.stringify(state.get("cells").toJS()) 85 | state.set("saves", state.get("saves").push(state.get("cells"))) 86 | 87 | 88 | module.exports = { 89 | GolState, calcNewState, addPoint, 90 | addRow, addCol, addCols, addRows, STATUS, 91 | stop, play, delPoint, saveSate 92 | } -------------------------------------------------------------------------------- /gol/gol/index.coffee: -------------------------------------------------------------------------------- 1 | initApp = require './app' 2 | 3 | # Mounting application 4 | initApp(document.getElementById('hello_container')) -------------------------------------------------------------------------------- /gol/gol/initial_data.coffee: -------------------------------------------------------------------------------- 1 | module.exports = [[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,0,0,0,0,0,0,1,1,0,0,0,0,0,0,0,0,0,0,0,0,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,1,0,0,0,0,1,1,0,0,0,0,0,0,0,0,0,0,0,0,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,1,1,0,0,0,0,0,0,0,0,1,0,0,0,0,0,1,0,0,0,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,1,1,0,0,0,0,0,0,0,0,1,0,0,0,1,0,1,1,0,0,0,0,1,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,1,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]] -------------------------------------------------------------------------------- /gol/gol/view.coffee: -------------------------------------------------------------------------------- 1 | React = require 'react' 2 | createFactory = React.createFactory 3 | {div, button, table, tbody, tr, td} = React.DOM 4 | PureRenderMixin = require('react/addons').addons.PureRenderMixin 5 | Immutable = require 'immutable' 6 | List = Immutable.List 7 | 8 | 9 | BlackCellClass = React.createClass 10 | render: -> 11 | td 12 | className: "live" 13 | onClick: @props.onSelect 14 | 15 | 16 | WhiteCellClass = React.createClass 17 | render: -> 18 | td 19 | className: "dead" 20 | onClick: @props.onSelect 21 | 22 | 23 | 24 | BlackCell = createFactory(BlackCellClass) 25 | WhiteCell = createFactory(WhiteCellClass) 26 | 27 | 28 | MainView = React.createClass 29 | mixins: [PureRenderMixin] 30 | 31 | getDefaultProps: -> 32 | cells: List(List()) 33 | 34 | render: -> 35 | div null, 36 | if @props.isPlay 37 | button 38 | onClick: => @props.eventStream.onNext 39 | action: "stop" 40 | "stop" 41 | else 42 | button 43 | onClick: => @props.eventStream.onNext 44 | action: "play" 45 | 46 | "play" 47 | 48 | button 49 | onClick: => @props.eventStream.onNext {action: "save"} 50 | "dump" 51 | 52 | table 53 | style: {border: "1px solid gray"} 54 | cellPadding: 0 55 | cellSpacing: 0 56 | tbody null, 57 | @props.cells.toArray().map((row, i) => 58 | tr {key: i}, 59 | row.toArray().map((cell, j) => 60 | if cell 61 | BlackCell 62 | key: j 63 | onSelect: => @props.eventStream.onNext { 64 | action: "del_point" 65 | point: [i, j] 66 | } 67 | else 68 | WhiteCell 69 | key: j 70 | onSelect: => @props.eventStream.onNext { 71 | action: "add_point" 72 | point: [i, j] 73 | } 74 | ) 75 | ) 76 | 77 | 78 | module.exports = MainView 79 | 80 | -------------------------------------------------------------------------------- /gol/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Rx.js + React.js • Gol 6 | 7 | 29 | 30 |

Gol in RxReact

31 |
32 | 33 | 34 | -------------------------------------------------------------------------------- /hello_world/hello_world/app.coffee: -------------------------------------------------------------------------------- 1 | Rx = require 'rx' 2 | React = require 'react' 3 | HelloView = React.createFactory(require './view') 4 | HelloStorage = require './storage' 5 | dispatchActions = require './dispatcher' 6 | 7 | 8 | initApp = (mountNode) -> 9 | eventStream = new Rx.Subject() 10 | store = new HelloStorage() 11 | view = React.render HelloView({eventStream}), mountNode 12 | dispatchActions(view, eventStream, store) 13 | 14 | 15 | module.exports = initApp -------------------------------------------------------------------------------- /hello_world/hello_world/dispatcher.coffee: -------------------------------------------------------------------------------- 1 | Rx = require 'rx' 2 | 3 | 4 | getViewState = (store) -> 5 | clicksCount: store.getClicksCount() 6 | 7 | 8 | dispatchActions = (view, eventStream, store) -> 9 | incrementClickStream = eventStream 10 | .filter(({action}) -> action is "increment_click_count") 11 | .do(-> store.incrementClicksCount()) 12 | 13 | Rx.Observable.merge( 14 | incrementClickStream 15 | # some more actions here for updating view ... 16 | 17 | ).subscribe( 18 | -> view.setProps getViewState(store) 19 | (err) -> 20 | console.error? err) 21 | 22 | 23 | module.exports = dispatchActions -------------------------------------------------------------------------------- /hello_world/hello_world/index.coffee: -------------------------------------------------------------------------------- 1 | initApp = require './app' 2 | 3 | # Mounting application 4 | initApp(document.getElementById('hello_container')) -------------------------------------------------------------------------------- /hello_world/hello_world/storage.coffee: -------------------------------------------------------------------------------- 1 | class HelloStorage 2 | constructor: -> 3 | @clicksCount = 0 4 | 5 | getClicksCount: -> @clicksCount 6 | 7 | incrementClicksCount: -> 8 | @clicksCount += 1 9 | 10 | module.exports = HelloStorage -------------------------------------------------------------------------------- /hello_world/hello_world/view.coffee: -------------------------------------------------------------------------------- 1 | React = require 'react' 2 | {div, button} = React.DOM 3 | 4 | 5 | HelloView = React.createClass 6 | getDefaultProps: -> 7 | clicksCount: 0 8 | 9 | incrementClickCount: -> 10 | @props.eventStream.onNext 11 | action: "increment_click_count" 12 | 13 | render: -> 14 | div null, 15 | div null, "You clicked #{@props.clicksCount} times" 16 | button 17 | onClick: @incrementClickCount 18 | "Click" 19 | 20 | 21 | module.exports = HelloView 22 | 23 | -------------------------------------------------------------------------------- /hello_world/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Rx.js + React.js • HelloWorld 6 | 7 | 8 |

Hello World for RxReact

9 |
10 | 11 | 12 | -------------------------------------------------------------------------------- /hello_world2/hello_world2/app.coffee: -------------------------------------------------------------------------------- 1 | Rx = require 'rx' 2 | React = require 'react' 3 | HelloView = React.createFactory(require './view/view') 4 | HelloStorage = require './storage' 5 | dispatchActions = require './dispatcher' 6 | 7 | 8 | initApp = (mountNode) -> 9 | eventStream = new Rx.Subject() 10 | store = new HelloStorage() 11 | view = React.render HelloView({eventStream}), mountNode 12 | dispatchActions(view, eventStream, store) 13 | 14 | 15 | module.exports = initApp -------------------------------------------------------------------------------- /hello_world2/hello_world2/dispatcher.coffee: -------------------------------------------------------------------------------- 1 | Rx = require 'rx' 2 | {saveToDb} = require './transport' 3 | 4 | getViewState = (store) -> 5 | clicksCount: store.getClicksCount() 6 | showSavedMessage: store.getShowSavedMessage() 7 | 8 | 9 | dispatchActions = (view, eventStream, store) -> 10 | incrementClickStream = eventStream 11 | .filter(({action}) -> action is "increment_click_count") 12 | .do(-> store.incrementClicksCount()) 13 | .share() 14 | 15 | decrementClickStream = eventStream 16 | .filter(({action}) -> action is "decrement_click_count") 17 | .do(-> store.decrementClickscount()) 18 | .share() 19 | 20 | countClicksStream = Rx.Observable 21 | .merge(incrementClickStream, decrementClickStream) 22 | 23 | showSavedMessageStream = countClicksStream 24 | .throttle(1000) 25 | .distinct(-> store.getClicksCount()) 26 | .flatMap(-> saveToDb store.getClicksCount()) 27 | .do(-> store.enableSavedMessage()) 28 | 29 | hideSavedMessageStream = showSavedMessageStream.delay(2000) 30 | .do(-> store.disableSavedMessage()) 31 | 32 | Rx.Observable.merge( 33 | countClicksStream 34 | showSavedMessageStream 35 | hideSavedMessageStream 36 | # some more actions here for updating view ... 37 | 38 | ).subscribe( 39 | -> view.setProps getViewState(store) 40 | (err) -> 41 | console.error? err) 42 | 43 | 44 | module.exports = dispatchActions -------------------------------------------------------------------------------- /hello_world2/hello_world2/index.coffee: -------------------------------------------------------------------------------- 1 | initApp = require './app' 2 | 3 | # Mounting application 4 | initApp(document.getElementById('hello_container')) -------------------------------------------------------------------------------- /hello_world2/hello_world2/storage.coffee: -------------------------------------------------------------------------------- 1 | class HelloStorage 2 | constructor: -> 3 | @clicksCount = 0 4 | @showSavedMessage = false 5 | 6 | getClicksCount: -> @clicksCount 7 | 8 | incrementClicksCount: -> 9 | @clicksCount += 1 10 | 11 | decrementClickscount: -> 12 | @clicksCount -= 1 13 | 14 | getShowSavedMessage: -> @showSavedMessage 15 | 16 | enableSavedMessage: -> 17 | @showSavedMessage = true 18 | 19 | disableSavedMessage: -> 20 | @showSavedMessage = false 21 | 22 | 23 | module.exports = HelloStorage -------------------------------------------------------------------------------- /hello_world2/hello_world2/transport.coffee: -------------------------------------------------------------------------------- 1 | Rx = require 'rx' 2 | 3 | # Mock for some async operation 4 | saveToDb = (value) -> 5 | Rx.Observable.create (observer) -> 6 | setTimeout( 7 | -> 8 | observer.onNext value 9 | observer.onCompleted() 10 | 2000 11 | ) 12 | 13 | module.exports = {saveToDb} 14 | -------------------------------------------------------------------------------- /hello_world2/hello_world2/view/view.coffee: -------------------------------------------------------------------------------- 1 | React = require 'react' 2 | {div, button, h1, p} = React.DOM 3 | 4 | 5 | HelloView = React.createClass 6 | getDefaultProps: -> 7 | clicksCount: 0 8 | 9 | incrementClickCount: -> 10 | @props.eventStream.onNext 11 | action: "increment_click_count" 12 | 13 | decrementClickCount: -> 14 | @props.eventStream.onNext 15 | action: "decrement_click_count" 16 | 17 | render: -> 18 | div null, 19 | div null, "Hello" 20 | 21 | if @props.showSavedMessage 22 | p {style: {color: "red"}}, "Count saved" 23 | 24 | div null, "You clicked #{@props.clicksCount} times" 25 | button 26 | onClick: @incrementClickCount 27 | "Click +1" 28 | 29 | button 30 | onClick: @decrementClickCount 31 | "Click -1" 32 | 33 | 34 | module.exports = HelloView 35 | 36 | -------------------------------------------------------------------------------- /hello_world2/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Rx.js + React.js • HelloWorld 6 | 7 | 8 |

Hello World for RxReact

9 |
10 | 11 | 12 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | RxReact = Rx.js + React.js 6 | 7 | 8 | 9 | 10 |

RxReact (Rx.js + React.js)

11 |

Yet another way to build your kickass javascript application

12 |

More info will be soon...

13 | 14 |

Demo

15 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "todo", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "author": "", 10 | "license": "ISC", 11 | "dependencies": { 12 | "immutable": "^3.7.3", 13 | "react": "^0.13.1", 14 | "rx": "^2.3.24" 15 | }, 16 | "devDependencies": { 17 | "coffeeify": "^1.0.0", 18 | "gulp": "^3.8.10", 19 | "gulp-browserify": "^0.5.1", 20 | "gulp-filter": "^2.0.0", 21 | "gulp-gh-pages": "^0.4.0", 22 | "gulp-react-render": "^1.0.0", 23 | "gulp-rename": "^1.2.0", 24 | "gulp-uglify": "^1.1.0" 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /todo/public/css/app.css: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2014, Facebook, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | * 9 | * base.css overrides 10 | */ 11 | 12 | /** 13 | * We are not changing from display:none, but rather re-rendering instead. 14 | * Therefore this needs to be displayed normally by default. 15 | */ 16 | #todo-list li .edit { 17 | display: inline; 18 | } 19 | -------------------------------------------------------------------------------- /todo/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Rx.js + React.js • TodoMVC 6 | 7 | 8 | 9 | 10 | 11 |
12 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /todo/public/todomvc-common/base.css: -------------------------------------------------------------------------------- 1 | html, 2 | body { 3 | margin: 0; 4 | padding: 0; 5 | } 6 | 7 | button { 8 | margin: 0; 9 | padding: 0; 10 | border: 0; 11 | background: none; 12 | font-size: 100%; 13 | vertical-align: baseline; 14 | font-family: inherit; 15 | color: inherit; 16 | -webkit-appearance: none; 17 | -ms-appearance: none; 18 | -o-appearance: none; 19 | appearance: none; 20 | } 21 | 22 | body { 23 | font: 14px 'Helvetica Neue', Helvetica, Arial, sans-serif; 24 | line-height: 1.4em; 25 | background: #eaeaea url('bg.png'); 26 | color: #4d4d4d; 27 | width: 550px; 28 | margin: 0 auto; 29 | -webkit-font-smoothing: antialiased; 30 | -moz-font-smoothing: antialiased; 31 | -ms-font-smoothing: antialiased; 32 | -o-font-smoothing: antialiased; 33 | font-smoothing: antialiased; 34 | } 35 | 36 | button, 37 | input[type="checkbox"] { 38 | outline: none; 39 | } 40 | 41 | #todoapp { 42 | background: #fff; 43 | background: rgba(255, 255, 255, 0.9); 44 | margin: 130px 0 40px 0; 45 | border: 1px solid #ccc; 46 | position: relative; 47 | border-top-left-radius: 2px; 48 | border-top-right-radius: 2px; 49 | box-shadow: 0 2px 6px 0 rgba(0, 0, 0, 0.2), 50 | 0 25px 50px 0 rgba(0, 0, 0, 0.15); 51 | } 52 | 53 | #todoapp:before { 54 | content: ''; 55 | border-left: 1px solid #f5d6d6; 56 | border-right: 1px solid #f5d6d6; 57 | width: 2px; 58 | position: absolute; 59 | top: 0; 60 | left: 40px; 61 | height: 100%; 62 | } 63 | 64 | #todoapp input::-webkit-input-placeholder { 65 | font-style: italic; 66 | } 67 | 68 | #todoapp input::-moz-placeholder { 69 | font-style: italic; 70 | color: #a9a9a9; 71 | } 72 | 73 | #todoapp h1 { 74 | position: absolute; 75 | top: -120px; 76 | width: 100%; 77 | font-size: 70px; 78 | font-weight: bold; 79 | text-align: center; 80 | color: #b3b3b3; 81 | color: rgba(255, 255, 255, 0.3); 82 | text-shadow: -1px -1px rgba(0, 0, 0, 0.2); 83 | -webkit-text-rendering: optimizeLegibility; 84 | -moz-text-rendering: optimizeLegibility; 85 | -ms-text-rendering: optimizeLegibility; 86 | -o-text-rendering: optimizeLegibility; 87 | text-rendering: optimizeLegibility; 88 | } 89 | 90 | #addbtn { 91 | position: absolute; 92 | top: -130px; 93 | background: rgba(122, 65, 10, 0.71); 94 | color: white; 95 | font-weight: bold; 96 | outline: auto; 97 | cursor: pointer; 98 | padding: 10px; 99 | width: 100%; 100 | } 101 | 102 | #header { 103 | padding-top: 15px; 104 | border-radius: inherit; 105 | } 106 | 107 | #header:before { 108 | content: ''; 109 | position: absolute; 110 | top: 0; 111 | right: 0; 112 | left: 0; 113 | height: 15px; 114 | z-index: 2; 115 | border-bottom: 1px solid #6c615c; 116 | background: #8d7d77; 117 | background: -webkit-gradient(linear, left top, left bottom, from(rgba(132, 110, 100, 0.8)),to(rgba(101, 84, 76, 0.8))); 118 | background: -webkit-linear-gradient(top, rgba(132, 110, 100, 0.8), rgba(101, 84, 76, 0.8)); 119 | background: linear-gradient(top, rgba(132, 110, 100, 0.8), rgba(101, 84, 76, 0.8)); 120 | filter: progid:DXImageTransform.Microsoft.gradient(GradientType=0,StartColorStr='#9d8b83', EndColorStr='#847670'); 121 | border-top-left-radius: 1px; 122 | border-top-right-radius: 1px; 123 | } 124 | 125 | #new-todo, 126 | .edit { 127 | position: relative; 128 | margin: 0; 129 | width: 100%; 130 | font-size: 24px; 131 | font-family: inherit; 132 | line-height: 1.4em; 133 | border: 0; 134 | outline: none; 135 | color: inherit; 136 | padding: 6px; 137 | border: 1px solid #999; 138 | box-shadow: inset 0 -1px 5px 0 rgba(0, 0, 0, 0.2); 139 | -moz-box-sizing: border-box; 140 | -ms-box-sizing: border-box; 141 | -o-box-sizing: border-box; 142 | box-sizing: border-box; 143 | -webkit-font-smoothing: antialiased; 144 | -moz-font-smoothing: antialiased; 145 | -ms-font-smoothing: antialiased; 146 | -o-font-smoothing: antialiased; 147 | font-smoothing: antialiased; 148 | } 149 | 150 | #new-todo { 151 | padding: 16px 16px 16px 60px; 152 | border: none; 153 | background: rgba(0, 0, 0, 0.02); 154 | z-index: 2; 155 | box-shadow: none; 156 | } 157 | 158 | #main { 159 | position: relative; 160 | z-index: 2; 161 | border-top: 1px dotted #adadad; 162 | } 163 | 164 | label[for='toggle-all'] { 165 | display: none; 166 | } 167 | 168 | #toggle-all { 169 | position: absolute; 170 | top: -42px; 171 | left: -4px; 172 | width: 40px; 173 | text-align: center; 174 | /* Mobile Safari */ 175 | border: none; 176 | } 177 | 178 | #toggle-all:before { 179 | content: '»'; 180 | font-size: 28px; 181 | color: #d9d9d9; 182 | padding: 0 25px 7px; 183 | } 184 | 185 | #toggle-all:checked:before { 186 | color: #737373; 187 | } 188 | 189 | #todo-list { 190 | margin: 0; 191 | padding: 0; 192 | list-style: none; 193 | } 194 | 195 | #todo-list li { 196 | position: relative; 197 | font-size: 24px; 198 | border-bottom: 1px dotted #ccc; 199 | } 200 | 201 | #todo-list li:last-child { 202 | border-bottom: none; 203 | } 204 | 205 | #todo-list li.editing { 206 | border-bottom: none; 207 | padding: 0; 208 | } 209 | 210 | #todo-list li.editing .edit { 211 | display: block; 212 | width: 506px; 213 | padding: 13px 17px 12px 17px; 214 | margin: 0 0 0 43px; 215 | } 216 | 217 | #todo-list li.editing .view { 218 | display: none; 219 | } 220 | 221 | #todo-list li .toggle { 222 | text-align: center; 223 | width: 40px; 224 | /* auto, since non-WebKit browsers doesn't support input styling */ 225 | height: auto; 226 | position: absolute; 227 | top: 0; 228 | bottom: 0; 229 | margin: auto 0; 230 | /* Mobile Safari */ 231 | border: none; 232 | -webkit-appearance: none; 233 | -ms-appearance: none; 234 | -o-appearance: none; 235 | appearance: none; 236 | } 237 | 238 | #todo-list li .toggle:after { 239 | content: '✔'; 240 | /* 40 + a couple of pixels visual adjustment */ 241 | line-height: 43px; 242 | font-size: 20px; 243 | color: #d9d9d9; 244 | text-shadow: 0 -1px 0 #bfbfbf; 245 | } 246 | 247 | #todo-list li .toggle:checked:after { 248 | color: #85ada7; 249 | text-shadow: 0 1px 0 #669991; 250 | bottom: 1px; 251 | position: relative; 252 | } 253 | 254 | #todo-list li label { 255 | white-space: pre; 256 | word-break: break-word; 257 | padding: 15px 60px 15px 15px; 258 | margin-left: 45px; 259 | display: block; 260 | line-height: 1.2; 261 | -webkit-transition: color 0.4s; 262 | transition: color 0.4s; 263 | } 264 | 265 | #todo-list li.completed label { 266 | color: #a9a9a9; 267 | text-decoration: line-through; 268 | } 269 | 270 | #todo-list li .destroy { 271 | display: none; 272 | position: absolute; 273 | top: 0; 274 | right: 10px; 275 | bottom: 0; 276 | width: 40px; 277 | height: 40px; 278 | margin: auto 0; 279 | font-size: 22px; 280 | color: #a88a8a; 281 | -webkit-transition: all 0.2s; 282 | transition: all 0.2s; 283 | } 284 | 285 | #todo-list li .destroy:hover { 286 | text-shadow: 0 0 1px #000, 287 | 0 0 10px rgba(199, 107, 107, 0.8); 288 | -webkit-transform: scale(1.3); 289 | -ms-transform: scale(1.3); 290 | transform: scale(1.3); 291 | } 292 | 293 | #todo-list li .destroy:after { 294 | content: '✖'; 295 | } 296 | 297 | #todo-list li:hover .destroy { 298 | display: block; 299 | } 300 | 301 | #todo-list li .edit { 302 | display: none; 303 | } 304 | 305 | #todo-list li.editing:last-child { 306 | margin-bottom: -1px; 307 | } 308 | 309 | #footer { 310 | color: #777; 311 | padding: 0 15px; 312 | position: absolute; 313 | right: 0; 314 | bottom: -31px; 315 | left: 0; 316 | height: 20px; 317 | z-index: 1; 318 | text-align: center; 319 | } 320 | 321 | #footer:before { 322 | content: ''; 323 | position: absolute; 324 | right: 0; 325 | bottom: 31px; 326 | left: 0; 327 | height: 50px; 328 | z-index: -1; 329 | box-shadow: 0 1px 1px rgba(0, 0, 0, 0.3), 330 | 0 6px 0 -3px rgba(255, 255, 255, 0.8), 331 | 0 7px 1px -3px rgba(0, 0, 0, 0.3), 332 | 0 43px 0 -6px rgba(255, 255, 255, 0.8), 333 | 0 44px 2px -6px rgba(0, 0, 0, 0.2); 334 | } 335 | 336 | #todo-count { 337 | float: left; 338 | text-align: left; 339 | } 340 | 341 | #filters { 342 | margin: 0; 343 | padding: 0; 344 | list-style: none; 345 | position: absolute; 346 | right: 0; 347 | left: 0; 348 | } 349 | 350 | #filters li { 351 | display: inline; 352 | } 353 | 354 | #filters li a { 355 | color: #83756f; 356 | margin: 2px; 357 | text-decoration: none; 358 | } 359 | 360 | #filters li a.selected { 361 | font-weight: bold; 362 | } 363 | 364 | #clear-completed { 365 | float: right; 366 | position: relative; 367 | line-height: 20px; 368 | text-decoration: none; 369 | background: rgba(0, 0, 0, 0.1); 370 | font-size: 11px; 371 | padding: 0 10px; 372 | border-radius: 3px; 373 | box-shadow: 0 -1px 0 0 rgba(0, 0, 0, 0.2); 374 | } 375 | 376 | #clear-completed:hover { 377 | background: rgba(0, 0, 0, 0.15); 378 | box-shadow: 0 -1px 0 0 rgba(0, 0, 0, 0.3); 379 | } 380 | 381 | #info { 382 | margin: 65px auto 0; 383 | color: #a6a6a6; 384 | font-size: 12px; 385 | text-shadow: 0 1px 0 rgba(255, 255, 255, 0.7); 386 | text-align: center; 387 | } 388 | 389 | #info a { 390 | color: inherit; 391 | } 392 | 393 | /* 394 | Hack to remove background from Mobile Safari. 395 | Can't use it globally since it destroys checkboxes in Firefox and Opera 396 | */ 397 | 398 | @media screen and (-webkit-min-device-pixel-ratio:0) { 399 | #toggle-all, 400 | #todo-list li .toggle { 401 | background: none; 402 | } 403 | 404 | #todo-list li .toggle { 405 | height: 40px; 406 | } 407 | 408 | #toggle-all { 409 | top: -56px; 410 | left: -15px; 411 | width: 65px; 412 | height: 41px; 413 | -webkit-transform: rotate(90deg); 414 | -ms-transform: rotate(90deg); 415 | transform: rotate(90deg); 416 | -webkit-appearance: none; 417 | appearance: none; 418 | } 419 | } 420 | 421 | .hidden { 422 | display: none; 423 | } 424 | 425 | hr { 426 | margin: 20px 0; 427 | border: 0; 428 | border-top: 1px dashed #C5C5C5; 429 | border-bottom: 1px dashed #F7F7F7; 430 | } 431 | 432 | .learn a { 433 | font-weight: normal; 434 | text-decoration: none; 435 | color: #b83f45; 436 | } 437 | 438 | .learn a:hover { 439 | text-decoration: underline; 440 | color: #787e7e; 441 | } 442 | 443 | .learn h3, 444 | .learn h4, 445 | .learn h5 { 446 | margin: 10px 0; 447 | font-weight: 500; 448 | line-height: 1.2; 449 | color: #000; 450 | } 451 | 452 | .learn h3 { 453 | font-size: 24px; 454 | } 455 | 456 | .learn h4 { 457 | font-size: 18px; 458 | } 459 | 460 | .learn h5 { 461 | margin-bottom: 0; 462 | font-size: 14px; 463 | } 464 | 465 | .learn ul { 466 | padding: 0; 467 | margin: 0 0 30px 25px; 468 | } 469 | 470 | .learn li { 471 | line-height: 20px; 472 | } 473 | 474 | .learn p { 475 | font-size: 15px; 476 | font-weight: 300; 477 | line-height: 1.3; 478 | margin-top: 0; 479 | margin-bottom: 0; 480 | } 481 | 482 | .quote { 483 | border: none; 484 | margin: 20px 0 60px 0; 485 | } 486 | 487 | .quote p { 488 | font-style: italic; 489 | } 490 | 491 | .quote p:before { 492 | content: '“'; 493 | font-size: 50px; 494 | opacity: .15; 495 | position: absolute; 496 | top: -20px; 497 | left: 3px; 498 | } 499 | 500 | .quote p:after { 501 | content: '”'; 502 | font-size: 50px; 503 | opacity: .15; 504 | position: absolute; 505 | bottom: -42px; 506 | right: 3px; 507 | } 508 | 509 | .quote footer { 510 | position: absolute; 511 | bottom: -40px; 512 | right: 0; 513 | } 514 | 515 | .quote footer img { 516 | border-radius: 3px; 517 | } 518 | 519 | .quote footer a { 520 | margin-left: 5px; 521 | vertical-align: middle; 522 | } 523 | 524 | .speech-bubble { 525 | position: relative; 526 | padding: 10px; 527 | background: rgba(0, 0, 0, .04); 528 | border-radius: 5px; 529 | } 530 | 531 | .speech-bubble:after { 532 | content: ''; 533 | position: absolute; 534 | top: 100%; 535 | right: 30px; 536 | border: 13px solid transparent; 537 | border-top-color: rgba(0, 0, 0, .04); 538 | } 539 | 540 | .learn-bar > .learn { 541 | position: absolute; 542 | width: 272px; 543 | top: 8px; 544 | left: -300px; 545 | padding: 10px; 546 | border-radius: 5px; 547 | background-color: rgba(255, 255, 255, .6); 548 | -webkit-transition-property: left; 549 | transition-property: left; 550 | -webkit-transition-duration: 500ms; 551 | transition-duration: 500ms; 552 | } 553 | 554 | @media (min-width: 899px) { 555 | .learn-bar { 556 | width: auto; 557 | margin: 0 0 0 300px; 558 | } 559 | 560 | .learn-bar > .learn { 561 | left: 8px; 562 | } 563 | 564 | .learn-bar #todoapp { 565 | width: 550px; 566 | margin: 130px auto 40px auto; 567 | } 568 | } 569 | -------------------------------------------------------------------------------- /todo/public/todomvc-common/bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AlexMost/RxReact/12e28e75f2861c7c29a5222238126d3c864ab9a2/todo/public/todomvc-common/bg.png -------------------------------------------------------------------------------- /todo/public/todomvc-common/bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "todomvc-common", 3 | "version": "0.1.9" 4 | } 5 | -------------------------------------------------------------------------------- /todo/public/todomvc-common/readme.md: -------------------------------------------------------------------------------- 1 | # todomvc-common 2 | 3 | > Bower component for some common utilities we use in every app 4 | 5 | 6 | ## License 7 | 8 | MIT 9 | -------------------------------------------------------------------------------- /todo/todo/app.coffee: -------------------------------------------------------------------------------- 1 | Rx = require 'rx' 2 | React = require 'react' 3 | {createFactory} = React 4 | TodoComponent = createFactory(require './views/todo_list') 5 | {TodoListState} = require './todo_state' 6 | dispatchActions = require './dispatcher' 7 | 8 | 9 | getViewState = (state) -> 10 | todoText: state.get("todoText") 11 | todoItems: state.get("todoItems") 12 | 13 | initApp = (mountNode, state, history) -> 14 | eventStream = new Rx.Subject() 15 | history or= [] 16 | 17 | state or= TodoListState() 18 | 19 | props = getViewState(state) 20 | props.eventStream = eventStream 21 | view = React.render TodoComponent(props), mountNode 22 | 23 | stateStream = dispatchActions(eventStream, state).share() 24 | 25 | subscribtion = stateStream.subscribe( 26 | (state) -> 27 | history.push(state) 28 | view.setProps getViewState(state) 29 | (err) -> throw new Error(err.stack)) 30 | 31 | 32 | history_back = eventStream.filter(({action}) -> action is "history_back") 33 | .subscribe( 34 | -> 35 | subscribtion.dispose() 36 | history_back.dispose() 37 | history.pop() 38 | initApp(mountNode, history[history.length - 1], history)) 39 | 40 | 41 | module.exports = initApp 42 | 43 | -------------------------------------------------------------------------------- /todo/todo/dispatcher.coffee: -------------------------------------------------------------------------------- 1 | Rx = require 'rx' 2 | {setTodoText, addItem, destroyItem, 3 | checkItem, setCheckAll} = require './todo_state' 4 | 5 | 6 | dispatch_actions = (subject, initialState) -> 7 | mainInputKeyDown = subject 8 | .filter(({action}) -> action is "mainInputKeyDown") 9 | 10 | main_input_enter_key_down = mainInputKeyDown 11 | .filter(({keyCode}) -> keyCode is 13) 12 | 13 | todoTextChange = subject 14 | .filter(({action}) -> action is "mainInputTextChange") 15 | .map(({text}) -> setTodoText(text)) 16 | 17 | addItemStream = main_input_enter_key_down 18 | .filter(({text}) -> text.length > 0) 19 | .map(({text}) -> addItem(text)) 20 | 21 | destroyItemStream = subject 22 | .filter(({action}) -> action is "itemDestroy") 23 | .map(({id}) -> destroyItem(id)) 24 | 25 | checkItemStream = subject 26 | .filter(({action}) -> action is "itemCheck") 27 | .map(({checked, id}) -> checkItem(id, checked)) 28 | 29 | checkAll = subject 30 | .filter(({action}) -> action is "toggleCheckAll") 31 | .map(({checked}) -> setCheckAll(checked)) 32 | 33 | Rx.Observable.merge( 34 | addItemStream, todoTextChange, destroyItemStream, checkItemStream, checkAll) 35 | .scan(initialState, (currentState, action) -> action(currentState)) 36 | 37 | 38 | module.exports = dispatch_actions -------------------------------------------------------------------------------- /todo/todo/index.coffee: -------------------------------------------------------------------------------- 1 | initApp = require './app' 2 | initApp(document.getElementById("todoapp")) -------------------------------------------------------------------------------- /todo/todo/todo_state.coffee: -------------------------------------------------------------------------------- 1 | Immutable = require 'immutable' 2 | Record = Immutable.Record 3 | List = Immutable.List 4 | 5 | 6 | TodoListState = Record 7 | todoItems: List() 8 | todoText: "" 9 | doneItems: 0 10 | checkAll: false 11 | 12 | 13 | TodoItemState = Record 14 | text: "" 15 | id: "" 16 | complete: false 17 | 18 | 19 | # String -> TodoState -> TodoState 20 | setTodoText = (text) -> (state) -> 21 | state.set("todoText", text) 22 | 23 | 24 | # String -> TodoState -> TodoState 25 | addItem = (text) -> (state) -> 26 | currentItems = state.get("todoItems") 27 | 28 | newItem = TodoItemState 29 | text: text 30 | id: text 31 | complete: false 32 | 33 | state.set("todoItems", currentItems.unshift(newItem)) 34 | .set("todoText", "") 35 | 36 | 37 | # String -> TodoState -> TodoState 38 | destroyItem = (id) -> (state) -> 39 | currentItems = state.get("todoItems") 40 | state.set("todoItems", currentItems.filter((i) -> i.id != id)) 41 | 42 | 43 | # (String, String) -> TodoState -> TodoState 44 | checkItem = (id, checked) -> (state) -> 45 | currentItems = state.get("todoItems") 46 | state.set("todoItems", currentItems.map( 47 | (i) -> 48 | if i.id == id 49 | i.set("complete", checked) 50 | else 51 | i 52 | )) 53 | 54 | # Boolean -> TodoState -> TodoState 55 | setCheckAll = (checkAll) -> (state) -> 56 | currentItems = state.get("todoItems") 57 | state.set("todoItems", 58 | currentItems.map((i) -> i.set("complete", checkAll))) 59 | 60 | 61 | module.exports = { 62 | TodoListState 63 | setTodoText 64 | addItem 65 | destroyItem 66 | checkItem 67 | setCheckAll 68 | } 69 | -------------------------------------------------------------------------------- /todo/todo/views/todo_item.coffee: -------------------------------------------------------------------------------- 1 | React = require 'react' 2 | PureRenderMixin = require('react/addons').addons.PureRenderMixin 3 | {div, h1, input, header, section, label, ul, li, button, p} = React.DOM 4 | 5 | 6 | TodoItem = React.createClass 7 | mixins: [PureRenderMixin] 8 | 9 | destroy: -> 10 | @props.eventStream.onNext 11 | action: "itemDestroy" 12 | id: @props.item.id 13 | 14 | check: (ev) -> 15 | @props.eventStream.onNext 16 | action: "itemCheck" 17 | checked: ev.target.checked 18 | id: @props.item.id 19 | 20 | edit: -> 21 | @props.eventStream.onNext 22 | action: "editItem" 23 | id: @props.item.id 24 | isEditMode: true 25 | 26 | render: -> 27 | li 28 | key: @props.item.id 29 | className: ( 30 | @props.item.complete and "completed" or 31 | @props.item.isEditMode and "editing") 32 | 33 | div className:"view", 34 | 35 | input 36 | className: "toggle" 37 | type: "checkbox" 38 | checked: @props.item.complete 39 | onChange: @check 40 | 41 | label 42 | onDoubleClick: @edit 43 | @props.item.text 44 | 45 | button 46 | className: "destroy" 47 | onClick: @destroy 48 | 49 | if @props.item.isEditMode 50 | input 51 | className: "edit" 52 | value: @props.item.text 53 | 54 | module.exports = TodoItem -------------------------------------------------------------------------------- /todo/todo/views/todo_list.coffee: -------------------------------------------------------------------------------- 1 | React = require 'react' 2 | PureRenderMixin = require('react/addons').addons.PureRenderMixin 3 | {createFactory} = React 4 | {div, h1, h2, input, header, section, label, ul, li, button, p, a} = React.DOM 5 | TodoItem = createFactory(require './todo_item') 6 | Immutable = require 'immutable' 7 | List = Immutable.List 8 | 9 | TodoList = React.createClass 10 | mixins: [PureRenderMixin] 11 | 12 | getDefaultProps: -> 13 | todoItems: List() 14 | 15 | 16 | mainInputKeyDown: (ev) -> 17 | @props.eventStream.onNext 18 | action: "mainInputKeyDown" 19 | keyCode: ev.keyCode 20 | text: ev.target.value 21 | 22 | 23 | mainInputTextChange: (ev) -> 24 | @props.eventStream.onNext 25 | action: "mainInputTextChange" 26 | text: ev.target.value 27 | 28 | 29 | toggleCheckAll: (ev) -> 30 | @props.eventStream.onNext 31 | action: "toggleCheckAll" 32 | checked: ev.target.checked 33 | 34 | 35 | render: -> 36 | div null, 37 | header {id: "header"}, 38 | 39 | h1 null, "todos" 40 | 41 | input 42 | id: "new-todo" 43 | autoFocus: true 44 | value: @props.todoText 45 | placeholder: "What needs to be done?" 46 | onKeyDown: @mainInputKeyDown 47 | onChange: @mainInputTextChange 48 | 49 | section {id: "main"}, 50 | 51 | input 52 | id: "toggle-all" 53 | type: "checkbox" 54 | checked: @props.checkAll 55 | onChange: @toggleCheckAll 56 | 57 | label 58 | htmlFor: "toggle-all" 59 | "Mark all as complete" 60 | 61 | ul {id: "todo-list"}, 62 | @props.todoItems.toArray().map (item) => 63 | TodoItem { 64 | key: item.id 65 | item 66 | eventStream: @props.eventStream 67 | } 68 | 69 | button 70 | onClick: => @props.eventStream.onNext {action: "history_back"} 71 | "Back" 72 | 73 | 74 | module.exports = TodoList --------------------------------------------------------------------------------