Comments editor
This commit is contained in:
parent
d889148e56
commit
7ea93c6290
3
.vscode/settings.json
vendored
3
.vscode/settings.json
vendored
@ -7,5 +7,6 @@
|
|||||||
"out": true // set this to false to include "out" folder in search results
|
"out": true // set this to false to include "out" folder in search results
|
||||||
},
|
},
|
||||||
"typescript.tsdk": "./node_modules/typescript/lib", // we want to use the TS server from our node_modules folder to control its version
|
"typescript.tsdk": "./node_modules/typescript/lib", // we want to use the TS server from our node_modules folder to control its version
|
||||||
"typescript.tsc.autoDetect": "off" // Turn off tsc task auto detection since we have the necessary task as npm scripts
|
"typescript.tsc.autoDetect": "off",
|
||||||
|
"todo-tree.tree.scanMode": "workspace only" // Turn off tsc task auto detection since we have the necessary task as npm scripts
|
||||||
}
|
}
|
||||||
|
@ -102,6 +102,10 @@ export default class KanbnTaskPanel {
|
|||||||
vscode.Uri.file(path.join(this._extensionPath, 'node_modules', 'vscode-codicons', 'dist'))
|
vscode.Uri.file(path.join(this._extensionPath, 'node_modules', 'vscode-codicons', 'dist'))
|
||||||
]
|
]
|
||||||
});
|
});
|
||||||
|
this._panel.iconPath = {
|
||||||
|
light: vscode.Uri.file(path.join(this._extensionPath, 'resources', 'task_light.svg')),
|
||||||
|
dark: vscode.Uri.file(path.join(this._extensionPath, 'resources', 'task_dark.svg'))
|
||||||
|
};
|
||||||
|
|
||||||
// Set the webview's title to the kanbn task name
|
// Set the webview's title to the kanbn task name
|
||||||
if (this._taskId !== null) {
|
if (this._taskId !== null) {
|
||||||
|
@ -91,13 +91,13 @@ const Board = ({ columns, startedColumns, completedColumns, dateFormat, vscode }
|
|||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className="kanbn-create-task-button"
|
className="kanbn-create-task-button"
|
||||||
|
title={`Create task in ${columnName}`}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
vscode.postMessage({
|
vscode.postMessage({
|
||||||
command: 'kanbn.addTask',
|
command: 'kanbn.addTask',
|
||||||
columnName
|
columnName
|
||||||
})
|
})
|
||||||
}}
|
}}
|
||||||
title={`Create task in ${columnName}`}
|
|
||||||
>
|
>
|
||||||
<i className="codicon codicon-add"></i>
|
<i className="codicon codicon-add"></i>
|
||||||
</button>
|
</button>
|
||||||
|
3
src/KanbnTask.d.ts
vendored
3
src/KanbnTask.d.ts
vendored
@ -1,3 +1,4 @@
|
|||||||
|
// Note that Date properties will be converted to strings (ISO) when a task is serialized and passed as a prop
|
||||||
declare type KanbnTask = {
|
declare type KanbnTask = {
|
||||||
uuid?: string,
|
uuid?: string,
|
||||||
id: string,
|
id: string,
|
||||||
@ -8,7 +9,7 @@ declare type KanbnTask = {
|
|||||||
remainingWorkload?: number,
|
remainingWorkload?: number,
|
||||||
progress?: number,
|
progress?: number,
|
||||||
metadata: {
|
metadata: {
|
||||||
created: string,
|
created?: string,
|
||||||
updated?: string,
|
updated?: string,
|
||||||
started?: string,
|
started?: string,
|
||||||
due?: string,
|
due?: string,
|
||||||
|
@ -5,6 +5,24 @@ import VSCodeApi from './VSCodeApi';
|
|||||||
import { paramCase } from 'param-case';
|
import { paramCase } from 'param-case';
|
||||||
import gitUsername from 'git-user-name';
|
import gitUsername from 'git-user-name';
|
||||||
|
|
||||||
|
interface KanbnTaskValidationOutput {
|
||||||
|
name: string,
|
||||||
|
metadata: {
|
||||||
|
tags: string[]
|
||||||
|
},
|
||||||
|
subTasks: Array<{
|
||||||
|
text: string
|
||||||
|
}>,
|
||||||
|
comments: Array<{
|
||||||
|
text: string
|
||||||
|
}>
|
||||||
|
}
|
||||||
|
|
||||||
|
interface KanbnTaskValidationInput extends KanbnTaskValidationOutput {
|
||||||
|
uuid: string,
|
||||||
|
id: string
|
||||||
|
}
|
||||||
|
|
||||||
const TaskEditor = ({ task, tasks, columnName, columnNames, dateFormat, vscode }: {
|
const TaskEditor = ({ task, tasks, columnName, columnNames, dateFormat, vscode }: {
|
||||||
task: KanbnTask|null,
|
task: KanbnTask|null,
|
||||||
tasks: Record<string, KanbnTask>,
|
tasks: Record<string, KanbnTask>,
|
||||||
@ -36,35 +54,55 @@ const TaskEditor = ({ task, tasks, columnName, columnNames, dateFormat, vscode }
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
// TODO progress bar below progress input
|
// Check if a task's due date is in the past
|
||||||
// TODO auto-colour tags while typing
|
const checkOverdue = (values: { metadata: { due?: string } }) => {
|
||||||
// TODO comments
|
if ('due' in values.metadata && values.metadata.due !== undefined) {
|
||||||
// TODO make sure all buttons have title attributes, maybe remove labels from array delete buttons?
|
return Date.parse(values.metadata.due) < (new Date()).getTime();
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="kanbn-task-editor">
|
<div className="kanbn-task-editor">
|
||||||
<h1 className="kanbn-task-editor-title">{editing ? 'Update task' : 'Create new task'}</h1>
|
<h1 className="kanbn-task-editor-title">
|
||||||
|
{editing ? 'Update task' : 'Create new task'}
|
||||||
|
{editing && <span className="kanbn-task-editor-dates">
|
||||||
|
{
|
||||||
|
[
|
||||||
|
'created' in task!.metadata ? `Created ${formatDate(task!.metadata.created, dateFormat)}` : null,
|
||||||
|
'updated' in task!.metadata ? `Updated ${formatDate(task!.metadata.updated, dateFormat)}` : null
|
||||||
|
].filter(i => i).join(', ')
|
||||||
|
}
|
||||||
|
</span>}
|
||||||
|
</h1>
|
||||||
<Formik
|
<Formik
|
||||||
initialValues={{
|
initialValues={{
|
||||||
uuid: task ? task.uuid : '',
|
uuid: task ? (task.uuid || '') : '',
|
||||||
id: task ? task.id : '',
|
id: task ? task.id : '',
|
||||||
name: task ? task.name : '',
|
name: task ? task.name : '',
|
||||||
description: task ? task.description : '',
|
description: task ? task.description : '',
|
||||||
column: columnName,
|
column: columnName,
|
||||||
progress: task ? task.progress : 0,
|
progress: task ? task.progress : 0,
|
||||||
metadata: {
|
metadata: {
|
||||||
due: (task && 'due' in task.metadata) ? formatDate(new Date(task.metadata.due!), 'yyyy-mm-dd') : '',
|
started: (task && 'started' in task.metadata) ? formatDate(task.metadata.started!, 'yyyy-mm-dd') : '',
|
||||||
|
due: (task && 'due' in task.metadata) ? formatDate(task.metadata.due!, 'yyyy-mm-dd') : '',
|
||||||
|
completed: (task && 'completed' in task.metadata) ? formatDate(task.metadata.completed!, 'yyyy-mm-dd') : '',
|
||||||
assigned: (task && 'assigned' in task.metadata) ? task.metadata.assigned : (gitUsername() || ''),
|
assigned: (task && 'assigned' in task.metadata) ? task.metadata.assigned : (gitUsername() || ''),
|
||||||
tags: (task && 'tags' in task.metadata) ? task.metadata.tags : []
|
tags: (task && 'tags' in task.metadata) ? (task.metadata.tags || []) : []
|
||||||
},
|
},
|
||||||
relations: task ? task.relations : [],
|
relations: task ? task.relations : [],
|
||||||
subTasks: task ? task.subTasks : [],
|
subTasks: task ? task.subTasks : [],
|
||||||
comments: task ? task.comments : []
|
comments: task ? task.comments : []
|
||||||
}}
|
}}
|
||||||
validate={values => {
|
validate={(values: KanbnTaskValidationInput): KanbnTaskValidationOutput => {
|
||||||
const errors: { name?: string } = {};
|
const errors: KanbnTaskValidationOutput = {
|
||||||
|
name: '',
|
||||||
// TODO validation
|
metadata: {
|
||||||
|
tags: []
|
||||||
|
},
|
||||||
|
subTasks: [],
|
||||||
|
comments: []
|
||||||
|
};
|
||||||
|
|
||||||
// Task name cannot be empty
|
// Task name cannot be empty
|
||||||
if (!values.name) {
|
if (!values.name) {
|
||||||
@ -75,6 +113,32 @@ const TaskEditor = ({ task, tasks, columnName, columnNames, dateFormat, vscode }
|
|||||||
if (values.id in tasks && tasks[values.id].uuid !== values.uuid) {
|
if (values.id in tasks && tasks[values.id].uuid !== values.uuid) {
|
||||||
errors.name = 'There is already a task with the same name or id.';
|
errors.name = 'There is already a task with the same name or id.';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Tag names cannot be empty
|
||||||
|
for (let i = 0; i < values.metadata.tags.length; i++) {
|
||||||
|
if (!values.metadata.tags[i]) {
|
||||||
|
errors.metadata.tags[i] = 'Tag cannot be empty.';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sub-tasks text cannot be empty
|
||||||
|
for (let i = 0; i < values.subTasks.length; i++) {
|
||||||
|
if (!values.subTasks[i].text) {
|
||||||
|
errors.subTasks[i] = {
|
||||||
|
text: 'Sub-task text cannot be empty.'
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Comments text cannot be empty
|
||||||
|
for (let i = 0; i < values.comments.length; i++) {
|
||||||
|
if (!values.comments[i].text) {
|
||||||
|
errors.comments[i] = {
|
||||||
|
text: 'Comment text cannot be empty.'
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return errors;
|
return errors;
|
||||||
}}
|
}}
|
||||||
onSubmit={(values, { setSubmitting }) => {
|
onSubmit={(values, { setSubmitting }) => {
|
||||||
@ -134,6 +198,63 @@ const TaskEditor = ({ task, tasks, columnName, columnNames, dateFormat, vscode }
|
|||||||
name="description"
|
name="description"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
<div className="kanbn-task-editor-field kanbn-task-editor-field-subtasks">
|
||||||
|
<h2 className="kanbn-task-editor-title">Sub-tasks</h2>
|
||||||
|
<FieldArray name="subTasks">
|
||||||
|
{({ insert, remove, push }) => (
|
||||||
|
<div>
|
||||||
|
{values.subTasks.length > 0 && values.subTasks.map((subTask, index) => (
|
||||||
|
<div className="kanbn-task-editor-row kanbn-task-editor-row-subtask" key={index}>
|
||||||
|
<div className="kanbn-task-editor-column kanbn-task-editor-field-subtask-completed">
|
||||||
|
<Field
|
||||||
|
className="kanbn-task-editor-field-checkbox"
|
||||||
|
type="checkbox"
|
||||||
|
name={`subTasks.${index}.completed`}
|
||||||
|
/>
|
||||||
|
<ErrorMessage
|
||||||
|
className="kanbn-task-editor-field-errors"
|
||||||
|
component="div"
|
||||||
|
name={`subTasks.${index}.completed`}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="kanbn-task-editor-column kanbn-task-editor-field-subtask-text">
|
||||||
|
<Field
|
||||||
|
className="kanbn-task-editor-field-input"
|
||||||
|
name={`subTasks.${index}.text`}
|
||||||
|
placeholder="Sub-task text"
|
||||||
|
/>
|
||||||
|
<ErrorMessage
|
||||||
|
className="kanbn-task-editor-field-errors"
|
||||||
|
component="div"
|
||||||
|
name={`subTasks.${index}.text`}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="kanbn-task-editor-column kanbn-task-editor-column-buttons">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="kanbn-task-editor-button kanbn-task-editor-button-delete"
|
||||||
|
title="Remove sub-task"
|
||||||
|
onClick={() => remove(index)}
|
||||||
|
>
|
||||||
|
<i className="codicon codicon-trash"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
<div className="kanbn-task-editor-buttons">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="kanbn-task-editor-button kanbn-task-editor-button-add"
|
||||||
|
title="Add sub-task"
|
||||||
|
onClick={() => push({ completed: false, text: '' })}
|
||||||
|
>
|
||||||
|
<i className="codicon codicon-tasklist"></i>Add sub-task
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</FieldArray>
|
||||||
|
</div>
|
||||||
<div className="kanbn-task-editor-field kanbn-task-editor-field-relations">
|
<div className="kanbn-task-editor-field kanbn-task-editor-field-relations">
|
||||||
<h2 className="kanbn-task-editor-title">Relations</h2>
|
<h2 className="kanbn-task-editor-title">Relations</h2>
|
||||||
<FieldArray name="relations">
|
<FieldArray name="relations">
|
||||||
@ -171,9 +292,10 @@ const TaskEditor = ({ task, tasks, columnName, columnNames, dateFormat, vscode }
|
|||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className="kanbn-task-editor-button kanbn-task-editor-button-delete"
|
className="kanbn-task-editor-button kanbn-task-editor-button-delete"
|
||||||
|
title="Remove relation"
|
||||||
onClick={() => remove(index)}
|
onClick={() => remove(index)}
|
||||||
>
|
>
|
||||||
<i className="codicon codicon-trash"></i>Delete
|
<i className="codicon codicon-trash"></i>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -182,54 +304,63 @@ const TaskEditor = ({ task, tasks, columnName, columnNames, dateFormat, vscode }
|
|||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className="kanbn-task-editor-button kanbn-task-editor-button-add"
|
className="kanbn-task-editor-button kanbn-task-editor-button-add"
|
||||||
|
title="Add relation"
|
||||||
onClick={() => push({ type: '', task: '' })}
|
onClick={() => push({ type: '', task: '' })}
|
||||||
>
|
>
|
||||||
<i className="codicon codicon-plus"></i>Add relation
|
<i className="codicon codicon-link"></i>Add relation
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</FieldArray>
|
</FieldArray>
|
||||||
</div>
|
</div>
|
||||||
<div className="kanbn-task-editor-field kanbn-task-editor-field-subtasks">
|
<div className="kanbn-task-editor-field kanbn-task-editor-field-comments">
|
||||||
<h2 className="kanbn-task-editor-title">Sub-tasks</h2>
|
<h2 className="kanbn-task-editor-title">Comments</h2>
|
||||||
<FieldArray name="subTasks">
|
<FieldArray name="comments">
|
||||||
{({ insert, remove, push }) => (
|
{({ insert, remove, push }) => (
|
||||||
<div>
|
<div>
|
||||||
{values.subTasks.length > 0 && values.subTasks.map((subTask, index) => (
|
{values.comments.length > 0 && values.comments.map((comment, index) => (
|
||||||
<div className="kanbn-task-editor-row kanbn-task-editor-row-subtask" key={index}>
|
<div className="kanbn-task-editor-row-comment" key={index}>
|
||||||
<div className="kanbn-task-editor-column kanbn-task-editor-field-subtask-completed">
|
<div className="kanbn-task-editor-row">
|
||||||
<Field
|
<div className="kanbn-task-editor-column kanbn-task-editor-field-comment-author">
|
||||||
className="kanbn-task-editor-field-checkbox"
|
<Field
|
||||||
type="checkbox"
|
className="kanbn-task-editor-field-input"
|
||||||
name={`subTasks.${index}.completed`}
|
name={`comments.${index}.author`}
|
||||||
/>
|
placeholder="Comment author"
|
||||||
<ErrorMessage
|
/>
|
||||||
className="kanbn-task-editor-field-errors"
|
<ErrorMessage
|
||||||
component="div"
|
className="kanbn-task-editor-field-errors"
|
||||||
name={`subTasks.${index}.completed`}
|
component="div"
|
||||||
/>
|
name={`comments.${index}.author`}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="kanbn-task-editor-column kanbn-task-editor-field-comment-date">
|
||||||
|
{formatDate(comment.date, dateFormat)}
|
||||||
|
</div>
|
||||||
|
<div className="kanbn-task-editor-column kanbn-task-editor-column-buttons">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="kanbn-task-editor-button kanbn-task-editor-button-delete"
|
||||||
|
title="Remove comment"
|
||||||
|
onClick={() => remove(index)}
|
||||||
|
>
|
||||||
|
<i className="codicon codicon-trash"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="kanbn-task-editor-column kanbn-task-editor-field-subtask-text">
|
<div className="kanbn-task-editor-row">
|
||||||
<Field
|
<div className="kanbn-task-editor-column kanbn-task-editor-field-comment-text">
|
||||||
className="kanbn-task-editor-field-input"
|
<Field
|
||||||
name={`subTasks.${index}.text`}
|
className="kanbn-task-editor-field-textarea"
|
||||||
placeholder="Sub-task text"
|
as="textarea"
|
||||||
/>
|
name={`comments.${index}.text`}
|
||||||
<ErrorMessage
|
/>
|
||||||
className="kanbn-task-editor-field-errors"
|
<ErrorMessage
|
||||||
component="div"
|
className="kanbn-task-editor-field-errors"
|
||||||
name={`subTasks.${index}.text`}
|
component="div"
|
||||||
/>
|
name={`comments.${index}.text`}
|
||||||
</div>
|
/>
|
||||||
<div className="kanbn-task-editor-column kanbn-task-editor-column-buttons">
|
</div>
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
className="kanbn-task-editor-button kanbn-task-editor-button-delete"
|
|
||||||
onClick={() => remove(index)}
|
|
||||||
>
|
|
||||||
<i className="codicon codicon-trash"></i>Delete
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
@ -237,9 +368,10 @@ const TaskEditor = ({ task, tasks, columnName, columnNames, dateFormat, vscode }
|
|||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className="kanbn-task-editor-button kanbn-task-editor-button-add"
|
className="kanbn-task-editor-button kanbn-task-editor-button-add"
|
||||||
onClick={() => push({ completed: false, text: '' })}
|
title="Add comment"
|
||||||
|
onClick={() => push({ text: '', date: new Date(), author: gitUsername() || '' })}
|
||||||
>
|
>
|
||||||
<i className="codicon codicon-plus"></i>Add sub-task
|
<i className="codicon codicon-comment"></i>Add comment
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -252,7 +384,7 @@ const TaskEditor = ({ task, tasks, columnName, columnNames, dateFormat, vscode }
|
|||||||
{editing && <button
|
{editing && <button
|
||||||
type="button"
|
type="button"
|
||||||
className="kanbn-task-editor-button kanbn-task-editor-button-delete"
|
className="kanbn-task-editor-button kanbn-task-editor-button-delete"
|
||||||
title="Delete"
|
title="Delete task"
|
||||||
onClick={handleRemoveTask}
|
onClick={handleRemoveTask}
|
||||||
>
|
>
|
||||||
<i className="codicon codicon-trash"></i>Delete
|
<i className="codicon codicon-trash"></i>Delete
|
||||||
@ -260,7 +392,7 @@ const TaskEditor = ({ task, tasks, columnName, columnNames, dateFormat, vscode }
|
|||||||
<button
|
<button
|
||||||
type="submit"
|
type="submit"
|
||||||
className="kanbn-task-editor-button kanbn-task-editor-button-submit"
|
className="kanbn-task-editor-button kanbn-task-editor-button-submit"
|
||||||
title="Save"
|
title="Save task"
|
||||||
disabled={isSubmitting}
|
disabled={isSubmitting}
|
||||||
>
|
>
|
||||||
<i className="codicon codicon-save"></i>Save
|
<i className="codicon codicon-save"></i>Save
|
||||||
@ -298,11 +430,29 @@ const TaskEditor = ({ task, tasks, columnName, columnNames, dateFormat, vscode }
|
|||||||
name="metadata.assigned"
|
name="metadata.assigned"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
<div className="kanbn-task-editor-field kanbn-task-editor-field-started">
|
||||||
|
<label className="kanbn-task-editor-field-label">
|
||||||
|
<p>Started date</p>
|
||||||
|
<Field
|
||||||
|
className="kanbn-task-editor-field-input"
|
||||||
|
type="date"
|
||||||
|
name="metadata.started"
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
<ErrorMessage
|
||||||
|
className="kanbn-task-editor-field-errors"
|
||||||
|
component="div"
|
||||||
|
name="metadata.started"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
<div className="kanbn-task-editor-field kanbn-task-editor-field-due">
|
<div className="kanbn-task-editor-field kanbn-task-editor-field-due">
|
||||||
<label className="kanbn-task-editor-field-label">
|
<label className="kanbn-task-editor-field-label">
|
||||||
<p>Due date</p>
|
<p>Due date</p>
|
||||||
<Field
|
<Field
|
||||||
className="kanbn-task-editor-field-input"
|
className={[
|
||||||
|
'kanbn-task-editor-field-input',
|
||||||
|
checkOverdue(values) ? 'kanbn-task-overdue' : null
|
||||||
|
].filter(i => i).join(' ')}
|
||||||
type="date"
|
type="date"
|
||||||
name="metadata.due"
|
name="metadata.due"
|
||||||
/>
|
/>
|
||||||
@ -313,6 +463,21 @@ const TaskEditor = ({ task, tasks, columnName, columnNames, dateFormat, vscode }
|
|||||||
name="metadata.due"
|
name="metadata.due"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
<div className="kanbn-task-editor-field kanbn-task-editor-field-completed">
|
||||||
|
<label className="kanbn-task-editor-field-label">
|
||||||
|
<p>Completed date</p>
|
||||||
|
<Field
|
||||||
|
className="kanbn-task-editor-field-input"
|
||||||
|
type="date"
|
||||||
|
name="metadata.completed"
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
<ErrorMessage
|
||||||
|
className="kanbn-task-editor-field-errors"
|
||||||
|
component="div"
|
||||||
|
name="metadata.completed"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
<div className="kanbn-task-editor-field kanbn-task-editor-field-progress">
|
<div className="kanbn-task-editor-field kanbn-task-editor-field-progress">
|
||||||
<label className="kanbn-task-editor-field-label">
|
<label className="kanbn-task-editor-field-label">
|
||||||
<p>Progress</p>
|
<p>Progress</p>
|
||||||
@ -324,6 +489,9 @@ const TaskEditor = ({ task, tasks, columnName, columnNames, dateFormat, vscode }
|
|||||||
max="1"
|
max="1"
|
||||||
step="0.05"
|
step="0.05"
|
||||||
/>
|
/>
|
||||||
|
<div className="kanbn-task-progress" style={{
|
||||||
|
width: `${Math.min(1, Math.max(0, values.progress || 0)) * 100}%`
|
||||||
|
}}></div>
|
||||||
</label>
|
</label>
|
||||||
<ErrorMessage
|
<ErrorMessage
|
||||||
className="kanbn-task-editor-field-errors"
|
className="kanbn-task-editor-field-errors"
|
||||||
@ -347,7 +515,14 @@ const TaskEditor = ({ task, tasks, columnName, columnNames, dateFormat, vscode }
|
|||||||
<Field
|
<Field
|
||||||
className="kanbn-task-editor-field-input"
|
className="kanbn-task-editor-field-input"
|
||||||
name={`metadata.tags.${index}`}
|
name={`metadata.tags.${index}`}
|
||||||
|
placeholder="Tag name"
|
||||||
/>
|
/>
|
||||||
|
<div
|
||||||
|
className={[
|
||||||
|
'kanbn-task-editor-tag-highlight',
|
||||||
|
`kanbn-task-tag-${paramCase(values.metadata.tags![index])}`
|
||||||
|
].join(' ')}
|
||||||
|
></div>
|
||||||
<ErrorMessage
|
<ErrorMessage
|
||||||
className="kanbn-task-editor-field-errors"
|
className="kanbn-task-editor-field-errors"
|
||||||
component="div"
|
component="div"
|
||||||
@ -358,9 +533,10 @@ const TaskEditor = ({ task, tasks, columnName, columnNames, dateFormat, vscode }
|
|||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className="kanbn-task-editor-button kanbn-task-editor-button-delete"
|
className="kanbn-task-editor-button kanbn-task-editor-button-delete"
|
||||||
|
title="Remove tag"
|
||||||
onClick={() => remove(index)}
|
onClick={() => remove(index)}
|
||||||
>
|
>
|
||||||
<i className="codicon codicon-trash"></i>Delete
|
<i className="codicon codicon-trash"></i>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -369,9 +545,10 @@ const TaskEditor = ({ task, tasks, columnName, columnNames, dateFormat, vscode }
|
|||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className="kanbn-task-editor-button kanbn-task-editor-button-add"
|
className="kanbn-task-editor-button kanbn-task-editor-button-add"
|
||||||
|
title="Add tag"
|
||||||
onClick={() => push('')}
|
onClick={() => push('')}
|
||||||
>
|
>
|
||||||
<i className="codicon codicon-plus"></i>Add tag
|
<i className="codicon codicon-tag"></i>Add tag
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -10,11 +10,20 @@ const TaskItem = ({ task, position, dateFormat, vscode }: {
|
|||||||
dateFormat: string,
|
dateFormat: string,
|
||||||
vscode: VSCodeApi
|
vscode: VSCodeApi
|
||||||
}) => {
|
}) => {
|
||||||
const createdDate = 'created' in task.metadata ? formatDate(task.metadata.created, dateFormat) : '';
|
const createdDate = 'created' in task.metadata ? formatDate(task.metadata.created, dateFormat) : null;
|
||||||
const updatedDate = 'updated' in task.metadata ? formatDate(task.metadata.updated, dateFormat) : '';
|
const updatedDate = 'updated' in task.metadata ? formatDate(task.metadata.updated, dateFormat) : null;
|
||||||
const startedDate = 'started' in task.metadata ? formatDate(task.metadata.started, dateFormat) : '';
|
const startedDate = 'started' in task.metadata ? formatDate(task.metadata.started, dateFormat) : null;
|
||||||
const dueDate = 'due' in task.metadata ? formatDate(task.metadata.due, dateFormat) : '';
|
const dueDate = 'due' in task.metadata ? formatDate(task.metadata.due, dateFormat) : null;
|
||||||
const completedDate = 'completed' in task.metadata ? formatDate(task.metadata.completed, dateFormat) : '';
|
const completedDate = 'completed' in task.metadata ? formatDate(task.metadata.completed, dateFormat) : null;
|
||||||
|
|
||||||
|
// Check if a task's due date is in the past
|
||||||
|
const checkOverdue = (task: KanbnTask) => {
|
||||||
|
if ('due' in task.metadata && task.metadata.due !== undefined) {
|
||||||
|
return Date.parse(task.metadata.due) < (new Date()).getTime();
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Draggable
|
<Draggable
|
||||||
key={task.id}
|
key={task.id}
|
||||||
@ -85,7 +94,12 @@ const TaskItem = ({ task, position, dateFormat, vscode }: {
|
|||||||
dueDate ? `Due ${dueDate}` : null,
|
dueDate ? `Due ${dueDate}` : null,
|
||||||
completedDate ? `Completed ${completedDate}` : null
|
completedDate ? `Completed ${completedDate}` : null
|
||||||
].filter(i => i).join('\n')}>
|
].filter(i => i).join('\n')}>
|
||||||
<i className="codicon codicon-clock"></i>{updatedDate || createdDate}
|
<i
|
||||||
|
className={[
|
||||||
|
'codicon codicon-clock',
|
||||||
|
checkOverdue(task) ? 'kanbn-task-overdue' : null
|
||||||
|
].filter(i => i).join(' ')}
|
||||||
|
></i>{updatedDate || createdDate}
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
@ -111,7 +125,7 @@ const TaskItem = ({ task, position, dateFormat, vscode }: {
|
|||||||
task.workload !== undefined &&
|
task.workload !== undefined &&
|
||||||
task.progress !== undefined &&
|
task.progress !== undefined &&
|
||||||
<div className="kanbn-task-progress" style={{
|
<div className="kanbn-task-progress" style={{
|
||||||
width: `${task.progress * 100}%`
|
width: `${Math.min(1, Math.max(0, task.progress)) * 100}%`
|
||||||
}}></div>
|
}}></div>
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,7 +1,8 @@
|
|||||||
body {
|
body {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 1em;
|
padding: 1em;
|
||||||
font-family: sans-serif;
|
font-family: var(--vscode-font-family);
|
||||||
|
font-size: var(--vscode-font-size);
|
||||||
background-color: var(--vscode-editor-background);
|
background-color: var(--vscode-editor-background);
|
||||||
color: var(--vscode-foreground);
|
color: var(--vscode-foreground);
|
||||||
}
|
}
|
||||||
@ -204,6 +205,10 @@ body {
|
|||||||
color: #333;
|
color: #333;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.kanbn-task-overdue {
|
||||||
|
color: #f42 !important;
|
||||||
|
}
|
||||||
|
|
||||||
.kanbn-task-progress {
|
.kanbn-task-progress {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
bottom: -2px;
|
bottom: -2px;
|
||||||
@ -220,14 +225,27 @@ body {
|
|||||||
border-bottom: 1px var(--vscode-activityBar-inactiveForeground) solid;
|
border-bottom: 1px var(--vscode-activityBar-inactiveForeground) solid;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.kanbn-task-editor-dates {
|
||||||
|
font-size: var(--vscode-font-size);
|
||||||
|
font-style: italic;
|
||||||
|
font-weight: normal;
|
||||||
|
opacity: 0.8;
|
||||||
|
float: right;
|
||||||
|
}
|
||||||
|
|
||||||
.kanbn-task-editor-form {
|
.kanbn-task-editor-form {
|
||||||
display: flex;
|
display: flex;
|
||||||
}
|
}
|
||||||
|
|
||||||
.kanbn-task-editor-field .kanbn-task-editor-title {
|
.kanbn-task-editor-field .kanbn-task-editor-title,
|
||||||
font-size: 1.1em;
|
.kanbn-task-editor-field-label p {
|
||||||
padding: 0.5em 0;
|
color: var(--vscode-editor-foreground);
|
||||||
border-bottom: 1px var(--vscode-activityBar-inactiveForeground) solid;
|
font-size: 0.8em;
|
||||||
|
letter-spacing: 0.1em;
|
||||||
|
font-weight: bold;
|
||||||
|
text-transform: uppercase;
|
||||||
|
padding: 4px 0;
|
||||||
|
border-bottom: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.kanbn-task-editor-column-left {
|
.kanbn-task-editor-column-left {
|
||||||
@ -243,15 +261,6 @@ body {
|
|||||||
margin-bottom: 1em;
|
margin-bottom: 1em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.kanbn-task-editor-field-label p {
|
|
||||||
color: var(--vscode-editor-foreground);
|
|
||||||
font-size: 0.8em;
|
|
||||||
letter-spacing: 0.1em;
|
|
||||||
font-weight: bold;
|
|
||||||
text-transform: uppercase;
|
|
||||||
padding: 4px 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
body.vscode-dark .kanbn-task-editor-field-input[type="date"]::-webkit-calendar-picker-indicator {
|
body.vscode-dark .kanbn-task-editor-field-input[type="date"]::-webkit-calendar-picker-indicator {
|
||||||
filter: invert(1);
|
filter: invert(1);
|
||||||
}
|
}
|
||||||
@ -270,6 +279,11 @@ body.vscode-dark .kanbn-task-editor-field-input[type="date"]::-webkit-calendar-p
|
|||||||
border: 1px transparent solid;
|
border: 1px transparent solid;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.kanbn-task-editor-field-input[type=date] {
|
||||||
|
font-family: var(--vscode-font-family);
|
||||||
|
font-size: var(--vscode-font-size);
|
||||||
|
}
|
||||||
|
|
||||||
.kanbn-task-editor-field-select {
|
.kanbn-task-editor-field-select {
|
||||||
padding-bottom: 7px;
|
padding-bottom: 7px;
|
||||||
}
|
}
|
||||||
@ -356,3 +370,46 @@ body.vscode-dark .kanbn-task-editor-field-input[type="date"]::-webkit-calendar-p
|
|||||||
.kanbn-task-editor-column-buttons .kanbn-task-editor-button {
|
.kanbn-task-editor-column-buttons .kanbn-task-editor-button {
|
||||||
margin: 8px 0;
|
margin: 8px 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.kanbn-task-editor-field-progress {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.kanbn-task-editor-field-progress .kanbn-task-progress {
|
||||||
|
bottom: 0;
|
||||||
|
height: 4px;
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.kanbn-task-editor-column .kanbn-task-editor-button-delete .codicon {
|
||||||
|
margin-right: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.kanbn-task-editor-field-tag {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.kanbn-task-editor-tag-highlight {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 8px;
|
||||||
|
left: 0;
|
||||||
|
height: 4px;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.kanbn-task-editor-row-comment {
|
||||||
|
padding-bottom: 1em;
|
||||||
|
border-bottom: 1px var(--vscode-activityBar-inactiveForeground) solid;
|
||||||
|
margin-bottom: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.kanbn-task-editor-field-comment-date {
|
||||||
|
padding: 16px 0;
|
||||||
|
text-align: right;
|
||||||
|
font-style: italic;
|
||||||
|
opacity: 0.8;
|
||||||
|
}
|
||||||
|
|
||||||
|
.kanbn-task-editor-field-comment-text .kanbn-task-editor-field-textarea {
|
||||||
|
min-height: 90px;
|
||||||
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user