Skip to content

Migrate from redux-form to react-final-form #1707

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 12 commits into from
Dec 14, 2020
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