diff --git a/client/constants.js b/client/constants.js index 145819a3..ce5960c3 100644 --- a/client/constants.js +++ b/client/constants.js @@ -30,6 +30,8 @@ export const PROJECT_SAVE_SUCCESS = 'PROJECT_SAVE_SUCCESS'; export const PROJECT_SAVE_FAIL = 'PROJECT_SAVE_FAIL'; export const NEW_PROJECT = 'NEW_PROJECT'; export const RESET_PROJECT = 'RESET_PROJECT'; +export const SHOW_EDIT_PROJECT_NAME = 'SHOW_EDIT_PROJECT_NAME'; +export const HIDE_EDIT_PROJECT_NAME = 'HIDE_EDIT_PROJECT_NAME'; export const SET_PROJECT = 'SET_PROJECT'; export const SET_PROJECTS = 'SET_PROJECTS'; diff --git a/client/modules/App/components/Overlay.js b/client/modules/App/components/Overlay.js new file mode 100644 index 00000000..31a451c4 --- /dev/null +++ b/client/modules/App/components/Overlay.js @@ -0,0 +1,17 @@ +import React, { PropTypes } from 'react'; + +function Overlay(props) { + return ( +
+
+ {props.children} +
+
+ ); +} + +Overlay.propTypes = { + children: PropTypes.object +}; + +export default Overlay; diff --git a/client/modules/IDE/actions/project.js b/client/modules/IDE/actions/project.js index d541a49f..926b88b9 100644 --- a/client/modules/IDE/actions/project.js +++ b/client/modules/IDE/actions/project.js @@ -41,8 +41,7 @@ export function getProject(id) { }; } -export function setProjectName(event) { - const name = event.target.textContent; +export function setProjectName(name) { return { type: ActionTypes.SET_PROJECT_NAME, name @@ -171,3 +170,15 @@ export function cloneProject() { }; } +export function showEditProjectName() { + return { + type: ActionTypes.SHOW_EDIT_PROJECT_NAME + }; +} + +export function hideEditProjectName() { + return { + type: ActionTypes.HIDE_EDIT_PROJECT_NAME + }; +} + diff --git a/client/modules/IDE/actions/projects.js b/client/modules/IDE/actions/projects.js new file mode 100644 index 00000000..d1898d77 --- /dev/null +++ b/client/modules/IDE/actions/projects.js @@ -0,0 +1,25 @@ +import * as ActionTypes from '../../../constants'; +import { browserHistory } from 'react-router'; +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 + })); + }; +} + +export function closeSketchList() { + browserHistory.push('/'); +} diff --git a/client/modules/IDE/components/SidebarItem.js b/client/modules/IDE/components/SidebarItem.js index 4d58377a..f68e0706 100644 --- a/client/modules/IDE/components/SidebarItem.js +++ b/client/modules/IDE/components/SidebarItem.js @@ -16,7 +16,6 @@ class SidebarItem extends React.Component { } handleKeyPress(event) { - console.log(event.key); if (event.key === 'Enter') { this.props.hideEditFileName(this.props.file.id); } diff --git a/client/modules/IDE/components/SketchList.js b/client/modules/IDE/components/SketchList.js new file mode 100644 index 00000000..5d2a759e --- /dev/null +++ b/client/modules/IDE/components/SketchList.js @@ -0,0 +1,68 @@ +import React, { PropTypes } from 'react'; +import { connect } from 'react-redux'; +import { bindActionCreators } from 'redux'; +import moment from 'moment'; +import { Link } from 'react-router'; +import * as SketchActions from '../actions/projects'; +import * as ProjectActions from '../actions/project'; +import InlineSVG from 'react-inlinesvg'; +const exitUrl = require('../../../images/exit.svg'); + +class SketchList extends React.Component { + componentDidMount() { + this.props.getProjects(); + } + + render() { + return ( +
+
+

Sketches

+ +
+
+ + + + + + + + + + {this.props.sketches.map(sketch => + + + + + + )} + +
NameCreatedLast Updated
{sketch.name}{moment(sketch.createdAt).format('MMM D, YYYY h:mm:ss A')}{moment(sketch.updatedAt).format('MMM D, YYYY h:mm:ss A')}
+
+
+ ); + } +} + +SketchList.propTypes = { + user: PropTypes.object.isRequired, + getProjects: PropTypes.func.isRequired, + sketches: PropTypes.array.isRequired, + closeSketchList: 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)(SketchList); diff --git a/client/modules/IDE/components/TextOutput.js b/client/modules/IDE/components/TextOutput.js index 9efee01d..949215d6 100644 --- a/client/modules/IDE/components/TextOutput.js +++ b/client/modules/IDE/components/TextOutput.js @@ -10,7 +10,6 @@ class TextOutput extends React.Component { className="text-output" id="canvas-sub" tabIndex="0" - role="main" aria-label="text-output" title="canvas text output" > @@ -18,23 +17,17 @@ class TextOutput extends React.Component {

-

-

-
); diff --git a/client/modules/IDE/components/Toolbar.js b/client/modules/IDE/components/Toolbar.js index 67400db8..0bdde6b0 100644 --- a/client/modules/IDE/components/Toolbar.js +++ b/client/modules/IDE/components/Toolbar.js @@ -6,67 +6,106 @@ const stopUrl = require('../../../images/stop.svg'); const preferencesUrl = require('../../../images/preferences.svg'); import classNames from 'classnames'; -function Toolbar(props) { - let playButtonClass = classNames({ - 'toolbar__play-button': true, - 'toolbar__play-button--selected': props.isPlaying - }); - let stopButtonClass = classNames({ - 'toolbar__stop-button': true, - 'toolbar__stop-button--selected': !props.isPlaying - }); - let preferencesButtonClass = classNames({ - 'toolbar__preferences-button': true, - 'toolbar__preferences-button--selected': props.preferencesIsVisible - }); +class Toolbar extends React.Component { + constructor(props) { + super(props); + this.handleKeyPress = this.handleKeyPress.bind(this); + this.handleProjectNameChange = this.handleProjectNameChange.bind(this); + } - return ( -
- p5js Logo - - - -
- + p5js Logo + + + +
+ { + this.originalProjectName = this.props.project.name; + this.props.showEditProjectName(); + setTimeout(() => this.refs.projectNameInput.focus(), 0); + }} + >{this.props.project.name} + { + this.validateProjectName(); + this.props.hideEditProjectName(); + }} + onKeyPress={this.handleKeyPress} + /> + {(() => { // eslint-disable-line + if (this.props.owner) { + return ( +

by {this.props.owner.username}

+ ); + } + })()} +
+
- -
- ); + ); + } } Toolbar.propTypes = { @@ -77,11 +116,16 @@ Toolbar.propTypes = { startTextOutput: PropTypes.func.isRequired, stopTextOutput: PropTypes.func.isRequired, setProjectName: PropTypes.func.isRequired, - projectName: PropTypes.string.isRequired, openPreferences: PropTypes.func.isRequired, owner: PropTypes.shape({ username: PropTypes.string - }) + }), + project: PropTypes.shape({ + name: PropTypes.string.isRequired, + isEditingName: PropTypes.bool + }).isRequired, + showEditProjectName: PropTypes.func.isRequired, + hideEditProjectName: PropTypes.func.isRequired }; export default Toolbar; diff --git a/client/modules/IDE/pages/IDEView.js b/client/modules/IDE/pages/IDEView.js index 49489b4b..a9a6d93d 100644 --- a/client/modules/IDE/pages/IDEView.js +++ b/client/modules/IDE/pages/IDEView.js @@ -17,9 +17,12 @@ import * as EditorAccessibilityActions from '../actions/editorAccessibility'; import * as PreferencesActions from '../actions/preferences'; import { getFile, getHTMLFile, getJSFiles, getCSSFiles, setSelectedFile } from '../reducers/files'; import SplitPane from 'react-split-pane'; +import Overlay from '../../App/components/Overlay'; +import SketchList from '../components/SketchList'; class IDEView extends React.Component { constructor(props) { + console.log(props); super(props); this._handleConsolePaneOnDragFinished = this._handleConsolePaneOnDragFinished.bind(this); this._handleSidebarPaneOnDragFinished = this._handleSidebarPaneOnDragFinished.bind(this); @@ -51,6 +54,10 @@ class IDEView extends React.Component { if (this.props.ide.sidebarIsExpanded !== nextProps.ide.sidebarIsExpanded) { this.sidebarSize = nextProps.ide.sidebarIsExpanded ? 180 : 20; } + + if (nextProps.params.project_id && !this.props.params.project_id) { + this.props.getProject(nextProps.params.project_id); + } } componentDidUpdate(prevProps) { @@ -118,10 +125,13 @@ class IDEView extends React.Component { stopTextOutput={this.props.stopTextOutput} projectName={this.props.project.name} setProjectName={this.props.setProjectName} + showEditProjectName={this.props.showEditProjectName} + hideEditProjectName={this.props.hideEditProjectName} openPreferences={this.props.openPreferences} preferencesIsVisible={this.props.ide.preferencesIsVisible} setTextOutput={this.props.setTextOutput} owner={this.props.project.owner} + project={this.props.project} /> { // eslint-disable-line + if (this.props.location.pathname === '/sketches') { + return ( + + + + ); + } + })()} ); @@ -253,6 +272,9 @@ IDEView.propTypes = { params: PropTypes.shape({ project_id: PropTypes.string }), + location: PropTypes.shape({ + pathname: PropTypes.string + }), getProject: PropTypes.func.isRequired, user: PropTypes.shape({ authenticated: PropTypes.bool.isRequired, @@ -330,7 +352,9 @@ IDEView.propTypes = { deleteFile: PropTypes.func.isRequired, showEditFileName: PropTypes.func.isRequired, hideEditFileName: PropTypes.func.isRequired, - updateFileName: PropTypes.func.isRequired + updateFileName: PropTypes.func.isRequired, + showEditProjectName: PropTypes.func.isRequired, + hideEditProjectName: PropTypes.func.isRequired }; function mapStateToProps(state) { diff --git a/client/modules/IDE/reducers/project.js b/client/modules/IDE/reducers/project.js index d50e1485..efca305b 100644 --- a/client/modules/IDE/reducers/project.js +++ b/client/modules/IDE/reducers/project.js @@ -22,6 +22,10 @@ const project = (state = initialState, action) => { }; case ActionTypes.RESET_PROJECT: return initialState; + case ActionTypes.SHOW_EDIT_PROJECT_NAME: + return Object.assign({}, state, { isEditingName: true }); + case ActionTypes.HIDE_EDIT_PROJECT_NAME: + return Object.assign({}, state, { isEditingName: false }); default: return state; } diff --git a/client/modules/IDE/reducers/projects.js b/client/modules/IDE/reducers/projects.js new file mode 100644 index 00000000..b973bccc --- /dev/null +++ b/client/modules/IDE/reducers/projects.js @@ -0,0 +1,12 @@ +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; diff --git a/client/modules/Sketch/pages/SketchListView.js b/client/modules/Sketch/pages/SketchListView.js index e46cab23..1f775429 100644 --- a/client/modules/Sketch/pages/SketchListView.js +++ b/client/modules/Sketch/pages/SketchListView.js @@ -22,24 +22,26 @@ class SketchListView extends React.Component { exportProjectAsZip={this.props.exportProjectAsZip} cloneProject={this.props.cloneProject} /> - - - - - - - - - - {this.props.sketches.map(sketch => - - - - +
+
NameCreatedLast Updated
{sketch.name}{moment(sketch.createdAt).format('MMM D, YYYY')}{moment(sketch.updatedAt).format('MMM D, YYYY')}
+ + + + + - )} - -
NameCreatedLast Updated
+ + + {this.props.sketches.map(sketch => + + {sketch.name} + {moment(sketch.createdAt).format('MMM D, YYYY')} + {moment(sketch.updatedAt).format('MMM D, YYYY')} + + )} + + + ); } diff --git a/client/routes.js b/client/routes.js index e071eb7f..356a27da 100644 --- a/client/routes.js +++ b/client/routes.js @@ -4,7 +4,7 @@ import App from './modules/App/App'; import IDEView from './modules/IDE/pages/IDEView'; import LoginView from './modules/User/pages/LoginView'; import SignupView from './modules/User/pages/SignupView'; -import SketchListView from './modules/Sketch/pages/SketchListView'; +// import SketchListView from './modules/Sketch/pages/SketchListView'; import { getUser } from './modules/User/actions'; const checkAuth = (store) => { @@ -18,7 +18,7 @@ const routes = (store) => - + ); diff --git a/client/styles/components/_overlay.scss b/client/styles/components/_overlay.scss new file mode 100644 index 00000000..3ece1be1 --- /dev/null +++ b/client/styles/components/_overlay.scss @@ -0,0 +1,17 @@ +.overlay { + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + z-index: 9999; + background-color: rgba(0, 0, 0, 0.1); +} + +.overlay-content { + height: 100%; + width: 100%; + display: flex; + justify-content: center; + align-items: center; +} \ No newline at end of file diff --git a/client/styles/components/_sketch-list.scss b/client/styles/components/_sketch-list.scss index 9ef6a786..4780b12a 100644 --- a/client/styles/components/_sketch-list.scss +++ b/client/styles/components/_sketch-list.scss @@ -1,9 +1,25 @@ +.sketch-list__header { + display: flex; + justify-content: space-between; + padding: #{20 / $base-font-size}rem; +} + +.sketches-table-container { + flex: 1 0 0%; + overflow-y: scroll; +} + .sketches-table { width: 100%; padding: #{10 / $base-font-size}rem 0; - padding-left: #{170 / $base-font-size}rem; + padding-left: #{20 / $base-font-size}rem; + height: 100%; } .sketches-table__row { margin: #{10 / $base-font-size}rem; +} + +.sketch-list__exit-button { + @extend %icon; } \ No newline at end of file diff --git a/client/styles/components/_toolbar.scss b/client/styles/components/_toolbar.scss index 2855509d..00f9e33d 100644 --- a/client/styles/components/_toolbar.scss +++ b/client/styles/components/_toolbar.scss @@ -57,6 +57,18 @@ &:focus { color: $light-inactive-text-color; } + + .toolbar__project-name-container--editing & { + display: none; + } +} + +.toolbar__project-name-input { + display: none; + border: 0px; + .toolbar__project-name-container--editing & { + display: block; + } } .toolbar__project-owner { diff --git a/client/styles/layout/_sketch-list.scss b/client/styles/layout/_sketch-list.scss index 979c48d4..7587262d 100644 --- a/client/styles/layout/_sketch-list.scss +++ b/client/styles/layout/_sketch-list.scss @@ -1,6 +1,8 @@ .sketch-list { + @extend %modal; display: flex; flex-wrap: wrap; - height: 100%; flex-flow: column; -} \ No newline at end of file + width: #{1000 / $base-font-size}rem; + height: #{700 / $base-font-size}rem; +} diff --git a/client/styles/main.scss b/client/styles/main.scss index 7a565c25..3b3a79a3 100644 --- a/client/styles/main.scss +++ b/client/styles/main.scss @@ -20,6 +20,7 @@ @import 'components/modal'; @import 'components/console'; @import 'components/resizer'; +@import 'components/overlay'; @import 'layout/ide'; @import 'layout/sketch-list'; diff --git a/static/interceptor-functions.js b/static/interceptor-functions.js index b20330a0..3c4d52fe 100644 --- a/static/interceptor-functions.js +++ b/static/interceptor-functions.js @@ -188,7 +188,7 @@ var Interceptor = { if(!(typeof(arguments[i])).localeCompare('number')){ arguments[i] = round(arguments[i]); } - objectArray[objectCount][x.params[i].description]=arguments[i]; + objectArray[objectCount][x.params[i].description.slice(3,-5)]=arguments[i]; } if(objectTypeCount[x.name]) { objectTypeCount[x.name]++; @@ -215,7 +215,7 @@ var Interceptor = { var tempCol = row.children.length; var properties = Object.keys(objectArray[j]); - if(tempCol