# Conflicts:
#	apps.json
pull/218/head
msdeibel 2020-03-24 17:13:24 +01:00
commit 94cd15c13f
79 changed files with 1242 additions and 350 deletions

View File

@ -120,7 +120,7 @@ Apps are listed in the Bangle.js menu, accessible from a clock app via the middl
#### `app-icon.js`
The icon image and short description is used in the menu entry as selection possibility.
The icon image and short description is used in Bangle.js's launcher.
Use the Espruino [image converter](https://www.espruino.com/Image+Converter) and upload your `app.png` file.
@ -136,11 +136,14 @@ Follow this steps to create a readable icon as image string.
Replace this line with the image converter output:
```
require("heatshrink").decompress(atob("mEwwJC/AH4A/AH4AgA=="));
require("heatshrink").decompress(atob("mEwwJC/AH4A/AH4AgA=="))
```
Keep in mind to use this converter for creating images you like to draw with `g.drawImage()` with your app.
You can also use this converter for creating images you like to draw with `g.drawImage()` with your app.
Apps that need widgets can call `Bangle.loadWidgets()` **once** at startup to load
them, and then `Bangle.drawWidgets()` to draw them onto the screen whenever the app
has call to completely clear the screen. Widgets themselves will update as and when needed.
### Widget Example
@ -149,6 +152,23 @@ The widget example is available in [`apps/_example_widget`](apps/_example_widget
* `add_to_apps.json` - insert into `apps.json`, describes the widget to bootloader and loader
* `widget.js` - widget code
Widgets are just small bits of code that run whenever an app that supports them
calls `Bangle.loadWidgets()`. If they want to display something in the 24px high
widget bars at the top and bottom of the screen they can add themselves to
the global `WIDGETS` array with:
```
WIDGETS["mywidget"]={
area:"tl", // tl (top left), tr (top right), bl (bottom left), br (bottom right)
width: 24, // how wide is the widget? You can change this and call Bangle.drawWidgets() to re-layout
draw:draw // called to draw the widget
};
```
When the widget is to be drawn, `x` and `y` values are set up in `WIDGETS["mywidget"]`
and `draw` can then use `this.x` and `this.y` to figure out where it needs to draw to.
### `app.info` format
This is the file that's **auto-generated** and loaded onto Bangle.js by the App Loader,
@ -306,6 +326,11 @@ See [apps/gpsrec/interface.html](the GPS Recorder) for a full example.
- 'Welcome' apps define a file called `welcome.js` which the booloader picks up. This then chain-loads the welcome app itself.
- 'Alarm' apps define a file called `alarm.js` which handles the actual alarm window.
- Locale is handled by `require("locale")`. An app may create a `locale` file in Storage which is
a module that overwrites Bangle.js's default locale.
### Graphic areas

105
apps.json
View File

@ -2,7 +2,7 @@
{ "id": "boot",
"name": "Bootloader",
"icon": "bootloader.png",
"version":"0.07",
"version":"0.10",
"description": "This is needed by Bangle.js to automatically load the clock, menu, widgets and settings",
"tags": "tool,system",
"type":"bootloader",
@ -28,7 +28,7 @@
{ "id": "about",
"name": "About",
"icon": "app.png",
"version":"0.01",
"version":"0.04",
"description": "Bangle.js About page - showing software version, stats, and a collaborative mural from the Bangle.js KickStarter backers",
"tags": "tool,system",
"allow_emulator":true,
@ -37,10 +37,23 @@
{"name":"about.img","url":"app-icon.js","evaluate":true}
]
},
{ "id": "locale",
"name": "Languages",
"icon": "locale.png",
"version":"0.01",
"description": "Translations for different countries",
"tags": "tool,system,locale,translate",
"type": "locale",
"custom":"locale.html",
"storage": [
{"name":"locale"}
],
"sortorder" : -10
},
{ "id": "welcome",
"name": "Welcome",
"icon": "app.png",
"version":"0.03",
"version":"0.04",
"description": "Appears at first boot and explains how to use Bangle.js",
"tags": "start,welcome",
"allow_emulator":true,
@ -53,7 +66,7 @@
{ "id": "gbridge",
"name": "Gadgetbridge",
"icon": "app.png",
"version":"0.03",
"version":"0.04",
"description": "The default notification handler for Gadgetbridge notifications from Android",
"tags": "tool,system,android,widget",
"storage": [
@ -79,7 +92,7 @@
{ "id": "setting",
"name": "Settings",
"icon": "settings.png",
"version":"0.05",
"version":"0.06",
"description": "A menu for setting up Bangle.js",
"tags": "tool,system",
"storage": [
@ -93,7 +106,7 @@
"name": "Default Alarm",
"shortName":"Alarms",
"icon": "app.png",
"version":"0.02",
"version":"0.04",
"description": "Set and respond to alarms",
"tags": "tool,alarm,widget",
"storage": [
@ -130,16 +143,17 @@
{"name":"aclock.img","url":"clock-analog-icon.js","evaluate":true}
]
},
{ "id": "clck3x2",
"name": "3x2 Pixel Clock",
"icon": "clock3x2.png",
"version":"0.03",
"description": "This is a simple clock using minimalistic 3x2 pixel numerical digits",
{ "id": "clock2x3",
"name": "2x3 Pixel Clock",
"icon": "clock2x3.png",
"version":"0.04",
"description": "This is a simple clock using minimalist 2x3 pixel numerical digits",
"tags": "clock",
"type": "clock",
"allow_emulator":true,
"storage": [
{"name":"clck3x2.app.js","url":"clock3x2.js"},
{"name":"clck3x2.img","url":"clock3x2-icon.js","evaluate":true}
{"name":"clock2x3.app.js","url":"clock2x3-app.js"},
{"name":"clock2x3.img","url":"clock2x3-icon.js","evaluate":true}
]
},
{ "id": "trex",
@ -236,7 +250,7 @@
{ "id": "gpsrec",
"name": "GPS Recorder",
"icon": "app.png",
"version":"0.04",
"version":"0.06",
"interface": "interface.html",
"description": "Application that allows you to record a GPS track. Can run in background",
"tags": "tool,outdoors,gps,widget",
@ -247,6 +261,20 @@
{"name":"gpsrec.wid.js","url":"widget.js"}
]
},
{ "id": "heart",
"name": "Heart Rate Recorder",
"icon": "app.png",
"version":"0.01",
"interface": "interface.html",
"description": "Application that allows you to record your heart rate. Can run in background",
"tags": "tool,health,widget",
"storage": [
{"name":"heart.app.js","url":"app.js"},
{"name":"heart.json","url":"app-settings.json","evaluate":true},
{"name":"heart.img","url":"app-icon.js","evaluate":true},
{"name":"heart.wid.js","url":"widget.js"}
]
},
{ "id": "slevel",
"name": "Spirit Level",
"icon": "spiritlevel.png",
@ -272,7 +300,7 @@
{ "id": "widbat",
"name": "Battery Level Widget",
"icon": "widget.png",
"version":"0.02",
"version":"0.04",
"description": "Show the current battery level and charging status in the top right of the clock",
"tags": "widget,battery",
"type":"widget",
@ -283,7 +311,7 @@
{ "id": "widbt",
"name": "Bluetooth Widget",
"icon": "widget.png",
"version":"0.01",
"version":"0.03",
"description": "Show the current Bluetooth connection status in the top right of the clock",
"tags": "widget,bluetooth",
"type":"widget",
@ -305,7 +333,7 @@
{ "id": "widhrm",
"name": "Simple Heart Rate widget",
"icon": "widget.png",
"version":"0.01",
"version":"0.03",
"description": "When the screen is on, the widget turns on the heart rate monitor and displays the current heart rate (or last known in grey). For this to work well you'll need at least a 15 second LCD Timeout.",
"tags": "health,widget",
"type": "widget",
@ -316,7 +344,7 @@
{ "id": "stetho",
"name": "Stethoscope",
"icon": "stetho.png",
"version":"0.0198",
"version":"0.01",
"description": "Hear your heart rate",
"tags": "health",
"storage": [
@ -459,7 +487,7 @@
{ "id": "widnceu",
"name": "NCEU Logo Widget",
"icon": "widget.png",
"version":"0.01",
"version":"0.02",
"description": "Show the NodeConf EU logo in the top left",
"tags": "widget",
"type":"widget",
@ -467,9 +495,6 @@
{"name":"widnceu.wid.js","url":"widget.js"}
]
},
{ "id": "sclock",
"name": "Simple Clock",
"icon": "clock-simple.png",
@ -638,7 +663,7 @@
"id": "gpsinfo",
"name": "GPS Info",
"icon": "gps-info.png",
"version":"0.01",
"version":"0.02",
"description": "An application that displays information about altitude, lat/lon, satellites and time",
"tags": "gps",
"type": "app",
@ -691,7 +716,7 @@
{ "id": "widclk",
"name": "Digital clock widget",
"icon": "widget.png",
"version":"0.01",
"version":"0.03",
"description": "A simple digital clock widget",
"tags": "widget,clock",
"type":"widget",
@ -702,7 +727,7 @@
{ "id": "widpedom",
"name": "Pedometer widget",
"icon": "widget.png",
"version":"0.06",
"version":"0.08",
"description": "Daily pedometer widget",
"tags": "widget",
"type":"widget",
@ -753,6 +778,7 @@
{ "id": "flagrse",
"name": "Espruino Flag Raiser",
"icon": "app.png",
"version":"0.01",
"description": "App to send a command to another Espruino to cause it to raise a flag",
"tags": "",
"storage": [
@ -774,6 +800,19 @@
{"name":"pipboy.img","url":"app-icon.js","evaluate":true}
]
},
{ "id": "torch",
"name": "Torch",
"shortName":"Torch",
"icon": "app.png",
"version":"0.01",
"description": "Turns screen white to help you see in the dark. Select from the launcher or press BTN3 four times in quick succession to start when in normal clock mode",
"tags": "tool,torch",
"storage": [
{"name":"torch.app.js","url":"app.js"},
{"name":"torch.wid.js","url":"widget.js"},
{"name":"torch.img","url":"app-icon.js","evaluate":true}
]
},
{ "id": "wohrm",
"name": "Workout Heart Rate Monitor",
"icon": "wohrm.png",
@ -783,9 +822,19 @@
"type": "app",
"allow_emulator":true,
"storage": [
{"name":"+wohrm","url":"wohrm.json"},
{"name":"-wohrm","url":"wohrm.js"},
{"name":"*wohrm","url":"wohrm-icon.js","evaluate":true}
{"name":"wohrm.app.js","url":"wohrm.js"},
{"name":"wohrm.img","url":"wohrm-icon.js","evaluate":true}
]
},
{ "id": "widid",
"name": "Bluetooth ID Widget",
"icon": "widget.png",
"version":"0.02",
"description": "Display the last two tuple of your Bangle.js MAC address in the widget section. This is useful for figuring out which Bangle.js to connect to if you have more than one Bangle.js!",
"tags": "widget,address,mac",
"type":"widget",
"storage": [
{"name":"widid.wid.js","url":"widget.js"}
]
}
]

View File

@ -7,8 +7,7 @@
"description": "A detailed description of my great app",
"tags": "",
"storage": [
{"name":"+7chname","url":"app.json"},
{"name":"-7chname","url":"app.js"},
{"name":"*7chname","url":"app-icon.js","evaluate":true}
{"name":"7chname.app.js","url":"app.js"},
{"name":"7chname.img","url":"app-icon.js","evaluate":true}
]
}

View File

@ -8,7 +8,6 @@
"tags": "widget",
"type": "widget",
"storage": [
{"name":"+7chname","url":"widget.json"},
{"name":"=7chname","url":"widget.js"}
{"name":"7chname.wid.js","url":"widget.js"}
]
}

View File

@ -1,17 +1,16 @@
/* run widgets in their own function scope so they don't interfere with
currently-running apps */
(() => {
// add the width
var xpos = WIDGETPOS.tr-24;/*<the widget width>*/;
WIDGETPOS.tr-= 28;/* the widget width plus some extra pixel to keep distance to others */;
// draw your widget at xpos
function draw() {
g.reset(); // reset the graphics context to defaults (color/font/etc)
// add your code
g.drawString("X", this.x, this.y);
}
// add your widget
WIDGETS["mywidget"]={draw:draw};
WIDGETS["mywidget"]={
area:"tl", // tl (top left), tr (top right), bl (bottom left), br (bottom right)
width: 28, // how wide is the widget? You can change this and call Bangle.drawWidgets() to re-layout
draw:draw // called to draw the widget
};
})()

View File

@ -1 +1,4 @@
0.01: New App!
0.02: Update version checker for new filename type
0.03: Actual pixels as of 5 Mar 2020
0.04: Actual pixels as of 9 Mar 2020

File diff suppressed because one or more lines are too long

View File

@ -1,2 +1,4 @@
0.01: New App!
0.02: Fix issues with alarm scheduling
0.03: More alarm scheduling issues
0.04: Tweaks for variable size widget system

View File

@ -10,7 +10,7 @@ function formatTime(t) {
function getCurrentHr() {
var time = new Date();
return time.getHours()+(time.getMinutes()/60);
return time.getHours()+(time.getMinutes()/60)+(time.getSeconds()/3600);
}
function showAlarm(alarm) {
@ -47,8 +47,8 @@ function showAlarm(alarm) {
// Check for alarms
var day = (new Date()).getDate();
var hr = getCurrentHr();
var alarms = require("Storage").readJSON("alarm.json")||[];
var hr = getCurrentHr()+10000; // get current time - 10s in future to ensure we alarm if we've started the app a tad early
var alarms = require("Storage").readJSON("alarm.json",1)||[];
var active = alarms.filter(a=>a.on&&(a.hr<hr)&&(a.last!=day));
if (active.length) {
// if there's an alarm, show it
@ -56,5 +56,5 @@ if (active.length) {
showAlarm(active[0]);
} else {
// otherwise just go back to default app
load();
setTimeout(load, 100);
}

View File

@ -1,7 +1,7 @@
Bangle.loadWidgets();
Bangle.drawWidgets();
var alarms = require("Storage").readJSON("alarm.json")||[];
var alarms = require("Storage").readJSON("alarm.json",1)||[];
/*alarms = [
{ on : true,
hr : 6.5, // hours + minutes/60
@ -19,7 +19,7 @@ function formatTime(t) {
function getCurrentHr() {
var time = new Date();
return time.getHours()+(time.getMinutes()/60);
return time.getHours()+(time.getMinutes()/60)+(time.getSeconds()/3600);
}
function showMainMenu() {

View File

@ -1,17 +1,11 @@
(() => {
var alarms = require('Storage').readJSON('alarm.json')||[];
var alarms = require('Storage').readJSON('alarm.json',1)||[];
alarms = alarms.filter(alarm=>alarm.on);
if (!alarms.length) return;
if (!alarms.length) return; // no alarms, no widget!
delete alarms;
// add the width
var xpos = WIDGETPOS.tl;
WIDGETPOS.tl += 24;/* the widget width plus some extra pixel to keep distance to others */;
// draw your widget at xpos
// add the widget
WIDGETS["alarm"]={draw:function() {
WIDGETS["alarm"]={area:"tl",width:24,draw:function() {
g.setColor(-1);
g.drawImage(atob("GBgBAAAAAAAAABgADhhwDDwwGP8YGf+YMf+MM//MM//MA//AA//AA//AA//AA//AA//AB//gD//wD//wAAAAADwAABgAAAAAAAAA"),xpos,0);
g.drawImage(atob("GBgBAAAAAAAAABgADhhwDDwwGP8YGf+YMf+MM//MM//MA//AA//AA//AA//AA//AA//AB//gD//wD//wAAAAADwAABgAAAAAAAAA"),this.x,this.y);
}};
})()

View File

@ -4,3 +4,7 @@
0.05: Add Welcome screen on boot
0.06: Disable GPS time log messages, add (default=1) setting to hide log messages
0.07: Fix issues with alarm scheduling
0.08: Fix issues if BLE=off, 'Make Connectable' is chosen, and the loader resets Bangle.js (fix #108)
0.09: Only check GPS for time after a fresh boot
0.10: Stop users calling save() (fix #125)
If Debug info is set to 'show' don't move to Terminal if connected!

View File

@ -1,7 +1,7 @@
// This ALWAYS runs at boot
E.setFlags({pretokenise:1});
// Load settings...
var s = require('Storage').readJSON('setting.json')||{};
var s = require('Storage').readJSON('setting.json',1)||{};
if (s.ble!==false) {
if (s.HID) { // Human interface device
Bangle.HID = E.toUint8Array(atob("BQEJBqEBhQIFBxngKecVACUBdQGVCIEClQF1CIEBlQV1AQUIGQEpBZEClQF1A5EBlQZ1CBUAJXMFBxkAKXOBAAkFFQAm/wB1CJUCsQLABQwJAaEBhQEVACUBdQGVAQm1gQIJtoECCbeBAgm4gQIJzYECCeKBAgnpgQIJ6oECwA=="));
@ -12,11 +12,12 @@ if (s.blerepl===false) { // If not programmable, force terminal off Bluetooth
if (s.log) Terminal.setConsole(true); // if showing debug, force REPL onto terminal
else E.setConsole(null,{force:true}); // on new (2v05+) firmware we have E.setConsole which allows a 'null' console
} else {
if (s.log) Terminal.setConsole(); // if showing debug, put REPL on terminal (until connection)
if (s.log && !NRF.getSecurityStatus().connected) Terminal.setConsole(); // if showing debug, put REPL on terminal (until connection)
else Bluetooth.setConsole(true); // else if no debug, force REPL to Bluetooth
}
// we just reset, so BLE should be on
if (s.ble===false) NRF.sleep();
// we just reset, so BLE should be on.
// Don't disconnect if something is already connected to us
if (s.ble===false && !NRF.getSecurityStatus().connected) NRF.sleep();
// Set time, vibrate, beep, etc
if (!s.vibrate) Bangle.buzz=Promise.resolve;
if (!s.beep) Bangle.beep=Promise.resolve;
@ -24,45 +25,27 @@ Bangle.setLCDTimeout(s.timeout);
if (!s.timeout) Bangle.setLCDPower(1);
E.setTimeZone(s.timezone);
delete s;
// stop users doing bad things!
global.save = function() { throw new Error("You can't use save() on Bangle.js without overwriting the bootloader!"); }
// check for alarms
function checkAlarm() {
var alarms = require('Storage').readJSON('alarm.json')||[];
var time = new Date();
var active = alarms.filter(a=>a.on&&(a.last!=time.getDate()));
if (active.length) {
active = active.sort((a,b)=>a.hr-b.hr);
var hr = time.getHours()+(time.getMinutes()/60)+(time.getSeconds()/3600);
if (!require('Storage').read("alarm.js")) {
console.log("No alarm app!");
require('Storage').write('alarm.json',"[]")
} else {
var t = 3600000*(active[0].hr-hr);
if (t<1000) t=1000;
/* execute alarm at the correct time. We avoid execing immediately
since this code will get called AGAIN when alarm.js is loaded. alarm.js
will then clearInterval() to get rid of this call so it can proceed
normally. */
setTimeout(function() {
load("alarm.js");
},t);
}
var alarms = require('Storage').readJSON('alarm.json',1)||[];
var time = new Date();
var active = alarms.filter(a=>a.on&&(a.last!=time.getDate()));
if (active.length) {
active = active.sort((a,b)=>a.hr-b.hr);
var hr = time.getHours()+(time.getMinutes()/60)+(time.getSeconds()/3600);
if (!require('Storage').read("alarm.js")) {
console.log("No alarm app!");
require('Storage').write('alarm.json',"[]")
} else {
var t = 3600000*(active[0].hr-hr);
if (t<1000) t=1000;
/* execute alarm at the correct time. We avoid execing immediately
since this code will get called AGAIN when alarm.js is loaded. alarm.js
will then clearInterval() to get rid of this call so it can proceed
normally. */
setTimeout(function() {
load("alarm.js");
},t);
}
}
// check to see if our clock is wrong - if it is use GPS time
if ((new Date()).getFullYear()==1970) {
//console.log("Searching for GPS time");
Bangle.on('GPS',function cb(g) {
Bangle.setGPSPower(0);
Bangle.removeListener("GPS",cb);
if (!g.time || (g.time.getFullYear()<2000) ||
(g.time.getFullYear()==2250)) {
//console.log("GPS receiver's time not set");
return;
}
setTime(g.time.getTime()/1000);
//console.log("GPS time",g.time.toString());
checkAlarm();
});
Bangle.setGPSPower(1);
} else checkAlarm();
delete checkAlarm;

View File

@ -1,6 +1,5 @@
// This runs after a 'fresh' boot
var settings={};
try { settings = require("Storage").readJSON('setting.json'); } catch (e) {}
var settings=require("Storage").readJSON('setting.json',1)||{};
if (!settings.welcomed && require("Storage").read("welcome.js")!==undefined) {
setTimeout(()=>load("welcome.js"));
} else {
@ -8,16 +7,32 @@ if (!settings.welcomed && require("Storage").read("welcome.js")!==undefined) {
var clockApp = settings.clock;
if (clockApp) clockApp = require("Storage").read(clockApp)
if (!clockApp) {
var clockApps = require("Storage").list(/\.info$/).map(app=>{
try { return require("Storage").readJSON(app); }
catch (e) {}
}).filter(app=>app.type=="clock").sort((a, b) => a.sortorder - b.sortorder);
var clockApps = require("Storage").list(/\.info$/).map(app=>require("Storage").readJSON(app,1)||{}).filter(app=>app.type=="clock").sort((a, b) => a.sortorder - b.sortorder);
if (clockApps && clockApps.length > 0)
clockApp = require("Storage").read(clockApps[0].src);
delete clockApps;
}
if (clockApp) eval(clockApp);
else E.showMessage("No Clock Found");
delete clockApp;
if (!clockApp) clockApp='E.showMessage("No Clock Found")';
delete settings;
// check to see if our clock is wrong - if it is use GPS time
if ((new Date()).getFullYear()==1970) {
E.showMessage("Searching for\nGPS time");
Bangle.on('GPS',function cb(g) {
Bangle.setGPSPower(0);
Bangle.removeListener("GPS",cb);
if (!g.time || (g.time.getFullYear()<2000) ||
(g.time.getFullYear()==2250)) {
// GPS receiver's time not set - just boot clock anyway
eval(clockApp);delete clockApp;
return;
}
// We have a GPS time. Set time and reboot (to load alarms properly)
setTime(g.time.getTime()/1000);
load();
});
Bangle.setGPSPower(1);
} else {
eval(clockApp);
delete clockApp;
}
}
delete settings;

View File

@ -1,2 +1,3 @@
0.02: Modified for use with new bootloader and firmware
0.03: Added 'reset' so we don't get the font color from widgets
0.04: Changed name from clck3x2 to clock2x3

View File

@ -76,8 +76,7 @@ function drawTime() {
}
let t = d.getSeconds()*1000 + d.getMilliseconds();
let delta = (60000 - t) % 60000; // time till next minute
idTimeout = setTimeout(drawTime, delta);
idTimeout = setTimeout(drawTime, 60000 - t); // time till next minute
}
// special function to handle display switch on

View File

Before

Width:  |  Height:  |  Size: 145 B

After

Width:  |  Height:  |  Size: 145 B

View File

@ -69,7 +69,7 @@ function showApps() {
var list = storage.list(/\.info$/).filter((a)=> {
return a !== 'setting.info';
}).sort().map((app) => {
var ret = storage.readJSON(app);
var ret = storage.readJSON(app,1)||{};
ret[''] = app;
return ret;
});

View File

@ -1,3 +1,4 @@
0.01: Initial version
0.02: Increase contrast (darker notification background, white text)
0.03: Gadgetbridge widget now shows connection state
0.04: Tweaks for variable size widget system

View File

@ -109,19 +109,17 @@
function draw() {
g.setColor(-1);
if (NRF.getSecurityStatus().connected)
g.drawImage(require("heatshrink").decompress(atob("i0WwgHExAABCIwJCBYwJEBYkIBQ2ACgvzCwoECx/z/AKDD4WD+YLBEIYKCx//+cvnAKCBwU/mc4/8/HYv//Ev+Y4EEAePn43DBQkzn4rCEIoABBIwKHO4cjmczK42I6mqlqEEBQeIBQaDED4IgDUhi6KaBbmIA==")),xpos+1,1);
g.drawImage(require("heatshrink").decompress(atob("i0WwgHExAABCIwJCBYwJEBYkIBQ2ACgvzCwoECx/z/AKDD4WD+YLBEIYKCx//+cvnAKCBwU/mc4/8/HYv//Ev+Y4EEAePn43DBQkzn4rCEIoABBIwKHO4cjmczK42I6mqlqEEBQeIBQaDED4IgDUhi6KaBbmIA==")),this.x+1,this.y+1);
else
g.drawImage(require("heatshrink").decompress(atob("i0WwQFC1WgAgYFDAgIFClQFCwEK1W/AoIPB1f+CAMq1f7/WqwQPB/fq1Gq1/+/4dC/2/CAIaB/YbBAAO///qAoX/B4QbBDQQ7BDQQrBAAWoIIIACIIIVC0ECB4cACAZiBAoRtCAoIDBA")),xpos+1,1);
g.drawImage(require("heatshrink").decompress(atob("i0WwQFC1WgAgYFDAgIFClQFCwEK1W/AoIPB1f+CAMq1f7/WqwQPB/fq1Gq1/+/4dC/2/CAIaB/YbBAAO///qAoX/B4QbBDQQ7BDQQrBAAWoIIIACIIIVC0ECB4cACAZiBAoRtCAoIDBA")),this.x+1,this.y+1);
}
function changed() {
draw();
WIDGETS["gbridgew"].draw();
g.flip();// turns screen on
}
NRF.on('connected',changed);
NRF.on('disconnected',changed);
var xpos = WIDGETPOS.tl;
WIDGETPOS.tl+=24;
WIDGETS["gbridgew"]={draw:draw};
WIDGETS["gbridgew"]={area:"tl",width:24,draw:draw};
})();

1
apps/gpsinfo/ChangeLog Normal file
View File

@ -0,0 +1 @@
0.02: Ensure screen doesn't display garbage at startup

View File

@ -2,6 +2,7 @@ var img = require("heatshrink").decompress(atob("mEwghC/AH4AKg9wC6t3u4uVC6wWBI6t
Bangle.setGPSPower(1);
Bangle.setLCDMode("doublebuffered");
E.showMessage("Loading..."); // avoid showing rubbish on screen
var lastFix = {
fix: 0,

View File

@ -2,3 +2,5 @@
0.02: Fix GPS time logging
0.03: Fix GPS time display in gpsrec app
0.04: Properly Fix GPS time display in gpsrec app
0.05: Tweaks for variable size widget system
0.06: Ensure widget update itself (fix #118) and change to using icons

View File

@ -1,7 +1,7 @@
Bangle.loadWidgets();
Bangle.drawWidgets();
var settings = require("Storage").readJSON("gpsrec.json")||{};
var settings = require("Storage").readJSON("gpsrec.json",1)||{};
function getFN(n) {
return ".gpsrc"+n.toString(36);

View File

@ -176,7 +176,7 @@ function getTrackList() {
for (var i=0;i<buttons.length;i++) {
buttons[i].addEventListener("click",event => {
var button = event.currentTarget;
var trackid = button.getAttribute("trackid");
var trackid = parseInt(button.getAttribute("trackid"));
var task = button.getAttribute("task");
if (task=="delete") {
showModal("Deleting Track...");

View File

@ -1,44 +1,29 @@
(() => {
// add the width
var xpos = WIDGETPOS.tl;
WIDGETPOS.tl += 24;/* the widget width plus some extra pixel to keep distance to others */;
var settings = {};
var hasFix = false;
var fixToggle = false; // toggles once for each reading
var gpsTrack; // file for GPS track
var periodCtr = 0;
// draw your widget at xpos
// draw your widget
function draw() {
if (!settings.recording) return;
g.reset();
g.setFont("4x6");
g.setFontAlign(0,0);
g.clearRect(xpos,0,xpos+23,23);
if (!settings.recording) {
g.setColor("#606060");
g.drawImage(atob("GBgCAAAAAAAAAAQAAAAAAD8AAAAAAP/AAAAAAP/wAAAAAH/8C9AAAB/8L/QAAAfwv/wAAAHS//wAAAAL//gAAAAf/+AAAAAf/4AAAAL//gAAAAD/+DwAAAB/Uf8AAAAfA//AAAACAf/wAAAAAH/0AAAAAB/wAAAAAAfAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"),this.x,this.y);
if (hasFix) {
g.setColor("#FF0000");
g.drawImage(fixToggle ? atob("CgoCAAAAA0AAOAAD5AAPwAAAAAAAAAAAAAAAAA==") : atob("CgoCAAABw0AcOAHj5A8PwHwAAvgAB/wABUAAAA=="),this.x,this.y+14);
} else {
g.setColor("#ff0000");
if (hasFix) {
if (fixToggle) {
g.fillCircle(xpos+11,11,9);
g.setColor("#000000");
} else
g.drawCircle(xpos+11,11,9);
} else {
g.setColor(fixToggle ? "#ff0000" : "#7f0000");
g.drawString("NO",xpos+12,5);
g.drawString("FIX",xpos+12,19);
}
g.setColor("#0000FF");
if (fixToggle)
g.setFont("6x8").drawString("?",this.x,this.y+14);
}
g.drawString("GPS",xpos+12,12);
g.setColor(-1); // change color back to be nice to other apps
}
function onGPS(fix) {
hasFix = fix.fix;
fixToggle = !fixToggle;
draw();
WIDGETS["gpsrec"].draw();
if (hasFix) {
periodCtr--;
if (periodCtr<=0) {
@ -53,25 +38,30 @@
}
}
// Called by the GPS app to reload settings and decide what's
// Called by the GPS app to reload settings and decide what to do
function reload() {
settings = require("Storage").readJSON("gpsrec.json")||{};
settings = require("Storage").readJSON("gpsrec.json",1)||{};
settings.period = settings.period||1;
settings.file |= 0;
Bangle.removeListener('GPS',onGPS);
if (settings.recording) {
WIDGETS["gpsrec"].width = 24;
Bangle.on('GPS',onGPS);
Bangle.setGPSPower(1);
var n = settings.file.toString(36);
gpsTrack = require("Storage").open(".gpsrc"+n,"a");
} else {
WIDGETS["gpsrec"].width = 0;
Bangle.setGPSPower(0);
gpsTrack = undefined;
}
draw();
}
reload();
// add the widget
WIDGETS["gpsrec"]={draw:draw,reload:reload};
WIDGETS["gpsrec"]={area:"tl",width:24,draw:draw,reload:function() {
reload();
Bangle.drawWidgets(); // relayout all widgets
}};
// load settings, set correct widget width
reload();
})()

2
apps/heart/ChangeLog Normal file
View File

@ -0,0 +1,2 @@
0.01: New App!

1
apps/heart/app-icon.js Normal file
View File

@ -0,0 +1 @@
require("heatshrink").decompress(atob("mEwwhC/AH4AWzIAByAHDhIICCpINDDAgIIFpAADBBQuKE4QIIFxgAKC7g9HABSbIBQQXWGxgXEKQxOMC5AhBC66WMC5AuBJ5h3ICoI3LeAwKBBAICBD4TmHC48ACgQCCfxC/HAgYXDL44vFA4YRDAoiOIHAgXFYRAXFBwwIIOw4OGIxKmIC5ylHGAoXIXpBIGLxxIIIx6IJFxwwNCxQwLFxYwLCxgwJFxowJCxwwHFx4wHCyAwFFyIwFCyQYDCygA/AH4AFA"))

View File

@ -0,0 +1,4 @@
{
"isRecording":false,
"fileNbr":0
}

100
apps/heart/app.js Normal file
View File

@ -0,0 +1,100 @@
Bangle.loadWidgets();
Bangle.drawWidgets();
var settings = require("Storage").readJSON("heart.json",1)||{};
function getFileNbr(n) {
return ".heart"+n.toString(36);
}
function updateSettings() {
require("Storage").write("heart.json", settings);
if (WIDGETS["heart"])
WIDGETS["heart"].reload();
}
function showMainMenu() {
const mainMenu = {
'': { 'title': 'Heart Recorder' },
'RECORD': {
value: !!settings.isRecording,
format: v=>v?"On":"Off",
onchange: v => {
settings.isRecording = v;
updateSettings();
}
},
'File Number': {
value: settings.fileNbr|0,
min: 0,
max: 35,
step: 1,
onchange: v => {
settings.isRecording = false;
settings.fileNbr = v;
updateSettings();
}
},
'View Records': viewRecords,
'< Back': ()=>{load();}
};
return E.showMenu(mainMenu);
}
function viewRecords() {
const menu = {
'': { 'title': 'Heart Records' }
};
var found = false;
for (var n=0;n<36;n++) {
var f = require("Storage").open(getFileNbr(n),"r");
if (f.readLine()!==undefined) {
menu["Record "+n] = viewRecord.bind(null,n);
found = true;
}
}
if (!found)
menu["No Records Found"] = function(){};
menu['< Back'] = showMainMenu;
return E.showMenu(menu);
}
function viewRecord(n) {
const menu = {
'': { 'title': 'Heart Record '+n }
};
var heartCount = 0;
var heartTime;
var f = require("Storage").open(getFileNbr(n),"r");
var l = f.readLine();
if (l!==undefined) {
var c = l.split(",");
heartTime = new Date(c[0]*1000);
}
while (l!==undefined) {
heartCount++;
// TODO: min/max/average of heart rate?
l = f.readLine();
}
if (heartTime)
menu[" "+heartTime.toString().substr(4,17)] = function(){};
menu[heartCount+" records"] = function(){};
// TODO: option to draw it? Just scan through, project using min/max
menu['Erase'] = function() {
E.showPrompt("Delete Record?").then(function(v) {
if (v) {
settings.isRecording = false;
updateSettings();
var f = require("Storage").open(getFileNbr(n),"r");
f.erase();
viewRecords();
} else
viewRecord(n);
});
};
menu['< Back'] = viewRecords;
print(menu);
return E.showMenu(menu);
}
showMainMenu();

BIN
apps/heart/app.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 883 B

149
apps/heart/interface.html Normal file
View File

@ -0,0 +1,149 @@
<html>
<head>
<link rel="stylesheet" href="../../css/spectre.min.css">
</head>
<body>
<div id="records"></div>
<div class="modal active" id="status-modal">
<div class="modal-overlay"></div>
<div class="modal-container">
<div class="modal-header">
<div class="modal-name h5">Please wait</div>
</div>
<div class="modal-body">
<div class="content">
Loading...
</div>
</div>
</div>
</div>
<script src="../../lib/interface.js"></script>
<script>
var domRecords = document.getElementById("records");
var domModal = document.getElementById("status-modal");
function showModal(name) {
domModal.querySelector(".content").innerHTML = name;
domModal.classList.add("active");
}
function hideModal(name) {
domModal.classList.remove("active");
}
function saveRecord(record,name) {
var csv = `${record.map(rec=>[rec.time, rec.bpm, rec.confidence].join(",")).join("\n")}`;
var a = document.createElement("a"),
file = new Blob([csv], {type: "Comma-separated value file"});
var url = URL.createObjectURL(file);
a.href = url;
a.download = name+".csv";
document.body.appendChild(a);
a.click();
setTimeout(function() {
document.body.removeChild(a);
window.URL.revokeObjectURL(url);
}, 0);
}
function recordLineToObject(l, hasRecordNbr) {
var t = l.trim().split(",");
var n = hasRecordNbr?1:0;
var o = {
time: parseInt(t[n+0]),
bpm: parseFloat(t[n+1]),
confidence: parseFloat(t[n+2]),
};
if (hasRecordNbr)
o.number = t[0];
return o;
}
function downloadRecord(recordNbr, callback) {
showModal("Downloading heart rate record...");
Puck.write(`\x10(function() {
var f = require("Storage").open(".heart${recordNbr.toString(36)}","r");
var l = f.readLine();
while (l!==undefined) { Bluetooth.print(l); l = f.readLine(); }
})()\n`,recordList=>{
hideModal();
var record = recordList.trim().split("\n").map(l=>recordLineToObject(l,false));
callback(record);
});
}
function getRecordList() {
showModal("Loading heart rate records...");
domRecords.innerHTML = "";
Puck.write(`\x10(function() {
for (var n=0;n<36;n++) {
var f = require("Storage").open(".heart"+n.toString(36),"r");
var l = f.readLine();
if (l!==undefined)
Bluetooth.println(n+","+l.trim());
}
})()\n`,recordList=>{
var recordLines = recordList.trim().split("\n");
var html = `<div class="container">
<div class="columns">\n`;
recordLines.forEach(l => {
var record = recordLineToObject(l, true /*has record number*/);
html += `
<div class="column col-12">
<div class="card-header">
<div class="card-title h5">Heart Rate Record ${record.number}</div>
<div class="card-subtitle text-gray">${(new Date(record.time*1000)).toString().substr(0,24)}</div>
</div>
<div class="card-body"></div>
<div class="card-footer">
<button class="btn btn-primary" recordNbr="${record.number}" task="download">Download</button>
<button class="btn btn-default" recordNbr="${record.number}" task="delete">Delete</button>
</div>
</div>
`;
});
if (recordLines.length==0) {
html += `
<div class="column col-12">
<div class="card-header">
<div class="card-title h5">No record</div>
<div class="card-subtitle text-gray">No heart rate record found</div>
</div>
</div>
`;
}
html += `
</div>
</div>`;
domRecords.innerHTML = html;
hideModal();
var buttons = domRecords.querySelectorAll("button");
for (var i=0;i<buttons.length;i++) {
buttons[i].addEventListener("click",event => {
var button = event.currentTarget;
var recordNbr = parseInt(button.getAttribute("recordNbr"));
var task = button.getAttribute("task");
if (task=="delete") {
showModal("Deleting record...");
Puck.write(`\x10require("Storage").open(".heart${recordNbr.toString(36)}","r").erase()\n`,()=>{
hideModal();
getRecordList();
});
}
if (task=="download") {
downloadRecord(recordNbr, record => saveRecord(record, `HeartRateRecord${recordNbr}`));
}
});
}
})
}
function onInit() {
getRecordList();
}
</script>
</body>
</html>

50
apps/heart/widget.js Normal file
View File

@ -0,0 +1,50 @@
(() => {
var settings = {};
var hrmToggle = true; // toggles once for each reading
var recFile; // file for heart rate recording
// draw your widget
function draw() {
if (!settings.isRecording) return;
g.reset();
g.setFontAlign(0,0);
g.clearRect(this.x,this.y,this.x+23,this.y+23);
g.setColor(hrmToggle?"#ff0000":"#ff8000");
g.fillCircle(this.x+6,this.y+6,4); // draw heart left circle
g.fillCircle(this.x+16,this.y+6,4); // draw heart right circle
g.fillPoly([this.x+2,this.y+8,this.x+20,this.y+8,this.x+11,this.y+18]); // draw heart bottom triangle
g.setColor(-1); // change color back to be nice to other apps
}
function onHRM(hrm) {
hrmToggle = !hrmToggle;
WIDGETS["heart"].draw();
if (recFile) recFile.write([getTime().toFixed(0),hrm.bpm,hrm.confidence].join(",")+"\n");
}
// Called by the heart app to reload settings and decide what's
function reload() {
settings = require("Storage").readJSON("heart.json",1)||{};
settings.fileNbr |= 0;
Bangle.removeListener('HRM',onHRM);
if (settings.isRecording) {
WIDGETS["heart"].width = 24;
Bangle.on('HRM',onHRM);
Bangle.setHRMPower(1);
var n = settings.fileNbr.toString(36);
recFile = require("Storage").open(".heart"+n,"a");
} else {
WIDGETS["heart"].width = 0;
Bangle.setHRMPower(0);
recFile = undefined;
}
}
// add the widget
WIDGETS["heart"]={area:"tl",width:24,draw:draw,reload:function() {
reload();
Bangle.drawWidgets(); // relayout all widgets
}};
// load settings, set correct widget width
reload();
})()

View File

@ -5,7 +5,7 @@ the touchscreen
var storage = require('Storage');
const settings = storage.readJSON('setting.json') || { HID: false };
const settings = storage.readJSON('setting.json',1) || { HID: false };
const KEY = {
A : 4 ,
B : 5 ,

View File

@ -1,6 +1,6 @@
var storage = require('Storage');
const settings = storage.readJSON('setting.json') || { HID: false };
const settings = storage.readJSON('setting.json',1) || { HID: false };
var sendHid, next, prev, toggle, up, down, profile;

View File

@ -1,6 +1,6 @@
var storage = require('Storage');
const settings = storage.readJSON('setting.json') || { HID: false };
const settings = storage.readJSON('setting.json',1) || { HID: false };
var sendHid, next, prev, toggle, up, down, profile;

View File

@ -1,8 +1,5 @@
var s = require("Storage");
var apps = s.list(/\.info$/).map(app=>{
try { return s.readJSON(app); }
catch (e) { return {name:"DEAD: "+app.substr(1)} }
}).filter(app=>app.type=="app" || app.type=="clock" || !app.type);
var apps = s.list(/\.info$/).map(app=>s.readJSON(app,1)||{name:"DEAD: "+app.substr(1)}).filter(app=>app.type=="app" || app.type=="clock" || !app.type);
apps.sort((a,b)=>{
var n=(0|a.sortorder)-(0|b.sortorder);
if (n) return n; // do sortorder first

1
apps/locale/ChangeLog Normal file
View File

@ -0,0 +1 @@
0.01: New App!

88
apps/locale/locale.html Normal file
View File

@ -0,0 +1,88 @@
<html>
<head>
<link rel="stylesheet" href="../../css/spectre.min.css">
</head>
<body>
<p>Please choose a language from the following list:</p>
<div class="form-group">
<select id="languages" class="form-select">
</select>
</div>
<p>Then click <button id="upload" class="btn btn-primary">Upload</button></p>
<script src="../../lib/customize.js"></script>
<script src="locales.js"></script>
<script>
var languageSelector = document.getElementById("languages");
languageSelector.innerHTML = Object.keys(locales).map(l=>`<option value="${l}">${l}</option>`).join("\n");
document.getElementById("upload").addEventListener("click", function() {
var lang = languageSelector.options[languageSelector.selectedIndex].value;
console.log(lang);
locale = locales[lang];
if (!locale) {
alert("Language not found!");
return;
}
var replaceList = {
"%Y": "${d.getFullYear()}",
"%y": "${(d.getFullYear().toString()).substr(-2)}",
"%m": "${('0'+(d.getMonth()+1).toString()).substr(-2)}",
"%-m": "${d.getMonth()+1}",
"%d": "${('0'+d.getDate()).substr(-2)}",
"%-d": "${d.getDate()}",
"%HH": "${('0'+d.getHours()).substr(-2)}",
"%MM": "${('0'+d.getMinutes()).substr(-2)}",
"%SS": "${('0'+d.getSeconds()).substr(-2)}",
"%A": "${locale.day.split(',')[d.getDay()]}",
"%a": "${locale.abday.split(',')[d.getDay()]}",
"%B": "${locale.month.split(',')[d.getMonth()]}",
"%b": "${locale.abmonth.split(',')[d.getMonth()]}",
"%p": "${(d.getHours()<12)?locale.ampm[0].toUpperCase():locale.ampm[1].toUpperCase()}",
"%P": "${(d.getHours()<12)?locale.ampm[0].toLowerCase():locale.ampm[1].toLowerCase()}"
};
var timeN = locales[lang].timePattern[0];
var timeS = locales[lang].timePattern[1];
var dateN = locales[lang].datePattern[0];
var dateS = locales[lang].datePattern[1];
Object.keys(replaceList).forEach(e => {
timeN = timeN.replace(e,replaceList[e]);
timeS = timeS.replace(e,replaceList[e]);
dateN = dateN.replace(e,replaceList[e]);
dateS = dateS.replace(e,replaceList[e]);
});
var app = `
locale = ${JSON.stringify(locales[lang])};
exports = {
lang: locale.lang,
currencySym: String.fromCharCode(locale.currency_symbol),
dow: (d,short) => {day = d.getDay();return (short) ? locale.abday.split(",")[day] : locale.day.split(",")[day];},
month: (d,short) => { month = d.getMonth(); return (short) ? locale.abmonth.split(",")[month] : locale.month.split(",")[month];},
number: n => n.toString().replace(locale.thousands_sep, locale.decimal_point),
currency: n => n.toFixed(2).replace(locale.thousands_sep, locale.decimal_point) + locale.currency_symbol,
distance: n => (n < 1000) ? Math.round(n) + locale.distance[0] : Math.round(n/1000) + locale.distance[1],
speed: s => Math.round(s) +locale.speed,
temp: t => Math.round(t) + locale.temperature,
translate: s => {s=""+s;return locale.trans[s]||locale.trans[s.toLowerCase()]||s},
date: (d,short) => (short) ? \`${dateS}\`: \`${dateN}\`,
time: (d,short) => (short) ? \`${timeS}\`: \`${timeN}\`,
};`;
sendCustomizedApp({
storage:[
{name:"locale", content:app}
]
});
});
</script>
</body>
</html>

BIN
apps/locale/locale.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

357
apps/locale/locales.js Normal file
View File

@ -0,0 +1,357 @@
/*
%Y year four digits
%y last two digits of year (00..99)
%m month (01..12)
%d day of month (e.g, 01)
%a locale's abbreviated weekday name (e.g., Sun)
%A locale's full weekday name (e.g., Sunday)
%b locale's abbreviated month name (e.g., Jan)
%B locale's full month name (e.g., January)
%H hour (00..23)
%M minute (00..59)
%S second (00..60)
%p locale's equivalent of either AM or PM; blank if not known
%P like %p, but lower case
*/
var locales = {
"en_GB": { // this is default
lang: "en_GB",
decimal_point: ".",
thousands_sep: ",",
currency_symbol: "£",
int_curr_symbol: "GBP",
speed: 'mph',
distance: { "0": "mi", "1": "kmi" },
temperature: '°C',
ampm: {0:"am",1:"pm"},
timePattern: { 0: "%HH:%MM:%SS ", 1: "%HH:%MM" },
datePattern: { 0: "%b %d %Y", 1: "%d/%m/%Y" }, // Feb 28 2020" // "01/03/2020"(short)
abmonth: "Jan,Feb,Mar,Apr,May,Jun,Jul,Aug,Sep,Oct,Nov,Dec",
month: "January,February,March,April,May,June,July,August,September,October,November,December",
abday: "Sun,Mon,Tue,Wed,Thu,Fri,Sat",
day: "Sunday,Monday,Tuesday,Wednesday,Thursday,Friday,Saturday",
trans: { /*yes: "yes", Yes: "Yes", no: "no", No: "No", ok: "ok", on: "on", off: "off"*/ }},
"de_DE": {
lang: "de_DE",
decimal_point: ",",
thousands_sep: ".",
currency_symbol: "\x80",
int_curr_symbol: "EUR",
speed: "kmh",
distance: { 0: "m", 1: "km" },
temperature: "°C",
ampm: {0:"",1:""},
timePattern: { 0: "%HH:%MM:%SS", 1: "%HH:%MM" },
datePattern: { 0: "%A, %d. %B %Y", "1": "%d.%m.%Y" }, // Sonntag, 1. März 2020 // 01.01.20
abmonth: "Jan,Feb,Mär,Apr,Mai,Jun,Jul,Aug,Sep,Okt,Nov,Dez",
month: "Januar,Februar,März,April,Mai,Juni,Juli,August,September,Oktober,November,Dezember",
abday: "So,Mo,Di,Mi,Do,Fr,Sa",
day: "Sonntag,Montag,Dienstag,Mittwoch,Donnerstag,Freitag,Samstag",
trans: { yes: "ja", Yes: "Ja", no: "nein", No: "Nein", ok: "ok", on: "an", off: "aus" }},
"en_US": {
lang: "en_US",
decimal_point: ".",
thousands_sep: ",",
currency_symbol: "$",
int_curr_symbol: "USD",
speed: "mph",
distance: { 0: "mi", 1: "kmi" },
temperature: "°F",
ampm: {0:"am",1:"pm"},
timePattern: { 0: "%HH:%MM:%SS ", 1: "%HH:%MM" },
datePattern: { 0: "", 1: "%m/%d/%y" },
abmonth: "Jan,Feb,Mar,Apr,May,Jun,Jul,Aug,Sep,Oct,Nov,Dec",
month: "January,February,March,April,May,June,July,August,September,October,November,December",
abday: "Sun,Mon,Tue,Wed,Thu,Fri,Sat",
day: "Sunday,Monday,Tuesday,Wednesday,Thursday,Friday,Saturday",
trans: { yes: "yes", Yes: "Yes", no: "no", No: "No", ok: "ok", on: "on", off: "off" }},
"en_JP": { // we do not have the font, so it is not ja_JP
lang: "en_JP",
decimal_point: ".",
thousands_sep: ",",
currency_symbol: "¥",
int_curr_symbol: "JPY",
speed: "kmh",
distance: { 0: "m", 1: "km" },
temperature: "°F",
ampm: {0:"",1:""},
timePattern: { 0: "%HH:%MM:%SS ", 1: "%HH:%MM" },
datePattern: { 0: "%y/%M/%d", 1: "%y/%m;/%d" },
abmonth: "Jan,Feb,Mar,Apr,May,Jun,Jul,Aug,Sep,Oct,Nov,Dec",
month: "January,February,March,April,May,June,July,August,September,October,November,December",
abday: "Sun,Mon,Tue,Wed,Thu,Fri,Sat",
day: "Sunday,Monday,Tuesday,Wednesday,Thursday,Friday,Saturday",
trans: { yes: "yes", Yes: "Yes", no: "no", No: "No", ok: "ok", on: "on", off: "off" }},
"nl_NL": {
lang: "nl_NL",
decimal_point: ",",
thousands_sep: ".",
currency_symbol: "\x80",
int_curr_symbol: "EUR",
speed: "kmh",
distance: { 0: "m", 1: "km" },
temperature: "°C",
ampm: {0:"",1:""},
timePattern: { 0: "%HH:%MM:%SS", 1: "%HH:%MM" },
datePattern: { 0: "%A %B %d %Y", 1: "%d.%m.%y" }, // zondag 1 maart 2020 // 01.01.20
abday: "zo,ma,di,wo,do,vr,za",
day: "zondag,maandag,dinsdag,woensdag,donderdag,vrijdag,zaterdag",
abmonth: "jan,feb,mrt,apr,mei,jun,jul,aug,sep,okt,nov,dec",
month: "januari,februari,maart,april,mei,juni,juli,augustus,september,oktober,november,december",
trans: { yes: "yes", Yes: "Yes", no: "no", No: "No", ok: "ok", on: "on", off: "off" }},
"en_CA": {
lang: "en_CA",
decimal_point: ".",
thousands_sep: ",",
currency_symbol: "$",
int_curr_symbol: "CAD",
speed: "mph",
distance: { 0: "mi", 1: "kmi" },
temperature: "°F",
ampm: {0:"am",1:"pm"},
timePattern: { 0: "%HH:%MM:%SS ", 1: "%HH:%MM" },
datePattern: { 0: "%A, %B %d, %Y", "1": "%Y-%m-%d" }, // Sunday, March 1, 2020 // 2012-12-20
abmonth: "Jan,Feb,Mar,Apr,May,Jun,Jul,Aug,Sep,Oct,Nov,Dec",
month: "January,February,March,April,May,June,July,August,September,October,November,December",
abday: "Sun,Mon,Tue,Wed,Thu,Fri,Sat",
day: "Sunday,Monday,Tuesday,Wednesday,Thursday,Friday,Saturday",
trans: { yes: "yes", Yes: "Yes", no: "no", No: "No", ok: "ok", on: "on", off: "off" }},
"fr_FR": {
lang: "fr_FR",
decimal_point: ",",
thousands_sep: ".",
currency_symbol: "\x80",
int_curr_symbol: "EUR",
speed: "kmh",
distance: { 0: "m", 1: "km" },
temperature: "°C",
ampm: {0:"",1:""},
timePattern: { 0: "%HH:%MM:%SS ", 1: "%HH:%MM" },
datePattern: { 0: "%A %d %B %Y", "1": "%d/%m/%Y" }, // dimanche 1 mars 2020 // 01/03/2020
abmonth: "janv,févr,mars,avril,mai,juin,juil,août,sept,oct,nov,déc",
month: "janvier,février,mars,avril,mai,juin,juillet,août,septembre,octobre,novembre,décembre",
abday: "dim,lun,mar,mer,jeu,ven,sam",
day: "dimanche,lundi,mardi,mercredi,jeudi,vendredi,samedi",
trans : { yes : "oui", Yes: "Oui", no: "no", No: "No", ok : "ok", on: "on", off: "off" }},
"sv_SE": {
lang: "sv_SE",
decimal_point: ",",
thousands_sep: ".",
currency_symbol: "kr",
int_curr_symbol: "SKR",
speed: "kmh",
distance: { 0: "m", 1: "km" },
temperature: "°C",
ampm: {0:"fm",1:"em"},
timePattern: { 0: "%HH:%MM:%SS ", 1: "%HH:%MM" },
datePattern: { 0: "%A %B %d %Y", "1": "%Y-%m-%d" }, // söndag 1 mars 2020 // 2020-03-01
abmonth: "jan,feb,mars,apr,maj,juni,juli,aug,sep,okt,nov,dec",
month: "januari,februari,mars,april,maj,juni,juli,augusti,september,oktober,november,december",
abday: "sön,mån,tis,ons,tors,fre,lör",
day: "söndag,måndag,tisdag,onsdag,torsdag,fredag,lördag",
trans : { yes : "ja", Yes: "Ja", no: "nej", No: "Nej", ok : "ok", on: "on", off: "off" }},
"en_AU": {
lang: "en_AU",
decimal_point: ".",
thousands_sep: ",",
currency_symbol: "$",
int_curr_symbol: "AUD",
speed: "mph",
distance: { 0: "mi", 1: "kmi" },
temperature: "°F",
ampm: {0:"am",1:"pm"},
timePattern: { 0: "%HH:%MM:%SS ", 1: "%HH:%MM" },
datePattern: { 0: "%A, %B %d, %Y", "1": "%m/%d/%y" }, // Sunday, 1 March 2020 // 1/3/20
abmonth: "Jan,Feb,Mar,Apr,May,Jun,Jul,Aug,Sep,Oct,Nov,Dec",
month: "January,February,March,April,May,June,July,August,September,October,November,December",
abday: "Sun,Mon,Tue,Wed,Thu,Fri,Sat",
day: "Sunday,Monday,Tuesday,Wednesday,Thursday,Friday,Saturday",
trans: { yes: "yes", Yes: "Yes", no: "no", No: "No", ok: "ok", on: "on", off: "off" }},
"de_AT": {
lang: "de_AT",
decimal_point: ",",
thousands_sep: ".",
currency_symbol: "\x80",
int_curr_symbol: "EUR",
speed: "kmh",
distance: { 0: "m", 1: "km" },
temperature: "°C",
timePattern: { 0: "%HH:%MM:%SS ", 1: "%HH:%MM" },
datePattern: { 0: "%A, %d. %B %Y", "1": "%d.%m.%y" }, // Sonntag, 1. März 2020 // 01.03.20
abmonth: "Jän,Feb,März,Apr,Mai,Jun,Jul,Aug,Sep,Okt,Nov,Dez",
month: "Jänner,Februar,März,April,Mai,Juni,Juli,August,September,Oktober,November,Dezember",
abday: "So,Mo,Di,Mi,Do,Fr,Sa",
day: "Sonntag,Montag,Dienstag,Mittwoch,Donnerstag,Freitag,Samstag",
trans: { yes: "ja", Yes: "Ja", no: "nein", No: "Nein", ok: "ok", on: "an", off: "aus" }},
"en_IL": {
lang: "en_IL",
decimal_point: ",",
thousands_sep: ".",
currency_symbol: "₪",
int_curr_symbol: "ILS",
speed: "kmh",
distance: { 0: "m", 1: "km" },
temperature: "°F",
ampm: {0:"am",1:"pm"},
timePattern: { 0: "%HH:%MM:%SS ", 1: "%HH:%MM" },
datePattern: { 0: "%A, %B %d, %Y", "1": "%d/%m/%Y" }, // Sunday, 1 March 2020 // 01/03/2020
abmonth: "Jan,Feb,Mar,Apr,May,Jun,Jul,Aug,Sep,Oct,Nov,Dec",
month: "January,February,March,April,May,June,July,August,September,October,November,December",
abday: "Sun,Mon,Tue,Wed,Thu,Fri,Sat",
day: "Sunday,Monday,Tuesday,Wednesday,Thursday,Friday,Saturday",
trans: { yes: "yes", Yes: "Yes", no: "no", No: "No", ok: "ok", on: "on", off: "off" }},
"es_ES": {
lang: "es_ES",
decimal_point: ",",
thousands_sep: ".",
currency_symbol: "\x80",
int_curr_symbol: "EUR",
speed: "kmh",
distance: { 0: "m", 1: "km" },
temperature: "°C",
ampm: {0:"",1:""},
timePattern: { 0: "%HH:%MM:%SS ", 1: "%HH:%MM" },
datePattern: { 0: "%A, %d de %B de %Y", "1": "%d/%m/%y" }, // domingo, 1 de marzo de 2020 // 01/03/20
abmonth: "ene,feb,mar,abr,may,jun,jul,ago,sept,oct,nov,dic",
month: "enero,febrero,marzo,abril,mayo,junio,julio,agosto,septiembre,octubre,noviembre,diciembre",
abday: "dom,lun,mar,mié,jue,vie,sáb",
day: "domingo,lunes,martes,miércoles,jueves,viernes,sábado",
trans: { yes : "sí", Yes: "Sí",no: "no", No: "No", ok : "ok", on: "on", off: "off" }},
"fr_BE": {
lang: "fr_BE",
decimal_point: ",",
thousands_sep: ".",
currency_symbol: "\x80",
int_curr_symbol: "EUR",
speed: "kmh",
distance: { 0: "m", 1: "km" },
temperature: "°C",
ampm: {0: "",1: ""},
timePattern: { 0: "%HH:%MM:%SS ", 1: "%HH:%MM" },
datePattern: { 0: "%A %B %d %Y", "1": "%d/%m/%y" }, // dimanche 1 mars 2020 // 01/03/20
abmonth: "anv.,févr.,mars,avril,mai,juin,juil.,août,sept.,oct.,nov.,déc.",
month: "janvier,février,mars,avril,mai,juin,juillet,août,septembre,octobre,novembre,décembre",
abday: "dim,lun,mar,mer,jeu,ven,sam",
day: "dimanche,lundi,mardi,mercredi,jeudi,vendredi,samedi",
trans : { yes : "oui", Yes: "Oui", no: "no", No: "No", ok : "ok", on: "on", off: "off" }},
"fi_FI": {
lang: "fi_FI",
decimal_point: ",",
thousands_sep: ".",
currency_symbol: "\x80",
int_curr_symbol: "EUR",
speed: "kmh",
distance: { 0: "m", 1: "km" },
temperature: "°C",
ampm: {0: "ap",1: "ip"},
timePattern: { 0: "%HH:%MM:%SS ", 1: "%HH:%MM" }, // 17.00.00 // 17.00
datePattern: { 0: "%A %d. %B %Y", "1": "%-d/%-m/%Y" }, // sunnuntai 1. maaliskuuta 2020 // 1.3.2020
abmonth: "tammik,helmik,maalisk,huhtik,toukok,kesäk,heinäk,elok,syysk,lokak,marrask,jouluk",
month: "tammikuuta,helmikuuta,maaliskuuta,huhtikuuta,toukokuuta,kesäkuuta,heinäkuuta,elokuuta,syyskuuta,lokakuuta,marraskuuta,joulukuuta",
abday: "su,ma,ti,ke,to,pe,la",
day: "sunnuntaina,maanantaina,tiistaina,keskiviikkona,torstaina,perjantaina,lauantaina",
trans : { yes : "oui", Yes: "Oui", no: "no", No: "No", ok : "ok", on: "on", off: "off" }},
"de_CH": {
lang: "de_CH",
decimal_point: ",",
thousands_sep: ".",
currency_symbol: "CHF",
int_curr_symbol: "CHF",
speed: "kmh",
distance: { 0: "m", 1: "km" },
temperature: "°C",
ampm: {0:"vorm",1:" nachm"},
timePattern: { 0: "%HH:%MM:%SS", 1: "%HH:%MM" },
datePattern: { 0: "%A, %d. %B %Y", "1": "%d.%m.%Y" }, // Sonntag, 1. März 2020 // 1.3.2020
abmonth: "Jan,Feb,März,Apr,Mai,Jun,Jul,Aug,Sep,Okt,Nov,Dez",
month: "Januar,Februar,März,April,Mai,Juni,Juli,August,September,Oktober,November,Dezember",
abday: "So,Mo,Di,Mi,Do,Fr,Sa",
day: "Sonntag,Montag,Dienstag,Mittwoch,Donnerstag,Freitag,Samstag",
trans: { yes: "ja", Yes: "Ja", no: "nein", No: "Nein", ok: "ok", on: "an", off: "aus" }},
"fr_CH": {
lang: "fr_CH",
decimal_point: ",",
thousands_sep: ".",
currency_symbol: "CHF",
int_curr_symbol: "CHF",
speed: "kmh",
distance: { 0: "m", 1: "km" },
temperature: "°C",
ampm: {0:"AM",1:"PM"},
timePattern: { 0: "%HH:%MM:%SS", 1: "%HH:%MM" },
datePattern: { 0: "%A %d %B %Y", "1": "%d/%m/%y" }, // dimanche 1 mars 2020 // 01/03/20
abmonth: "anv.,févr.,mars,avril,mai,juin,juil.,août,sept.,oct.,nov.,déc.",
month: "janvier,février,mars,avril,mai,juin,juillet,août,septembre,octobre,novembre,décembre",
abday: "dim,lun,mar,mer,jeu,ven,sam",
day: "dimanche,lundi,mardi,mercredi,jeudi,vendredi,samedi",
trans : { yes : "oui", Yes: "Oui", no: "no", No: "No", ok : "ok", on: "on", off: "off" }},
"it_CH": {
lang: "it_CH",
decimal_point: ",",
thousands_sep: ".",
currency_symbol: "CHF",
int_curr_symbol: "CHF",
speed: 'kmh',
distance: { "0": "m", "1": "km" },
temperature: '°C',
timePattern: { 0: "%HH.%MM.%SS ", 1: "%HH.%MM" }, // 17.00.00 // 17.00
datePattern: { 0: "%A %B %d %Y", "1": "%d/%m/%Y" }, // sunnuntai 1. maaliskuuta 2020 // 1.3.2020
abmonth: "gen,feb,mar,apr,mag,giu,lug,ago,set,ott,nov,dic",
month: "gennaio,febbraio,marzo,aprile,maggio,giugno,luglio,agosto,settembre,ottobre,novembre,dicembre",
abday : "dom,lun,mar,mer,gio,ven,sab",
day: "domenica,lunedì,martedì,mercoledì,giovedì,venerdì, sabato",
trans : { yes: "sì", Yes: "Sì", no: "no", No: "No", ok: "ok", on: "on", off: "off" }},
"wae_CH" : {
lang: "wae_CH",
decimal_point: ",",
thousands_sep: ".",
currency_symbol: "CHF",
int_curr_symbol: "CHF",
speed: 'kmh',
distance: { "0": "m", "1": "km" },
temperature: '°C',
timePattern: { 0: "%HH.%MM.%SS ", 1: "%HH.%MM" }, // 17.00.00 // 17.00
datePattern: { 0: "%A, %d. %B %Y", "1": "%Y-%m-%d" }, // Sunntag, 1. Märze 2020 // 2020-03-01
abmonth: "Jen,Hor,Mär,Abr,Mei,Brá,Hei,Öig,Her,Wím,Win,Chr",
month: "Jenner,Hornig,Märze,Abrille,Meije,Bráčet,Heiwet,Öigšte,Herbštmánet,Wímánet,Wintermánet,Chrištmánet",
abday: "Sun,Män,Ziš,Mit,Fró,Fri,Sam",
day: "Sunntag,Mäntag,Zištag,Mittwuč,Fróntag,Fritag,Samštag",
trans : { yes: "sì", Yes: "Sì", no: "no", No: "No", ok: "ok", on: "on", off: "off" }},
"tr_TR": { // this is default
lang: "tr_TR",
decimal_point: ",",
thousands_sep: ".",
currency_symbol: "TL",
int_curr_symbol: "TRY",
speed: 'kmh',
distance: { "0": "m", "1": "km" },
temperature: '°C',
ampm: {0:"öö",1:"ös"},
timePattern: { 0: "%HH:%MM:%SS ", 1: "%HH:%MM" },
datePattern: { 0: "%d %w %Y %A", 1: "%d/%m/%Y" }, // 1 Mart 2020 Pazar // "01/03/2020"
abmonth: "Oca,Sub,Mar,Nis,May,Haz,Tem,Agu,Eyl,Eki,Kas,Ara",
month: "Ocak,Subat,Mart,Nisan,Mayis,Haziran,Temmuz,Agustos,Eylul,Ekim,Kasim,Aralik",
abday: "Paz,Pzt,Sal,Car,Per,Cum,Cmt",
day: "Pazar,Pazartesi,Sali,Carsamba,Persembe,Cuma,Cumartesi",
trans: { yes: "evet", Yes: "Evet", no: "hayir", No: "Hayir", ok: "tamam", on: "acik", off: "kapali" }},
"hu_HU": {
lang: "hu_HU",
decimal_point: ",",
thousands_sep: " ",
currency_symbol: "Ft",
int_curr_symbol: "HUF",
speed: 'kph',
distance: { "0": "m", "1": "km" },
temperature: '°C',
ampm: {0:"de",1:"du"},
timePattern: { 0: "%HH:%MM:%SS ", 1: "%HH:%MM" },
datePattern: { 0: "%Y %d %b", 1: "%Y.%m.%d" }, // 2020 Feb 28" // "2020.03.01."(short)
abmonth: "Jan,Feb,Már,Ápr,Máj,Jún,Júl,Aug,Szep,Okt,Nov,Dec",
month: "Január,Február,Március,Április,Május,Június,Július,Augusztus,Szeptember,Október,November,December",
abday: "Vas,Hét,Ke,Szer,Csüt,Pén,Szom",
day: "Vasárnap,Hétfő,Kedd,Szerda,Csütörtök,Péntek,Szombat",
trans: { yes: "igen", Yes: "Igen", no: "nem", No: "Nem", ok: "ok", on: "be", off: "ki" }},
};

View File

@ -115,11 +115,9 @@ function info() {
}
function cleanup() {
try {
var settings = require("Storage").readJSON('setting.json');
settings.welcomed = true;
require("Storage").write('setting.json',settings);
} catch (e) {}
var settings = require("Storage").readJSON('setting.json',1)||{};
settings.welcomed = true;
require("Storage").write('setting.json',settings);
return Promise.resolve();
}

View File

@ -4,7 +4,7 @@
</head>
<body>
<p>Enter a URL: <input type="text" id="url" value="http://espruino.com"></p>
<p>Enter a URL: <input type="text" id="url" class="form-input" value="http://espruino.com"></p>
<p>Try your QR Code: <div id="qrcode"></div></p>
<p>Click <button id="upload" class="btn btn-primary">Upload</button></p>

View File

@ -11,7 +11,7 @@ const yposYear = 175;
const yposGMT = 220;
// Check settings for what type our clock should be
var is12Hour = (require("Storage").readJSON("setting.json")||{})["12hour"];
var is12Hour = (require("Storage").readJSON("setting.json",1)||{})["12hour"];
function drawSimpleClock() {
// get date

View File

@ -2,3 +2,4 @@
0.03: Add support for Welcome app
0.04: Add setting to disable log messages
0.05: Fix Settings json
0.06: Remove distance setting as there's a separate app for Locale now

View File

@ -9,6 +9,5 @@
HID : false, // BLE HID mode, off by default
clock: null, // a string for the default clock's name
"12hour" : false, // 12 or 24 hour clock?
distance : "kilometer" // or "mile"
// welcomed : undefined/true (whether welcome app should show)
}

View File

@ -21,15 +21,12 @@ function resetSettings() {
HID : false, // BLE HID mode, off by default
clock: null, // a string for the default clock's name
"12hour" : false, // 12 or 24 hour clock?
distance : "kilometer" // or "mile"
// welcomed : undefined/true (whether welcome app should show)
};
updateSettings();
}
try {
settings = storage.readJSON('setting.json');
} catch (e) {}
settings = storage.readJSON('setting.json',1);
if (!settings) resetSettings();
const boolFormat = v => v ? "On" : "Off";
@ -143,15 +140,7 @@ function showLocaleMenu() {
settings["12hour"] = v;
updateSettings();
}
},
'Distance/Speed': {
value: settings.distance=="mile",
format: v => v?"mile":"km",
onchange: v => {
settings.distance = v?"mile":"kilometer";
updateSettings();
}
},
}
};
return E.showMenu(localemenu);
}

1
apps/torch/ChangeLog Normal file
View File

@ -0,0 +1 @@
0.01: New App!

1
apps/torch/app-icon.js Normal file
View File

@ -0,0 +1 @@
require("heatshrink").decompress(atob("mEwxH+AH4A/AH4Acq0yF1tWlksF10yqwuuSVIuGSVF/FwySm5nM0YuGSUovBGAIuGAgIukGAQuGAgIvlSQozEF0iSEeowvlGAT1HF0iSDeo4vlegSShF5fMv4uFSLQkC0QACSRguevErld4GBKSDFz4ABF5CSBLsIvLdDK7GFwiPIF0AvEFw4tbFwztKFrguDF4gADFkAuFF44unF4wuoGAouqAAwu/ABtWqwutmUsmQutF4JhKF0iSJdT4uFAoIwGFz4wCFwgECF0qRCFwo3BF0qSDMQiSBF0owCFwgFBF86SBF1qSFF1SSDF1gA/AH4A1A"))

8
apps/torch/app.js Normal file
View File

@ -0,0 +1,8 @@
Bangle.setLCDPower(1);
Bangle.setLCDTimeout(0);
g.reset();
g.fillRect(0,0,g.getWidth(),g.getHeight());
// Any button turns off
setWatch(()=>load(), BTN1);
setWatch(()=>load(), BTN2);
setWatch(()=>load(), BTN3);

BIN
apps/torch/app.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

18
apps/torch/widget.js Normal file
View File

@ -0,0 +1,18 @@
var clickTimes = [];
var CLICK_COUNT = 4; // number of taps
var CLICK_PERIOD = 1; // second
// we don't actually create/draw a widget here at all...
Bangle.on("lcdPower",function(on) {
// First click (that turns LCD on) isn't given to
// setWatch, so handle it here
if (on) clickTimes=[getTime()];
});
setWatch(function(e) {
while (clickTimes.length>=CLICK_COUNT) clickTimes.shift();
clickTimes.push(e.time);
var clickPeriod = e.time-clickTimes[0];
if (clickTimes.length==CLICK_COUNT && clickPeriod<CLICK_PERIOD)
load("torch.app.js");
}, BTN3, {repeat:true, edge:"rising"});

View File

@ -1,3 +1,4 @@
0.01: New App!
0.02: Animate balloon intro
0.03: BTN3 now won't restart when at the end
0.04: Fix regression after tweaks to Storage.readJSON

View File

@ -283,11 +283,9 @@ setWatch(()=>move(1), BTN3, {repeat:true});
setWatch(()=>{
// If we're on the last page
if (sceneNumber == scenes.length-1) {
try {
var settings = require("Storage").readJSON('setting.json');
settings.welcomed = true;
require("Storage").write('setting.json',settings);
} catch (e) {}
var settings = require("Storage").readJSON('setting.json',1)||{};
settings.welcomed = true;
require("Storage").write('setting.json',settings);
load();
}
}, BTN2, {repeat:true,edge:"rising"});

View File

@ -1 +1,3 @@
0.02: Now refresh battery monitor every minute if LCD on
0.03: Tweaks for variable size widget system
0.04: Ensure redrawing works with variable size widget system

View File

@ -1,17 +1,15 @@
(function(){
var img_charge = E.toArrayBuffer(atob("DhgBHOBzgc4HOP////////////////////3/4HgB4AeAHgB4AeAHgB4AeAHg"));
var CHARGING = 0x07E0;
var xpos = WIDGETPOS.tr-64;
WIDGETPOS.tr-=68;
function setWidth() {
WIDGETS["bat"].width = 40 + (Bangle.isCharging()?16:0);
}
function draw() {
var s = 63;
var x = xpos, y = 0;
g.clearRect(x,y,x+s,y+23);
var s = 39;
var x = this.x, y = this.y;
if (Bangle.isCharging()) {
g.setColor(CHARGING).drawImage(img_charge,x,y);
g.setColor(CHARGING).drawImage(atob("DhgBHOBzgc4HOP////////////////////3/4HgB4AeAHgB4AeAHgB4AeAHg"),x,y);
x+=16;
s-=16;
}
g.setColor(-1);
g.fillRect(x,y+2,x+s-4,y+21);
@ -20,11 +18,16 @@ function draw() {
g.setColor(CHARGING).fillRect(x+4,y+6,x+4+E.getBattery()*(s-12)/100,y+17);
g.setColor(-1);
}
Bangle.on('charging',function(charging) { draw(); g.flip(); if(charging)Bangle.buzz(); });
Bangle.on('charging',function(charging) {
if(charging) Bangle.buzz();
setWidth();
Bangle.drawWidgets(); // relayout widgets
g.flip();
});
var batteryInterval;
Bangle.on('lcdPower', function(on) {
if (on) {
draw();
WIDGETS["bat"].draw();
// refresh once a minute if LCD on
if (!batteryInterval)
batteryInterval = setInterval(draw, 60000);
@ -35,5 +38,6 @@ Bangle.on('lcdPower', function(on) {
}
}
});
WIDGETS["battery"]={draw:draw};
WIDGETS["bat"]={area:"tr",width:40,draw:draw};
setWidth();
})()

2
apps/widbt/ChangeLog Normal file
View File

@ -0,0 +1,2 @@
0.02: Tweaks for variable size widget system
0.03: Ensure redrawing works with variable size widget system

View File

@ -1,22 +1,19 @@
(function(){
var img_bt = E.toArrayBuffer(atob("CxQBBgDgFgJgR4jZMawfAcA4D4NYybEYIwTAsBwDAA=="));
var xpos = WIDGETPOS.tr-24;
WIDGETPOS.tr-=24;
function draw() {
var x = xpos, y = 0;
g.reset();
if (NRF.getSecurityStatus().connected)
g.setColor(0,0.5,1);
else
g.setColor(0.3,0.3,0.3);
g.drawImage(img_bt,10+x,2+y);
g.setColor(1,1,1);
g.drawImage(img_bt,10+this.x,2+this.y);
}
function changed() {
draw();
WIDGETS["bluetooth"].draw();
g.flip();// turns screen on
}
NRF.on('connected',changed);
NRF.on('disconnected',changed);
WIDGETS["bluetooth"]={draw:draw};
WIDGETS["bluetooth"]={area:"tr",width:24,draw:draw};
})()

2
apps/widclk/ChangeLog Normal file
View File

@ -0,0 +1,2 @@
0.02: Now refresh battery monitor every minute if LCD on
0.03: Ensure redrawing works with variable size widget system

View File

@ -1,39 +1,27 @@
(() => {
let intervalRef = null;
var width = 5 * 6*2
var xpos = WIDGETPOS.tr - width;
WIDGETPOS.tr -= (width + 2);
function draw() {
// Widget (0,0,239,23)
let date = new Date();
var dateArray = date.toString().split(" ");
g.setColor(1,1,1);
g.setFont("6x8", 2);
g.setFontAlign(-1, 0);
g.drawString(dateArray[4].substr(0, 5), xpos, 11, true); // 5 * 6*2 = 60
g.flip();
g.reset().setFont("6x8", 2).setFontAlign(-1, 0);
var time = require("locale").time(new Date(),1);
g.drawString(time, this.x, this.y+11, true); // 5 * 6*2 = 60
}
function clearTimers(){
if(intervalRef) {
clearInterval(intervalRef);
intervalRef = null;
}
if(intervalRef) {
clearInterval(intervalRef);
intervalRef = null;
}
}
function startTimers(){
if(intervalRef) clearTimers();
intervalRef = setInterval(draw, 60*1000);
draw();
intervalRef = setInterval(draw, 60*1000);
WIDGETS["wdclk"].draw();
}
Bangle.on('lcdPower', (on) => {
if (on) {
// startTimers(); // comment out as it is called by app anyway
} else {
clearTimers();
}
clearTimers();
if (on) startTimers();
});
// add your widget
WIDGETS["wdclk"]={draw:startTimers};
})()
WIDGETS["wdclk"]={area:"tr",width:width,draw:draw};
if (Bangle.isLCDOn) intervalRef = setInterval(draw, 60*1000);
})()

View File

@ -1 +1,3 @@
0.01: New Widget!
0.02: Tweaks for variable size widget system
0.03: Ensure redrawing works with variable size widget system

View File

@ -1,16 +1,14 @@
(() => {
var xpos = WIDGETPOS.tl;
var width = 24;
WIDGETPOS.tl += width+2;
var currentBPM = undefined;
var lastBPM = undefined;
var firstBPM = true; // first reading since sensor turned on
function draw() {
var width = 24;
g.reset();
g.setFont("6x8", 1);
g.setFontAlign(0, 0);
g.clearRect(xpos,15,xpos+width,24); // erase background
g.clearRect(this.x,this.y+15,this.x+width,this.y+23); // erase background
var bpm = currentBPM, isCurrent = true;
if (bpm===undefined) {
bpm = lastBPM;
@ -19,9 +17,9 @@
if (bpm===undefined)
bpm = "--";
g.setColor(isCurrent ? "#ffffff" : "#808080");
g.drawString(bpm, xpos+width/2, 19);
g.drawString(bpm, this.x+width/2, this.y+19);
g.setColor(isCurrent ? "#ff0033" : "#808080");
g.drawImage(atob("CgoCAAABpaQ//9v//r//5//9L//A/+AC+AAFAA=="),xpos+(width-10)/2,1);
g.drawImage(atob("CgoCAAABpaQ//9v//r//5//9L//A/+AC+AAFAA=="),this.x+(width-10)/2,this.y+1);
g.setColor(-1);
}
@ -31,7 +29,7 @@
Bangle.setHRMPower(1);
firstBPM = true;
currentBPM = undefined;
draw();
WIDGETS["hrm"].draw();
} else {
Bangle.setHRMPower(0);
}
@ -44,10 +42,10 @@
currentBPM = d.bpm;
lastBPM = currentBPM;
}
draw();
WIDGETS["hrm"].draw();
});
Bangle.setHRMPower(Bangle.isLCDOn());
// add your widget
WIDGETS["hrm"]={draw:draw};
WIDGETS["hrm"]={area:"tl",width:24,draw:draw};
})();

2
apps/widid/ChangeLog Normal file
View File

@ -0,0 +1,2 @@
0.01: New Widget!
0.02: Tweaks for variable size widget system

12
apps/widid/widget.js Normal file
View File

@ -0,0 +1,12 @@
/* jshint esversion: 6 */
(() => {
var id = NRF.getAddress().substr().substr(12).split(":");
// draw your widget at xpos
function draw() {
g.reset().setColor(0, 0.5, 1).setFont("6x8", 1);
g.drawString(id[0], this.x+2, this.y+4, true);
g.drawString(id[1], this.x+2, this.y+14, true);
}
WIDGETS["widid"] = { area:"tr", width:16, draw: draw };
})();

BIN
apps/widid/widget.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

2
apps/widnceu/ChangeLog Normal file
View File

@ -0,0 +1,2 @@
0.01: New Widget!
0.02: Tweaks for variable size widget system

View File

@ -1,13 +1,5 @@
(function(){
var img = E.toArrayBuffer(atob("SxgCAAAAAAAAAAAAAAAAAAAAAAAAALwDwH/gD/0B//Af+AAD4C8f/wL8Dwf/8H//C//C//AAD9C8f/wL9Dw//+H//i4AD0AH8D/C8fAAL/Dz///H//y8ALgAf/D/i8fAALrzz///H//2//PAAv/Dz28f/gLz7z///H//29VPQAv/Dx+8fqQLw/y///H//y4ALwAP/Dwv8fAALwfw//9H//i+qD8FC0DwP8fAALwPwP/4H/+C//A//AADwD8fAAAAAAC/QD+gAAAAL4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGqooBkD+AP0fgvgAAAAAAAAAAL/88C4NBw0NBjQcAAAAAAAAAALQA8C4AAyQDBjAJAAAAAAAAAALQA8C4AAzACBjAKAAAAAAAAAAL/88C4ABTACRjQbAAAAAAAAAAL/48C4AHDACRgvjAAAAAAAAAALQA8C4AcDACBgACAAAAAAAAAALQA+D0BwCQDBgANAAAAAAAAAAL/8P/wHAA0NBgAoAAAAAAAAAAGqoC+AP/0HgAQuAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"));
var xpos = WIDGETPOS.tl;
WIDGETPOS.tl+=75;
WIDGETS["nceu"]={draw:()=>{
var x = xpos, y = 0;
g.setColor(0.17,0.2,0.5);
g.drawImage(img,x,y);
WIDGETS["nceu"]={area:"tl",width:75,draw:function(){
g.reset().setColor(0.17,0.2,0.5);
g.drawImage(atob("SxgCAAAAAAAAAAAAAAAAAAAAAAAAALwDwH/gD/0B//Af+AAD4C8f/wL8Dwf/8H//C//C//AAD9C8f/wL9Dw//+H//i4AD0AH8D/C8fAAL/Dz///H//y8ALgAf/D/i8fAALrzz///H//2//PAAv/Dz28f/gLz7z///H//29VPQAv/Dx+8fqQLw/y///H//y4ALwAP/Dwv8fAALwfw//9H//i+qD8FC0DwP8fAALwPwP/4H/+C//A//AADwD8fAAAAAAC/QD+gAAAAL4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGqooBkD+AP0fgvgAAAAAAAAAAL/88C4NBw0NBjQcAAAAAAAAAALQA8C4AAyQDBjAJAAAAAAAAAALQA8C4AAzACBjAKAAAAAAAAAAL/88C4ABTACRjQbAAAAAAAAAAL/48C4AHDACRgvjAAAAAAAAAALQA8C4AcDACBgACAAAAAAAAAALQA+D0BwCQDBgANAAAAAAAAAAL/8P/wHAA0NBgAoAAAAAAAAAAGqoC+AP/0HgAQuAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"),this.x,this.y);
g.setColor(1,1,1);
}};
})()

View File

@ -3,3 +3,5 @@
0.04: Fix pedometer reload when going back to clock app
0.05: Add foot icon, use tidier font, and move to the left of the screen
0.06: Fix widget position increment
0.07: Tweaks for variable size widget system
0.08: Ensure redrawing works with variable size widget system

View File

@ -1,17 +1,11 @@
(() => {
const PEDOMFILE = "wpedom.json";
// add the width
// WIDGETPOS.tr is originally 208 without any widgets
var xpos = WIDGETPOS.tl;
var width = 24;
WIDGETPOS.tl += (width + 2);
let lastUpdate = new Date();
let stp_today = 0;
// draw your widget at xpos
// draw your widget
function draw() {
// Widget (0,0,239,23)
var width = 24;
if (stp_today > 99999){
stp_today = stp_today % 100000; // cap to five digits + comma = 6 characters
}
@ -24,9 +18,9 @@
g.setFont("6x8", 1);
}
g.setFontAlign(0, 0); // align to x: center, y: center
g.clearRect(xpos,15,xpos+width,24); // erase background
g.drawString(stps, xpos+width/2, 19);
g.drawImage(atob("CgoCLguH9f2/7+v6/79f56CtAAAD9fw/n8Hx9A=="),xpos+(width-10)/2,2);
g.clearRect(this.x,this.y+15,this.x+width,this.y+23); // erase background
g.drawString(stps, this.x+width/2, this.y+19);
g.drawImage(atob("CgoCLguH9f2/7+v6/79f56CtAAAD9fw/n8Hx9A=="),this.x+(width-10)/2,this.y+2);
}
Bangle.on('step', (up) => {
@ -39,11 +33,11 @@
}
lastUpdate = date;
//console.log("up: " + up + " stp: " + stp_today + " " + date.toString());
if (Bangle.isLCDOn()) draw();
if (Bangle.isLCDOn()) WIDGETS["wpedom"].draw();
});
// redraw when the LCD turns on
Bangle.on('lcdPower', function(on) {
if (on) draw();
if (on) WIDGETS["wpedom"].draw();
});
// When unloading, save state
E.on('kill', () => {
@ -55,9 +49,9 @@
});
// add your widget
WIDGETS["wpedom"]={draw:draw};
WIDGETS["wpedom"]={area:"tl",width:26,draw:draw};
// Load data at startup
let pedomData = require("Storage").readJSON(PEDOMFILE);
let pedomData = require("Storage").readJSON(PEDOMFILE,1);
if (pedomData) {
if (pedomData.lastUpdate)
lastUpdate = new Date(pedomData.lastUpdate);

View File

@ -10,16 +10,10 @@ var APPDIR = ROOTDIR+'/apps';
var APPJSON = ROOTDIR+'/apps.json';
var OUTFILE = ROOTDIR+'/firmware.js';
var APPS = [ // IDs of apps to install
"boot",
"launch",
"mclock",
"setting",
"astroid",
"gpstime",
"compass",
"sbt",
"sbat"
"boot","launch","mclock","setting",
"about","alarm","widbat","widbt","welcome"
];
var MINIFY = true;
var fs = require("fs");
var AppInfo = require(ROOTDIR+"/appinfo.js");
@ -28,14 +22,27 @@ var appfiles = [];
function fileGetter(url) {
console.log("Loading "+url)
/*if (url.endsWith(".js")) {
var f = url.slice(0,-3);
console.log("MINIFYING "+f);
const execSync = require('child_process').execSync;
code = execSync(`espruino --board BANGLEJS --minify ${f}.js -o ${f}.min.js`);
console.log(code.toString());
url = f+".min.js";
}*/
if (MINIFY) {
/*if (url.endsWith(".js")) {
var f = url.slice(0,-3);
console.log("MINIFYING "+f);
const execSync = require('child_process').execSync;
// --config PRETOKENISE=true
// --minify
code = execSync(`espruino --config SET_TIME_ON_WRITE=false --minify --board BANGLEJS ${f}.js -o ${f}.min.js`);
console.log(code.toString());
url = f+".min.js";
}*/
if (url.endsWith(".json")) {
var f = url.slice(0,-5);
console.log("MINIFYING JSON "+f);
var j = eval("("+fs.readFileSync(url).toString()+")");
var code = JSON.stringify(j);
//console.log(code);
url = f+".min.json";
fs.writeFileSync(url, code);
}
}
return Promise.resolve(fs.readFileSync(url).toString());
}
@ -50,6 +57,9 @@ Promise.all(APPS.map(appid => {
var js = "// Generated by BangleApps/bin/firmwaremaker.js\n";
appfiles.forEach((file) => {
js += file.cmd+"\n";
/*if (file.crc && file.evaluate!==true) {
js += `\x10if (E.CRC32(require('Storage').read(${JSON.stringify(file.name)}))!=${file.crc}){console.log("${file.name} invalid");FAIL++}\n`;
}*/
});
fs.writeFileSync(OUTFILE, js);
console.log("Output written to "+OUTFILE);

36
bin/sanitycheck.js Normal file → Executable file
View File

@ -18,11 +18,11 @@ try {
var BASEDIR = __dirname+"/../";
var APPSDIR = BASEDIR+"apps/";
function ERROR(s) {
console.error(s);
console.error("ERROR: "+s);
process.exit(1);
}
function WARN(s) {
console.log(s);
console.log("Warning: "+s);
}
var appsFile, apps;
@ -39,13 +39,24 @@ try{
apps.forEach((app,addIdx) => {
if (!app.id) ERROR(`App ${appIdx} has no id`);
console.log(`Checking ${app.id}...`);
//console.log(`Checking ${app.id}...`);
var appDir = APPSDIR+app.id+"/";
if (!fs.existsSync(APPSDIR+app.id)) ERROR(`App ${app.id} has no directory`);
if (!app.name) ERROR(`App ${app.id} has no name`);
var isApp = !app.type || app.type=="app";
if (app.name.length>20 && !app.shortName && isApp) ERROR(`App ${app.id} has a long name, but no shortName`);
if (!app.version) WARN(`App ${app.id} has no version`);
else {
if (!fs.existsSync(appDir+"ChangeLog")) {
if (app.version != "0.01")
WARN(`App ${app.id} has no ChangeLog`);
} else {
var versions = fs.readFileSync(appDir+"ChangeLog").toString().match(/\d+\.\d+:/g);
var lastChangeLog = versions.pop().slice(0,-1);
if (lastChangeLog != app.version)
WARN(`App ${app.id} app version (${app.version}) and ChangeLog (${lastChangeLog}) don't agree`);
}
}
if (!app.description) ERROR(`App ${app.id} has no description`);
if (!app.icon) ERROR(`App ${app.id} has no icon`);
if (!fs.existsSync(appDir+app.icon)) ERROR(`App ${app.id} icon doesn't exist`);
@ -59,8 +70,10 @@ apps.forEach((app,addIdx) => {
fileNames.push(file.name);
if (file.url) if (!fs.existsSync(appDir+file.url)) ERROR(`App ${app.id} file ${file.url} doesn't exist`);
if (!file.url && !file.content && !app.custom) ERROR(`App ${app.id} file ${file.name} has no contents`);
var fileContents = "";
if (file.content) fileContents = file.content;
if (file.url) fileContents = fs.readFileSync(appDir+file.url).toString();
if (file.evaluate) {
var fileContents = file.content ? file.content : fs.readFileSync(appDir+file.url).toString();
try {
acorn.parse("("+fileContents+")");
} catch(e) {
@ -74,6 +87,21 @@ apps.forEach((app,addIdx) => {
ERROR(`App ${app.id}'s ${file.name} has evaluate:true but is not valid JS expression`);
}
}
if (file.name.endsWith(".js")) {
// TODO: actual lint?
try {
acorn.parse(fileContents);
} catch(e) {
console.log("=====================================================");
console.log(" PARSE OF "+appDir+file.url+" failed.");
console.log("");
console.log(e);
console.log("=====================================================");
console.log(fileContents);
console.log("=====================================================");
ERROR(`App ${app.id}'s ${file.name} is a JS file but isn't valid JS`);
}
}
});
//console.log(fileNames);
if (isApp && !fileNames.includes(app.id+".app.js")) ERROR(`App ${app.id} has no entrypoint`);

View File

@ -2,28 +2,47 @@ Puck.debug=3;
// FIXME: use UART lib so that we handle errors properly
var Comms = {
reset : () => new Promise((resolve,reject) => {
Puck.write("\x03\x10reset();\n", (result) => {
if (result===null) return reject("");
reset : (opt) => new Promise((resolve,reject) => {
Puck.write(`\x03\x10reset(${opt=="wipe"?"1":""});\n`, (result) => {
if (result===null) return reject("Connection failed");
setTimeout(resolve,500);
});
}),
uploadApp : (app,skipReset) => {
return AppInfo.getFiles(app, httpGet).then(fileContents => {
return new Promise((resolve,reject) => {
fileContents = fileContents.map(storageFile=>storageFile.cmd).join("\n")+"\n";
console.log("uploadApp",fileContents);
console.log("uploadApp",fileContents.map(f=>f.name).join(", "));
// Upload each file one at a time
function doUploadFiles() {
// No files left - print 'reboot' message
if (fileContents.length==0) {
Puck.write(`\x10E.showMessage('Hold BTN3\\nto reload')\n`,(result) => {
if (result===null) return reject("");
resolve(app);
});
return;
}
var f = fileContents.shift();
console.log(`Upload ${f.name} => ${JSON.stringify(f.content)}`);
// Chould check CRC here if needed instead of returning 'OK'...
// E.CRC32(require("Storage").read(${JSON.stringify(app.name)}))
Puck.write(`\x10${f.cmd};Bluetooth.println("OK")\n`,(result) => {
if (!result || result.trim()!="OK") return reject("Unexpected response "+(result||""));
doUploadFiles();
}, true); // wait for a newline
}
// Start the upload
function doUpload() {
Puck.write(`\x10E.showMessage('Uploading\\n${app.id}...')\n${fileContents}\x10E.showMessage('Hold BTN3\\nto reload')\n`,(result) => {
Puck.write(`\x10E.showMessage('Uploading\\n${app.id}...')\n`,(result) => {
if (result===null) return reject("");
resolve(appJSON);
doUploadFiles();
});
}
if (skipReset) {
doUpload();
} else {
// reset to ensure we have enough memory to upload what we need to
Comms.reset().then(doUpload)
Comms.reset().then(doUpload, reject)
}
});
});
@ -32,7 +51,7 @@ getInstalledApps : () => {
return new Promise((resolve,reject) => {
Puck.write("\x03",(result) => {
if (result===null) return reject("");
Puck.eval('require("Storage").list(/\.info$/).map(f=>{var j=require("Storage").readJSON(f)||{};j.id=f.slice(0,-5);return j})', (appList,err) => {
Puck.eval('require("Storage").list(/\.info$/).map(f=>{var j=require("Storage").readJSON(f,1)||{};j.id=f.slice(0,-5);return j})', (appList,err) => {
if (appList===null) return reject(err || "");
console.log("getInstalledApps", appList);
resolve(appList);
@ -41,7 +60,8 @@ getInstalledApps : () => {
});
},
removeApp : app => { // expects an app structure
var cmds = app.storage.map(file=>{
var storage = [{name:app.id+".info"}].concat(app.storage);
var cmds = storage.map(file=>{
return `\x10require("Storage").erase(${toJS(file.name)});\n`;
}).join("");
console.log("removeApp", cmds);
@ -53,7 +73,7 @@ removeApp : app => { // expects an app structure
}));
},
removeAllApps : () => {
return Comms.reset().then(() => new Promise((resolve,reject) => {
return Comms.reset("wipe").then(() => new Promise((resolve,reject) => {
// Use write with newline here so we wait for it to finish
Puck.write('\x10E.showMessage("Erasing...");require("Storage").eraseAll();Bluetooth.println("OK")\n', (result,err) => {
if (!result || result.trim()!="OK") return reject(err || "");
@ -68,7 +88,7 @@ setTime : () => {
var cmd = '\x03\x10setTime('+(d.getTime()/1000)+');';
// in 1v93 we have timezones too
cmd += 'E.setTimeZone('+tz+');';
cmd += "(s=>{s&&(s.timezone="+tz+")&&require('Storage').write('setting.json',s);})(require('Storage').readJSON('setting.json'))\n";
cmd += "(s=>{s&&(s.timezone="+tz+")&&require('Storage').write('setting.json',s);})(require('Storage').readJSON('setting.json',1))\n";
Puck.write(cmd, (result) => {
if (result===null) return reject("");
resolve();

File diff suppressed because one or more lines are too long

BIN
img/github-icon-sml.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 472 B

BIN
img/github-icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 926 B

View File

@ -29,13 +29,19 @@
}
.chip {
cursor: pointer;
}
.tile-content { position: relative; }
.link-github {
position:absolute;
top: 36px;
left: -24px;
}
</style>
</head>
<body>
<header class="navbar-primary navbar">
<section class="navbar-section">
<a href="https://banglejs.com" class="navbar-brand mr-2"><img src="img/banglejs-logo-sml.png" alt="Bangle.js">&nbsp;&nbsp;App Loader</a>
<a href="https://banglejs.com" target="_blank" class="navbar-brand mr-2"><img src="img/banglejs-logo-sml.png" alt="Bangle.js">&nbsp;&nbsp;App Loader</a>
<!-- <a href="#" class="btn btn-link">...</a> -->
</section>
<section class="navbar-section">
@ -50,9 +56,8 @@
</header>
<div class="container" style="padding-top:4px">
<p><span class="label label-error">App Loader is incompatible with 'old' Bangle.js firmwares</span>
<a href="https://www.espruino.com/Bangle.js#firmware-updates" target="_blank">(<a href="http://forum.espruino.com/conversations/344251/" target="_blank">more info</a>) Please update to the <a href="https://www.espruino.com/Bangle.js#firmware-updates">latest firmware</a> or
<a href="https://banglejs.com/oldapps/">use the legacy apps</a>.
<p><b>Note:</b> If you have a version of Bangle.js firmware before 2v04, please update to the <a href="https://www.espruino.com/Bangle.js#firmware-updates" target="_blank">latest firmware</a> or
<a href="https://banglejs.com/oldapps/">use the legacy app loader</a>.
</p>
</div>
@ -104,15 +109,15 @@
<div class="container bangle-tab" id="aboutcontainer" style="display:none">
<div class="hero bg-gray">
<div class="hero-body">
<a href="https://banglejs.com"><img src="img/banglejs-logo-mid.png" alt="Bangle.js"></a>
<a href="https://banglejs.com" target="_blank"><img src="img/banglejs-logo-mid.png" alt="Bangle.js"></a>
<h2>App Loader</h2>
<p>A tool for uploading and removing apps from <a href="https://banglejs.com">Bangle.js Smart Watches</a></p>
<p>A tool for uploading and removing apps from <a href="https://banglejs.com" target="_blank">Bangle.js Smart Watches</a></p>
</div>
</div>
<div class="container" style="padding-top: 8px;">
<p>Using <a href="https://espruino.com/">Espruino</a>, Icons from <a href="https://icons8.com/">icons8.com</a></p>
<p>Check out <a href="https://github.com/espruino/BangleApps">the Source on GitHub</a> for more information.</p>
<p>Check out <a href="https://github.com/espruino/BangleApps" target="_blank">the Source on GitHub</a>, or
find out <a href="https://www.espruino.com/Bangle.js+App+Loader" target="_blank">how to add your own app</p>
<p>Using <a href="https://espruino.com/" target="_blank">Espruino</a>, Icons from <a href="https://icons8.com/" target="_blank">icons8.com</a></p>
<h3>Utilities</h3>
<p><button class="btn" id="settime">Set Bangle.js Time</button>

View File

@ -272,11 +272,12 @@ function refreshLibrary() {
if (versionInfo) versionInfo = " <small>("+versionInfo+")</small>";
return `<div class="tile column col-6 col-sm-12 col-xs-12">
<div class="tile-icon">
<figure class="avatar"><img src="apps/${app.icon?`${app.id}/${app.icon}`:"unknown.png"}" alt="${escapeHtml(app.name)}"></figure>
<figure class="avatar"><img src="apps/${app.icon?`${app.id}/${app.icon}`:"unknown.png"}" alt="${escapeHtml(app.name)}"></figure><br/>
</div>
<div class="tile-content">
<p class="tile-title text-bold">${escapeHtml(app.name)} ${versionInfo}</p>
<p class="tile-subtitle">${escapeHtml(app.description)}</p>
<a href="https://github.com/espruino/BangleApps/tree/master/apps/${app.id}" target="_blank" class="link-github"><img src="img/github-icon-sml.png" alt="See the code on GitHub"/></a>
</div>
<div class="tile-action">
<button class="btn btn-link btn-action btn-lg ${(appInstalled&&app.interface)?"":"d-hide"}" appid="${app.id}" title="Download data from app"><i class="icon icon-download"></i></button>
@ -302,13 +303,14 @@ function refreshLibrary() {
// check icon to figure out what we should do
if (icon.classList.contains("icon-share")) {
// emulator
var file = app.storage.find(f=>f.name[0]=='-');
var file = app.storage.find(f=>f.name.endsWith('.js'));
if (!file) {
console.error("No entrypoint found for "+appid);
return;
}
var baseurl = window.location.href;
var url = baseurl+"apps/"+app.id+"/"+file.url;
baseurl = baseurl.substr(0,baseurl.lastIndexOf("/"));
var url = baseurl+"/apps/"+app.id+"/"+file.url;
window.open(`https://espruino.com/ide/emulator.html?codeurl=${url}&upload`);
} else if (icon.classList.contains("icon-upload")) {
// upload

View File

@ -7,6 +7,7 @@
"acorn": ""
},
"scripts": {
"test": "node bin/sanitycheck.js"
"test": "node bin/sanitycheck.js",
"start": "npx http-server"
}
}