From 6efe282b8129351e737237cbf5ef980f5fa93bc4 Mon Sep 17 00:00:00 2001 From: Gordon Date: Mon, 5 Apr 2021 23:09:43 +0100 Subject: [PATCH] Edit relations and sub-tasks --- ext-src/KanbnBoardPanel.ts | 13 +- ext-src/KanbnTaskPanel.ts | 24 ++- package-lock.json | 41 +++++- package.json | 3 + src/App.tsx | 5 +- src/Board.tsx | 10 +- src/KanbnTask.d.ts | 1 + src/TaskEditor.tsx | 292 ++++++++++++++++++++++++++----------- src/index.css | 110 ++++++++++++-- 9 files changed, 379 insertions(+), 120 deletions(-) diff --git a/ext-src/KanbnBoardPanel.ts b/ext-src/KanbnBoardPanel.ts index 1f991d6..554e89f 100644 --- a/ext-src/KanbnBoardPanel.ts +++ b/ext-src/KanbnBoardPanel.ts @@ -42,12 +42,19 @@ export default class KanbnBoardPanel { vscode.window.showErrorMessage(error instanceof Error ? error.message : error); return; } + let tasks: any[]; + try { + tasks = (await KanbnBoardPanel.currentPanel._kanbn.loadAllTrackedTasks(index)).map( + task => KanbnBoardPanel.currentPanel!._kanbn.hydrateTask(index, task) + ); + } catch (error) { + vscode.window.showErrorMessage(error instanceof Error ? error.message : error); + return; + } KanbnBoardPanel.currentPanel._panel.webview.postMessage({ type: 'index', index, - tasks: (await KanbnBoardPanel.currentPanel._kanbn.loadAllTrackedTasks(index)).map( - task => KanbnBoardPanel.currentPanel!._kanbn.hydrateTask(index, task) - ), + tasks, startedColumns: index.options.startedColumns ?? [], completedColumns: index.options.completedColumns ?? [], dateFormat: KanbnBoardPanel.currentPanel._kanbn.getDateFormat(index) diff --git a/ext-src/KanbnTaskPanel.ts b/ext-src/KanbnTaskPanel.ts index 9dc829e..55e86ab 100644 --- a/ext-src/KanbnTaskPanel.ts +++ b/ext-src/KanbnTaskPanel.ts @@ -1,5 +1,6 @@ import * as path from 'path'; import * as vscode from 'vscode'; +import { v4 as uuidv4 } from 'uuid'; export default class KanbnTaskPanel { private static readonly viewType = 'react'; @@ -28,14 +29,21 @@ export default class KanbnTaskPanel { vscode.window.showErrorMessage(error instanceof Error ? error.message : error); return; } - let task: any = null; + let tasks: any[]; + try { + tasks = (await kanbn.loadAllTrackedTasks(index)).map( + task => ({ + uuid: uuidv4(), + ...kanbn.hydrateTask(index, task) + }) + ); + } catch (error) { + vscode.window.showErrorMessage(error instanceof Error ? error.message : error); + return; + } + let task = null; if (taskId) { - try { - task = await kanbn.hydrateTask(index, await kanbn.getTask(taskId)); - } catch (error) { - vscode.window.showErrorMessage(error instanceof Error ? error.message : error); - return; - } + task = tasks.find(t => t.id === taskId) ?? null; } // If no columnName is specified, use the first column @@ -60,7 +68,7 @@ export default class KanbnTaskPanel { index, task, columnName: taskPanel._columnName, - tasks: await kanbn.loadAllTrackedTasks(index), + tasks, dateFormat: kanbn.getDateFormat(index) }); } diff --git a/package-lock.json b/package-lock.json index a682edd..cbde04e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -3355,6 +3355,12 @@ "resolved": "https://registry.npmjs.org/@types/dateformat/-/dateformat-3.0.1.tgz", "integrity": "sha512-KlPPdikagvL6ELjWsljbyDIPzNCeliYkqRpI+zea99vBBbCIA5JNshZAwQKTON139c87y9qvTFVgkFd14rtS4g==" }, + "@types/git-user-name": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@types/git-user-name/-/git-user-name-2.0.0.tgz", + "integrity": "sha512-bZhPykkyyPdHW2wMc30aLIWntMIMO49jWKGQipPg3RQNGq2LdAnW7OCinHew+M8EpZ5qHNyFynIiou+UyxOfww==", + "dev": true + }, "@types/jest": { "version": "23.3.14", "resolved": "https://registry.npmjs.org/@types/jest/-/jest-23.3.14.tgz", @@ -3417,6 +3423,12 @@ "integrity": "sha512-42zEJkBpNfMEAvWR5WlwtTH22oDzcMjFsL9gDGExwF8X8WvAiw7Vwop7hPw03QT8TKfec83LwbHj6SvpqM4ELQ==", "dev": true }, + "@types/uuid": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-8.3.0.tgz", + "integrity": "sha512-eQ9qFW/fhfGJF8WKHGEHZEyVWfZxrT+6CLIJGBcZPfxUh/+BnEj+UCGYMlr9qZuX/2AltsvwrGqp0LhEW8D0zQ==", + "dev": true + }, "@webassemblyjs/ast": { "version": "1.7.11", "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.7.11.tgz", @@ -17478,6 +17490,14 @@ "tough-cookie": "~2.5.0", "tunnel-agent": "^0.6.0", "uuid": "^3.3.2" + }, + "dependencies": { + "uuid": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", + "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==", + "dev": true + } } }, "request-promise-core": { @@ -18859,6 +18879,12 @@ "requires": { "websocket-driver": ">=0.5.1" } + }, + "uuid": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", + "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==", + "dev": true } } }, @@ -20076,10 +20102,9 @@ "dev": true }, "uuid": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", - "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==", - "dev": true + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==" }, "validate-npm-package-license": { "version": "3.0.4", @@ -20913,6 +20938,14 @@ "requires": { "ansi-colors": "^3.0.0", "uuid": "^3.3.2" + }, + "dependencies": { + "uuid": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", + "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==", + "dev": true + } } }, "webpack-manifest-plugin": { diff --git a/package.json b/package.json index 2cce83e..b9b5214 100644 --- a/package.json +++ b/package.json @@ -39,6 +39,7 @@ "react-beautiful-dnd": "12.2.0", "react-dom": "^16.3.2", "terser": "3.14.1", + "uuid": "^8.3.2", "vscode": "^1.1.37", "vscode-codicons": "^0.0.15" }, @@ -51,11 +52,13 @@ "eject": "react-scripts eject" }, "devDependencies": { + "@types/git-user-name": "^2.0.0", "@types/jest": "^23.3.14", "@types/lodash": "^4.14.168", "@types/node": "^10.17.56", "@types/react": "^16.14.5", "@types/react-dom": "^16.9.12", + "@types/uuid": "^8.3.0", "react-scripts": "^2.1.8", "rewire": "^4.0.1", "typescript": "^4.0.2" diff --git a/src/App.tsx b/src/App.tsx index 2a81b6f..b79afc3 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -18,13 +18,14 @@ function App() { const [completedColumns, setCompletedColumns] = useState([]); const [dateFormat, setDateFormat] = useState(''); const [task, setTask] = useState({}); + const [tasks, setTasks] = useState({}); const [columnName, setColumnName] = useState(''); const [columnNames, setColumnNames] = useState([] as string[]); window.addEventListener('message', event => { + const tasks = Object.fromEntries(event.data.tasks.map(task => [task.id, task])); switch (event.data.type) { case 'index': - const tasks = Object.fromEntries(event.data.tasks.map(task => [task.id, task])); setName(event.data.index.name); setDescription(event.data.index.description); setColumns(Object.fromEntries( @@ -39,6 +40,7 @@ function App() { case 'task': setTask(event.data.task); + setTasks(tasks); setColumnName(event.data.columnName); setColumnNames(Object.keys(event.data.index.columns)); break; @@ -69,6 +71,7 @@ function App() { type === 'task' && { const [, setColumns] = useState(columns); return ( -
+
onDragEnd(result, columns, setColumns, vscode)} > @@ -80,9 +75,6 @@ const Board = ({ columns, startedColumns, completedColumns, dateFormat, vscode } 'kanbn-column', `kanbn-column-${paramCase(columnName)}` ].join(' ')} - style={{ - flex: 1 - }} key={columnName} >

diff --git a/src/KanbnTask.d.ts b/src/KanbnTask.d.ts index 56c8d34..b30ea46 100644 --- a/src/KanbnTask.d.ts +++ b/src/KanbnTask.d.ts @@ -1,4 +1,5 @@ declare type KanbnTask = { + uuid?: string, id: string, name: string, description: string, diff --git a/src/TaskEditor.tsx b/src/TaskEditor.tsx index 492a4a4..2a89700 100644 --- a/src/TaskEditor.tsx +++ b/src/TaskEditor.tsx @@ -1,12 +1,13 @@ import React from 'react'; -import { Formik } from 'formik'; +import { Formik, Form, Field, ErrorMessage, FieldArray } from 'formik'; import formatDate from 'dateformat'; import VSCodeApi from './VSCodeApi'; import { paramCase } from 'param-case'; -import * as gitUsername from 'git-user-name'; +import gitUsername from 'git-user-name'; -const TaskEditor = ({ task, columnName, columnNames, dateFormat, vscode }: { +const TaskEditor = ({ task, tasks, columnName, columnNames, dateFormat, vscode }: { task: KanbnTask|null, + tasks: Record, columnName: string, columnNames: string[], dateFormat: string, @@ -32,6 +33,7 @@ const TaskEditor = ({ task, columnName, columnNames, dateFormat, vscode }: {

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

{ 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'; - // } + if (!values.name) { + errors.name = 'Task name is required.'; + } + + // Check if the id is already in use + if (values.id in tasks && tasks[values.id].uuid !== values.uuid) { + errors.name = 'There is already a task with the same name or id.'; + } return errors; }} onSubmit={(values, { setSubmitting }) => { @@ -73,125 +76,246 @@ const TaskEditor = ({ task, columnName, columnNames, dateFormat, vscode }: { > {({ values, - errors, - touched, handleChange, - handleBlur, - handleSubmit, isSubmitting }) => ( -
-
+ +
-
-