Skip to content

Commit

Permalink
implement application logic with redux
Browse files Browse the repository at this point in the history
  • Loading branch information
rikukissa committed Jun 28, 2016
1 parent ab5af36 commit ceadf01
Show file tree
Hide file tree
Showing 6 changed files with 357 additions and 188 deletions.
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@
"react-redux": "^4.4.5",
"react-router": "^2.0.0",
"react-textarea-autosize": "^4.0.2",
"redux": "^3.5.2"
"redux": "^3.5.2",
"redux-thunk": "^2.1.0"
}
}
229 changes: 62 additions & 167 deletions src/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,7 @@ import { find } from 'lodash';
import { connect } from 'react-redux';

import {
saveThought,
getThoughts,
deleteThought,
updateThought
} from 'utils/storage';

import {
createThought,
getUnfinishedTodos,
sortByCreatedAt,
UNFINISHED_TODO_TAG
} from 'utils/thought';

Expand All @@ -28,195 +19,93 @@ import FilterBar from 'components/filter-bar';
import Scaler from 'components/scaler';
import Background from 'components/background';

import {
addThought,
deleteThought,
modifyThought,
stopEditing,
setEditable,
resetFilters,
removeFilter,
addFilter
} from 'thoughts/actions';

const App = React.createClass({
getInitialState() {
return {
thoughts: [],
editableThoughtId: null,
currentText: '',
hashtagFilters: [],
// Thoughts created or modified while filter view
// It would probably be weird if they would just disappeared when you delete a tag
editedWhileFilterOn: []
};
},
componentWillReceiveProps(nextProps) {
if (nextProps.board === this.props.board) {
return;
}

// When board param changes, basically reset everything
this.setState(this.getInitialState());
this.loadThoughts(nextProps.board);
},
componentDidMount() {
document.addEventListener('keydown', this.checkForSpecialKey, true);

if (!this.isInIntroMode()) {
this.loadThoughts(this.props.board);
}
},
componentWillUnmount() {
document.removeEventListener('keydown', this.checkForSpecialKey, true);
},
isInIntroMode() {
return !this.props.board;
},
loadThoughts(board) {
getThoughts(board).then((thoughts) => {
this.setState({
thoughts: thoughts.sort(sortByCreatedAt)
});
});
},
checkForSpecialKey(event) {
const thoughts = this.state.thoughts;

// Edit the most recent thought
if (!this.state.editableThoughtId && isUp(event.keyCode) && thoughts.length > 0) {
this.setEditable(thoughts[0]);
return;
}

// Reset filters with ESC
if (!this.state.editableThoughtId && isEsc(event.keyCode)) {
this.resetFilters();
return;
}

// Create thought
if (!this.state.editableThoughtId && isThoughtCreatingKeypress(event)) {
const initialText = `${this.state.hashtagFilters.join(' ')} `;
const newThought = this.createThought(initialText);
this.setEditable(newThought);
return;
}
},
createThought(text) {
const newThought = createThought(text);
const updatedThoughts = [newThought].concat(this.state.thoughts);

this.setState({
thoughts: updatedThoughts
});

return newThought;
updateThought(thought) {
this.props.dispatch(modifyThought(thought));
},
deleteThought(thought) {

// Delete thought from state optimistically
// Do nothing with the return value of the delete api request
const updatedThoughts = this.state.thoughts.filter((thoug) =>
thought !== thoug
);

this.setState({
thoughts: updatedThoughts,
editableThoughtId: null
});

if (!this.isInIntroMode()) {
deleteThought(this.props.board, thought);
}
},
hasFilter() {
return this.state.hashtagFilters.length > 0;
},
findThoughtById(id) {
return find(this.state.thoughts, { id });
this.props.dispatch(deleteThought(thought));
},
setEditable(thought) {
// Something is already being edited
if (this.state.editableThoughtId) {
const editableThought =
this.findThoughtById(this.state.editableThoughtId);

this.stopEditing(editableThought);
}

this.setState({
editableThoughtId: thought.id,
editedWhileFilterOn: this.hasFilter() ?
this.state.editedWhileFilterOn.concat(thought.id) :
this.state.editedWhileFilterOn
});
this.props.dispatch(setEditable(thought));
},
stopEditing(thought) {
this.resetEditable();

if (thought.text.trim() === '') {
this.deleteThought(thought);
return;
}

if (this.isInIntroMode()) {
return;
}

if (thought._id) {
updateThought(this.props.board, thought)
.then((updatedThought) => {
this.updateThought(thought, updatedThought);
});
} else {
saveThought(this.props.board, thought).then((savedThought) => {
this.updateThought(thought, savedThought);
});
}
this.props.dispatch(stopEditing(thought));
},
addFilter(hashtag) {
this.props.dispatch(addFilter(hashtag));
},
removeFromFilter(hashtag) {
this.props.dispatch(removeFilter(hashtag));
},
resetFilters() {
this.props.dispatch(resetFilters());
},
resetEditable() {
this.setState({ editableThoughtId: null });
const id = this.props.editableThoughtId;
if (id) {
const thought = find(this.props.thoughts, { id });
this.props.dispatch(stopEditing(thought));
}
},
addFilter(hashtag) {
const filterExists = this.state.hashtagFilters.indexOf(hashtag) > -1;
checkForSpecialKey(event) {
const thoughts = this.props.thoughts;

if (filterExists) {
// Edit the most recent thought
if (!this.props.editableThoughtId && isUp(event.keyCode) && thoughts.length > 0) {
this.setEditable(thoughts[0]);
return;
}

this.setState({
hashtagFilters: this.state.hashtagFilters.concat(hashtag),
editedWhileFilterOn: []
});
},
removeFromFilter(hashtag) {
if (this.state.hashtagFilters.length === 1) {
// Reset filters with ESC
if (!this.props.editableThoughtId && isEsc(event.keyCode)) {
this.resetFilters();
return;
}

this.setState({
hashtagFilters: this.state.hashtagFilters.filter((hash) => hash !== hashtag)
});
},
resetFilters() {
this.setState({
hashtagFilters: [],
editedWhileFilterOn: []
});
},
updateThought(thought, newThought) {
const updatedThoughts = this.state.thoughts.map((thoug) => {
if (thoug !== thought) {
return thoug;
}
return newThought;
});

this.setState({
thoughts: updatedThoughts
});
// Create thought
if (!this.props.editableThoughtId && isThoughtCreatingKeypress(event)) {
const initialText = `${this.props.hashtagFilters.join(' ')} `;
this.props.dispatch(addThought(initialText));
}
},
render() {
const thoughts = this.state.thoughts;
const hashtagFilters = this.state.hashtagFilters;

const thoughts = this.props.thoughts;
const hashtagFilters = this.props.hashtagFilters;
const unfinishedTodos = getUnfinishedTodos(thoughts);

const filteredThoughts = hashtagFilters.length === 0 ?
thoughts :
thoughts.filter((thought) => {
const edited = this.state.editedWhileFilterOn.indexOf(thought.id) > -1;
// Show thoughts that match current filters or that have been
// edited while current filter was on

const edited = this.props.editedWhileFilterOn.indexOf(thought.id) > -1;

const hasMatchingTag = hashtagFilters.every((hashtag) =>
thought.hashtags.indexOf(hashtag) > -1
Expand All @@ -225,6 +114,7 @@ const App = React.createClass({
return edited || hasMatchingTag;
});

// Use thought scaler only when filters are not used
const ThoughtsWrapper = hashtagFilters.length === 0 ?
Scaler :
'div';
Expand Down Expand Up @@ -254,14 +144,12 @@ const App = React.createClass({
event.stopPropagation();
this.setEditable(thought);
}}
onChange={(updatedThought) =>
this.updateThought(thought, updatedThought)}
onSubmit={(updatedThought) =>
this.stopEditing(updatedThought)}
onChange={this.updateThought}
onSubmit={this.stopEditing}
onCancel={() => this.stopEditing(thought)}
onDelete={() => this.deleteThought(thought)}
onHashtagClick={this.addFilter}
editable={this.state.editableThoughtId === thought.id}
editable={this.props.editableThoughtId === thought.id}
thought={thought} />
))
}
Expand All @@ -271,6 +159,13 @@ const App = React.createClass({
}
});

export default connect((store) => ({
thoughts: store.thoughts
}))(App);
function storeToProps(store) {
return {
thoughts: store.thoughts,
editableThoughtId: store.editor.editableThoughtId,
editedWhileFilterOn: store.editor.editedWhileFilterOn,
hashtagFilters: store.editor.hashtagFilters
};
}

export default connect(storeToProps)(App);
9 changes: 6 additions & 3 deletions src/components/thought/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -62,10 +62,8 @@ export const ThoughtContent = React.createClass({
source={this.props.expanded ? this.props.thought.text : breakText(this.props.thought.text)}
allowedTypes={ReactMarkdown.types.concat(['Checkbox', 'Hashtag'])}
renderers={customRenderers}
walker={walker}
/>
walker={walker} />
);

}
});

Expand All @@ -88,6 +86,11 @@ export default React.createClass({
this.focus();
}
},
componentDidMount() {
if (this.props.editable) {
this.focus();
}
},
emitUpdatedText(newText) {
this.props.onChange(updateText(this.props.thought, newText));
},
Expand Down
Loading

0 comments on commit ceadf01

Please sign in to comment.