Skip to content

Instantly share code, notes, and snippets.

@stuart-warren
Last active March 2, 2021 18:16
Show Gist options
  • Save stuart-warren/f24acd82a8d8884ba659832a4714e97d to your computer and use it in GitHub Desktop.
Save stuart-warren/f24acd82a8d8884ba659832a4714e97d to your computer and use it in GitHub Desktop.
Google Workspace countdown timer add-on
function onOpen(e) {
DocumentApp.getUi().createAddonMenu()
.addItem('Start', 'showSidebar')
.addToUi();
showSidebar()
}
function onInstall(e) {
onOpen(e);
}
function showSidebar() {
var ui = HtmlService.createHtmlOutputFromFile('Sidebar')
.setTitle('CountdownSidebar')
.setSandboxMode(HtmlService.SandboxMode.IFRAME);
DocumentApp.getUi().showSidebar(ui);
}
No data is gathered
<!DOCTYPE html>
<html>
<head>
<style>
body {
font-family: sans-serif;
display: grid;
height: 100vh;
place-items: center;
}
.base-timer {
position: relative;
width: 280px;
height: 280px;
}
.base-timer__svg {
transform: scaleX(-1);
}
.base-timer__circle {
fill: none;
stroke: none;
}
.base-timer__path-elapsed {
stroke-width: 7px;
stroke: grey;
}
.base-timer__path-remaining {
stroke-width: 7px;
stroke-linecap: round;
transform: rotate(90deg);
transform-origin: center;
transition: 1s linear all;
fill-rule: nonzero;
stroke: currentColor;
}
.base-timer__path-remaining.green {
color: rgb(65, 184, 131);
}
.base-timer__path-remaining.orange {
color: orange;
}
.base-timer__path-remaining.red {
color: red;
}
.base-timer__label {
position: absolute;
width: 280px;
height: 280px;
top: 0;
display: flex;
align-items: center;
justify-content: center;
font-size: 48px;
color: black;
}
</style>
</head>
<body>
<div id="app"></div>
<div id="control">
<label for="startTime">Time in minutes</label>
<input type="text" id="startTime" onkeyup="setTimer();" size="8">
<button type="button" onclick="startTimer();">Start</button>
</div>
<script>
// Credit: Mateusz Rybczonec
// https://css-tricks.com/how-to-create-an-animated-countdown-timer-with-html-css-and-javascript/
const FULL_DASH_ARRAY = 283;
const WARNING_THRESHOLD = 10;
const ALERT_THRESHOLD = 5;
const COLOR_CODES = {
info: {
color: "green"
},
warning: {
color: "orange",
threshold: WARNING_THRESHOLD
},
alert: {
color: "red",
threshold: ALERT_THRESHOLD
}
};
let TIME_LIMIT = 60*25;
let timePassed = 0;
let timeLeft = TIME_LIMIT;
let timerInterval = null;
let flashInterval = null;
let remainingPathColor = COLOR_CODES.info.color;
document.getElementById("startTime").value = Math.floor(TIME_LIMIT/60);
document.getElementById("app").innerHTML = `
<div class="base-timer">
<svg class="base-timer__svg" viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg">
<g class="base-timer__circle">
<circle class="base-timer__path-elapsed" cx="50" cy="50" r="45"></circle>
<path
id="base-timer-path-remaining"
stroke-dasharray="283"
class="base-timer__path-remaining ${remainingPathColor}"
d="
M 50, 50
m -45, 0
a 45,45 0 1,0 90,0
a 45,45 0 1,0 -90,0
"
></path>
</g>
</svg>
<span id="base-timer-label" class="base-timer__label">${formatTime(
timeLeft
)}</span>
</div>
`;
function resetTimer() {
clearInterval(timerInterval);
clearInterval(flashInterval);
document.getElementById("base-timer-label").style.color = 'black';
timePassed = 0;
setCircleDasharray();
remainingPathColor = COLOR_CODES.info.color;
remaining = document.getElementById("base-timer-path-remaining")
remaining.classList.remove(COLOR_CODES.warning.color);
remaining.classList.remove(COLOR_CODES.alert.color);
remaining.classList.add(COLOR_CODES.info.color);
}
function setTimer() {
resetTimer();
duration = document.getElementById("startTime").value*60;
TIME_LIMIT = duration;
timeLeft = duration;
document.getElementById("base-timer-label").innerHTML = formatTime(
timeLeft
);
}
function flash() {
flashInterval = setInterval(() => {
let timerLabel = document.getElementById("base-timer-label");
let colorCheck = timerLabel.style.color;
if (colorCheck === 'black') {
timerLabel.style.color = 'white';
} else {
timerLabel.style.color = 'black';
}
}, 500);
}
function onTimesUp() {
clearInterval(timerInterval);
flash();
}
function startTimer() {
setTimer();
if (timeLeft === 0) {
onTimesUp();
return;
}
timerInterval = setInterval(() => {
timePassed = timePassed += 1;
timeLeft = TIME_LIMIT - timePassed;
document.getElementById("base-timer-label").innerHTML = formatTime(
timeLeft
);
setCircleDasharray();
setRemainingPathColor(timeLeft);
if (timeLeft === 0) {
onTimesUp();
}
}, 1000);
}
function formatTime(time) {
const minutes = Math.floor(time / 60);
let seconds = time % 60;
if (seconds < 10) {
seconds = `0${seconds}`;
}
return `${minutes}:${seconds}`;
}
function setRemainingPathColor(timeLeft) {
const { alert, warning, info } = COLOR_CODES;
if (timeLeft <= alert.threshold) {
document
.getElementById("base-timer-path-remaining")
.classList.remove(warning.color);
document
.getElementById("base-timer-path-remaining")
.classList.add(alert.color);
} else if (timeLeft <= warning.threshold) {
document
.getElementById("base-timer-path-remaining")
.classList.remove(info.color);
document
.getElementById("base-timer-path-remaining")
.classList.add(warning.color);
}
}
function calculateTimeFraction() {
const rawTimeFraction = timeLeft / TIME_LIMIT;
return rawTimeFraction - (1 / TIME_LIMIT) * (1 - rawTimeFraction);
}
function setCircleDasharray() {
const circleDasharray = `${(
calculateTimeFraction() * FULL_DASH_ARRAY
).toFixed(0)} 283`;
document
.getElementById("base-timer-path-remaining")
.setAttribute("stroke-dasharray", circleDasharray);
}
</script>
</body>
</html>
Copyright 2021 stuart-warren
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
@stuart-warren
Copy link
Author

stuart-warren commented Feb 25, 2021

@stuart-warren
Copy link
Author

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment