Skip to content

Commit 2d1bbe6

Browse files
committed
Create Collection
1 parent bea6e79 commit 2d1bbe6

File tree

8 files changed

+249
-4
lines changed

8 files changed

+249
-4
lines changed

client/constants.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,10 @@ export const SET_PROJECT = 'SET_PROJECT';
3333
export const SET_PROJECTS = 'SET_PROJECTS';
3434

3535
export const SET_COLLECTIONS = 'SET_COLLECTIONS';
36+
export const CREATE_COLLECTION = 'CREATED_COLLECTION';
37+
38+
export const ADD_TO_COLLECTION = 'ADD_TO_COLLECTION';
39+
export const REMOVE_FROM_COLLECTION = 'REMOVE_FROM_COLLECTION';
3640

3741
export const DELETE_PROJECT = 'DELETE_PROJECT';
3842

client/modules/IDE/actions/collections.js

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,3 +32,80 @@ export function getCollections(username) {
3232
});
3333
};
3434
}
35+
36+
export function createCollection(collection) {
37+
return (dispatch) => {
38+
dispatch(startLoader());
39+
const url = `${ROOT_URL}/collections`;
40+
return axios.post(url, collection, { withCredentials: true })
41+
.then((response) => {
42+
dispatch({
43+
type: ActionTypes.CREATE_COLLECTION
44+
});
45+
dispatch(stopLoader());
46+
47+
return response.data;
48+
})
49+
.catch((response) => {
50+
dispatch({
51+
type: ActionTypes.ERROR,
52+
error: response.data
53+
});
54+
dispatch(stopLoader());
55+
56+
return response.data;
57+
});
58+
};
59+
}
60+
61+
export function addToCollection(collectionId, projectId) {
62+
return (dispatch) => {
63+
dispatch(startLoader());
64+
const url = `${ROOT_URL}/collections/${collectionId}/${projectId}`;
65+
return axios.post(url, { withCredentials: true })
66+
.then((response) => {
67+
dispatch({
68+
type: ActionTypes.ADD_TO_COLLECTION,
69+
payload: response.data
70+
});
71+
dispatch(stopLoader());
72+
73+
return response.data;
74+
})
75+
.catch((response) => {
76+
dispatch({
77+
type: ActionTypes.ERROR,
78+
error: response.data
79+
});
80+
dispatch(stopLoader());
81+
82+
return response.data;
83+
});
84+
};
85+
}
86+
87+
export function removeFromCollection(collectionId, projectId) {
88+
return (dispatch) => {
89+
dispatch(startLoader());
90+
const url = `${ROOT_URL}/collections/${collectionId}/${projectId}`;
91+
return axios.delete(url, { withCredentials: true })
92+
.then((response) => {
93+
dispatch({
94+
type: ActionTypes.REMOVE_FROM_COLLECTION,
95+
payload: response.data
96+
});
97+
dispatch(stopLoader());
98+
99+
return response.data;
100+
})
101+
.catch((response) => {
102+
dispatch({
103+
type: ActionTypes.ERROR,
104+
error: response.data
105+
});
106+
dispatch(stopLoader());
107+
108+
return response.data;
109+
});
110+
};
111+
}
Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
import format from 'date-fns/format';
2+
import PropTypes from 'prop-types';
3+
import React from 'react';
4+
import { Helmet } from 'react-helmet';
5+
import InlineSVG from 'react-inlinesvg';
6+
import { connect } from 'react-redux';
7+
import { browserHistory } from 'react-router';
8+
import { bindActionCreators } from 'redux';
9+
import classNames from 'classnames';
10+
import * as ProjectActions from '../actions/project';
11+
import * as ProjectsActions from '../actions/projects';
12+
import * as CollectionsActions from '../actions/collections';
13+
import * as ToastActions from '../actions/toast';
14+
import * as SortingActions from '../actions/sorting';
15+
import * as IdeActions from '../actions/ide';
16+
import { getCollection } from '../selectors/collections';
17+
import Loader from '../../App/components/loader';
18+
import Overlay from '../../App/components/Overlay';
19+
20+
const arrowUp = require('../../../images/sort-arrow-up.svg');
21+
const arrowDown = require('../../../images/sort-arrow-down.svg');
22+
const downFilledTriangle = require('../../../images/down-filled-triangle.svg');
23+
24+
class CollectionCreate extends React.Component {
25+
state = {
26+
collection: {
27+
name: 'My collection name',
28+
description: ''
29+
}
30+
}
31+
32+
getTitle() {
33+
if (this.props.username === this.props.user.username) {
34+
return 'p5.js Web Editor | My collections';
35+
}
36+
return `p5.js Web Editor | ${this.props.username}'s collections`;
37+
}
38+
39+
handleTextChange = field => (evt) => {
40+
this.setState({
41+
collection: {
42+
...this.state.collection,
43+
[field]: evt.target.value,
44+
}
45+
});
46+
}
47+
48+
handleCreateCollection = () => {
49+
this.props.createCollection(this.state.collection)
50+
.then(({ id, owner }) => {
51+
// Redirect to collection URL
52+
console.log('Done, will redirect to collection');
53+
browserHistory.replace(`/${owner.username}/collections/${id}`);
54+
})
55+
.catch((error) => {
56+
console.error('Error creating collection', error);
57+
});
58+
}
59+
60+
_renderCollectionMetadata() {
61+
return (
62+
<div className="collections-metadata">
63+
<p><input type="text" value={this.state.collection.description} placeholder="This is a collection of..." onChange={this.handleTextChange('description')} /></p>
64+
</div>
65+
);
66+
}
67+
68+
render() {
69+
const username = this.props.username !== undefined ? this.props.username : this.props.user.username;
70+
71+
return (
72+
<Overlay
73+
ariaLabel="collection"
74+
title={<input type="text" value={this.state.collection.name} onChange={this.handleTextChange('name')} />}
75+
previousPath={this.props.previousPath}
76+
>
77+
<div className="sketches-table-container">
78+
<Helmet>
79+
<title>{this.getTitle()}</title>
80+
</Helmet>
81+
{this._renderCollectionMetadata()}
82+
<button onClick={this.handleCreateCollection}>Add collection</button>
83+
</div>
84+
</Overlay>
85+
);
86+
}
87+
}
88+
89+
CollectionCreate.propTypes = {
90+
user: PropTypes.shape({
91+
username: PropTypes.string,
92+
authenticated: PropTypes.bool.isRequired
93+
}).isRequired,
94+
getCollections: PropTypes.func.isRequired,
95+
collection: PropTypes.shape({}).isRequired, // TODO
96+
username: PropTypes.string,
97+
loading: PropTypes.bool.isRequired,
98+
toggleDirectionForField: PropTypes.func.isRequired,
99+
resetSorting: PropTypes.func.isRequired,
100+
sorting: PropTypes.shape({
101+
field: PropTypes.string.isRequired,
102+
direction: PropTypes.string.isRequired
103+
}).isRequired
104+
};
105+
106+
CollectionCreate.defaultProps = {
107+
username: undefined
108+
};
109+
110+
function mapStateToProps(state, ownProps) {
111+
return {
112+
user: state.user,
113+
};
114+
}
115+
116+
function mapDispatchToProps(dispatch) {
117+
return bindActionCreators(Object.assign({}, CollectionsActions, ProjectsActions, ToastActions, SortingActions), dispatch);
118+
}
119+
120+
export default connect(mapStateToProps, mapDispatchToProps)(CollectionCreate);

client/modules/IDE/components/CollectionList.jsx

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,14 @@ class CollectionListRowBase extends React.Component {
134134
}
135135
}
136136

137+
handleCollectionAdd = () => {
138+
this.props.addToCollection(this.props.collection.id, this.props.project.id);
139+
}
140+
141+
handleCollectionRemove = () => {
142+
this.props.removeFromCollection(this.props.collection.id, this.props.project.id);
143+
}
144+
137145
static projectInCollection(project, collection) {
138146
return collection.items.find(item => item.project.id === project.id) != null;
139147
}
@@ -146,9 +154,9 @@ class CollectionListRowBase extends React.Component {
146154

147155
if (project != null && addMode === true) {
148156
if (CollectionListRowBase.projectInCollection(project, collection)) {
149-
actions = <td>Added</td>;
157+
actions = <td><button onClick={this.handleCollectionRemove}>Remove</button></td>;
150158
} else {
151-
actions = <td>Add to project</td>;
159+
actions = <td><button onClick={this.handleCollectionAdd}>Add</button></td>;
152160
}
153161
} else {
154162
actions = (
@@ -228,7 +236,7 @@ CollectionListRowBase.propTypes = {
228236
};
229237

230238
function mapDispatchToPropsSketchListRow(dispatch) {
231-
return bindActionCreators(Object.assign({}, ProjectActions, IdeActions), dispatch);
239+
return bindActionCreators(Object.assign({}, CollectionsActions, ProjectActions, IdeActions), dispatch);
232240
}
233241

234242
const CollectionListRow = connect(null, mapDispatchToPropsSketchListRow)(CollectionListRowBase);
@@ -299,6 +307,9 @@ class CollectionList extends React.Component {
299307
<Helmet>
300308
<title>{this.getTitle()}</title>
301309
</Helmet>
310+
311+
<Link to={`/${username}/collections/create`}>New collection</Link>
312+
302313
{this._renderLoader()}
303314
{this._renderEmptyTable()}
304315
{this.hasCollections() &&

client/modules/IDE/pages/IDEView.jsx

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ import Overlay from '../../App/components/Overlay';
3232
import SketchList from '../components/SketchList';
3333
import Collection from '../components/Collection';
3434
import CollectionList from '../components/CollectionList';
35+
import CollectionCreate from '../components/CollectionCreate';
3536
import AssetList from '../components/AssetList';
3637
import About from '../components/About';
3738
import Feedback from '../components/Feedback';
@@ -391,14 +392,21 @@ class IDEView extends React.Component {
391392
/>
392393
</Overlay>
393394
}
394-
{this.props.location.pathname.match(/collections\//) &&
395+
{this.props.location.pathname.match(/collections\/(?!.*create)/) &&
395396
<Collection
396397
collectionId={this.props.params.collection_id}
397398
previousPath={this.props.ide.previousPath}
398399
username={this.props.params.username}
399400
user={this.props.user}
400401
/>
401402
}
403+
{this.props.location.pathname.match(/collections\/create$/) &&
404+
<CollectionCreate
405+
previousPath={this.props.ide.previousPath}
406+
username={this.props.params.username}
407+
user={this.props.user}
408+
/>
409+
}
402410
{this.props.location.pathname.match(/add-to-collection$/) &&
403411
<Overlay
404412
ariaLabel="add to collection"

client/modules/IDE/reducers/collections.js

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,18 @@ const sketches = (state = [], action) => {
44
switch (action.type) {
55
case ActionTypes.SET_COLLECTIONS:
66
return action.collections;
7+
8+
// The API returns the complete new collection
9+
// with the items added or removed
10+
case ActionTypes.ADD_TO_COLLECTION:
11+
case ActionTypes.REMOVE_FROM_COLLECTION:
12+
return state.map((collection) => {
13+
if (collection.id === action.payload.id) {
14+
return action.payload;
15+
}
16+
17+
return collection;
18+
});
719
default:
820
return state;
921
}

client/routes.jsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ const routes = store => (
4242
<Route path="/:username/sketches/:project_id/add-to-collection" component={IDEView} />
4343
<Route path="/:username/sketches" component={IDEView} />
4444
<Route path="/:username/collections" component={IDEView} />
45+
<Route path="/:username/collections/create" component={IDEView} />
4546
<Route path="/:username/collections/:collection_id" component={IDEView} />
4647
<Route path="/about" component={IDEView} />
4748
<Route path="/feedback" component={IDEView} />

server/routes/server.routes.js

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,18 @@ router.get('/:username/collections', (req, res) => {
108108
));
109109
});
110110

111+
router.get('/:username/collections/create', (req, res) => {
112+
userExists(req.params.username, exists => (
113+
exists ? res.send(renderIndex()) : get404Sketch(html => res.send(html))
114+
));
115+
});
116+
117+
router.get('/:username/sketches/:project_id/add-to-collection', (req, res) => {
118+
projectForUserExists(req.params.username, req.params.project_id, exists => (
119+
exists ? res.send(renderIndex()) : get404Sketch(html => res.send(html))
120+
));
121+
});
122+
111123
router.get('/:username/collections/:id', (req, res) => {
112124
collectionForUserExists(req.params.username, req.params.id, exists => (
113125
exists ? res.send(renderIndex()) : get404Sketch(html => res.send(html))

0 commit comments

Comments
 (0)