commit 7c965ea19d0d8cb9c55e5da85777e1825d128495 Author: Ian Gulliver Date: Sat Sep 12 03:43:21 2020 +0000 Move from other repo diff --git a/icon.png b/icon.png new file mode 100644 index 0000000..a93ca8b Binary files /dev/null and b/icon.png differ diff --git a/index.html b/index.html new file mode 100644 index 0000000..c972220 --- /dev/null +++ b/index.html @@ -0,0 +1,27 @@ + + + + SimpleCal + + + + + + +
+
+
+
+ + + + + + + diff --git a/simplecal.css b/simplecal.css new file mode 100644 index 0000000..29d2efe --- /dev/null +++ b/simplecal.css @@ -0,0 +1,85 @@ +@import url('https://fonts.googleapis.com/css2?family=Roboto+Mono&family=Roboto:wght@500&display=swap'); + +body { + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + margin: 0; + display: flex; + flex-direction: column; + background: black; + color: white; + 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; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; +} + +#next { + flex-grow: 1; + flex-shrink: 0; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; +} + +#currentLeft { + font-family: 'Roboto Mono', monospace; + font-size: 10vmin; +} + + +#currentName { + text-align: center; + font-size: 15vmin; + mix-blend-mode: difference; +} + +#nextTitle { + font-size: 7vmin; +} + +#nextName { + text-align: center; + font-size: 10vmin; +} + +.event, .break { + visibility: visible !important; +} + +.event { + color: #e28659; +} + +.break { + color: #f9cf41; +} diff --git a/simplecal.js b/simplecal.js new file mode 100644 index 0000000..ff120f7 --- /dev/null +++ b/simplecal.js @@ -0,0 +1,210 @@ +let events = []; + +function handleClientLoad() { + gapi.load('client:auth2', initClient); +} + +function findPrevious() { + 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() { + 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 && end >= now) { + return event; + } + } + + return null; +} + +function anyRemaining() { + const current = findCurrent(); + 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() { + const current = findCurrent(); + 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 && (!current || currentEnd == start)) { + return event; + } + } + + 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() { + const curElem = document.getElementById("current"); + const curName = document.getElementById("currentName"); + const curLeft = document.getElementById("currentLeft"); + const nextElem = document.getElementById("next"); + const nextName = document.getElementById("nextName"); + + const current = findCurrent(); + const prev = findPrevious(); + const next = findNext(); + + 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"); + if (anyRemaining()) { + setText(nextName, "😎 Break"); + } else { + setText(nextName, "✅ Done!"); + } + } else { + nextElem.classList.remove("break"); + nextElem.classList.remove("event"); + setText(nextName, ""); + } +} + +function loadEvents() { + const tz = timezoneOffset(); + const today = new Date(); + const date = `${today.getFullYear().toString()}-${(today.getMonth() + 1).toString().padStart(2, '0')}-${today.getDate().toString().padStart(2, '0')}`; + return gapi.client.calendar.events.list({ + 'calendarId': 'primary', + 'timeMin': `${date}T00:00:00${tz}`, + 'timeMax': `${date}T23:59:59${tz}`, + 'showDeleted': false, + 'singleEvents': true, + 'maxResults': 250, + 'orderBy': 'startTime', + }); +} + +function initClient() { + gapi.client.init({ + apiKey: 'AIzaSyDzNMBbLqQoSCMiug_7UbUrgaAvnoyzYYU', + clientId: '969085949455-0vtpi7g173fi63akr1o2eakp9nm1bp4i.apps.googleusercontent.com', + discoveryDocs: ['https://www.googleapis.com/discovery/v1/apis/calendar/v3/rest'], + scope: 'https://www.googleapis.com/auth/calendar.events.readonly', + ux_mode: 'redirect', + }).then(() => { + if (gapi.auth2.getAuthInstance().isSignedIn.get()) { + return loadEvents(); + } else { + gapi.auth2.getAuthInstance().signIn(); + return; + } + }).then((response) => { + events = response.result.items; + setInterval(() => render(), 250); + setInterval(() => { + loadEvents().then((response) => { + events = response.result.items; + }); + }, 5 * 60 * 1000); + }); +} +