Refactor task editor to use formik

This commit is contained in:
Gordon
2021-04-05 13:43:59 +01:00
parent 29fca87a99
commit be9daf1e42
4 changed files with 213 additions and 199 deletions

View File

@ -1,29 +1,10 @@
import React, { useReducer, useCallback } from "react";
import React from 'react';
import { Formik } from 'formik';
import formatDate from 'dateformat';
import VSCodeApi from "./VSCodeApi";
import VSCodeApi from './VSCodeApi';
import { paramCase } from 'param-case';
import produce from 'immer';
import { set, has } from 'lodash';
import * as gitUsername from 'git-user-name';
// https://levelup.gitconnected.com/handling-complex-form-state-using-react-hooks-76ee7bc937
function reducer(state, action) {
if (action.constructor === Function) {
return { ...state, ...action(state) };
}
if (action.constructor === Object) {
if (has(action, "_path") && has(action, "_value")) {
const { _path, _value } = action;
return produce(state, draft => {
set(draft, _path, _value);
});
} else {
return { ...state, ...action };
}
}
}
const TaskEditor = ({ task, columnName, columnNames, dateFormat, vscode }: {
task: KanbnTask|null,
columnName: string,
@ -32,183 +13,189 @@ const TaskEditor = ({ task, columnName, columnNames, dateFormat, vscode }: {
vscode: VSCodeApi
}) => {
const editing = task !== null;
const [taskData, setTaskData] = useReducer(reducer, {
id: task ? task.id : '',
name: task ? task.name : '',
description: task ? task.description : '',
column: columnName,
progress: task ? task.progress : 0,
metadata: {
due: (task && 'due' in task.metadata) ? formatDate(new Date(task.metadata.due!), 'yyyy-mm-dd') : '',
assigned: (task && 'assigned' in task.metadata) ? task.metadata.assigned : gitUsername(),
tags: (task && 'tags' in task.metadata) ? task.metadata.tags : []
},
relations: [],
subTasks: [],
comments: []
});
const handleChange = useCallback(({ target: { value, name, type } }) => {
const updatePath = name.split(".");
// Called when the name field is changed
const handleUpdateName = ({ target: { value }}, values) => {
// Handle updating checkbox states (depends on previous state)
if (type === 'checkbox') {
setTaskData((previousState) => ({
[name]: !previousState[name]
}));
return;
}
// Update the id preview
values.id = paramCase(value);
// Handle updating root-level properties
if (updatePath.length === 1) {
const [key] = updatePath;
const newTaskData = {
[key]: value
};
// If the name is updated, generate a new id and set the webview panel title
if (key === 'name') {
newTaskData['id'] = paramCase(value);
vscode.postMessage({
command: 'kanbn.updatePanelTitle',
title: value || 'Untitled task'
});
}
setTaskData(newTaskData);
}
// Handle updating nested properties using _path and _value
if (updatePath.length > 1) {
setTaskData({
_path: updatePath,
_value: value
});
}
}, []);
const handleSubmit = e => {
e.preventDefault();
// If a task prop was passed in, we're updating a task, otherwise we're creating a new task
if (editing) {
vscode.postMessage({
command: 'kanbn.update'
});
} else {
vscode.postMessage({
command: 'kanbn.create'
});
}
console.log(e);
// Update the webview panel title
vscode.postMessage({
command: 'kanbn.updatePanelTitle',
title: value || 'Untitled task'
});
};
return (
<form className="kanbn-task-editor" onSubmit={handleSubmit}>
<div className="kanbn-task-editor">
<h1 className="kanbn-task-editor-title">{editing ? 'Update task' : 'Create new task'}</h1>
<div
style={{
display: "flex"
<Formik
initialValues={{
id: task ? task.id : '',
name: task ? task.name : '',
description: task ? task.description : '',
column: columnName,
progress: task ? task.progress : 0,
metadata: {
due: (task && 'due' in task.metadata) ? formatDate(new Date(task.metadata.due!), 'yyyy-mm-dd') : '',
assigned: (task && 'assigned' in task.metadata) ? task.metadata.assigned : gitUsername(),
tags: (task && 'tags' in task.metadata) ? task.metadata.tags : []
},
relations: [],
subTasks: [],
comments: []
}}
validate={values => {
const errors: { name?: string } = {};
// if (!values.email) {
// errors.email = 'Required';
// } else if (
// !/^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}$/i.test(values.email)
// ) {
// errors.email = 'Invalid email address';
// }
return errors;
}}
onSubmit={(values, { setSubmitting }) => {
if (editing) {
vscode.postMessage({
command: 'kanbn.update'
});
} else {
vscode.postMessage({
command: 'kanbn.create'
});
}
console.log(values);
setSubmitting(false);
}}
>
<div className="kanbn-task-editor-column-left">
<div className="kanbn-task-field kanbn-task-field-name">
<label className="kanbn-task-field-label">
<p>Name</p>
<input
className="kanbn-task-field-input"
placeholder="Name"
name="name"
value={taskData.name}
onChange={handleChange}
></input>
</label>
<span className="kanbn-task-id">{taskData.id}</span>
</div>
<div className="kanbn-task-field kanbn-task-field-description">
<label className="kanbn-task-field-label">
<p>Description</p>
<textarea
className="kanbn-task-field-textarea"
placeholder="Description"
name="description"
value={taskData.description}
onChange={handleChange}
></textarea>
</label>
</div>
</div>
<div className="kanbn-task-editor-column-right">
<div>
<button
type="submit"
className="kanbn-task-editor-button kanbn-task-editor-button-submit"
title="Save"
{({
values,
errors,
touched,
handleChange,
handleBlur,
handleSubmit,
isSubmitting
}) => (
<form onSubmit={handleSubmit}>
<div
style={{
display: "flex"
}}
>
Save
</button>
<button
type="button"
className="kanbn-task-editor-button kanbn-task-editor-button-delete"
title="Delete"
>
Delete
</button>
</div>
<div className="kanbn-task-field kanbn-task-field-column">
<label className="kanbn-task-field-label">
<p>Column</p>
<select
className="kanbn-task-field-select"
name="column"
value={taskData.column}
onChange={handleChange}
>
{columnNames.map(c => <option value={c}>{c}</option>)}
</select>
</label>
</div>
<div className="kanbn-task-field kanbn-task-field-assigned">
<label className="kanbn-task-field-label">
<p>Assigned to</p>
<input
className="kanbn-task-field-input"
name="metadata.assigned"
value={taskData.metadata.assigned}
onChange={handleChange}
></input>
</label>
</div>
<div className="kanbn-task-field kanbn-task-field-due">
<label className="kanbn-task-field-label">
<p>Due date</p>
<input
type="date"
className="kanbn-task-field-input"
name="metadata.due"
value={taskData.metadata.due}
onChange={handleChange}
></input>
</label>
</div>
<div className="kanbn-task-field kanbn-task-field-progress">
<label className="kanbn-task-field-label">
<p>Progress</p>
<input
type="number"
className="kanbn-task-field-input"
name="progress"
value={taskData.progress}
onChange={handleChange}
min="0"
max="1"
step="0.05"
></input>
</label>
</div>
</div>
</div>
</form>
<div className="kanbn-task-editor-column-left">
<div className="kanbn-task-field kanbn-task-field-name">
<label className="kanbn-task-field-label">
<p>Name</p>
<input
className="kanbn-task-field-input"
placeholder="Name"
name="name"
value={values.name}
onChange={e => {
handleChange(e);
handleUpdateName(e, values);
}}
onBlur={handleBlur}
></input>
</label>
{errors.name && touched.name && errors.name}
<span className="kanbn-task-id">{values.id}</span>
</div>
<div className="kanbn-task-field kanbn-task-field-description">
<label className="kanbn-task-field-label">
<p>Description</p>
<textarea
className="kanbn-task-field-textarea"
placeholder="Description"
name="description"
value={values.description}
onChange={handleChange}
onBlur={handleBlur}
></textarea>
</label>
</div>
</div>
<div className="kanbn-task-editor-column-right">
<div>
<button
type="submit"
className="kanbn-task-editor-button kanbn-task-editor-button-submit"
title="Save"
disabled={isSubmitting}
>Save</button>
<button
type="button"
className="kanbn-task-editor-button kanbn-task-editor-button-delete"
title="Delete"
>Delete</button>
</div>
<div className="kanbn-task-field kanbn-task-field-column">
<label className="kanbn-task-field-label">
<p>Column</p>
<select
className="kanbn-task-field-select"
name="column"
value={values.column}
onChange={handleChange}
onBlur={handleBlur}
>
{columnNames.map(c => <option value={c}>{c}</option>)}
</select>
</label>
</div>
<div className="kanbn-task-field kanbn-task-field-assigned">
<label className="kanbn-task-field-label">
<p>Assigned to</p>
<input
className="kanbn-task-field-input"
placeholder="Assigned to"
name="metadata.assigned"
value={values.metadata.assigned}
onChange={handleChange}
onBlur={handleBlur}
></input>
</label>
</div>
<div className="kanbn-task-field kanbn-task-field-due">
<label className="kanbn-task-field-label">
<p>Due date</p>
<input
type="date"
className="kanbn-task-field-input"
name="metadata.due"
value={values.metadata.due}
onChange={handleChange}
onBlur={handleBlur}
></input>
</label>
</div>
<div className="kanbn-task-field kanbn-task-field-progress">
<label className="kanbn-task-field-label">
<p>Progress</p>
<input
type="number"
className="kanbn-task-field-input"
name="progress"
value={values.progress}
onChange={handleChange}
onBlur={handleBlur}
min="0"
max="1"
step="0.05"
></input>
</label>
</div>
</div>
</div>
</form>
)}
</Formik>
</div>
);
}
};
export default TaskEditor;