diff --git a/examples/todomvc/.babelrc b/examples/loggit-todomvc/.babelrc similarity index 100% rename from examples/todomvc/.babelrc rename to examples/loggit-todomvc/.babelrc diff --git a/examples/loggit-todomvc/actions/ActionTypes.js b/examples/loggit-todomvc/actions/ActionTypes.js new file mode 100644 index 0000000000..3d32d4a5bf --- /dev/null +++ b/examples/loggit-todomvc/actions/ActionTypes.js @@ -0,0 +1,24 @@ +import _ from 'lodash'; + +// Hides actual keys so everyone uses these constants. +function mirrorKeys(keys) { + return keys.reduce((actionMap, key) => { + return { + [key]: _.uniqueId(key + ':'), + ...actionMap + }; + }, {}); +} + +export default mirrorKeys([ + 'ADDED_TODO', + 'DELETED_TODO', + 'EDITED_TODO', + 'CHECK_TODO', + 'UNCHECK_TODO', + 'CHECK_ALL', + 'UNCHECK_ALL', + 'CLEAR_MARKED', + 'FINISHED_EDITING_TODO', + 'WILL_EDIT_TODO' +]); diff --git a/examples/loggit-todomvc/actions/TodoActions.js b/examples/loggit-todomvc/actions/TodoActions.js new file mode 100644 index 0000000000..85a869086e --- /dev/null +++ b/examples/loggit-todomvc/actions/TodoActions.js @@ -0,0 +1,69 @@ +import * as types from './ActionTypes'; + +export function addTodo(text) { + return { + type: types.ADDED_TODO, + text + }; +} + +export function deleteTodo(id) { + return { + type: types.DELETED_TODO, + id + }; +} + +export function editTodo(id, text) { + return { + type: types.EDITED_TODO, + id, + text + }; +} + +export function checkTodo(id) { + return { + type: types.CHECK_TODO, + id + }; +} + +export function uncheckTodo(id) { + return { + type: types.UNCHECK_TODO, + id + }; +} + +export function checkAll() { + return { + type: types.CHECK_ALL + }; +} + +export function uncheckAll() { + return { + type: types.UNCHECK_ALL + }; +} + +export function clearMarked() { + return { + type: types.CLEAR_MARKED + }; +} + +export function willEditTodo(todoId) { + return { + type: types.WILL_EDIT_TODO, + todoId: todoId + }; +} + +export function finishedEditingTodo(todoId) { + return { + type: types.FINISHED_EDITING_TODO, + todoId: todoId + }; +} diff --git a/examples/loggit-todomvc/components/Debugger.js b/examples/loggit-todomvc/components/Debugger.js new file mode 100644 index 0000000000..feff43700f --- /dev/null +++ b/examples/loggit-todomvc/components/Debugger.js @@ -0,0 +1,100 @@ +import React from 'react'; +import * as TodoActions from '../actions/TodoActions'; +import compactionKey from '../stores/compaction_fn'; + +// For hacking on internals +export default class Debugger extends React.Component { + static propTypes = { + loggit: React.PropTypes.object.isRequired + }; + + constructor(props, context) { + super(props, context); + this.state = { isMonkeyAwake: false }; + this.MonkeyTimer = null; + this.pokeMonkey = this.pokeMonkey.bind(this); + } + + // For easier profiling + componentDidMount() { + window.setTimeout(() => this.startMonkeying(), 4000); + } + + startMonkeying() { + this.setState({ isMonkeyAwake: true }); + const before = this.profileSnapshot(); + window.setTimeout(() => { + this.setState({ isMonkeyAwake: false }); + const after = this.profileSnapshot(); + this.outputProfiling(before, after); + }, 3000); + } + + profileSnapshot() { + return { + heap: window.performance.memory.usedJSHeapSize + }; + } + + outputProfiling(before, after) { + // console.table([ + // { heap: before.heap }, + // { heap: after.heap }, + // { heap: '+' + (after.heap - before.heap) } + // ]); + console.info({ heap: after.heap, delta: '+' + (after.heap - before.heap) }); + console.info(window.profilingReporter.printStats()); + } + + handleMonkey() { + this.setState({ isMonkeyAwake: !this.state.isMonkeyAwake }); + } + + componentDidUpdate(prevProps, prevState) { + if (prevState.isMonkeyAwake === this.state.isMonkeyAwake) { + return; + } + + if (this.state.isMonkeyAwake) { + this.MonkeyTimer = window.setInterval(this.pokeMonkey, 10); + } else { + window.clearInterval(this.MonkeyTimer); + } + } + + pokeMonkey() { + const actionFns = [ + TodoActions.checkAll, + TodoActions.uncheckAll, + TodoActions.clearMarked, + () => TodoActions.addTodo('do something: ' + Math.random()) + ] + const randomIndex = Math.floor(Math.random() * actionFns.length); + const randomAction = actionFns[randomIndex](); + this.props.loggit.recordFact(randomAction); + } + + forceCompaction() { + this.props.loggit.experimental.forceCompaction(); + } + + render() { + return ( +
+ + +
+ ); + } +} diff --git a/examples/todomvc/components/Footer.js b/examples/loggit-todomvc/components/Footer.js similarity index 100% rename from examples/todomvc/components/Footer.js rename to examples/loggit-todomvc/components/Footer.js diff --git a/examples/loggit-todomvc/components/Header.js b/examples/loggit-todomvc/components/Header.js new file mode 100644 index 0000000000..cfc8787055 --- /dev/null +++ b/examples/loggit-todomvc/components/Header.js @@ -0,0 +1,26 @@ +import React, { PropTypes } from 'react'; +import TodoTextInput from './TodoTextInput'; +import * as TodoActions from '../actions/TodoActions'; + +export default class Header { + static propTypes = { + loggit: PropTypes.object.isRequired + }; + + handleSave(text) { + if (text.length === 0) return; + const userAddedTodo = TodoActions.addTodo(text); + this.props.loggit.recordFact(userAddedTodo); + } + + render() { + return ( +
+

todos

+ +
+ ); + } +} diff --git a/examples/loggit-todomvc/components/MainSection.js b/examples/loggit-todomvc/components/MainSection.js new file mode 100644 index 0000000000..7e3ee369c5 --- /dev/null +++ b/examples/loggit-todomvc/components/MainSection.js @@ -0,0 +1,104 @@ +import React, { Component, PropTypes } from 'react'; +import TodoItem from './TodoItem'; +import Footer from './Footer'; +import { SHOW_ALL, SHOW_MARKED, SHOW_UNMARKED } from '../constants/TodoFilters'; +import ComputeTodos from '../stores/todos.js' +import * as TodoActions from '../actions/TodoActions'; + + +const TODO_FILTERS = { + [SHOW_ALL]: () => true, + [SHOW_UNMARKED]: todo => !todo.marked, + [SHOW_MARKED]: todo => todo.marked +}; + +export default class MainSection extends Component { + static propTypes = { + loggit: PropTypes.object.isRequired + }; + + constructor(props, context) { + super(props, context); + this.state = { filter: SHOW_ALL }; + } + + handleClearMarked() { + const {todos} = this.data(); + const atLeastOneMarked = todos.some(todo => todo.marked); + if (!atLeastOneMarked) return; + + this.props.loggit.recordFact(TodoActions.clearMarked()); + } + + handleShow(filter) { + this.setState({ filter }); + } + + handleInputChanged() { + const {todos} = this.data(); + const fact = (todos.every(todo => todo.marked)) + ? TodoActions.uncheckAll() + : TodoActions.checkAll(); + this.props.loggit.recordFact(fact); + } + + computations() { + return { + todos: ComputeTodos + } + } + + data() { + return this.props.loggit.computeFor(this); + } + + render() { + const { todos } = this.data(); + const { filter } = this.state; + + const filteredTodos = todos.filter(TODO_FILTERS[filter]); + const markedCount = todos.reduce((count, todo) => + todo.marked ? count + 1 : count, + 0 + ); + + return ( +
+ {this.renderToggleAll(todos, markedCount)} + + {this.renderFooter(markedCount)} +
+ ); + } + + renderToggleAll(todos, markedCount) { + if (todos.length > 0) { + return ( + + ); + } + } + + renderFooter(markedCount) { + const { todos } = this.data(); + const { filter } = this.state; + const unmarkedCount = todos.length - markedCount; + + if (todos.length) { + return ( +