diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..40b878d --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +node_modules/ \ No newline at end of file diff --git a/.idea/.name b/.idea/.name new file mode 100644 index 0000000..500064c --- /dev/null +++ b/.idea/.name @@ -0,0 +1 @@ +meteor-redux \ No newline at end of file diff --git a/.idea/encodings.xml b/.idea/encodings.xml new file mode 100644 index 0000000..97626ba --- /dev/null +++ b/.idea/encodings.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/.idea/jsLibraryMappings.xml b/.idea/jsLibraryMappings.xml new file mode 100644 index 0000000..b8387eb --- /dev/null +++ b/.idea/jsLibraryMappings.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/.idea/meteor-redux.iml b/.idea/meteor-redux.iml new file mode 100644 index 0000000..80caaa8 --- /dev/null +++ b/.idea/meteor-redux.iml @@ -0,0 +1,18 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..e500f7c --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..bfac488 --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..94a25f7 --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/.meteor/.finished-upgraders b/.meteor/.finished-upgraders new file mode 100644 index 0000000..dacc2c0 --- /dev/null +++ b/.meteor/.finished-upgraders @@ -0,0 +1,13 @@ +# This file contains information which helps Meteor properly upgrade your +# app when you run 'meteor update'. You should check it into version control +# with your project. + +notices-for-0.9.0 +notices-for-0.9.1 +0.9.4-platform-file +notices-for-facebook-graph-api-2 +1.2.0-standard-minifiers-package +1.2.0-meteor-platform-split +1.2.0-cordova-changes +1.2.0-breaking-changes +1.3.0-split-minifiers-package diff --git a/.meteor/.gitignore b/.meteor/.gitignore new file mode 100644 index 0000000..4083037 --- /dev/null +++ b/.meteor/.gitignore @@ -0,0 +1 @@ +local diff --git a/.meteor/.id b/.meteor/.id new file mode 100644 index 0000000..13cc39d --- /dev/null +++ b/.meteor/.id @@ -0,0 +1,7 @@ +# This file contains a token that is unique to your project. +# Check it into your repository along with the rest of this directory. +# It can be used for purposes such as: +# - ensuring you don't accidentally deploy one app on top of another +# - providing package authors with aggregated statistics + +13b1yi71jis8gxb3ceye diff --git a/.meteor/packages b/.meteor/packages new file mode 100644 index 0000000..d4c8001 --- /dev/null +++ b/.meteor/packages @@ -0,0 +1,21 @@ +# Meteor packages used by this project, one per line. +# Check this file (and the other files in this directory) into your repository. +# +# 'meteor add' and 'meteor remove' will edit this file for you, +# but you can also edit it by hand. + +meteor-base # Packages every Meteor app needs to have +mobile-experience # Packages for a great mobile UX +mongo # The database Meteor supports right now +blaze-html-templates # Compile .html files into Meteor Blaze views +reactive-var # Reactive variable for tracker +jquery # Helpful client-side library +tracker # Meteor's client-side reactive programming library + +standard-minifier-css # CSS minifier run for production mode +standard-minifier-js # JS minifier run for production mode +es5-shim # ECMAScript 5 compatibility for older browsers. +ecmascript # Enable ECMAScript2015+ syntax in app code + +autopublish # Publish all data to the clients (for prototyping) +insecure # Allow all DB writes from clients (for prototyping) diff --git a/.meteor/platforms b/.meteor/platforms new file mode 100644 index 0000000..efeba1b --- /dev/null +++ b/.meteor/platforms @@ -0,0 +1,2 @@ +server +browser diff --git a/.meteor/release b/.meteor/release new file mode 100644 index 0000000..940e0b5 --- /dev/null +++ b/.meteor/release @@ -0,0 +1 @@ +METEOR@1.3.2.4 diff --git a/.meteor/versions b/.meteor/versions new file mode 100644 index 0000000..0e2f545 --- /dev/null +++ b/.meteor/versions @@ -0,0 +1,69 @@ +allow-deny@1.0.4 +autopublish@1.0.7 +autoupdate@1.2.9 +babel-compiler@6.6.4 +babel-runtime@0.1.8 +base64@1.0.8 +binary-heap@1.0.8 +blaze@2.1.7 +blaze-html-templates@1.0.4 +blaze-tools@1.0.8 +boilerplate-generator@1.0.8 +caching-compiler@1.0.4 +caching-html-compiler@1.0.6 +callback-hook@1.0.8 +check@1.2.1 +ddp@1.2.5 +ddp-client@1.2.7 +ddp-common@1.2.5 +ddp-server@1.2.6 +deps@1.0.12 +diff-sequence@1.0.5 +ecmascript@0.4.3 +ecmascript-runtime@0.2.10 +ejson@1.0.11 +es5-shim@4.5.10 +fastclick@1.0.11 +geojson-utils@1.0.8 +hot-code-push@1.0.4 +html-tools@1.0.9 +htmljs@1.0.9 +http@1.1.5 +id-map@1.0.7 +insecure@1.0.7 +jquery@1.11.8 +launch-screen@1.0.11 +livedata@1.0.18 +logging@1.0.12 +meteor@1.1.14 +meteor-base@1.0.4 +minifier-css@1.1.11 +minifier-js@1.1.11 +minimongo@1.0.16 +mobile-experience@1.0.4 +mobile-status-bar@1.0.12 +modules@0.6.1 +modules-runtime@0.6.3 +mongo@1.1.7 +mongo-id@1.0.4 +npm-mongo@1.4.43 +observe-sequence@1.0.11 +ordered-dict@1.0.7 +promise@0.6.7 +random@1.0.9 +reactive-var@1.0.9 +reload@1.1.8 +retry@1.0.7 +routepolicy@1.0.10 +spacebars@1.0.11 +spacebars-compiler@1.0.11 +standard-minifier-css@1.0.6 +standard-minifier-js@1.0.6 +templating@1.1.9 +templating-tools@1.0.4 +tracker@1.0.13 +ui@1.0.11 +underscore@1.0.8 +url@1.0.9 +webapp@1.2.8 +webapp-hashing@1.0.9 diff --git a/client/main.css b/client/main.css new file mode 100644 index 0000000..3593c36 --- /dev/null +++ b/client/main.css @@ -0,0 +1,125 @@ +body { + font-family: sans-serif; + background-color: #315481; + background-image: linear-gradient(to bottom, #315481, #918e82 100%); + background-attachment: fixed; + + position: absolute; + top: 0; + bottom: 0; + left: 0; + right: 0; + + padding: 0; + margin: 0; + + font-size: 14px; +} + +.container { + max-width: 600px; + margin: 0 auto; + min-height: 100%; + background: white; +} + +header { + background: #d2edf4; + background-image: linear-gradient(to bottom, #d0edf5, #e1e5f0 100%); + padding: 20px 15px 15px 15px; + position: relative; +} + +#login-buttons { + display: block; +} + +h1 { + font-size: 1.5em; + margin: 0; + margin-bottom: 10px; + display: inline-block; + margin-right: 1em; +} + +form { + margin-top: 10px; + margin-bottom: -10px; + position: relative; +} + +.new-task input { + box-sizing: border-box; + padding: 10px 0; + background: transparent; + border: none; + width: 100%; + padding-right: 80px; + font-size: 1em; +} + +.new-task input:focus{ + outline: 0; +} + +ul { + margin: 0; + padding: 0; + background: white; +} + +.delete { + float: right; + font-weight: bold; + background: none; + font-size: 1em; + border: none; + position: relative; +} + +li { + position: relative; + list-style: none; + padding: 15px; + border-bottom: #eee solid 1px; +} + +li .text { + margin-left: 10px; +} + +li.checked { + color: #888; +} + +li.checked .text { + text-decoration: line-through; +} + +li.private { + background: #eee; + border-color: #ddd; +} + +header .hide-completed { + float: right; +} + +.toggle-private { + margin-left: 5px; +} + +@media (max-width: 600px) { + li { + padding: 12px 15px; + } + + .search { + width: 150px; + clear: both; + } + + .new-task input { + padding-bottom: 5px; + } +} \ No newline at end of file diff --git a/client/main.html b/client/main.html new file mode 100644 index 0000000..a84b4db --- /dev/null +++ b/client/main.html @@ -0,0 +1,7 @@ + + Todo List + + + +
+ diff --git a/client/main.js b/client/main.js new file mode 100644 index 0000000..25f7604 --- /dev/null +++ b/client/main.js @@ -0,0 +1,11 @@ +import React from 'react'; +import ReactDOM from 'react-dom'; +import App from '../imports/components/app.jsx'; +import { Provider } from 'react-redux' +import store from '../imports/store'; + +Meteor.startup(() => { + ReactDOM.render( + , + document.getElementById('app')); +}); diff --git a/imports/actionCreators.js b/imports/actionCreators.js new file mode 100644 index 0000000..5197a94 --- /dev/null +++ b/imports/actionCreators.js @@ -0,0 +1,15 @@ +import { createAction } from 'redux-actions'; + +let nextTodoId = 0; + +export const ADD_TODO = 'ADD_TODO'; +export const addTodo = createAction(ADD_TODO, text => ({ text, id: nextTodoId++ })); + +export const REMOVE_TODO = 'REMOVE_TODO'; +export const removeTodo = createAction(REMOVE_TODO, id => ({ id })); + +export const TOGGLE_TODO = 'TOGGLE_TODO'; +export const toggleTodo = createAction(TOGGLE_TODO, id => ({ id })); + +export const TOGGLE_VISIBILITY_FILTER = 'TOGGLE_VISIBILITY_FILTER'; +export const toggleVisibilityFilter = createAction(TOGGLE_VISIBILITY_FILTER); diff --git a/imports/api/tasks.js b/imports/api/tasks.js new file mode 100644 index 0000000..4a68367 --- /dev/null +++ b/imports/api/tasks.js @@ -0,0 +1,5 @@ +import { Mongo } from 'meteor/mongo'; + +const Tasks = new Mongo.Collection('tasks'); + +export default Tasks; diff --git a/imports/components/app.jsx b/imports/components/app.jsx new file mode 100644 index 0000000..48709b8 --- /dev/null +++ b/imports/components/app.jsx @@ -0,0 +1,65 @@ +import React, { Component } from 'react'; +import { connect } from 'react-redux' +import TaskList from './list'; +import { addTodo, toggleVisibilityFilter } from '../actionCreators'; + +class App extends Component { + constructor(props) { + super(props); + + this.handleSubmit = this.handleSubmit.bind(this); + } + + renderTasks() { + return this.getTasks().map((task, i) => ( + + )); + } + + handleSubmit(e) { + const todo = this.refs.textInput.value; + this.props.addTodo(todo); + + e.stopPropagation(); + e.preventDefault(); + } + + render() { + return ( +
+
+

Todo List

+ + +
+ +
+
+ + +
+ ); + } +} + +export default connect( + (state => ({ + visibilityFilter: state.visibilityFilter === 'NONE' + })), + (dispatch) => ({ + addTodo: (text) => dispatch(addTodo(text)), + toggleVisibilityFilter: () => dispatch(toggleVisibilityFilter()) + }) +)(App); diff --git a/imports/components/list.js b/imports/components/list.js new file mode 100644 index 0000000..08b2910 --- /dev/null +++ b/imports/components/list.js @@ -0,0 +1,44 @@ +import React, { Component } from 'react'; +import { connect } from 'react-redux' +import { toggleTodo, removeTodo } from '../actionCreators'; + +import Task from './task.jsx'; + +const mapStateToProps = (state) => { + return { + todos: state.todos.filter(todo => state.visibilityFilter === 'ALL' || !todo.checked) + } +}; + +const mapDispatchToProps = (dispatch) => ({ + toggleTodo: (id) => dispatch(toggleTodo(id)), + removeTodo: (id) => dispatch(removeTodo(id)), +}); + +const EmptyTaskPlaceHolder = () => (); + +// App component - represents the whole app +class TaskList extends Component { + renderTasks() { + return this.props.todos.map((task, i) => ( + this.props.toggleTodo(task.id)} + onDeleted={() => this.props.removeTodo(task.id)} + /> + )); + } + + render() { + return ( + + ); + } +} + +export default connect(mapStateToProps, mapDispatchToProps)(TaskList); diff --git a/imports/components/task.jsx b/imports/components/task.jsx new file mode 100644 index 0000000..4b89fac --- /dev/null +++ b/imports/components/task.jsx @@ -0,0 +1,34 @@ +import React, { Component, PropTypes } from 'react'; + +// Task component - represents a single item +class Task extends Component { + render() { + const taskClassName = this.props.task.checked ? 'checked' : ''; + + return ( +
  • + + + + + {this.props.task.text} +
  • + ); + } +} + +Task.propTypes = { + // This component gets the task to display through a React prop. + // We can use propTypes to indicate it is required + task: PropTypes.object.isRequired, + onToggled: PropTypes.func, + onDeleted: PropTypes.func +}; + +export default Task; diff --git a/imports/reducers.js b/imports/reducers.js new file mode 100644 index 0000000..498929e --- /dev/null +++ b/imports/reducers.js @@ -0,0 +1,49 @@ +import { combineReducers } from 'redux'; +import { ADD_TODO, TOGGLE_TODO, REMOVE_TODO, TOGGLE_VISIBILITY_FILTER } from './actionCreators'; +import update from 'immutability-helper'; + +function todos(state = [], action) { + const payload = action.payload; + let index; + + switch (action.type) { + case REMOVE_TODO: + index = state.findIndex(todo => todo.id === payload.id); + + return update(state, { + $splice: [[index, 1]] + }); + case ADD_TODO: + return state.concat([ {...action.payload} ]) + case TOGGLE_TODO: + index = state.findIndex(todo => todo.id === payload.id); + + if (index !== -1) { + return update(state, { + [index]: { + checked: { $set: !state[index].checked } + } + }); + } + + return state; + default: + return state + } +} + +function visibilityFilter(state = 'ALL', action) { + switch (action.type) { + case TOGGLE_VISIBILITY_FILTER: + return state === 'NONE' ? 'ALL' : 'NONE'; + default: + return state; + } +} + +const todoApps = combineReducers({ + visibilityFilter, + todos +}); + +export default todoApps; \ No newline at end of file diff --git a/imports/store.js b/imports/store.js new file mode 100644 index 0000000..662d04f --- /dev/null +++ b/imports/store.js @@ -0,0 +1,14 @@ +import { applyMiddleware, createStore } from 'redux'; +import thunk from 'redux-thunk'; +import promise from 'redux-promise'; +import createLogger from 'redux-logger'; +import todoApp from './reducers'; + +const logger = createLogger(); + +const store = createStore( + todoApp, + applyMiddleware(thunk, promise, logger) +); + +export default store; diff --git a/package.json b/package.json new file mode 100644 index 0000000..e70bb4e --- /dev/null +++ b/package.json @@ -0,0 +1,19 @@ +{ + "name": "meteor-redux", + "private": true, + "scripts": { + "start": "meteor run" + }, + "dependencies": { + "immutability-helper": "^2.0.0", + "meteor-node-stubs": "~0.2.0", + "react": "^15.1.0", + "react-dom": "^15.1.0", + "react-redux": "^4.4.5", + "redux": "^3.5.2", + "redux-actions": "^0.9.1", + "redux-logger": "^2.6.1", + "redux-promise": "^0.5.3", + "redux-thunk": "^2.1.0" + } +}