User:Alessia/pomodoro timer: Difference between revisions

From XPUB & Lens-Based wiki
No edit summary
No edit summary
Line 5: Line 5:
'''I want to make my own pomodoro timer app. I'll study everything needed to understand how a timer works before, through javascript and python, then get into the app making with the aesthetic I want.
'''I want to make my own pomodoro timer app. I'll study everything needed to understand how a timer works before, through javascript and python, then get into the app making with the aesthetic I want.
'''
'''
==Pomodoro Timer with JavaScript==
=Pomodoro Timer with JavaScript=
I am following this tutorial to get a little bit used to javascript as well:<br>
I am following this tutorial to get a little bit used to javascript as well:<br>
https://freshman.tech/pomodoro-timer/
https://freshman.tech/pomodoro-timer/
Line 330: Line 330:




<big>'''How to automatically starting session'''</big>
The pomodoro sessions and break sessions need to connect to each other, starting one after another. And after 4 consecutive pomodoro sessions a long break should be triggered. <br>
Add a new '''sessions''' property to the '''timer''' object <br>


const timer = {
  pomodoro: 25,
  shortBreak: 5,
  longBreak: 15,
  longBreakInterval: 4,
  sessions: 0,
};
modify the '''startTimer()''' function so that the '''sessions''' property is incremented at the start of a pomodoro session
function startTimer() {
  let { total } = timer.remainingTime;
  const endTime = Date.parse(new Date()) + total * 1000;
  if (timer.mode === 'pomodoro') timer.sessions++;
  mainButton.dataset.action = 'stop';
  mainButton.textContent = 'stop';
  mainButton.classList.add('active');
  interval = setInterval(function() {
    timer.remainingTime = getRemainingTime(endTime);
    updateClock();
    total = timer.remainingTime.total;
    if (total <= 0) {
      clearInterval(interval);
    }
  }, 1000);
}
if (timer.mode === 'pomodoro') timer.sessions++; this line checks if the mode is pomodoro and increments the '''timer.session''' by 1. <br>
Then for the auto switch to the next session on completion of the current modify the startTimer() function: <br>
function startTimer() {
  let { total } = timer.remainingTime;
  const endTime = Date.parse(new Date()) + total * 1000;
  if (timer.mode === 'pomodoro') timer.sessions++;
  mainButton.dataset.action = 'stop';
  mainButton.textContent = 'stop';
  mainButton.classList.add('active');
  interval = setInterval(function() {
    timer.remainingTime = getRemainingTime(endTime);
    updateClock();
    total = timer.remainingTime.total;
    if (total <= 0) {
      clearInterval(interval);
      switch (timer.mode) {
        case 'pomodoro':
          if (timer.sessions % timer.longBreakInterval === 0) {
            switchMode('longBreak');
          } else {
            switchMode('shortBreak');
          }
          break;
        default:
          switchMode('pomodoro');
      }
      startTimer();
    }
  }, 1000);
}
Once the countdown reaches 0, the '''switch''' statement present above causes the app to switch to a new break session or pomodoro session depending on the value of '''timer.mode'''. The if statement checks if '''timer.sessions''' is divisible by '''timer.longBreakInterval'''. Otherwise a short break session is triggered. The '''default''' case is executed if a break session is ending which causes a new pomodoro session to begin.
<br>
'''startTimer()''' is executed again causing the countdown to start again as before. It’s possible to execute a function from within itself!
<br>
To test it set pomodoro, shortBreak and longBreak to 1 temporarily, change it again after! <br>
<br>
<br>
<big>'''Update the progress bar'''</big>
<br>
The progress bar is represented by the '''<progress>''' element which needs a '''max''' and a '''value''' attribute
index.html!!!
<progress id="js-progress" value="0"></progress>
By default, the '''value''' attribute is set to 0 indicating that no progress has been made but the '''max''' attribute is left out. This attribute is essential to determine what represents 100% completion of a task and it must be greater than zero. The '''max''' attribute is set on the '''<progress>''' element in '''switchMode()'''
<br>
main.js
function switchMode(mode) {
  timer.mode = mode;
  timer.remainingTime = {
    total: timer[mode] * 60,
    minutes: timer[mode],
    seconds: 0,
  };
  document
    .querySelectorAll('button[data-mode]')
    .forEach(e => e.classList.remove('active'));
  document.querySelector(`[data-mode="${mode}"]`).classList.add('active');
  document.body.style.backgroundColor = `var(--${mode})`;
  document
    .getElementById('js-progress')
    .setAttribute('max', timer.remainingTime.total);
  updateClock();
}
change '''updateClock()''' function
main.js
function updateClock() {
  const { remainingTime } = timer;
  const minutes = `${remainingTime.minutes}`.padStart(2, '0');
  const seconds = `${remainingTime.seconds}`.padStart(2, '0');
  const min = document.getElementById('js-minutes');
  const sec = document.getElementById('js-seconds');
  min.textContent = minutes;
  sec.textContent = seconds;
  const progress = document.getElementById('js-progress');
  progress.value = timer[timer.mode] * 60 - timer.remainingTime.total;
}
Each time '''updateClock()''' is invoked, the value attribute of the '''<progress>''' element is updated to the result of the remaining amount of seconds minus from the total number of seconds in the session and this causes the progress bar to update according to it. <br>
<br>
Freshman advise to reflect the countdown on the page title as well, for practical reason, so that users don't switch tabs.
<br>
Modify the '''updateClock()''' function
main.js
function updateClock() {
  const { remainingTime } = timer;
  const minutes = `${remainingTime.minutes}`.padStart(2, '0');
  const seconds = `${remainingTime.seconds}`.padStart(2, '0');
  const min = document.getElementById('js-minutes');
  const sec = document.getElementById('js-seconds');
  const time = `${minutes}:${seconds}`;
  min.textContent = minutes;
  sec.textContent = seconds;
  const text = timer.mode === 'pomodoro' ? 'Get back to work!' : 'Take a break!';
  document.title = `${minutes}:${seconds} — ${text}`;
  const progress = document.getElementById('js-progress');
  progress.value = timer[timer.mode] * 60 - timer.remainingTime.total;
}
Wow! Amazing, easy, just put the document.title there and it's done!
<br>
<br>

Revision as of 20:45, 23 December 2023



🍅🍅🍅🍅🍅🍅🍅🍅🍅🍅🍅🍅🍅🍅🍅🍅🍅🍅🍅🍅🍅🍅🍅🍅🍅🍅🍅🍅🍅🍅🍅🍅🍅🍅

I want to make my own pomodoro timer app. I'll study everything needed to understand how a timer works before, through javascript and python, then get into the app making with the aesthetic I want.

Pomodoro Timer with JavaScript

I am following this tutorial to get a little bit used to javascript as well:
https://freshman.tech/pomodoro-timer/

html, css, javascript. Needed: Git, Node.js, npm

basics

What is Git in simple terms?
Git, a version control system for software development, ensures developers can revert back to specific past versions of their work to see records of work completed and allows for streamlined collaboration by permitting changes made by multiple people to be merged into one location.

What is a version control system (VCS)?
a time machine for your files, an assistant tool to organise your work, collaborate with others, lets you travel back in time if things go wrong?

a Version control systems is a software tool that help software teams manage changes to source code over time. As development environments have accelerated, version control systems help software teams work faster and smarter. It records changes made to a code over time in a database called repository, there you can look at the project history. Without a version control system it could be mandatory to constantly store copied of the entire projects in various folders, manually merging...

Why should I use Git with my coding projects?
It's sort of a cloud for code. You don't have to necessarily use GIT. You might use Mercurial or Subversion as SCM/VCS...
In this case, Git is useful to clone the repository(?)

what is Node.js?
multitasking assistant, allows you to use javascript on the server side as well, not just the front-end part, the web browser. Scalability?
Node.js is a runtime environment for executing JavaScript on the server side, while Git is a version control system for tracking and managing changes in your codebase. Both tools play important roles in modern software development, with Node.js handling the server-side logic and Git managing version control and collaboration. Using them together can contribute to a more efficient and organized development process.

what is a nmp?

NPM is like an online store for JavaScript tools and libraries. These tools can be anything from small utility functions to entire frameworks that help you build web applications. When you're building a project using Node.js, you often rely on existing code written by others. NPM helps you easily find and install these pieces of code (packages) into your project. These packages are the dependencies your project needs to run.

difference between Git and npm?
Git is for tracking changes in your code, while NPM is for adding ready-made code components to your project.
what is Github?
It's the social network for Git. It hosts Git repositories online.


what does it mean markup?
In the web development contest "markup" refers to the practice of adding special symbols or codes to a document to provide information about the structure, formatting, or presentation of the content. These symbols or codes are often called "tags." The most common form of markup is HTML (Hypertext Markup Language), which is used to structure content on the web. HTML uses tags to define elements such as headings, paragraphs, links, images, and more. Markup is a way to add structure and meaning to content, allowing computers or other systems to interpret and display it in a specific way.

what is a browser-sync dependency?



clone the repository to your filesystem

git clone https://github.com/Freshman-tech/pomodoro-starter-files.git

cd into it in your terminal

cd pomodoro-starter-files 

run the following command to install the browser-sync dependency which is used to automatically refresh the browser once a file is changed

npm install 

start the app on http://localhost:3000 using:

npm start 

getting into the browser



Then interface of Freshman timer is quite simple. a progress bar + 3 buttons for the 3 modes of the application.Then the countdown timer and the start button.
As a tradition pomodoro session is 25 mins + short break 5 mins/ long break 15 mins after 4 consecutive session.
To create a timer variable follow:

main.js

const timer = {
  pomodoro: 25,
  shortBreak: 5,
  longBreak: 15,
  longBreakInterval: 4,
};

what is const? a keyword to declare a costant variable, cannot be changed.
so this is to get the variables

We need to update the countdown with the right amount of minutes and seconds. We need to create an event listener that detects a click on the buttons and a function to switch the mode of the timer appropriately.

what the hell is an event listener?
a function in JavaScript that waits for an event to occur then responds to it

main.js

const modeButtons = document.querySelector('#js-mode-buttons');
modeButtons.addEventListener('click', handleMode);

function handleMode(event) {
  const { mode } = event.target.dataset;

  if (!mode) return;

  switchMode(mode);
}

document refers to the entire HTML document

On this previous part we use event delegation to detect a click on any mode buttons.

what is an event delegation? technique where instead of attaching an event listener to each individual element, you attach a single event listener to a common ancestor (like a parent element) of multiple elements. This single event listener then handles events for all the elements inside that ancestor.

what's an ancestor in javascript? I'll go with a logical guess

The modeButtons variable points to the containing element and once a click is detected on the element, the handleMode() function is invoked. Within the handleMode() function, the value of the data-mode attribute is retrieved from the target element. If this attribute does not exist, it means that the target element was not one of the buttons and the function exits. Otherwise, a switchMode() function is invoked with the value of the data-mode attribute as its only argument.

Then create the switchMode() function just above handleMode()

main.js

function switchMode(mode) {
  timer.mode = mode;
  timer.remainingTime = {
    total: timer[mode] * 60,
    minutes: timer[mode],
    seconds: 0,
  };
document
    .querySelectorAll('button[data-mode]')
    .forEach(e => e.classList.remove('active'));
  document.querySelector(`[data-mode="${mode}"]`).classList.add('active');
  document.body.style.backgroundColor = `var(--${mode})`;

  updateClock();
}
 

switch statement: evaluates an expression. The value of the expression is then compared with the values of each case in the structure. If there is a match, the associated block of code is executed.
switchMode is a function that takes a 'mode' as an input.

switchMode usage

function switchMode(mode) {
  switch (mode) {
    case 'A':
      // Do something for mode A
      break;
    case 'B':
      // Do something for mode B
      break;
    default:
      // Do something if mode doesn't match A or B 
  }
} 

switchMode is a function that takes a mode as an input. Inside the function, there's a switch statement that checks the value of mode. If mode is 'A', it performs the actions specified for mode A. If mode is 'B', it performs the actions specified for mode B. If mode doesn't match either 'A' or 'B', it performs actions specified in the default case. SwitchMode is like a function that does different things based on the value of mode you give it. It's a way to handle different cases or scenarios in the code.
In this case the switchMode() adds two properties to the "timer" object. First a "mode" property to current mode which could be pomodoro, shortBreak or longBreak.
Then remainingTime set on the timer. remainingTime contains three properties: total (number of seconds remaining), set to the number of minutes of the current mode multiplied by 60. So if mode is shortBreak, total will be se to 300 (5*60).
minutes is the number of minutes for the mode (pomodoro-25 min)
seconds is always set to zero at the start of a session
querySelector is a method provided by the document object that allows you to find and select HTML elements on the page

querySelector('.example') - the example is the CSS selector, it's looking for an element with that class name

CSS selector: selectors allow you to target specific elements in an HTML document so that you can apply styling or perform other operations on them (element, class, ID, attribute, descendant, child)

let's move to css

styles.css
:root {
 --pomodoro: hsl(223, 25%, 40%);
 --shortBreak: hsl(48, 23%, 40%);
 --longBreak: hsl(105, 16%, 40%);
}


The updateClock() function extracts the value of the minutes and seconds properties on the remainingTime object and adds eros where necessary so that the number always has a width of two. For example, 8 seconds will become 08 seconds, but 12 minutes will be left as 12 minutes.

Let's start the timer next step: add the ability to start the timer and countdown to zero, declare an interval variable under timer (just after const timer on the first line)

main.js
let interval;

above updateClock() add startTimer():

main.js
function startTimer() {
 let { total } = timer.remainingTime;
 const endTime = Date.parse(new Date()) + total * 1000;
 interval = setInterval(function() {
   timer.remainingTime = getRemainingTime(endTime);
   updateClock();
   total = timer.remainingTime.total;
   if (total <= 0) {
     clearInterval(interval);
   }
 }, 1000);
}


Before being able to start the timer we need to get the exact time in the future when the timer will end, achieved by retrieving the timestamp of the current moment? (Date.parse(new Date())) which is in milliseconds, 1 second = 1000ms. This value is then stored in the endTime variable.
The interval variable is set to the setInterval() method which executes the callback function every 1000 milliseconds (1 second). This callback function references a getRemainingTime() function which should be created above startTimer
callback(data), callback funtion is a way to handle asynchtonous operations

main.js
function getRemainingTime(endTime) {
 const currentTime = Date.parse(new Date());
 const difference = endTime - currentTime;

 const total = Number.parseInt(difference / 1000, 10);
 const minutes = Number.parseInt((total / 60) % 60, 10);
 const seconds = Number.parseInt(total % 60, 10);

 return {
   total,
   minutes,
   seconds,
 };
}

The function above takes a timestamp argument and finds the difference between the current time and the end time in milliseconds.
This value is stored in the difference variable and used to compute the total number of seconds left by dividing by 1000. The result is converted to an integer in base 10 through the Number.parseInt() method and stored in the total variable.

The minutes variable contains the number of whole minutes left (if any) and seconds is the number of seconds left after whole minutes have been accounted for. For example, if total is 230 seconds, minutes will equal 3 and seconds will be 50.

-you should learn a little bit more about this part-
calling the startTimer() is needed once the start button is clicked. Add this above the modeButtons:

const mainButton = document.getElementById('js-btn');
mainButton.addEventListener('click', () => {
 const { action } = mainButton.dataset;
 if (action === 'start') {
   startTimer();
 }
});


We need to make a small modification to startTimer() so that the button text changes to “stop” and the button becomes depressed like a hardware button (add the mainButton part)

function startTimer() {
 let { total } = timer.remainingTime;
 const endTime = Date.parse(new Date()) + total * 1000;

 mainButton.dataset.action = 'stop';
 mainButton.textContent = 'stop';
 mainButton.classList.add('active');

 interval = setInterval(function() {
   timer.remainingTime = getRemainingTime(endTime);
   updateClock();

   total = timer.remainingTime.total;
   if (total <= 0) {
     clearInterval(interval);
   }
 }, 1000);
}


A final thing to do in this section is to ensure that the mode and remainingTime properties are set on the timer object on page load. To do so, we can execute the switchMode() property once the DOMContentLoaded event is fired. This ensures that the default mode for the timer is pomodoro and the contents of timer.remainingTime is set to the appropriate values for a pomodoro session. If the above snippet is not present, the program will crash with a TypeError if startTimer() is invoked because timer.remainingTime will not exist and we’re trying to access the value of the total property in that object on the first line of the function.
(I didn't understand this part). Add this at the end of main.js

document.addEventListener('DOMContentLoaded', () => {

 switchMode('pomodoro');

});

to test the app set timer.pomodoro to 1 temporaroly and click the start button. Refresh before continuing.

Let's stop the timer

to stop the timer when the stop button is clicked. It's the value of the data-action attribute on the button that allows to determine whether to start or stop the timer.
Then add a new stopTimer() below startTimer()

function stopTimer() {
 clearInterval(interval);

 mainButton.dataset.action = 'start';
 mainButton.textContent = 'start';
 mainButton.classList.remove('active');
}

clearInterval() cause the setInterval() triggered in startTimer() to be cancelled so that the countdown is paused.Then the value of the button's data-action attribute is changed to "start" and it is returned to its original form by removing the active class.
To execute stopTimer() when data-action is set to “stop”, modify the mainButton like this:

mainButton.addEventListener('click', () => {
 const { action } = mainButton.dataset;
 if (action === 'start') {
   startTimer();
 } else {
   stopTimer();
 }
});

Also needed to stop the timer when the mode is changed by clicking any of the three buttons above the countdown:

function handleMode(event) {
 const { mode } = event.target.dataset;

 if (!mode) return;

 switchMode(mode);
 stopTimer();
}


How to automatically starting session The pomodoro sessions and break sessions need to connect to each other, starting one after another. And after 4 consecutive pomodoro sessions a long break should be triggered.
Add a new sessions property to the timer object

const timer = {
 pomodoro: 25,
 shortBreak: 5,
 longBreak: 15,
 longBreakInterval: 4,
 sessions: 0,
};

modify the startTimer() function so that the sessions property is incremented at the start of a pomodoro session

function startTimer() {
 let { total } = timer.remainingTime;
 const endTime = Date.parse(new Date()) + total * 1000;

 if (timer.mode === 'pomodoro') timer.sessions++;

 mainButton.dataset.action = 'stop';
 mainButton.textContent = 'stop';
 mainButton.classList.add('active');

 interval = setInterval(function() {
   timer.remainingTime = getRemainingTime(endTime);
   updateClock();

   total = timer.remainingTime.total;
   if (total <= 0) {
     clearInterval(interval);
   }
 }, 1000);
}

if (timer.mode === 'pomodoro') timer.sessions++; this line checks if the mode is pomodoro and increments the timer.session by 1.
Then for the auto switch to the next session on completion of the current modify the startTimer() function:

function startTimer() {
 let { total } = timer.remainingTime;
 const endTime = Date.parse(new Date()) + total * 1000;

 if (timer.mode === 'pomodoro') timer.sessions++;

 mainButton.dataset.action = 'stop';
 mainButton.textContent = 'stop';
 mainButton.classList.add('active');

 interval = setInterval(function() {
   timer.remainingTime = getRemainingTime(endTime);
   updateClock();
   total = timer.remainingTime.total;
   if (total <= 0) {
     clearInterval(interval);

     switch (timer.mode) {
       case 'pomodoro':
         if (timer.sessions % timer.longBreakInterval === 0) {
           switchMode('longBreak');
         } else {
           switchMode('shortBreak');
         }
         break;
       default:
         switchMode('pomodoro');
     }

     startTimer();
   }
 }, 1000);
}

Once the countdown reaches 0, the switch statement present above causes the app to switch to a new break session or pomodoro session depending on the value of timer.mode. The if statement checks if timer.sessions is divisible by timer.longBreakInterval. Otherwise a short break session is triggered. The default case is executed if a break session is ending which causes a new pomodoro session to begin.
startTimer() is executed again causing the countdown to start again as before. It’s possible to execute a function from within itself!
To test it set pomodoro, shortBreak and longBreak to 1 temporarily, change it again after!


Update the progress bar
The progress bar is represented by the <progress> element which needs a max and a value attribute

index.html!!!

<progress id="js-progress" value="0"></progress>

By default, the value attribute is set to 0 indicating that no progress has been made but the max attribute is left out. This attribute is essential to determine what represents 100% completion of a task and it must be greater than zero. The max attribute is set on the <progress> element in switchMode()

main.js
function switchMode(mode) {
 timer.mode = mode;
 timer.remainingTime = {
   total: timer[mode] * 60,
   minutes: timer[mode],
   seconds: 0,
 };

 document
   .querySelectorAll('button[data-mode]')
   .forEach(e => e.classList.remove('active'));
 document.querySelector(`[data-mode="${mode}"]`).classList.add('active');
 document.body.style.backgroundColor = `var(--${mode})`;
 document
   .getElementById('js-progress')
   .setAttribute('max', timer.remainingTime.total);

 updateClock();
}

change updateClock() function

main.js
function updateClock() {
 const { remainingTime } = timer;
 const minutes = `${remainingTime.minutes}`.padStart(2, '0');
 const seconds = `${remainingTime.seconds}`.padStart(2, '0');

 const min = document.getElementById('js-minutes');
 const sec = document.getElementById('js-seconds');
 min.textContent = minutes;
 sec.textContent = seconds;

 const progress = document.getElementById('js-progress');
 progress.value = timer[timer.mode] * 60 - timer.remainingTime.total;
}

Each time updateClock() is invoked, the value attribute of the <progress> element is updated to the result of the remaining amount of seconds minus from the total number of seconds in the session and this causes the progress bar to update according to it.

Freshman advise to reflect the countdown on the page title as well, for practical reason, so that users don't switch tabs.
Modify the updateClock() function

main.js
function updateClock() {
 const { remainingTime } = timer;
 const minutes = `${remainingTime.minutes}`.padStart(2, '0');
 const seconds = `${remainingTime.seconds}`.padStart(2, '0');

 const min = document.getElementById('js-minutes');
 const sec = document.getElementById('js-seconds');
 const time = `${minutes}:${seconds}`;
 min.textContent = minutes;
 sec.textContent = seconds;

 const text = timer.mode === 'pomodoro' ? 'Get back to work!' : 'Take a break!';
 document.title = `${minutes}:${seconds} — ${text}`;

 const progress = document.getElementById('js-progress');
 progress.value = timer[timer.mode] * 60 - timer.remainingTime.total;
}

Wow! Amazing, easy, just put the document.title there and it's done!