initial commit
This commit is contained in:
28
web/index.html
Normal file
28
web/index.html
Normal file
@@ -0,0 +1,28 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<title>7am</title>
|
||||
<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=Geist:wght@100..900&display=swap" rel="stylesheet">
|
||||
<link href="./style.css" rel="stylesheet">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<main>
|
||||
<h1>7am</h1>
|
||||
<h2>Daily weather updates delivered to you at 7am.</h2>
|
||||
<hr class="divider" />
|
||||
<ul>
|
||||
<li><a href="/london">London</a></li>
|
||||
<li><a href="/sf">San Francisco</a></li>
|
||||
<li><a href="/sj">San Jose</a></li>
|
||||
<li><a href="/la">Los Angeles</a></li>
|
||||
<li><a href="/nyc">New York City</a></li>
|
||||
<li><a href="/tokyo">Tokyo</a></li>
|
||||
</ul>
|
||||
</main>
|
||||
</body>
|
||||
|
||||
</html>
|
79
web/style.css
Normal file
79
web/style.css
Normal file
@@ -0,0 +1,79 @@
|
||||
:root {
|
||||
--background-color: #f3f4f6;
|
||||
--text-color: #1f2937;
|
||||
--button-color: #030712;
|
||||
--button-text-color: #f3f4f6;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:root {
|
||||
--background-color: #030712;
|
||||
--text-color: #f3f4f6;
|
||||
--button-color: #f3f4f6;
|
||||
--button-text-color: #030712;
|
||||
}
|
||||
}
|
||||
|
||||
html, body {
|
||||
font-family: Geist, sans-serif;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background-color: var(--background-color);
|
||||
color: var(--text-color);
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-weight: bolder;
|
||||
font-size: 1em;
|
||||
margin: 0;
|
||||
padding-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-weight: 400;
|
||||
font-size: 1em;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
ul {
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
ul > li:not(:last-child) {
|
||||
padding-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
a {
|
||||
text-decoration: underline;
|
||||
color: var(--text-color)
|
||||
}
|
||||
|
||||
hr.divider {
|
||||
border-color: var(--text-color);
|
||||
margin: 1.5rem 0;
|
||||
}
|
||||
|
||||
main {
|
||||
padding-left: 1rem;
|
||||
padding-right: 1rem;
|
||||
}
|
||||
|
||||
button {
|
||||
background-color: var(--button-color);
|
||||
color: var(--button-text-color);
|
||||
border: 0;
|
||||
padding: 1rem 3rem;
|
||||
border-radius: 4px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.summary {
|
||||
max-width: 30ch;
|
||||
font-size: 2em;
|
||||
}
|
20
web/summary.html
Normal file
20
web/summary.html
Normal file
@@ -0,0 +1,20 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<title>WeatherBoy</title>
|
||||
<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=Geist:wght@100..900&display=swap" rel="stylesheet">
|
||||
<link href="/style.css" rel="stylesheet">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<main>
|
||||
<p class="summary">{{.Summary}}</p>
|
||||
<button type="button" id="get-summary-btn" data-loc="{{.Location}}">Get daily updates at 7am</button>
|
||||
</main>
|
||||
<script src="/summary.js"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
39
web/summary.js
Normal file
39
web/summary.js
Normal file
@@ -0,0 +1,39 @@
|
||||
const KEY_UPDATES_ENABLED = "updatesEnabled"
|
||||
|
||||
const canReceiveUpdates = "Notification" in window && "serviceWorker" in navigator
|
||||
const getSummaryButton = document.getElementById("get-summary-btn")
|
||||
const loc = getSummaryButton.dataset.loc
|
||||
getSummaryButton.style.display = "none"
|
||||
|
||||
console.log("can receive updates?", canReceiveUpdates)
|
||||
|
||||
if (canReceiveUpdates) {
|
||||
window.addEventListener("load", () => {
|
||||
navigator.serviceWorker.register("/sw.js")
|
||||
})
|
||||
|
||||
navigator.serviceWorker.ready.then((reg) => {
|
||||
if (Notification.permission === "granted" && localStorage.getItem(KEY_UPDATES_ENABLED) === "true") {
|
||||
getSummaryButton.innerText = "Stop updates"
|
||||
reg.active.postMessage(loc)
|
||||
} else {
|
||||
getSummaryButton.innerText = "Get daily updates at 7am"
|
||||
}
|
||||
|
||||
getSummaryButton.addEventListener("click", async () => {
|
||||
const currentlyEnabled = localStorage.getItem(KEY_UPDATES_ENABLED) === "true"
|
||||
const worker = await navigator.serviceWorker.ready
|
||||
if (currentlyEnabled) {
|
||||
localStorage.removeItem(KEY_UPDATES_ENABLED)
|
||||
worker.active.postMessage("cancel")
|
||||
} else if (await Notification.requestPermission()) {
|
||||
localStorage.setItem(KEY_UPDATES_ENABLED, "true")
|
||||
worker.active.postMessage(loc)
|
||||
} else {
|
||||
console.log("notification denied")
|
||||
}
|
||||
})
|
||||
|
||||
getSummaryButton.style.display = "block"
|
||||
})
|
||||
}
|
47
web/sw.js
Normal file
47
web/sw.js
Normal file
@@ -0,0 +1,47 @@
|
||||
let ws
|
||||
let shouldRetry = false
|
||||
|
||||
function start(location) {
|
||||
shouldRetry = true
|
||||
const port = self.location.port ? `:${self.location.port}` : ""
|
||||
const socket = new WebSocket(`ws://${self.location.hostname}${port}/ws?location=${location}`)
|
||||
socket.onopen = () => {
|
||||
ws = socket
|
||||
}
|
||||
socket.onclose = () => {
|
||||
reconnect()
|
||||
}
|
||||
socket.onerror = () => {
|
||||
reconnect()
|
||||
}
|
||||
socket.onmessage = (event) => {
|
||||
self.registration.showNotification("7am weather summary", {
|
||||
body: event.data
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
function cancel() {
|
||||
shouldRetry = false
|
||||
if (ws) {
|
||||
ws.close()
|
||||
ws = null
|
||||
}
|
||||
}
|
||||
|
||||
async function reconnect() {
|
||||
while (shouldRetry) {
|
||||
await new Promise((resolve) => {
|
||||
setTimeout(resolve, 5 * 60 * 1000)
|
||||
})
|
||||
start(location)
|
||||
}
|
||||
}
|
||||
|
||||
self.addEventListener("message", (event) => {
|
||||
if (event.data === "cancel") {
|
||||
cancel()
|
||||
} else {
|
||||
start(event.data)
|
||||
}
|
||||
})
|
Reference in New Issue
Block a user