Add ability to delete projects from sketch list (#125)
* Add ability to delete projects from sketch list - Fixes #76 - Also gets rid of outdated Sketch module * Styling for Sketch List trash can * Merge all the stuff * Fix trash can line height
This commit is contained in:
parent
85bbe045e5
commit
76bd1b1630
13 changed files with 83 additions and 108 deletions
|
@ -41,7 +41,6 @@ This project is currently in the early stages of development! It will definitely
|
||||||
GITHUB_ID=<your-github-client-id>
|
GITHUB_ID=<your-github-client-id>
|
||||||
GITHUB_SECRET=<your-github-client-secret>
|
GITHUB_SECRET=<your-github-client-secret>
|
||||||
```
|
```
|
||||||
|
|
||||||
For production, you will need to have real Github and Amazon credentions. Refer to [this gist](https://gist.github.com/catarak/70c9301f0fd1ac2d6b58de03f61997e3) for creating an S3 bucket for testing.
|
For production, you will need to have real Github and Amazon credentions. Refer to [this gist](https://gist.github.com/catarak/70c9301f0fd1ac2d6b58de03f61997e3) for creating an S3 bucket for testing.
|
||||||
5. `$ npm run build`
|
5. `$ npm run build`
|
||||||
6. `$ npm run start:prod`
|
6. `$ npm run start:prod`
|
||||||
|
|
|
@ -35,6 +35,8 @@ export const HIDE_EDIT_PROJECT_NAME = 'HIDE_EDIT_PROJECT_NAME';
|
||||||
export const SET_PROJECT = 'SET_PROJECT';
|
export const SET_PROJECT = 'SET_PROJECT';
|
||||||
export const SET_PROJECTS = 'SET_PROJECTS';
|
export const SET_PROJECTS = 'SET_PROJECTS';
|
||||||
|
|
||||||
|
export const DELETE_PROJECT = 'DELETE_PROJECT';
|
||||||
|
|
||||||
export const SET_SELECTED_FILE = 'SET_SELECTED_FILE';
|
export const SET_SELECTED_FILE = 'SET_SELECTED_FILE';
|
||||||
export const SHOW_MODAL = 'SHOW_MODAL';
|
export const SHOW_MODAL = 'SHOW_MODAL';
|
||||||
export const HIDE_MODAL = 'HIDE_MODAL';
|
export const HIDE_MODAL = 'HIDE_MODAL';
|
||||||
|
|
8
client/images/trash-can.svg
Executable file
8
client/images/trash-can.svg
Executable file
|
@ -0,0 +1,8 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!-- Generated by IcoMoon.io -->
|
||||||
|
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||||
|
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="15" height="17" viewBox="0 0 20 20">
|
||||||
|
<g id="trash" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||||
|
<path id="trash" d="M18.984 3.984v2.016h-13.969v-2.016h3.469l1.031-0.984h4.969l1.031 0.984h3.469zM6 18.984v-12h12v12c0 1.078-0.938 2.016-2.016 2.016h-7.969c-1.078 0-2.016-0.938-2.016-2.016z"></path>
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 609 B |
|
@ -29,3 +29,15 @@ export function getProjects(username) {
|
||||||
export function closeSketchList() {
|
export function closeSketchList() {
|
||||||
browserHistory.goBack();
|
browserHistory.goBack();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function deleteProject(id) {
|
||||||
|
return (dispatch) => {
|
||||||
|
axios.delete(`${ROOT_URL}/projects/${id}`, { withCredentials: true })
|
||||||
|
.then(() => {
|
||||||
|
dispatch({
|
||||||
|
type: ActionTypes.DELETE_PROJECT,
|
||||||
|
id
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
|
@ -7,6 +7,7 @@ import * as SketchActions from '../actions/projects';
|
||||||
import * as ProjectActions from '../actions/project';
|
import * as ProjectActions from '../actions/project';
|
||||||
import InlineSVG from 'react-inlinesvg';
|
import InlineSVG from 'react-inlinesvg';
|
||||||
const exitUrl = require('../../../images/exit.svg');
|
const exitUrl = require('../../../images/exit.svg');
|
||||||
|
const trashCan = require('../../../images/trash-can.svg');
|
||||||
|
|
||||||
class SketchList extends React.Component {
|
class SketchList extends React.Component {
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
|
@ -27,6 +28,7 @@ class SketchList extends React.Component {
|
||||||
<table className="sketches-table" summary="table containing all saved projects">
|
<table className="sketches-table" summary="table containing all saved projects">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
|
<th className="sketch-list__trash-column" scope="col"></th>
|
||||||
<th scope="col">Name</th>
|
<th scope="col">Name</th>
|
||||||
<th scope="col">Created</th>
|
<th scope="col">Created</th>
|
||||||
<th scope="col">Last Updated</th>
|
<th scope="col">Last Updated</th>
|
||||||
|
@ -34,7 +36,19 @@ class SketchList extends React.Component {
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{this.props.sketches.map(sketch =>
|
{this.props.sketches.map(sketch =>
|
||||||
<tr className="sketches-table__row" key={sketch.id}>
|
<tr className="sketches-table__row visibility-toggle" key={sketch.id}>
|
||||||
|
<td>
|
||||||
|
<button
|
||||||
|
className="sketch-list__trash-button"
|
||||||
|
onClick={() => {
|
||||||
|
if (window.confirm(`Are you sure you want to delete "${sketch.name}"?`)) {
|
||||||
|
this.props.deleteProject(sketch.id);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<InlineSVG src={trashCan} alt="Delete Project" />
|
||||||
|
</button>
|
||||||
|
</td>
|
||||||
<td scope="row"><Link to={`/projects/${sketch._id}`}>{sketch.name}</Link></td>
|
<td scope="row"><Link to={`/projects/${sketch._id}`}>{sketch.name}</Link></td>
|
||||||
<td>{moment(sketch.createdAt).format('MMM D, YYYY h:mm:ss A')}</td>
|
<td>{moment(sketch.createdAt).format('MMM D, YYYY h:mm:ss A')}</td>
|
||||||
<td>{moment(sketch.updatedAt).format('MMM D, YYYY h:mm:ss A')}</td>
|
<td>{moment(sketch.updatedAt).format('MMM D, YYYY h:mm:ss A')}</td>
|
||||||
|
@ -53,7 +67,8 @@ SketchList.propTypes = {
|
||||||
getProjects: PropTypes.func.isRequired,
|
getProjects: PropTypes.func.isRequired,
|
||||||
sketches: PropTypes.array.isRequired,
|
sketches: PropTypes.array.isRequired,
|
||||||
closeSketchList: PropTypes.func.isRequired,
|
closeSketchList: PropTypes.func.isRequired,
|
||||||
username: PropTypes.string
|
username: PropTypes.string,
|
||||||
|
deleteProject: PropTypes.func.isRequired
|
||||||
};
|
};
|
||||||
|
|
||||||
function mapStateToProps(state) {
|
function mapStateToProps(state) {
|
||||||
|
|
|
@ -4,6 +4,10 @@ const sketches = (state = [], action) => {
|
||||||
switch (action.type) {
|
switch (action.type) {
|
||||||
case ActionTypes.SET_PROJECTS:
|
case ActionTypes.SET_PROJECTS:
|
||||||
return action.projects;
|
return action.projects;
|
||||||
|
case ActionTypes.DELETE_PROJECT:
|
||||||
|
return state.filter((sketch) =>
|
||||||
|
sketch.id !== action.id
|
||||||
|
);
|
||||||
default:
|
default:
|
||||||
return state;
|
return state;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,20 +0,0 @@
|
||||||
import * as ActionTypes from '../../constants';
|
|
||||||
import axios from 'axios';
|
|
||||||
|
|
||||||
const ROOT_URL = location.href.indexOf('localhost') > 0 ? 'http://localhost:8000/api' : '/api';
|
|
||||||
|
|
||||||
export function getProjects() {
|
|
||||||
return (dispatch) => {
|
|
||||||
axios.get(`${ROOT_URL}/projects`, { withCredentials: true })
|
|
||||||
.then(response => {
|
|
||||||
dispatch({
|
|
||||||
type: ActionTypes.SET_PROJECTS,
|
|
||||||
projects: response.data
|
|
||||||
});
|
|
||||||
})
|
|
||||||
.catch(response => dispatch({
|
|
||||||
type: ActionTypes.ERROR,
|
|
||||||
error: response.data
|
|
||||||
}));
|
|
||||||
};
|
|
||||||
}
|
|
|
@ -1,71 +0,0 @@
|
||||||
import React, { PropTypes } from 'react';
|
|
||||||
import { connect } from 'react-redux';
|
|
||||||
import { bindActionCreators } from 'redux';
|
|
||||||
import moment from 'moment';
|
|
||||||
import { Link } from 'react-router';
|
|
||||||
import Nav from '../../../components/Nav';
|
|
||||||
import * as SketchActions from '../actions';
|
|
||||||
import * as ProjectActions from '../../IDE/actions/project';
|
|
||||||
|
|
||||||
class SketchListView extends React.Component {
|
|
||||||
componentDidMount() {
|
|
||||||
this.props.getProjects();
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
|
||||||
return (
|
|
||||||
<div className="sketch-list">
|
|
||||||
<Nav
|
|
||||||
user={this.props.user}
|
|
||||||
newProject={this.props.newProject}
|
|
||||||
saveProject={this.props.saveProject}
|
|
||||||
exportProjectAsZip={this.props.exportProjectAsZip}
|
|
||||||
cloneProject={this.props.cloneProject}
|
|
||||||
/>
|
|
||||||
<div className="sketches-table-container">
|
|
||||||
<table className="sketches-table" summary="table containing all saved projects">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th scope="col">Name</th>
|
|
||||||
<th scope="col">Created</th>
|
|
||||||
<th scope="col">Last Updated</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
{this.props.sketches.map(sketch =>
|
|
||||||
<tr className="sketches-table__row" key={sketch.id}>
|
|
||||||
<td scope="row"><Link to={`/projects/${sketch._id}`}>{sketch.name}</Link></td>
|
|
||||||
<td>{moment(sketch.createdAt).format('MMM D, YYYY')}</td>
|
|
||||||
<td>{moment(sketch.updatedAt).format('MMM D, YYYY')}</td>
|
|
||||||
</tr>
|
|
||||||
)}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
SketchListView.propTypes = {
|
|
||||||
user: PropTypes.object.isRequired,
|
|
||||||
newProject: PropTypes.func.isRequired,
|
|
||||||
saveProject: PropTypes.func.isRequired,
|
|
||||||
getProjects: PropTypes.func.isRequired,
|
|
||||||
sketches: PropTypes.array.isRequired,
|
|
||||||
exportProjectAsZip: PropTypes.func.isRequired,
|
|
||||||
cloneProject: PropTypes.func.isRequired
|
|
||||||
};
|
|
||||||
|
|
||||||
function mapStateToProps(state) {
|
|
||||||
return {
|
|
||||||
user: state.user,
|
|
||||||
sketches: state.sketches
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function mapDispatchToProps(dispatch) {
|
|
||||||
return bindActionCreators(Object.assign({}, SketchActions, ProjectActions), dispatch);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default connect(mapStateToProps, mapDispatchToProps)(SketchListView);
|
|
|
@ -1,12 +0,0 @@
|
||||||
import * as ActionTypes from '../../constants';
|
|
||||||
|
|
||||||
const sketches = (state = [], action) => {
|
|
||||||
switch (action.type) {
|
|
||||||
case ActionTypes.SET_PROJECTS:
|
|
||||||
return action.projects;
|
|
||||||
default:
|
|
||||||
return state;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
export default sketches;
|
|
|
@ -5,7 +5,7 @@ import preferences from './modules/IDE/reducers/preferences';
|
||||||
import project from './modules/IDE/reducers/project';
|
import project from './modules/IDE/reducers/project';
|
||||||
import editorAccessibility from './modules/IDE/reducers/editorAccessibility';
|
import editorAccessibility from './modules/IDE/reducers/editorAccessibility';
|
||||||
import user from './modules/User/reducers';
|
import user from './modules/User/reducers';
|
||||||
import sketches from './modules/Sketch/reducers';
|
import sketches from './modules/IDE/reducers/projects';
|
||||||
import toast from './modules/IDE/reducers/toast';
|
import toast from './modules/IDE/reducers/toast';
|
||||||
import { reducer as form } from 'redux-form';
|
import { reducer as form } from 'redux-form';
|
||||||
|
|
||||||
|
|
|
@ -23,10 +23,15 @@
|
||||||
padding: #{10 / $base-font-size}rem 0;
|
padding: #{10 / $base-font-size}rem 0;
|
||||||
padding-left: #{20 / $base-font-size}rem;
|
padding-left: #{20 / $base-font-size}rem;
|
||||||
max-height: 100%;
|
max-height: 100%;
|
||||||
|
|
||||||
|
& .sketch-list__trash-column {
|
||||||
|
width: #{22 / $base-font-size}rem;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.sketches-table__row {
|
.sketches-table__row {
|
||||||
margin: #{10 / $base-font-size}rem;
|
margin: #{10 / $base-font-size}rem;
|
||||||
|
height: #{25 / $base-font-size}rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.sketch-list__exit-button {
|
.sketch-list__exit-button {
|
||||||
|
@ -34,3 +39,25 @@
|
||||||
@extend %icon;
|
@extend %icon;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.visibility-toggle .sketch-list__trash-button {
|
||||||
|
@extend %hidden-element;
|
||||||
|
}
|
||||||
|
|
||||||
|
.visibility-toggle:hover .sketch-list__trash-button {
|
||||||
|
@include themify() {
|
||||||
|
background-color: transparent;
|
||||||
|
border: none;
|
||||||
|
cursor: pointer;
|
||||||
|
padding: 0;
|
||||||
|
position: initial;
|
||||||
|
left: 0;
|
||||||
|
top: 0;
|
||||||
|
width:#{20 / $base-font-size}rem;
|
||||||
|
height:#{20 / $base-font-size}rem;
|
||||||
|
& g {
|
||||||
|
opacity: 1;
|
||||||
|
fill: getThemifyVariable('icon-hover-color');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -60,6 +60,15 @@ export function getProject(req, res) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function deleteProject(req, res) {
|
||||||
|
Project.remove({_id: req.params.project_id}, (err) => {
|
||||||
|
if (err) {
|
||||||
|
return res.status(404).send({ message: 'Project with that id does not exist' });
|
||||||
|
}
|
||||||
|
return res.json({ success: true });
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
export function getProjects(req, res) {
|
export function getProjects(req, res) {
|
||||||
if (req.user) {
|
if (req.user) {
|
||||||
Project.find({user: req.user._id}) // eslint-disable-line no-underscore-dangle
|
Project.find({user: req.user._id}) // eslint-disable-line no-underscore-dangle
|
||||||
|
|
|
@ -9,6 +9,8 @@ router.route('/projects/:project_id').put(ProjectController.updateProject);
|
||||||
|
|
||||||
router.route('/projects/:project_id').get(ProjectController.getProject);
|
router.route('/projects/:project_id').get(ProjectController.getProject);
|
||||||
|
|
||||||
|
router.route('/projects/:project_id').delete(ProjectController.deleteProject);
|
||||||
|
|
||||||
router.route('/projects').get(ProjectController.getProjects);
|
router.route('/projects').get(ProjectController.getProjects);
|
||||||
|
|
||||||
router.route('/:username/projects').get(ProjectController.getProjectsForUser);
|
router.route('/:username/projects').get(ProjectController.getProjectsForUser);
|
||||||
|
|
Loading…
Reference in a new issue