From 7c965ea19d0d8cb9c55e5da85777e1825d128495 Mon Sep 17 00:00:00 2001 From: Ian Gulliver Date: Sat, 12 Sep 2020 03:43:21 +0000 Subject: [PATCH] Move from other repo --- icon.png | Bin 0 -> 7333 bytes index.html | 27 +++++++ simplecal.css | 85 ++++++++++++++++++++ simplecal.js | 210 ++++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 322 insertions(+) create mode 100644 icon.png create mode 100644 index.html create mode 100644 simplecal.css create mode 100644 simplecal.js diff --git a/icon.png b/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..a93ca8b5f025e7d5cc635c6ebe623771d8e2f170 GIT binary patch literal 7333 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 + + + 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); + }); +} +