diff options
Diffstat (limited to 'app/static')
| -rw-r--r-- | app/static/block_time.js | 49 | ||||
| -rw-r--r-- | app/static/block_weather.js | 14 | ||||
| -rw-r--r-- | app/static/script.js | 50 | ||||
| -rw-r--r-- | app/static/style.css | 100 |
4 files changed, 213 insertions, 0 deletions
diff --git a/app/static/block_time.js b/app/static/block_time.js new file mode 100644 index 0000000..d6510ae --- /dev/null +++ b/app/static/block_time.js @@ -0,0 +1,49 @@ +function init() { + initTimeVisualizer('timeVisualizer'); +} + +export function updateTime() { + fetch("/time") + .then(res => res.json()) + .then(data => { + document.getElementById("weatherSummary").innerText = data.time; + }); +} + +function initTimeVisualizer(containerId) { + const grid = document.createElement('div'); + grid.className = 'grid'; + document.getElementById(containerId).appendChild(grid); + + function updateTime() { + grid.innerHTML = ''; + const now = new Date(); + const hours = now.getHours(); + const minutes = now.getMinutes(); + + for (let i = 0; i < 24; i++) { + const cell = document.createElement('div'); + cell.className = 'cell'; + + if (i >= 20 || i < 4) cell.classList.add('sleep'); + if (i < hours) cell.classList.add('past'); + + if (i === hours) { + cell.classList.add('current'); + const fillPercentage = (minutes / 60) * 100; + cell.style.setProperty('--fill', `${fillPercentage}%`); + const line = document.createElement('div'); + line.className = 'timeline'; + line.style.left = `calc(${fillPercentage}% - 1px)`; + cell.appendChild(line); + } + + grid.appendChild(cell); + } + } + + updateTime(); + setInterval(updateTime, 60000); // 1min +} + +init() diff --git a/app/static/block_weather.js b/app/static/block_weather.js new file mode 100644 index 0000000..4ae6d2d --- /dev/null +++ b/app/static/block_weather.js @@ -0,0 +1,14 @@ +export function updateWeather() { + const city = document.getElementById("city").value; + fetch(`/weather?city=${encodeURIComponent(city)}`) + .then(res => res.json()) + .then(data => { + if (!data) { + document.getElementById("weather-summary").innerText = `Error, "${city}" city not found`; + return + } + document.getElementById("weather-summary").innerText = data.summary; + document.getElementById("weather-icon").src = data.icon_url; + }); +} + diff --git a/app/static/script.js b/app/static/script.js new file mode 100644 index 0000000..efd6836 --- /dev/null +++ b/app/static/script.js @@ -0,0 +1,50 @@ +import { updateTime } from './block_time.js'; +import { updateWeather } from './block_weather.js'; + +const config = { + polling: true +} + +function init() { + const blocks = { + time: { interval: 30 * 1000, lastUpdate: 0, update: updateTime }, // 30s + weather: { interval: 30 * 60000, lastUpdate: 0, update: updateWeather } // 30min + // Add more: { interval: X, lastUpdate: 0, update: updateFunction } + }; + + initHeaderControls(blocks) + + // Initial load + // Poll every 500ms to check intervals (fast enough for 1s updates, light on CPU) + // maybe the 1s updates should be special case, but for now let's keep it simple + reloadAll(blocks); + setInterval(() => pollUpdates(blocks), 500); +} + +function reloadAll(blocks) { + Object.keys(blocks).forEach(key => { + blocks[key].update(); + blocks[key].lastUpdate = Date.now(); + }); +} + +function pollUpdates(blocks) { + if (!config.polling) return; + const now = Date.now(); + Object.keys(blocks).forEach(key => { + const block = blocks[key]; + if (now - block.lastUpdate >= block.interval) { + block.update(); + block.lastUpdate = now; + } + }); +} + +function initHeaderControls(blocks) { + document.getElementById("reload").addEventListener("click", () => reloadAll(blocks)); + document.getElementById("pause").addEventListener("change", (e) => { + config.polling = e.target.checked; + }); +} + +init() diff --git a/app/static/style.css b/app/static/style.css new file mode 100644 index 0000000..a186f24 --- /dev/null +++ b/app/static/style.css @@ -0,0 +1,100 @@ +/* MASU */ +body { + margin: 0; + padding: 0; + min-height: 100vh; + display: flex; + flex-direction: column; + align-items: center; +} + +header { + width: 100%; + height: 80px; + display: flex; + align-items: center; + justify-content: center; + flex-shrink: 0; +} + +header button, header label { + margin: 0 10px; +} + +#container { + width: 100%; + /* max-width: 1920px; */ + padding: 20px; + box-sizing: border-box; + display: flex; + flex-wrap: wrap; + gap: 20px; + justify-content: center; +} + +.block { + width: 420px; + height: 420px; + flex-shrink: 0; + + padding: 5px; + border: 1px solid black; +} + +@media (max-width: 440px) { + .grid { + padding: 20px 0; + } +} + +/* block_time */ +#time .block { + justify-content: center; +} + +#timeVisualizer .grid { + display: grid; + grid-template-columns: repeat(8, 42px); + grid-template-rows: repeat(3, 42px); + gap: 2px; + background: #f0f0f0; + padding: 5px; + width: fit-content; + margin: 0 auto; +} + +#timeVisualizer .cell { + width: 42px; + height: 42px; + background: #ffffff; + position: relative; + border: 1px solid #ddd; +} + +#timeVisualizer .sleep { + background: #e6f3ff; +} + +#timeVisualizer .past { + background: #cccccc; +} + +#timeVisualizer .current::after { + content: ''; + position: absolute; + left: 0; + top: 0; + height: 100%; + width: var(--fill); + background: #cccccc; +} + +#timeVisualizer .timeline { + position: absolute; + width: 2px; + height: 100%; + background: #000000; + z-index: 1; +} + +/* block_weather */ |
