Aquí te dejo todo el código por separado, de un sistema de Pomodoro escrito en JavaScript, almacena los datos de manera persistente en el propio navegador haciendo uso de «localStorage» de JS.
El código hace uso de las librerías de Bootstrap para los elementos visuales y JQuery para mayor comodidad.
Todos los campos y etiquetas, incluyendo el código CSS, son modificables mediante campos de formulario, y se puede usar de manera local sin necesidad de servidor. Esta pensado para que pueda funcionar perfectamente dentro de la aplicación OBS (Open Broadcaster Software).
pomodoro.js
if (!localStorage.total_blocks) {
localStorage.total_blocks = 0;
}
if (!localStorage.total_pomodoro_sessions) {
localStorage.total_pomodoro_sessions = 0;
}
if (!sessionStorage.actual_pomodoro_session) {
sessionStorage.actual_pomodoro_session = 1;
}
if (!localStorage.total_short_breaks) {
localStorage.total_short_breaks = 0;
}
if (!localStorage.total_long_breaks) {
localStorage.total_long_breaks = 0;
}
if (!localStorage.msg_pomodoro) {
localStorage.msg_pomodoro = "";
}
if (!localStorage.msg_short) {
localStorage.msg_short = "";
}
if (!localStorage.msg_long) {
localStorage.msg_long = "";
}
if (!localStorage.pomodoro_time) {
localStorage.pomodoro_time = 25;
}
if (!localStorage.pomodoro_short_break) {
localStorage.pomodoro_short_break = 5;
}
if (!localStorage.pomodoro_long_break) {
localStorage.pomodoro_long_break = 15;
}
if (!localStorage.pomodoro_blocks) {
localStorage.pomodoro_blocks = 4;
}
if (!localStorage.label_total_blocks) {
localStorage.label_total_blocks = "T.BLOCKS: ";
}
if (!localStorage.label_total_session) {
localStorage.label_total_session = "T.POMODOROS: ";
}
if (!localStorage.label_total_short_breaks) {
localStorage.label_total_short_breaks = "T.SHORT BREAKS: ";
}
if (!localStorage.label_total_long_breaks) {
localStorage.label_total_long_breaks = "T.LONG BREAKS: ";
}
if (!localStorage.label_session) {
localStorage.label_session = "SESION ACTUAL: ";
}
$(".total_session").html(localStorage.total_pomodoro_sessions);
$(".actual_session").html(sessionStorage.actual_pomodoro_session);
$(".total_blocks").html(localStorage.total_blocks);
$(".total_short_breaks").html(localStorage.total_short_breaks);
$(".total_long_breaks").html(localStorage.total_long_breaks);
$(".pomodoro_block_label").html($("#pomodoro_blocks").val());
$("#css_style").val(localStorage.default_css_style);
//LABELS
$(".label_total_blocks").html(localStorage.label_total_blocks);
$(".label_total_session").html(localStorage.label_total_session);
$(".label_total_short_breaks").html(localStorage.label_total_short_breaks);
$(".label_total_long_breaks").html(localStorage.label_total_long_breaks);
$(".label_session").html(localStorage.label_session);
$("#label_total_blocks").val(localStorage.label_total_blocks);
$("#label_total_session").val(localStorage.label_total_session);
$("#label_total_short_breaks").val(localStorage.label_total_short_breaks);
$("#label_total_long_breaks").val(localStorage.label_total_long_breaks);
$("#label_session").val(localStorage.label_session);
$("#msg_pomodoro").val(localStorage.msg_pomodoro);
$("#msg_short").val(localStorage.msg_short);
$("#msg_long").val(localStorage.msg_long);
$("#pomodoro_time").val(localStorage.pomodoro_time);
$("#pomodoro_short_break").val(localStorage.pomodoro_short_break);
$("#pomodoro_long_break").val(localStorage.pomodoro_long_break);
$("#pomodoro_blocks").val(localStorage.pomodoro_blocks);
$(".ptimer").html($("#pomodoro_time").val().padStart(2, '0') + ":00");
function fn_pomodoro(classname){
$(".ptimer").html($(classname).val().padStart(2, '0') + ":00");
$(".msg_label").html($("#msg_pomodoro").val());
const unixTime = Math.floor(Date.now() / 1000);
var pomodoro_time = $(classname).val()*60;
if (typeof pomodoro_timer !== 'undefined') {clearInterval(pomodoro_timer)};
pomodoro_timer = setInterval(function(){
var remaining_secs = (unixTime + pomodoro_time)- (Math.floor(Date.now() / 1000));
let unix_timestamp = remaining_secs;
var date = new Date(unix_timestamp * 1000);
var hours = date.getHours();
var minutes = "0" + date.getMinutes();
var seconds = "0" + date.getSeconds();
var formattedTime = minutes.substr(-2) + ':' + seconds.substr(-2);
if(remaining_secs > 0){
$(".ptimer").html(formattedTime);
} else {
clearInterval(pomodoro_timer);
$(".ptimer").html("00:00");
$(".msg_label").html("");
localStorage.total_pomodoro_sessions = Number(localStorage.total_pomodoro_sessions) + 1;
if(sessionStorage.actual_pomodoro_session >= Number($("#pomodoro_blocks").val()) ){
sessionStorage.actual_pomodoro_session = 1;
localStorage.total_blocks = Number(localStorage.total_blocks) + 1;
$(".total_blocks").html(localStorage.total_blocks);
}else{
sessionStorage.actual_pomodoro_session = Number(sessionStorage.actual_pomodoro_session) + 1;
}
$(".total_session").html(localStorage.total_pomodoro_sessions);
$(".actual_session").html(sessionStorage.actual_pomodoro_session);
}
},1000)
}
function fn_break(classname){
$(".ptimer").html($(classname).val().padStart(2, '0') + ":00");
$(".msg_label").html(((classname === "#pomodoro_short_break") ? $("#msg_short").val():$("#msg_long").val()));
const unixTime = Math.floor(Date.now() / 1000);
var pomodoro_time = $(classname).val()*60;
if (typeof pomodoro_timer !== 'undefined') {clearInterval(pomodoro_timer)};
pomodoro_timer = setInterval(function(){
var remaining_secs = (unixTime + pomodoro_time)- (Math.floor(Date.now() / 1000));
let unix_timestamp = remaining_secs;
var date = new Date(unix_timestamp * 1000);
var hours = date.getHours();
var minutes = "0" + date.getMinutes();
var seconds = "0" + date.getSeconds();
var formattedTime = minutes.substr(-2) + ':' + seconds.substr(-2);
if(remaining_secs > 0){
$(".ptimer").html(formattedTime);
} else {
clearInterval(pomodoro_timer);
$(".ptimer").html("00:00");
$(".msg_label").html("");
if(classname === "#pomodoro_short_break"){
localStorage.total_short_breaks = Number(localStorage.total_short_breaks) + 1;
$(".total_short_breaks").html(localStorage.total_short_breaks);
}else{
localStorage.total_long_breaks = Number(localStorage.total_long_breaks) + 1;
$(".total_long_breaks").html(localStorage.total_long_breaks);
}
}
},1000)
}
function set_default_value(value){
if (typeof pomodoro_timer === 'undefined') {
if ((value) || (value !="")) {
$(".ptimer").html(value + ":00");
}else{
$(".ptimer").html("00:00");
}
if(Number(value) > 59){
$(".ptimer").html("00:00");
}
};
}
$("#sp").on("click",function(e){
fn_pomodoro("#pomodoro_time");
});
$("#ssb").on("click",function(e){
fn_break("#pomodoro_short_break");
});
$("#slb").on("click",function(e){
fn_break("#pomodoro_long_break");
});
$("#reset").on("click",function(e){
if (typeof pomodoro_timer !== 'undefined') {
clearInterval(pomodoro_timer);
$(".ptimer").html("00:00");
$(".msg_label").html("");
};
});
$(".set_pomodoro_time").on("click",function(e){
set_default_value($("#pomodoro_time").val().padStart(2, '0'));
localStorage.pomodoro_time = $("#pomodoro_time").val();
});
$(".set_pomodoro_short_break").on("click",function(e){
set_default_value($("#pomodoro_short_break").val().padStart(2, '0'));
localStorage.pomodoro_short_break = $("#pomodoro_short_break").val();
});
$(".set_pomodoro_long_break").on("click",function(e){
set_default_value($("#pomodoro_long_break").val().padStart(2, '0'));
localStorage.pomodoro_long_break = $("#pomodoro_long_break").val();
});
$(".set_pomodoro_blocks").on("click",function(e){
localStorage.pomodoro_blocks = $("#pomodoro_blocks").val();
});
$(".set_css_style").on("click",function(e){
localStorage.default_css_style = $("#css_style").val();
var sheet = document.createElement('style');
sheet.innerHTML = localStorage.default_css_style;
document.body.appendChild(sheet);
});
$(".set_msg_pomodoro").on("click",function(e){
localStorage.msg_pomodoro = $("#msg_pomodoro").val();
});
$(".set_msg_short").on("click",function(e){
localStorage.msg_short = $("#msg_short").val();
});
$(".set_msg_long").on("click",function(e){
localStorage.msg_long = $("#msg_long").val();
});
$("#save_settings").on("click",function(e){
localStorage.label_total_blocks = $("#label_total_blocks").val();
localStorage.label_total_session = $("#label_total_session").val();
localStorage.label_total_short_breaks = $("#label_total_short_breaks").val();
localStorage.label_total_long_breaks = $("#label_total_long_breaks").val();
localStorage.label_session = $("#label_session").val();
$(".label_total_blocks").html(localStorage.label_total_blocks);
$(".label_total_session").html(localStorage.label_total_session);
$(".label_total_short_breaks").html(localStorage.label_total_short_breaks);
$(".label_total_long_breaks").html(localStorage.label_total_long_breaks);
$(".label_session").html(localStorage.label_session);
});
var default_css_style = `body{
font-family: 'Montserrat', sans-serif;
}
.ptimer{
font-size: 36px;
font-weight: 600;
}
.msg_label {
font-size: 24px;
font-weight: 600;
}
.actual_session_session{
font-size: 16px;
font-weight: 600;
}
.actual_session_session .actual_session{
color: #ff0000;
}
.actual_session_session .pomodoro_block_label{
color: #00d362;
}
.total_blocks{color: #0e00d3;}
.total_session{color: #0e00d3;}
.total_short_breaks{color: #0e00d3;}
.total_long_breaks{color: #0e00d3;}
div.block_label_total_blocks{ margin:20px; border: 4px dashed #ff0000; padding: 20px; }
div.block_total_session{ margin:20px; border: 4px dashed #ff0000; padding: 20px; }
div.block_total_short_breaks{ margin:20px; border: 4px dashed #ff0000; padding: 20px; }
div.block_total_long_breaks{ margin:20px; border: 4px dashed #ff0000; padding: 20px; }
div.block_session{ margin:20px; border: 4px dashed #ff0000; padding: 20px; }
div.timer{ margin:20px; border: 4px dashed #ff0000; padding: 20px; }
div.msg{ margin:20px; border: 4px dashed #ff0000; padding: 20px; }
`;
$("#reset_css_style").on("click",function(e){
localStorage.default_css_style = default_css_style;
$("#css_style").val(default_css_style);
var sheet = document.createElement('style');
sheet.innerHTML = localStorage.default_css_style;
document.body.appendChild(sheet);
});
if (!localStorage.default_css_style) {
localStorage.default_css_style = default_css_style;
};
var sheet = document.createElement('style');
sheet.innerHTML = localStorage.default_css_style;
document.body.appendChild(sheet);
JavaScriptAhora el código para el HTML, aquí tienes la interfaz gráfica, los elementos personalizables y el formulario, así como los botones de función.
index.html
<html>
<head>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.6.0/jquery.min.js"></script>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Montserrat:wght@400;600&display=swap" rel="stylesheet">
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.0-beta1/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-0evHe/X+R7YkIZDRvuzKMRqM+OrBnVFBL6DOitfPri4tjfHxaWutUpFmBp4vmVor" crossorigin="anonymous">
</head>
<body>
<div class="block_label_total_blocks"><span class="label_total_blocks"></span><span class="total_blocks"></span></div>
<div class="block_total_session"><span class="label_total_session"></span><span class="total_session"></span></div>
<div class="block_total_short_breaks"><span class="label_total_short_breaks"></span><span class="total_short_breaks"></span></div>
<div class="block_total_long_breaks"><span class="label_total_long_breaks"></span><span class="total_long_breaks"></span></div>
<div class="block_session"><span class="label_session"></span><span class="actual_session_session"><span class="actual_session"></span> / <span class="pomodoro_block_label"></span></span></div>
<div class="timer"><span class="ptimer">00:00</span></div>
<div class="msg"><span class="msg_label"> </span></div>
<div class="container">
<div class="card">
<div class="card-body">
<div class="row">
<div class="col">
<input class="btn btn-lg btn-primary" type="button" id="sp" value="start pomodoro">
<input class="btn btn-lg btn-success" type="button" id="ssb" value="start short break">
<input class="btn btn-lg btn-info" type="button" id="slb" value="start long break">
<input class="btn btn-lg btn-danger" type="button" id="reset" value="Stop All">
</div>
</div>
</div>
</div>
<br />
<h2>SETTINGS</h2>
<div class="card">
<div class="card-body">
<div class="mb-3 row">
<label for="msg_pomodoro" class="col-sm-2 col-form-label">P.MESSAGE</label>
<div class="col-sm-10">
<div class="input-group mb-3">
<input type="text" class="form-control form-control-lg" name="msg_pomodoro" id="msg_pomodoro" placeholder="Focus time">
<button class="btn btn-primary set_msg_pomodoro">SET</button>
</div>
</div>
</div>
<div class="mb-3 row">
<label for="msg_short" class="col-sm-2 col-form-label">S.MESSAGE</label>
<div class="col-sm-10">
<div class="input-group mb-3">
<input class="form-control form-control-lg" type="text" name="msg_short" id="msg_short" placeholder="Short break">
<button class="btn btn-primary set_msg_short">SET</button>
</div>
</div>
</div>
<div class="mb-3 row">
<label for="msg_long" class="col-sm-2 col-form-label">L.MESSAGE</label>
<div class="col-sm-10">
<div class="input-group mb-3">
<input class="form-control form-control-lg" type="text" name="msg_long" id="msg_long" placeholder="Long break">
<button class="btn btn-primary mb-1 set_msg_long">SET</button>
</div>
</div>
</div>
<div class="mb-3 row">
<label for="pomodoro_time" class="col-sm-2 col-form-label">P.TIME</label>
<div class="col-sm-10">
<div class="input-group mb-3">
<input class="form-control form-control-lg" type="number" name="pomodoro_time" id="pomodoro_time" value="25" min ="1" max="59">
<button class="btn btn-primary mb-1 set_pomodoro_time">SET</button>
</div>
</div>
</div>
<div class="mb-3 row">
<label for="pomodoro_short_break" class="col-sm-2 col-form-label">P.SHORT TIME</label>
<div class="col-sm-10">
<div class="input-group mb-3">
<input class="form-control form-control-lg" type="number" name="pomodoro_short_break" id="pomodoro_short_break" value="5" min ="1" max="59">
<button class="btn btn-primary mb-1 set_pomodoro_short_break">SET</button>
</div>
</div>
</div>
<div class="mb-3 row">
<label for="pomodoro_long_break" class="col-sm-2 col-form-label">P.LONG TIME</label>
<div class="col-sm-10">
<div class="input-group mb-3">
<input class="form-control form-control-lg" type="number" name="pomodoro_long_break" id="pomodoro_long_break" value="15" min ="1" max="59">
<button class="btn btn-primary mb-1 set_pomodoro_long_break">SET</button>
</div>
</div>
</div>
<div class="mb-3 row">
<label for="pomodoro_blocks" class="col-sm-2 col-form-label">POMODOROS POR BLOQUE</label>
<div class="col-sm-10">
<div class="input-group mb-3">
<input class="form-control form-control-lg" type="number" name="pomodoro_blocks" id="pomodoro_blocks" value="4">
<button class="btn btn-primary mb-1 set_pomodoro_blocks">SET</button>
</div>
</div>
</div>
</div>
</div>
<br />
<h2>LABELS</h2>
<div class="card">
<div class="card-body">
<div class="mb-3 row">
<label for="label_total_blocks" class="col-sm-2 col-form-label">LABEL TOTAL BLOQUES</label>
<div class="col-sm-10">
<input class="form-control form-control-lg" type="text" name="label_total_blocks" id="label_total_blocks">
</div>
</div>
<div class="mb-3 row">
<label for="label_total_session" class="col-sm-2 col-form-label">LABEL TOTAL POMODOROS</label>
<div class="col-sm-10">
<input class="form-control form-control-lg" type="text" name="label_total_session" id="label_total_session">
</div>
</div>
<div class="mb-3 row">
<label for="label_total_short_breaks" class="col-sm-2 col-form-label">LABEL TOTAL SHORT BREAKS</label>
<div class="col-sm-10">
<input class="form-control form-control-lg" type="text" name="label_total_short_breaks" id="label_total_short_breaks">
</div>
</div>
<div class="mb-3 row">
<label for="label_total_long_breaks" class="col-sm-2 col-form-label">LABEL TOTAL LONG BREAKS</label>
<div class="col-sm-10">
<input class="form-control form-control-lg" type="text" name="label_total_long_breaks" id="label_total_long_breaks">
</div>
</div>
<div class="mb-3 row">
<label for="label_session" class="col-sm-2 col-form-label">LABEL ACTUAL SESSION</label>
<div class="col-sm-10">
<input class="form-control form-control-lg" type="text" name="label_session" id="label_session">
</div>
</div>
<div class="mb-3 row">
<div class="col-sm-10">
<input class="btn btn-lg btn-primary" type="button" name="label_session" id="save_settings" value="save labels">
</div>
</div>
</div>
</div>
<br />
<h2>CSS STYLE</h2>
<div class="card">
<div class="card-body">
<div class="mb-3 row">
<label for="label_session" class="col-sm-2 col-form-label">CSS CODE</label>
<div class="col-sm-10">
<textarea name="css_style" id="css_style" cols="30" rows="10" class="form-control form-control-lg css_style"></textarea>
</div>
</div>
<div class="mb-3 row">
<div class="col-sm-10">
<input class="btn btn-lg btn-primary set_css_style" type="button" name="set_css_style" id="set_css_style" value="Apply & save CSS">
<input class="btn btn-lg btn-danger reset_css_style" type="button" name="reset_css_style" id="reset_css_style" value="RESET to DEFAULT">
</div>
</div>
</div>
</div>
</div>
</body>
<script type="text/javascript" src="pomodoro.js"></script>
</html>
HTML