aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMitsuo Tokumori <[email protected]>2025-03-08 01:30:17 +0900
committerMitsuo Tokumori <[email protected]>2025-03-08 01:30:17 +0900
commitec80766af5e2f59f3842b613f271c1100705af5e (patch)
tree4798439deb8b0a951ebb924d4739b05ca27577b3
parent304d0a2c3e0e1eea58d6db1762c1b96e450b5843 (diff)
downloadmasu-ec80766af5e2f59f3842b613f271c1100705af5e.tar.gz
masu-ec80766af5e2f59f3842b613f271c1100705af5e.tar.bz2
masu-ec80766af5e2f59f3842b613f271c1100705af5e.zip
Change block layout, add time visualization
Block layout is now flex.
-rw-r--r--block_weather.py4
-rw-r--r--static/script.js18
-rw-r--r--static/style.css58
-rw-r--r--static/timeVisualizer.css44
-rw-r--r--static/timeVisualizer.js35
-rw-r--r--templates/index.html17
6 files changed, 154 insertions, 22 deletions
diff --git a/block_weather.py b/block_weather.py
index dc041fd..4443de3 100644
--- a/block_weather.py
+++ b/block_weather.py
@@ -22,7 +22,7 @@ def get_current_weather(city: str) -> dict:
data['summary'] = _format_current_weather(data)
# On icon URL codes: https://openweathermap.org/weather-conditions#Weather-Condition-Codes-2
- data['icon_url'] = f"https://openweathermap.org/img/wn/{data['weather'][0]['icon']}@2x.png"
+ data['icon_url'] = f"https://openweathermap.org/img/wn/{data['weather'][0]['icon']}.png"
return data
@@ -34,7 +34,7 @@ def _format_current_weather(data: dict) -> str:
{data['weather'][0]['description']}
{data['main']['temp']}°C (feels like {data['main']['feels_like']}°C), humidity: {data['main']['humidity']}%, pressure: {data['main']['grnd_level']} hPa
visibility: {data['visibility']/1000:.1f}km
-wind: {data['wind']['speed']}m/s from {data['wind']['deg']}°N (with gusts of {data['wind'].get('gust', 0)} m/s)
+wind: {data['wind']['speed']}m/s from {data['wind']['deg']}°N
clouds: {data['clouds']['all']}%
sunrise & sunset: {ts[1].strftime('%H:%M')}, {ts[2].strftime('%H:%M')}"""
return s
diff --git a/static/script.js b/static/script.js
index 1cc3e44..f81f9ef 100644
--- a/static/script.js
+++ b/static/script.js
@@ -1,3 +1,5 @@
+import { initTimeVisualizer } from './timeVisualizer.js';
+
let polling = true
const blocks = {
time: { interval: 30 * 1000, lastUpdate: 0, update: updateTime }, // 30s
@@ -9,7 +11,9 @@ let lastPoll = 0;
function updateTime() {
fetch("/time")
.then(res => res.json())
- .then(data => document.getElementById("time").innerText = data.time);
+ .then(data => {
+ document.getElementById("weatherSummary").innerText = data.time;
+ });
}
function updateWeather() {
@@ -17,9 +21,12 @@ function updateWeather() {
fetch(`/weather?city=${encodeURIComponent(city)}`)
.then(res => res.json())
.then(data => {
- const weatherBlock = document.getElementById("weather");
- if (!data) weatherBlock.innerText = "Error: Bad city";
- else weatherBlock.innerText = `${data.summary}`;
+ 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;
});
}
@@ -54,3 +61,6 @@ reloadAll();
// 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
setInterval(pollUpdates, 500);
+
+// timeVisualizer
+initTimeVisualizer('timeVisualizer');
diff --git a/static/style.css b/static/style.css
index ff01a13..4b3696e 100644
--- a/static/style.css
+++ b/static/style.css
@@ -1,18 +1,52 @@
-body { margin: 0 }
+body {
+ margin: 0;
+ padding: 0;
+ min-height: 100vh;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+}
-header { padding: 10px; text-align: center; }
-header button, header label { margin: 0 10px; }
+header {
+ width: 100%;
+ height: 80px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ flex-shrink: 0;
+}
-.grid {
- display: grid;
- grid-template-columns: repeat(3, 1fr);
- gap: 5px;
+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 {
- aspect-ratio: 1;
- border: 1px solid #000;
- background: #fff;
- padding: 10px;
- text-align: left;
+ width: 420px;
+ height: 420px;
+ flex-shrink: 0;
+
+ padding: 5px;
+ border: 1px solid black;
+}
+
+@media (max-width: 440px) {
+ .grid {
+ padding: 20px 0;
+ }
+}
+
+/* Block specific stuff */
+.block #time {
+ justify-content: center;
}
diff --git a/static/timeVisualizer.css b/static/timeVisualizer.css
new file mode 100644
index 0000000..600ec4b
--- /dev/null
+++ b/static/timeVisualizer.css
@@ -0,0 +1,44 @@
+#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;
+}
diff --git a/static/timeVisualizer.js b/static/timeVisualizer.js
new file mode 100644
index 0000000..2dfb59e
--- /dev/null
+++ b/static/timeVisualizer.js
@@ -0,0 +1,35 @@
+export 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);
+}
diff --git a/templates/index.html b/templates/index.html
index 6337647..be23282 100644
--- a/templates/index.html
+++ b/templates/index.html
@@ -1,7 +1,9 @@
<!DOCTYPE html>
<html>
<head>
+ <title>MASU</title>
<link rel="stylesheet" href="/static/style.css">
+ <link rel="stylesheet" href="/static/timeVisualizer.css">
</head>
<body>
<header>
@@ -9,11 +11,18 @@
<label>Weather City: <input id="city" value="Kofu, JP"></label>
<label><input type="checkbox" id="pause" checked> Auto-Update</label>
</header>
- <div class="grid">
- <div class="block" id="time"></div>
- <div class="block" id="weather"></div>
+ <div id="container">
+ <!-- TODO: Use Jinja2 blocks -->
+ <div class="block" id="time">
+ <p id="weatherSummary"></p>
+ <div id="timeVisualizer"></div>
+ </div>
+ <div class="block" id="weather">
+ <img id="weather-icon" src="" alt="weather icon">
+ <p id="weather-summary"></p>
+ </div>
<!-- Add more blocks -->
</div>
- <script src="/static/script.js"></script>
+ <script type="module" src="/static/script.js"></script>
</body>
</html>