diff --git a/index.html b/index.html
index f9c05e3..a9bda69 100644
--- a/index.html
+++ b/index.html
@@ -88,6 +88,7 @@
Error Occured
rows="16"
>
+
diff --git a/index.js b/index.js
index a953976..39af72c 100644
--- a/index.js
+++ b/index.js
@@ -16,7 +16,7 @@
\********************/
/***/ ((__unused_webpack_module, exports, __webpack_require__) => {
-eval("\nObject.defineProperty(exports, \"__esModule\", ({ value: true }));\nexports.listNameToID = exports.API = void 0;\nconst cardhandler_1 = __webpack_require__(/*! ./cardhandler */ \"./src/cardhandler.js\");\nconst cardlist_1 = __webpack_require__(/*! ./cardlist */ \"./src/cardlist.js\");\nconst layout_1 = __webpack_require__(/*! ./layout */ \"./src/layout.js\");\nconst task_1 = __webpack_require__(/*! ./task */ \"./src/task.js\");\nclass API {\n get Tasks() {\n return this.tasks;\n }\n get TaskLists() {\n return Array.from(this.cardHandler.Lists.values());\n }\n get CurrentTaskIndex() {\n return this.currentTaskIndex;\n }\n constructor() {\n this.currentTaskIndex = 0;\n this.tasks = new Map();\n this.cardHandler = new cardhandler_1.CardHandler();\n }\n serializeToLocalStorage() {\n const kanbanStr = JSON.stringify({\n index: this.currentTaskIndex,\n tasks: Array.from(this.tasks.entries()),\n taskLists: Array.from(this.cardHandler.Lists.keys()),\n });\n localStorage.setItem('my-kanban-board', kanbanStr);\n }\n tryLoadFromLocalStorage() {\n const boardDataStr = localStorage.getItem('my-kanban-board');\n if (!boardDataStr) {\n return;\n }\n const boardData = JSON.parse(boardDataStr);\n this.currentTaskIndex = boardData.index;\n boardData.tasks.forEach((item) => this.tasks.set(item[0], task_1.TaskItem.fromLoadedData(item[1])));\n boardData.taskLists.forEach((list) => this.cardHandler.push(new cardlist_1.CardList(list, listNameToID(list))));\n (0, layout_1.generateElementsForLoadedData)(this);\n }\n tryAddNewList() {\n return this.cardHandler.tryAddNewList();\n }\n taskListContains(identifier) {\n return this.cardHandler.Lists.has(identifier);\n }\n renameTaskList(oldID, newID) {\n this.cardHandler.renameTaskList(oldID, newID);\n }\n deleteTaskList(identifier) {\n this.cardHandler.removeWithID(identifier);\n }\n addNewTask(createdAt, listID, title, tag, dueDate, color, description) {\n this.tasks.set(this.currentTaskIndex, new task_1.TaskItem(this.currentTaskIndex, createdAt, listID, title, tag, dueDate, color, description));\n this.advanceTask();\n }\n getTaskFromID(identifier) {\n const indexStr = identifier.lastIndexOf('-') + 1;\n const taskIndex = +identifier.slice(indexStr);\n const task = this.tasks.get(taskIndex);\n if (!task) {\n console.log(identifier);\n throw new Error(`Fatal task fetch error. Tried to fetch item with index: ${taskIndex}`);\n }\n return task;\n }\n advanceTask() {\n this.currentTaskIndex += 1;\n }\n}\nexports.API = API;\nfunction listNameToID(value) {\n let newName = new String(value);\n newName = newName.replace(' ', '-').toLocaleLowerCase().concat('-list');\n return newName.toString();\n}\nexports.listNameToID = listNameToID;\n\n\n//# sourceURL=webpack://kanban-board/./src/api.js?");
+eval("\nObject.defineProperty(exports, \"__esModule\", ({ value: true }));\nexports.listNameToID = exports.API = void 0;\nconst cardhandler_1 = __webpack_require__(/*! ./cardhandler */ \"./src/cardhandler.js\");\nconst cardlist_1 = __webpack_require__(/*! ./cardlist */ \"./src/cardlist.js\");\nconst layout_1 = __webpack_require__(/*! ./layout */ \"./src/layout.js\");\nconst task_1 = __webpack_require__(/*! ./task */ \"./src/task.js\");\nclass API {\n get Tasks() {\n return this.tasks;\n }\n get TaskLists() {\n return Array.from(this.cardHandler.Lists.values());\n }\n get CurrentTaskIndex() {\n return this.currentTaskIndex;\n }\n constructor() {\n this.currentTaskIndex = 0;\n this.tasks = new Map();\n this.cardHandler = new cardhandler_1.CardHandler();\n }\n serializeToLocalStorage() {\n const kanbanStr = JSON.stringify({\n index: this.currentTaskIndex,\n tasks: Array.from(this.tasks.entries()),\n taskLists: Array.from(this.cardHandler.Lists.keys()),\n });\n localStorage.setItem('my-kanban-board', kanbanStr);\n }\n tryLoadFromLocalStorage() {\n const boardDataStr = localStorage.getItem('my-kanban-board');\n if (!boardDataStr) {\n return;\n }\n const boardData = JSON.parse(boardDataStr);\n this.currentTaskIndex = boardData.index;\n boardData.tasks.forEach((item) => this.tasks.set(item[0], task_1.TaskItem.fromLoadedData(item[1])));\n boardData.taskLists.forEach((list) => this.cardHandler.push(new cardlist_1.CardList(list, listNameToID(list))));\n this.cardHandler.setHasDefault();\n (0, layout_1.generateElementsFromLoadedData)(this);\n }\n removeTaskItem(item) {\n this.tasks.delete(item.ID);\n }\n tryAddNewList() {\n return this.cardHandler.tryAddNewList();\n }\n taskListContains(identifier) {\n return this.cardHandler.Lists.has(identifier);\n }\n renameTaskList(oldID, newID) {\n this.cardHandler.renameTaskList(oldID, newID);\n this.cardHandler.setHasDefault();\n }\n deleteTaskList(identifier) {\n this.cardHandler.removeWithID(identifier);\n }\n addNewTask(createdAt, listID, title, tag, dueDate, color, description) {\n this.tasks.set(this.currentTaskIndex, new task_1.TaskItem(this.currentTaskIndex, createdAt, listID, title, tag, dueDate, color, description));\n this.advanceTask();\n }\n getTaskFromID(identifier) {\n const indexStr = identifier.lastIndexOf('-') + 1;\n const taskIndex = +identifier.slice(indexStr);\n const task = this.tasks.get(taskIndex);\n if (!task) {\n console.log(identifier);\n throw new Error(`Fatal task fetch error. Tried to fetch item with index: ${taskIndex}`);\n }\n return task;\n }\n advanceTask() {\n this.currentTaskIndex += 1;\n }\n}\nexports.API = API;\nfunction listNameToID(value) {\n let newName = new String(value);\n newName = newName.replace(' ', '-').toLocaleLowerCase().concat('-list');\n return newName.toString();\n}\nexports.listNameToID = listNameToID;\n\n\n//# sourceURL=webpack://kanban-board/./src/api.js?");
/***/ }),
@@ -26,7 +26,7 @@ eval("\nObject.defineProperty(exports, \"__esModule\", ({ value: true }));\nexpo
\****************************/
/***/ ((__unused_webpack_module, exports, __webpack_require__) => {
-eval("\nObject.defineProperty(exports, \"__esModule\", ({ value: true }));\nexports.CardHandler = void 0;\nconst api_1 = __webpack_require__(/*! ./api */ \"./src/api.js\");\nconst cardlist_1 = __webpack_require__(/*! ./cardlist */ \"./src/cardlist.js\");\nclass CardHandler {\n get HasDefault() {\n return this.hasDefault;\n }\n get Lists() {\n return this.lists;\n }\n constructor() {\n this.hasDefault = false;\n this.lists = new Map();\n }\n push(card) {\n this.lists.set(card.Identifier, card);\n }\n tryAddNewList() {\n if (this.lists.has('New List')) {\n return false;\n }\n this.lists.set('New List', new cardlist_1.CardList('New List', 'new-list'));\n return true;\n }\n removeWithID(identifier) {\n for (let [id, list] of this.lists.entries()) {\n if (list.ElementID === identifier) {\n this.lists.delete(id);\n return;\n }\n }\n }\n renameTaskList(oldID, newID) {\n const oldCard = this.lists.get(oldID);\n if (oldCard) {\n this.lists.delete(oldID);\n this.lists.set(newID, oldCard);\n return;\n }\n this.lists.set(newID, new cardlist_1.CardList(newID, (0, api_1.listNameToID)(newID)));\n }\n}\nexports.CardHandler = CardHandler;\n\n\n//# sourceURL=webpack://kanban-board/./src/cardhandler.js?");
+eval("\nObject.defineProperty(exports, \"__esModule\", ({ value: true }));\nexports.CardHandler = void 0;\nconst api_1 = __webpack_require__(/*! ./api */ \"./src/api.js\");\nconst cardlist_1 = __webpack_require__(/*! ./cardlist */ \"./src/cardlist.js\");\nclass CardHandler {\n get HasDefault() {\n return this.hasDefault;\n }\n get Lists() {\n return this.lists;\n }\n constructor() {\n this.hasDefault = false;\n this.lists = new Map();\n }\n setHasDefault() {\n this.hasDefault = this.lists.has('New List');\n }\n push(list) {\n this.lists.set(list.Identifier, list);\n }\n tryAddNewList() {\n if (this.hasDefault) {\n return false;\n }\n this.lists.set('New List', new cardlist_1.CardList('New List', 'new-list'));\n this.hasDefault = true;\n return true;\n }\n removeWithID(identifier) {\n for (let [id, list] of this.lists.entries()) {\n if (list.ElementID === identifier) {\n this.lists.delete(id);\n this.setHasDefault();\n return;\n }\n }\n }\n renameTaskList(oldID, newID) {\n const oldCard = this.lists.get(oldID);\n if (oldCard) {\n this.lists.delete(oldID);\n this.lists.set(newID, oldCard);\n return;\n }\n this.lists.set(newID, new cardlist_1.CardList(newID, (0, api_1.listNameToID)(newID)));\n }\n}\nexports.CardHandler = CardHandler;\n\n\n//# sourceURL=webpack://kanban-board/./src/cardhandler.js?");
/***/ }),
@@ -56,7 +56,7 @@ eval("\nObject.defineProperty(exports, \"__esModule\", ({ value: true }));\nexpo
\**********************/
/***/ ((__unused_webpack_module, exports, __webpack_require__) => {
-eval("\nObject.defineProperty(exports, \"__esModule\", ({ value: true }));\nconst api_1 = __webpack_require__(/*! ./api */ \"./src/api.js\");\nconst dragging_1 = __webpack_require__(/*! ./dragging */ \"./src/dragging.js\");\nconst layout_1 = __webpack_require__(/*! ./layout */ \"./src/layout.js\");\n// 30 seconds\nconst serialiseInterval = 30 * 1000;\nfunction main() {\n const api = new api_1.API();\n api.tryLoadFromLocalStorage();\n setInterval(() => api.serializeToLocalStorage(), serialiseInterval);\n (0, layout_1.setupAddTask)(api);\n (0, layout_1.setupListAddButton)(api);\n (0, dragging_1.setupDraggables)(api);\n (0, layout_1.setupErrorModalLayout)();\n (0, layout_1.setupModalLayout)();\n}\nwindow.addEventListener('load', main);\n\n\n//# sourceURL=webpack://kanban-board/./src/index.js?");
+eval("\nObject.defineProperty(exports, \"__esModule\", ({ value: true }));\nconst api_1 = __webpack_require__(/*! ./api */ \"./src/api.js\");\nconst dragging_1 = __webpack_require__(/*! ./dragging */ \"./src/dragging.js\");\nconst layout_1 = __webpack_require__(/*! ./layout */ \"./src/layout.js\");\nfunction main() {\n const api = new api_1.API();\n api.tryLoadFromLocalStorage();\n (0, layout_1.setupAddTask)(api);\n (0, layout_1.setupListAddButton)(api);\n (0, dragging_1.setupDraggables)(api);\n (0, layout_1.setupErrorModalLayout)();\n (0, layout_1.setupModalLayout)();\n}\nwindow.addEventListener('load', main);\n\n\n//# sourceURL=webpack://kanban-board/./src/index.js?");
/***/ }),
@@ -66,7 +66,7 @@ eval("\nObject.defineProperty(exports, \"__esModule\", ({ value: true }));\ncons
\***********************/
/***/ ((__unused_webpack_module, exports, __webpack_require__) => {
-eval("\nObject.defineProperty(exports, \"__esModule\", ({ value: true }));\nexports.generateElementsForLoadedData = exports.setupNewTaskModalFields = exports.setupAddTask = exports.showErrorModal = exports.addDragListeners = exports.setupModalLayout = exports.setupErrorModalLayout = exports.setupListAddButton = void 0;\nconst api_1 = __webpack_require__(/*! ./api */ \"./src/api.js\");\nconst dragging_1 = __webpack_require__(/*! ./dragging */ \"./src/dragging.js\");\nconst task_1 = __webpack_require__(/*! ./task */ \"./src/task.js\");\nfunction setupListAddButton(api) {\n const btn = document.querySelector('#list-add-btn');\n btn.onclick = () => {\n const listDiv = document.querySelector('#task-lists');\n const listEl = createDefaultList(api);\n if (!listEl) {\n showErrorModal('Cannot create default list, as one already exists.');\n return;\n }\n listDiv.insertBefore(listEl, listDiv.children[listDiv.children.length - 1]);\n };\n}\nexports.setupListAddButton = setupListAddButton;\nfunction setupErrorModalLayout() {\n const modal = document.querySelector('#error-modal');\n const close = document.querySelector('#error-modal-close');\n close.onclick = function (e) {\n e.preventDefault();\n modal.style.display = 'none';\n };\n}\nexports.setupErrorModalLayout = setupErrorModalLayout;\nfunction setupModalLayout() {\n // Get the modal\n const modal = document.querySelector('#modal');\n const modalHeader = document.querySelector('#modal-header');\n // Get the element that closes the modal\n const span = document.querySelector('#close');\n const colorSelected = document.querySelector('#color-selected');\n const firstStyle = colorSelected.options[0].style;\n colorSelected.value = firstStyle.backgroundColor;\n colorSelected.style.background = firstStyle.backgroundColor;\n // When the user clicks on (x), close the modal\n span.onclick = function () {\n modal.style.display = 'none';\n };\n colorSelected.onchange = function () {\n let color = colorSelected.options[colorSelected.selectedIndex].style.backgroundColor;\n colorSelected.style.backgroundColor = color;\n modalHeader.style.backgroundColor = color;\n };\n}\nexports.setupModalLayout = setupModalLayout;\nfunction addDragListeners(el) {\n el.addEventListener('dragstart', () => {\n el.classList.add('is-dragging');\n });\n el.addEventListener('dragend', () => {\n el.classList.remove('is-dragging');\n });\n}\nexports.addDragListeners = addDragListeners;\nfunction showErrorModal(message) {\n const errorModal = document.querySelector('#error-modal');\n const modalText = document.querySelector('#error-modal-text');\n modalText.innerText = message;\n errorModal.style.display = 'block';\n}\nexports.showErrorModal = showErrorModal;\nfunction setupAddTask(api) {\n const form = document.querySelector('#add-task-btn');\n form.onclick = (e) => {\n e.preventDefault();\n const lists = document.querySelectorAll('.swim-list');\n if (lists.length === 0) {\n showErrorModal('No lists available to add a task.');\n return;\n }\n const modal = document.querySelector('#modal');\n modal.style.display = 'block';\n setupNewTaskModalFields(api);\n };\n}\nexports.setupAddTask = setupAddTask;\nfunction setupNewTaskModalFields(api) {\n const modal = document.querySelector('#modal');\n const modalHeader = document.querySelector('#modal-header');\n const createdOn = document.querySelector('#created-on');\n const taskTitle = document.querySelector('#task-title');\n const taskDesc = document.querySelector('#task-desc');\n const saveBtn = document.querySelector('#modal-save-btn');\n const tagKind = document.querySelector('#kind-option');\n tagKind.value = 'prg';\n const dueDate = document.querySelector('#due-date');\n dueDate.valueAsDate = null;\n const colorSelected = document.querySelector('#color-selected');\n const firstStyle = colorSelected.options[0].style;\n colorSelected.value = firstStyle.backgroundColor;\n colorSelected.style.background = firstStyle.backgroundColor;\n modalHeader.style.backgroundColor = colorSelected.value;\n const taskTitleText = `Task Title #${api.CurrentTaskIndex + 1}`;\n taskTitle.value = taskTitleText;\n taskDesc.value = '';\n const date = new Date();\n modal.style.display = 'block';\n createdOn.innerText = `Created on ${date.toDateString()}`;\n saveBtn.onclick = () => {\n if (taskTitle.value === taskTitleText) {\n showErrorModal('Invalid title name.');\n return;\n }\n // Add to todo list\n const firstList = document.querySelectorAll('.swim-list')[0];\n const listContent = firstList.querySelector('.list-content');\n const taskEl = createTaskItemElement(api, taskTitle.value, api.CurrentTaskIndex);\n listContent.appendChild(taskEl);\n const tagKind = document.querySelector('#kind-option');\n const dueDate = document.querySelector('#due-date');\n const colorSelected = document.querySelector('#color-selected');\n api.addNewTask(date, firstList.id, taskTitle.value, (0, task_1.tagStrToKind)(tagKind.value), dueDate.valueAsDate, colorSelected.style.background, taskDesc.value);\n // Hide modal\n modal.style.display = 'none';\n };\n}\nexports.setupNewTaskModalFields = setupNewTaskModalFields;\nfunction generateElementsForLoadedData(api) {\n const listDiv = document.querySelector('#task-lists');\n // Generate all list elements\n api.TaskLists.forEach((list) => {\n const listEl = createListWithName(api, list.Identifier);\n listDiv.insertBefore(listEl, listDiv.children[listDiv.children.length - 1]);\n });\n // Generate all task elements\n api.Tasks.forEach((task) => {\n console.log(task);\n console.log(`Loading '${task.ListID}'`);\n const list = document.querySelector(`#${task.ListID}`);\n const content = list.querySelector('.list-content');\n content.appendChild(createTaskItemElement(api, task.Title, task.ID));\n });\n}\nexports.generateElementsForLoadedData = generateElementsForLoadedData;\nfunction populateTaskModalFields(api, taskID) {\n const task = api.getTaskFromID(taskID);\n const modal = document.querySelector('#modal');\n const modalHeader = document.querySelector('#modal-header');\n const createdOn = document.querySelector('#created-on');\n const taskTitle = document.querySelector('#task-title');\n const saveBtn = document.querySelector('#modal-save-btn');\n const taskDesc = document.querySelector('#task-desc');\n const tagKind = document.querySelector('#kind-option');\n const dueDate = document.querySelector('#due-date-option');\n const colorSelected = document.querySelector('#color-selected');\n const taskTitleText = task.Title;\n taskTitle.value = taskTitleText;\n taskDesc.value = task.Description;\n createdOn.innerText = `Created on ${task.CreatedAt.toDateString()}`;\n tagKind.value = (0, task_1.tagKindToStr)(task.Tag);\n dueDate.valueAsDate = task.DueDate;\n modalHeader.style.background = task.Color;\n colorSelected.value = task.Color;\n colorSelected.style.background = task.Color;\n saveBtn.onclick = () => {\n task.applyFields(taskTitle.value, (0, task_1.tagStrToKind)(tagKind.value), dueDate.valueAsDate, colorSelected.style.background, taskDesc.value);\n // Hide modal\n modal.style.display = 'none';\n };\n // Show modal\n modal.style.display = 'block';\n}\nfunction createTaskItemElement(api, taskTitle, index) {\n /**\n * \n */\n const taskID = `task-id-${index}`;\n const newTask = document.createElement('div');\n newTask.className = 'task';\n newTask.id = taskID;\n newTask.setAttribute('draggable', 'true');\n const taskTitleEl = document.createElement('p');\n newTask.appendChild(taskTitleEl);\n taskTitleEl.className = 'task-title';\n taskTitleEl.innerText = taskTitle;\n const innerBtn = document.createElement('button');\n newTask.appendChild(innerBtn);\n innerBtn.className = 'task-edit';\n innerBtn.innerText = '...';\n innerBtn.onclick = () => populateTaskModalFields(api, taskID);\n addDragListeners(newTask);\n return newTask;\n}\nfunction createDefaultList(api) {\n if (!api.tryAddNewList()) {\n return null;\n }\n return createListWithName(api, 'New List');\n}\nfunction createListWithName(api, title) {\n /**\n * \n TODO\n
\n */\n const listID = (0, api_1.listNameToID)(title);\n const el = document.createElement('div');\n el.className = 'swim-list';\n el.id = listID;\n const header = document.createElement('div');\n el.appendChild(header);\n header.className = 'list-heading-inner-text';\n const headerTitle = document.createElement('input');\n header.appendChild(headerTitle);\n headerTitle.className = 'list-heading-input';\n headerTitle.type = 'text';\n headerTitle.value = title;\n headerTitle.defaultValue = title;\n headerTitle.onchange = (e) => listHeaderChange(el, headerTitle, e, api);\n const deleteSpan = document.createElement('span');\n header.appendChild(deleteSpan);\n deleteSpan.className = 'list-delete-span';\n deleteSpan.innerText = '\\u{00D7}';\n deleteSpan.onclick = (e) => {\n e.preventDefault();\n if (el.children.length > 1) {\n showErrorModal('Cannot delete list that contains tasks.');\n return;\n }\n api.deleteTaskList(listID);\n const listDiv = document.querySelector('#task-lists');\n listDiv.removeChild(el);\n };\n const listContentZone = document.createElement('div');\n el.appendChild(listContentZone);\n listContentZone.setAttribute('value', listID);\n listContentZone.className = 'list-content';\n (0, dragging_1.setupListDragZone)(api, listContentZone);\n return el;\n}\nfunction listHeaderChange(el, self, e, api) {\n e.preventDefault();\n const newID = (0, api_1.listNameToID)(self.value);\n if (el.id === newID) {\n return;\n }\n if (api.taskListContains(self.value)) {\n showErrorModal(`Task list with name '${self.value}' already exists.`);\n self.value = self.defaultValue;\n return;\n }\n const content = el.querySelector('.list-content');\n content.setAttribute('value', newID);\n api.renameTaskList(self.defaultValue, self.value);\n self.defaultValue = self.value;\n el.id = newID;\n}\n\n\n//# sourceURL=webpack://kanban-board/./src/layout.js?");
+eval("\nObject.defineProperty(exports, \"__esModule\", ({ value: true }));\nexports.generateElementsFromLoadedData = exports.setupNewTaskModalFields = exports.setupAddTask = exports.showErrorModal = exports.addDragListeners = exports.setupModalLayout = exports.setupErrorModalLayout = exports.setupListAddButton = void 0;\nconst api_1 = __webpack_require__(/*! ./api */ \"./src/api.js\");\nconst dragging_1 = __webpack_require__(/*! ./dragging */ \"./src/dragging.js\");\nconst task_1 = __webpack_require__(/*! ./task */ \"./src/task.js\");\nfunction setupListAddButton(api) {\n const btn = document.querySelector('#list-add-btn');\n btn.onclick = () => {\n const listDiv = document.querySelector('#task-lists');\n const listEl = createDefaultList(api);\n if (!listEl) {\n showErrorModal('Cannot create default list, as one already exists.');\n return;\n }\n listDiv.insertBefore(listEl, listDiv.children[listDiv.children.length - 1]);\n // Save results to disk\n api.serializeToLocalStorage();\n };\n}\nexports.setupListAddButton = setupListAddButton;\nfunction setupErrorModalLayout() {\n const modal = document.querySelector('#error-modal');\n const close = document.querySelector('#error-modal-close');\n close.onclick = function (e) {\n e.preventDefault();\n modal.style.display = 'none';\n };\n}\nexports.setupErrorModalLayout = setupErrorModalLayout;\nfunction setupModalLayout() {\n // Get the modal\n const modal = document.querySelector('#modal');\n const modalHeader = document.querySelector('#modal-header');\n // Get the element that closes the modal\n const span = document.querySelector('#close');\n const colorSelected = document.querySelector('#color-selected');\n const firstStyle = colorSelected.options[0].style;\n colorSelected.value = firstStyle.backgroundColor;\n colorSelected.style.background = firstStyle.backgroundColor;\n span.onclick = function () {\n modal.style.display = 'none';\n };\n colorSelected.onchange = function () {\n let color = colorSelected.options[colorSelected.selectedIndex].style.backgroundColor;\n colorSelected.style.backgroundColor = color;\n modalHeader.style.backgroundColor = color;\n };\n}\nexports.setupModalLayout = setupModalLayout;\nfunction addDragListeners(el) {\n el.addEventListener('dragstart', () => {\n el.classList.add('is-dragging');\n });\n el.addEventListener('dragend', () => {\n el.classList.remove('is-dragging');\n });\n}\nexports.addDragListeners = addDragListeners;\nfunction showErrorModal(message) {\n const errorModal = document.querySelector('#error-modal');\n const modalText = document.querySelector('#error-modal-text');\n modalText.innerText = message;\n errorModal.style.display = 'block';\n}\nexports.showErrorModal = showErrorModal;\nfunction setupAddTask(api) {\n const form = document.querySelector('#add-task-btn');\n form.onclick = (e) => {\n e.preventDefault();\n const lists = document.querySelectorAll('.swim-list');\n if (lists.length === 0) {\n showErrorModal('No lists available to add a task.');\n return;\n }\n const modal = document.querySelector('#modal');\n modal.style.display = 'block';\n setupNewTaskModalFields(api);\n };\n}\nexports.setupAddTask = setupAddTask;\nfunction setupNewTaskModalFields(api) {\n const modal = document.querySelector('#modal');\n const modalHeader = document.querySelector('#modal-header');\n const createdOn = document.querySelector('#created-on');\n const taskTitle = document.querySelector('#task-title');\n const taskDesc = document.querySelector('#task-desc');\n const saveBtn = document.querySelector('#modal-save-btn');\n const deleteBtn = document.querySelector('#modal-delete-btn');\n const tagKind = document.querySelector('#kind-option');\n tagKind.value = 'prg';\n const dueDate = document.querySelector('#due-date');\n dueDate.valueAsDate = null;\n const colorSelected = document.querySelector('#color-selected');\n const firstStyle = colorSelected.options[0].style;\n colorSelected.value = firstStyle.backgroundColor;\n colorSelected.style.background = firstStyle.backgroundColor;\n modalHeader.style.backgroundColor = colorSelected.value;\n const taskTitleText = `Task Title #${api.CurrentTaskIndex + 1}`;\n taskTitle.value = taskTitleText;\n taskDesc.value = '';\n const date = new Date();\n modal.style.display = 'block';\n createdOn.innerText = `Created on ${date.toDateString()}`;\n saveBtn.onclick = () => {\n if (taskTitle.value === taskTitleText) {\n showErrorModal('Invalid title name.');\n return;\n }\n // Add to todo list\n const firstList = document.querySelectorAll('.swim-list')[0];\n const listContent = firstList.querySelector('.list-content');\n const taskEl = createTaskItemElement(api, taskTitle.value, api.CurrentTaskIndex);\n listContent.appendChild(taskEl);\n const tagKind = document.querySelector('#kind-option');\n const dueDate = document.querySelector('#due-date');\n const colorSelected = document.querySelector('#color-selected');\n api.addNewTask(date, firstList.id, taskTitle.value, (0, task_1.tagStrToKind)(tagKind.value), dueDate.valueAsDate, colorSelected.style.background, taskDesc.value);\n // Save results to disk\n api.serializeToLocalStorage();\n // Hide modal\n modal.style.display = 'none';\n };\n // Hide delete button when creating new task\n deleteBtn.style.display = 'none';\n}\nexports.setupNewTaskModalFields = setupNewTaskModalFields;\nfunction generateElementsFromLoadedData(api) {\n const listDiv = document.querySelector('#task-lists');\n // Generate all list elements\n api.TaskLists.forEach((list) => {\n const listEl = createListWithName(api, list.Identifier);\n listDiv.insertBefore(listEl, listDiv.children[listDiv.children.length - 1]);\n });\n // Generate all task elements\n api.Tasks.forEach((task) => {\n const list = document.querySelector(`#${task.ListID}`);\n const content = list.querySelector('.list-content');\n content.appendChild(createTaskItemElement(api, task.Title, task.ID));\n });\n}\nexports.generateElementsFromLoadedData = generateElementsFromLoadedData;\nfunction populateTaskModalFields(api, taskID) {\n const task = api.getTaskFromID(taskID);\n const modal = document.querySelector('#modal');\n const modalHeader = document.querySelector('#modal-header');\n const createdOn = document.querySelector('#created-on');\n const taskTitle = document.querySelector('#task-title');\n const saveBtn = document.querySelector('#modal-save-btn');\n const deleteBtn = document.querySelector('#modal-delete-btn');\n const taskDesc = document.querySelector('#task-desc');\n const tagKind = document.querySelector('#kind-option');\n const dueDate = document.querySelector('#due-date-option');\n const colorSelected = document.querySelector('#color-selected');\n const taskTitleText = task.Title;\n taskTitle.value = taskTitleText;\n taskDesc.value = task.Description;\n createdOn.innerText = `Created on ${task.CreatedAt.toDateString()}`;\n tagKind.value = (0, task_1.tagKindToStr)(task.Tag);\n dueDate.valueAsDate = task.DueDate;\n modalHeader.style.background = task.Color;\n colorSelected.value = task.Color;\n colorSelected.style.background = task.Color;\n saveBtn.onclick = () => {\n task.applyFields(taskTitle.value, (0, task_1.tagStrToKind)(tagKind.value), dueDate.valueAsDate, colorSelected.style.background, taskDesc.value);\n // Save results to disk\n api.serializeToLocalStorage();\n // Hide modal\n modal.style.display = 'none';\n };\n deleteBtn.style.display = 'inline';\n deleteBtn.onclick = () => {\n // TODO: Add confirmation modal\n api.removeTaskItem(task);\n const listEl = document.querySelector(`#${task.ListID}`);\n const content = listEl.querySelector('.list-content');\n content.removeChild(document.querySelector(`#task-id-${task.ID}`));\n // Save results to disk\n api.serializeToLocalStorage();\n // Hide modal\n modal.style.display = 'none';\n };\n // Show modal\n modal.style.display = 'block';\n}\nfunction createTaskItemElement(api, taskTitle, index) {\n /**\n * \n */\n const taskID = `task-id-${index}`;\n const newTask = document.createElement('div');\n newTask.className = 'task';\n newTask.id = taskID;\n newTask.setAttribute('draggable', 'true');\n const taskTitleEl = document.createElement('p');\n newTask.appendChild(taskTitleEl);\n taskTitleEl.className = 'task-title';\n taskTitleEl.innerText = taskTitle;\n const innerBtn = document.createElement('button');\n newTask.appendChild(innerBtn);\n innerBtn.className = 'task-edit';\n innerBtn.innerText = '...';\n innerBtn.onclick = () => populateTaskModalFields(api, taskID);\n addDragListeners(newTask);\n return newTask;\n}\nfunction createDefaultList(api) {\n if (!api.tryAddNewList()) {\n return null;\n }\n return createListWithName(api, 'New List');\n}\nfunction createListWithName(api, title) {\n /**\n * \n TODO\n
\n */\n const listID = (0, api_1.listNameToID)(title);\n const el = document.createElement('div');\n el.className = 'swim-list';\n el.id = listID;\n const header = document.createElement('div');\n el.appendChild(header);\n header.className = 'list-heading-inner-text';\n const headerTitle = document.createElement('input');\n header.appendChild(headerTitle);\n headerTitle.className = 'list-heading-input';\n headerTitle.type = 'text';\n headerTitle.value = title;\n headerTitle.defaultValue = title;\n headerTitle.onchange = (e) => listHeaderChange(el, headerTitle, e, api);\n const listContentZone = document.createElement('div');\n el.appendChild(listContentZone);\n listContentZone.setAttribute('value', listID);\n listContentZone.className = 'list-content';\n (0, dragging_1.setupListDragZone)(api, listContentZone);\n const deleteSpan = document.createElement('span');\n header.appendChild(deleteSpan);\n deleteSpan.className = 'list-delete-span';\n deleteSpan.innerText = '\\u{00D7}';\n deleteSpan.onclick = (e) => {\n e.preventDefault();\n if (listContentZone.children.length > 0) {\n showErrorModal('Cannot delete list that contains tasks.');\n return;\n }\n const listDiv = document.querySelector('#task-lists');\n listDiv.removeChild(el);\n api.deleteTaskList(listID);\n // Save results to disk\n api.serializeToLocalStorage();\n };\n return el;\n}\nfunction listHeaderChange(el, self, e, api) {\n e.preventDefault();\n const newID = (0, api_1.listNameToID)(self.value);\n if (el.id === newID) {\n return;\n }\n if (api.taskListContains(self.value)) {\n showErrorModal(`Task list with name '${self.value}' already exists.`);\n self.value = self.defaultValue;\n return;\n }\n const content = el.querySelector('.list-content');\n content.setAttribute('value', newID);\n api.renameTaskList(self.defaultValue, self.value);\n self.defaultValue = self.value;\n el.id = newID;\n // Save results to disk\n api.serializeToLocalStorage();\n}\n\n\n//# sourceURL=webpack://kanban-board/./src/layout.js?");
/***/ }),
diff --git a/style.css b/style.css
index b65be6c..30ecb66 100644
--- a/style.css
+++ b/style.css
@@ -51,8 +51,11 @@
gap: 16px;
padding: 24px 32px;
+ float: none;
- overflow: hidden;
+ white-space: nowrap;
+ overflow-x: scroll;
+ overflow-y: hidden;
}
.list-heading-text {
@@ -324,6 +327,11 @@
animation-duration: 0.35s;
}
+#modal-content button {
+ padding: 4px;
+ margin-right: 0.5rem;
+}
+
/* Add Animation */
@keyframes animatetop {
from {
diff --git a/ts-src/api.ts b/ts-src/api.ts
index aa52e9e..2dcfa8d 100644
--- a/ts-src/api.ts
+++ b/ts-src/api.ts
@@ -1,6 +1,6 @@
import { CardHandler } from './cardhandler';
import { CardList } from './cardlist';
-import { generateElementsForLoadedData } from './layout';
+import { generateElementsFromLoadedData } from './layout';
import { Color, DueDate, TagKind, TaskItem } from './task';
interface KanbanBoardData {
@@ -57,7 +57,13 @@ export class API {
this.cardHandler.push(new CardList(list, listNameToID(list)))
);
- generateElementsForLoadedData(this);
+ this.cardHandler.setHasDefault();
+
+ generateElementsFromLoadedData(this);
+ }
+
+ public removeTaskItem(item: TaskItem) {
+ this.tasks.delete(item.ID);
}
public tryAddNewList(): boolean {
@@ -70,6 +76,7 @@ export class API {
public renameTaskList(oldID: string, newID: string) {
this.cardHandler.renameTaskList(oldID, newID);
+ this.cardHandler.setHasDefault();
}
public deleteTaskList(identifier: string) {
diff --git a/ts-src/cardhandler.ts b/ts-src/cardhandler.ts
index 006e70a..17ff8eb 100644
--- a/ts-src/cardhandler.ts
+++ b/ts-src/cardhandler.ts
@@ -18,15 +18,22 @@ export class CardHandler {
this.lists = new Map();
}
- public push(card: CardList) {
- this.lists.set(card.Identifier, card);
+ public setHasDefault() {
+ this.hasDefault = this.lists.has('New List');
+ }
+
+ public push(list: CardList) {
+ this.lists.set(list.Identifier, list);
}
public tryAddNewList(): boolean {
- if (this.lists.has('New List')) {
+ if (this.hasDefault) {
return false;
}
+
this.lists.set('New List', new CardList('New List', 'new-list'));
+ this.hasDefault = true;
+
return true;
}
@@ -34,6 +41,7 @@ export class CardHandler {
for (let [id, list] of this.lists.entries()) {
if (list.ElementID === identifier) {
this.lists.delete(id);
+ this.setHasDefault();
return;
}
}
diff --git a/ts-src/index.ts b/ts-src/index.ts
index 1e7825a..247e0ef 100644
--- a/ts-src/index.ts
+++ b/ts-src/index.ts
@@ -7,15 +7,10 @@ import {
setupErrorModalLayout,
} from './layout';
-// 30 seconds
-const serialiseInterval = 30 * 1000;
-
function main() {
const api = new API();
api.tryLoadFromLocalStorage();
- setInterval(() => api.serializeToLocalStorage(), serialiseInterval);
-
setupAddTask(api);
setupListAddButton(api);
setupDraggables(api);
diff --git a/ts-src/layout.ts b/ts-src/layout.ts
index f1bc4e0..7385405 100644
--- a/ts-src/layout.ts
+++ b/ts-src/layout.ts
@@ -13,6 +13,9 @@ export function setupListAddButton(api: API) {
}
listDiv.insertBefore(listEl, listDiv.children[listDiv.children.length - 1]);
+
+ // Save results to disk
+ api.serializeToLocalStorage();
};
}
@@ -38,7 +41,6 @@ export function setupModalLayout() {
colorSelected.value = firstStyle.backgroundColor;
colorSelected.style.background = firstStyle.backgroundColor;
- // When the user clicks on (x), close the modal
span.onclick = function () {
modal.style.display = 'none';
};
@@ -93,6 +95,7 @@ export function setupNewTaskModalFields(api: API) {
const taskTitle = document.querySelector('#task-title') as HTMLInputElement;
const taskDesc = document.querySelector('#task-desc') as HTMLTextAreaElement;
const saveBtn = document.querySelector('#modal-save-btn') as HTMLButtonElement;
+ const deleteBtn = document.querySelector('#modal-delete-btn') as HTMLButtonElement;
const tagKind = document.querySelector('#kind-option') as HTMLSelectElement;
tagKind.value = 'prg';
@@ -142,12 +145,18 @@ export function setupNewTaskModalFields(api: API) {
taskDesc.value
);
+ // Save results to disk
+ api.serializeToLocalStorage();
+
// Hide modal
modal.style.display = 'none';
};
+
+ // Hide delete button when creating new task
+ deleteBtn.style.display = 'none';
}
-export function generateElementsForLoadedData(api: API) {
+export function generateElementsFromLoadedData(api: API) {
const listDiv = document.querySelector('#task-lists') as HTMLDivElement;
// Generate all list elements
@@ -158,9 +167,6 @@ export function generateElementsForLoadedData(api: API) {
// Generate all task elements
api.Tasks.forEach((task) => {
- console.log(task);
-
- console.log(`Loading '${task.ListID}'`);
const list = document.querySelector(`#${task.ListID}`) as HTMLDivElement;
const content = list.querySelector('.list-content') as HTMLDivElement;
content.appendChild(createTaskItemElement(api, task.Title, task.ID));
@@ -175,6 +181,7 @@ function populateTaskModalFields(api: API, taskID: string) {
const createdOn = document.querySelector('#created-on') as HTMLElement;
const taskTitle = document.querySelector('#task-title') as HTMLInputElement;
const saveBtn = document.querySelector('#modal-save-btn') as HTMLButtonElement;
+ const deleteBtn = document.querySelector('#modal-delete-btn') as HTMLButtonElement;
const taskDesc = document.querySelector('#task-desc') as HTMLTextAreaElement;
const tagKind = document.querySelector('#kind-option') as HTMLSelectElement;
@@ -201,6 +208,27 @@ function populateTaskModalFields(api: API, taskID: string) {
colorSelected.style.background,
taskDesc.value
);
+
+ // Save results to disk
+ api.serializeToLocalStorage();
+
+ // Hide modal
+ modal.style.display = 'none';
+ };
+
+ deleteBtn.style.display = 'inline';
+ deleteBtn.onclick = () => {
+ // TODO: Add confirmation modal
+
+ api.removeTaskItem(task);
+
+ const listEl = document.querySelector(`#${task.ListID}`) as HTMLElement;
+ const content = listEl.querySelector('.list-content') as HTMLElement;
+ content.removeChild(document.querySelector(`#task-id-${task.ID}`)!);
+
+ // Save results to disk
+ api.serializeToLocalStorage();
+
// Hide modal
modal.style.display = 'none';
};
@@ -274,6 +302,14 @@ function createListWithName(api: API, title: string): HTMLElement {
headerTitle.onchange = (e) => listHeaderChange(el, headerTitle, e, api);
+ const listContentZone = document.createElement('div') as HTMLDivElement;
+ el.appendChild(listContentZone);
+
+ listContentZone.setAttribute('value', listID);
+ listContentZone.className = 'list-content';
+
+ setupListDragZone(api, listContentZone);
+
const deleteSpan = document.createElement('span') as HTMLSpanElement;
header.appendChild(deleteSpan);
@@ -282,24 +318,19 @@ function createListWithName(api: API, title: string): HTMLElement {
deleteSpan.onclick = (e) => {
e.preventDefault();
- if (el.children.length > 1) {
+ if (listContentZone.children.length > 0) {
showErrorModal('Cannot delete list that contains tasks.');
return;
}
- api.deleteTaskList(listID);
-
const listDiv = document.querySelector('#task-lists') as HTMLDivElement;
listDiv.removeChild(el);
- };
- const listContentZone = document.createElement('div') as HTMLDivElement;
- el.appendChild(listContentZone);
-
- listContentZone.setAttribute('value', listID);
- listContentZone.className = 'list-content';
+ api.deleteTaskList(listID);
- setupListDragZone(api, listContentZone);
+ // Save results to disk
+ api.serializeToLocalStorage();
+ };
return el;
}
@@ -322,6 +353,10 @@ function listHeaderChange(el: HTMLElement, self: HTMLInputElement, e: Event, api
content.setAttribute('value', newID);
api.renameTaskList(self.defaultValue, self.value);
+
self.defaultValue = self.value;
el.id = newID;
+
+ // Save results to disk
+ api.serializeToLocalStorage();
}