Merge branch 'espruino:master' into tinyheads
|
@ -112,6 +112,7 @@ module.exports = {
|
|||
"getSerial": "readonly",
|
||||
"getTime": "readonly",
|
||||
"global": "readonly",
|
||||
"globalThis": "readonly",
|
||||
"HIGH": "readonly",
|
||||
"I2C1": "readonly",
|
||||
"Infinity": "readonly",
|
||||
|
|
|
@ -24,10 +24,10 @@
|
|||
var dateStr = require("locale").date(date);
|
||||
// draw time
|
||||
g.setFontAlign(0,0).setFont("Vector",48);
|
||||
g.clearRect(0,y-15,g.getWidth(),y+25); // clear the background
|
||||
g.clearRect(0,y-20,g.getWidth(),y+25); // clear the background
|
||||
g.drawString(timeStr,x,y);
|
||||
// draw date
|
||||
y += 35;
|
||||
y += 30;
|
||||
g.setFontAlign(0,0).setFont("6x8");
|
||||
g.clearRect(0,y-4,g.getWidth(),y+4); // clear the background
|
||||
g.drawString(dateStr,x,y);
|
||||
|
@ -41,6 +41,8 @@
|
|||
// Show launcher when middle button pressed
|
||||
Bangle.setUI({mode:"clock", remove:function() {
|
||||
// free any memory we allocated to allow fast loading
|
||||
if (drawTimeout) clearTimeout(drawTimeout);
|
||||
drawTimeout = undefined;
|
||||
}});
|
||||
// Load widgets
|
||||
Bangle.loadWidgets();
|
||||
|
|
|
@ -51,3 +51,7 @@
|
|||
0.46: Show alarm groups if the Show Group setting is ON. Scroll alarms menu back to previous position when getting back to it.
|
||||
0.47: Fix wrap around when snoozed through midnight
|
||||
0.48: Use datetimeinput for Events, if available. Scroll back when getting out of group. Menu date format setting for shorter dates on current year.
|
||||
0.49: fix uncaught error if no scroller (Bangle 1). Would happen when trying
|
||||
to select an alarm in the main menu.
|
||||
0.50: Bangle.js 2: Long touch of alarm in main menu toggle it on/off. Touching the icon on
|
||||
the right will do the same.
|
||||
|
|
|
@ -20,6 +20,8 @@ It uses the [`sched` library](https://github.com/espruino/BangleApps/blob/master
|
|||
- `Disable All` → Disable _all_ enabled alarms & timers
|
||||
- `Delete All` → Delete _all_ alarms & timers
|
||||
|
||||
On Bangle.js 2 it's possible to toggle alarms, timers and events from the main menu. This is done by clicking the indicator icons of corresponding entries. Or long pressing anywhere on them.
|
||||
|
||||
## Creator
|
||||
|
||||
- [Gordon Williams](https://github.com/gfwilliams)
|
||||
|
@ -29,6 +31,7 @@ It uses the [`sched` library](https://github.com/espruino/BangleApps/blob/master
|
|||
- [Alessandro Cocco](https://github.com/alessandrococco) - New UI, full rewrite, new features
|
||||
- [Sabin Iacob](https://github.com/m0n5t3r) - Auto snooze support
|
||||
- [storm64](https://github.com/storm64) - Fix redrawing in submenus
|
||||
- [thyttan](https://github.com/thyttan) - Toggle alarms directly from main menu.
|
||||
|
||||
## Attributions
|
||||
|
||||
|
|
|
@ -88,13 +88,23 @@ function showMainMenu(scroll, group, scrollback) {
|
|||
const getGroups = settings.showGroup && !group;
|
||||
const groups = getGroups ? {} : undefined;
|
||||
var showAlarm;
|
||||
const getIcon = (e)=>{return e.on ? (e.timer ? iconTimerOn : iconAlarmOn) : (e.timer ? iconTimerOff : iconAlarmOff);};
|
||||
|
||||
alarms.forEach((e, index) => {
|
||||
showAlarm = !settings.showGroup || (group ? e.group === group : !e.group);
|
||||
if(showAlarm) {
|
||||
menu[trimLabel(getLabel(e),40)] = {
|
||||
value: e.on ? (e.timer ? iconTimerOn : iconAlarmOn) : (e.timer ? iconTimerOff : iconAlarmOff),
|
||||
onchange: () => setTimeout(e.timer ? showEditTimerMenu : showEditAlarmMenu, 10, e, index, undefined, scroller.scroll, group)
|
||||
const label = trimLabel(getLabel(e),40);
|
||||
menu[label] = {
|
||||
value: e.on,
|
||||
onchange: (v, touch) => {
|
||||
if (touch && (2==touch.type || 145<touch.x)) { // Long touch or touched icon.
|
||||
e.on = v;
|
||||
saveAndReload();
|
||||
} else {
|
||||
setTimeout(e.timer ? showEditTimerMenu : showEditAlarmMenu, 10, e, index, undefined, scroller?scroller.scroll:undefined, group);
|
||||
}
|
||||
},
|
||||
format: v=>getIcon(e)
|
||||
};
|
||||
} else if (getGroups) {
|
||||
groups[e.group] = undefined;
|
||||
|
@ -102,7 +112,7 @@ function showMainMenu(scroll, group, scrollback) {
|
|||
});
|
||||
|
||||
if (!group) {
|
||||
Object.keys(groups).sort().forEach(g => menu[g] = () => showMainMenu(null, g, scroller.scroll));
|
||||
Object.keys(groups).sort().forEach(g => menu[g] = () => showMainMenu(null, g, scroller?scroller.scroll:undefined));
|
||||
menu[/*LANG*/"Advanced"] = () => showAdvancedMenu();
|
||||
}
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
"id": "alarm",
|
||||
"name": "Alarms & Timers",
|
||||
"shortName": "Alarms",
|
||||
"version": "0.48",
|
||||
"version": "0.50",
|
||||
"description": "Set alarms and timers on your Bangle",
|
||||
"icon": "app.png",
|
||||
"tags": "tool,alarm",
|
||||
|
|
|
@ -37,3 +37,5 @@
|
|||
0.35: Implement API to enable/disable acceleration data tracking.
|
||||
0.36: Move from wrapper function to {} and let - faster execution at boot
|
||||
Allow `calendar-` to take an array of items to remove
|
||||
0.37: Support Gadgetbridge canned responses
|
||||
0.38: Don't rewrite settings file on every boot!
|
|
@ -10,19 +10,15 @@
|
|||
let gpsState = {}; // keep information on GPS via Gadgetbridge
|
||||
|
||||
// this settings var is deleted after this executes to save memory
|
||||
let settings = require("Storage").readJSON("android.settings.json",1)||{};
|
||||
//default alarm settings
|
||||
if (settings.rp == undefined) settings.rp = true;
|
||||
if (settings.as == undefined) settings.as = true;
|
||||
if (settings.vibrate == undefined) settings.vibrate = "..";
|
||||
require('Storage').writeJSON("android.settings.json", settings);
|
||||
let settings = Object.assign({rp:true,as:true,vibrate:".."},
|
||||
require("Storage").readJSON("android.settings.json",1)||{}
|
||||
);
|
||||
let _GB = global.GB;
|
||||
let fetchRecInterval;
|
||||
global.GB = (event) => {
|
||||
// feed a copy to other handlers if there were any
|
||||
if (_GB) setTimeout(_GB,0,Object.assign({},event));
|
||||
|
||||
|
||||
/* TODO: Call handling, fitness */
|
||||
var HANDLERS = {
|
||||
// {t:"notify",id:int, src,title,subject,body,sender,tel:string} add
|
||||
|
@ -67,6 +63,9 @@
|
|||
title:event.name||/*LANG*/"Call", body:/*LANG*/"Incoming call\n"+event.number});
|
||||
require("messages").pushMessage(event);
|
||||
},
|
||||
"canned_responses_sync" : function() {
|
||||
require("Storage").writeJSON("replies.json", event.d);
|
||||
},
|
||||
// {"t":"alarm", "d":[{h:int,m:int,rep:int},... }
|
||||
"alarm" : function() {
|
||||
//wipe existing GB alarms
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
"id": "android",
|
||||
"name": "Android Integration",
|
||||
"shortName": "Android",
|
||||
"version": "0.36",
|
||||
"version": "0.38",
|
||||
"description": "Display notifications/music/etc sent from the Gadgetbridge app on Android. This replaces the old 'Gadgetbridge' Bangle.js widget.",
|
||||
"icon": "app.png",
|
||||
"tags": "tool,system,messages,notifications,gadgetbridge",
|
||||
|
|
|
@ -1 +1,4 @@
|
|||
0.10: New app introduced to the app loader!
|
||||
0.20: skipped (internal revision)
|
||||
0.30: skipped (internal revision)
|
||||
0.40: added functionality for customizing colors
|
||||
|
|
|
@ -9,5 +9,9 @@ From top to bottom the watch face shows four rows of leds:
|
|||
* day (yellow leds, top row)
|
||||
* month (yellow leds, bottom row)
|
||||
|
||||
As usual, luminous leds represent a logical one, dark leds a logcal '0'.
|
||||
The colors are default colors and can be changed at the settings page, also, the outer ring can be disabled.
|
||||
|
||||
The rightmost LED represents 1, the second 2, the third 4, the next 8 and so on, i.e. values are the powers of two.
|
||||
|
||||
As usual, luminous leds represent a logical one, and "dark" leds a logcal '0'. Dark means the color of the background.
|
||||
Widgets aren't affected and are shown as normal.
|
||||
|
|
130
apps/blc/blc.js
|
@ -2,70 +2,110 @@
|
|||
|
||||
{ // must be inside our own scope here so that when we are unloaded everything disappears
|
||||
// we also define functions using 'let fn = function() {..}' for the same reason. function decls are global
|
||||
|
||||
const SETTINGSFILE = "BinaryClk.settings.json";
|
||||
|
||||
// variables defined from settings
|
||||
let HourCol;
|
||||
let MinCol;
|
||||
let DayCol;
|
||||
let MonCol;
|
||||
let RingOn;
|
||||
|
||||
// color arrays
|
||||
// !!! don't change order unless change oder in BinaryClk.settings.js !!!
|
||||
// !!! order must correspond to each other between arrays !!!
|
||||
let LED_Colors = ["#FFF", "#F00", "#0F0", "#00F", "#FF0", "#F0F", "#0FF", "#000"];
|
||||
let LED_ColorNames = ["white", "red", "green", "blue", "yellow", "magenta", "cyan", "black"];
|
||||
|
||||
// load settings
|
||||
let loadSettings = function()
|
||||
{
|
||||
function def (value, def) {return value !== undefined ? value : def;}
|
||||
|
||||
var settings = require('Storage').readJSON(SETTINGSFILE, true) || {};
|
||||
// get name from setting, find index of name and assign corresponding color code by index
|
||||
HourCol = LED_Colors[LED_ColorNames.indexOf(def(settings.HourCol, "red"))];
|
||||
MinCol = LED_Colors[LED_ColorNames.indexOf(def(settings.MinCol, "green"))];
|
||||
DayCol = LED_Colors[LED_ColorNames.indexOf(def(settings.DayCol, "yellow"))];
|
||||
MonCol = LED_Colors[LED_ColorNames.indexOf(def(settings.MonCol, "yellow"))];
|
||||
RingOn = def(settings.RingOn, true);
|
||||
|
||||
delete settings; // settings in local var -> no more required
|
||||
}
|
||||
|
||||
let drawTimeout;
|
||||
|
||||
// Actually draw the watch face
|
||||
// actually draw the watch face
|
||||
let draw = function()
|
||||
{
|
||||
// Bangle.js2 -> 176x176
|
||||
var x_rgt = g.getWidth();
|
||||
var y_bot = g.getHeight();
|
||||
//var x_cntr = x_rgt / 2;
|
||||
var y_cntr = y_bot / 18*7; // not to high because of widget-field (1/3 is to high)
|
||||
var y_cntr = y_bot / 18*7;
|
||||
g.reset().clearRect(Bangle.appRect); // clear whole background (w/o widgets)
|
||||
|
||||
let white = [1,1,1];
|
||||
let red = [1,0,0];
|
||||
let green = [0,1,0];
|
||||
//let blue = [0,0,1];
|
||||
let yellow = [1,1,0];
|
||||
//let magenta = [1,0,1];
|
||||
//let cyan = [0,1,1];
|
||||
let black = [0,0,0];
|
||||
let bord_col = white;
|
||||
let col_off = black;
|
||||
var white = "#FFF";
|
||||
var black = "#000";
|
||||
var bord_col = white;
|
||||
var col_off = black;
|
||||
|
||||
var col = new Array(red, green, yellow, yellow); // [R,G,B]
|
||||
var col = new Array(HourCol, MinCol, DayCol, MonCol); // each #rgb
|
||||
if (g.theme.dark)
|
||||
{
|
||||
bord_col = white;
|
||||
col_off = black;
|
||||
}
|
||||
else
|
||||
{
|
||||
bord_col = black;
|
||||
col_off = white;
|
||||
}
|
||||
|
||||
let pot_2 = [1, 2, 4, 8, 16, 32]; // array with powers of two, because power-op (**)
|
||||
// doesn't work -> maybe also faster
|
||||
let pwr2 = [1, 2, 4, 8, 16, 32]; // array with powers of 2, because poweroperator '**' doesnt work
|
||||
// maybe also faster
|
||||
|
||||
|
||||
var nr_lines = 4; // 4 rows: hour (hr), minute (min), day (day), month (mon)
|
||||
var no_lines = 4; // 4 rows: hour (hr), minute (min), day (day), month (mon)
|
||||
var no_hour = 5;
|
||||
var no_min = 6;
|
||||
var no_day = 5;
|
||||
var no_mon = 4;
|
||||
|
||||
// Arrays: [hr, min, day, mon]
|
||||
//No of Bits: 5 6 5 4
|
||||
let msbits = [4, 5, 4, 3]; // MSB = No bits - 1
|
||||
let rad = [12, 12, 8, 8]; // radiuses for each row
|
||||
var x_dist = 28;
|
||||
let y_dist = [0, 30, 60, 85]; // y-position from y_centr for each row from top
|
||||
// arrays: [hr, min, day, mon]
|
||||
let msbits = [no_hour-1, no_min-1, no_day-1, no_mon-1]; // MSB = No bits - 1
|
||||
let rad = [13, 13, 9, 9]; // radiuses for each row
|
||||
var x_dist = 29;
|
||||
let y_dist = [0, 35, 75, 100]; // y-position from y_centr for each row from top
|
||||
// don't calc. automatic as for x, because of different spaces
|
||||
var x_offs_rgt = 16; // distance from right border (layout)
|
||||
var x_offs_rgt = 15; // offset from right border (layout)
|
||||
var y_offs_cntr = 25; // vertical offset from center
|
||||
|
||||
// Date-Time-Array: 4x6 Bit
|
||||
//var idx_hr = 0;
|
||||
////////////////////////////////////////
|
||||
// compute bit-pattern from time/date and draw leds
|
||||
////////////////////////////////////////
|
||||
|
||||
// date-time-array: 4x6 bit
|
||||
//var idx_hour = 0;
|
||||
//var idx_min = 1;
|
||||
//var idx_day = 2;
|
||||
//var idx_mon = 3;
|
||||
var dt_bit_arr = [[0, 0, 0, 0, 0, 0],[0, 0, 0, 0, 0, 0],[0, 0, 0, 0, 0, 0],[0, 0, 0, 0, 0, 0]];
|
||||
|
||||
var date_time = new Date();
|
||||
var hr = date_time.getHours(); // 0..23
|
||||
var hour = date_time.getHours(); // 0..23
|
||||
var min = date_time.getMinutes(); // 0..59
|
||||
var day = date_time.getDate(); // 1..31
|
||||
var mon = date_time.getMonth() + 1; // GetMonth() -> 0..11
|
||||
|
||||
let dt_array = [hr, min, day, mon];
|
||||
let dt_array = [hour, min, day, mon];
|
||||
|
||||
|
||||
////////////////////////////////////////
|
||||
// compute bit-pattern from time/date and draw leds
|
||||
////////////////////////////////////////
|
||||
var line_cnt = 0;
|
||||
var cnt = 0;
|
||||
var bit_cnt = 0;
|
||||
|
||||
while (line_cnt < nr_lines)
|
||||
while (line_cnt < no_lines)
|
||||
{
|
||||
|
||||
////////////////////////////////////////
|
||||
|
@ -74,9 +114,9 @@
|
|||
|
||||
while (bit_cnt >= 0)
|
||||
{
|
||||
if (dt_array[line_cnt] >= pot_2[bit_cnt])
|
||||
if (dt_array[line_cnt] >= pwr2[bit_cnt])
|
||||
{
|
||||
dt_array[line_cnt] -= pot_2[bit_cnt];
|
||||
dt_array[line_cnt] -= pwr2[bit_cnt];
|
||||
dt_bit_arr[line_cnt][bit_cnt] = 1;
|
||||
}
|
||||
else
|
||||
|
@ -87,23 +127,25 @@
|
|||
}
|
||||
|
||||
////////////////////////////////////////
|
||||
// draw leds (first white border for black screen, then led itself)
|
||||
// draw leds (and border, if enabled)
|
||||
cnt = 0;
|
||||
|
||||
while (cnt <= msbits[line_cnt])
|
||||
{
|
||||
g.setColor(bord_col[0], bord_col[1], bord_col[2]);
|
||||
g.drawCircle(x_rgt-x_offs_rgt-cnt*x_dist, y_cntr-20+y_dist[line_cnt], rad[line_cnt]);
|
||||
|
||||
if (RingOn) // draw outer ring, if enabled
|
||||
{
|
||||
g.setColor(bord_col);
|
||||
g.drawCircle(x_rgt-x_offs_rgt-cnt*x_dist, y_cntr-y_offs_cntr+y_dist[line_cnt], rad[line_cnt]);
|
||||
}
|
||||
if (dt_bit_arr[line_cnt][cnt] == 1)
|
||||
{
|
||||
g.setColor(col[line_cnt][0], col[line_cnt][1], col[line_cnt][2]);
|
||||
g.setColor(col[line_cnt]);
|
||||
}
|
||||
else
|
||||
{
|
||||
g.setColor(col_off[0], col_off[1], col_off[2]);
|
||||
g.setColor(col_off);
|
||||
}
|
||||
g.fillCircle(x_rgt-x_offs_rgt-cnt*x_dist, y_cntr-20+y_dist[line_cnt], rad[line_cnt]-1);
|
||||
g.fillCircle(x_rgt-x_offs_rgt-cnt*x_dist, y_cntr-y_offs_cntr+y_dist[line_cnt], rad[line_cnt]-1);
|
||||
cnt++;
|
||||
}
|
||||
line_cnt++;
|
||||
|
@ -116,7 +158,11 @@
|
|||
drawTimeout = undefined;
|
||||
draw();
|
||||
}, 60000 - (Date.now() % 60000));
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
// Init the settings of the app
|
||||
loadSettings();
|
||||
|
||||
// Show launcher when middle button pressed
|
||||
Bangle.setUI(
|
||||
|
|
|
@ -0,0 +1,72 @@
|
|||
// Change settings for BinaryClk
|
||||
|
||||
(function(back){
|
||||
|
||||
// color array -- don't change order unless change oder in BinaryClk.js
|
||||
let LED_ColorNames = ["white", "red", "green", "blue", "yellow", "magenta", "cyan", "black"];
|
||||
|
||||
var FILE = "BinaryClk.settings.json";
|
||||
// Load settings
|
||||
var settings = Object.assign({
|
||||
HourCol: "red",
|
||||
MinCol: "green",
|
||||
DayCol: "yellow",
|
||||
MonCol: "yellow",
|
||||
RingOn: true,
|
||||
}, require('Storage').readJSON(FILE, true) || {});
|
||||
|
||||
function writeSettings(){
|
||||
require('Storage').writeJSON(FILE, settings);
|
||||
}
|
||||
|
||||
// Helper method which uses int-based menu item for set of string values
|
||||
function stringItems(startvalue, writer, values) {
|
||||
return{
|
||||
value: (startvalue === undefined ? 0 : values.indexOf(startvalue)),
|
||||
format: v => values[v],
|
||||
min: 0,
|
||||
max: values.length - 1,
|
||||
wrap: true,
|
||||
step: 1,
|
||||
onchange: v => {
|
||||
writer(values[v]);
|
||||
writeSettings();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// Helper method which breaks string set settings down to local settings object
|
||||
function stringInSettings(name, values) {
|
||||
return stringItems(settings[name], v => settings[name] = v, values);
|
||||
}
|
||||
|
||||
// Show the menu
|
||||
var mainmenu = {
|
||||
"" : {
|
||||
"title" : "BinaryCLK"
|
||||
},
|
||||
"< Back" : () => back(),
|
||||
'Color Hour.:': stringInSettings("HourCol", LED_ColorNames),
|
||||
'Color Minute:': stringInSettings("MinCol", LED_ColorNames),
|
||||
'Color Day': stringInSettings("DayCol", LED_ColorNames),
|
||||
'Color Month:': stringInSettings("MonCol", LED_ColorNames),
|
||||
'LED ring on/off': {
|
||||
value: (settings.RingOn !== undefined ? settings.RingOn : true),
|
||||
onchange: v => {
|
||||
settings.RingOn = v;
|
||||
writeSettings();
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
// Show submenues
|
||||
//var submenu1 = {
|
||||
//"": {
|
||||
// "title": "Show sub1..."
|
||||
//},
|
||||
//"< Back": () => E.showMenu(mainmenu),
|
||||
//"ItemName": stringInSettings("settingsVar", ["Yes", "No", "DontCare"]),
|
||||
//};
|
||||
|
||||
E.showMenu(mainmenu);
|
||||
});
|
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"HourCol": "red",
|
||||
"MinCol": "green",
|
||||
"DayCol": "yellow",
|
||||
"MonCol": "yellow",
|
||||
"RingOn": true
|
||||
}
|
|
@ -1,10 +1,10 @@
|
|||
{
|
||||
"id":"blc",
|
||||
"name":"Binary LED Clock",
|
||||
"version": "0.10",
|
||||
"description": "Binary LED Clock with date",
|
||||
"version": "0.40",
|
||||
"description": "a binary LED-Clock with time and date and customizable LED-colors",
|
||||
"icon":"blc-icon.png",
|
||||
"screenshots": [{"url":"screenshot_blc.bmp"}],
|
||||
"screenshots": [{"url":"screenshot_blc_1.bmp"},{"url":"screenshot_blc_2.bmp"}],
|
||||
"type": "clock",
|
||||
"tags": "clock",
|
||||
"supports": ["BANGLEJS2"],
|
||||
|
@ -12,6 +12,8 @@
|
|||
"readme": "README.md",
|
||||
"storage": [
|
||||
{"name":"blc.app.js","url":"blc.js"},
|
||||
{"name":"blc.settings.js","url":"blc.settings.js"},
|
||||
{"name":"blc.img","url":"blc-icon.js","evaluate":true}
|
||||
]
|
||||
],
|
||||
"data": [{"name":"blc.settings.json"}]
|
||||
}
|
||||
|
|
Before Width: | Height: | Size: 15 KiB |
After Width: | Height: | Size: 91 KiB |
After Width: | Height: | Size: 91 KiB |
|
@ -13,3 +13,4 @@
|
|||
- [+] Fixed drag handler by adding E.stopEventPropagation()
|
||||
- [+] General code optimization and cleanup
|
||||
0.09: Revised event handler code
|
||||
0.10: Revised suffix formatting in getDate function
|
|
@ -208,12 +208,23 @@
|
|||
// 7. String forming helper functions
|
||||
let getDate = function(short, shortMonth, disableSuffix) {
|
||||
const date = new Date();
|
||||
const dayOfMonth = date.getDate();
|
||||
const day = date.getDate();
|
||||
const month = shortMonth ? locale.month(date, 1) : locale.month(date, 0);
|
||||
const year = date.getFullYear();
|
||||
let suffix = ["st", "nd", "rd"][(dayOfMonth - 1) % 10] || "th";
|
||||
let dayOfMonthStr = disableSuffix ? dayOfMonth : `${dayOfMonth}${suffix}`;
|
||||
return `${month} ${dayOfMonthStr}${short ? '' : `, ${year}`}`;
|
||||
|
||||
const getSuffix = (day) => {
|
||||
if (day >= 11 && day <= 13) return 'th';
|
||||
const lastDigit = day % 10;
|
||||
switch (lastDigit) {
|
||||
case 1: return 'st';
|
||||
case 2: return 'nd';
|
||||
case 3: return 'rd';
|
||||
default: return 'th';
|
||||
}
|
||||
};
|
||||
|
||||
const dayStr = disableSuffix ? day : `${day}${getSuffix(day)}`;
|
||||
return `${month} ${dayStr}${short ? '' : `, ${year}`}`; // not including year for short version
|
||||
};
|
||||
|
||||
let getDayOfWeek = function(date, short) {
|
||||
|
|
|
@ -11,14 +11,14 @@
|
|||
"xOffset": 3,
|
||||
"yOffset": 0,
|
||||
"boxPos": {
|
||||
"x": "0.494",
|
||||
"x": "0.5",
|
||||
"y": "0.739"
|
||||
}
|
||||
},
|
||||
"dow": {
|
||||
"font": "6x8",
|
||||
"fontSize": 3,
|
||||
"outline": 1,
|
||||
"outline": 2,
|
||||
"color": "bgH",
|
||||
"outlineColor": "fg",
|
||||
"border": "#f0f",
|
||||
|
@ -27,7 +27,7 @@
|
|||
"xOffset": 2,
|
||||
"yOffset": 0,
|
||||
"boxPos": {
|
||||
"x": "0.421",
|
||||
"x": "0.5",
|
||||
"y": "0.201"
|
||||
},
|
||||
"short": false
|
||||
|
@ -35,7 +35,7 @@
|
|||
"date": {
|
||||
"font": "6x8",
|
||||
"fontSize": 2,
|
||||
"outline": 1,
|
||||
"outline": 2,
|
||||
"color": "bgH",
|
||||
"outlineColor": "fg",
|
||||
"border": "#f0f",
|
||||
|
@ -44,7 +44,7 @@
|
|||
"xOffset": 1,
|
||||
"yOffset": 0,
|
||||
"boxPos": {
|
||||
"x": "0.454",
|
||||
"x": "0.5",
|
||||
"y": "0.074"
|
||||
},
|
||||
"shortMonth": false,
|
||||
|
@ -62,7 +62,7 @@
|
|||
"xOffset": 2,
|
||||
"yOffset": 1,
|
||||
"boxPos": {
|
||||
"x": "0.494",
|
||||
"x": "0.5",
|
||||
"y": "0.926"
|
||||
},
|
||||
"prefix": "Steps: "
|
||||
|
@ -79,7 +79,7 @@
|
|||
"xOffset": 2,
|
||||
"yOffset": 2,
|
||||
"boxPos": {
|
||||
"x": "0.805",
|
||||
"x": "0.8",
|
||||
"y": "0.427"
|
||||
},
|
||||
"suffix": "%"
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"id": "boxclk",
|
||||
"name": "Box Clock",
|
||||
"version": "0.09",
|
||||
"version": "0.10",
|
||||
"description": "A customizable clock with configurable text boxes that can be positioned to show your favorite background",
|
||||
"icon": "app.png",
|
||||
"dependencies" : { "clockbg":"module" },
|
||||
|
|
Before Width: | Height: | Size: 7.3 KiB After Width: | Height: | Size: 11 KiB |
|
@ -5,3 +5,4 @@
|
|||
0.05: Grid positioning and swipe controls to switch between numbers, operators and special (for Bangle.js 2)
|
||||
0.06: Bangle.js 2: Exit with a short press of the physical button
|
||||
0.07: Bangle.js 2: Exit by pressing upper left corner of the screen
|
||||
0.08: truncate long numbers (and append '...' to displayed value)
|
||||
|
|
|
@ -10,9 +10,9 @@
|
|||
g.clear();
|
||||
require("Font7x11Numeric7Seg").add(Graphics);
|
||||
|
||||
var DEFAULT_SELECTION_NUMBERS = '5', DEFAULT_SELECTION_OPERATORS = '=', DEFAULT_SELECTION_SPECIALS = 'R';
|
||||
var RIGHT_MARGIN = 20;
|
||||
var DEFAULT_SELECTION_NUMBERS = '5';
|
||||
var RESULT_HEIGHT = 40;
|
||||
var RESULT_MAX_LEN = Math.floor((g.getWidth() - 20) / 14);
|
||||
var COLORS = {
|
||||
// [normal, selected]
|
||||
DEFAULT: ['#7F8183', '#A6A6A7'],
|
||||
|
@ -88,28 +88,11 @@ function prepareScreen(screen, grid, defaultColor) {
|
|||
}
|
||||
|
||||
function drawKey(name, k, selected) {
|
||||
var rMargin = 0;
|
||||
var bMargin = 0;
|
||||
var color = k.color || COLORS.DEFAULT;
|
||||
g.setColor(color[selected ? 1 : 0]);
|
||||
g.setFont('Vector', 20).setFontAlign(0,0);
|
||||
g.fillRect(k.xy[0], k.xy[1], k.xy[2], k.xy[3]);
|
||||
g.setColor(-1);
|
||||
// correct margins to center the texts
|
||||
if (name == '0') {
|
||||
rMargin = (RIGHT_MARGIN * 2) - 7;
|
||||
} else if (name === '/') {
|
||||
rMargin = 5;
|
||||
} else if (name === '*') {
|
||||
bMargin = 5;
|
||||
rMargin = 3;
|
||||
} else if (name === '-') {
|
||||
rMargin = 3;
|
||||
} else if (name === 'R' || name === 'N') {
|
||||
rMargin = k.val === 'C' ? 0 : -9;
|
||||
} else if (name === '%') {
|
||||
rMargin = -3;
|
||||
}
|
||||
g.drawString(k.val || name, (k.xy[0] + k.xy[2])/2, (k.xy[1] + k.xy[3])/2);
|
||||
}
|
||||
|
||||
|
@ -138,29 +121,21 @@ function drawGlobal() {
|
|||
screen[k] = specials[k];
|
||||
}
|
||||
drawKeys();
|
||||
var selected = DEFAULT_SELECTION_NUMBERS;
|
||||
var prevSelected = DEFAULT_SELECTION_NUMBERS;
|
||||
}
|
||||
function drawNumbers() {
|
||||
screen = numbers;
|
||||
screenColor = COLORS.DEFAULT;
|
||||
drawKeys();
|
||||
var selected = DEFAULT_SELECTION_NUMBERS;
|
||||
var prevSelected = DEFAULT_SELECTION_NUMBERS;
|
||||
}
|
||||
function drawOperators() {
|
||||
screen = operators;
|
||||
screenColor =COLORS.OPERATOR;
|
||||
drawKeys();
|
||||
var selected = DEFAULT_SELECTION_OPERATORS;
|
||||
var prevSelected = DEFAULT_SELECTION_OPERATORS;
|
||||
}
|
||||
function drawSpecials() {
|
||||
screen = specials;
|
||||
screenColor = COLORS.SPECIAL;
|
||||
drawKeys();
|
||||
var selected = DEFAULT_SELECTION_SPECIALS;
|
||||
var prevSelected = DEFAULT_SELECTION_SPECIALS;
|
||||
}
|
||||
|
||||
function getIntWithPrecision(x) {
|
||||
|
@ -218,8 +193,6 @@ function doMath(x, y, operator) {
|
|||
}
|
||||
|
||||
function displayOutput(num) {
|
||||
var len;
|
||||
var minusMarge = 0;
|
||||
g.setBgColor(0).clearRect(0, 0, g.getWidth(), RESULT_HEIGHT-1);
|
||||
g.setColor(-1);
|
||||
if (num === Infinity || num === -Infinity || isNaN(num)) {
|
||||
|
@ -230,9 +203,7 @@ function displayOutput(num) {
|
|||
num = '-INFINITY';
|
||||
} else {
|
||||
num = 'NOT A NUMBER';
|
||||
minusMarge = -25;
|
||||
}
|
||||
len = (num + '').length;
|
||||
currNumber = null;
|
||||
results = null;
|
||||
isDecimal = false;
|
||||
|
@ -261,6 +232,9 @@ function displayOutput(num) {
|
|||
num = num.toString();
|
||||
num = num.replace("-","- "); // fix padding for '-'
|
||||
g.setFont('7x11Numeric7Seg', 2);
|
||||
if (num.length > RESULT_MAX_LEN) {
|
||||
num = num.substr(0, RESULT_MAX_LEN - 1)+'...';
|
||||
}
|
||||
}
|
||||
g.setFontAlign(1,0);
|
||||
g.drawString(num, g.getWidth()-20, RESULT_HEIGHT/2);
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
"id": "calculator",
|
||||
"name": "Calculator",
|
||||
"shortName": "Calculator",
|
||||
"version": "0.07",
|
||||
"version": "0.08",
|
||||
"description": "Basic calculator reminiscent of MacOs's one. Handy for small calculus.",
|
||||
"icon": "calculator.png",
|
||||
"screenshots": [{"url":"screenshot_calculator.png"}],
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
0.01: First release
|
|
@ -0,0 +1,18 @@
|
|||
# Messages Clockinfo
|
||||
|
||||
A simple messages counter for clockinfo enabled watchfaces
|
||||
|
||||
## Usage
|
||||
|
||||
You can choose between read and unread counter.
|
||||
Tap to go to messages UI.
|
||||
|
||||
## Todo / Known Issues
|
||||
|
||||
* GB triggers for message read on phone are not handled
|
||||
* Icons are not consistent
|
||||
* Maybe use messageicons for handling icon from last notification
|
||||
|
||||
## Attributions
|
||||
|
||||
All icons used in this app are from [icons8](https://icons8.com).
|
After Width: | Height: | Size: 326 B |
|
@ -0,0 +1,84 @@
|
|||
(function() {
|
||||
|
||||
var unreadImg = function() {
|
||||
return atob("GBiBAAAAAAAAAAAAAB//+D///D///D///D///D///D///D5mfD5mfD///D///D///D///D///D///B//+APgAAOAAAOAAAAAAAAAAA==");
|
||||
}
|
||||
var allImg = function() {
|
||||
return atob("GBiBAAAAAAAAAAB+AAD/AAPDwA8A8B4AeDgAHDgAHDwAPD8A/D/D/D/n/D///D///D///D///D///D///B//+AAAAAAAAAAAAAAAAA==");
|
||||
}
|
||||
|
||||
var debug = function(o) {
|
||||
//console.log(o);
|
||||
}
|
||||
var msgUnread;
|
||||
var msgAll;
|
||||
var msgs = require("messages");
|
||||
|
||||
var getAllMSGs = function() {
|
||||
if (msgAll === undefined) {
|
||||
debug("msgAll is undefined");
|
||||
msgAll = msgs.getMessages().filter(m => !['call', 'map', 'music'].includes(m.id)).length;
|
||||
}
|
||||
return msgAll;
|
||||
}
|
||||
|
||||
|
||||
var getUnreadMGS = function() {
|
||||
if (msgUnread === undefined) {
|
||||
debug("msgUnread is undefined");
|
||||
msgUnread = msgs.getMessages().filter(m => m.new && !['call', 'map', 'music'].includes(m.id)).length;
|
||||
}
|
||||
return msgUnread;
|
||||
}
|
||||
|
||||
var msgCounter = function(type, msg) {
|
||||
var msgsNow = msgs.getMessages(msg);
|
||||
msgUnread = msgsNow.filter(m => m.new && !['call', 'map', 'music'].includes(m.id)).length;
|
||||
msgAll = msgsNow.filter(m => !['call', 'map', 'music'].includes(m.id)).length;
|
||||
//TODO find nicer way to redraw current shown CI counter
|
||||
info.items[0].emit("redraw");
|
||||
info.items[1].emit("redraw");
|
||||
}
|
||||
|
||||
var info = {
|
||||
name: "Messages",
|
||||
img: unreadImg(),
|
||||
items: [
|
||||
{ name : "Unread",
|
||||
get : () => {
|
||||
return {
|
||||
text : getUnreadMGS(),
|
||||
img : unreadImg()
|
||||
};
|
||||
},
|
||||
show : function() {
|
||||
Bangle.on("message", msgCounter);
|
||||
},
|
||||
hide : function() {
|
||||
Bangle.removeListener("message", msgCounter);
|
||||
},
|
||||
run : () => {
|
||||
require("messages").openGUI();
|
||||
}
|
||||
},
|
||||
{ name : "All",
|
||||
get : () => {
|
||||
return {
|
||||
text : getAllMSGs(),
|
||||
img : allImg()
|
||||
};
|
||||
},
|
||||
show : function() {
|
||||
Bangle.on("message", msgCounter);
|
||||
},
|
||||
hide : function() {
|
||||
Bangle.removeListener("message", msgCounter);
|
||||
},
|
||||
run : () => {
|
||||
require("messages").openGUI();
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
return info;
|
||||
})
|
|
@ -0,0 +1,15 @@
|
|||
{ "id": "clkinfomsg",
|
||||
"name": "Messages Clockinfo",
|
||||
"version":"0.01",
|
||||
"description": "For clocks that display 'clockinfo', this displays the messages count",
|
||||
"icon": "app.png",
|
||||
"type": "clkinfo",
|
||||
"screenshots": [{"url":"screenshot.png"}],
|
||||
"readme":"README.md",
|
||||
"tags": "clkinfo",
|
||||
"supports" : ["BANGLEJS2"],
|
||||
"dependencies" : { "messages":"app" },
|
||||
"storage": [
|
||||
{"name":"clkinfomsg.clkinfo.js","url":"clkinfo.js"}
|
||||
]
|
||||
}
|
After Width: | Height: | Size: 2.1 KiB |
|
@ -50,7 +50,7 @@ extensions).
|
|||
`load()` returns an array of menu objects, where each object contains a list of menu items:
|
||||
* `name` : text to display and identify menu object (e.g. weather)
|
||||
* `img` : a 24x24px image
|
||||
* `dynamic` : if `true`, items are not constant but are sorted (e.g. calendar events sorted by date)
|
||||
* `dynamic` : if `true`, items are not constant but are sorted (e.g. calendar events sorted by date). This is only used by a few clocks, for example `circlesclock`
|
||||
* `items` : menu items such as temperature, humidity, wind etc.
|
||||
|
||||
Note that each item is an object with:
|
||||
|
|
|
@ -1,3 +1,7 @@
|
|||
0.01: New App!
|
||||
0.02: Moved settings into 'Settings->Apps'
|
||||
0.03: Add 'Squares' option for random squares background
|
||||
0.04: More options for different background colors
|
||||
'Plasma' generative background
|
||||
Add a 'view' option in settings menu to view the current background
|
||||
0.05: Random square+plasma speed improvements (~2x faster)
|
|
@ -15,6 +15,7 @@ You can either:
|
|||
* `Random Color` - a new color every time the clock starts
|
||||
* `Image` - choose from a previously uploaded image
|
||||
* `Squares` - a randomly generated pattern of squares in the selected color palette
|
||||
* `Plasma` - a randomly generated 'plasma' pattern of squares in the selected color palette (random noise with a gaussian filter applied)
|
||||
|
||||
|
||||
## Usage in code
|
||||
|
@ -30,6 +31,9 @@ background.fillRect(Bangle.appRect);
|
|||
|
||||
// to fill just one part of the screen
|
||||
background.fillRect(x1, y1, x2, y2);
|
||||
|
||||
// if you ever need to reload to a new background (this could take ~100ms)
|
||||
background.reload();
|
||||
```
|
||||
|
||||
You should also add `"dependencies" : { "clockbg":"module" },` to your app's metadata to
|
||||
|
@ -39,8 +43,9 @@ ensure that the clock background library is automatically loaded.
|
|||
|
||||
A few features could be added that would really improve functionality:
|
||||
|
||||
* When 'fast loading', 'random' backgrounds don't update at the moment
|
||||
* When 'fast loading', 'random' backgrounds don't update at the moment (calling `.reload` can fix this now, but it slows things down)
|
||||
* Support for >1 image to be uploaded (requires some image management in `interface.html`), and choose randomly between them
|
||||
* Support for gradients (random colors)
|
||||
* More types of auto-generated pattern (as long as they can be generated quickly or in the background)
|
||||
* Storing 'clear' areas of uploaded images so clocks can easily position themselves
|
||||
* Some backgrounds could update themselves in the background (eg a mandelbrot could calculate the one it should display next time while the watch is running)
|
|
@ -1,25 +1,49 @@
|
|||
let settings = Object.assign({
|
||||
let settings;
|
||||
|
||||
exports.reload = function() {
|
||||
settings = Object.assign({
|
||||
style : "randomcolor",
|
||||
colors : ["#F00","#0F0","#00F"]
|
||||
},require("Storage").readJSON("clockbg.json")||{});
|
||||
if (settings.style=="image")
|
||||
},require("Storage").readJSON("clockbg.json")||{});
|
||||
if (settings.style=="image")
|
||||
settings.img = require("Storage").read(settings.fn);
|
||||
else if (settings.style=="randomcolor") {
|
||||
else if (settings.style=="randomcolor") {
|
||||
settings.style = "color";
|
||||
let n = (0|(Math.random()*settings.colors.length)) % settings.colors.length;
|
||||
settings.color = settings.colors[n];
|
||||
delete settings.colors;
|
||||
} else if (settings.style=="squares") {
|
||||
} else if (settings.style=="squares") { // 32ms
|
||||
settings.style = "image";
|
||||
let bpp = (settings.colors.length>4)?4:2;
|
||||
let bg = Graphics.createArrayBuffer(11,11,bpp,{msb:true});
|
||||
E.mapInPlace(bg.buffer, bg.buffer, ()=>Math.random()*256); // random pixels
|
||||
let u32 = new Uint32Array(bg.buffer); // faster to do 1/4 of the ops of E.mapInPlace(bg.buffer, bg.buffer, ()=>Math.random()*256);
|
||||
E.mapInPlace(u32, u32, function(r,n){"ram";return r()*n}.bind(null,Math.random,0x100000000)); // random pixels
|
||||
bg.buffer[bg.buffer.length-1]=Math.random()*256; // 11x11 isn't a multiple of 4 bytes - we need to set the last one!
|
||||
bg.palette = new Uint16Array(1<<bpp);
|
||||
bg.palette.set(settings.colors.map(c=>g.toColor(c)));
|
||||
settings.img = bg.asImage("string");
|
||||
settings.img = bg;
|
||||
settings.imgOpt = {scale:16};
|
||||
delete settings.colors;
|
||||
}
|
||||
} else if (settings.style=="plasma") { // ~47ms
|
||||
settings.style = "image";
|
||||
let bg = Graphics.createArrayBuffer(16,16,4,{msb:true});
|
||||
let u32 = new Uint32Array(bg.buffer); // faster to do 1/4 of the ops of E.mapInPlace(bg.buffer, bg.buffer, ()=>Math.random()*256);
|
||||
E.mapInPlace(u32, u32, function(r,n){"ram";return r()*n}.bind(null,Math.random,0x100000000)); // random pixels
|
||||
bg.filter([ // a gaussian filter to smooth out
|
||||
1, 4, 7, 4, 1,
|
||||
4,16,26,16, 4,
|
||||
7,26,41,26, 7,
|
||||
4,16,26,16, 4,
|
||||
1, 4, 7, 4, 1
|
||||
], { w:5, h:5, div:120, offset:-800 });
|
||||
bg.palette = new Uint16Array(16);
|
||||
bg.palette.set(settings.colors.map(c=>g.toColor(c)));
|
||||
settings.img = bg;
|
||||
settings.imgOpt = {scale:11};
|
||||
delete settings.colors;
|
||||
}
|
||||
};
|
||||
exports.reload();
|
||||
|
||||
// Fill a rectangle with the current background style, rect = {x,y,w,h}
|
||||
// eg require("clockbg").fillRect({x:10,y:10,w:50,h:50})
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
{ "id": "clockbg",
|
||||
"name": "Clock Backgrounds",
|
||||
"shortName":"Backgrounds",
|
||||
"version": "0.03",
|
||||
"version": "0.05",
|
||||
"description": "Library that allows clocks to include a custom background (generated on demand or uploaded).",
|
||||
"icon": "app.png",
|
||||
"screenshots": [{"url":"screenshot.png"},{"url":"screenshot2.png"}],
|
||||
"screenshots": [{"url":"screenshot.png"},{"url":"screenshot2.png"},{"url":"screenshot3.png"}],
|
||||
"type": "module",
|
||||
"readme": "README.md",
|
||||
"provides_modules" : ["clockbg"],
|
||||
|
|
After Width: | Height: | Size: 2.3 KiB |
|
@ -1,20 +1,20 @@
|
|||
(function(back) {
|
||||
let settings = Object.assign({
|
||||
let settings = Object.assign({
|
||||
style : "randomcolor",
|
||||
colors : ["#F00","#0F0","#00F"]
|
||||
},require("Storage").readJSON("clockbg.json")||{});
|
||||
},require("Storage").readJSON("clockbg.json")||{});
|
||||
|
||||
function saveSettings() {
|
||||
function saveSettings() {
|
||||
if (settings.style!="image")
|
||||
delete settings.fn;
|
||||
if (settings.style!="color")
|
||||
delete settings.color;
|
||||
if (settings.style!="randomcolor" && settings.style!="squares")
|
||||
if (!["randomcolor","squares","plasma"].includes(settings.style))
|
||||
delete settings.colors;
|
||||
require("Storage").writeJSON("clockbg.json", settings);
|
||||
}
|
||||
}
|
||||
|
||||
function getColorsImage(cols) {
|
||||
function getColorsImage(cols) {
|
||||
var bpp = 1;
|
||||
if (cols.length>4) bpp=4;
|
||||
else if (cols.length>2) bpp=2;
|
||||
|
@ -26,9 +26,9 @@ function getColorsImage(cols) {
|
|||
b.palette[i] = g.toColor(c);
|
||||
});
|
||||
return "\0"+b.asImage("string");
|
||||
}
|
||||
}
|
||||
|
||||
function showModeMenu() {
|
||||
function showModeMenu() {
|
||||
E.showMenu({
|
||||
"" : {title:/*LANG*/"Background", back:showMainMenu},
|
||||
/*LANG*/"Solid Color" : function() {
|
||||
|
@ -50,6 +50,10 @@ function showModeMenu() {
|
|||
var cols = [
|
||||
["#F00","#0F0","#FF0","#00F","#F0F","#0FF"],
|
||||
["#F00","#0F0","#00F"],
|
||||
["#FF0","#F0F","#0FF"],
|
||||
["#00f","#0bf","#0f7","#3f0","#ff0","#f30","#f07","#b0f"],
|
||||
["#66f","#6df","#6fb","#8f6","#ff6","#f86","#f6b","#d6f"],
|
||||
["#007","#057","#073","#170","#770","#710","#703","#507"]
|
||||
// Please add some more!
|
||||
];
|
||||
var menu = {"":{title:/*LANG*/"Colors", back:showModeMenu}};
|
||||
|
@ -81,24 +85,43 @@ function showModeMenu() {
|
|||
}
|
||||
},
|
||||
/*LANG*/"Squares" : function() {
|
||||
/*
|
||||
a = new Array(16);
|
||||
a.fill(0);
|
||||
print(a.map((n,i)=>E.HSBtoRGB(0 + i/16,1,1,24).toString(16).padStart(6,0).replace(/(.).(.).(.)./,"\"#$1$2$3\"")).join(","))
|
||||
*/
|
||||
var cols = [ // list of color palettes used as possible square colours - either 4 or 16 entries
|
||||
["#00f","#05f","#0bf","#0fd","#0f7","#0f1","#3f0","#9f0","#ff0","#f90","#f30","#f01","#f07","#f0d","#b0f","#50f"],
|
||||
["#44f","#48f","#4df","#4fe","#4fa","#4f6","#7f4","#bf4","#ff4","#fb4","#f74","#f46","#f4a","#f4e","#d4f","#84f"],
|
||||
["#009","#039","#079","#098","#094","#091","#290","#590","#990","#950","#920","#901","#904","#908","#709","#309"],
|
||||
["#0FF","#0CC","#088","#044"],
|
||||
["#FFF","#FBB","#F66","#F44"],
|
||||
["#FFF","#BBB","#666","#000"]
|
||||
// Please add some more!
|
||||
["#FFF","#BBB","#666","#000"],
|
||||
["#fff","#bbf","#77f","#33f"],
|
||||
["#fff","#bff","#7fe","#3fd"]
|
||||
// Please add some more! 4 or 16 only!
|
||||
];
|
||||
var menu = {"":{title:/*LANG*/"Squares", back:showModeMenu}};
|
||||
cols.forEach(col => {
|
||||
menu[getColorsImage(col)] = () => {
|
||||
settings.style = "squares";
|
||||
settings.colors = col;
|
||||
console.log(settings);
|
||||
saveSettings();
|
||||
showMainMenu();
|
||||
};
|
||||
});
|
||||
E.showMenu(menu);
|
||||
},
|
||||
/*LANG*/"Plasma" : function() {
|
||||
var cols = [ // list of color palettes used as possible square colours - 16 entries
|
||||
["#00f","#05f","#0bf","#0fd","#0f7","#0f1","#3f0","#9f0","#ff0","#f90","#f30","#f01","#f07","#f0d","#b0f","#50f"],
|
||||
["#44f","#48f","#4df","#4fe","#4fa","#4f6","#7f4","#bf4","#ff4","#fb4","#f74","#f46","#f4a","#f4e","#d4f","#84f"],
|
||||
["#009","#039","#079","#098","#094","#091","#290","#590","#990","#950","#920","#901","#904","#908","#709","#309"],
|
||||
["#fff","#fef","#fdf","#fcf","#fbf","#fae","#f9e","#f8e","#f7e","#f6e","#f5d","#f4d","#f3d","#f2d","#f1d","#f0c"],
|
||||
["#fff","#eff","#dff","#cef","#bef","#adf","#9df","#8df","#7cf","#6cf","#5bf","#4bf","#3bf","#2af","#1af","#09f"],
|
||||
["#000","#010","#020","#130","#140","#250","#260","#270","#380","#390","#4a0","#4b0","#5c0","#5d0","#5e0","#6f0"]
|
||||
// Please add some more!
|
||||
];
|
||||
var menu = {"":{title:/*LANG*/"Plasma", back:showModeMenu}};
|
||||
cols.forEach(col => {
|
||||
menu[getColorsImage(col)] = () => {
|
||||
settings.style = "plasma";
|
||||
settings.colors = col;
|
||||
saveSettings();
|
||||
showMainMenu();
|
||||
};
|
||||
|
@ -106,17 +129,43 @@ function showModeMenu() {
|
|||
E.showMenu(menu);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function showMainMenu() {
|
||||
function showMainMenu() {
|
||||
E.showMenu({
|
||||
"" : {title:/*LANG*/"Clock Background", back:back},
|
||||
/*LANG*/"Mode" : {
|
||||
value : settings.style,
|
||||
onchange : showModeMenu
|
||||
},
|
||||
/*LANG*/"View" : () => {
|
||||
Bangle.setUI({mode:"custom",touch:showMainMenu,btn:showMainMenu});
|
||||
require("clockbg").reload();
|
||||
require("clockbg").fillRect(Bangle.appRect);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
showMainMenu();
|
||||
})
|
||||
/* Scripts for generating colors. Change the values in HSBtoRGB to generate different effects
|
||||
|
||||
|
||||
a = new Array(16);
|
||||
a.fill(0);
|
||||
g.clear();
|
||||
w = Math.floor(g.getWidth()/a.length);
|
||||
print(a.map((n,i)=>{
|
||||
var j = i/(a.length-1); // 0..1
|
||||
var c = E.HSBtoRGB(j,1,1,24); // rainbow
|
||||
var c = E.HSBtoRGB(j,0.6,1,24); // faded rainbow
|
||||
var c = E.HSBtoRGB(0.8, j,1,24); // purple->white
|
||||
var c = E.HSBtoRGB(0.1, j,1,24); // blue->white
|
||||
var c = E.HSBtoRGB(0.4, 1,j,24); // black->green
|
||||
var col = c.toString(16).padStart(6,0).replace(/(.).(.).(.)./,"\"#$1$2$3\"");
|
||||
g.setColor(eval(col)).fillRect(i*w,0, i*w+w-1,31);
|
||||
return col;
|
||||
}).join(","))
|
||||
|
||||
*/
|
||||
|
||||
showMainMenu();
|
||||
})
|
|
@ -12,3 +12,4 @@
|
|||
0.30: Added options to show widgets and date on twist and tap. New fonts.
|
||||
0.31: Bugfix, no more freeze.
|
||||
0.32: Minor code improvements
|
||||
0.33: Messages would sometimes halt the clock. This should be fixed now.
|
||||
|
|
|
@ -1,9 +1,6 @@
|
|||
{
|
||||
let drawTimeout;
|
||||
let extrasTimeout;
|
||||
let onLock;
|
||||
//let onTap;
|
||||
//let onTwist;
|
||||
let extrasTimer=0;
|
||||
let settings = require('Storage').readJSON("contourclock.json", true) || {};
|
||||
if (settings.fontIndex == undefined) {
|
||||
settings.fontIndex = 0;
|
||||
|
@ -16,9 +13,9 @@
|
|||
require('Storage').writeJSON("contourclock.json", settings);
|
||||
}
|
||||
require("FontTeletext10x18Ascii").add(Graphics);
|
||||
let extrasShown = (!settings.hidewhenlocked) && (!Bangle.isLocked());
|
||||
let installedFonts = require('Storage').readJSON("contourclock-install.json") || {};
|
||||
if (installedFonts.n > 0) { //New install - check for unused font files
|
||||
// New install - check for unused font files. This should probably be handled by the installer instead
|
||||
if (installedFonts.n > 0) {
|
||||
settings.fontIndex = E.clip(settings.fontIndex, -installedFonts.n + 1, installedFonts.n - 1);
|
||||
require('Storage').writeJSON("contourclock.json", settings);
|
||||
for (let n = installedFonts.n;; n++) {
|
||||
|
@ -27,14 +24,22 @@
|
|||
}
|
||||
require("Storage").erase("contourclock-install.json");
|
||||
}
|
||||
let showExtras = function() { //show extras for a limited time
|
||||
let onLock = function(locked) {if (!locked) showExtras();};
|
||||
let showExtras = function() { //show extras for 5s
|
||||
drawExtras();
|
||||
if (extrasTimeout) clearTimeout(extrasTimeout);
|
||||
extrasTimeout = setTimeout(() => {
|
||||
extrasTimeout = undefined;
|
||||
hideExtras();
|
||||
extrasTimer = 5000-60000-(Date.now()%60000);
|
||||
if (extrasTimer<0) { //schedule next redraw early to hide extras
|
||||
if (drawTimeout) clearTimeout(drawTimeout);
|
||||
drawTimeout = setTimeout(function() {
|
||||
drawTimeout = undefined;
|
||||
draw();
|
||||
}, 5000);
|
||||
extrasShown = false;
|
||||
}
|
||||
};
|
||||
let hideExtras = function() {
|
||||
g.reset();
|
||||
g.clearRect(0, 138, g.getWidth() - 1, 176);
|
||||
if (settings.widgets) require("widget_utils").hide();
|
||||
};
|
||||
let drawExtras = function() { //draw date, day of the week and widgets
|
||||
let date = new Date();
|
||||
|
@ -43,38 +48,27 @@
|
|||
g.setFont("Teletext10x18Ascii").setFontAlign(0, 1);
|
||||
if (settings.weekday) g.drawString(require("locale").dow(date).toUpperCase(), g.getWidth() / 2, g.getHeight() - 18);
|
||||
if (settings.date) g.drawString(require('locale').date(date, 1), g.getWidth() / 2, g.getHeight());
|
||||
require("widget_utils").show();
|
||||
extrasShown = true;
|
||||
};
|
||||
let hideExtras = function() {
|
||||
if (extrasTimeout) clearTimeout(extrasTimeout);
|
||||
extrasTimeout = undefined; //NEW
|
||||
g.reset();
|
||||
g.clearRect(0, 138, g.getWidth() - 1, 176);
|
||||
require("widget_utils").hide();
|
||||
extrasShown = false; ///NEW
|
||||
if (settings.widgets) require("widget_utils").show();
|
||||
};
|
||||
let draw = function() {
|
||||
if (drawTimeout) clearTimeout(drawTimeout); //NEW
|
||||
if (extrasTimer>0) { //schedule next draw early to remove extras
|
||||
drawTimeout = setTimeout(function() {
|
||||
drawTimeout = undefined;
|
||||
draw();
|
||||
}, extrasTimer);
|
||||
extrasTimer=0;
|
||||
} else {
|
||||
if (settings.hideWhenLocked) hideExtras();
|
||||
drawTimeout = setTimeout(function() {
|
||||
drawTimeout = undefined;
|
||||
draw();
|
||||
}, 60000 - (Date.now() % 60000));
|
||||
}
|
||||
g.reset();
|
||||
if (extrasShown) drawExtras();
|
||||
else hideExtras();
|
||||
if (!settings.hideWhenLocked) drawExtras();
|
||||
require('contourclock').drawClock(settings.fontIndex);
|
||||
};
|
||||
if (settings.hideWhenLocked) {
|
||||
onLock = locked => {
|
||||
if (!locked) {
|
||||
require("widget_utils").show();
|
||||
drawExtras();
|
||||
} else {
|
||||
require("widget_utils").hide();
|
||||
hideExtras();
|
||||
}
|
||||
};
|
||||
Bangle.on('lock', onLock);
|
||||
if (settings.tapToShow) Bangle.on('tap', showExtras);
|
||||
if (settings.twistToShow) Bangle.on('twist', showExtras);
|
||||
|
@ -82,14 +76,16 @@
|
|||
Bangle.setUI({
|
||||
mode: "clock",
|
||||
remove: function() {
|
||||
if (settings.hideWhenLocked) {
|
||||
Bangle.removeListener('lock', onLock);
|
||||
Bangle.removeListener('tap', showExtras);
|
||||
Bangle.removeListener('twist', showExtras);
|
||||
if (drawTimeout) clearTimeout(drawTimeout);
|
||||
if (extrasTimeout) clearTimeout(extrasTimeout);
|
||||
if (settings.tapToShow) Bangle.removeListener('tap', showExtras);
|
||||
if (settings.twistToShow) Bangle.removeListener('twist', showExtras);
|
||||
}
|
||||
if (drawTimeout) {
|
||||
clearTimeout(drawTimeout);
|
||||
drawTimeout = undefined;
|
||||
extrasTimeout = undefined;
|
||||
if (settings.hideWhenLocked) require("widget_utils").show();
|
||||
}
|
||||
if (settings.hideWhenLocked && settings.widgets) require("widget_utils").show();
|
||||
g.reset();
|
||||
g.clear();
|
||||
}
|
||||
|
@ -97,7 +93,8 @@
|
|||
g.clear();
|
||||
if (settings.widgets) {
|
||||
Bangle.loadWidgets();
|
||||
setTimeout(Bangle.drawWidgets,0); //NEW
|
||||
Bangle.drawWidgets();
|
||||
}
|
||||
draw();
|
||||
if (!settings.hideWhenLocked || !Bangle.isLocked()) showExtras();
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{ "id": "contourclock",
|
||||
"name": "Contour Clock",
|
||||
"shortName" : "Contour Clock",
|
||||
"version": "0.32",
|
||||
"version": "0.33",
|
||||
"icon": "app.png",
|
||||
"readme": "README.md",
|
||||
"description": "A Minimalist clockface with large Digits.",
|
||||
|
|
|
@ -2,3 +2,4 @@
|
|||
0.02: Added decrement and touch functions
|
||||
0.03: Set color - ensures widgets don't end up coloring the counter's text
|
||||
0.04: Adopted for BangleJS 2
|
||||
0.05: Support translations
|
||||
|
|
|
@ -95,9 +95,9 @@ if (BANGLEJS2) {
|
|||
g.clear(1).setFont("6x8");
|
||||
g.setBgColor(g.theme.bg).setColor(g.theme.fg);
|
||||
if (BANGLEJS2) {
|
||||
g.drawString('Swipe up to increase\nSwipe down to decrease\nPress button to reset.', x, 100 + y);
|
||||
g.drawString([/*LANG*/"Swipe up to increase", /*LANG*/"Swipe down to decrease", /*LANG*/"Press button to reset"].join("\n"), x, 100 + y);
|
||||
} else {
|
||||
g.drawString('Tap right or BTN1 to increase\nTap left or BTN3 to decrease\nPress BTN2 to reset.', x, 100 + y);
|
||||
g.drawString([/*LANG*/"Tap right or BTN1 to increase", /*LANG*/"Tap left or BTN3 to decrease", /*LANG*/"Press BTN2 to reset"].join("\n"), x, 100 + y);
|
||||
}
|
||||
|
||||
Bangle.loadWidgets();
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"id": "counter",
|
||||
"name": "Counter",
|
||||
"version": "0.04",
|
||||
"version": "0.05",
|
||||
"description": "Simple counter",
|
||||
"icon": "counter_icon.png",
|
||||
"tags": "tool",
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
0.01: Initial release.
|
||||
0.02: Fix reset of progress bars on midnight. Fix display of 100k+ steps.
|
||||
0.03: Added option to display weather.
|
||||
0.04: Added option to display live updates of step count.
|
||||
|
|
|
@ -20,8 +20,9 @@ The appearance is highly configurable. In the settings menu you can:
|
|||
- Set the daily step goal.
|
||||
- En- or disable the individual progress bars.
|
||||
- Set if your week should start with Monday or Sunday (for week progress bar).
|
||||
- Toggle live step count updates.*
|
||||
|
||||
*) Hiding seconds should further reduce power consumption as the draw interval is prolonged as well.
|
||||
*) Hiding seconds and leaving live steps off should further reduce power consumption as the draw interval is prolonged as well.
|
||||
|
||||
The clock implements Fast Loading for faster switching to and fro.
|
||||
|
||||
|
|
|
@ -14,6 +14,7 @@
|
|||
weekBar: true,
|
||||
mondayFirst: true,
|
||||
dayBar: true,
|
||||
liveSteps: false,
|
||||
}, require('Storage').readJSON('edgeclk.settings.json', true) || {});
|
||||
|
||||
/* Runtime Variables
|
||||
|
@ -279,6 +280,9 @@
|
|||
drawLower();
|
||||
};
|
||||
|
||||
const onStep = function () {
|
||||
drawSteps();
|
||||
}
|
||||
|
||||
/* Lifecycle Functions
|
||||
------------------------------------------------------------------------------*/
|
||||
|
@ -298,6 +302,9 @@
|
|||
|
||||
// Charging event signals when charging status changes:
|
||||
Bangle.on('charging', onCharging);
|
||||
|
||||
// Continously update step count when they happen:
|
||||
if (settings.redrawOnStep) Bangle.on('step', onStep);
|
||||
};
|
||||
|
||||
const deregisterEvents = function () {
|
||||
|
@ -306,6 +313,7 @@
|
|||
Bangle.removeListener('health', onHealth);
|
||||
Bangle.removeListener('lock', onLock);
|
||||
Bangle.removeListener('charging', onCharging);
|
||||
if (settings.redrawOnStep) Bangle.removeListener('step', onStep);
|
||||
};
|
||||
|
||||
const startTimers = function () {
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
"id": "edgeclk",
|
||||
"name": "Edge Clock",
|
||||
"shortName": "Edge Clock",
|
||||
"version": "0.03",
|
||||
"version": "0.04",
|
||||
"description": "Crisp clock with perfect readability.",
|
||||
"readme": "README.md",
|
||||
"icon": "app.png",
|
||||
|
|
|
@ -14,6 +14,7 @@
|
|||
weekBar: true,
|
||||
mondayFirst: true,
|
||||
dayBar: true,
|
||||
redrawOnStep: false,
|
||||
};
|
||||
|
||||
const saved_settings = storage.readJSON(SETTINGS_FILE, true);
|
||||
|
@ -121,5 +122,12 @@
|
|||
save();
|
||||
},
|
||||
},
|
||||
'Live steps': {
|
||||
value: settings.redrawOnStep,
|
||||
onchange: () => {
|
||||
settings.redrawOnStep = !settings.redrawOnStep;
|
||||
save();
|
||||
},
|
||||
},
|
||||
});
|
||||
})
|
||||
|
|
After Width: | Height: | Size: 2.2 KiB |
After Width: | Height: | Size: 3.2 KiB |
After Width: | Height: | Size: 1.9 KiB |
After Width: | Height: | Size: 3.2 KiB |
After Width: | Height: | Size: 3.1 KiB |
After Width: | Height: | Size: 2.8 KiB |
After Width: | Height: | Size: 2.5 KiB |
|
@ -0,0 +1 @@
|
|||
0.1: New App! Need to work out locale settings
|
|
@ -0,0 +1,38 @@
|
|||
# Exact Words
|
||||
|
||||
This is a clock for expressing the time in exact words. Each minute of
|
||||
the day has a different phrase.
|
||||
|
||||
Ranging from "Twelve" to "Coming up to midnight" to "A little after
|
||||
twenty-five past four in the early hours"
|
||||
|
||||
Screenshots best demonstrate
|
||||
|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||
|
||||
|
||||
"just gone " - as in Just gone quarter past four is 16:16
|
||||
|
||||
"a little after ", as in A little after quarter past three is 15:17
|
||||
|
||||
"coming up to ", as in Coming up to midnight is 23:58
|
||||
|
||||
"almost " as in Almost twenty-five to seven is 06:34
|
||||
|
||||
## To Do
|
||||
|
||||
Add localisation.
|
||||
|
||||
## Requests
|
||||
|
||||
Written by: [Brendan Sleight](https://github.com/bmsleight/) For support and discussion please post in the Bangle JS Forum
|
||||
|
||||
|
||||
## Creator
|
||||
|
||||
[Brendan Sleight](https://github.com/bmsleight/)
|
|
@ -0,0 +1 @@
|
|||
require("heatshrink").decompress(atob("mEwgJC/AAMEjtogFQgEMjIFB6EAh0d/eH7dwAoNrx/X4Eaju7g/boAoKkACFh9f23BzswAoO38P/0EP78/wN/0EO70WwPf2EGDYKNCguAgFAlAFDgAFBg/d3F4v3ggvz/F8lXgg/x3F8nXAPBkHEgQABn+Xs1MAoN9393/wFBqu+r++AoN021W9ytYQIQACv3/j/bz+cv+/j/0/8cgECI4MQPYWUqoYCgP//4fDiAQCAAoA="))
|
|
@ -0,0 +1,225 @@
|
|||
// timeout used to update every minute
|
||||
var drawTimeout;
|
||||
|
||||
// https://www.espruino.com/Bangle.js+Locale
|
||||
|
||||
// schedule a draw for the next minute
|
||||
function queueDraw() {
|
||||
if (drawTimeout) clearTimeout(drawTimeout);
|
||||
drawTimeout = setTimeout(function() {
|
||||
drawTimeout = undefined;
|
||||
draw();
|
||||
}, 60000 - (Date.now() % 60000));
|
||||
}
|
||||
|
||||
function wordsFromTime(h, m)
|
||||
{
|
||||
|
||||
// Tests
|
||||
/*
|
||||
// Example 12:00 = Twelve
|
||||
// h = 12;
|
||||
// m = 0;
|
||||
// Example 23:58 = Coming up to midnight
|
||||
// h = 23;
|
||||
// m = 58;
|
||||
// Example 12:15 = Quarter past twelve
|
||||
// h = 12;
|
||||
// m = 15;
|
||||
// Example 04:16 = Just gone quarter past four
|
||||
// h = 16;
|
||||
// m = 16;
|
||||
// Example 01:00 = One at night
|
||||
// h = 1;
|
||||
// m = 0;
|
||||
// Example 17:01 = Just gone five in the afternoon
|
||||
// h = 17;
|
||||
// m = 1;
|
||||
// Example 05:25 = Twenty-five past five in the early hours
|
||||
// h = 23;
|
||||
// m = 33;
|
||||
// Example 22:33 = coming up to eleven at night
|
||||
// max
|
||||
//words = "a little after twenty-five past four in the early hours";
|
||||
// h = 04;
|
||||
// m = 27;
|
||||
*/
|
||||
|
||||
|
||||
const HOUR_WORD_ARRAY = [
|
||||
"midnight", "one", "two", "three", "four", "five", "six", "seven",
|
||||
"eight", "nine", "ten", "eleven", "twelve", "one", "two", "three",
|
||||
"four", "five", "six", "seven", "eight", "nine", "ten", "eleven",
|
||||
"midnight"];
|
||||
const PART_DAY_WORD_ARRAY = ["",
|
||||
" at night",
|
||||
" in the early hours",
|
||||
" in the early hours",
|
||||
" in the early hours",
|
||||
" in the early hours",
|
||||
" in the morning",
|
||||
" in the morning",
|
||||
" in the morning",
|
||||
" in the morning",
|
||||
" in the morning",
|
||||
" in the morning",
|
||||
"",
|
||||
" in the afternoon",
|
||||
" in the afternoon",
|
||||
" in the afternoon",
|
||||
" in the afternoon",
|
||||
" in the afternoon",
|
||||
" in the evening",
|
||||
" in the evening",
|
||||
" in the evening",
|
||||
" in the evening",
|
||||
" at night",
|
||||
" at night",
|
||||
""];
|
||||
const MINUTES_ROUGH_ARRAY = ["",
|
||||
"five past ",
|
||||
"ten past ",
|
||||
"quarter past ",
|
||||
"twenty past ",
|
||||
"twenty-five past ",
|
||||
"half past ",
|
||||
"twenty-five to ",
|
||||
"twenty to ",
|
||||
"quarter to ",
|
||||
"ten to ",
|
||||
"five to ",
|
||||
""];
|
||||
const MINUTES_ACCURATE_ARRAY = ["", "just gone ", "a little after ", "coming up to ", "almost "];
|
||||
|
||||
var hourAdjusted = h;
|
||||
var words = " ", hourWord = " ", partDayWord = " ", minutesRough = " ", minutesAccurate = " ";
|
||||
|
||||
// At 33 past the hours we start referign to the next hour
|
||||
if (m > 32) {
|
||||
hourAdjusted = (h+ 1) % 24;
|
||||
} else {
|
||||
hourAdjusted = h;
|
||||
}
|
||||
|
||||
hourWord = HOUR_WORD_ARRAY[hourAdjusted];
|
||||
partDayWord = PART_DAY_WORD_ARRAY[Math.round(hourAdjusted)];
|
||||
minutesRough = MINUTES_ROUGH_ARRAY[Math.round((m + 0 ) / 5)];
|
||||
minutesAccurate = MINUTES_ACCURATE_ARRAY[m % 5];
|
||||
|
||||
words = minutesAccurate + minutesRough + hourWord + partDayWord;
|
||||
words = words.charAt(0).toUpperCase() + words.slice(1);
|
||||
return words;
|
||||
}
|
||||
|
||||
function wordsFromDayMonth(day, date, month)
|
||||
{
|
||||
// Tests
|
||||
|
||||
// Example 12:00 = Twelve
|
||||
// New Year's Day
|
||||
// date = 1;
|
||||
// month = 0;
|
||||
// on the Ides of March
|
||||
// date = 15;
|
||||
// month = 2;
|
||||
// , ERROR C Nonsense in BASIC
|
||||
// date = 1;
|
||||
// month = 3;
|
||||
// - O'Canada
|
||||
// date = 1;
|
||||
// month = 6;
|
||||
// - on Halloween
|
||||
// date = 31;
|
||||
// month = 9;
|
||||
// - Christmas Eve
|
||||
// date = 24;
|
||||
// month = 11;
|
||||
// - Christmas Day
|
||||
// date = 25;
|
||||
// month = 11;
|
||||
// - Boxing day
|
||||
// date = 26;
|
||||
// month = 11;
|
||||
// New Year's eve
|
||||
// date = 31;
|
||||
// month = 11;
|
||||
// longest
|
||||
// date = 29;
|
||||
// month = 10;
|
||||
|
||||
|
||||
const DAY_WORD_ARRAY = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"];
|
||||
const DATE_WORD_ARRAY = ["zero", "first", "second", "third", "fourth", "fifth", "sixth", "seventh","eighth", "ninth", "tenth", "eleventh", "twelfth", "thirteenth", "fourteenth", "fifteenth","sixteenth", "seventeenth", "eighteenth", "nineteenth", "twentieth", "twenty-first", "twenty-second", "twenty-third","twenty-fourth", "twenty-fifth", "twenty-sixth", "twenty-seventh", "twenty-eighth", "twenty-ninth", "thirtieth", "thirty-first"];
|
||||
const MONTH_WORD_ARRAY = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"];
|
||||
var words = " ";
|
||||
words = DAY_WORD_ARRAY[day] + ", " + DATE_WORD_ARRAY[date] + " of " + MONTH_WORD_ARRAY[month];
|
||||
if ((date == 1) && (month == 0)) {
|
||||
words = "New Year's Day";
|
||||
} else if ((date == 15) && (month == 2)) {
|
||||
words = DAY_WORD_ARRAY[day] + " on the Ides of March";
|
||||
} else if ((date == 1) && (month == 3)) {
|
||||
words = DAY_WORD_ARRAY[day] + ", ERROR C Nonsense in BASIC";
|
||||
} else if ((date == 1) && (month == 6)) {
|
||||
words = DAY_WORD_ARRAY[day] + " - O'Canada";
|
||||
} else if ((date == 31) && (month == 9)) {
|
||||
words = DAY_WORD_ARRAY[day] + " - on Halloween";
|
||||
} else if ((date == 24) && (month == 11)) {
|
||||
words = "Christmas Eve";
|
||||
} else if ((date == 25) && (month == 11)) {
|
||||
words = "Christmas Day";
|
||||
} else if ((date == 26) && (month == 11)) {
|
||||
words = "Boxing Day";
|
||||
} else if ((date == 31) && (month == 11)) {
|
||||
words = "New Year's eve";
|
||||
}
|
||||
return words;
|
||||
}
|
||||
|
||||
function draw() {
|
||||
var x = g.getWidth()/2;
|
||||
var y = g.getHeight()/2;
|
||||
g.reset();
|
||||
|
||||
var d = new Date();
|
||||
var h = d.getHours();
|
||||
var m = d.getMinutes();
|
||||
var day = d.getDay();
|
||||
var date = d.getDate();
|
||||
var month = d.getMonth();
|
||||
|
||||
var timeStr = wordsFromTime(h,m);
|
||||
var dateStr = wordsFromDayMonth(day, date, month);
|
||||
|
||||
// draw time
|
||||
g.setBgColor(g.theme.bg);
|
||||
g.setColor(g.theme.fg);
|
||||
g.clear();
|
||||
g.setFontAlign(0,0).setFont("Vector",24);
|
||||
g.drawString(g.wrapString(timeStr, g.getWidth()).join("\n"),x,y-24*0);
|
||||
// draw date
|
||||
|
||||
g.setFontAlign(0,0).setFont("Vector",12);
|
||||
g.drawString(g.wrapString(dateStr, g.getWidth()).join("\n"),x,y+12*6);
|
||||
// queue draw in one minute
|
||||
queueDraw();
|
||||
}
|
||||
|
||||
// Clear the screen once/, at startup
|
||||
g.clear();
|
||||
// draw immediately at first, queue update
|
||||
draw();
|
||||
// Stop updates when LCD is off, restart when on
|
||||
Bangle.on('lcdPower',on=>{
|
||||
if (on) {
|
||||
draw(); // draw immediately, queue redraw
|
||||
} else { // stop draw timer
|
||||
if (drawTimeout) clearTimeout(drawTimeout);
|
||||
drawTimeout = undefined;
|
||||
}
|
||||
});
|
||||
|
||||
// Show launcher when middle button pressed
|
||||
Bangle.setUI("clock");
|
||||
// Load widgets
|
||||
Bangle.loadWidgets();
|
||||
Bangle.drawWidgets();
|
After Width: | Height: | Size: 19 KiB |
|
@ -0,0 +1,22 @@
|
|||
{ "id": "exactwords",
|
||||
"name": "Exact Words Clock",
|
||||
"shortName":"Exact Words",
|
||||
"version":"0.1",
|
||||
"description": "Each minute of the day has a different phrase. ",
|
||||
"icon": "app.png",
|
||||
"screenshots" : [ { "url":"1517.png" },
|
||||
{ "url":"0634.png" },
|
||||
{ "url":"1200.png" },
|
||||
{ "url":"1517.png" },
|
||||
{ "url":"1616.png" },
|
||||
{ "url":"2020.png" },
|
||||
{ "url":"2358.png" } ],
|
||||
"tags": "clock",
|
||||
"type": "clock",
|
||||
"supports" : ["BANGLEJS2"],
|
||||
"readme": "README.md",
|
||||
"storage": [
|
||||
{"name":"exactwords.app.js","url":"app.js"},
|
||||
{"name":"exactwords.img","url":"app-icon.js","evaluate":true}
|
||||
]
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
0.01: New Clock!
|
|
@ -0,0 +1,5 @@
|
|||
# Fact Clock
|
||||
|
||||
A clock that displays a random fact alongside the time.
|
||||
|
||||
This uses `text_facts` for the list of facts, but you can implement new apps that provide the `textsource` module (see the readme for the `text_facts` app) to make this clock display different information.
|
|
@ -0,0 +1 @@
|
|||
require("heatshrink").decompress(atob("mEwwcF23btoCV7QfBrYRM2kAhMkgEtCJegiwDBgBBLtAMCoA3BCwQRHoADCNIUCKxMDAYPAPgZcICIWwRwgRI6ARBwAREhYRIjYRBgVggEDCJOACIO07dsgETtsBCJcTsEGBIORCJRHCmwJB2LRHg0DGQIFC7ZKBNwnRkmSm0ACIsAgO2yRcCwIRDhgRILgWJCIMWgENR4fbhuBCINBMgMJCJNt2YLBoEtCIcGwEN2wRE3+kCI1C4DpDR4WdGoIRFpHYCIUBR4XtCJFYCIacBtgRIpKECCIdnpJZHpMhZIRcB7c7AwIRHkktEYNN23ZaYQRIwDhDrwRLyUCBoOXCoYRJpMgIgIIECJMkEAKwBCJ41OCMoFECJlpNaJZ0I/0BCKEACIsAn//AAX0yUQCIQAGFQ2QCJMACIuAlu2wASIAAkBJYPQBIsF0AHFhZgEARoA=="))
|
|
@ -0,0 +1,110 @@
|
|||
Graphics.prototype.setFontBebasNeue = function() {
|
||||
// Actual height 31 (32 - 2)
|
||||
// 1 BPP
|
||||
return this.setFontCustom(
|
||||
atob('AAAAAAAAAAAAAAAAAAAPgAAAAAD4AAAAAA+AAAAAAPgAAAAAD4AAAAAAAAAAAAAAgAAAAAA4AAAAAB+AAAAAD/gAAAAD/4AAAAH/4AAAAH/4AAAAP/wAAAAP/wAAAAf/gAAAAf/gAAAA//AAAAA/+AAAAAP+AAAAAD8AAAAAA8AAAAAAIAAAAAAAAAAAAAAAAAAAAAAf//8AAAf///wAAP///+AAH////wAD////+AA/////gAPgAAD4AD4AAA+AA+AAAPgAPgAAD4AD////+AAf////AAH////wAA////4AAH///8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOAAAAAADwAAAAAA8AAAAAAfAAAAAAHwAAAAAD////4AB////+AA/////gAP////4AD////+AA/////gAAAAAAAAAAAAAAAAAcAAHgAB/gA/4AA/4A/+AAf+Af/gAP/gP/4AD/wH/+AA+AD+PgAPgD+D4AD4B/A+AA+B/gPgAP//wD4AD//4A+AAf/8APgAD/8AD4AAf8AAeAAAAAAAAAAAAAAAAADgAfAAAH4AH8AAD+AB/wAB/gAf8AA/4AH/gAPwPgH4AD4D4A+AA+A+APgAPgPwD4AD8H+B+AA/////gAH////wAB//f/8AAP/j/+AAA/gf+AAAAAAAAAAAAAHgAAAAAP8AAAAAP/AAAAAP/wAAAAf/8AAAAf//AAAA//HwAAA/+B8AAA/+AfAAA/+AHwAAP////4AD////+AA/////gAP////4AD////+AAAAAHwAAAAAB8AAAAAAAAAAAAAAAAAAAAh4AAD//4fwAA//+H+AAP//h/wAD//4f+AA//+B/gAPgeAD4AD4PAA+AA+DwAPgAPh+AD4AD4f//+AA+D///gAPg///wAD4H//4AA8A//8AAAAAAAAAAAAAAAAAA///gAAB////AAA////4AAf////AAP////4AD////+AA+A8APgAPgfAD4AD4HwA+AA+B8APgAP8f//4AD/H//+AAfx///AAD8P//gAAfB//wAAAAB/AAAAAAAAAAAAAAAAAA8AAAAAAPgAAAAAD4AAACAA+AAAHgAPgAAf4AD4AA/+AA+AD//gAPgH//4AD4P//wAA+///gAAP//+AAAD//8AAAA//wAAAAP/AAAAAD+AAAAAAAAAAAAAAAAAAAAAH4D/gAAH/j/+AAD/9//wAB////8AA/////gAP9/4H4AD4D8A+AA+A+APgAPgPgD4AD4D8A+AA/////gAP////4AB////8AAP/3/+AAB/4f/AAAAAA+AAAAAAAAAAAAAAAAAAH/wHAAAH//B8AAH//4fwAB///H8AA///x/gAPwH8H4AD4AfA+AA+AHwPgAPgB4D4AD4A+B+AA/////gAH////wAB////8AAP///+AAA///+AAAAAAAAAAAAAAAAAAAAAAAAAAPgA+AAAD4APgAAA+AD4AAAPgA+AAAD4APgAAAAAAAAA'),
|
||||
46,
|
||||
atob("CBIRDxEREhESERIRCA=="),
|
||||
44|65536
|
||||
);
|
||||
};
|
||||
|
||||
{
|
||||
// the font we're using
|
||||
const factFont = "6x15";
|
||||
// swap every 10 minutes
|
||||
const minsPerFact = 5;
|
||||
// timeout used to update every minute
|
||||
let drawTimeout;
|
||||
// the fact we're going to display (pre-rendered with a border)
|
||||
let factGfx;
|
||||
// how long until the next fact?
|
||||
let factCounter = minsPerFact;
|
||||
// the gfx we use for the time so we can gat a shadow on it
|
||||
let timeGfx = Graphics.createArrayBuffer(g.getWidth()>>1, 48, 2, {msb:true});
|
||||
timeGfx.transparent = 0;
|
||||
timeGfx.palette = new Uint16Array([
|
||||
0, g.toColor(g.theme.bg), 0, g.toColor(g.theme.fg)
|
||||
]);
|
||||
|
||||
|
||||
let getNewFact = () => {
|
||||
let fact = require("textsource").getRandomText();
|
||||
// wrap to fit the screen
|
||||
let lines = g.setFont(factFont).wrapString(fact.txt, g.getWidth()-10);
|
||||
let txt = lines.join("\n");
|
||||
// allocate a gfx for this
|
||||
factGfx = Graphics.createArrayBuffer(g.getWidth(), g.stringMetrics(txt).height+4, 2, {msb:true});
|
||||
factGfx.transparent = 0;
|
||||
factGfx.setFont(factFont).setFontAlign(0,-1).setColor(3).drawString(txt, factGfx.getWidth()/2, 2);
|
||||
if (factGfx.filter) factGfx.filter([ // add shadow behind text
|
||||
0,1,1,1,0,
|
||||
1,1,1,1,1,
|
||||
1,1,1,1,1,
|
||||
1,1,1,1,1,
|
||||
0,1,1,1,0,
|
||||
], { w:5, h:5, div:1, max:1, filter:"max" });
|
||||
factGfx.palette = new Uint16Array([
|
||||
0, g.toColor(g.theme.bg), 0, g.toColor(g.theme.fg)
|
||||
]);
|
||||
};
|
||||
getNewFact();
|
||||
|
||||
// schedule a draw for the next minute
|
||||
let queueDraw = function() {
|
||||
if (drawTimeout) clearTimeout(drawTimeout);
|
||||
drawTimeout = setTimeout(function() {
|
||||
drawTimeout = undefined;
|
||||
draw();
|
||||
}, 60000 - (Date.now() % 60000));
|
||||
};
|
||||
|
||||
let draw = function() {
|
||||
// queue next draw in one minute
|
||||
queueDraw();
|
||||
// new fact?
|
||||
if (--factCounter < 0) {
|
||||
factCounter = minsPerFact;
|
||||
getNewFact();
|
||||
}
|
||||
// Work out where to draw...
|
||||
g.reset();
|
||||
require("clockbg").fillRect(Bangle.appRect);
|
||||
// work out locale-friendly date/time
|
||||
var date = new Date();
|
||||
var timeStr = require("locale").time(date,1);
|
||||
var dateStr = require("locale").date(date,1);
|
||||
// draw time to buffer
|
||||
timeGfx.clear(1);
|
||||
timeGfx.setFontAlign(0,-1).setFont("BebasNeue");
|
||||
timeGfx.drawString(timeStr,timeGfx.getWidth()/2,2);
|
||||
timeGfx.setFontAlign(0,1).setFont("6x8");
|
||||
timeGfx.drawString(dateStr,timeGfx.getWidth()/2,timeGfx.getHeight()-2);
|
||||
// add shadow to buffer and render
|
||||
if (timeGfx.filter) timeGfx.filter([ // add shadow behind text
|
||||
0,1,1,1,0,
|
||||
1,1,1,1,1,
|
||||
1,1,1,1,1,
|
||||
1,1,1,1,1,
|
||||
0,1,1,1,0,
|
||||
], { w:5, h:5, div:1, max:1, filter:"max" });
|
||||
var y = (Bangle.appRect.y+g.getHeight()-(factGfx.getHeight()+timeGfx.getHeight()*2))>>1;
|
||||
g.drawImage(timeGfx,0, y, {scale:2});
|
||||
// draw the fact
|
||||
g.drawImage(factGfx,0, g.getHeight()-factGfx.getHeight());
|
||||
};
|
||||
|
||||
// Show launcher when middle button pressed
|
||||
Bangle.setUI({mode:"clock", remove:function() {
|
||||
// free any memory we allocated to allow fast loading
|
||||
delete Graphics.prototype.setFontBebasNeue;
|
||||
if (drawTimeout) clearTimeout(drawTimeout);
|
||||
drawTimeout = undefined;
|
||||
require('widget_utils').show(); // re-show widgets
|
||||
}});
|
||||
// Load widgets
|
||||
Bangle.loadWidgets();
|
||||
require("widget_utils").swipeOn();
|
||||
// draw immediately at first, queue update
|
||||
draw();
|
||||
}
|
After Width: | Height: | Size: 14 KiB |
|
@ -0,0 +1,17 @@
|
|||
{ "id": "factclock",
|
||||
"name": "Fact Clock",
|
||||
"shortName":"Facts",
|
||||
"version":"0.01",
|
||||
"description": "A clock that displays a random fact alongside the time",
|
||||
"icon": "icon.png",
|
||||
"screenshots": [{"url":"screenshot.png"}],
|
||||
"type": "clock",
|
||||
"tags": "clock",
|
||||
"supports" : ["BANGLEJS2"],
|
||||
"dependencies" : { "textsource":"module", "clockbg":"module" },
|
||||
"readme": "README.md",
|
||||
"storage": [
|
||||
{"name":"factclock.app.js","url":"app.js"},
|
||||
{"name":"factclock.img","url":"app-icon.js","evaluate":true}
|
||||
]
|
||||
}
|
After Width: | Height: | Size: 5.8 KiB |
|
@ -1,2 +1,3 @@
|
|||
0.01: New App!
|
||||
0.02: Corrected formatting of punctuation
|
||||
0.03: Extend range slightly to include Ukrainian fonts
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{ "id": "fontext",
|
||||
"name": "Fonts (150+ languages)",
|
||||
"version":"0.02",
|
||||
"version":"0.03",
|
||||
"description": "Installs a font containing 1000 Unifont characters, which should handle the majority of non-Chinese/Japanese/Korean languages (only 20kb)",
|
||||
"icon": "app.png",
|
||||
"tags": "font,fonts,language",
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
0.01: New App!
|
|
@ -0,0 +1 @@
|
|||
require("heatshrink").decompress(atob("mEwwYHEgMkyVAkmQDJYREyQRRoARQpARQpIRRkARNggRBkgRNgARCwARNiQRBSRIREgQRBSRIREgARCSRARFhKSKCIoFCSRAjISQ0BAQJZHCI6ZBTwKPEI44tBTIMSYoZ9IBIYyEWZCHEKwbXIDwZ6MBghjBWBR7DIQbmJAAJ7BexYRHGZZHEchRrGNJYRIRpARJWI7XDCIrVHLIeACIpuIgKwBR4RcQyDLFCJbLGCJcAZZgLEiRcLCIkCZZYvFCKAjDI6BZOPqD+PWaUJa6ARCTxARICBQRFPRIRHPRIRHBg4A="))
|
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
Bangle.setUI({mode:"custom",remove:()=>{}});"Bangle.loadWidgets"; // Allow fastloading.
|
||||
|
||||
Bluetooth.println(JSON.stringify({t:"intent", action:"nodomain.freeyourgadget.gadgetbridge.BLUETOOTH_DISCONNECT", extra:{EXTRA_DEVICE_ADDRESS:NRF.getAddress()}}));
|
||||
|
||||
Bangle.showClock();
|
||||
}
|
After Width: | Height: | Size: 1.1 KiB |
|
@ -0,0 +1,13 @@
|
|||
{ "id": "gbdiscon",
|
||||
"name": "Disconnect from Gadgetbridge",
|
||||
"shortName":"Disconnect Gadgetbridge",
|
||||
"version":"0.01",
|
||||
"description": "Disconnect from your android device by running this app. The app will forward you to your clock face immediately after triggering the command. (Gadgetbridge nightly required until version 82 is released)",
|
||||
"icon": "app.png",
|
||||
"tags": "android, gadgetbridge, bluetooth, bt",
|
||||
"supports" : ["BANGLEJS", "BANGLEJS2"],
|
||||
"storage": [
|
||||
{"name":"gbdiscon.app.js","url":"app.js"},
|
||||
{"name":"gbdiscon.img","url":"app-icon.js","evaluate":true}
|
||||
]
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
hadash.json
|
||||
node_modules
|
||||
package-lock.json
|
||||
package.json
|
|
@ -0,0 +1,2 @@
|
|||
1.00: initial release
|
||||
1.01: remember scoll positions of menus; "silent" option for service calls
|
|
@ -0,0 +1,66 @@
|
|||
# Home-Assistant Dashboard
|
||||
|
||||
This app interacts with a Home-Assistant (HA) instance. You can query entity
|
||||
states and call services. This allows you access to up-to-date information of
|
||||
any home automation system integrated into HA, and you can also control your
|
||||
automations from your wrist.
|
||||
|
||||

|
||||
|
||||
|
||||
## How It Works
|
||||
|
||||
This app uses the REST API to directly interact with HA (which requires a
|
||||
"long-lived access token" - refer to "Configuration").
|
||||
|
||||
You can define a menu structure to be displayed on your Bangle, with the states
|
||||
to be queried and services to be called. Menu entries can be:
|
||||
|
||||
* entry to show the state of a HA entity
|
||||
* entry to call a HA service
|
||||
* sub-menus, including nested sub-menus
|
||||
|
||||
Calls to a service can also have optional input for data fields on the Bangle
|
||||
itself.
|
||||
|
||||
|
||||
## Configuration
|
||||
|
||||
After installing the app, use the "interface" page (floppy disk icon) in the
|
||||
App Loader to configure it.
|
||||
|
||||
Make sure to set the "Home-Assistant API Base URL" (which must include the
|
||||
"/api" path, as well - but no slash at the end).
|
||||
|
||||
Also create a "long-lived access token" in HA (under the Profile section, at
|
||||
the bottom) and enter it as the "Long-lived access token".
|
||||
|
||||
The tricky bit will be to configure your menu structure. You need to have a
|
||||
basic understanding of the JSON format. The configuration page uses a JSON
|
||||
Editor which will check the syntax and highlight any errors for you. Follow the
|
||||
instructions on the page regarding how to configure menus, menu entries and the
|
||||
required attributes. It also contains examples.
|
||||
|
||||
Once you're happy with the menu structure (and you've entered the base URL and
|
||||
access token), click the "Configure / Upload to Bangle" button.
|
||||
|
||||
|
||||
## Security
|
||||
|
||||
The "long-lived access token" will be stored unencrypted on your Bangle. This
|
||||
would - in theory - mean that if your Bangle gets stolen, the new "owner" would
|
||||
have unrestricted access to your Home-Assistant instance (the thief would have
|
||||
to be fairly tech-savvy, though). However, I suggest you create a separate
|
||||
token exclusively for your Bangle - that way, it's very easy to simply delete
|
||||
that token in case your watch is stolen or lost.
|
||||
|
||||
|
||||
## To-Do
|
||||
|
||||
- A better way to configure the menu structure would be useful, something like a custom editor (replacing the jsoneditor).
|
||||
|
||||
|
||||
## Author
|
||||
|
||||
Flaparoo [github](https://github.com/flaparoo)
|
||||
|
|
@ -0,0 +1 @@
|
|||
require("heatshrink").decompress(atob("mEwwgmjhGACyuIxAYUCwIABFyowUCwYwSFwgwSCwowQFwwwQCw4wOFxAwOCxIwMFwuDnAwPCwv//4YFFx0/C4PzGBpXFCwIABMJiMGC5IwGQ4wXJGAq7HC5QwEW4+PCwP4YZTqJFxAwEdBIXKGAQXWIxIXMwAXXBRIXFwYFBnATKC5E/AoPzC6bdKC/4XTx4WCO5CbGC4YuDU5CbGC4QuCma+JKYwECXhoXIFwTsLC5DrONgxzMO4woDFx6nDFIYuPJQoqBFx4ADRIYuSGAiUEGCQXUYg4wUC6YwDBpUIGBYWJwA"))
|
|
@ -0,0 +1,247 @@
|
|||
/*
|
||||
* Home-Assistant Dashboard - Bangle.js
|
||||
*/
|
||||
|
||||
const APP_NAME = 'hadash';
|
||||
|
||||
var scroller;
|
||||
|
||||
// Load settings
|
||||
var settings = Object.assign({
|
||||
menu: [
|
||||
{ type: 'state', title: 'Check for updates', id: 'update.home_assistant_core_update' },
|
||||
{ type: 'service', title: 'Create Notification', domain: 'persistent_notification', service: 'create',
|
||||
data: { 'message': 'test notification', 'title': 'Test'} },
|
||||
{ type: 'menu', title: 'Sub-menu', data:
|
||||
[
|
||||
{ type: 'state', title: 'Check for Supervisor updates', id: 'update.home_assistant_supervisor_update' },
|
||||
{ type: 'service', title: 'Restart HA', domain: 'homeassistant', service: 'restart', silent: true, data: {} }
|
||||
]
|
||||
},
|
||||
{ type: 'service', title: 'Custom Notification', domain: 'persistent_notification', service: 'create',
|
||||
data: { 'title': 'Not via input'},
|
||||
input: { 'message': { options: [], value: 'Pre-filled text' },
|
||||
'notification_id': { options: [ 123, 456, 136 ], value: 999, label: "ID" } } },
|
||||
],
|
||||
HAbaseUrl: '',
|
||||
HAtoken: '',
|
||||
}, require('Storage').readJSON(APP_NAME+'.json', true) || {});
|
||||
|
||||
|
||||
// wrapper to show a menu (preserving scroll position)
|
||||
function showScrollerMenu(menu) {
|
||||
const r = E.showMenu(menu).scroller;
|
||||
scroller = r;
|
||||
return r;
|
||||
}
|
||||
|
||||
|
||||
// query an entity state
|
||||
function queryState(title, id, level) {
|
||||
menus[level][''].scroll = scroller.scroll;
|
||||
E.showMessage('Fetching entity state from HA', { title: title });
|
||||
Bangle.http(settings.HAbaseUrl+'/states/'+id, {
|
||||
headers: {
|
||||
'Authorization': 'Bearer '+settings.HAtoken,
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
}).then(data => {
|
||||
//console.log(data);
|
||||
let HAresp = JSON.parse(data.resp);
|
||||
let title4prompt = title;
|
||||
let msg = HAresp.state;
|
||||
if ('attributes' in HAresp) {
|
||||
if ('friendly_name' in HAresp.attributes)
|
||||
title4prompt = HAresp.attributes.friendly_name;
|
||||
if ('unit_of_measurement' in HAresp.attributes)
|
||||
msg += HAresp.attributes.unit_of_measurement;
|
||||
}
|
||||
E.showPrompt(msg, { title: title4prompt, buttons: {OK: true} }).then((v) => { showScrollerMenu(menus[level]); });
|
||||
}).catch( error => {
|
||||
console.log(error);
|
||||
E.showPrompt('Error querying state!', { title: title, buttons: {OK: true} }).then((v) => { showScrollerMenu(menus[level]); });
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
// call a service
|
||||
function callService(title, domain, service, data, level, silent) {
|
||||
menus[level][''].scroll = scroller.scroll;
|
||||
E.showMessage('Calling HA service', { title: title });
|
||||
Bangle.http(settings.HAbaseUrl+'/services/'+domain+'/'+service, {
|
||||
method: 'POST',
|
||||
body: data,
|
||||
headers: {
|
||||
'Authorization': 'Bearer '+settings.HAtoken,
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
}).then(data => {
|
||||
//console.log(data);
|
||||
if (! silent) {
|
||||
return E.showPrompt('Service called successfully', { title: title, buttons: {OK: true} });
|
||||
}
|
||||
}).then(() => {
|
||||
showScrollerMenu(menus[level]);
|
||||
}).catch( error => {
|
||||
console.log(error);
|
||||
E.showPrompt('Error calling service!', { title: title, buttons: {OK: true} }).then((v) => { showScrollerMenu(menus[level]); });
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
// callbacks for service input menu entries
|
||||
function serviceInputChoiceChange(v, key, entry, level) {
|
||||
entry.input[key].value = entry.input[key].options[v];
|
||||
getServiceInputData(entry, level);
|
||||
}
|
||||
|
||||
function serviceInputFreeform(key, entry, level) {
|
||||
require("textinput").input({text: entry.input[key].value}).then(result => {
|
||||
entry.input[key].value = result;
|
||||
getServiceInputData(entry, level);
|
||||
});
|
||||
}
|
||||
|
||||
// get input data before calling a service
|
||||
function getServiceInputData(entry, level) {
|
||||
menus[level][''].scroll = scroller.scroll;
|
||||
let serviceInputMenu = {
|
||||
'': {
|
||||
'title': entry.title,
|
||||
'back': () => showScrollerMenu(menus[level])
|
||||
},
|
||||
};
|
||||
let CBs = {};
|
||||
for (let key in entry.input) {
|
||||
// pre-fill data with default values
|
||||
if ('value' in entry.input[key])
|
||||
entry.data[key] = entry.input[key].value;
|
||||
|
||||
let label = ( ('label' in entry.input[key] && entry.input[key].label) ? entry.input[key].label : key );
|
||||
let key4CB = key;
|
||||
|
||||
if ('options' in entry.input[key] && entry.input[key].options.length) {
|
||||
// give choice from a selection of options
|
||||
let idx = -1;
|
||||
for (let i in entry.input[key].options) {
|
||||
if (entry.input[key].value == entry.input[key].options[i]) {
|
||||
idx = i;
|
||||
}
|
||||
}
|
||||
if (idx == -1) {
|
||||
idx = entry.input[key].options.push(entry.input[key].value) - 1;
|
||||
}
|
||||
// the setTimeout method can not be used for the "format" CB since it expects a return value:
|
||||
CBs[`${key}_format`] = ((key) => function(v) { return entry.input[key].options[v]; })(key);
|
||||
serviceInputMenu[label] = {
|
||||
value: parseInt(idx),
|
||||
min: 0,
|
||||
max: entry.input[key].options.length - 1,
|
||||
format: CBs[key+'_format'],
|
||||
onchange: (v) => setTimeout(serviceInputChoiceChange, 10, v, key4CB, entry, level)
|
||||
};
|
||||
|
||||
} else {
|
||||
// free-form text input
|
||||
serviceInputMenu[label] = () => setTimeout(serviceInputFreeform, 10, key4CB, entry, level);
|
||||
}
|
||||
}
|
||||
// menu entry to actually call the service:
|
||||
serviceInputMenu['Call service'] = function() { callService(entry.title, entry.domain, entry.service, entry.data, level, entry.silent); };
|
||||
E.showMenu(serviceInputMenu);
|
||||
}
|
||||
|
||||
|
||||
// menu hierarchy
|
||||
var menus = [];
|
||||
|
||||
|
||||
// add menu entries
|
||||
function addMenuEntries(level, entries) {
|
||||
for (let i in entries) {
|
||||
let entry = entries[i];
|
||||
let entryCB;
|
||||
|
||||
// is there a menu entry title?
|
||||
if (! ('title' in entry) || ! entry.title)
|
||||
entry.title = 'TBD';
|
||||
|
||||
switch (entry.type) {
|
||||
case 'state':
|
||||
/*
|
||||
* query entity state
|
||||
*/
|
||||
if ('id' in entry && entry.id) {
|
||||
entryCB = () => setTimeout(queryState, 10, entry.title, entry.id, level);
|
||||
}
|
||||
break;
|
||||
|
||||
case 'service':
|
||||
/*
|
||||
* call HA service
|
||||
*/
|
||||
if (! ('silent' in entry))
|
||||
entry.silent = false;
|
||||
if ('domain' in entry && entry.domain && 'service' in entry && entry.service) {
|
||||
if (! ('data' in entry))
|
||||
entry.data = {};
|
||||
if ('input' in entry) {
|
||||
// get input for some data fields first
|
||||
entryCB = () => setTimeout(getServiceInputData, 10, entry, level);
|
||||
} else {
|
||||
// call service straight away
|
||||
entryCB = () => setTimeout(callService, 10, entry.title, entry.domain, entry.service, entry.data, level, entry.silent);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case 'menu':
|
||||
/*
|
||||
* sub-menu
|
||||
*/
|
||||
entryCB = () => setTimeout(showSubMenu, 10, level + 1, entry.title, entry.data);
|
||||
break;
|
||||
}
|
||||
|
||||
// only attach a call-back to menu entry if it's properly configured
|
||||
if (! entryCB) {
|
||||
menus[level][entry.title + ' - not correctly configured!'] = {};
|
||||
} else {
|
||||
menus[level][entry.title] = entryCB;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// create and show a sub menu
|
||||
function showSubMenu(level, title, entries) {
|
||||
menus[level - 1][''].scroll = scroller.scroll;
|
||||
menus[level] = {
|
||||
'': {
|
||||
'title': title,
|
||||
'back': () => showScrollerMenu(menus[level - 1])
|
||||
},
|
||||
};
|
||||
addMenuEntries(level, entries);
|
||||
showScrollerMenu(menus[level]);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* create the main menu
|
||||
*/
|
||||
menus[0] = {
|
||||
'': {
|
||||
'title': 'HA-Dash',
|
||||
'back': () => load()
|
||||
},
|
||||
};
|
||||
addMenuEntries(0, settings.menu);
|
||||
|
||||
// check required configuration
|
||||
if (! settings.HAbaseUrl || ! settings.HAtoken) {
|
||||
E.showAlert('The app is not yet configured!', 'HA-Dash').then(() => showScrollerMenu(menus[0]));
|
||||
} else {
|
||||
showScrollerMenu(menus[0]);
|
||||
}
|
||||
|
After Width: | Height: | Size: 10 KiB |
|
@ -0,0 +1,261 @@
|
|||
<html>
|
||||
<head>
|
||||
<link rel="stylesheet" href="../../css/spectre.min.css">
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="form-label" for="HAbaseUrl">Home-Assistant API Base URL:</label>
|
||||
<input class="form-input" type="text" id="HAbaseUrl" placeholder="https://ha.example:8123/api" />
|
||||
<div>
|
||||
<small class="text-muted">Make sure to include "/api" as the URL path, but no slash at the end.</small>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label" for="HAtoken">Long-lived access token:</label>
|
||||
<input class="form-input" type="text" id="HAtoken" placeholder="Your Long-lived Access Token" />
|
||||
<div>
|
||||
<small class="text-muted">It's recommended to create a dedicated token for your Bangle.</small>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="divider"></div>
|
||||
|
||||
<h4>Menu structure</h4>
|
||||
|
||||
<p>Use the editor below to configure the menu structure displayed in the
|
||||
Bangle app. It is in the JSON format.</p>
|
||||
|
||||
<p>The main menu, and any sub-menus, are arrays. They can contain 3
|
||||
different types of entries (objects) defined by the "type" attribute:</p>
|
||||
<ul>
|
||||
<li>
|
||||
<b>Query an Entity State</b>
|
||||
<pre class="code" data-lang="JSON">
|
||||
{
|
||||
"type": "state",
|
||||
"title": "Menu entry title",
|
||||
"id": "HA Entity ID"
|
||||
}
|
||||
</pre>
|
||||
The required Entity ID can be looked up in HA under Settings ->
|
||||
Devices & Services -> Entities. For example:
|
||||
<pre class="code" data-lang="JSON">
|
||||
{
|
||||
"type": "state",
|
||||
"title": "Check for updates",
|
||||
"id": "update.home_assistant_core_update"
|
||||
}
|
||||
</pre>
|
||||
</li>
|
||||
<li>
|
||||
<b>Call a HA service</b>
|
||||
<pre class="code" data-lang="JSON">
|
||||
{
|
||||
"type": "service",
|
||||
"title": "Menu entry title",
|
||||
"domain": "HA Domain",
|
||||
"service": "HA Service",
|
||||
"silent": (true|false),
|
||||
"data": {
|
||||
"key": "value"
|
||||
}
|
||||
}
|
||||
</pre>
|
||||
<p>The required information to call a HA service can be found in HA
|
||||
under the Developer tools -> Services. Use the "Go to YAML Mode"
|
||||
function to see the actual names and values. The domain and service
|
||||
parts are the 2 parts of the service name which are separated by a dot.
|
||||
If the optional "silent" boolean is set to true (false is default),
|
||||
there will be no message in case of a successful call. Any (optional)
|
||||
data key/value pairs can be added under the "data" field. For example,
|
||||
here's a service call YAML:
|
||||
<pre class="code" data-lang="YAML">
|
||||
service: persistent_notification.create
|
||||
data:
|
||||
message: test Notification
|
||||
title: Test
|
||||
</pre>
|
||||
The resulting menu entry (JSON object) should be:
|
||||
<pre class="code" data-lang="JSON">
|
||||
{
|
||||
"type": "service",
|
||||
"title": "Create Notification",
|
||||
"domain": "persistent_notification",
|
||||
"service": "create",
|
||||
"data": {
|
||||
"message": "test Notification",
|
||||
"title": "Test"
|
||||
}
|
||||
}
|
||||
</pre>
|
||||
If the service requires a target, include the
|
||||
"entity_id"/"device_id"/etc. (listed under "target:") also as "data"
|
||||
key/value pairs. For example, if the YAML also includes:
|
||||
<pre class="code" data-lang="YAML">
|
||||
target:
|
||||
device_id: abcd1234
|
||||
</pre>
|
||||
... add another "data" key/value pair: <code>"device_id": "abcd1234"</code>.
|
||||
If that doesn't work, list the device (or entity) ID in an array:
|
||||
<code>"device_id": [ "abcd1234" ]</code></p>
|
||||
|
||||
<p>Data fields can also have variable input on the Bangle. In that
|
||||
case, don't add the key/value pair under "data", but create an "input"
|
||||
object with entries per "data" key:
|
||||
<pre class="code" data-lang="JSON">
|
||||
{
|
||||
"key": {
|
||||
"options": [],
|
||||
"value": "",
|
||||
"label": ""
|
||||
}
|
||||
}
|
||||
</pre>
|
||||
If "options" is left empty, the preferred text-input (pick your
|
||||
favourite app - I like the dragboard) is called to allow entering a
|
||||
free-form value. Otherwise, list the allowed values in the "options"
|
||||
array. The "value" is the pre-defined/default value. The "label" is
|
||||
optional and can be used to override the label for this key (as
|
||||
displayed on the Bangle). For example:
|
||||
<pre class="code" data-lang="JSON">
|
||||
{
|
||||
"type": "service",
|
||||
"title": "Custom Notification",
|
||||
"domain": "persistent_notification",
|
||||
"service": "create",
|
||||
"data": {
|
||||
"title": "Fixed"
|
||||
},
|
||||
"input": {
|
||||
"message": {
|
||||
"options": [],
|
||||
"value": "Pre-filled text"
|
||||
},
|
||||
"notification_id": {
|
||||
"options": [
|
||||
"123",
|
||||
"456",
|
||||
"136"
|
||||
],
|
||||
"value": "999",
|
||||
"label": "ID"
|
||||
}
|
||||
}
|
||||
}
|
||||
</pre>
|
||||
In the above example, the "data" will have 3 key/value pairs when the
|
||||
service is called: the "title" is always the same, "message" can be
|
||||
entered via the (free-form) text-input and "notification_id" can be
|
||||
selected from a list of numbers (however, the prompt will be for "ID"
|
||||
and not "notification_id"). If the default value is not listed in
|
||||
"options" (like "999"), it will be added to that list.</p>
|
||||
</li>
|
||||
<li>
|
||||
<b>Sub-menu</b>
|
||||
<pre class="code" data-lang="JSON">
|
||||
{
|
||||
"type": "menu",
|
||||
"title": "Menu entry / sub-menu title",
|
||||
"data": []
|
||||
}
|
||||
</pre>
|
||||
The "data" needs to be another array of menu entries. For example:
|
||||
<pre class="code" data-lang="JSON">
|
||||
{
|
||||
"type": "menu",
|
||||
"title": "Sub-menu",
|
||||
"data": [
|
||||
{
|
||||
"type": "state",
|
||||
"title": "Check for Supervisor updates",
|
||||
"id": "update.home_assistant_supervisor_update"
|
||||
},
|
||||
{
|
||||
"type": "service",
|
||||
"title": "Restart HA",
|
||||
"domain": "homeassistant",
|
||||
"service": "restart",
|
||||
"data": {}
|
||||
}
|
||||
]
|
||||
}
|
||||
</pre>
|
||||
Sub-menus can contain other sub-menus, so you can have multiple levels
|
||||
of nested menus.
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<div id="jsoneditor"></div>
|
||||
|
||||
<div class="divider"></div>
|
||||
|
||||
<p><div id="status"></div></p>
|
||||
|
||||
<button id="upload" class="btn btn-primary">Configure / Upload to Bangle</button>
|
||||
|
||||
|
||||
<script>
|
||||
var JSONEditorInstance;
|
||||
var JSONEditor_target = document.getElementById('jsoneditor');
|
||||
</script>
|
||||
<script src="jsoneditor.bundlejs"></script>
|
||||
|
||||
<script src="../../core/lib/interface.js"></script>
|
||||
<script>
|
||||
var settings = {
|
||||
menu: [
|
||||
{ type: 'state', title: 'Check for updates', id: 'update.home_assistant_core_update' },
|
||||
{ type: 'service', title: 'Create Notification', domain: 'persistent_notification', service: 'create',
|
||||
data: { 'message': 'test notification', 'title': 'Test'} },
|
||||
{ type: 'menu', title: 'Sub-menu', data:
|
||||
[
|
||||
{ type: 'state', title: 'Check for Supervisor updates', id: 'update.home_assistant_supervisor_update' },
|
||||
{ type: 'service', title: 'Restart HA', domain: 'homeassistant', service: 'restart', silent: true, data: {} }
|
||||
]
|
||||
},
|
||||
{ type: 'service', title: 'Custom Notification', domain: 'persistent_notification', service: 'create',
|
||||
data: { 'title': 'Not via input'},
|
||||
input: { 'message': { options: [], value: 'Pre-filled text' },
|
||||
'notification_id': { options: [ 123, 456, 136 ], value: 999, label: "ID" } } },
|
||||
]
|
||||
};
|
||||
|
||||
function onInit() {
|
||||
// read in existing settings from the Bangle
|
||||
try {
|
||||
Util.readStorageJSON('hadash.json', currentSettings => {
|
||||
if (currentSettings) {
|
||||
settings = currentSettings;
|
||||
if ('HAbaseUrl' in settings)
|
||||
document.getElementById('HAbaseUrl').value = settings.HAbaseUrl;
|
||||
if ('HAtoken' in settings)
|
||||
document.getElementById('HAtoken').value = settings.HAtoken;
|
||||
if ('menu' in settings)
|
||||
JSONEditorInstance.update({ text: undefined, json: settings.menu });
|
||||
}
|
||||
});
|
||||
} catch (e) {
|
||||
console.log("Failed to read existing settings: "+e);
|
||||
}
|
||||
}
|
||||
|
||||
document.getElementById("upload").addEventListener("click", function() {
|
||||
if (JSONEditorInstance.get().json) {
|
||||
settings.menu = JSONEditorInstance.get().json;
|
||||
} else {
|
||||
settings.menu = JSON.parse(JSONEditorInstance.get().text);
|
||||
}
|
||||
if (! settings.menu) {
|
||||
document.getElementById("status").innerHTML = 'Generating the menu failed (or menu is empty) - upload aborted!';
|
||||
return;
|
||||
}
|
||||
settings.HAbaseUrl = document.getElementById('HAbaseUrl').value;
|
||||
settings.HAtoken = document.getElementById('HAtoken').value;
|
||||
Util.writeStorage('hadash.json', JSON.stringify(settings), () => {
|
||||
document.getElementById("status").innerHTML = 'HA-Dash configuration successfully uploaded to Bangle!';
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,22 @@
|
|||
/*
|
||||
* JSONEditor wrapper
|
||||
*
|
||||
* This script is bundled together with the actual JSONEditor (https://github.com/josdejong/svelte-jsoneditor)
|
||||
* using ESBuild (see below).
|
||||
*
|
||||
* The following global variables need to be defined before including the jsoneditor-bundle.js:
|
||||
*
|
||||
* JSONEditorInstance will contain the new JSONEditor instance
|
||||
* JSONEditor_target element ID of container (<div>) for the JSONEditor
|
||||
*
|
||||
* To build the bundle, run the following commands:
|
||||
* npm install esbuild
|
||||
* npm install vanilla-jsoneditor
|
||||
* ./node_modules/.bin/esbuild jsoneditor.wrapperjs --bundle --outfile=jsoneditor.bundlejs
|
||||
*
|
||||
*/
|
||||
|
||||
import { JSONEditor } from 'vanilla-jsoneditor/standalone.js'
|
||||
|
||||
JSONEditorInstance = new JSONEditor({ target: JSONEditor_target, props: {} });
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
{
|
||||
"id": "hadash",
|
||||
"name": "Home-Assistant Dashboard",
|
||||
"shortName":"HA-Dash",
|
||||
"version":"1.01",
|
||||
"description": "Interact with Home-Assistant (query states, call services)",
|
||||
"icon": "hadash.png",
|
||||
"screenshots": [{ "url": "screenshot.png" }],
|
||||
"type": "app",
|
||||
"tags": "tool,online",
|
||||
"supports": ["BANGLEJS2"],
|
||||
"dependencies": { "textinput": "type" },
|
||||
"readme": "README.md",
|
||||
"interface": "interface.html",
|
||||
"storage": [
|
||||
{ "name":"hadash.app.js", "url":"hadash.app.js" },
|
||||
{ "name":"hadash.img", "url":"hadash-icon.js", "evaluate":true }
|
||||
],
|
||||
"data": [{ "name":"hadash.json" }]
|
||||
}
|
After Width: | Height: | Size: 3.1 KiB |
|
@ -2,3 +2,4 @@
|
|||
0.02: Minor code improvements
|
||||
0.03: Better scrolling behaviour
|
||||
0.04: Fix incorrect appRect handling (missing back buttons and doubled menu titles)
|
||||
0.05: Bring in change from the firmware implementation forwarding the type of touch (short/long).
|
||||
|
|
|
@ -50,7 +50,7 @@
|
|||
if ((menuScrollMin<0 || i>=0) && i<options.c){
|
||||
let yAbs = (e.y + rScroll - R.y);
|
||||
let yInElement = yAbs - i*options.h;
|
||||
options.select(i, {x:e.x, y:yInElement});
|
||||
options.select(i, {x:e.x, y:yInElement, type:e.type});
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
(function(){E.showScroller=function(c){function k(a){return a*c.h+Bangle.appRect.y-l}function h(a){return Math.floor((a+l-Bangle.appRect.y)/c.h)}if(!c)return Bangle.setUI();let n,f=0,p=0,q=0,r=0,t=0,m=0|c.scrollMin;const u=()=>{let a=c.h*c.c-Bangle.appRect.h;a<m&&(a=m);return a},v=()=>{let a=Bangle.appRect;g.reset().clearRect(a).setClipRect(a.x,a.y,a.x2,a.y2);for(var b=h(a.y),d=Math.min(h(a.y2),c.c-1);b<=d;b++)c.draw(b,{x:a.x,y:k(b),w:a.w,h:c.h});g.setClipRect(0,0,g.getWidth()-
|
||||
1,g.getHeight()-1)},w=()=>{let a=Bangle.appRect;.1<f&&(n||(n=setTimeout(w,0)),f*=1-(Date.now()-r)/8E3,.1>=f?f=0:e.scroll-=f*q);var b=u();e.scroll>b&&(e.scroll=b,f=0);e.scroll<m&&(e.scroll=m,f=0);b=l;l=e.scroll&-2;if(b-=l){g.reset().setClipRect(a.x,a.y,a.x2,a.y2).scroll(0,b);if(0>b){b=Math.max(a.y2-(1-b),a.y);g.setClipRect(a.x,b,a.x2,a.y2);var d=h(b);for(b=k(d);b<a.y2;b+=c.h)c.draw(d,{x:a.x,y:b,w:a.w,h:c.h}),d++}else for(b=Math.min(a.y+b,a.y2),g.setClipRect(a.x,a.y,a.x2,b),d=h(b),k(d),b=k(d);b>a.y-
|
||||
c.h;b-=c.h)c.draw(d,{x:a.x,y:b,w:a.w,h:c.h}),d--;g.setClipRect(0,0,g.getWidth()-1,g.getHeight()-1);n=void 0}};let x={mode:"custom",back:c.back,drag:a=>{let b=Date.now();q=Math.sign(a.dy);e.scroll-=a.dy;if(0<a.b){r=b;if(!t||0>p*q&&0<a.dy*q)t=r,p=f=0;p+=a.dy}else 150>b-r&&(f=q*p/(b-t)*100),t=0;w()},touch:(a,b)=>{a=Bangle.appRect;if(!(b.y<a.y-4)){p=f=0;var d=h(b.y);(0>m||0<=d)&&d<c.c&&c.select(d,{x:b.x,y:b.y+l-a.y-d*c.h})}},redraw:v};c.remove&&(x.remove=()=>{n&&clearTimeout(n);c.remove()});Bangle.setUI(x);
|
||||
let e={scroll:E.clip(0|c.scroll,m,u()),draw:()=>{let a=Bangle.appRect;g.reset().clearRect(a).setClipRect(a.x,a.y,a.x2,a.y2);var b=h(a.y);let d=Math.min(h(a.y2),c.c-1);for(;b<=d;b++)c.draw(b,{x:a.x,y:k(b),w:a.w,h:c.h});g.setClipRect(0,0,g.getWidth()-1,g.getHeight()-1)},drawItem:a=>{let b=Bangle.appRect,d=k(a);g.reset().setClipRect(b.x,Math.max(d,b.y),b.x2,Math.min(d+c.h,b.y2));c.draw(a,{x:b.x,y:d,w:b.w,h:c.h});g.setClipRect(0,0,g.getWidth()-1,g.getHeight()-1)},isActive:()=>Bangle.uiRedraw==v},l=e.scroll&
|
||||
-2;e.draw();g.flip();return e}})()
|
||||
c.h;b-=c.h)c.draw(d,{x:a.x,y:b,w:a.w,h:c.h}),d--;g.setClipRect(0,0,g.getWidth()-1,g.getHeight()-1);n=void 0}};let x={mode:"custom",back:c.back,drag:a=>{let b=Date.now();q=Math.sign(a.dy);e.scroll-=a.dy;if(0<a.b){r=b;if(!t||0>p*q&&0<a.dy*q)t=r,p=f=0;p+=a.dy}else 150>b-r&&(f=q*p/(b-t)*100),t=0;w()},touch:(a,b)=>{a=Bangle.appRect;if(!(b.y<a.y-4)){p=f=0;var d=h(b.y);(0>m||0<=d)&&d<c.c&&c.select(d,{x:b.x,y:b.y+l-a.y-d*c.h,type:b.type})}},redraw:v};c.remove&&(x.remove=()=>{n&&clearTimeout(n);c.remove()});
|
||||
Bangle.setUI(x);let e={scroll:E.clip(0|c.scroll,m,u()),draw:()=>{let a=Bangle.appRect;g.reset().clearRect(a).setClipRect(a.x,a.y,a.x2,a.y2);var b=h(a.y);let d=Math.min(h(a.y2),c.c-1);for(;b<=d;b++)c.draw(b,{x:a.x,y:k(b),w:a.w,h:c.h});g.setClipRect(0,0,g.getWidth()-1,g.getHeight()-1)},drawItem:a=>{let b=Bangle.appRect,d=k(a);g.reset().setClipRect(b.x,Math.max(d,b.y),b.x2,Math.min(d+c.h,b.y2));c.draw(a,{x:b.x,y:d,w:b.w,h:c.h});g.setClipRect(0,0,g.getWidth()-1,g.getHeight()-1)},isActive:()=>Bangle.uiRedraw==
|
||||
v},l=e.scroll&-2;e.draw();g.flip();return e}})()
|
|
@ -1,7 +1,7 @@
|
|||
{ "id": "kineticscroll",
|
||||
"name": "Kinetic Scroll",
|
||||
"shortName":"Kinetic Scroll",
|
||||
"version": "0.04",
|
||||
"version": "0.05",
|
||||
"description": "Replacement for the system scroller with kinetic scrolling.",
|
||||
"icon": "app.png",
|
||||
"type": "bootloader",
|
||||
|
|
|
@ -4,3 +4,4 @@
|
|||
0.04: clock_info is loaded before widgets to match other clocks
|
||||
0.05: fix alignment of clock items caused by 0.04 (fix #2970)
|
||||
0.06: Minor code improvements
|
||||
0.07: fix special characters in clockinfo menus
|
||||
|
|
|
@ -38,7 +38,7 @@ let clockInfoDraw = (itm, info, options) => {
|
|||
|
||||
if (info.img) g.drawImage(info.img, options.x+2, options.y+2);
|
||||
var title = clockInfoItems[options.menuA].name;
|
||||
var text = info.text.toString().toUpperCase();
|
||||
var text = info.text.toString().toUpperCase().replace(/[^A-Z0-9]/g, "");
|
||||
if (title!="Bangle") g.setFontAlign(1,0).drawString(title.toUpperCase(), options.x+options.w-2, options.y+14);
|
||||
if (g.setFont("7Seg:2").stringWidth(text)+8>options.w) g.setFont("7Seg");
|
||||
g.setFontAlign(0,0).drawString(text, options.x+options.w/2, options.y+40);
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{ "id": "lcdclock",
|
||||
"name": "LCD Clock",
|
||||
"version": "0.06",
|
||||
"version": "0.07",
|
||||
"description": "A Casio-style clock, with ClockInfo areas at the top and bottom. Tap them and swipe up/down to toggle between different information",
|
||||
"icon": "app.png",
|
||||
"screenshots": [{"url":"screenshot.png"}],
|
||||
|
|
|
@ -1,2 +1,3 @@
|
|||
0.01: New App!
|
||||
0.02: Minor code improvements
|
||||
0.03: fix special characters in clockinfo menus
|
||||
|
|
|
@ -51,7 +51,7 @@ let clockInfoDraw = (itm, info, options) => {
|
|||
if (info.img) {
|
||||
g.drawImage(info.img, options.x+1,options.y+2);
|
||||
}
|
||||
var text = info.text.toString().toUpperCase();
|
||||
var text = info.text.toString().toUpperCase().replace(/[^A-Z0-9]/g, "");
|
||||
if (g.setFont("7Seg:2").stringWidth(text)+24-2>options.w) g.setFont("7Seg");
|
||||
g.setFontAlign(0,-1).drawString(text, options.x+options.w/2+13, options.y+6);
|
||||
};
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{ "id": "lcdclockplus",
|
||||
"name": "LCD Clock Plus",
|
||||
"version": "0.02",
|
||||
"version": "0.03",
|
||||
"description": "A Casio-style clock, with four ClockInfo areas at the top and bottom. Tap them and swipe up/down and left/right to toggle between different information.",
|
||||
"icon": "app.png",
|
||||
"screenshots": [{"url":"screenshot.png"},{"url":"screenshot2.png"}],
|
||||
|
|
|
@ -91,13 +91,6 @@ module.exports = {
|
|||
"no-unused-vars"
|
||||
]
|
||||
},
|
||||
"apps/mtnclock/app.js": {
|
||||
"hash": "c48e3ed1a605e6a131e5947718e26cc9481c6eeab36c5670bb74f0c58cb96cd8",
|
||||
"rules": [
|
||||
"no-unused-vars",
|
||||
"no-undef"
|
||||
]
|
||||
},
|
||||
"apps/mmonday/manic-monday.js": {
|
||||
"hash": "2eff38d4d1cde2b9f17800a554da58300a6250de3e014995a6958a11bcb5b76a",
|
||||
"rules": [
|
||||
|
@ -189,13 +182,6 @@ module.exports = {
|
|||
"no-unused-vars"
|
||||
]
|
||||
},
|
||||
"apps/skyspy/skyspy.app.js": {
|
||||
"hash": "49a727a4c052e8c6322a502750ca036b0d58896f476b1cffebe9c53e426c8bcc",
|
||||
"rules": [
|
||||
"no-unused-vars",
|
||||
"no-undef"
|
||||
]
|
||||
},
|
||||
"apps/scribble/app.js": {
|
||||
"hash": "6d13abd27bab8009a6bdabe1df2df394bc14aac20c68f67e8f8b085fa6b427cd",
|
||||
"rules": [
|
||||
|
@ -463,12 +449,6 @@ module.exports = {
|
|||
"no-undef"
|
||||
]
|
||||
},
|
||||
"apps/calculator/app.js": {
|
||||
"hash": "fcb7c7b6c4ec5ce0f425d2a690baab8da235a12e685fe2680cbd4cf2cfdef0b0",
|
||||
"rules": [
|
||||
"no-unused-vars"
|
||||
]
|
||||
},
|
||||
"apps/bowserWF/app.js": {
|
||||
"hash": "83feae92eda4c25028892b5b8b7d1b04f7ec3bb45f51eeba517a80b3ab2053cf",
|
||||
"rules": [
|
||||
|
@ -673,12 +653,6 @@ module.exports = {
|
|||
"no-undef"
|
||||
]
|
||||
},
|
||||
"apps/slopeclockpp/app.js": {
|
||||
"hash": "f6e3f6723ed4fc71a3cacb3d24ec4fb47447a65d495adccb9d86333c19d4d0bd",
|
||||
"rules": [
|
||||
"no-undef"
|
||||
]
|
||||
},
|
||||
"apps/slopeclock/app.js": {
|
||||
"hash": "fe29b4674b3e3a791898fd2067acc7c0fcb433dc8d4a8e8e3338c6d42a6c468f",
|
||||
"rules": [
|
||||
|
@ -1195,12 +1169,6 @@ module.exports = {
|
|||
"no-undef"
|
||||
]
|
||||
},
|
||||
"apps/aviatorclk/aviatorclk.app.js": {
|
||||
"hash": "5bc629c86eada72533fc664e01a47e05f207baeeae2092323ae4d04bd1c9fe9a",
|
||||
"rules": [
|
||||
"no-undef"
|
||||
]
|
||||
},
|
||||
"apps/authentiwatch/app.js": {
|
||||
"hash": "02aafe5b6f3f3dce979d5b04f4660f90261e0c893cb39e9eeb2cf0bdb2c256b8",
|
||||
"rules": [
|
||||
|
@ -1304,7 +1272,7 @@ module.exports = {
|
|||
]
|
||||
},
|
||||
"apps/kineticscroll/boot.min.js": {
|
||||
"hash": "1345b3c556f1a268a81c3a57825d096d9bbce9740217339aa6d79223d9daad4d",
|
||||
"hash": "0814125a19aff5c608b9357926cd4a4c038334e31b0d07a70deefaf2c77959b9",
|
||||
"rules": [
|
||||
"no-cond-assign"
|
||||
]
|
||||
|
|