add new folder modal
This commit is contained in:
parent
7f5c970b6c
commit
9d3bcf2a15
14 changed files with 291 additions and 70 deletions
|
@ -65,5 +65,10 @@ export const SET_LINT_WARNING = 'SET_LINT_WARNING';
|
|||
export const SET_PREFERENCES = 'SET_PREFERENCES';
|
||||
export const SET_TEXT_OUTPUT = 'SET_TEXT_OUTPUT';
|
||||
|
||||
export const OPEN_PROJECT_OPTIONS = 'OPEN_PROJECT_OPTIONS';
|
||||
export const CLOSE_PROJECT_OPTIONS = 'CLOSE_PROJECT_OPTIONS';
|
||||
export const SHOW_NEW_FOLDER_MODAL = 'SHOW_NEW_FOLDER_MODAL';
|
||||
export const CLOSE_NEW_FOLDER_MODAL = 'CLOSE_NEW_FOLDER_MODAL';
|
||||
|
||||
// eventually, handle errors more specifically and better
|
||||
export const ERROR = 'ERROR';
|
||||
|
|
|
@ -118,6 +118,51 @@ export function createFile(formProps) {
|
|||
};
|
||||
}
|
||||
|
||||
export function createFolder(formProps) {
|
||||
return (dispatch, getState) => {
|
||||
const state = getState();
|
||||
const rootFile = state.files.filter(file => file.name === 'root')[0];
|
||||
if (state.project.id) {
|
||||
const postParams = {
|
||||
name: createUniqueName(formProps.name, state.files),
|
||||
content: '',
|
||||
parentId: rootFile.id,
|
||||
fileType: 'folder'
|
||||
};
|
||||
axios.post(`${ROOT_URL}/projects/${state.project.id}/files`, postParams, { withCredentials: true })
|
||||
.then(response => {
|
||||
dispatch({
|
||||
type: ActionTypes.CREATE_FILE,
|
||||
...response.data,
|
||||
parentId: rootFile.id
|
||||
});
|
||||
dispatch({
|
||||
type: ActionTypes.CLOSE_NEW_FOLDER_MODAL
|
||||
});
|
||||
})
|
||||
.catch(response => dispatch({
|
||||
type: ActionTypes.ERROR,
|
||||
error: response.data
|
||||
}));
|
||||
} else {
|
||||
const id = objectID().toHexString();
|
||||
dispatch({
|
||||
type: ActionTypes.CREATE_FILE,
|
||||
name: createUniqueName(formProps.name, state.files),
|
||||
id,
|
||||
_id: id,
|
||||
content: '',
|
||||
// TODO pass parent id from File Tree
|
||||
parentId: rootFile.id,
|
||||
fileType: 'folder'
|
||||
});
|
||||
dispatch({
|
||||
type: ActionTypes.CLOSE_NEW_FOLDER_MODAL
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export function showFileOptions(fileId) {
|
||||
return {
|
||||
type: ActionTypes.SHOW_FILE_OPTIONS,
|
||||
|
|
|
@ -102,3 +102,27 @@ export function closePreferences() {
|
|||
type: ActionTypes.CLOSE_PREFERENCES
|
||||
};
|
||||
}
|
||||
|
||||
export function openProjectOptions() {
|
||||
return {
|
||||
type: ActionTypes.OPEN_PROJECT_OPTIONS
|
||||
};
|
||||
}
|
||||
|
||||
export function closeProjectOptions() {
|
||||
return {
|
||||
type: ActionTypes.CLOSE_PROJECT_OPTIONS
|
||||
};
|
||||
}
|
||||
|
||||
export function newFolder() {
|
||||
return {
|
||||
type: ActionTypes.SHOW_NEW_FOLDER_MODAL
|
||||
};
|
||||
}
|
||||
|
||||
export function closeNewFolderModal() {
|
||||
return {
|
||||
type: ActionTypes.CLOSE_NEW_FOLDER_MODAL
|
||||
};
|
||||
}
|
||||
|
|
|
@ -65,14 +65,14 @@ export class FileNode extends React.Component {
|
|||
<div
|
||||
className={itemClass}
|
||||
onClick={this.handleFileClick}
|
||||
onBlur={() => setTimeout(() => this.props.hideFileOptions(this.props.id), 100)}
|
||||
onBlur={() => setTimeout(() => this.props.hideFileOptions(this.props.id), 200)}
|
||||
>
|
||||
{(() => { // eslint-disable-line
|
||||
if (this.props.name !== 'root') {
|
||||
return (
|
||||
<div className="file-item__content">
|
||||
{(() => { // eslint-disable-line
|
||||
if (this.props.type === 'file') {
|
||||
if (this.props.fileType === 'file') {
|
||||
return (
|
||||
<span className="sidebar__file-item-icon">
|
||||
<InlineSVG src={fileUrl} />
|
||||
|
@ -151,7 +151,7 @@ FileNode.propTypes = {
|
|||
parentId: PropTypes.string,
|
||||
children: PropTypes.array,
|
||||
name: PropTypes.string.isRequired,
|
||||
type: PropTypes.string.isRequired,
|
||||
fileType: PropTypes.string.isRequired,
|
||||
isSelected: PropTypes.bool,
|
||||
isOptionsOpen: PropTypes.bool,
|
||||
isEditingName: PropTypes.bool,
|
||||
|
|
|
@ -1,20 +1,32 @@
|
|||
import React, { PropTypes } from 'react';
|
||||
|
||||
function NewFileForm(props) {
|
||||
const { fields: { name }, handleSubmit } = props;
|
||||
return (
|
||||
<form className="new-file-form" onSubmit={handleSubmit(props.createFile.bind(this))}>
|
||||
<label className="new-file-form__name-label" htmlFor="name">Name:</label>
|
||||
<input
|
||||
className="new-file-form__name-input"
|
||||
id="name"
|
||||
type="text"
|
||||
placeholder="Name"
|
||||
{...name}
|
||||
/>
|
||||
<input type="submit" value="Add File" aria-label="add file" />
|
||||
</form>
|
||||
);
|
||||
class NewFileForm extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.createFile = this.props.createFile.bind(this);
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.refs.fileName.focus();
|
||||
}
|
||||
|
||||
render() {
|
||||
const { fields: { name }, handleSubmit } = this.props;
|
||||
return (
|
||||
<form className="new-file-form" onSubmit={handleSubmit(this.createFile)}>
|
||||
<label className="new-file-form__name-label" htmlFor="name">Name:</label>
|
||||
<input
|
||||
className="new-file-form__name-input"
|
||||
id="name"
|
||||
type="text"
|
||||
placeholder="Name"
|
||||
ref="fileName"
|
||||
{...name}
|
||||
/>
|
||||
<input type="submit" value="Add File" aria-label="add file" />
|
||||
</form>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
NewFileForm.propTypes = {
|
||||
|
|
|
@ -12,40 +12,35 @@ import FileUploader from './FileUploader';
|
|||
// At some point this will probably be generalized to a generic modal
|
||||
// in which you can insert different content
|
||||
// but for now, let's just make this work
|
||||
class NewFileModal extends React.Component {
|
||||
componentDidMount() {
|
||||
document.getElementById('name').focus();
|
||||
}
|
||||
render() {
|
||||
const modalClass = classNames({
|
||||
modal: true,
|
||||
'modal--reduced': !this.props.canUploadMedia
|
||||
});
|
||||
return (
|
||||
<section className={modalClass}>
|
||||
<div className="modal-content">
|
||||
<div className="modal__header">
|
||||
<h2 className="modal__title">Add File</h2>
|
||||
<button className="modal__exit-button" onClick={this.props.closeModal}>
|
||||
<InlineSVG src={exitUrl} alt="Close New File Modal" />
|
||||
</button>
|
||||
</div>
|
||||
<NewFileForm {...this.props} />
|
||||
{(() => {
|
||||
if (this.props.canUploadMedia) {
|
||||
return (
|
||||
<div>
|
||||
<p className="modal__divider">OR</p>
|
||||
<FileUploader />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
return '';
|
||||
})()}
|
||||
function NewFileModal(props) {
|
||||
const modalClass = classNames({
|
||||
modal: true,
|
||||
'modal--reduced': !props.canUploadMedia
|
||||
});
|
||||
return (
|
||||
<section className={modalClass}>
|
||||
<div className="modal-content">
|
||||
<div className="modal__header">
|
||||
<h2 className="modal__title">Add File</h2>
|
||||
<button className="modal__exit-button" onClick={props.closeModal}>
|
||||
<InlineSVG src={exitUrl} alt="Close New File Modal" />
|
||||
</button>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
<NewFileForm {...props} />
|
||||
{(() => {
|
||||
if (props.canUploadMedia) {
|
||||
return (
|
||||
<div>
|
||||
<p className="modal__divider">OR</p>
|
||||
<FileUploader />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
return '';
|
||||
})()}
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
NewFileModal.propTypes = {
|
||||
|
@ -53,10 +48,8 @@ NewFileModal.propTypes = {
|
|||
canUploadMedia: PropTypes.bool.isRequired
|
||||
};
|
||||
|
||||
function mapStateToProps(state) {
|
||||
return {
|
||||
file: state.files
|
||||
};
|
||||
function mapStateToProps() {
|
||||
return {};
|
||||
}
|
||||
|
||||
function mapDispatchToProps(dispatch) {
|
||||
|
|
36
client/modules/IDE/components/NewFolderForm.js
Normal file
36
client/modules/IDE/components/NewFolderForm.js
Normal file
|
@ -0,0 +1,36 @@
|
|||
import React, { PropTypes } from 'react';
|
||||
|
||||
class NewFolderForm extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.createFolder = this.props.createFolder.bind(this);
|
||||
}
|
||||
|
||||
render() {
|
||||
const { fields: { name }, handleSubmit } = this.props;
|
||||
return (
|
||||
<form className="new-folder-form" onSubmit={handleSubmit(this.createFolder)}>
|
||||
<label className="new-folder-form__name-label" htmlFor="name">Name:</label>
|
||||
<input
|
||||
className="new-folder-form__name-input"
|
||||
id="name"
|
||||
type="text"
|
||||
placeholder="Name"
|
||||
ref="fileName"
|
||||
{...name}
|
||||
/>
|
||||
<input type="submit" value="Add Folder" aria-label="add folder" />
|
||||
</form>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
NewFolderForm.propTypes = {
|
||||
fields: PropTypes.shape({
|
||||
name: PropTypes.string.isRequired
|
||||
}).isRequired,
|
||||
handleSubmit: PropTypes.func.isRequired,
|
||||
createFolder: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
export default NewFolderForm;
|
31
client/modules/IDE/components/NewFolderModal.js
Normal file
31
client/modules/IDE/components/NewFolderModal.js
Normal file
|
@ -0,0 +1,31 @@
|
|||
import React, { PropTypes } from 'react';
|
||||
import { reduxForm } from 'redux-form';
|
||||
import InlineSVG from 'react-inlinesvg';
|
||||
const exitUrl = require('../../../images/exit.svg');
|
||||
import NewFolderForm from './NewFolderForm';
|
||||
|
||||
function NewFolderModal(props) {
|
||||
return (
|
||||
<section className="modal">
|
||||
<div className="modal-content-folder">
|
||||
<div className="modal__header">
|
||||
<h2 className="modal__title">Add Folder</h2>
|
||||
<button className="modal__exit-button" onClick={props.closeModal}>
|
||||
<InlineSVG src={exitUrl} alt="Close New Folder Modal" />
|
||||
</button>
|
||||
</div>
|
||||
<NewFolderForm {...props} />
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
NewFolderModal.propTypes = {
|
||||
closeModal: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
export default reduxForm({
|
||||
form: 'new-folder',
|
||||
fields: ['name']
|
||||
})(NewFolderModal);
|
||||
|
|
@ -21,7 +21,8 @@ class Sidebar extends React.Component {
|
|||
render() {
|
||||
const sidebarClass = classNames({
|
||||
sidebar: true,
|
||||
'sidebar--contracted': !this.props.isExpanded
|
||||
'sidebar--contracted': !this.props.isExpanded,
|
||||
'sidebar--project-options': this.props.projectOptionsVisible
|
||||
});
|
||||
|
||||
return (
|
||||
|
@ -35,12 +36,25 @@ class Sidebar extends React.Component {
|
|||
</h3>
|
||||
<div className="sidebar__icons">
|
||||
<button
|
||||
aria-label="add file"
|
||||
aria-label="add file or folder"
|
||||
className="sidebar__add"
|
||||
onClick={this.props.newFile}
|
||||
onClick={this.props.openProjectOptions}
|
||||
onBlur={() => setTimeout(this.props.closeProjectOptions, 200)}
|
||||
>
|
||||
<InlineSVG src={downArrowUrl} />
|
||||
</button>
|
||||
<ul className="sidebar__project-options">
|
||||
<li>
|
||||
<a onClick={this.props.newFolder} >
|
||||
Add Folder
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a onClick={this.props.newFile} >
|
||||
Add File
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
<button
|
||||
aria-label="collapse file navigation"
|
||||
className="sidebar__contract"
|
||||
|
@ -84,6 +98,7 @@ Sidebar.propTypes = {
|
|||
files: PropTypes.array.isRequired,
|
||||
setSelectedFile: PropTypes.func.isRequired,
|
||||
isExpanded: PropTypes.bool.isRequired,
|
||||
projectOptionsVisible: PropTypes.bool.isRequired,
|
||||
newFile: PropTypes.func.isRequired,
|
||||
collapseSidebar: PropTypes.func.isRequired,
|
||||
expandSidebar: PropTypes.func.isRequired,
|
||||
|
@ -92,7 +107,10 @@ Sidebar.propTypes = {
|
|||
deleteFile: PropTypes.func.isRequired,
|
||||
showEditFileName: PropTypes.func.isRequired,
|
||||
hideEditFileName: PropTypes.func.isRequired,
|
||||
updateFileName: PropTypes.func.isRequired
|
||||
updateFileName: PropTypes.func.isRequired,
|
||||
openProjectOptions: PropTypes.func.isRequired,
|
||||
closeProjectOptions: PropTypes.func.isRequired,
|
||||
newFolder: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
export default Sidebar;
|
||||
|
|
|
@ -6,6 +6,7 @@ import Toolbar from '../components/Toolbar';
|
|||
import TextOutput from '../components/TextOutput';
|
||||
import Preferences from '../components/Preferences';
|
||||
import NewFileModal from '../components/NewFileModal';
|
||||
import NewFolderModal from '../components/NewFolderModal';
|
||||
import Nav from '../../../components/Nav';
|
||||
import Console from '../components/Console';
|
||||
import { bindActionCreators } from 'redux';
|
||||
|
@ -177,6 +178,10 @@ class IDEView extends React.Component {
|
|||
showEditFileName={this.props.showEditFileName}
|
||||
hideEditFileName={this.props.hideEditFileName}
|
||||
updateFileName={this.props.updateFileName}
|
||||
projectOptionsVisible={this.props.ide.projectOptionsVisible}
|
||||
openProjectOptions={this.props.openProjectOptions}
|
||||
closeProjectOptions={this.props.closeProjectOptions}
|
||||
newFolder={this.props.newFolder}
|
||||
/>
|
||||
<SplitPane
|
||||
split="vertical"
|
||||
|
@ -258,6 +263,17 @@ class IDEView extends React.Component {
|
|||
}
|
||||
return '';
|
||||
})()}
|
||||
{(() => {
|
||||
if (this.props.ide.newFolderModalVisible) {
|
||||
return (
|
||||
<NewFolderModal
|
||||
closeModal={this.props.closeNewFolderModal}
|
||||
createFolder={this.props.createFolder}
|
||||
/>
|
||||
);
|
||||
}
|
||||
return '';
|
||||
})()}
|
||||
{(() => { // eslint-disable-line
|
||||
if (this.props.location.pathname.match(/sketches$/)) {
|
||||
return (
|
||||
|
@ -304,7 +320,9 @@ IDEView.propTypes = {
|
|||
modalIsVisible: PropTypes.bool.isRequired,
|
||||
sidebarIsExpanded: PropTypes.bool.isRequired,
|
||||
consoleIsExpanded: PropTypes.bool.isRequired,
|
||||
preferencesIsVisible: PropTypes.bool.isRequired
|
||||
preferencesIsVisible: PropTypes.bool.isRequired,
|
||||
projectOptionsVisible: PropTypes.bool.isRequired,
|
||||
newFolderModalVisible: PropTypes.bool.isRequired
|
||||
}).isRequired,
|
||||
startSketch: PropTypes.func.isRequired,
|
||||
stopSketch: PropTypes.func.isRequired,
|
||||
|
@ -370,7 +388,12 @@ IDEView.propTypes = {
|
|||
updateFileName: PropTypes.func.isRequired,
|
||||
showEditProjectName: PropTypes.func.isRequired,
|
||||
hideEditProjectName: PropTypes.func.isRequired,
|
||||
logoutUser: PropTypes.func.isRequired
|
||||
logoutUser: PropTypes.func.isRequired,
|
||||
openProjectOptions: PropTypes.func.isRequired,
|
||||
closeProjectOptions: PropTypes.func.isRequired,
|
||||
newFolder: PropTypes.func.isRequired,
|
||||
closeNewFolderModal: PropTypes.func.isRequired,
|
||||
createFolder: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
function mapStateToProps(state) {
|
||||
|
|
|
@ -43,7 +43,7 @@ function initialState() {
|
|||
id: r,
|
||||
_id: r,
|
||||
children: [a, b, c],
|
||||
type: 'folder'
|
||||
fileType: 'folder'
|
||||
},
|
||||
{
|
||||
name: 'sketch.js',
|
||||
|
@ -51,21 +51,21 @@ function initialState() {
|
|||
id: a,
|
||||
_id: a,
|
||||
isSelected: true,
|
||||
type: 'file'
|
||||
fileType: 'file'
|
||||
},
|
||||
{
|
||||
name: 'index.html',
|
||||
content: defaultHTML,
|
||||
id: b,
|
||||
_id: b,
|
||||
type: 'file'
|
||||
fileType: 'file'
|
||||
},
|
||||
{
|
||||
name: 'style.css',
|
||||
content: defaultCSS,
|
||||
id: c,
|
||||
_id: c,
|
||||
type: 'file'
|
||||
fileType: 'file'
|
||||
}];
|
||||
}
|
||||
|
||||
|
@ -105,7 +105,13 @@ const files = (state, action) => {
|
|||
}
|
||||
return file;
|
||||
});
|
||||
return [...newState, { name: action.name, id: action.id, _id: action._id, content: action.content, url: action.url, type: 'file' }];
|
||||
return [...newState,
|
||||
{ name: action.name,
|
||||
id: action.id,
|
||||
_id: action._id,
|
||||
content: action.content,
|
||||
url: action.url,
|
||||
fileType: action.fileType || 'file' }];
|
||||
}
|
||||
case ActionTypes.SHOW_FILE_OPTIONS:
|
||||
return state.map(file => {
|
||||
|
|
|
@ -10,7 +10,9 @@ const initialState = {
|
|||
modalIsVisible: false,
|
||||
sidebarIsExpanded: true,
|
||||
consoleIsExpanded: false,
|
||||
preferencesIsVisible: false
|
||||
preferencesIsVisible: false,
|
||||
projectOptionsVisible: false,
|
||||
newFolderModalVisible: false
|
||||
};
|
||||
|
||||
const ide = (state = initialState, action) => {
|
||||
|
@ -45,6 +47,14 @@ const ide = (state = initialState, action) => {
|
|||
return Object.assign({}, state, { preferencesIsVisible: false });
|
||||
case ActionTypes.RESET_PROJECT:
|
||||
return initialState;
|
||||
case ActionTypes.OPEN_PROJECT_OPTIONS:
|
||||
return Object.assign({}, state, { projectOptionsVisible: true });
|
||||
case ActionTypes.CLOSE_PROJECT_OPTIONS:
|
||||
return Object.assign({}, state, { projectOptionsVisible: false });
|
||||
case ActionTypes.SHOW_NEW_FOLDER_MODAL:
|
||||
return Object.assign({}, state, { newFolderModalVisible: true });
|
||||
case ActionTypes.CLOSE_NEW_FOLDER_MODAL:
|
||||
return Object.assign({}, state, { newFolderModalVisible: false });
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
|
|
|
@ -15,6 +15,11 @@
|
|||
}
|
||||
}
|
||||
|
||||
.modal-content-folder {
|
||||
@extend .modal-content;
|
||||
height: #{150 / $base-font-size}rem;
|
||||
}
|
||||
|
||||
.modal__exit-button {
|
||||
@extend %icon;
|
||||
}
|
||||
|
@ -25,11 +30,11 @@
|
|||
margin-bottom: #{20 / $base-font-size}rem;
|
||||
}
|
||||
|
||||
.new-file-form__name-label {
|
||||
display: none;
|
||||
.new-file-form__name-label, .new-folder-form__name-label {
|
||||
@extend %hidden-element;
|
||||
}
|
||||
|
||||
.new-file-form__name-input {
|
||||
.new-file-form__name-input, .new-folder-form__name-input {
|
||||
margin-right: #{10 / $base-font-size}rem;
|
||||
}
|
||||
|
||||
|
|
|
@ -126,6 +126,7 @@
|
|||
.sidebar__icons {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.sidebar__folder-icon {
|
||||
|
@ -138,3 +139,15 @@
|
|||
.sidebar__file-item-icon {
|
||||
margin-right: #{5 / $base-font-size}rem;
|
||||
}
|
||||
|
||||
.sidebar__project-options {
|
||||
@extend %modal;
|
||||
display: none;
|
||||
position: absolute;
|
||||
.sidebar--project-options & {
|
||||
display: block;
|
||||
}
|
||||
top: 100%;
|
||||
padding: #{8 / $base-font-size}rem;
|
||||
width: #{110 / $base-font-size}rem;
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue