Skip to content

Commit

Permalink
build modal of task details (#18)
Browse files Browse the repository at this point in the history
* add task details modal in html, in open status so we can see it. add div for the left side of the modal (which is the first 'column' of the checkbox for marking the task as complete), and right side being all the other values and inputs for existing task's details. rename all ids to avoid nameclashing with the new-task-modal

* generalise checkbox styling between checkbox on the 'preview' task item on the <main> part of the page, and the expanded task details in the modal

* resize (make bigger in the modal) checkboxes that mark a task as complete

* add comment for clarity

* position left and right side of the task details modal form. add margin to the top of the checkbox to manually align

* Adjust margin-top for checkbox to better align with task name. rename right side of the modal for task details to have more meaning (self-documenting)

* Reduce gap and remove comment

* create js module that opens then handles DOM events relating to the modal for task details

* clicking on task element in <main> section opens up the modal

* stretch fields. align gap with add task modal

* reduce width of task details modal

* reduce spacing of checkbox in task details modal

* reduce spacing of checkbox in task details modal

* increase font size of task name

* reduce gap between task attributes

* remove-comments

* generalise handlers for task modals (other than submitting it) so these functions can be reused across add new task modal as well as edit existing task modal

* ensure minimum height of tasks

* closing modal with cancel button

* show existing projects as options in dropdown of existing task expanded

* adjust extra margin above button group

* thicker border around checkbox in modal. add comment for clarity

* generalise urgency styling of modal (orange border on leftside if switched on + svg icon for urgency going from outline to filled) to both add task modal and edit existing task modals

* remove unused comments

* give option for getProjects() in App Controller to get all projects, including 'Inbox' or not

* rewrite html modal for task details in js so it is generated on click of the task object in <main> section (task preview)

this is because of issues with attaching event handlers if it's just sitting in the html

* add event listener for urgency toggle button when the task details modal is being generated

* when a task details modal closes, it is also removed from the html

avoids issues with selecting the right element for event handling.

* remove section that held the task details modal, and align with other modals by adding directly to body element of html

* clear error messages in a handler for modals that is generalised and can be imported

* generalise clearing of form error messages and closing modal as handlers for modals, and imported into flow for adding projects

* extend handlers relating to closing modal  to modal for adding tasks

* add close modal event listeners for viewing task details. remove the child

* attach escape key press event listener to the modal itself, and not the entirety of the document to avoid clashes

* remove event parameter as unused

* store id as a property of tasks

* add optional parameter for task factory that aligns with project factory in that there is a setter for ID, but only can be used when reconstructing from JSON

* skip past certain attributes when generating a task div (in main section of page), including id

* do not show new task's project if it is added there

for future

* remove old flow for console version of app

* add diagram

* stores projects in localStorage in the 'projects' key of localStorage. remove id from the details of project as this is already the key

* write helper function that retrieves project by name

* add retriever to get projects as objects, or aretrieve projects as an array

* enforce uniqueness of project names by readding projectExists check from localStorage helpers, imported into project factory

* get all projects in modal for adding task and viewing (expanding) task details

* ensure correct helper function is called in app controller. remove comments

* catch error on intitial load of app (when localStorage is empty) and return an empty array

* rename task getter

tasks, in the current design of the data structure, will be an array of task ids

* check first if the project being stored already is a project in storage

* fix -- adding new project does not replace all previous projects now

* add new task's unique id to the array called 'tasks' under projects

* inspect task details

* add localstorage helper function that retrieve all tasks as objects

* update diagram to add prefixes to ids to better distinguish tasks and projects from each other

* update diagram to reflect design - we want projects to have a P prefix in its id, and tasks to have T prefix to better identify them. Inbox is going to just be 'inbox' for its id

* check if task is existing

because tasks is an array, we don't want to just be completely replacing the tasks array when storing - we will lose all of the other tasks. instead, we need to get the entire array, remove the task if it exists, then push it back

* align the way that tasks and projects are stored

* update recreation of project from json by adding that the recontsructed project should replace all of its tasks with what is stored in json

* simplify storing of tasks

* enable adding of task to inbox and other projects and properly storing into localStorage

* display project names in nav. add attributes for id and name

id is for future-proofing for when we allow for duplicate project names

* throw error if duplicate project names are found

* create getter for id of project so that it can be called in updateHeader for when user clicks on a new project page

* update header of each projects page. we now try and leverage id of project instead of tasks because it is more unique

* rename methods and variables in project trelating to tasks

clearer that projects just store the more lightweight ids

* write method for projects to get the tasks as objects

because tasks are stored in an array of task Ids for project, this then retrieves the entire object (task) from the ids

* fix -- error when an empty project is clicked from nav and attempted to be displayed. if there are no tasks mapped to the project, just return out of the methods

* fix -- align naming of attribute of projects relating to tasks being task id and not 'tasks' anymore

* refresh the project page you are on after you add a task, whether to that page or not

* ensure ordering of projects in dropdown of modals align with the order in the nav to avoid confusion

* fix -- was calling the old localstorage helper in check (also in localstorage helper) whether a project name already exists

* have localstorage helper retrieve project by id

id is the most unique identifier

* at the app level, get the project by id

* display page based on projectId parameter, not project name anymore

* initially display inbox based on the 'inbox' project id

* update data attributes of project link divs in nav. pass project id to nav link click handler instead of name

simpler data attributes allow easier calling of those attributes' values when required

* pass id of inbox in when clicking the branding in navbar

* update hardcoded data attributes of inbox link

* rename helper

* simplify arrow function

* move pseudocode

* when checking whether project name exists, we remove parentheses with numbers inside, then trim the white spaces on each end, to confirm if the project name already exists

* write setter for name of project

* create project using their name, but now there is a suffix to help distinguish them

* ensure that name is being reconstructed for projects. fixes issue where the project name was not showing on the header of the project page

* add task id as data-attribute to the article element representing the task

purpose is to be able to uniquely select that task, even if it has the same name

* rename method for opening modal for given task. pass task id through viewing details of tasks, down to the generation of the specific task's modal for its details.

at the moment, this hasn't really been utilised within the functions, but it is the best way to uniquely identify the task

* instantiate app controller

this is so that we can properly retrieve the task from localStorage

* add getTask method in App

yes, this is just calling the helper from localstorage, but i don't want any dom elements to be using the helpers - they should ONLY be interfacing with App

* pass task object through to the function creating and displaying the modal for the particular task's details, rather than just the id

this means we don't need to reconstruct the task from its id again at the displayTaskDetailsInModal level

* restore helper function to retrieve a project object ((without its methods just yet) by _name_. this is because when a user is creating a task, they don't get to see the ID, so it can't really interface with the equivalent helper using id. names are also unique now, in that they will always have a suffix

* give unique identifier to both tasks and projects (#22)

* store id as a property of tasks

* add optional parameter for task factory that aligns with project factory in that there is a setter for ID, but only can be used when reconstructing from JSON

* skip past certain attributes when generating a task div (in main section of page), including id

* do not show new task's project if it is added there

for future

* remove old flow for console version of app

* add diagram

* stores projects in localStorage in the 'projects' key of localStorage. remove id from the details of project as this is already the key

* write helper function that retrieves project by name

* add retriever to get projects as objects, or aretrieve projects as an array

* enforce uniqueness of project names by readding projectExists check from localStorage helpers, imported into project factory

* get all projects in modal for adding task and viewing (expanding) task details

* ensure correct helper function is called in app controller. remove comments

* catch error on intitial load of app (when localStorage is empty) and return an empty array

* rename task getter

tasks, in the current design of the data structure, will be an array of task ids

* check first if the project being stored already is a project in storage

* fix -- adding new project does not replace all previous projects now

* add new task's unique id to the array called 'tasks' under projects

* inspect task details

* add localstorage helper function that retrieve all tasks as objects

* update diagram to add prefixes to ids to better distinguish tasks and projects from each other

* update diagram to reflect design - we want projects to have a P prefix in its id, and tasks to have T prefix to better identify them. Inbox is going to just be 'inbox' for its id

* check if task is existing

because tasks is an array, we don't want to just be completely replacing the tasks array when storing - we will lose all of the other tasks. instead, we need to get the entire array, remove the task if it exists, then push it back

* align the way that tasks and projects are stored

* update recreation of project from json by adding that the recontsructed project should replace all of its tasks with what is stored in json

* simplify storing of tasks

* enable adding of task to inbox and other projects and properly storing into localStorage

* display project names in nav. add attributes for id and name

id is for future-proofing for when we allow for duplicate project names

* throw error if duplicate project names are found

* create getter for id of project so that it can be called in updateHeader for when user clicks on a new project page

* update header of each projects page. we now try and leverage id of project instead of tasks because it is more unique

* rename methods and variables in project trelating to tasks

clearer that projects just store the more lightweight ids

* write method for projects to get the tasks as objects

because tasks are stored in an array of task Ids for project, this then retrieves the entire object (task) from the ids

* fix -- error when an empty project is clicked from nav and attempted to be displayed. if there are no tasks mapped to the project, just return out of the methods

* fix -- align naming of attribute of projects relating to tasks being task id and not 'tasks' anymore

* refresh the project page you are on after you add a task, whether to that page or not

* ensure ordering of projects in dropdown of modals align with the order in the nav to avoid confusion

* fix -- was calling the old localstorage helper in check (also in localstorage helper) whether a project name already exists

* have localstorage helper retrieve project by id

id is the most unique identifier

* at the app level, get the project by id

* display page based on projectId parameter, not project name anymore

* initially display inbox based on the 'inbox' project id

* update data attributes of project link divs in nav. pass project id to nav link click handler instead of name

simpler data attributes allow easier calling of those attributes' values when required

* pass id of inbox in when clicking the branding in navbar

* update hardcoded data attributes of inbox link

* rename helper

* simplify arrow function

* move pseudocode

* when checking whether project name exists, we remove parentheses with numbers inside, then trim the white spaces on each end, to confirm if the project name already exists

* write setter for name of project

* create project using their name, but now there is a suffix to help distinguish them

* ensure that name is being reconstructed for projects. fixes issue where the project name was not showing on the header of the project page

* add task id as data-attribute to the article element representing the task

purpose is to be able to uniquely select that task, even if it has the same name

* rename method for opening modal for given task. pass task id through viewing details of tasks, down to the generation of the specific task's modal for its details.

at the moment, this hasn't really been utilised within the functions, but it is the best way to uniquely identify the task

* instantiate app controller

this is so that we can properly retrieve the task from localStorage

* add getTask method in App

yes, this is just calling the helper from localstorage, but i don't want any dom elements to be using the helpers - they should ONLY be interfacing with App

* pass task object through to the function creating and displaying the modal for the particular task's details, rather than just the id

this means we don't need to reconstruct the task from its id again at the displayTaskDetailsInModal level

* restore helper function to retrieve a project object ((without its methods just yet) by _name_. this is because when a user is creating a task, they don't get to see the ID, so it can't really interface with the equivalent helper using id. names are also unique now, in that they will always have a suffix

* update diagram

* rename parameter to clarify what should be passed in

* add getter for name of task

* update name of iterable for clarity on what is being iterated over. populate task name value in modal

* write getters for every property of task. display the description retrieved from localstorage for a task when expanded

nicer to have getters rather than calling .viewDetails().propertyname

* populate project and due date

project is populated by the current page project's name. i've done this so i don't have to manage having 'projects' as its own attribute for tasks which, at the moment, feels like a lot of overhead

* if a task is completed, the checkbox in the modal is already checked

* simplify setting of attribute for urgency button

* if a task is urgent, upon opening, it is also urgent

* remove unused import
  • Loading branch information
henrylin03 authored May 21, 2024
1 parent 6862dfa commit 8c4abe5
Show file tree
Hide file tree
Showing 19 changed files with 523 additions and 173 deletions.
4 changes: 0 additions & 4 deletions docs/console-flow.svg

This file was deleted.

89 changes: 89 additions & 0 deletions docs/localStorage-structure.drawio

Large diffs are not rendered by default.

Empty file.
51 changes: 39 additions & 12 deletions src/assets/styles/global.css
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,7 @@ main>.container {
}

.task {
min-height: 70px;
background-color: white;
border-radius: 10px;
padding: 15px;
Expand All @@ -184,15 +185,14 @@ main>.container {
filter: brightness(.99) saturate(1.2)
}

.task>.checkbox-container {
.checkbox-container {
display: block;
position: relative;
cursor: pointer;
z-index: 99;
}

.task .checkbox {
width: 15px;
height: 15px;
.checkbox {
background-color: white;
border-radius: 50%;
vertical-align: middle;
Expand All @@ -203,16 +203,21 @@ main>.container {
transition: background-color .15s;
}

.task .checkbox:hover {
.checkbox:hover {
background-color: var(--lighter-primary-clr);
}

.task .checkbox:checked {
.checkbox:checked {
background-color: var(--primary-clr);
appearance: auto;
clip-path: circle(50% at 50% 50%);
}

.task .checkbox {
width: 15px;
height: 15px;
}

.task .right {
display: flex;
flex-direction: column;
Expand All @@ -224,13 +229,16 @@ main>.container {
font-size: .8rem;
display: flex;
align-items: center;
gap: 0.75rem;
gap: 10px;
}

.task-attributes li {
display: flex;
align-items: center;
gap: 5px;
}

.task-attributes figure {
margin: 0 5px 0 1px;
}

.task-attributes li:not(:first-child)::before {
Expand Down Expand Up @@ -338,7 +346,8 @@ dialog .top {
}

#new-project-name,
#new-task-name {
#new-task-name,
#task-name-in-modal {
font-size: 1.1rem;
}

Expand All @@ -360,7 +369,7 @@ dialog .top {
display: flex;
justify-content: end;
gap: 15px;
margin-top: 13px;
margin-top: 15px;
}

dialog button {
Expand Down Expand Up @@ -389,7 +398,7 @@ dialog button:active {
background-color: #dedede;
}

.new-task-modal.is-urgent {
dialog.is-urgent {
border-left: 10px solid var(--highlight-clr);
}

Expand Down Expand Up @@ -423,4 +432,22 @@ dialog button:active {
gap: 15px;
}

/* todo: consider using components due to the large nature of this css style sheet now! */
/* modal for details of tasks */
.task-details-modal form {
flex-direction: row;
width: 100%;
}

.task-details-modal .checkbox {
width: 20px;
height: 20px;
border-width: 2px;
margin: 15px -10px 0 -5px;
}

.task-details-modal .task-details-fields {
display: flex;
flex-direction: column;
gap: 15px;
width: 100%;
}
36 changes: 21 additions & 15 deletions src/controllers/App/createAppController.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import { createProject, recreateProjectFromJSON } from "../../models/Project";
import { createTask } from "../../models/Task";
import {
retrieveAllProjects,
retrieveProject,
retrieveProjectById,
retrieveProjectByName,
retrieveProjects,
retrieveTaskById,
} from "../../helpers/localStorageHelpers";

const createAppController = () => {
Expand All @@ -12,32 +14,36 @@ const createAppController = () => {
newTask.setDescription(description);
newTask.setDueDate(dueDate);
newTask.setUrgency(urgency);
newTask.store();

const project = recreateProjectFromJSON(retrieveProject(projectName));
project.addTask(newTask);
const project = recreateProjectFromJSON(retrieveProjectByName(projectName));
project.addTask(newTask.getId());
project.store();
};

const addProject = (newProjectName) => {
if (!newProjectName) return;
createProject(newProjectName).store();
const newProject = createProject();
newProject.setName(newProjectName);
newProject.store();
};

const getProjects = () =>
// exclude inbox, which is a project object behind the scenes
retrieveAllProjects()
.filter((project) => project.name != "Inbox")
.sort((projectA, projectB) => projectA.id - projectB.id);
const getProjects = (excludeInbox = true) => {
let projects = retrieveProjects();
if (excludeInbox)
projects = projects.filter((project) => project.name !== "Inbox");
return projects.sort((projectA, projectB) => projectA.id - projectB.id);
};

const getProject = (projectId) =>
recreateProjectFromJSON(retrieveProjectById(projectId));

const getProject = (projectName) =>
recreateProjectFromJSON(retrieveProject(projectName));
const getTask = (taskId) => retrieveTaskById(taskId);

// run
if (localStorage.length === 0) createProject("Inbox").store();

return { addTask, addProject, getProjects, getProject };
return { addTask, getTask, addProject, getProjects, getProject };
};

export { createAppController };

// TODO: DO NOT STORE TASKS UNLESS VIEWDETAILS HAVE BEEN CALLED ON IT. BECAUSE THEN IT LIKELY MEANS THAT IT WASN'T RECONSTRUCTED FIRST BEFORE MANIPULATION AND THUS INVALID.
29 changes: 12 additions & 17 deletions src/controllers/Screen/displayPage/displayTasks.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { format, isToday, isPast } from "date-fns";
import displayTaskDetailsInModal from "../modals/displayTaskDetailsInModal";

// todo: make svgs easier to manipulate (we need to be able to colour them, but also center them (mask-image didn't work with flexbox - so maybe grid if we pursue that?)) - otherwise, maybe a JSON with all the SVGs inside would be good in like a data/ folder!
const SVGS = {
Expand All @@ -21,27 +22,24 @@ const tasksContainer = document.querySelector("main .container");

const displayTasks = (project) => {
const taskObjects = project.getTasksAsObjects();
if (!taskObjects) return;

// clear everything
tasksContainer.replaceChildren();

taskObjects.forEach((t) => {
const taskArticle = generateTaskDiv(t);
tasksContainer.appendChild(taskArticle);
taskObjects.forEach((taskObject) => {
const taskArticleElement = generateTaskDiv(taskObject);
tasksContainer.appendChild(taskArticleElement);
});

// todo: each task card can be opened and enable modification/deletion (this might have to be done later in a separate pr)

//todo: sort tasks

return;
};

const generateTaskDiv = (task) => {
const taskId = task.getId();
const taskDetails = task.viewDetails();

const article = document.createElement("article");
article.classList.add("task");
article.setAttribute("data-id", taskId);
article.addEventListener("mousedown", () => displayTaskDetailsInModal(task));
if (taskDetails.urgency) article.classList.add("urgent");
if (taskDetails.completed) article.classList.add("completed");

Expand Down Expand Up @@ -76,15 +74,12 @@ const generateTaskDiv = (task) => {
};

const generateTaskAttributes = (taskDetails) => {
const attributeListItems = [];
const ATTRIBUTES_NOT_GENERATED = ["id", "name", "completed"];
ATTRIBUTES_NOT_GENERATED.forEach((a) => delete taskDetails[a]);

const attributeListItems = [];
for (const attribute in taskDetails) {
if (
attribute === "name" ||
attribute === "completed" ||
!taskDetails[attribute]
)
continue;
if (!taskDetails[attribute]) continue;

const item = document.createElement("li");
item.classList.add(attribute);
Expand Down
4 changes: 2 additions & 2 deletions src/controllers/Screen/displayPage/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@ import { createAppController } from "../../App/createAppController";
import displayTasks from "./displayTasks";
import updateHeader from "./updateHeader";

const displayPage = (projectName) => {
const displayPage = (projectId) => {
const app = createAppController();
const project = app.getProject(projectName);
const project = app.getProject(projectId);

updateHeader(project);
displayTasks(project);
Expand Down
7 changes: 4 additions & 3 deletions src/controllers/Screen/displayPage/updateHeader.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,15 +21,16 @@ const SVGS = {
};

const updateHeader = (project) => {
const projectName = project.getName();
const projectId = project.getId();

const pageIcon = document.querySelector("header .left figure");
const pageTitle = document.querySelector(".page-title");
const navbarLink = document.querySelector(`[data-project="${projectName}"]`);
const navbarLink = document.querySelector(`[data-id="${projectId}"]`);

navbarLink.classList.add("selected");
pageTitle.textContent = project.getName();
pageIcon.innerHTML = projectName === "Inbox" ? SVGS.inbox : SVGS.project;
console.log(project.viewDetails());
pageIcon.innerHTML = projectId === "inbox" ? SVGS.inbox : SVGS.project;
};

export default updateHeader;
8 changes: 3 additions & 5 deletions src/controllers/Screen/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,21 +5,19 @@ import displayPage from "./displayPage";
import handleNavLinkClicks from "./nav/handleNavLinkClicks";

const createScreenController = () => {
// select required dom elements here
const brandingInSidebar = document.querySelector(".branding");
const inboxLinkInSidebar = document.querySelector("#inbox-link");
const addProjectBtn = document.querySelector(".add-project");
const addTaskBtn = document.querySelector(".add-task");

// add anything that needs to be run
displayProjectsInNav();
displayPage("Inbox");
displayPage("inbox");

// add required event listeners
//event listeners
addTaskBtn.addEventListener("click", addTaskUsingModal);
addProjectBtn.addEventListener("click", addProjectUsingModal);
[brandingInSidebar, inboxLinkInSidebar].forEach((elem) =>
elem.addEventListener("click", () => handleNavLinkClicks("Inbox"))
elem.addEventListener("click", () => handleNavLinkClicks("inbox"))
);
};

Expand Down
43 changes: 15 additions & 28 deletions src/controllers/Screen/modals/addProjectUsingModal.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { createAppController } from "../../App/createAppController";
import { clearFormErrorMessages, closeModal } from "./taskModalsHandlers";
import displayProjectsInNav from "../nav/displayProjectsInNav";
import displayPage from "../displayPage";

const dialog = document.querySelector(".new-project-modal");
const InputAndErrorMessageElements = document.querySelector(
const modal = document.querySelector(".new-project-modal");
const inputAndErrorMessageElements = document.querySelector(
".new-project-modal .top"
).children;
const form = document.querySelector(".new-project-modal form");
Expand All @@ -12,47 +12,34 @@ const cancelBtn = document.querySelector(".new-project-modal .cancel-btn");

const addProjectUsingModal = () => {
form.addEventListener("submit", handleSubmit);
input.addEventListener("input", clearErrorMessage);
cancelBtn.addEventListener("click", () => {
clearErrorMessage();
dialog.close();
});
document.addEventListener("keydown", (e) => {
if (e.key === "Escape") {
form.reset();
clearErrorMessage();
dialog.close();
}
input.addEventListener(
"input",
clearFormErrorMessages(inputAndErrorMessageElements)
);
cancelBtn.addEventListener("click", () => closeModal(modal));
modal.addEventListener("keydown", (e) => {
if (e.key === "Escape") closeModal(modal);
});

// run
dialog.showModal();
modal.showModal();
};

function handleSubmit(e) {
e.preventDefault();

const app = createAppController();

// dealing with errors when project already exists
try {
const newProjectName = input.value;

app.addProject(newProjectName);
form.reset();
clearErrorMessage();
dialog.close();
app.addProject(input.value);
closeModal(modal);
displayProjectsInNav();
} catch {
[...InputAndErrorMessageElements].forEach((elem) =>
[...inputAndErrorMessageElements].forEach((elem) =>
elem.classList.add("error")
);
}
}

function clearErrorMessage() {
[...InputAndErrorMessageElements].forEach((elem) =>
elem.classList.remove("error")
);
}

export default addProjectUsingModal;
Loading

0 comments on commit 8c4abe5

Please sign in to comment.