mirror of https://github.com/espruino/BangleApps
commit
e975bad1ad
|
@ -3,3 +3,4 @@ node_modules
|
|||
package-lock.json
|
||||
.DS_Store
|
||||
*.js.bak
|
||||
appdates.csv
|
||||
|
|
|
@ -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)
|
||||
|
|
13
README.md
13
README.md
|
@ -6,7 +6,7 @@ Bangle.js App Loader (and Apps)
|
|||
* Try the **release version** at [banglejs.com/apps](https://banglejs.com/apps)
|
||||
* Try the **development version** at [github.io](https://espruino.github.io/BangleApps/)
|
||||
|
||||
**All software (including apps) in this repository is MIT Licensed - see [LICENSE](LICENSE)** By
|
||||
**All software (including apps) in this repository is MIT Licensed - see [LICENSE](LICENSE)** By
|
||||
submitting code to this repository you confirm that you are happy with it being MIT licensed,
|
||||
and that it is not licensed in another way that would make this impossible.
|
||||
|
||||
|
@ -203,7 +203,7 @@ and which gives information about the app for the Launcher.
|
|||
// added by BangleApps loader on upload - lists all files
|
||||
// that belong to the app so it can be deleted
|
||||
"data":"appid.data.json,appid.data?.json;appidStorageFile,appidStorageFile*"
|
||||
// added by BangleApps loader on upload - lists files that
|
||||
// added by BangleApps loader on upload - lists files that
|
||||
// the app might write, so they can be deleted on uninstall
|
||||
// typically these files are not uploaded, but created by the app
|
||||
// these can include '*' or '?' wildcards
|
||||
|
@ -251,7 +251,7 @@ and which gives information about the app for the Launcher.
|
|||
"storageFile":true // if supplied, file is treated as storageFile
|
||||
},
|
||||
{"wildcard":"appid.data.*" // wildcard of filenames used in storage
|
||||
}, // this is mutually exclusive with using "name"
|
||||
}, // this is mutually exclusive with using "name"
|
||||
],
|
||||
"sortorder" : 0, // optional - choose where in the list this goes.
|
||||
// this should only really be used to put system
|
||||
|
@ -341,9 +341,12 @@ See [apps/gpsrec/interface.html](the GPS Recorder) for a full example.
|
|||
Apps (or widgets) can add their own settings to the "Settings" menu under "App/widget settings".
|
||||
To do so, the app needs to include a `settings.js` file, containing a single function
|
||||
that handles configuring the app.
|
||||
When the app settings are opened, this function is called with one
|
||||
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
|
||||
|
@ -352,7 +355,7 @@ Example `settings.js`
|
|||
function save(key, value) {
|
||||
settings[key] = value;
|
||||
require('Storage').write('app.json',settings);
|
||||
}
|
||||
}
|
||||
const appMenu = {
|
||||
'': {'title': 'App Settings'},
|
||||
'< Back': back,
|
||||
|
|
86
apps.json
86
apps.json
|
@ -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",
|
||||
|
@ -1699,7 +1710,7 @@
|
|||
"version": "0.01",
|
||||
"description": "A clock for time travellers. The light pie segment shows the minutes, the black circle, the hour. The dial itself reads 'time' just in case you forget.",
|
||||
"tags": "clock",
|
||||
"readme": "README.md",
|
||||
"readme": "README.md",
|
||||
"type": "clock",
|
||||
"allow_emulator":true,
|
||||
"storage": [
|
||||
|
@ -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"
|
||||
}
|
||||
]
|
||||
]
|
||||
|
|
|
@ -11,4 +11,4 @@
|
|||
{"name":"7chname.app.js","url":"app.js"},
|
||||
{"name":"7chname.img","url":"app-icon.js","evaluate":true}
|
||||
]
|
||||
}
|
||||
}
|
|
@ -6,4 +6,5 @@
|
|||
0.06: Fixes widget events and charting of component states
|
||||
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.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)
|
|
@ -1,6 +1,7 @@
|
|||
(() => {
|
||||
let recordingInterval = null;
|
||||
const Storage = require("Storage");
|
||||
|
||||
|
||||
const switchableConsumers = {
|
||||
none: 0,
|
||||
lcd: 1,
|
||||
|
@ -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();
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -21,20 +21,22 @@ 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) {
|
||||
return new Promise(function(resolve) {
|
||||
if ((0|freq)<=0) freq=4000;
|
||||
if ((0|time)<=0) time=200;
|
||||
if (time>5000) time=5000;
|
||||
analogWrite(D13,0.1,{freq:freq});
|
||||
setTimeout(function() {
|
||||
digitalWrite(D13,0);
|
||||
resolve();
|
||||
}, time);
|
||||
});
|
||||
};
|
||||
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;
|
||||
if (time>5000) time=5000;
|
||||
analogWrite(D13,0.1,{freq:freq});
|
||||
setTimeout(function() {
|
||||
digitalWrite(D13,0);
|
||||
resolve();
|
||||
}, time);
|
||||
});
|
||||
};
|
||||
}
|
||||
Bangle.setLCDTimeout(s.timeout);
|
||||
if (!s.timeout) Bangle.setLCDPower(1);
|
||||
E.setTimeZone(s.timezone);
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
0.01: Create dotmatrix clock app
|
|
@ -0,0 +1,28 @@
|
|||
# Dotmatrix clock
|
||||
|
||||
A clock face simulating the classic dotmatrix displays. Shows time, date, compass, and heart rate.
|
||||
|
||||

|
||||
|
||||
## 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`
|
|
@ -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 |
|
@ -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 |
|
@ -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() {
|
||||
|
|
|
@ -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) {
|
||||
|
@ -133,21 +133,21 @@ const drawDecoration = () => {
|
|||
drawSegment(params);
|
||||
params = {
|
||||
fromX: 0.4,
|
||||
fromY: 0.2,
|
||||
fromY: 0.2,
|
||||
toX: 0.6,
|
||||
toY: 0.1
|
||||
};
|
||||
drawThickLine(params);
|
||||
params = {
|
||||
fromX: -0.2,
|
||||
fromY: -0.05,
|
||||
fromY: -0.05,
|
||||
toX: -0.7,
|
||||
toY: -0.7
|
||||
};
|
||||
drawThickLine(params);
|
||||
params = {
|
||||
fromX: -0.3,
|
||||
fromY: 0.05,
|
||||
fromY: 0.05,
|
||||
toX: -0.95,
|
||||
toY: -0.3
|
||||
};
|
||||
|
@ -166,7 +166,7 @@ const drawMinuteHand = () => {
|
|||
break;
|
||||
case "blue":
|
||||
g.setColor(0,0,1);
|
||||
break;
|
||||
break;
|
||||
case "80s":
|
||||
g.setColor(1,0,0);
|
||||
break;
|
||||
|
@ -203,7 +203,7 @@ const drawClockFace = () => {
|
|||
break;
|
||||
case "blue":
|
||||
g.setColor(0,0.3,0.8);
|
||||
break;
|
||||
break;
|
||||
case "80s":
|
||||
g.setColor(1,1,1);
|
||||
break;
|
||||
|
|
|
@ -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'},
|
||||
|
@ -21,13 +21,13 @@
|
|||
min:0,max:1,
|
||||
format: m => onoff[m],
|
||||
onchange: m => {save('widgets', m)}
|
||||
},
|
||||
},
|
||||
'Decoration': {
|
||||
value: 0|settings['decoration'],
|
||||
min:0,max:1,
|
||||
format: m => onoff[m],
|
||||
onchange: m => {save('decoration', m)}
|
||||
}
|
||||
}
|
||||
};
|
||||
E.showMenu(appMenu)
|
||||
})
|
||||
})
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
0.01: First working version
|
||||
0.02: Added delay in replying for dramatic effect
|
||||
0.03: Fixed apps.json entry
|
|
@ -0,0 +1 @@
|
|||
require("heatshrink").decompress(atob("mEwhBC/AGMrq2B1gAEwNWlYthq2s64AKGYIydFpoAEGLUrFqIADqxcXFqhiDFymBFy7GCF1owTRjCSVlYudeiGsF7/XlaNqSKBeP1mBwJxQMBReO1gaEleBMDBLN1hAC1hhBAoIwNCwQAGlZINqxvFGAIXOSBAXQN4hPBC5yQIVBxfBCAgvQSBC+NFAYRDMwJHOF654DqxkBYooALF6+sbIhkEF8Z3CRIWBR6AvXFAzvQF6wnIYQJgNd5AWNdoLoGBBAvPO5pfYH4IvUUwS/GVBzXBYCpHCq2s1mBDwKOWDwRgNPAwVVMCRLCwIABCZ6OJJSAATLxZgRACJeLAAMrFz9WFxiRgRpoADwIub1guQGDmsXhqSfRiL0G1jqkMRYxRwKLUGK2sFryVEq2B1gAEwNWFkIA/AH4A/AH4AQ"))
|
|
@ -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" });
|
Binary file not shown.
After Width: | Height: | Size: 1.5 KiB |
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
},
|
||||
|
|
|
@ -34,7 +34,7 @@
|
|||
|
||||
function onchange(v) {
|
||||
settings[btn] = v;
|
||||
s.write("largeclock.json", settings);
|
||||
s.writeJSON("largeclock.json", settings);
|
||||
}
|
||||
|
||||
const btnMenu = {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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" },
|
||||
const colors = {
|
||||
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) {
|
||||
|
|
|
@ -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);
|
||||
});
|
|
@ -0,0 +1 @@
|
|||
0.01: New App!
|
|
@ -0,0 +1 @@
|
|||
require("heatshrink").decompress(atob("mEwxH+4AA/AH4A/AAPQ64AQ44ZKBYwvc6/QF9wwFF9XXF73I4IAH44/F54vdCpYQESAYvm5Avm44ADA4Yvmc44v/F/4v/F/4v/F/4v+zmjv4ABF9GrwoACrYvn2WGFwgABSh4AV0QtDFwYvmFwlhF9wuDF9QuEF82jFw4vmMIQuFv4vuEDOi0d/WgV/0YNGBIN/LrSvCABAkECAI4GAChKBF5QABCIYEDADKpCsIvJLQQ0EGoShJMBwACSJYvEB5GiMCYxJF4LuCLorTLF6IxGQILuBMYgAGC4QwP0YwHYwQbCF4K1CL4lhCohfQMBBiCFQQvEAgIsFAATxWSQyPDAYYSIHgRgZE4IsBF4QGCCI6NRMBboFXgTTIFyYwIPYWcGAb2CCI2iF6qwBD4aqERYQABIQ+cFywALLwgAqXoYvrSAQROA=="))
|
|
@ -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 });
|
Binary file not shown.
After Width: | Height: | Size: 1.6 KiB |
|
@ -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
|
||||
|
|
|
@ -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')
|
||||
})
|
||||
}
|
||||
|
|
|
@ -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)
|
|
@ -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.
|
Binary file not shown.
After Width: | Height: | Size: 1.9 KiB |
|
@ -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();
|
||||
}
|
||||
});
|
||||
|
||||
})();
|
|
@ -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
|
||||
|
|
|
@ -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(){
|
||||
|
|
|
@ -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')
|
||||
})
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
})
|
||||
})
|
||||
|
|
|
@ -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"
|
35
index.html
35
index.html
|
@ -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,17 +94,26 @@
|
|||
</div>
|
||||
|
||||
<div class="container bangle-tab" id="librarycontainer">
|
||||
<div class="filter-nav">
|
||||
<label class="chip active" filterid="">All</label>
|
||||
<label class="chip" filterid="clock">Clocks</label>
|
||||
<label class="chip" filterid="game">Games</label>
|
||||
<label class="chip" filterid="tool">Tools</label>
|
||||
<label class="chip" filterid="widget">Widgets</label>
|
||||
<label class="chip" filterid="bluetooth">Bluetooth</label>
|
||||
<label class="chip" filterid="outdoors">Outdoors</label>
|
||||
<label class="chip" filterid="favourites">Favourites</label>
|
||||
<div>
|
||||
<div class="filter-nav">
|
||||
<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>
|
||||
<label class="chip" filterid="widget">Widgets</label>
|
||||
<label class="chip" filterid="bluetooth">Bluetooth</label>
|
||||
<label class="chip" filterid="outdoors">Outdoors</label>
|
||||
<label class="chip" filterid="favourites">Favourites</label>
|
||||
</div>
|
||||
<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">
|
||||
|
||||
<div class="panel" style="clear:both">
|
||||
<div class="panel-header">
|
||||
<div class="input-group" id="searchform">
|
||||
<input class="form-input" type="text" placeholder="Keywords...">
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
@ -4123,7 +4126,7 @@ Object.defineProperty(exports, '__esModule', { value: true });
|
|||
} else if (isIn(chNum,ch)) { // NUMBER
|
||||
type = "NUMBER";
|
||||
var chRange = chNum;
|
||||
if (ch=="0") { // Handle
|
||||
if (ch=="0") { // Handle
|
||||
s+=ch;
|
||||
nextCh();
|
||||
if ("xXoObB".indexOf(ch)>=0) {
|
||||
|
@ -4132,12 +4135,12 @@ Object.defineProperty(exports, '__esModule', { value: true });
|
|||
if (ch=="x" || ch=="X") chRange="0123456789ABCDEFabcdef";
|
||||
s+=ch;
|
||||
nextCh();
|
||||
}
|
||||
}
|
||||
}
|
||||
while (isIn(chRange,ch) || ch==".") {
|
||||
s+=ch;
|
||||
nextCh();
|
||||
}
|
||||
}
|
||||
} else if (isIn(chQuotes,ch)) { // STRING
|
||||
type = "STRING";
|
||||
var q = ch;
|
||||
|
@ -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
|
||||
|
@ -6436,18 +6433,18 @@ To add a new serial device, you must add an object to
|
|||
This Source Code is subject to the terms of the Mozilla Public
|
||||
License, v2.0. If a copy of the MPL was not distributed with this
|
||||
file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
|
||||
------------------------------------------------------------------
|
||||
Try and get any URLS that are from GitHub
|
||||
------------------------------------------------------------------
|
||||
**/
|
||||
"use strict";
|
||||
(function(){
|
||||
|
||||
|
||||
function init() {
|
||||
Espruino.addProcessor("getURL", getGitHub);
|
||||
Espruino.addProcessor("getURL", getGitHub);
|
||||
}
|
||||
|
||||
|
||||
function getGitHub(data, callback) {
|
||||
var match = data.url.match(/^https?:\/\/github.com\/([^\/]+)\/([^\/]+)\/blob\/([^\/]+)\/(.*)$/);
|
||||
if (match) {
|
||||
|
@ -6457,7 +6454,7 @@ To add a new serial device, you must add an object to
|
|||
branch : match[3],
|
||||
path : match[4]
|
||||
};
|
||||
|
||||
|
||||
var url = "https://raw.githubusercontent.com/"+git.owner+"/"+git.repo+"/"+git.branch+"/"+git.path;
|
||||
console.log("Found GitHub", JSON.stringify(git));
|
||||
callback({url: url});
|
||||
|
@ -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;
|
||||
|
||||
|
|
46
js/index.js
46
js/index.js
|
@ -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") {
|
||||
|
|
|
@ -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();
|
||||
});
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue