initial commit of jsonclock

master
David Volovskiy 2025-04-19 08:34:52 -04:00
parent 9be612592d
commit f66efa1b36
5 changed files with 255 additions and 0 deletions

1
apps/jsonclock/ChangeLog Normal file
View File

@ -0,0 +1 @@
0.01: first release

View File

@ -0,0 +1 @@
require("heatshrink").decompress(atob("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"))

234
apps/jsonclock/app.js Normal file
View File

@ -0,0 +1,234 @@
var SunCalc = require("suncalc"); // from modules folder
const storage = require('Storage');
const widget_utils = require('widget_utils');
//const SETTINGS_FILE = "jsonClock.json";
const global_settings = storage.readJSON("setting.json", true) || {};
const LOCATION_FILE = "mylocation.json";
const h = g.getHeight();
const w = g.getWidth();
let location;
var settings = {
hr_12: global_settings["12hour"] !== undefined ? global_settings["12hour"] : false,
dark_mode: g.theme.dark
};
let clrs = {
tab : settings.dark_mode ? "#2d2d30" : "#DDDDDD", // grey
keys: settings.dark_mode ? "#4287f5" : "#0000FF", // blue
strings: settings.dark_mode ? "#F0A000" : "#FF0000", // orange or red
ints: settings.dark_mode ? "#00FF00" : "#00FF00", // green
bg: g.theme.bg,
brackets: g.theme.fg,
};
let jsonText;
let lines = [];
let scrollOffset = 0;
let fontSize = 12;
const lineHeight = 16;
const buttonHeight = 12;
const buttonX = 78;
const buttonY = 3;
let valuePositions = [];
const headerHeight = 16;
const usableHeight = h - headerHeight;
const maxLines = Math.floor(usableHeight / lineHeight);
var numWidth = 0;
// requires the myLocation app
function loadLocation() {
location = require("Storage").readJSON(LOCATION_FILE,1)||{};
location.lat = location.lat||35.7796;
location.lon = location.lon||-78.6382;
location.location = location.location||"Raleigh";
}
function extractTime(d){
var h = d.getHours(), m = d.getMinutes();
var amPm = "";
if (settings.hr_12) {
amPm = h < 12 ? "AM" : "PM";
h = h % 12;
if (h == 0) h = 12;
}
return `${h}:${("0"+m).substr(-2)}${amPm}`;
}
function extractDate(d) {
const weekdays = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
const months = ["Jan", "Feb", "Mar", "Apr", "May", "Jun",
"Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
const weekday = weekdays[d.getDay()];
const month = months[d.getMonth()];
const day = d.getDate();
return `${weekday} ${month}, ${day}`;
}
function getSteps() {
try {
return Bangle.getHealthStatus("day").steps;
} catch (e) {
if (WIDGETS.wpedom !== undefined)
return WIDGETS.wpedom.getSteps();
else
return 0;
}
}
function getVal() {
vals = {};
const now = new Date();
const sunTimes = SunCalc.getTimes(now, location.lat, location.lon);
vals.time = extractTime(now);
vals.date = extractDate(now);
vals.rise = extractTime(sunTimes.sunrise);
vals.set = extractTime(sunTimes.sunset);
vals.batt_pct = E.getBattery();
vals.steps = getSteps();
return vals;
}
function loadJson() {
vals = getVal();
const raw = JSON.stringify({
time: vals.time,
date: vals.date,
sun: {
rise: vals.rise,
set: vals.set,
},
"batt_%": vals.batt_pct,
steps: vals.steps
});
jsonText = JSON.stringify(JSON.parse(raw), null, 2);
lines = jsonText.split("\n");
}
function draw() {
g.clear();
g.setFontAlign(-1, -1);
g.setFont("Vector", 10);
valuePositions = [];
g.setColor(clrs.tab);
g.fillRect(90, 0, w, headerHeight);
loadJson();
g.setColor(clrs.brackets);
g.drawString("clockface.json",3,3);
g.setFont("Vector", buttonHeight);
g.drawString("X",buttonX,buttonY);
g.setFont("Vector", fontSize);
for (let i = 0; i < maxLines; i++) {
const lineIndex = i + scrollOffset;
const line = lines[lineIndex];
if (!line) continue;
const y = headerHeight + i * lineHeight;
const lineNumberStr = (lineIndex + 1).toString().padStart(2, " ") + " ";
g.drawString(lineNumberStr, 0, y);
numWidth = Math.max(numWidth, g.stringWidth(lineNumberStr));
}
redraw();
}
function redraw() {
for (let i = 0; i < maxLines; i++) {
const lineIndex = i + scrollOffset;
const line = lines[lineIndex];
if (!line) continue;
const y = headerHeight + i * lineHeight;
const indentMatch = line.match(/^(\s*)/);
const indent = indentMatch ? indentMatch[1] : "";
const kvMatch = line.trim().match(/^"([^"]+)":\s*(.+)$/);
if (kvMatch) {
const key = kvMatch[1];
let value = kvMatch[2];
// Key
g.setColor(clrs.keys);
g.drawString(indent + `"${key}"`, numWidth, y);
const keyWidth = g.stringWidth(indent + `"${key}"`);
const valueX = numWidth + keyWidth;
const valueText = ": " + value;
// Value color
if (value.startsWith('"')) {
g.setColor(clrs.strings);
}
else if (value.startsWith('{') || value.startsWith('}')) {
g.setColor(clrs.brackets);
}
else {
g.setColor(clrs.ints);
}
g.drawString(valueText, valueX, y);
valuePositions.push({ key, x: valueX, y, text: value });
}
else {
g.setColor(clrs.brackets);
g.drawString(line, numWidth, y);
}
}
Bangle.drawWidgets();
}
function clearVals() {
g.setFont("Vector", fontSize);
g.setFontAlign(-1, -1);
valuePositions.forEach(pos => {
g.setColor(clrs.bg);
g.fillRect(pos.x, pos.y, w, pos.y + lineHeight);
});
}
function redrawValues(){
loadJson();
clearVals();
redraw();
}
function scroll(amount) {
const maxOffset = Math.max(0, lines.length - Math.floor((h - headerHeight) / lineHeight));
scrollOffset = Math.max(0, Math.min(scrollOffset + amount, maxOffset));
draw();
}
Bangle.on('touch', (zone, e) => {
if (e.x >= (buttonY - buttonHeight) && e.x <= (buttonX + buttonHeight)
&& (e.y >= (buttonY - buttonHeight) && e.y <= (buttonY + buttonHeight))) {
load(); // Exit app
}
});
Bangle.on('drag', (e) => scroll(-e.dy));
Bangle.setUI({
mode: "clock",
remove: function () {
if (drawTimeout) clearTimeout(drawTimeout);
drawTimeout = undefined;
}
});
loadLocation();
Bangle.loadWidgets();
widget_utils.hide();
draw();
setInterval(redrawValues, 5000);

BIN
apps/jsonclock/app.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

View File

@ -0,0 +1,19 @@
{ "id": "jsonclock",
"name": "jsonclock",
"version": "0.01",
"dependencies": {"mylocation":"app"},
"description": "JSON view of the time, date, steps, battery, and sunrise and sunset times",
"icon": "app.png",
"type": "clock",
"tags": "clock",
"supports" : ["BANGLEJS2"],
"screenshots": [{"url":"app.png"}],
"readme": "README.md",
"storage": [
{"name":"jsonclock.app.js","url":"app.js"},
{"name":"jsonclock.img","url":"app-icon.js","evaluate":true},
{"name":"jsonclock.settings.js","url":"settings.js"}
],
"data": [{"name":"jsonclock.json"}],
"sortorder": -9
}