Skip to content
Merged
191 changes: 101 additions & 90 deletions client/modules/IDE/actions/files.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import objectID from 'bson-objectid';
import blobUtil from 'blob-util';
import { reset } from 'redux-form';
import apiClient from '../../../utils/apiClient';
import * as ActionTypes from '../../../constants';
import { setUnsavedChanges, closeNewFolderModal, closeNewFileModal } from './ide';
import { setProjectSavedTime } from './project';
import { createError } from './ide';


function appendToFilename(filename, string) {
Expand Down Expand Up @@ -36,106 +36,117 @@ export function updateFileContent(id, content) {
};
}

export function createFile(formProps) {
export function createFile(file, parentId) {
return {
type: ActionTypes.CREATE_FILE,
...file,
parentId
};
}

export function submitFile(formProps, files, parentId, projectId) {
if (projectId) {
const postParams = {
name: createUniqueName(formProps.name, parentId, files),
url: formProps.url,
content: formProps.content || '',
parentId,
children: []
};
return apiClient.post(`/projects/${projectId}/files`, postParams)
.then(response => ({
file: response.data.updatedFile,
updatedAt: response.data.project.updatedAt
}));
}
const id = objectID().toHexString();
const file = {
name: createUniqueName(formProps.name, parentId, files),
id,
_id: id,
url: formProps.url,
content: formProps.content || '',
children: []
};
return Promise.resolve({
file
});
}

export function handleCreateFile(formProps) {
return (dispatch, getState) => {
const state = getState();
const { files } = state;
const { parentId } = state.ide;
if (state.project.id) {
const postParams = {
name: createUniqueName(formProps.name, parentId, state.files),
url: formProps.url,
content: formProps.content || '',
parentId,
children: []
};
apiClient.post(`/projects/${state.project.id}/files`, postParams)
.then((response) => {
dispatch({
type: ActionTypes.CREATE_FILE,
...response.data.updatedFile,
parentId
});
dispatch(setProjectSavedTime(response.data.project.updatedAt));
dispatch(closeNewFileModal());
dispatch(reset('new-file'));
// dispatch({
// type: ActionTypes.HIDE_MODAL
// });
dispatch(setUnsavedChanges(true));
})
.catch((error) => {
const { response } = error;
dispatch({
type: ActionTypes.ERROR,
error: response.data
});
});
} else {
const id = objectID().toHexString();
dispatch({
type: ActionTypes.CREATE_FILE,
name: createUniqueName(formProps.name, parentId, state.files),
id,
_id: id,
url: formProps.url,
content: formProps.content || '',
parentId,
children: []
const projectId = state.project.id;
return new Promise((resolve) => {
submitFile(formProps, files, parentId, projectId).then((response) => {
const { file, updatedAt } = response;
dispatch(createFile(file, parentId));
if (updatedAt) dispatch(setProjectSavedTime(updatedAt));
dispatch(closeNewFileModal());
dispatch(setUnsavedChanges(true));
resolve();
}).catch((error) => {
const { response } = error;
dispatch(createError(response.data));
resolve({ error });
});
dispatch(reset('new-file'));
// dispatch({
// type: ActionTypes.HIDE_MODAL
// });
dispatch(setUnsavedChanges(true));
dispatch(closeNewFileModal());
}
});
};
}

export function submitFolder(formProps, files, parentId, projectId) {
if (projectId) {
const postParams = {
name: createUniqueName(formProps.name, parentId, files),
content: '',
children: [],
parentId,
fileType: 'folder'
};
return apiClient.post(`/projects/${projectId}/files`, postParams)
.then(response => ({
file: response.data.updatedFile,
updatedAt: response.data.project.updatedAt
}));
}
const id = objectID().toHexString();
const file = {
type: ActionTypes.CREATE_FILE,
name: createUniqueName(formProps.name, parentId, files),
id,
_id: id,
content: '',
// TODO pass parent id from File Tree
fileType: 'folder',
children: []
};
return Promise.resolve({
file
});
}

export function createFolder(formProps) {
export function handleCreateFolder(formProps) {
return (dispatch, getState) => {
const state = getState();
const { files } = state;
const { parentId } = state.ide;
if (state.project.id) {
const postParams = {
name: createUniqueName(formProps.name, parentId, state.files),
content: '',
children: [],
parentId,
fileType: 'folder'
};
apiClient.post(`/projects/${state.project.id}/files`, postParams)
.then((response) => {
dispatch({
type: ActionTypes.CREATE_FILE,
...response.data.updatedFile,
parentId
});
dispatch(setProjectSavedTime(response.data.project.updatedAt));
dispatch(closeNewFolderModal());
})
.catch((error) => {
const { response } = error;
dispatch({
type: ActionTypes.ERROR,
error: response.data
});
});
} else {
const id = objectID().toHexString();
dispatch({
type: ActionTypes.CREATE_FILE,
name: createUniqueName(formProps.name, parentId, state.files),
id,
_id: id,
content: '',
// TODO pass parent id from File Tree
parentId,
fileType: 'folder',
children: []
const projectId = state.project.id;
return new Promise((resolve) => {
submitFolder(formProps, files, parentId, projectId).then((response) => {
const { file, updatedAt } = response;
dispatch(createFile(file, parentId));
if (updatedAt) dispatch(setProjectSavedTime(updatedAt));
dispatch(closeNewFolderModal());
dispatch(setUnsavedChanges(true));
resolve();
}).catch((error) => {
const { response } = error;
dispatch(createError(response.data));
resolve({ error });
});
dispatch(closeNewFolderModal());
}
});
};
}

Expand Down
7 changes: 7 additions & 0 deletions client/modules/IDE/actions/ide.js
Original file line number Diff line number Diff line change
Expand Up @@ -278,3 +278,10 @@ export function stopSketch() {
dispatch(stopVisualSketch());
};
}

export function createError(error) {
return {
type: ActionTypes.ERROR,
error
};
}
133 changes: 73 additions & 60 deletions client/modules/IDE/components/NewFileForm.jsx
Original file line number Diff line number Diff line change
@@ -1,69 +1,82 @@
import PropTypes from 'prop-types';
import React from 'react';
import { withTranslation } from 'react-i18next';
import { domOnlyProps } from '../../../utils/reduxFormUtils';
import React, { useEffect, useRef } from 'react';
import { useTranslation } from 'react-i18next';
import { Form, Field } from 'react-final-form';
import { useDispatch } from 'react-redux';
import { handleCreateFile } from '../actions/files';
import { CREATE_FILE_REGEX } from '../../../../server/utils/fileUtils';

import Button from '../../../common/Button';

class NewFileForm extends React.Component {
constructor(props) {
super(props);
this.createFile = this.props.createFile.bind(this);
}
function NewFileForm() {
const fileNameInput = useRef(null);
const { t } = useTranslation();
const dispatch = useDispatch();

componentDidMount() {
this.fileName.focus();
function onSubmit(formProps) {
return dispatch(handleCreateFile(formProps));
}

render() {
const {
fields: { name },
handleSubmit,
} = this.props;
return (
<form
className="new-file-form"
onSubmit={(data) => {
this.props.focusOnModal();
handleSubmit(this.createFile)(data);
}}
>
<div className="new-file-form__input-wrapper">
<label className="new-file-form__name-label" htmlFor="name">
Name:
</label>
<input
className="new-file-form__name-input"
id="name"
type="text"
placeholder={this.props.t('NewFileForm.Placeholder')}
maxLength="128"
{...domOnlyProps(name)}
ref={(element) => {
this.fileName = element;
}}
/>
<Button
type="submit"
>{this.props.t('NewFileForm.AddFileSubmit')}
</Button>
</div>
{name.touched && name.error && (
<span className="form-error">{name.error}</span>
)}
</form>
);
function validate(formProps) {
const errors = {};

if (!formProps.name) {
errors.name = t('NewFileModal.EnterName');
} else if (!formProps.name.match(CREATE_FILE_REGEX)) {
errors.name = t('NewFileModal.InvalidType');
}

return errors;
}
}

NewFileForm.propTypes = {
fields: PropTypes.shape({
name: PropTypes.objectOf(PropTypes.shape()).isRequired
}).isRequired,
handleSubmit: PropTypes.func.isRequired,
createFile: PropTypes.func.isRequired,
focusOnModal: PropTypes.func.isRequired,
t: PropTypes.func.isRequired
};
useEffect(() => {
fileNameInput.current.focus();
});

return (
<Form
fields={['name']}
validate={validate}
onSubmit={onSubmit}
>
{({
handleSubmit, errors, touched, invalid, submitting
}) => (
<form
className="new-file-form"
onSubmit={handleSubmit}
>
<div className="new-file-form__input-wrapper">
<Field name="name">
{field => (
<React.Fragment>
<label className="new-file-form__name-label" htmlFor="name">
Name:
</label>
<input
className="new-file-form__name-input"
id="name"
type="text"
placeholder={t('NewFileForm.Placeholder')}
maxLength="128"
{...field.input}
ref={fileNameInput}
/>
</React.Fragment>
)}
</Field>
<Button
type="submit"
disabled={invalid || submitting}
>{t('NewFileForm.AddFileSubmit')}
</Button>
</div>
{touched.name && errors.name && (
<span className="form-error">{errors.name}</span>
)}
</form>
)}
</Form>
);
}

export default withTranslation()(NewFileForm);
export default NewFileForm;
Loading