diff --git a/easycal/easycal.css b/easycal/easycal.css index 8935253..29d2efe 100644 --- a/easycal/easycal.css +++ b/easycal/easycal.css @@ -14,6 +14,25 @@ body { font-family: 'Roboto', sans-serif; } +.blinkSlow { + animation: blink 1.0s infinite; +} + +.blinkFast { + animation: blink 0.5s infinite; +} + +@keyframes blink { + 0% { filter: none; } + 49% { filter: none; } + 50% { filter: invert(); } + 100% { filter: invert(); } +} + +#current, #next { + visibility: hidden; +} + #current { flex-grow: 4; flex-shrink: 0; @@ -53,6 +72,10 @@ body { font-size: 10vmin; } +.event, .break { + visibility: visible !important; +} + .event { color: #e28659; } diff --git a/easycal/easycal.js b/easycal/easycal.js index 7bd9816..d1c52f2 100644 --- a/easycal/easycal.js +++ b/easycal/easycal.js @@ -2,6 +2,22 @@ function handleClientLoad() { gapi.load('client:auth2', initClient); } +function findPrevious(events) { + const now = Date.now(); + let max = null; + let maxTime = 0; + + for (const event of events) { + const end = new Date(event.end.dateTime).getTime(); + if (end <= now && (!max || end >= maxTime)) { + max = event; + maxTime = end; + } + } + + return max; +} + function findCurrent(events) { const now = Date.now(); for (const event of events) { @@ -15,6 +31,22 @@ function findCurrent(events) { return null; } +function anyRemaining(events) { + const current = findCurrent(events); + const currentEnd = current ? new Date(current.end.dateTime).getTime() : null; + + const now = Date.now(); + for (const event of events) { + const start = new Date(event.start.dateTime).getTime(); + const end = new Date(event.end.dateTime).getTime(); + if (start >= now) { + return true; + } + } + + return false; +} + function findNext(events) { const current = findCurrent(events); const currentEnd = current ? new Date(current.end.dateTime).getTime() : null; @@ -31,36 +63,106 @@ function findNext(events) { return null; } +function setText(elem, text) { + if (elem.innerText != text) { + elem.innerText = text; + } +} + +function setLeft(elem, end) { + const now = Date.now(); + const left = Math.round((new Date(end).getTime() - now) / 1000); + const hours = Math.floor(left / 3600); + const minutes = Math.floor((left % 3600) / 60); + const seconds = left % 60; + + let text = `${minutes.toString().padStart(2, '0')}m${seconds.toString().padStart(2, '0')}s`; + + if (hours > 0) { + text = `${hours.toString()}h${text}`; + } + + setText(elem, text); + + if (left < 30) { + document.body.classList.remove("blinkSlow"); + document.body.classList.add("blinkFast"); + } else if (left < 60) { + document.body.classList.remove("blinkFast"); + document.body.classList.add("blinkSlow"); + } else { + document.body.classList.remove("blinkSlow", "blinkFast"); + } +} + +function setProgress(elem, start, end) { + const elapsed = Date.now() - new Date(start).getTime(); + const duration = new Date(end).getTime() - new Date(start).getTime(); + const perc = Math.round(elapsed / duration * 100000) / 1000; + elem.style.background = `-webkit-linear-gradient(left, #e28659 ${perc.toString()}%, transparent ${perc.toString()}%)`; +} + +function timezoneOffset() { + const offset = new Date().getTimezoneOffset(); + return `${offset > 0 ? '-' : '+'}${Math.floor(offset / 60).toString().padStart(2, '0')}:${(offset % 60).toString().padStart(2, '0')}`; +} + function render(events) { const curElem = document.getElementById("current"); const curName = document.getElementById("currentName"); const curLeft = document.getElementById("currentLeft"); - curElem.classList.remove("break", "event"); - - const current = findCurrent(events); - if (current) { - curElem.classList.add("event"); - curName.innerText = current.summary; - const elapsed = Date.now() - new Date(current.start.dateTime).getTime(); - const duration = new Date(current.end.dateTime).getTime() - new Date(current.start.dateTime).getTime(); - const perc = Math.round(elapsed / duration * 100000) / 1000; - curElem.style.background = `-webkit-linear-gradient(left, #e28659 ${perc.toString()}%, transparent ${perc.toString()}%)`; - } else { - curElem.classList.add("break"); - curName.innerText = '😎 Break'; - } - const nextElem = document.getElementById("next"); const nextName = document.getElementById("nextName"); - nextElem.classList.remove("break", "event"); + const current = findCurrent(events); + const prev = findPrevious(events); const next = findNext(events); - if (next) { - nextElem.classList.add("event"); - nextName.innerText = next.summary; + + if (current) { + // Currently in an event + curElem.classList.remove("break"); + curElem.classList.add("event"); + setText(curName, current.summary); + setProgress(curElem, current.start.dateTime, current.end.dateTime); + setText(curLeft, ""); + document.body.classList.remove("blinkSlow", "blinkFast"); } else { + // Currently between/before/after events + curElem.classList.remove("event"); + curElem.classList.add("break"); + + if (prev && next) { + setText(curName, "😎 Break"); + setProgress(curElem, prev.end.dateTime, next.start.dateTime); + setLeft(curLeft, next.start.dateTime); + } else if (next) { + setText(curName, "😎 Break"); + curElem.style.background = null; + setLeft(curLeft, next.start.dateTime); + } else { + setText(curName, "✅ Done!"); + curElem.style.background = null; + setText(curLeft, ""); + document.body.classList.remove("blinkSlow", "blinkFast"); + } + } + + if (next) { + nextElem.classList.remove("break"); + nextElem.classList.add("event"); + setText(nextName, next.summary); + } else if (current) { + nextElem.classList.remove("event"); nextElem.classList.add("break"); - nextName.innerText = '😎 Break'; + if (anyRemaining(events)) { + setText(nextName, "😎 Break"); + } else { + setText(nextName, "✅ Done!"); + } + } else { + nextElem.classList.remove("break"); + nextElem.classList.remove("event"); + setText(nextName, ""); } } @@ -73,9 +175,12 @@ function initClient() { ux_mode: 'redirect', }).then(() => { if (gapi.auth2.getAuthInstance().isSignedIn.get()) { + const tz = timezoneOffset(); + const date = new Date().toISOString().substring(0,10); return gapi.client.calendar.events.list({ 'calendarId': 'primary', - 'timeMin': (new Date()).toISOString(), + 'timeMin': `${date}T00:00:00${tz}`, + 'timeMax': `${date}T23:59:59${tz}`, 'showDeleted': false, 'singleEvents': true, 'maxResults': 250,