Merge pull request #1 from espruino/master

Updating to latest version
pull/470/head
Richard Hopkins 2020-05-17 17:41:37 +01:00 committed by GitHub
commit e975bad1ad
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
51 changed files with 976 additions and 180 deletions

1
.gitignore vendored
View File

@ -3,3 +3,4 @@ node_modules
package-lock.json
.DS_Store
*.js.bak
appdates.csv

View File

@ -14,3 +14,7 @@ Changed for individual apps are listed in `apps/appname/ChangeLog`
* Added espruinotools.js for pretokenisation
* Included image and compression tools in repo
* Added better upload of large files (incl. compression)
* URL fetch is now async
* Adding '#search' after the URL (when not the name of a 'filter' chip) will set up search for that term
* If `bin/pre-publish.sh` has been run and recent.csv created, add 'Sort By' chip
* New 'espruinotools' which fixes pretokenise issue when ID follows ID (fix #416)

View File

@ -344,6 +344,9 @@ that handles configuring the app.
When the app settings are opened, this function is called with one
argument, `back`: a callback to return to the settings menu.
Usually it will save any information in `app.json` where `app` is the name
of your app - so you should change the example accordingly.
Example `settings.js`
```js
// make sure to enclose the function in parentheses

View File

@ -2,7 +2,7 @@
{ "id": "boot",
"name": "Bootloader",
"icon": "bootloader.png",
"version":"0.16",
"version":"0.17",
"description": "This is needed by Bangle.js to automatically load the clock, menu, widgets and settings",
"tags": "tool,system",
"type":"bootloader",
@ -78,7 +78,7 @@
{ "id": "welcome",
"name": "Welcome",
"icon": "app.png",
"version":"0.08",
"version":"0.09",
"description": "Appears at first boot and explains how to use Bangle.js",
"tags": "start,welcome",
"allow_emulator":true,
@ -108,7 +108,7 @@
{ "id": "mclock",
"name": "Morphing Clock",
"icon": "clock-morphing.png",
"version":"0.05",
"version":"0.06",
"description": "7 segment clock that morphs between minutes and hours",
"tags": "clock",
"type":"clock",
@ -168,7 +168,7 @@
"name": "Image background clock",
"shortName":"Image Clock",
"icon": "app.png",
"version":"0.05",
"version":"0.06",
"description": "A clock with an image as a background",
"tags": "clock",
"type" : "clock",
@ -589,7 +589,7 @@
"id": "ncstart",
"name": "NCEU Startup",
"icon": "start.png",
"version":"0.05",
"version":"0.06",
"description": "NodeConfEU 2019 'First Start' Sequence",
"tags": "start,welcome",
"storage": [
@ -1272,7 +1272,7 @@
"name": "Battery Chart",
"shortName":"Battery Chart",
"icon": "app.png",
"version":"0.09",
"version":"0.10",
"readme": "README.md",
"description": "A widget and an app for recording and visualizing battery percentage over time.",
"tags": "app,widget,battery,time,record,chart,tool",
@ -1421,7 +1421,7 @@
"id": "metronome",
"name": "Metronome",
"icon": "metronome_icon.png",
"version": "0.04",
"version": "0.05",
"readme": "README.md",
"description": "Makes the watch blinking and vibrating with a given rate",
"tags": "tool",
@ -1435,7 +1435,8 @@
"name": "metronome.img",
"url": "metronome-icon.js",
"evaluate": true
}
},
{"name":"metronome.settings.js","url":"settings.js"}
]
},
{ "id": "blackjack",
@ -1469,7 +1470,7 @@
"name": "Round clock with seconds, minutes and date",
"shortName":"Round Clock",
"icon": "app.png",
"version":"0.02",
"version":"0.03",
"description": "Designed round clock with ticks for minutes and seconds and heart rate indication",
"tags": "clock",
"type": "clock",
@ -1572,7 +1573,7 @@
"id": "largeclock",
"name": "Large Clock",
"icon": "largeclock.png",
"version": "0.02",
"version": "0.03",
"description": "A readable and informational digital watch, with date, seconds and moon phase",
"readme": "README.md",
"tags": "clock",
@ -1591,12 +1592,10 @@
{
"name": "largeclock.settings.js",
"url": "settings.js"
},
{
"name": "largeclock.json",
"url": "largeclock.json",
"evaluate": true
}
],
"data": [
{"name":"largeclock.json"}
]
},
{ "id": "smtswch",
@ -1617,6 +1616,18 @@
{"name":"switch-off.img","url":"switch-off.js","evaluate":true}
]
},
{ "id": "miplant",
"name": "Xiaomi Plant Sensor",
"shortName":"Mi Plant",
"icon": "app.png",
"version":"0.01",
"description": "Reads and displays data from Xiaomi bluetooth plant moisture sensors",
"tags": "xiaomi,mi,plant,ble,bluetooth",
"storage": [
{"name":"miplant.app.js","url":"app.js"},
{"name":"miplant.img","url":"app-icon.js","evaluate":true}
]
},
{
"id": "simpletimer",
"name": "Timer",
@ -1708,7 +1719,46 @@
{ "name": "gallifr.settings.js", "url": "settings.js" }
],
"data": [
{"name":"app.json"}
{"name":"gallifr.json"}
]
},
{ "id": "rndmclk",
"name": "Random Clock Loader",
"icon": "rndmclk.png",
"version":"0.02",
"description": "Load a different clock whenever the LCD is switched on.",
"readme": "README.md",
"tags": "widget,clock",
"type":"widget",
"storage": [
{"name":"rndmclk.wid.js","url":"widget.js"}
]
},
{ "id": "dotmatrixclock",
"name": "Dotmatrix Clock",
"icon": "dotmatrixclock.png",
"version":"0.01",
"description": "A clear white-on-blue dotmatrix simulated clock",
"tags": "clock,dotmatrix,retro",
"type": "clock",
"allow_emulator":true,
"readme": "README.md",
"storage": [
{"name":"dotmatrixclock.app.js","url":"app.js"},
{"name":"dotmatrixclock.img","url":"dotmatrixclock-icon.js","evaluate":true}
]
},
{
"id": "jbm8b",
"name": "Magic 8 Ball",
"shortName": "Magic 8 Ball",
"icon": "app.png",
"description": "A simple fortune telling app",
"tags": "game",
"storage": [
{ "name": "jbm8b.app.js", "url": "app.js" },
{ "name": "jbm8b.img", "url": "app-icon.js", "evaluate": true }
],
"version": "0.03"
}
]

View File

@ -7,3 +7,4 @@
0.07: Improve logging and charting of component states and add widget icon
0.08: Fix for Home button in the app and README added.
0.09: Fix failing dismissal of Gadgetbridge notifications, record (coarse) bluetooth state
0.10: Remove widget icon and improve listener and setInterval handling for widget (might help with https://github.com/espruino/BangleApps/issues/381)

View File

@ -1,4 +1,5 @@
(() => {
let recordingInterval = null;
const Storage = require("Storage");
const switchableConsumers = {
@ -14,53 +15,44 @@
const recordingInterval10Min = 60 * 10 * 1000;
const recordingInterval1Min = 60 * 1000; //For testing
const recordingInterval10S = 10 * 1000; //For testing
var recordingInterval = null;
var compassEventReceived = false;
var gpsEventReceived = false;
var hrmEventReceived = false;
// draw your widget
function draw() {
let x = this.x;
let y = this.y;
g.setColor(0, 1, 0);
g.fillPoly([x + 5, y, x + 5, y + 4, x + 1, y + 4, x + 1, y + 20, x + 18, y + 20, x + 18, y + 4, x + 13, y + 4, x + 13, y], true);
g.setColor(0, 0, 0);
g.drawPoly([x + 5, y + 6, x + 8, y + 12, x + 13, y + 12, x + 16, y + 18], false);
g.reset();
// void
}
function onMag() {
function batteryChartOnMag() {
compassEventReceived = true;
// Stop handling events when no longer necessarry
Bangle.removeListener("mag", onMag);
Bangle.removeListener("mag", batteryChartOnMag);
}
function onGps() {
function batterChartOnGps() {
gpsEventReceived = true;
Bangle.removeListener("GPS", onGps);
Bangle.removeListener("GPS", batterChartOnGps);
}
function onHrm() {
function batteryChartOnHrm() {
hrmEventReceived = true;
Bangle.removeListener("HRM", onHrm);
Bangle.removeListener("HRM", batteryChartOnHrm);
}
function getEnabledConsumersValue() {
// Wait for an event from each of the devices to see if they are switched on
var enabledConsumers = switchableConsumers.none;
Bangle.on('mag', onMag);
Bangle.on('GPS', onGps);
Bangle.on('HRM', onHrm);
Bangle.on('mag', batteryChartOnMag);
Bangle.on('GPS', batterChartOnGps);
Bangle.on('HRM', batteryChartOnHrm);
// Wait two seconds, that should be enough for each of the events to get raised once
setTimeout(() => {
Bangle.removeAllListeners();
Bangle.removeListener('mag', batteryChartOnMag);
Bangle.removeListener('GPS', batterChartOnGps);
Bangle.removeListener('HRM', batteryChartOnHrm);
}, 2000);
if (Bangle.isLCDOn())
@ -112,14 +104,20 @@
}
function reload() {
WIDGETS["batchart"].width = 24;
console.log("Reloading BatteryChart widget");
WIDGETS["batchart"].width = 0;
if (recordingInterval) {
clearInterval(recordingInterval);
recordingInterval = null;
}
recordingInterval = setInterval(logBatteryData, recordingInterval10Min);
}
// add the widget
WIDGETS["batchart"] = {
area: "tl", width: 24, draw: draw, reload: reload
area: "tl", width: 0, draw: draw, reload: reload
};
reload();

View File

@ -15,3 +15,4 @@
0.14: Move welcome loaders to *.boot.js
0.15: Added BLE HID option for Joystick and bare Keyboard
0.16: Detect out of memory errors and draw them onto the bottom of the screen in red
0.17: Don't modify beep/buzz behaviour if firmware does it automatically

View File

@ -21,9 +21,10 @@ if (s.blerepl===false) { // If not programmable, force terminal off Bluetooth
// 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===false) Bangle.beep=Promise.resolve;
else if (s.beep=="vib") Bangle.beep = function (time, freq) {
if (!Bangle.F_BEEPSET) {
if (!s.vibrate) Bangle.buzz=Promise.resolve;
if (s.beep===false) Bangle.beep=Promise.resolve;
else if (s.beep=="vib") Bangle.beep = function (time, freq) {
return new Promise(function(resolve) {
if ((0|freq)<=0) freq=4000;
if ((0|time)<=0) time=200;
@ -34,7 +35,8 @@ else if (s.beep=="vib") Bangle.beep = function (time, freq) {
resolve();
}, time);
});
};
};
}
Bangle.setLCDTimeout(s.timeout);
if (!s.timeout) Bangle.setLCDPower(1);
E.setTimeZone(s.timezone);

View File

@ -0,0 +1 @@
0.01: Create dotmatrix clock app

View File

@ -0,0 +1,28 @@
# Dotmatrix clock
A clock face simulating the classic dotmatrix displays. Shows time, date, compass, and heart rate.
![](dotmatrix-clock-screen-shot.png)
## Features
* Easy to read digits
* Simulated white-on-blue dotmatrix display
* Compass
* Heart rate monitor
* Multiple colour palletes, swipe to change
## Usage
### Sensor readings
When the display is activated by 'flipping' the watch up, the compass and heart sensors will be activated automatically, but if
you activate the LCD through a button press, then the sensors will remain off until you press button-1.
### Colours
The display defaults to blue, but you can change this to orange by swiping the screen
## Requests
If you have any feature requests, please send an email to the author paulcockrell@gmail.com`

354
apps/dotmatrixclock/app.js Executable file
View File

@ -0,0 +1,354 @@
/**
* BangleJS DotMatrixCLOCK
*
* + Original Author: Paul Cockrell https://github.com/paulcockrell
* + Created: May 2020
*/
const storage = require('Storage');
const settings = (storage.readJSON('setting.json', 1) || {});
const is12Hour = settings["12hour"] || false;
const timeout = settings.timeout || 20;
const font7x7 = {
"empty": "00000000",
"0": "3E61514945433E",
"1": "1808080808081C",
"2": "7E01013E40407F",
"3": "7E01013E01017E",
"4": "4141417F010101",
"5": "7F40407E01017E",
"6": "3E40407E41413E",
"7": "3F010202040408",
"8": "3E41413E41413E",
"9": "3E41413F01013E",
};
const font5x5 = {
"empty": "00000000",
"-": "0000FF0000",
"0": "0E1915130E",
"1": "0C0404040E",
"2": "1E010E101F",
"3": "1E010E011E",
"4": "11111F0101",
"5": "1F101E011E",
"6": "0E101E110E",
"7": "1F01020408",
"8": "0E110E110E",
"9": "0E110F010E",
"A": "040A0E1111",
"B": "1E111E111E",
"C": "0F1010100F",
"D": "1E1111111E",
"E": "1F101E101F",
"F": "1F101E1010",
"G": "0F1013110E",
"H": "11111F1111",
"I": "0E0404040E",
"J": "1F0404140C",
"L": "101010101F",
"M": "111B151111",
"N": "1119151311",
"O": "0E1111110E",
"P": "1E111E1010",
"R": "1E111E1111",
"S": "0F100E011E",
"T": "1F04040404",
"U": "111111110E",
"V": "1111110A04",
"W": "111115150A",
"Y": "110A040404",
};
// Char renderer
const COLORS = {
blue: {
BG: "#0297fe",
DARK: "#3b3ce8",
LIGHT: "#E9ffff",
},
orange: {
BG: "#f7b336",
DARK: "#ac721e",
LIGHT: "#f6fc0f",
}
};
let selectedColor = "blue";
let displayTimeoutRef, sensorTimeoutRef;
// Example
// binToHex(["0111110", "1000000", "1000000", "1111110", "1000001", "1000001", "0111110"])
function binToHex(bins) {
return bins.map(bin => ("00" + (parseInt(bin, 2).toString(16))).substr(-2).toUpperCase()).join("");
}
// Example
// hexToBin("3E40407E41413E")
function hexToBin(hexStr) {
const regEx = new RegExp("..", "g");
const bin = hexStr
.replace(regEx, el => el + '_')
.slice(0, -1)
.split('_')
.map(hex => ("00000000" + (parseInt(hex, 16)).toString(2)).substr(-8));
return bin;
}
function drawPixel(opts) {
g.setColor(opts.color);
g.fillRect(opts.x, opts.y, opts.x + opts.w, opts.y + opts.h);
}
function drawGrid(pos, dims, charAsBin, opts) {
const defaultOpts = {
pxlW: 5,
pxlH: 5,
gap: 1,
offColor: COLORS[selectedColor].DARK,
onColor: COLORS[selectedColor].LIGHT
};
const pxl = Object.assign({}, defaultOpts, opts);
for (let rowY = 0; rowY < dims.rows; rowY++) {
const y = pos.y + ((pxl.pxlH + pxl.gap) * rowY);
for (let colX = 7; colX > (7 - dims.cols); colX--) {
const x = pos.x + ((pxl.pxlW + pxl.gap) * colX);
const color = (charAsBin && parseInt(charAsBin[rowY][colX])) ? pxl.onColor : pxl.offColor;
drawPixel({
x: x,
y: y,
w: pxl.pxlW,
h: pxl.pxlH,
color: color,
});
}
}
}
function drawFont(str, font, x, y) {
let fontMap, rows, cols;
switch(font) {
case "7x7":
fontMap = font7x7;
rows = cols = 7;
break;
case "5x5":
fontMap = font5x5;
rows = cols = 5;
break;
default:
throw "Unknown font type: " + font;
}
const pxlW = 2;
const pxlH = 2;
const gap = 2;
const gutter = 3;
const charArr = str.split("");
const gridWidthTotal = (rows * (pxlW + gap)) + gutter;
for (let i = 0; i < charArr.length; i++) {
const charAsBin = fontMap.hasOwnProperty(charArr[i])?
hexToBin(fontMap[charArr[i]]):
fontMap.empty;
drawGrid(
{x: x + (i * gridWidthTotal), y: y},
{rows: rows, cols: cols},
charAsBin,
{pxlW: pxlW, pxlH: pxlH, gap: gap}
);
}
}
function drawTitles() {
g.setColor("#ffffff");
g.setFont("6x8");
g.drawString("COMPASS", 52, 49);
g.drawString("HEART", 122, 49);
g.drawString("TIME", 52, 94);
g.drawString("DATE", 52, 144);
}
function drawCompass(lastHeading) {
const directions = [
'N',
'NE',
'E',
'SE',
'S',
'SW',
'W',
'NW'
];
const cps = Bangle.getCompass();
let angle = cps.heading;
let heading = angle?
directions[Math.round(((angle %= 360) < 0 ? angle + 360 : angle) / 45) % 8]:
"-- ";
heading = (heading + " ").slice(0, 3);
if (lastHeading != heading) drawFont(heading, "5x5", 40, 67);
setTimeout(drawCompass.bind(null, heading), 1000 * 2);
}
function drawHeart(hrm) {
drawFont((" " + (hrm ? hrm.bpm : "---")).slice(-3), "5x5", 109, 67);
}
function drawTime(lastHrs, lastMns, toggle) {
const date = new Date();
const h = date.getHours();
const hrs = ("00" + ((is12Hour && h > 12) ? h - 12 : h)).substr(-2);
const mns = ("00" + date.getMinutes()).substr(-2);
if (lastHrs != hrs) {
drawFont(hrs, "7x7", 48, 109);
}
if (lastMns != mns) {
drawFont(mns, "7x7", 124, 109);
}
const color = toggle? COLORS[selectedColor].LIGHT : COLORS[selectedColor].DARK;
// This should toggle on/off per second
drawPixel({
color: color,
x: 118, y: 118,
w: 2, h: 2,
});
drawPixel({
color: color,
x: 118, y: 125,
w: 2, h: 2,
});
setTimeout(drawTime.bind(null, hrs, mns, !toggle), 1000);
}
function drawDate(lastDate) {
const locale = require('locale');
const date = new Date();
if (lastDate != date.toISOString().split('T')[0]) {
const dow = locale.dow(date, 1).toUpperCase();
const dayNum = ("00" + date.getDate()).slice(-2);
const mon = locale.month(date).toUpperCase().slice(0, 3);
const yr = date.getFullYear().toString().slice(-2);
drawFont(dow + " " + dayNum, "5x5", 40, 159);
drawFont(mon + " " + yr, "5x5", 40, 189);
}
setTimeout(drawDate.bind(null, date.toISOString().split('T')), 1000 * 60);
}
function setSensors(state) {
// Already reading sensors and trying to activate sensors, do nothing
if (sensorTimeoutRef && state === 1) return;
// If we are activating the sensors, turn them off again in one minute
if (state === 1) {
sensorTimeoutRef = setTimeout(() => { setSensors(0); }, 1000 * 60);
} else {
if (sensorTimeoutRef) {
clearInterval(sensorTimeoutRef);
sensorTimeoutRef = null;
}
// Bit nasty, but we only redraw the heart value on sensor callback
// but we want to blank out when sensor is off, but no callback for
// that so force redraw here
drawHeart();
}
Bangle.setHRMPower(state);
Bangle.setCompassPower(state);
}
function drawScreen() {
g.setBgColor(COLORS[selectedColor].BG);
g.clearRect(0, 24, g.getWidth(), g.getHeight());
// Draw components
drawTitles();
drawCompass();
drawHeart();
drawTime();
drawDate();
}
function clearTimers(){
if (displayTimeoutRef) {
clearInterval(displayTimeoutRef);
displayTimeoutRef = null;
}
if (sensorTimeoutRef) {
clearInterval(sensorTimeoutRef);
sensorTimeoutRef = null;
}
}
function resetDisplayTimeout() {
if (displayTimeoutRef) clearInterval(displayTimeoutRef);
Bangle.setLCDPower(true);
displayTimeoutRef = setTimeout(() => {
if (Bangle.isLCDOn()) Bangle.setLCDPower(false);
clearTimers();
}, 1000 * timeout);
}
// Turn sensors on
setSensors(1);
// Reset screen
g.clear();
// Load and draw widgets
Bangle.loadWidgets();
Bangle.drawWidgets();
// Draw screen
drawScreen();
resetDisplayTimeout();
// Setup callbacks
Bangle.on('swipe', (sDir) => {
selectedColor = selectedColor === "blue" ? "orange" : "blue";
resetDisplayTimeout();
drawScreen();
});
Bangle.on('HRM', drawHeart);
setWatch(() => {
setSensors(1);
resetDisplayTimeout();
}, BTN1, {repeat: true, edge: "falling"});
setWatch(() => {
setSensors(0);
clearTimers();
Bangle.setLCDMode();
Bangle.showLauncher();
}, BTN2, {repeat: false, edge: "falling"});
Bangle.on('lcdPower', (on) => {
if(on) {
resetDisplayTimeout();
} else {
clearTimers();
setSensors(0);
}
});
Bangle.on('faceUp', (up) => {
if (up && !Bangle.isLCDOn()) {
setSensors(1);
resetDisplayTimeout();
}
});

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

@ -0,0 +1 @@
require("heatshrink").decompress(atob("mEwxH+AAmBAEgrFAAZelFo+s1krAEcAE4IuFHIIJBAEXXE4KSEF84nCF4WBgErBYoAfEoaTBF42zF8PXF5QNBi4AgMIYv/F9nX64CDAw4ACl8vBIgGGF/4AOEgKPfI4xfoF96P/R/6PdACAv/F/4v/F/4v/F8HX68Xl8vAwIDCBIQADBIQQDBoQQDF/4AOGQqPbLAxmGL5gGDF/4AfF/6PRBIQQDSwwv/ABwoCR7xYGMwxfhF94AeF/4vr1nXBoIAf64mCF4gJEF8IkCF4YABFYQLDAEItBwIuCF9InBF4iSBwMrAEgnBFwgACXsIADFo4ABqwAkFQg="))

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.8 KiB

View File

@ -201,7 +201,7 @@ function showSortAppsManually() {
function setSortorder(app, val) {
app = store.readJSON(app.id + '.info', 1);
app.sortorder = val;
store.writeJSON(app.id + '.info', app);
store.write(app.id + '.info', JSON.stringify(app));
}
function getAppsList() {

View File

@ -10,7 +10,7 @@ const cirRad = 2*Math.PI;
const proportion = 0.3; // relative size of hour hand
const thickness = 4; // thickness of decorative lines
// retrieve settings from menu
let settings = require('Storage').readJSON('app.json',1)||{};
let settings = require('Storage').readJSON('gallifr.json',1)||{};
const decoration = !settings.decoration;
const widgets = !settings.widgets;
if (widgets) {

View File

@ -1,11 +1,11 @@
// make sure to enclose the function in parentheses
(function (back) {
let settings = require('Storage').readJSON('app.json',1)||{};
let settings = require('Storage').readJSON('gallifr.json',1)||{};
let colours = ["green","red","blue","80s"];
let onoff = ["on","off"];
function save(key, value) {
settings[key] = value;
require('Storage').write('app.json',settings);
require('Storage').writeJSON('gallifr.json',settings);
}
const appMenu = {
'': {'title': 'Clock Settings'},

3
apps/jbm8b/ChangeLog Normal file
View File

@ -0,0 +1,3 @@
0.01: First working version
0.02: Added delay in replying for dramatic effect
0.03: Fixed apps.json entry

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

@ -0,0 +1 @@
require("heatshrink").decompress(atob("mEwhBC/AGMrq2B1gAEwNWlYthq2s64AKGYIydFpoAEGLUrFqIADqxcXFqhiDFymBFy7GCF1owTRjCSVlYudeiGsF7/XlaNqSKBeP1mBwJxQMBReO1gaEleBMDBLN1hAC1hhBAoIwNCwQAGlZINqxvFGAIXOSBAXQN4hPBC5yQIVBxfBCAgvQSBC+NFAYRDMwJHOF654DqxkBYooALF6+sbIhkEF8Z3CRIWBR6AvXFAzvQF6wnIYQJgNd5AWNdoLoGBBAvPO5pfYH4IvUUwS/GVBzXBYCpHCq2s1mBDwKOWDwRgNPAwVVMCRLCwIABCZ6OJJSAATLxZgRACJeLAAMrFz9WFxiRgRpoADwIub1guQGDmsXhqSfRiL0G1jqkMRYxRwKLUGK2sFryVEq2B1gAEwNWFkIA/AH4A/AH4AQ"))

80
apps/jbm8b/app.js Normal file
View File

@ -0,0 +1,80 @@
const affirmative = [
'It is\ncertain.',
'It is\ndicededly\nso.',
'Without\na doubt.',
'Yes\ndefinitely.',
'You may\nrely\non it.',
'As I see,\nit yes.',
'Most\nlikely.',
'Outlook\ngood.',
'Yes.',
'Signs point\nto yes.'
];
const nonCommittal = [
'Reply hazy,\ntry again.',
'Ask again\nlater.',
'Better not\ntell you\nnow.',
'Cannot\npredict\nnow.',
'Concentrate\nand\nask again.'
];
const negative = [
'Don\'t\ncount on it.',
'My reply\nis no.',
'My sources\nsay no.',
'Outlook\nis not\nso\ngood.',
'Very\ndoubtful.'
];
const title = 'Magic 8 Ball';
const answers = [affirmative, nonCommittal, negative];
function getRandomArbitrary(min, max) {
return Math.random() * (max - min) + min;
}
function predict() {
// affirmative, negative or non-committal
let max = answers.length;
const a = Math.floor(getRandomArbitrary(0, max));
// sets max compared to answer category
max = answers[a].length;
const b = Math.floor(getRandomArbitrary(0, max));
// get the answer
const response = answers[a][b];
return response;
}
function draw(msg) {
// console.log(msg);
g.clear();
E.showMessage(msg, title);
}
function reply(button) {
const theButton = (typeof button === 'undefined' || isNaN(button)) ? 1 : button;
const timer = Math.floor(getRandomArbitrary(0, theButton) * 1000);
// Thinking...
draw('...');
setTimeout('draw(predict());', timer);
}
function ask() {
draw('Ask me a\nYes or No\nquestion\nand\ntouch the\nscreen');
}
g.clear();
Bangle.loadWidgets();
Bangle.drawWidgets();
ask();
// Event Handlers
Bangle.on('touch', (button) => reply(button));
setWatch(ask, BTN1, { repeat: true, edge: "falling" });
setWatch(reply, BTN3, { repeat: true, edge: "falling" });
// Back to launcher
setWatch(Bangle.showLauncher, BTN2, { repeat: false, edge: "falling" });

BIN
apps/jbm8b/app.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

@ -1,2 +1,3 @@
0.01: Init
0.02: fix 3/4 moon orientation
0.03: Change `largeclock.json` to 'data' file to allow settings to be preserved

View File

@ -9,10 +9,8 @@ const moonX = 215;
const moonY = 50;
const settings = require("Storage").readJSON("largeclock.json", 1);
const BTN1app = settings.BTN1;
const BTN3app = settings.BTN3;
console.log("BTN1app", BTN1app);
console.log("BTN3app", BTN3app);
const BTN1app = settings.BTN1 || "";
const BTN3app = settings.BTN3 || "";
function drawMoon(d) {
const BLACK = 0,
@ -174,14 +172,14 @@ Bangle.setLCDMode();
// Show launcher when middle button pressed
clearWatch();
setWatch(Bangle.showLauncher, BTN2, { repeat: false, edge: "falling" });
setWatch(
if (BTN1app) setWatch(
function() {
load(BTN1app);
},
BTN1,
{ repeat: false, edge: "rising" }
);
setWatch(
if (BTN3app) setWatch(
function() {
load(BTN3app);
},

View File

@ -34,7 +34,7 @@
function onchange(v) {
settings[btn] = v;
s.write("largeclock.json", settings);
s.writeJSON("largeclock.json", settings);
}
const btnMenu = {

View File

@ -3,3 +3,4 @@
0.04: Improve performance, attempt to remove occasional glitch when LCD on (fix #279)
0.05: Add "ram" keyword to allow 2v06 Espruino builds to cache function that needs to be fast
Fix issue where first digit could get stuck going from "2x:xx" to " x:xx" (fix #365)
0.06: Support 12 hour time

View File

@ -1,3 +1,4 @@
var is12Hour = (require("Storage").readJSON("setting.json",1)||{})["12hour"];
var locale = require("locale");
var CHARW = 34; // how tall are digits?
var CHARP = 2; // how chunky are digits?
@ -146,7 +147,7 @@ function drawDigits(lastText,thisText,n) {
x+=s+p+7;
}
}
function drawSeconds() {
function drawEverythingElse() {
var x = (CHARW + CHARP + 6)*5;
var y = Y + 2*CHARW + CHARP;
var d = new Date();
@ -154,6 +155,8 @@ function drawSeconds() {
g.setFont("6x8");
g.setFontAlign(-1,-1);
g.drawString(("0"+d.getSeconds()).substr(-2), x, y-8, true);
// meridian
if (is12Hour) g.drawString((d.getHours() < 12) ? "AM" : "PM", x, Y + 4, true);
// date
g.setFontAlign(0,-1);
var date = locale.date(d,false);
@ -164,13 +167,15 @@ function drawSeconds() {
function showTime() {
if (animInterval) return; // in animation - quit
var d = new Date();
var t = (" "+d.getHours()).substr(-2)+":"+
var hours = d.getHours();
if (is12Hour) hours = ((hours + 11) % 12) + 1;
var t = (" "+hours).substr(-2)+":"+
("0"+d.getMinutes()).substr(-2);
var l = lastTime;
// same - don't animate
if (t==l || l=="-----") {
drawDigits(l,t,0);
drawSeconds();
drawEverythingElse();
lastTime = t;
return;
}

View File

@ -2,3 +2,4 @@
0.02: Watch vibrates with every beat
0.03: Uses mean of three time intervalls to calculate bmp
0.04: App shows instructions, Widgets remain visible, color changed
0.05: Buzz intensity and beats per bar can be changed via settings-app

View File

@ -8,6 +8,7 @@ This metronome makes your watch blink and vibrate with a given rate.
* Use `BTN1` to increase the bmp value by one.
* Use `BTN3` to decrease the bmp value by one.
* You can change the bpm value any time by tapping the screen or using `BTN1` and `BTN3`.
* Intensity of buzzing and the beats per bar (default 4) can be changed with the settings-app. The first beat per bar will be marked in red.
## Attributions

View File

@ -6,31 +6,40 @@ var tindex=0; //index to iterate through time_diffs
Bangle.setLCDTimeout(undefined); //do not deaktivate display while running this app
const storage = require("Storage");
const SETTINGS_FILE = 'metronome.settings.json';
//return setting
function setting(key) {
//define default settings
const DEFAULTS = {
'beatsperbar': 4,
'buzzintens': 0.75,
};
if (!settings) { loadSettings(); }
return (key in settings) ? settings[key] : DEFAULTS[key];
}
//load settings
let settings;
function loadSettings() {
settings = storage.readJSON(SETTINGS_FILE, 1) || {};
}
function changecolor() {
const maxColors = 2;
const colors = {
0: { value: 0xFFFF, name: "White" },
// 1: { value: 0x000F, name: "Navy" },
// 2: { value: 0x03E0, name: "DarkGreen" },
// 3: { value: 0x03EF, name: "DarkCyan" },
// 4: { value: 0x7800, name: "Maroon" },
// 5: { value: 0x780F, name: "Purple" },
// 6: { value: 0x7BE0, name: "Olive" },
// 7: { value: 0xC618, name: "LightGray" },
// 8: { value: 0x7BEF, name: "DarkGrey" },
// 9: { value: 0x001F, name: "Blue" },
// 10: { value: 0x07E0, name: "Green" },
// 11: { value: 0x07FF, name: "Cyan" },
1: { value: 0xF800, name: "Red" },
// 13: { value: 0xF81F, name: "Magenta" },
// 14: { value: 0xFFE0, name: "Yellow" },
// 15: { value: 0xFFFF, name: "White" },
// 16: { value: 0xFD20, name: "Orange" },
// 17: { value: 0xAFE5, name: "GreenYellow" },
// 18: { value: 0xF81F, name: "Pink" },
0: { value: 0xF800, name: "Red" },
1: { value: 0xFFFF, name: "White" },
2: { value: 0x9492, name: "gray" },
3: { value: 0xFFFF, name: "White" },
4: { value: 0x9492, name: "gray" },
5: { value: 0xFFFF, name: "White" },
6: { value: 0x9492, name: "gray" },
7: { value: 0xFFFF, name: "White" },
};
g.setColor(colors[cindex].value);
if (cindex == maxColors-1) {
if (cindex == setting('beatsperbar')-1) {
cindex = 0;
}
else {
@ -42,11 +51,16 @@ function changecolor() {
function updateScreen() {
g.clearRect(0, 50, 250, 150);
changecolor();
Bangle.buzz(50, 0.75);
try {
Bangle.buzz(50, setting('buzzintens'));
}
catch(err) {
}
g.setFont("Vector",48);
g.drawString(Math.floor(bpm)+"bpm", 5, 60);
}
Bangle.on('touch', function(button) {
// setting bpm by tapping the screen. Uses the mean time difference between several tappings.
if (tindex < time_diffs.length) {

View File

@ -0,0 +1,48 @@
// This file should contain exactly one function, which shows the app's settings
/**
* @param {function} back Use back() to return to settings menu
*/
(function(back) {
const SETTINGS_FILE = 'metronome.settings.json';
// initialize with default settings...
let s = {
'beatsperbar': 4,
'buzzintens': 0.75,
};
// ...and overwrite them with any saved values
// This way saved values are preserved if a new version adds more settings
const storage = require('Storage');
const saved = storage.readJSON(SETTINGS_FILE, 1) || {};
for (const key in saved) {
s[key] = saved[key];
}
// creates a function to safe a specific setting, e.g. save('color')(1)
function save(key) {
return function(value) {
s[key] = value;
storage.write(SETTINGS_FILE, s);
};
}
const menu = {
'': { 'title': 'Metronome' },
'< Back': back,
'beats per bar': {
value: s.beatsperbar,
min: 1,
max: 8,
step: 1,
onchange: save('beatsperbar'),
},
'buzz intensity': {
value: s.buzzintens,
min: 0.0,
max: 1.0,
step: 0.25,
onchange: save('buzzintens'),
},
};
E.showMenu(menu);
});

1
apps/miplant/ChangeLog Normal file
View File

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

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

@ -0,0 +1 @@
require("heatshrink").decompress(atob("mEwxH+4AA/AH4A/AAPQ64AQ44ZKBYwvc6/QF9wwFF9XXF73I4IAH44/F54vdCpYQESAYvm5Avm44ADA4Yvmc44v/F/4v/F/4v/F/4v+zmjv4ABF9GrwoACrYvn2WGFwgABSh4AV0QtDFwYvmFwlhF9wuDF9QuEF82jFw4vmMIQuFv4vuEDOi0d/WgV/0YNGBIN/LrSvCABAkECAI4GAChKBF5QABCIYEDADKpCsIvJLQQ0EGoShJMBwACSJYvEB5GiMCYxJF4LuCLorTLF6IxGQILuBMYgAGC4QwP0YwHYwQbCF4K1CL4lhCohfQMBBiCFQQvEAgIsFAATxWSQyPDAYYSIHgRgZE4IsBF4QGCCI6NRMBboFXgTTIFyYwIPYWcGAb2CCI2iF6qwBD4aqERYQABIQ+cFywALLwgAqXoYvrSAQROA=="))

74
apps/miplant/app.js Normal file
View File

@ -0,0 +1,74 @@
function getImgHum() {
return require("heatshrink").decompress(atob("jUoxH+AEtlsoYYDS4ZYDAYaVDLAYFDSQYHDSIZYDBIaPDLAYLDRoZYDBoaLDLAYPDRIZYDCIaHDLAYTDQoZYDCoaDDOQYXAA+JxIYX1utDSwYBAAIzYGiwZUTgpODQpzPGGgY3OdI4aRDIIaMDJIYCDIztDGRwaJP5oaWDAwaRDBAbOC5YcKB5I="));
}
function getImgTemp() {
return require("heatshrink").decompress(atob("iUqxH+AA2sAAQLHCBASMCAoSLCPOBAAQRfI/5Hn3YACy4ACCL4ADCL5H/I/AQHCRAQJCQwQLCQgQNCQYRQCB4A/ADaPjYqTpSCRYQGCZALFA"));
}
function getImgFert() {
return require("heatshrink").decompress(atob("kklxH+AC+FwtbDbAfFAAVbEbgiGEbYiHEbQiEsIjiEQYjeEQiPdEQrXdEdKnTAAJsMD6QlJFZAAIGAIkPEaIkCrdhEaR9MT4gkLFAyjMYoojNUZ4jFEoxrGEBCJDEZSWEEZdhCwpsKJQiJFAgYgGEQwjLD4QjFCRD+KCAylGQ4gjXVhAiPEhAKDJIwiQEowIEEQo2GERgAKEYwAcEUQkDEL9VAAgHFETgAIDJwePEZwdTE5ggdMJt6AAQEEqwRMABYQDAAwkBF5AkKEBQAPEUR6ESAQicJIX+A=="));
}
var deviceInfo = {};
function parseDevice(device) {
var d = new DataView(device.serviceData["fe95"]);
var frame = d.getUint16(0,true);
var offset = 5;
if (frame&16) offset+=6; // mac address
if (frame&32) offset+=1; // capabilitities
if (frame&64) { // event
var l = d.getUint8(offset+2);
var code = d.getUint16(offset,true);
if (!deviceInfo[device.id]) deviceInfo[device.id]={id:device.id};
event = deviceInfo[device.id];
switch (code) {
case 0x1004: event.temperature = d.getInt16(offset+3,true)/10; break;
case 0x1006: event.humidity = d.getInt16(offset+3)/10; break;
case 0x100D:
event.temperature = d.getInt16(offset+3,true)/10;
event.humidity = d.getInt16(offset+5)/10; break;
case 0x1008: event.moisture = d.getUint8(offset+3); break;
case 0x1009: event.fertility = d.getUint16(offset+3,true)/10; break;
// case 0x1007: break; // 3 bytes? got 84,0,0 or 68,0,0
default: event.code = code;
event.raw = new Uint8Array(d.buffer, offset+3, l);
break;
}
//print(event);
show(event);
}
}
/*
eg. {
"id": "c4:7c:8d:6a:ac:79 public",
"temperature": 16.6, "code": 4103,
"raw": new Uint8Array([246, 0, 0]),
"moisture": 46, "fertility": 20.8 }
*/
function show(event) {
g.reset().setFont("6x8");
var y = 45 + 50*Object.keys(deviceInfo).indexOf(event.id);
g.drawString(event.id.substr(0,17),0,y);
g.drawImage(getImgHum(),0,y+15);
g.setFont("6x8",2);
var t = (event.moisture===undefined) ? "?" : event.moisture;
g.drawString((t+" ").substr(0,3),35,y+25,true);
g.drawImage(getImgFert(),80,y+15);
t = Math.round(event.fertility) || "?";
g.drawString((t+" ").substr(0,3), 120, y+25, true);
g.drawImage(getImgTemp(),160,y+15);
t = Math.round(event.temperature) || "?";
g.drawString((t+" ").substr(0,3), 180, y+25, true);
g.flip();
}
g.clear();
g.setFont("6x8",2).setFontAlign(0,-1).drawString("Scanning...",120,24);
Bangle.loadWidgets()
Bangle.drawWidgets()
NRF.setScan(parseDevice, { filters: [{serviceData:{"fe95":{}}}], timeout: 2000 });

BIN
apps/miplant/app.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

@ -6,3 +6,4 @@
Don't run again when settings app is updated (or absent)
Add "Run Now" option to settings
0.05: Don't overwrite existing settings on app update
0.06: Allow welcome to run after a fresh install

View File

@ -1,11 +1,8 @@
(function() {
let s = require('Storage').readJSON('ncstart.json', 1)
|| require('Storage').readJSON('setting.json', 1)
|| {welcomed: true} // do NOT run if global settings are also absent
if (!s.welcomed && require('Storage').read('ncstart.app.js')) {
let s = require('Storage').readJSON('ncstart.json', 1) || {};
if (!s.welcomed) {
setTimeout(() => {
s.welcomed = true
require('Storage').write('ncstart.json', s)
require('Storage').write('ncstart.json', {welcomed: true})
load('ncstart.app.js')
})
}

2
apps/rndmclk/ChangeLog Normal file
View File

@ -0,0 +1,2 @@
0.01: New widget
0.02: Less invasive, change default clock setting instead of directly loading the new clock (no longer breaks Gadgetbridge notifications)

6
apps/rndmclk/README.md Normal file
View File

@ -0,0 +1,6 @@
# Summary
Random Clock is a widget that will randomly show one of the installed watch faces each time the LCD is turned on.
# How it works
Everytime the LCD is turned off, the widget randomly changes the clock. When you long press BTN 3 the next time,
you might (or might not, it's random after all) see another watch face.

BIN
apps/rndmclk/rndmclk.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

35
apps/rndmclk/widget.js Normal file
View File

@ -0,0 +1,35 @@
(() => {
let currentClock = "";
/**
* Random value between zero (inclusive) and max (exclusive)
* @param {int} max
*/
function getRandomInt(max) {
return Math.floor(Math.random() * Math.floor(max));
}
function loadRandomClock() {
// Find available clock apps (same way as in the bootloader)
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) {
var clockIndex = getRandomInt(clockApps.length);
// Only update the file if the clock really change to be nice to the FLASH mem
if (clockApps[clockIndex].src != currentClock) {
currentClock = clockApps[clockIndex].src;
settings = require("Storage").readJSON('setting.json', 1);
settings.clock = clockApps[clockIndex].src;
require("Storage").write('setting.json', settings);
}
}
}
Bangle.on('lcdPower', (on) => {
if (!on) {
loadRandomClock();
}
});
})();

View File

@ -8,3 +8,6 @@
Don't run again when settings app is updated (or absent)
Add "Run Now" option to settings
0.08: Don't overwrite existing settings on app update
0.09: Allow welcome to run after a fresh install
More useful app menu
BTN2 now goes to menu on release

View File

@ -285,7 +285,7 @@ setWatch(()=>{
if (sceneNumber == scenes.length-1) {
load();
}
}, BTN2, {repeat:true,edge:"rising"});
}, BTN2, {repeat:true,edge:"falling"});
setWatch(()=>move(-1), BTN1, {repeat:true});
(function migrateSettings(){

View File

@ -1,11 +1,8 @@
(function() {
let s = require('Storage').readJSON('welcome.json', 1)
|| require('Storage').readJSON('setting.json', 1)
|| {welcomed: true} // do NOT run if global settings are also absent
if (!s.welcomed && require('Storage').read('welcome.app.js')) {
let s = require('Storage').readJSON('welcome.json', 1) || {};
if (!s.welcomed) {
setTimeout(() => {
s.welcomed = true
require('Storage').write('welcome.json', {welcomed: "yes"})
require('Storage').write('welcome.json', {welcomed: true})
load('welcome.app.js')
})
}

View File

@ -3,12 +3,16 @@
|| require('Storage').readJSON('setting.json', 1) || {}
E.showMenu({
'': { 'title': 'Welcome App' },
'Run on Next Boot': {
'Run next boot': {
value: !settings.welcomed,
format: v => v ? 'OK' : 'No',
format: v => v ? 'Yes' : 'No',
onchange: v => require('Storage').write('welcome.json', {welcomed: !v}),
},
'Run Now': () => load('welcome.app.js'),
'Turn off & run next': () => {
require('Storage').write('welcome.json', {welcomed: false});
Bangle.off();
},
'< Back': back,
})
})

18
bin/pre-publish.sh Executable file
View File

@ -0,0 +1,18 @@
#!/bin/bash
cd `dirname $0`/..
nodejs bin/sanitycheck.js || exit 1
echo "Sanity check passed."
echo "Finding app dates..."
# Create list of:
# appid,created_time,modified_time
cd apps
for appfolder in *; do
echo "$appfolder,$(git log --follow --format=%ai -- $appfolder | tail -n 1),$(git log --follow --format=%ai -- $appfolder | head -n 1)" ;
done | grep -v _example_ | grep -v unknown.png > ../appdates.csv
cd ..
echo "Ready to publish"

View File

@ -40,6 +40,12 @@
.chip {
cursor: pointer;
}
.filter-nav {
display: inline-block;
}
.sort-nav {
float: right;
}
.tile-content { position: relative; }
.link-github {
position:absolute;
@ -88,8 +94,9 @@
</div>
<div class="container bangle-tab" id="librarycontainer">
<div>
<div class="filter-nav">
<label class="chip active" filterid="">All</label>
<label class="chip active" filterid="">Default</label>
<label class="chip" filterid="clock">Clocks</label>
<label class="chip" filterid="game">Games</label>
<label class="chip" filterid="tool">Tools</label>
@ -98,7 +105,15 @@
<label class="chip" filterid="outdoors">Outdoors</label>
<label class="chip" filterid="favourites">Favourites</label>
</div>
<div class="panel">
<div class="sort-nav hidden">
<span>Sort by:</span>
<label class="chip active" sortid="">None</label>
<label class="chip" sortid="created">New</label>
<label class="chip" sortid="modified">Updated</label>
</div>
</div>
<div class="panel" style="clear:both">
<div class="panel-header">
<div class="input-group" id="searchform">
<input class="form-input" type="text" placeholder="Keywords...">

View File

@ -1,10 +1,13 @@
if (typeof btoa==="undefined")
function btoa(d) { return Buffer.from(d).toString('base64'); }
// Converts a string into most efficient way to send to Espruino (either json, base64, or compressed base64)
function toJS(txt) {
var json = JSON.stringify(txt);
var b64 = "atob("+JSON.stringify(btoa(json))+")";
var js = b64.length < json.length ? b64 : json;
if (heatshrink) {
if (typeof heatshrink !== "undefined") {
var ua = new Uint8Array(txt.length);
for (var i=0;i<txt.length;i++) ua[i] = txt.charCodeAt(i);
var c = heatshrink.compress(ua);

View File

@ -1,6 +1,6 @@
// EspruinoTools bundle (https://github.com/espruino/EspruinoTools)
// Created with https://github.com/espruino/EspruinoWebIDE/blob/gh-pages/extras/create_espruinotools_js.sh
// Based on EspruinoWebIDE 0.73.4
// Based on EspruinoWebIDE 0.73.7
/**
Copyright 2014 Gordon Williams (gw@pur3.co.uk)
@ -23,6 +23,7 @@ var Espruino;
*
* Common processors are:
*
* jsCodeChanged - called when the code in the editor changes with {code}
* sending - sending code to Espruino (no data)
* transformForEspruino - transform code ready to be sent to Espruino
* transformModuleForEspruino({code,name})
@ -123,6 +124,7 @@ Espruino.Core.Status = {
hasProgress : function() { return false; },
incrementProgress : function(amt) {}
};
var acorn = (function(){ var exports={};var module={};
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
typeof define === 'function' && define.amd ? define(['exports'], factory) :
@ -3988,6 +3990,7 @@ exports.nonASCIIwhitespace = nonASCIIwhitespace;
Object.defineProperty(exports, '__esModule', { value: true });
})));
return exports;})();
/**
Copyright 2014 Gordon Williams (gw@pur3.co.uk)
@ -4507,8 +4510,8 @@ Object.defineProperty(exports, '__esModule', { value: true });
fileLoader.click();
}
/* Save a file with a save file dialog. callback(savedFileName) only called in chrome app case when we knopw the filename*/
function fileSaveDialog(data, filename, callback) {
// Save a file with a save file dialog
function fileSaveDialog(data, filename) {
function errorHandler() {
Espruino.Core.Notifications.error("Error Saving", true);
}
@ -4524,7 +4527,6 @@ Object.defineProperty(exports, '__esModule', { value: true });
writer.onwriteend = function(e) {
writer.onwriteend = function(e) {
console.log('FileWriter: complete');
if (callback) callback(writableFileEntry.name);
};
console.log('FileWriter: writing');
writer.write(blob);
@ -4535,8 +4537,10 @@ Object.defineProperty(exports, '__esModule', { value: true });
}, errorHandler);
});
} else {
var rawdata = new Uint8Array(data.length);
for (var i=0;i<data.length;i++) rawdata[i]=data.charCodeAt(i);
var a = document.createElement("a"),
file = new Blob([data], {type: "text/plain"});
file = new Blob([rawdata.buffer], {type: "text/plain"});
var url = URL.createObjectURL(file);
a.href = url;
a.download = filename;
@ -4765,6 +4769,16 @@ Object.defineProperty(exports, '__esModule', { value: true });
console.log("GET chrome.storage.sync = "+JSON.stringify(value));
callback(value);
});
} else if (typeof window !== 'undefined' && window.localStorage) {
var data = {};
var value = window.localStorage.getItem("CONFIG");
console.log("GET window.localStorage = "+JSON.stringify(value));
try {
data = JSON.parse(value);
} catch (e) {
console.log("Invalid config data");
}
callback(data);
} else if (typeof document != "undefined") {
var data = {};
var cookie = document.cookie;
@ -4786,8 +4800,11 @@ Object.defineProperty(exports, '__esModule', { value: true });
function _set(data) {
if (typeof chrome !== 'undefined' && chrome.storage) {
console.log("SET chrome.storage.sync = "+JSON.stringify(data));
console.log("SET chrome.storage.sync = "+JSON.stringify(data,null,2));
chrome.storage.sync.set({ CONFIGS : data });
} else if (typeof window !== 'undefined' && window.localStorage) {
console.log("SET window.localStorage = "+JSON.stringify(data,null,2));
window.localStorage.setItem("CONFIG",JSON.stringify(data));
} else if (typeof document != "undefined") {
document.cookie = "CONFIG="+btoa(JSON.stringify(data));
}
@ -4811,7 +4828,6 @@ Object.defineProperty(exports, '__esModule', { value: true });
addSection("General", { sortOrder:100, description: "General Web IDE Settings" });
addSection("Communications", { sortOrder:200, description: "Settings for communicating with the Espruino Board" });
addSection("Board", { sortOrder:300, description: "Settings for the Espruino Board itself" });
}
function add(name, options) {
@ -5076,6 +5092,7 @@ To add a new serial device, you must add an object to
}
}
var portInfo = { port:serialPort };
connectionInfo = undefined;
flowControlXOFF = false;
currentDevice = portToDevice[serialPort];
@ -5088,7 +5105,6 @@ To add a new serial device, you must add an object to
connectionInfo = cInfo;
connectedPort = serialPort;
console.log("Connected", cInfo);
var portInfo = { port:serialPort };
if (connectionInfo.portName)
portInfo.portName = connectionInfo.portName;
Espruino.callProcessor("connected", portInfo, function() {
@ -5127,8 +5143,8 @@ To add a new serial device, you must add an object to
sendingBinary = false;
flowControlXOFF = false;
Espruino.callProcessor("disconnected", undefined, function() {
disconnectCallback();
Espruino.callProcessor("disconnected", portInfo, function() {
disconnectCallback(portInfo);
});
});
};
@ -5608,9 +5624,6 @@ To add a new serial device, you must add an object to
// When code is sent to Espruino, search it for modules and add extra code required to load them
Espruino.addProcessor("transformForEspruino", function(code, callback) {
if (Espruino.Config.ROLLUP) {
return loadModulesRollup(code, callback);
}
loadModules(code, callback);
});
@ -5787,24 +5800,8 @@ To add a new serial device, you must add an object to
callback(loadedModuleData.join("\n") + "\n" + code);
});
}
}
};
function loadModulesRollup(code, callback) {
rollupTools.loadModulesRollup(code)
.then(generated => {
const minified = generated.code;
console.log('rollup: '+minified.length+' bytes');
// FIXME: needs warnings?
Espruino.Core.Notifications.info('Rollup no errors. Bundling ' + code.length + ' bytes to ' + minified.length + ' bytes');
callback(minified);
})
.catch(err => {
console.log('rollup:error', err);
Espruino.Core.Notifications.error("Rollup errors - Bundling failed: " + String(err).trim());
callback(code);
});
}
Espruino.Core.Modules = {
init : init
@ -6484,6 +6481,7 @@ To add a new serial device, you must add an object to
(function(){
if (typeof acorn == "undefined") {
console.log("pretokenise: needs acorn, disabling.");
return;
}
function init() {
@ -6587,11 +6585,12 @@ To add a new serial device, you must add an object to
var tp = "?";
if (tk.type.label=="template" || tk.type.label=="string") tp="STRING";
if (tk.type.label=="num") tp="NUMBER";
if (tk.type.keyword) tp="ID";
if (tk.type.keyword || tk.type.label=="name") tp="ID";
if (tp=="?" && tk.start+1==tk.end) tp="CHAR";
return {
startIdx : tk.start,
endIdx : tk.end,
str : code.substr(tk.start, tk.end),
str : code.substring(tk.start, tk.end),
type : tp
};
}};
@ -6802,5 +6801,7 @@ Espruino.transform = function(code, options) {
});
};
if ("undefined"==typeof document) Espruino.init();
if ("undefined"!=typeof module)
module.exports = Espruino;

View File

@ -1,5 +1,6 @@
var appJSON = []; // List of apps and info from apps.json
var appsInstalled = []; // list of app JSON
var appSortInfo = {}; // list of data to sort by, from appdates.csv { created, modified }
var files = []; // list of files on Bangle
var DEFAULTSETTINGS = {
pretokenise : true,
@ -19,6 +20,19 @@ httpGet("apps.json").then(apps=>{
refreshFilter();
});
httpGet("appdates.csv").then(csv=>{
document.querySelector(".sort-nav").classList.remove("hidden");
csv.split("\n").forEach(line=>{
var l = line.split(",");
appSortInfo[l[0]] = {
created : Date.parse(l[1]),
modified : Date.parse(l[2])
};
});
}).catch(err=>{
console.log("No recent.csv - app sort disabled");
});
// =========================================== Top Navigation
function showChangeLog(appid) {
var app = appNameToApp(appid);
@ -182,11 +196,12 @@ function showTab(tabname) {
// =========================================== Library
var chips = Array.from(document.querySelectorAll('.chip')).map(chip => chip.attributes.filterid.value);
var chips = Array.from(document.querySelectorAll('.filter-nav .chip')).map(chip => chip.attributes.filterid.value);
var hash = window.location.hash ? window.location.hash.slice(1) : '';
var activeFilter = !!~chips.indexOf(hash) ? hash : '';
var currentSearch = '';
var activeSort = '';
var currentSearch = activeFilter ? '' : hash;
function refreshFilter(){
var filtersContainer = document.querySelector("#librarycontainer .filter-nav");
@ -194,6 +209,12 @@ function refreshFilter(){
if(activeFilter) filtersContainer.querySelector('.chip[filterid="'+activeFilter+'"]').classList.add('active');
else filtersContainer.querySelector('.chip[filterid]').classList.add('active');
}
function refreshSort(){
var sortContainer = document.querySelector("#librarycontainer .sort-nav");
sortContainer.querySelector('.active').classList.remove('active');
if(activeSort) sortContainer.querySelector('.chip[sortid="'+activeSort+'"]').classList.add('active');
else sortContainer.querySelector('.chip[sortid]').classList.add('active');
}
function refreshLibrary() {
var panelbody = document.querySelector("#librarycontainer .panel-body");
var visibleApps = appJSON;
@ -202,7 +223,7 @@ function refreshLibrary() {
if (activeFilter) {
if ( activeFilter == "favourites" ) {
visibleApps = visibleApps.filter(app => app.id && (favourites.filter( e => e == app.id).length));
}else{
} else {
visibleApps = visibleApps.filter(app => app.tags && app.tags.split(',').includes(activeFilter));
}
}
@ -211,6 +232,13 @@ function refreshLibrary() {
visibleApps = visibleApps.filter(app => app.name.toLowerCase().includes(currentSearch) || app.tags.includes(currentSearch));
}
if (activeSort) {
visibleApps = visibleApps.slice(); // clone the array so sort doesn't mess with original
if (activeSort=="created" || activeSort=="modified") {
visibleApps = visibleApps.sort((a,b) => appSortInfo[b.id][activeSort] - appSortInfo[a.id][activeSort]);
} else throw new Error("Unknown sort type "+activeSort);
}
panelbody.innerHTML = visibleApps.map((app,idx) => {
var appInstalled = appsInstalled.find(a=>a.id==app.id);
var version = getVersionInfo(app, appInstalled);
@ -580,12 +608,22 @@ filtersContainer.addEventListener('click', ({ target }) => {
});
var librarySearchInput = document.querySelector("#searchform input");
librarySearchInput.value = currentSearch;
librarySearchInput.addEventListener('input', evt => {
currentSearch = evt.target.value.toLowerCase();
refreshLibrary();
});
var sortContainer = document.querySelector("#librarycontainer .sort-nav");
sortContainer.addEventListener('click', ({ target }) => {
if (target.classList.contains('active')) return;
activeSort = target.getAttribute('sortid') || '';
refreshSort();
refreshLibrary();
window.location.hash = activeFilter;
});
// =========================================== About
if (window.location.host=="banglejs.com") {

View File

@ -37,7 +37,10 @@ function httpGet(url) {
});
oReq.addEventListener("error", () => reject());
oReq.addEventListener("abort", () => reject());
oReq.open("GET", url);
oReq.open("GET", url, true);
oReq.onerror = function () {
reject("HTTP Request failed");
};
oReq.send();
});
}