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

Get Groceries

\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

Get Groceries

\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(); }