shake when trying to delete root or last child

Hunter
Thursday, August 8th, 2024 at 8:33 PM

index.html+54-42

index.html
241  			function loadTasksFromLocalStorage() {
242  				const savedTasks = localStorage.getItem('taskTree');
243  				if (savedTasks) {
244 +					console.log('%cloaded tasks from local storage:', "color: green;");
245 +					console.log(savedTasks);
246  					return deserializeTaskTree(savedTasks);
247  				} else {
248  					return { id: 'root', text: 'todo', state: 0, subtasks: [{ id: Date.now(), text: '', state: 0, subtasks: [] }], selectedSubtaskId: null };

350  								} else {
351  									deleteSubtask(task);
352  								}
353 +							} else {
354 +								// Attempting to delete root task
355 +								applyShakeAnimation(task.id);
356  							}
357  						} else if (taskInput.value !== '') {
358  							keyHandler.backspace.canDelete = false;

469  
470  			function toggleTaskState(task) {
471  				if (task.state === 1) {
    -					// if all direct children are checked, disallow unchecking
472  					if (task.subtasks.length === 0 || !task.subtasks.every(t => t.state === 1)) {
473  						task.state = 0;
474  						updateSubtasksState(task, 0);

483  						scheduleSave();
484  					} else {
485  						// disallow unchecking and play a 'shake' animation
    -						const checkbox = document.querySelector(`.task-container[data-id="${task.id}"] .checkbox-label`);
    -						if (checkbox) {
    -							checkbox.classList.add('shake');
    -							setTimeout(() => {
    -								checkbox.classList.remove('shake');
    -							}, 250); // Remove class after animation completes
    -						}
486 +						applyShakeAnimation(task.id);
487  					}
488  				} else {
489  					task.state = 1;

500  				}
501  			}
502  
503 +			function applyShakeAnimation(taskId) {
504 +				const checkbox = document.querySelector(`.task-container[data-id="${taskId}"] .checkbox-label`);
505 +				if (checkbox) {
506 +					checkbox.classList.add('shake');
507 +					setTimeout(() => {
508 +						checkbox.classList.remove('shake');
509 +					}, 250); // Remove class after animation completes
510 +				}
511 +			}
512 +
513  			function recalculateTaskState(task) {
514  				if (task.subtasks.length === 0) {
515  					return task.state; // If no subtasks, keep current state

562  				const index = parentTask.subtasks.findIndex(t => t.id === subtask.id);
563  
564  				if (parentTask.id === 'root' && parentTask.subtasks.length === 1) {
565 +					// Disallow deletion of the root task's sole remaining child
566 +					applyShakeAnimation(subtask.id);
567  					return;
568  				}
569  

587  				scheduleSave();
588  			}
589  
590 +			function deleteCurrentParentTask() {
591 +				if (taskPath.length <= 1) return; // Don't delete root task
592 +
593 +				const currentParentTask = taskPath[taskPath.length - 1];
594 +				const grandparentTask = taskPath[taskPath.length - 2];
595 +
596 +				// Check if we're trying to delete the sole child of the root
597 +				if (grandparentTask.id === 'root' && grandparentTask.subtasks.length === 1) {
598 +					// Don't allow deletion of the sole child of root
599 +					applyShakeAnimation(currentParentTask.id);
600 +					return;
601 +				}
602 +
603 +				const index = grandparentTask.subtasks.findIndex(t => t.id === currentParentTask.id);
604 +
605 +				grandparentTask.subtasks = grandparentTask.subtasks.filter(t => t.id !== currentParentTask.id);
606 +				updateTaskAndAncestors(grandparentTask);
607 +
608 +				taskPath.pop(); // Remove the deleted task from the path
609 +				currentTask = grandparentTask;
610 +
611 +				if (grandparentTask.subtasks.length === 0 && taskPath.length > 1) {
612 +					// If we've just deleted the last subtask, navigate up another level
613 +					navigateToParentTask();
614 +				} else {
615 +					renderCurrentView();
616 +					if (grandparentTask.subtasks.length > 0) {
617 +						// Select the subtask before the deleted one, or the one after if at the start
618 +						const targetIndex = Math.max(0, index - 1);
619 +						selectAndFocusTask(grandparentTask.subtasks[targetIndex]);
620 +					} else {
621 +						selectAndFocusTask(grandparentTask);
622 +					}
623 +				}
624 +			}
625 +
626  			function findParentTask(task) {
627  				for (let i = taskPath.length - 1; i >= 0; i--) {
628  					const potentialParent = taskPath[i];

717  				taskInput.setSelectionRange(0, 0);
718  			}
719  
    -			function deleteCurrentParentTask() {
    -				if (taskPath.length <= 1) return; // Don't delete root task
    -
    -				const currentParentTask = taskPath[taskPath.length - 1];
    -				const grandparentTask = taskPath[taskPath.length - 2];
    -
    -				// Check if we're trying to delete the sole child of the root
    -				if (grandparentTask.id === 'root' && grandparentTask.subtasks.length === 1) {
    -					return; // Don't allow deletion of the sole child of root
    -				}
    -
    -				const index = grandparentTask.subtasks.findIndex(t => t.id === currentParentTask.id);
    -
    -				grandparentTask.subtasks = grandparentTask.subtasks.filter(t => t.id !== currentParentTask.id);
    -				updateTaskAndAncestors(grandparentTask);
    -
    -				taskPath.pop(); // Remove the deleted task from the path
    -				currentTask = grandparentTask;
    -
    -				if (grandparentTask.subtasks.length === 0 && taskPath.length > 1) {
    -					// If we've just deleted the last subtask, navigate up another level
    -					navigateToParentTask();
    -				} else {
    -					renderCurrentView();
    -					if (grandparentTask.subtasks.length > 0) {
    -						// Select the subtask before the deleted one, or the one after if at the start
    -						const targetIndex = Math.max(0, index - 1);
    -						selectAndFocusTask(grandparentTask.subtasks[targetIndex]);
    -					} else {
    -						selectAndFocusTask(grandparentTask);
    -					}
    -				}
    -			}
    -
720  			function navigateTasks(direction) {
721  				const tasks = currentTask.subtasks;
722  				const currentElement = document.activeElement;