From 7ea93c6290d5be396a7711d19453f6bd6783d1ba Mon Sep 17 00:00:00 2001 From: Gordon Date: Tue, 6 Apr 2021 22:00:56 +0100 Subject: [PATCH] Comments editor --- .vscode/settings.json | 3 +- ext-src/KanbnTaskPanel.ts | 4 + src/Board.tsx | 2 +- src/KanbnTask.d.ts | 3 +- src/TaskEditor.tsx | 291 ++++++++++++++++++++++++++++++-------- src/TaskItem.tsx | 28 +++- src/index.css | 85 +++++++++-- 7 files changed, 335 insertions(+), 81 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index 54b7d36..68d4168 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -7,5 +7,6 @@ "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.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 } diff --git a/ext-src/KanbnTaskPanel.ts b/ext-src/KanbnTaskPanel.ts index cddb050..da364b8 100644 --- a/ext-src/KanbnTaskPanel.ts +++ b/ext-src/KanbnTaskPanel.ts @@ -102,6 +102,10 @@ export default class KanbnTaskPanel { 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 if (this._taskId !== null) { diff --git a/src/Board.tsx b/src/Board.tsx index 164e693..aefca01 100644 --- a/src/Board.tsx +++ b/src/Board.tsx @@ -91,13 +91,13 @@ const Board = ({ columns, startedColumns, completedColumns, dateFormat, vscode } diff --git a/src/KanbnTask.d.ts b/src/KanbnTask.d.ts index b30ea46..2e5cf9a 100644 --- a/src/KanbnTask.d.ts +++ b/src/KanbnTask.d.ts @@ -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 = { uuid?: string, id: string, @@ -8,7 +9,7 @@ declare type KanbnTask = { remainingWorkload?: number, progress?: number, metadata: { - created: string, + created?: string, updated?: string, started?: string, due?: string, diff --git a/src/TaskEditor.tsx b/src/TaskEditor.tsx index 4e001ac..e848ef9 100644 --- a/src/TaskEditor.tsx +++ b/src/TaskEditor.tsx @@ -5,6 +5,24 @@ import VSCodeApi from './VSCodeApi'; import { paramCase } from 'param-case'; 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 }: { task: KanbnTask|null, tasks: Record, @@ -36,35 +54,55 @@ const TaskEditor = ({ task, tasks, columnName, columnNames, dateFormat, vscode } }); }; - // TODO progress bar below progress input - // TODO auto-colour tags while typing - // TODO comments - // TODO make sure all buttons have title attributes, maybe remove labels from array delete buttons? + // Check if a task's due date is in the past + const checkOverdue = (values: { metadata: { due?: string } }) => { + if ('due' in values.metadata && values.metadata.due !== undefined) { + return Date.parse(values.metadata.due) < (new Date()).getTime(); + } + return false; + }; return (
-

{editing ? 'Update task' : 'Create new task'}

+

+ {editing ? 'Update task' : 'Create new task'} + {editing && + { + [ + '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(', ') + } + } +

{ - const errors: { name?: string } = {}; - - // TODO validation + validate={(values: KanbnTaskValidationInput): KanbnTaskValidationOutput => { + const errors: KanbnTaskValidationOutput = { + name: '', + metadata: { + tags: [] + }, + subTasks: [], + comments: [] + }; // Task name cannot be empty 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) { 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; }} onSubmit={(values, { setSubmitting }) => { @@ -134,6 +198,63 @@ const TaskEditor = ({ task, tasks, columnName, columnNames, dateFormat, vscode } name="description" />
+
+

Sub-tasks

+ + {({ insert, remove, push }) => ( +
+ {values.subTasks.length > 0 && values.subTasks.map((subTask, index) => ( +
+
+ + +
+
+ + +
+
+ +
+
+ ))} +
+ +
+
+ )} +
+

Relations

@@ -171,9 +292,10 @@ const TaskEditor = ({ task, tasks, columnName, columnNames, dateFormat, vscode }
@@ -182,54 +304,63 @@ const TaskEditor = ({ task, tasks, columnName, columnNames, dateFormat, vscode } )} -
-

Sub-tasks

- +
+

Comments

+ {({ insert, remove, push }) => (
- {values.subTasks.length > 0 && values.subTasks.map((subTask, index) => ( -
-
- - + {values.comments.length > 0 && values.comments.map((comment, index) => ( +
+
+
+ + +
+
+ {formatDate(comment.date, dateFormat)} +
+
+ +
-
- - -
-
- +
+
+ + +
))} @@ -237,9 +368,10 @@ const TaskEditor = ({ task, tasks, columnName, columnNames, dateFormat, vscode }
@@ -252,7 +384,7 @@ const TaskEditor = ({ task, tasks, columnName, columnNames, dateFormat, vscode } {editing &&
+
+ + +
+
+ + +
+
remove(index)} > - Delete +
@@ -369,9 +545,10 @@ const TaskEditor = ({ task, tasks, columnName, columnNames, dateFormat, vscode }
diff --git a/src/TaskItem.tsx b/src/TaskItem.tsx index 03001bf..bfac009 100644 --- a/src/TaskItem.tsx +++ b/src/TaskItem.tsx @@ -10,11 +10,20 @@ const TaskItem = ({ task, position, dateFormat, vscode }: { dateFormat: string, vscode: VSCodeApi }) => { - const createdDate = 'created' in task.metadata ? formatDate(task.metadata.created, dateFormat) : ''; - const updatedDate = 'updated' in task.metadata ? formatDate(task.metadata.updated, dateFormat) : ''; - const startedDate = 'started' in task.metadata ? formatDate(task.metadata.started, dateFormat) : ''; - const dueDate = 'due' in task.metadata ? formatDate(task.metadata.due, dateFormat) : ''; - const completedDate = 'completed' in task.metadata ? formatDate(task.metadata.completed, dateFormat) : ''; + const createdDate = 'created' in task.metadata ? formatDate(task.metadata.created, dateFormat) : null; + const updatedDate = 'updated' in task.metadata ? formatDate(task.metadata.updated, dateFormat) : null; + const startedDate = 'started' in task.metadata ? formatDate(task.metadata.started, dateFormat) : null; + const dueDate = 'due' in task.metadata ? formatDate(task.metadata.due, dateFormat) : null; + 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 ( i).join('\n')}> - {updatedDate || createdDate} + i).join(' ')} + >{updatedDate || createdDate} } { @@ -111,7 +125,7 @@ const TaskItem = ({ task, position, dateFormat, vscode }: { task.workload !== undefined && task.progress !== undefined &&
} diff --git a/src/index.css b/src/index.css index 9950fe5..ea5cc96 100644 --- a/src/index.css +++ b/src/index.css @@ -1,7 +1,8 @@ body { margin: 0; padding: 1em; - font-family: sans-serif; + font-family: var(--vscode-font-family); + font-size: var(--vscode-font-size); background-color: var(--vscode-editor-background); color: var(--vscode-foreground); } @@ -204,6 +205,10 @@ body { color: #333; } +.kanbn-task-overdue { + color: #f42 !important; +} + .kanbn-task-progress { position: absolute; bottom: -2px; @@ -220,14 +225,27 @@ body { 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 { display: flex; } -.kanbn-task-editor-field .kanbn-task-editor-title { - font-size: 1.1em; - padding: 0.5em 0; - border-bottom: 1px var(--vscode-activityBar-inactiveForeground) solid; +.kanbn-task-editor-field .kanbn-task-editor-title, +.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; + border-bottom: none; } .kanbn-task-editor-column-left { @@ -243,15 +261,6 @@ body { 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 { filter: invert(1); } @@ -270,6 +279,11 @@ body.vscode-dark .kanbn-task-editor-field-input[type="date"]::-webkit-calendar-p 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 { 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 { 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; +}