Merge branch 'master' into calculator
commit
f5f1059627
|
|
@ -7,3 +7,4 @@ Changed for individual apps are listed in `apps/appname/ChangeLog`
|
|||
* Added optional `README.md` file for apps
|
||||
* Remove 2v04 version warning, add links in About to official/developer versions
|
||||
* Fix issue removing an app that was just installed (Fix #253)
|
||||
* Add `Favourite` functionality
|
||||
|
|
|
|||
52
apps.json
52
apps.json
|
|
@ -119,7 +119,7 @@
|
|||
{ "id": "setting",
|
||||
"name": "Settings",
|
||||
"icon": "settings.png",
|
||||
"version":"0.12",
|
||||
"version":"0.13",
|
||||
"description": "A menu for setting up Bangle.js",
|
||||
"tags": "tool,system",
|
||||
"storage": [
|
||||
|
|
@ -915,7 +915,7 @@
|
|||
{ "id": "marioclock",
|
||||
"name": "Mario Clock",
|
||||
"icon": "marioclock.png",
|
||||
"version":"0.09",
|
||||
"version":"0.12",
|
||||
"description": "Animated retro Mario clock, with Gameboy style 8-bit grey-scale graphics.",
|
||||
"tags": "clock,mario,retro",
|
||||
"type": "clock",
|
||||
|
|
@ -1148,9 +1148,9 @@
|
|||
},
|
||||
{ "id": "batchart",
|
||||
"name": "Battery Chart",
|
||||
"shortName":"BatChart",
|
||||
"shortName":"Battery Chart",
|
||||
"icon": "app.png",
|
||||
"version":"0.03",
|
||||
"version":"0.05",
|
||||
"description": "A widget and an app for recording and visualizing battery percentage over time.",
|
||||
"tags": "app,widget,battery,time,record,chart,tool",
|
||||
"storage": [
|
||||
|
|
@ -1159,7 +1159,49 @@
|
|||
{"name":"batchart.img","url":"app-icon.js","evaluate":true}
|
||||
]
|
||||
},
|
||||
{ "id": "calculator",
|
||||
{ "id": "nato",
|
||||
"name": "NATO Alphabet",
|
||||
"shortName" : "NATOAlphabet",
|
||||
"icon": "nato.png",
|
||||
"version":"0.01",
|
||||
"type": "app",
|
||||
"description": "Learn the NATO Phonetic alphabet plus some numbers.",
|
||||
"tags": "app,learn,visual",
|
||||
"allow_emulator":true,
|
||||
"storage": [
|
||||
{"name":"nato.app.js","url":"nato.js"},
|
||||
{"name":"nato.img","url":"nato-icon.js","evaluate":true}
|
||||
]
|
||||
},
|
||||
{ "id": "numerals",
|
||||
"name": "Numerals Clock",
|
||||
"shortName": "Numerals Clock",
|
||||
"icon": "numerals.png",
|
||||
"version":"0.01",
|
||||
"description": "A simple big numerals clock",
|
||||
"tags": "numerals,clock",
|
||||
"type":"clock",
|
||||
"allow_emulator":true,
|
||||
"storage": [
|
||||
{"name":"numerals.app.js","url":"numerals.app.js"},
|
||||
{"name":"numerals.img","url":"numerals-icon.js","evaluate":true},
|
||||
{"name":"numerals.settings.js","url":"numerals.settings.js"}
|
||||
]
|
||||
},
|
||||
{ "id": "bledetect",
|
||||
"name": "BLE Detector",
|
||||
"shortName":"BLE Detector",
|
||||
"icon": "bledetect.png",
|
||||
"version":"0.02",
|
||||
"description": "Detect BLE devices and show some informations.",
|
||||
"tags": "app,bluetooth,tool",
|
||||
"readme": "README.md",
|
||||
"storage": [
|
||||
{"name":"bledetect.app.js","url":"bledetect.js"},
|
||||
{"name":"bledetect.img","url":"bledetect-icon.js","evaluate":true}
|
||||
]
|
||||
},
|
||||
{ "id": "calculator",
|
||||
"name": "Calculator",
|
||||
"shortName":"Calculator",
|
||||
"icon": "calculator.png",
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
0.01: New app and widget
|
||||
0.02: Widget stores data to file (1 dataset/10min)
|
||||
0.03: Rotate log files once a week.
|
||||
0.04: chart in the app is now active.
|
||||
0.05: Display temperature and LCD state in chart
|
||||
|
|
@ -1,20 +1,195 @@
|
|||
// place your const, vars, functions or classes here
|
||||
const GraphXZero = 40;
|
||||
const GraphYZero = 180;
|
||||
const GraphY100 = 80;
|
||||
|
||||
function renderBatteryChart(){
|
||||
g.drawString("t", 215, 175);
|
||||
g.drawLine(40,190,40,80);
|
||||
const GraphMarkerOffset = 5;
|
||||
const MaxValueCount = 144;
|
||||
const GraphXMax = GraphXZero + MaxValueCount;
|
||||
|
||||
const GraphLcdY = GraphYZero + 10;
|
||||
// const GraphCompassY = GraphYZero + 16;
|
||||
// const GraphBluetoothY = GraphYZero + 22;
|
||||
// const GraphGpsY = GraphYZero + 28;
|
||||
// const GraphHrmY = GraphYZero + 34;
|
||||
|
||||
var Storage = require("Storage");
|
||||
|
||||
function renderCoordinateSystem() {
|
||||
g.setFont("6x8", 1);
|
||||
|
||||
g.drawString("%", 39, 70);
|
||||
g.drawString("100", 15, 75);
|
||||
g.drawLine(35,80,40,80);
|
||||
// Left Y axis (Battery)
|
||||
g.setColor(1, 1, 0);
|
||||
g.drawLine(GraphXZero, GraphYZero + GraphMarkerOffset, GraphXZero, GraphY100);
|
||||
g.drawString("%", 39, GraphY100 - 10);
|
||||
|
||||
g.drawString("50", 20,125);
|
||||
g.drawLine(35,130,40,130);
|
||||
g.setFontAlign(1, -1, 0);
|
||||
g.drawString("100", 30, GraphY100 - GraphMarkerOffset);
|
||||
g.drawLine(GraphXZero - GraphMarkerOffset, GraphY100, GraphXZero, GraphY100);
|
||||
|
||||
g.drawString("0", 25, 175);
|
||||
g.drawLine(35,180,210,180);
|
||||
g.drawString("50", 30, GraphYZero - 50 - GraphMarkerOffset);
|
||||
g.drawLine(GraphXZero - GraphMarkerOffset, 130, GraphXZero, 130);
|
||||
|
||||
g.drawString("Chart not yet functional", 60, 125);
|
||||
g.drawString("0", 30, GraphYZero - GraphMarkerOffset);
|
||||
|
||||
g.setColor(1,1,1);
|
||||
g.setFontAlign(1, -1, 0);
|
||||
g.drawLine(GraphXZero - GraphMarkerOffset, GraphYZero, GraphXMax + GraphMarkerOffset, GraphYZero);
|
||||
|
||||
// Right Y axis (Temperature)
|
||||
g.setColor(0.4, 0.4, 1);
|
||||
g.drawLine(GraphXMax, GraphYZero + GraphMarkerOffset, GraphXMax, GraphY100);
|
||||
g.drawString("°C", GraphXMax + GraphMarkerOffset, GraphY100 - 10);
|
||||
g.setFontAlign(-1, -1, 0);
|
||||
g.drawString("20", GraphXMax + 2 * GraphMarkerOffset, GraphYZero - GraphMarkerOffset);
|
||||
|
||||
g.drawLine(GraphXMax + GraphMarkerOffset, 130, GraphXMax, 130);
|
||||
g.drawString("30", GraphXMax + 2 * GraphMarkerOffset, GraphYZero - 50 - GraphMarkerOffset);
|
||||
|
||||
g.drawLine(GraphXMax + GraphMarkerOffset, 80, GraphXMax, 80);
|
||||
g.drawString("40", GraphXMax + 2 * GraphMarkerOffset, GraphY100 - GraphMarkerOffset);
|
||||
|
||||
g.setColor(1,1,1);
|
||||
}
|
||||
|
||||
function decrementDay(dayToDecrement) {
|
||||
return dayToDecrement === 0 ? 6 : dayToDecrement-1;
|
||||
}
|
||||
|
||||
function loadData() {
|
||||
const startingDay = new Date().getDay();
|
||||
|
||||
// Load data for the current day
|
||||
let logFileName = "bclog" + startingDay;
|
||||
|
||||
let dataLines = loadLinesFromFile(MaxValueCount, logFileName);
|
||||
|
||||
// Top up to MaxValueCount from previous days as required
|
||||
let previousDay = decrementDay(startingDay);
|
||||
while (dataLines.length < MaxValueCount
|
||||
&& previousDay !== startingDay) {
|
||||
|
||||
let topUpLogFileName = "bclog" + previousDay;
|
||||
let remainingLines = MaxValueCount - dataLines.length;
|
||||
let topUpLines = loadLinesFromFile(remainingLines, topUpLogFileName);
|
||||
dataLines = topUpLines.concat(dataLines);
|
||||
|
||||
previousDay = decrementDay(previousDay);
|
||||
}
|
||||
|
||||
return dataLines;
|
||||
}
|
||||
|
||||
function loadLinesFromFile(requestedLineCount, fileName) {
|
||||
let allLines = [];
|
||||
let returnLines = [];
|
||||
|
||||
var readFile = Storage.open(fileName, "r");
|
||||
|
||||
while ((nextLine = readFile.readLine())) {
|
||||
if(nextLine) {
|
||||
allLines.push(nextLine);
|
||||
}
|
||||
}
|
||||
|
||||
readFile = null;
|
||||
|
||||
if (allLines.length <= 0) return;
|
||||
|
||||
let linesToReadCount = Math.min(requestedLineCount, allLines.length);
|
||||
let startingLineIndex = Math.max(0, allLines.length - requestedLineCount - 1);
|
||||
|
||||
for (let i = startingLineIndex; i < linesToReadCount + startingLineIndex; i++) {
|
||||
if(allLines[i]) {
|
||||
returnLines.push(allLines[i]);
|
||||
}
|
||||
}
|
||||
|
||||
allLines = null;
|
||||
|
||||
return returnLines;
|
||||
}
|
||||
|
||||
function renderData(dataArray) {
|
||||
const switchableConsumers = {
|
||||
none: 0,
|
||||
lcd: 1,
|
||||
compass: 2,
|
||||
bluetooth: 4,
|
||||
gps: 8,
|
||||
hrm: 16
|
||||
};
|
||||
|
||||
//const timestampIndex = 0;
|
||||
const batteryIndex = 1;
|
||||
const temperatureIndex = 2;
|
||||
const switchabelsIndex = 3;
|
||||
|
||||
var allConsumers = switchableConsumers.none | switchableConsumers.lcd | switchableConsumers.compass | switchableConsumers.bluetooth | switchableConsumers.gps | switchableConsumers.hrm;
|
||||
|
||||
for (let i = 0; i < dataArray.length; i++) {
|
||||
const element = dataArray[i];
|
||||
|
||||
var dataInfo = element.split(",");
|
||||
|
||||
// Battery percentage
|
||||
g.setColor(1, 1, 0);
|
||||
g.setPixel(GraphXZero + i, GraphYZero - parseInt(dataInfo[batteryIndex]));
|
||||
|
||||
// Temperature
|
||||
g.setColor(0.4, 0.4, 1);
|
||||
let scaledTemp = Math.floor(((parseFloat(dataInfo[temperatureIndex]) * 100) - 2000)/20) + ((((parseFloat(dataInfo[temperatureIndex]) * 100) - 2000) % 100)/25);
|
||||
|
||||
g.setPixel(GraphXZero + i, GraphYZero - scaledTemp);
|
||||
|
||||
// LCD state
|
||||
if (parseInt(dataInfo[switchabelsIndex]) & switchableConsumers.lcd == switchableConsumers.lcd) {
|
||||
g.setColor(1, 1, 1);
|
||||
g.setFontAlign(1, -1, 0);
|
||||
g.drawString("LCD", GraphXZero - GraphMarkerOffset, GraphLcdY - 2, true);
|
||||
g.drawLine(GraphXZero + i, GraphLcdY, GraphXZero + i, GraphLcdY + 1);
|
||||
}
|
||||
|
||||
// // Compass state
|
||||
// if (switchables & switchableConsumers.lcd == switchableConsumers.lcd) {
|
||||
// g.setColor(0, 1, 0);
|
||||
// g.setFontAlign(-1, -1, 0);
|
||||
// g.drawString("Compass", GraphXMax + GraphMarkerOffset, GraphCompassY - 2, true);
|
||||
// g.drawLine(GraphXZero + i, GraphCompassY, GraphXZero + i, GraphCompassY + 1);
|
||||
// }
|
||||
|
||||
// // Bluetooth state
|
||||
// if (switchables & switchableConsumers.lcd == switchableConsumers.lcd) {
|
||||
// g.setColor(0, 0, 1);
|
||||
// g.setFontAlign(1, -1, 0);
|
||||
// g.drawString("BLE", GraphXZero - GraphMarkerOffset, GraphBluetoothY - 2, true);
|
||||
// g.drawLine(GraphXZero + i, GraphBluetoothY, GraphXZero + i, GraphBluetoothY + 1);
|
||||
// }
|
||||
|
||||
// // Gps state
|
||||
// if (switchables & switchableConsumers.lcd == switchableConsumers.lcd) {
|
||||
// g.setColor(0.8, 0.5, 0.24);
|
||||
// g.setFontAlign(-1, -1, 0);
|
||||
// g.drawString("GPS", GraphXMax + GraphMarkerOffset, GraphGpsY - 2, true);
|
||||
// g.drawLine(GraphXZero + i, GraphGpsY, GraphXZero + i, GraphGpsY + 1);
|
||||
// }
|
||||
|
||||
// // Hrm state
|
||||
// if (switchables & switchableConsumers.lcd == switchableConsumers.lcd) {
|
||||
// g.setColor(1, 0, 0);
|
||||
// g.setFontAlign(1, -1, 0);
|
||||
// g.drawString("HRM", GraphXZero - GraphMarkerOffset, GraphHrmY - 2, true);
|
||||
// g.drawLine(GraphXZero + i, GraphHrmY, GraphXZero + i, GraphHrmY + 1);
|
||||
// }
|
||||
}
|
||||
|
||||
dataArray = null;
|
||||
}
|
||||
|
||||
function renderBatteryChart() {
|
||||
renderCoordinateSystem();
|
||||
let data = loadData();
|
||||
renderData(data);
|
||||
data = null;
|
||||
}
|
||||
|
||||
// special function to handle display switch on
|
||||
|
|
@ -22,6 +197,9 @@ Bangle.on('lcdPower', (on) => {
|
|||
if (on) {
|
||||
// call your app function here
|
||||
// If you clear the screen, do Bangle.drawWidgets();
|
||||
g.clear()
|
||||
Bangle.loadWidgets();
|
||||
Bangle.drawWidgets();
|
||||
renderBatteryChart();
|
||||
}
|
||||
});
|
||||
|
|
|
|||
|
|
@ -60,7 +60,6 @@
|
|||
}
|
||||
}
|
||||
|
||||
// Called by the heart app to reload settings and decide what's
|
||||
function reload() {
|
||||
WIDGETS["batchart"].width = 24;
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,2 @@
|
|||
0.01: New App!
|
||||
0.02: Fixed issue with wrong device informations
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
# BLE Detector
|
||||
|
||||
BLE Detector it's an app born for testing purpose that aim to show as informations as possible about near BLE devices.
|
||||
|
||||
## Features
|
||||
|
||||
BLE Detector shows:
|
||||
|
||||
- Device name (if available)
|
||||
- Received Signal Strength Indication (RSSI)
|
||||
- Manufacturer
|
||||
- MAC Address
|
||||
|
||||
More informations will coming with future versions.
|
||||
|
|
@ -0,0 +1 @@
|
|||
require("heatshrink").decompress(atob("mEwwgJGhGAEKuIxAXXGCoXBGCoXCDCgXDJKYXDGCYUBhAwUFgQwPEogTCGBwNFFYYYNHwoEGJJQlFCIgKCdR4XHJBQNEI6IOFO6IPEDQYGDahoYEa6BJFxBFPJJIuQGAouRGAoWSGAgXTSIoAEgUgL6cCkQACDJCOFGAYWDAAJFLX4gWFGA4sFC40gJQYuHwBEDAQISCMYowEFgoJDCAwYBAwZYEC45AEgIHERAgXMA4i4FC6bPDC4hXFC5B7FC57CHI54XIawgXRVwS/JC5SuDC4wGGC45HBFAQRCAooXIVwYRBAAoXLLIwAFC5IuDGCIuFDAyQLABphKABgwaC6owB"))
|
||||
|
|
@ -0,0 +1,59 @@
|
|||
let menu = {
|
||||
"": { "title": "BLE Detector" },
|
||||
"RE-SCAN": () => scan()
|
||||
};
|
||||
|
||||
function showMainMenu() {
|
||||
menu["< Back"] = () => load();
|
||||
return E.showMenu(menu);
|
||||
}
|
||||
|
||||
function showDeviceInfo(device){
|
||||
const deviceMenu = {
|
||||
"": { "title": "Device Info" },
|
||||
"name": {
|
||||
value: device.name
|
||||
},
|
||||
"rssi": {
|
||||
value: device.rssi
|
||||
},
|
||||
"manufacturer": {
|
||||
value: device.manufacturer
|
||||
}
|
||||
};
|
||||
|
||||
deviceMenu[device.id] = () => {};
|
||||
deviceMenu["< Back"] = () => showMainMenu();
|
||||
|
||||
return E.showMenu(deviceMenu);
|
||||
}
|
||||
|
||||
function scan() {
|
||||
menu = {
|
||||
"": { "title": "BLE Detector" },
|
||||
"RE-SCAN": () => scan()
|
||||
};
|
||||
|
||||
waitMessage();
|
||||
|
||||
NRF.findDevices(devices => {
|
||||
devices.forEach(device =>{
|
||||
let deviceName = device.id.substring(0,17);
|
||||
|
||||
if (device.name) {
|
||||
deviceName = device.name;
|
||||
}
|
||||
|
||||
menu[deviceName] = () => showDeviceInfo(device);
|
||||
});
|
||||
showMainMenu(menu);
|
||||
}, { active: true });
|
||||
}
|
||||
|
||||
function waitMessage() {
|
||||
E.showMenu();
|
||||
E.showMessage("scanning");
|
||||
}
|
||||
|
||||
scan();
|
||||
waitMessage();
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 4.1 KiB |
|
|
@ -6,4 +6,7 @@
|
|||
0.06: Performance refactor, and enhanced graphics!
|
||||
0.07: Swipe right to change between Mario and Toad characters, swipe left to toggle night mode
|
||||
0.08: Update date panel to be info panel toggling between Date, Battery and Temperature. Add Princes Daisy
|
||||
0.09: Add GadgetBridge functionality. Mario shows message type in speach bubble, while message scrolls in info panel
|
||||
0.09: Add GadgetBridge functionality. Mario shows message type in speach bubble, while message scrolls in info panel
|
||||
0.10: Swiping left to enable night-mode now also reduces LCD brightness through 3 levels before returning to day-mode.
|
||||
0.11: User settings persisted and read to file.
|
||||
0.12: Add info banner message when phone (dis)connects. Display low-battery warning (<=10%)
|
||||
|
|
@ -8,7 +8,7 @@ Enjoy watching Mario, or one of the other game characters run through a level wh
|
|||
## Features
|
||||
|
||||
* Multiple characters - swipe the screen right to change the character between `Mario`, `Toad`, and `Daisy`
|
||||
* Night and Day modes - swipe left to toggle mode
|
||||
* Night and Day modes - swipe left to enter night mode, with 3 levels of darkness before returning to day mode.
|
||||
* Smooth animation
|
||||
* Awesome 8-bit style grey-scale graphics
|
||||
* Mario jumps to change the time, every minute
|
||||
|
|
|
|||
|
|
@ -16,6 +16,8 @@ const is12Hour = settings["12hour"] || false;
|
|||
|
||||
// Screen dimensions
|
||||
let W, H;
|
||||
// Screen brightness
|
||||
let brightness = 1;
|
||||
|
||||
let intervalRef, displayTimeoutRef = null;
|
||||
|
||||
|
|
@ -79,6 +81,16 @@ const phone = {
|
|||
messageType: null,
|
||||
};
|
||||
|
||||
const SETTINGS_FILE = "marioclock.json";
|
||||
|
||||
function readSettings() {
|
||||
return require('Storage').readJSON(SETTINGS_FILE, 1) || {};
|
||||
}
|
||||
|
||||
function writeSettings(newSettings) {
|
||||
require("Storage").writeJSON(SETTINGS_FILE, newSettings);
|
||||
}
|
||||
|
||||
function phoneOutbound(msg) {
|
||||
Bluetooth.println(JSON.stringify(msg));
|
||||
}
|
||||
|
|
@ -164,7 +176,17 @@ function switchCharacter() {
|
|||
}
|
||||
|
||||
function toggleNightMode() {
|
||||
nightMode = !nightMode;
|
||||
if (!nightMode) {
|
||||
nightMode = true;
|
||||
return;
|
||||
}
|
||||
|
||||
brightness -= 0.30;
|
||||
if (brightness <= 0) {
|
||||
brightness = 1;
|
||||
nightMode = false;
|
||||
}
|
||||
Bangle.setLCDBrightness(brightness);
|
||||
}
|
||||
|
||||
function incrementTimer() {
|
||||
|
|
@ -324,16 +346,20 @@ function drawToadFrame(idx, x, y) {
|
|||
function drawNotice(x, y) {
|
||||
if (phone.message === null) return;
|
||||
|
||||
let img;
|
||||
switch (phone.messageType) {
|
||||
case "call":
|
||||
const callImg = require("heatshrink").decompress(atob("h8PxH+AAMHABIND6wAJB4INEw9cAAIPFBxAPEBw/WBxYACDrQ7QLI53OSpApDBoQAHB4INLByANNAwo="));
|
||||
g.drawImage(callImg, characterSprite.x, characterSprite.y - 16);
|
||||
img = require("heatshrink").decompress(atob("h8PxH+AAMHABIND6wAJB4INEw9cAAIPFBxAPEBw/WBxYACDrQ7QLI53OSpApDBoQAHB4INLByANNAwo="));
|
||||
break;
|
||||
case "notify":
|
||||
const msgImg = require("heatshrink").decompress(atob("h8PxH+AAMHABIND6wAJB4INCrgAHB4QOEDQgOIAIQFGBwovDA4gOGFooOVLJR3OSpApDBoQAHB4INLByANNAwoA="));
|
||||
g.drawImage(msgImg, characterSprite.x, characterSprite.y - 16);
|
||||
img = require("heatshrink").decompress(atob("h8PxH+AAMHABIND6wAJB4INCrgAHB4QOEDQgOIAIQFGBwovDA4gOGFooOVLJR3OSpApDBoQAHB4INLByANNAwoA="));
|
||||
break;
|
||||
case "lowBatt":
|
||||
img = require("heatshrink").decompress(atob("h8PxH+AAMHABIND6wAJB4INFrgABB4oOEBoQPFBwwDGB0uHAAIOLJRB3OSpApDBoQAHB4INLByANNAwo"));
|
||||
break;
|
||||
}
|
||||
|
||||
if (img) g.drawImage(img, characterSprite.x, characterSprite.y - 16);
|
||||
}
|
||||
|
||||
function drawCharacter(date, character) {
|
||||
|
|
@ -555,8 +581,39 @@ function startTimers(){
|
|||
redraw();
|
||||
}
|
||||
|
||||
function loadSettings() {
|
||||
const settings = readSettings();
|
||||
if (!settings) return;
|
||||
|
||||
if (settings.character) characterSprite.character = settings.character;
|
||||
if (settings.nightMode) nightMode = settings.nightMode;
|
||||
if (settings.brightness) {
|
||||
brightness = settings.brightness;
|
||||
Bangle.setLCDBrightness(brightness);
|
||||
}
|
||||
}
|
||||
|
||||
function updateSettings() {
|
||||
const newSettings = {
|
||||
character: characterSprite.character,
|
||||
nightMode: nightMode,
|
||||
brightness: brightness,
|
||||
};
|
||||
writeSettings(newSettings);
|
||||
}
|
||||
|
||||
function checkBatteryLevel() {
|
||||
if (Bangle.isCharging()) return;
|
||||
if (E.getBattery() > 10) return;
|
||||
if (phone.message !== null) return;
|
||||
|
||||
phoneNewMessage("lowBatt", "Warning, battery is low");
|
||||
}
|
||||
|
||||
// Main
|
||||
function init() {
|
||||
loadSettings();
|
||||
|
||||
clearInterval();
|
||||
|
||||
// Initialise display
|
||||
|
|
@ -606,23 +663,31 @@ function init() {
|
|||
default:
|
||||
toggleNightMode();
|
||||
}
|
||||
|
||||
updateSettings();
|
||||
});
|
||||
|
||||
// Phone connectivity
|
||||
try { NRF.wake(); } catch (e) {}
|
||||
|
||||
NRF.on('disconnect', () => Bangle.buzz());
|
||||
NRF.on('disconnect', () => {
|
||||
phoneNewMessage(null, "Phone disconnected");
|
||||
});
|
||||
|
||||
NRF.on('connect', () => {
|
||||
setTimeout(() => {
|
||||
phoneOutbound({ t: "status", bat: E.getBattery() });
|
||||
}, ONE_SECOND * 2);
|
||||
Bangle.buzz();
|
||||
phoneNewMessage(null, "Phone connected");
|
||||
});
|
||||
|
||||
GB = (evt) => phoneInbound(evt);
|
||||
|
||||
startTimers();
|
||||
|
||||
setInterval(checkBatteryLevel, ONE_SECOND * 60 * 10);
|
||||
checkBatteryLevel();
|
||||
}
|
||||
|
||||
// Initialise!
|
||||
init()
|
||||
init();
|
||||
|
|
@ -0,0 +1 @@
|
|||
0.01: New App!
|
||||
|
|
@ -0,0 +1 @@
|
|||
require("heatshrink").decompress(atob("mEwwgFCiIABiAGFiINJAAUS///CAgGEgMT//zBoYXFmIiCC40fEooXF+QXJn4lCC5ARDC4oFC//xMAoXDJAQXFBgY9DC4wKCC4p2CPA4XDCQQXEOwXxPA4XBEQJICC4p2BmICCC44KBJAIXEiIJBkMvPAwXCWgYXFAgQMBPAoXCBwUxC4jtDeI4XDJAQXDFYXxHAoXGJAYXDLYPykUieIwXDJAYXDG4IAEPAgXCRgJICPYoAEPAgXDZ4TcDmYXGMAgXDUAZiEPwIABCALEBC5BZC+YQCRwRsEC45ID+S5BCAkBEYJ4DC4hID+IbCIAYjCCIYXGEgMxXoJwEgI3CA4JQDAAwaBmQGDFIQ3CC5UzkSLBdwIIDmYXCWY4jBCAJBCPYQ0EC5bXGkLuDC5QtEAAXzPoZMCmZwB+YFCbYkykQFCVoZMDWALnDQwRjDeoZIDZAgJCWwYeBFATWFC5LuHawgXKdwyJDD4YXIOAMzH4gICmIXKEwQXXkQXFKAKQFC85HNO64XDU44XMX48Sa5zvCmJICA4YXLE4fziIACJ4PyM4gXHCAQwBCwI2GC5JADAApGFC5ERmYWFFwwXHDARJCMgYWFB4MTmYiFLgMjCwMyiIuGE4QABNIyPDBQgA=="))
|
||||
|
|
@ -0,0 +1,106 @@
|
|||
// Teach a user the NATO Phonetic Alphabet + numbers
|
||||
// Based on the Morse Code app
|
||||
|
||||
const FONT_NAME = 'Vector12';
|
||||
const FONT_SIZE = 80;
|
||||
const SCREEN_PIXELS = 240;
|
||||
const UNIT = 100;
|
||||
const NATO_MAP = {
|
||||
A: 'ALFA',
|
||||
B: 'BRAVO',
|
||||
C: 'CHARLIE',
|
||||
D: 'DELTA',
|
||||
E: 'ECHO',
|
||||
F: 'FOXTROT',
|
||||
G: 'GOLF',
|
||||
H: 'HOTEL',
|
||||
I: 'INDIA',
|
||||
J: 'JULIETT',
|
||||
K: 'KILO',
|
||||
L: 'LIMA',
|
||||
M: 'MIKE',
|
||||
N: 'NOVEMBER',
|
||||
O: 'OSCAR',
|
||||
P: 'PAPA',
|
||||
Q: 'QUEBEC',
|
||||
R: 'ROMEO',
|
||||
S: 'SIERRA',
|
||||
T: 'TANGO',
|
||||
U: 'UNIFORM',
|
||||
V: 'VICTOR',
|
||||
W: 'WHISKEY',
|
||||
X: 'X-RAY',
|
||||
Y: 'YANKEE',
|
||||
Z: 'ZULU',
|
||||
'0': 'ZE-RO',
|
||||
'1': 'WUN',
|
||||
'2': 'TOO',
|
||||
'3': 'TREE',
|
||||
'4': 'FOW-ER',
|
||||
'5': 'FIFE',
|
||||
'6': 'SIX',
|
||||
'7': 'SEV-EN',
|
||||
'8': 'AIT',
|
||||
'9': 'NIN-ER',
|
||||
};
|
||||
|
||||
let INDEX = 0;
|
||||
let showLetter = true;
|
||||
|
||||
const writeText = (txt) => {
|
||||
g.clear();
|
||||
g.setFont(FONT_NAME, FONT_SIZE);
|
||||
|
||||
var width = g.stringWidth(txt);
|
||||
|
||||
// Fit text to screen
|
||||
var fontFix = FONT_SIZE;
|
||||
while(width > SCREEN_PIXELS-10){
|
||||
fontFix--;
|
||||
g.setFont(FONT_NAME, fontFix);
|
||||
width = g.stringWidth(txt);
|
||||
}
|
||||
g.drawString(txt, (SCREEN_PIXELS / 2) - (width / 2), SCREEN_PIXELS / 2);
|
||||
};
|
||||
const writeLetter = () => {
|
||||
writeText(Object.keys(NATO_MAP)[INDEX]);
|
||||
};
|
||||
const writeCode = () => {
|
||||
writeText(NATO_MAP[Object.keys(NATO_MAP)[INDEX]]);
|
||||
};
|
||||
const toggle = () => {
|
||||
showLetter = !showLetter;
|
||||
if(showLetter){
|
||||
writeLetter();
|
||||
}else {
|
||||
writeCode();
|
||||
}
|
||||
};
|
||||
|
||||
// Bootstrapping
|
||||
|
||||
g.clear();
|
||||
g.setFont(FONT_NAME, FONT_SIZE);
|
||||
g.setColor(0, 1, 0);
|
||||
g.setFontAlign(-1, 0, 0);
|
||||
|
||||
|
||||
const step = (positive) => () => {
|
||||
if (positive) {
|
||||
INDEX = INDEX + 1;
|
||||
if (INDEX > Object.keys(NATO_MAP).length - 1) INDEX = 0;
|
||||
} else {
|
||||
INDEX = INDEX - 1;
|
||||
if (INDEX < 0) INDEX = Object.keys(NATO_MAP).length - 1;
|
||||
}
|
||||
showLetter = true; // for toggle()
|
||||
writeLetter();
|
||||
};
|
||||
|
||||
writeLetter();
|
||||
|
||||
// Press the middle button to see the NATO Phonetic wording
|
||||
setWatch(toggle, BTN2, { repeat: true });
|
||||
// Allow user to switch between letters
|
||||
setWatch(step(true), BTN1, { repeat: true });
|
||||
setWatch(step(false), BTN3, { repeat: true });
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 2.0 KiB |
|
|
@ -0,0 +1 @@
|
|||
0.01: New App!
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
# Numerals Clock
|
||||
|
||||
This is a simple big numerals clock.
|
||||
Settings can be accessed through the app/widget settings menu of the Bangle.js
|
||||
|
||||
## Settings available
|
||||
|
||||
### color:
|
||||
* rnd - shows numerals in different color combinations every time the watches wakes
|
||||
* r/g - red/green
|
||||
* y/w - yellow/white
|
||||
* o/c - orange/cyan
|
||||
* b/y - blue/yellow'ish
|
||||
|
||||
### draw mode
|
||||
* fill - fill numerals
|
||||
* frame - only shows outline of numerals
|
||||
|
|
@ -0,0 +1 @@
|
|||
require("heatshrink").decompress(atob("mEwwhC/ABMBzIADyAJIAAkQBoMZBIoXCBIwADyIkIGAIuKGAQkIBJIwEEKQANC/4XWR58RiIHFWpAXFe4QRFcpAXFewQRFcxAXEFwQwGA4QXKiAXDGAgX/C/4X/C/4X/C7uQCwcBBwYXNBwYuEC54wCFwgXPzMRiIHFC54AHC/4XiCAoXRhIHDyK3GAAwOBJA0QG45VGC4YwCD4YwKFwgABcgIfEAwIAHBwgA/AAgA=="))
|
||||
|
|
@ -0,0 +1,93 @@
|
|||
/**
|
||||
* Bangle.js Numerals Clock
|
||||
*
|
||||
* + Original Author: Raik M. https://github.com/ps-igel
|
||||
* + Created: April 2020
|
||||
* + see README.md for details
|
||||
*/
|
||||
|
||||
var numerals = {
|
||||
0:[[9,1,82,1,90,9,90,82,82,90,9,90,1,82,1,9,9,1],[30,21,61,21,69,29,69,61,61,69,30,69,22,61,22,29,30,21]],
|
||||
1:[[59,1,82,1,90,9,90,82,82,90,73,90,65,82,65,27,59,27,51,19,51,9,59,1]],
|
||||
2:[[9,1,82,1,90,9,90,47,82,55,21,55,21,64,82,64,90,72,90,82,82,90,9,90,1,82,1,43,9,35,70,35,70,25,9,25,1,17,1,9,9,1]],
|
||||
3:[[9,1,82,1,90,9,90,82,82,90,9,90,1,82,1,74,9,66,70,66,70,57,9,57,1,49,1,41,9,33,70,33,70,25,9,25,1,17,1,9,9,1]],
|
||||
4:[[9,1,14,1,22,9,22,34,69,34,69,9,77,1,82,1,90,9,90,82,82,90,78,90,70,82,70,55,9,55,1,47,1,9,9,1]],
|
||||
5:[[9,1,82,1,90,9,90,17,82,25,21,25,21,35,82,35,90,43,90,82,82,90,9,90,1,82,1,72,9,64,71,64,71,55,9,55,1,47,1,9,9,1]],
|
||||
6:[[9,1,82,1,90,9,90,14,82,22,22,22,22,36,82,36,90,44,90,82,82,90,9,90,1,82,1,9,9,1],[22,55,69,55,69,69,22,69,22,55]],
|
||||
7:[[9,1,82,1,90,9,90,15,15,90,9,90,1,82,1,76,54,23,9,23,1,15,1,9,9,1]],
|
||||
8:[[9,1,82,1,90,9,90,82,82,90,9,90,1,82,1,9,9,1],[22,22,69,22,69,36,22,36,22,22],[22,55,69,55,69,69,22,69,22,55]],
|
||||
9:[[9,1,82,1,90,9,90,82,82,90,9,90,1,82,1,77,9,69,69,69,69,55,9,55,1,47,1,9,9,1],[22,22,69,22,69,36,22,36,22,22]],
|
||||
};
|
||||
var _hCol = ["#ff5555","#ffff00","#FF9901","#2F00FF"];
|
||||
var _mCol = ["#55ff55","#ffffff","#00EFEF","#FFBF00"];
|
||||
var _rCol = 0;
|
||||
var interval = 0;
|
||||
const REFRESH_RATE = 10E3;
|
||||
|
||||
function translate(tx, ty, p) {
|
||||
return p.map((x, i)=> x+((i%2)?ty:tx));
|
||||
}
|
||||
|
||||
function fill(poly){
|
||||
return g.fillPoly(poly);
|
||||
}
|
||||
|
||||
function frame(poly){
|
||||
return g.drawPoly(poly);
|
||||
}
|
||||
|
||||
let settings = require('Storage').readJSON('numerals.json',1);
|
||||
if (!settings) {
|
||||
settings = {
|
||||
color: 0,
|
||||
drawMode: "fill"
|
||||
};
|
||||
}
|
||||
|
||||
function drawNum(num,col,x,y,func){
|
||||
g.setColor(col);
|
||||
let tx = x*100+35;
|
||||
let ty = y*100+35;
|
||||
for (let i=0;i<numerals[num].length;i++){
|
||||
if (i>0) g.setColor((func==fill)?"#000000":col);
|
||||
func(translate(tx, ty,numerals[num][i]));
|
||||
}
|
||||
}
|
||||
|
||||
function draw(drawMode){
|
||||
let d = new Date();
|
||||
let h1 = Math.floor(d.getHours()/10);
|
||||
let h2 = d.getHours()%10;
|
||||
let m1 = Math.floor(d.getMinutes()/10);
|
||||
let m2 = d.getMinutes()%10;
|
||||
g.clearRect(0,24,240,240);
|
||||
drawNum(h1,_hCol[_rCol],0,0,eval(drawMode));
|
||||
drawNum(h2,_hCol[_rCol],1,0,eval(drawMode));
|
||||
drawNum(m1,_mCol[_rCol],0,1,eval(drawMode));
|
||||
drawNum(m2,_mCol[_rCol],1,1,eval(drawMode));
|
||||
}
|
||||
|
||||
Bangle.setLCDMode();
|
||||
|
||||
clearWatch();
|
||||
setWatch(Bangle.showLauncher, BTN1, {repeat:false,edge:"falling"});
|
||||
|
||||
g.clear();
|
||||
clearInterval();
|
||||
if (settings.color>0) _rCol=settings.color-1;
|
||||
interval=setInterval(draw, REFRESH_RATE, settings.drawMode);
|
||||
draw(settings.drawMode);
|
||||
|
||||
Bangle.on('lcdPower', function(on) {
|
||||
if (on) {
|
||||
if (settings.color==0) _rCol = Math.floor(Math.random()*_hCol.length);
|
||||
draw(settings.drawMode);
|
||||
interval=setInterval(draw, REFRESH_RATE, settings.drawMode);
|
||||
}else
|
||||
{
|
||||
clearInterval(interval);
|
||||
}
|
||||
});
|
||||
|
||||
Bangle.loadWidgets();
|
||||
Bangle.drawWidgets();
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 1.1 KiB |
|
|
@ -0,0 +1,33 @@
|
|||
(function(back) {
|
||||
function updateSettings() {
|
||||
storage.write('numerals.json', numeralsSettings);
|
||||
};
|
||||
function resetSettings() {
|
||||
numeralsSettings = {
|
||||
color: 0,
|
||||
drawMode: "fill"
|
||||
};
|
||||
updateSettings();
|
||||
}
|
||||
let numeralsSettings = storage.readJSON('numerals.json',1);
|
||||
if (!numeralsSettings) resetSettings();
|
||||
let dm = ["fill","frame"];
|
||||
let col = ["rnd","r/g","y/w","o/c","b/y"]
|
||||
var menu={
|
||||
"" : { "title":"Numerals"},
|
||||
"Colors": {
|
||||
value: 0|numeralsSettings.color,
|
||||
min:0,max:4,
|
||||
format: v=>col[v],
|
||||
onchange: v=> { numeralsSettings.color=v; updateSettings();}
|
||||
},
|
||||
"Draw mode": {
|
||||
value: 0|dm.indexOf(numeralsSettings.drawMode),
|
||||
min:0,max:1,
|
||||
format: v=>dm[v],
|
||||
onchange: v=> { numeralsSettings.drawMode=dm[v]; updateSettings();}
|
||||
},
|
||||
"< back": back
|
||||
};
|
||||
E.showMenu(menu);
|
||||
})
|
||||
|
|
@ -12,3 +12,6 @@
|
|||
0.12: Fix memory leak (#206)
|
||||
Bring App settings nearer the top
|
||||
Move LCD Timeout to wakeup menu
|
||||
0.13: Fix memory leak for App settings
|
||||
Make capitalization more consistent
|
||||
Move LCD Brightness menu into more general LCD menu
|
||||
|
|
@ -64,7 +64,7 @@ function showMainMenu() {
|
|||
const mainmenu = {
|
||||
'': { 'title': 'Settings' },
|
||||
'Make Connectable': ()=>makeConnectable(),
|
||||
'App/widget settings': ()=>showAppSettingsMenu(),
|
||||
'App/Widget Settings': ()=>showAppSettingsMenu(),
|
||||
'BLE': {
|
||||
value: settings.ble,
|
||||
format: boolFormat,
|
||||
|
|
@ -81,7 +81,7 @@ function showMainMenu() {
|
|||
updateSettings();
|
||||
}
|
||||
},
|
||||
'Debug info': {
|
||||
'Debug Info': {
|
||||
value: settings.log,
|
||||
format: v => v ? "Show" : "Hide",
|
||||
onchange: () => {
|
||||
|
|
@ -89,17 +89,6 @@ function showMainMenu() {
|
|||
updateSettings();
|
||||
}
|
||||
},
|
||||
'LCD Brightness': {
|
||||
value: settings.brightness,
|
||||
min: 0.1,
|
||||
max: 1,
|
||||
step: 0.1,
|
||||
onchange: v => {
|
||||
settings.brightness = v || 1;
|
||||
updateSettings();
|
||||
Bangle.setLCDBrightness(settings.brightness);
|
||||
}
|
||||
},
|
||||
'Beep': {
|
||||
value: 0 | beepV.indexOf(settings.beep),
|
||||
min: 0, max: 2,
|
||||
|
|
@ -134,7 +123,7 @@ function showMainMenu() {
|
|||
}
|
||||
},
|
||||
'Set Time': ()=>showSetTimeMenu(),
|
||||
'LCD Wake-Up': ()=>showWakeUpMenu(),
|
||||
'LCD': ()=>showLCDMenu(),
|
||||
'Reset Settings': ()=>showResetMenu(),
|
||||
'Turn Off': ()=>Bangle.off(),
|
||||
'< Back': ()=>load()
|
||||
|
|
@ -142,10 +131,21 @@ function showMainMenu() {
|
|||
return E.showMenu(mainmenu);
|
||||
}
|
||||
|
||||
function showWakeUpMenu() {
|
||||
const wakeUpMenu = {
|
||||
'': { 'title': 'LCD Wake-Up' },
|
||||
function showLCDMenu() {
|
||||
const lcdMenu = {
|
||||
'': { 'title': 'LCD' },
|
||||
'< Back': ()=>showMainMenu(),
|
||||
'LCD Brightness': {
|
||||
value: settings.brightness,
|
||||
min: 0.1,
|
||||
max: 1,
|
||||
step: 0.1,
|
||||
onchange: v => {
|
||||
settings.brightness = v || 1;
|
||||
updateSettings();
|
||||
Bangle.setLCDBrightness(settings.brightness);
|
||||
}
|
||||
},
|
||||
'LCD Timeout': {
|
||||
value: settings.timeout,
|
||||
min: 0,
|
||||
|
|
@ -157,7 +157,7 @@ function showWakeUpMenu() {
|
|||
Bangle.setLCDTimeout(settings.timeout);
|
||||
}
|
||||
},
|
||||
'Wake On BTN1': {
|
||||
'Wake on BTN1': {
|
||||
value: settings.options.wakeOnBTN1,
|
||||
format: boolFormat,
|
||||
onchange: () => {
|
||||
|
|
@ -165,7 +165,7 @@ function showWakeUpMenu() {
|
|||
updateOptions();
|
||||
}
|
||||
},
|
||||
'Wake On BTN2': {
|
||||
'Wake on BTN2': {
|
||||
value: settings.options.wakeOnBTN2,
|
||||
format: boolFormat,
|
||||
onchange: () => {
|
||||
|
|
@ -173,7 +173,7 @@ function showWakeUpMenu() {
|
|||
updateOptions();
|
||||
}
|
||||
},
|
||||
'Wake On BTN3': {
|
||||
'Wake on BTN3': {
|
||||
value: settings.options.wakeOnBTN3,
|
||||
format: boolFormat,
|
||||
onchange: () => {
|
||||
|
|
@ -197,7 +197,7 @@ function showWakeUpMenu() {
|
|||
updateOptions();
|
||||
}
|
||||
},
|
||||
'Wake On Twist': {
|
||||
'Wake on Twist': {
|
||||
value: settings.options.wakeOnTwist,
|
||||
format: boolFormat,
|
||||
onchange: () => {
|
||||
|
|
@ -236,7 +236,7 @@ function showWakeUpMenu() {
|
|||
}
|
||||
}
|
||||
}
|
||||
return E.showMenu(wakeUpMenu)
|
||||
return E.showMenu(lcdMenu)
|
||||
}
|
||||
|
||||
function showLocaleMenu() {
|
||||
|
|
@ -450,7 +450,7 @@ function showAppSettings(app) {
|
|||
}
|
||||
try {
|
||||
// pass showAppSettingsMenu as "back" argument
|
||||
appSettings(showAppSettingsMenu);
|
||||
appSettings(()=>showAppSettingsMenu());
|
||||
} catch (e) {
|
||||
console.log(`${app.name} settings error:`, e)
|
||||
return showError('Error in settings');
|
||||
|
|
|
|||
|
|
@ -37,7 +37,13 @@ try{
|
|||
ERROR("apps.json not valid JSON");
|
||||
}
|
||||
|
||||
apps.forEach((app,addIdx) => {
|
||||
const APP_KEYS = [
|
||||
'id', 'name', 'shortName', 'version', 'icon', 'description', 'tags', 'type',
|
||||
'sortorder', 'readme', 'custom', 'interface', 'storage', 'allow_emulator',
|
||||
];
|
||||
const STORAGE_KEYS = ['name', 'url', 'content', 'evaluate'];
|
||||
|
||||
apps.forEach((app,appIdx) => {
|
||||
if (!app.id) ERROR(`App ${appIdx} has no id`);
|
||||
//console.log(`Checking ${app.id}...`);
|
||||
var appDir = APPSDIR+app.id+"/";
|
||||
|
|
@ -105,9 +111,15 @@ apps.forEach((app,addIdx) => {
|
|||
ERROR(`App ${app.id}'s ${file.name} is a JS file but isn't valid JS`);
|
||||
}
|
||||
}
|
||||
for (const key in file) {
|
||||
if (!STORAGE_KEYS.includes(key)) ERROR(`App ${app.id}'s ${file.name} has unknown key ${key}`);
|
||||
}
|
||||
});
|
||||
//console.log(fileNames);
|
||||
if (isApp && !fileNames.includes(app.id+".app.js")) ERROR(`App ${app.id} has no entrypoint`);
|
||||
if (isApp && !fileNames.includes(app.id+".img")) ERROR(`App ${app.id} has no JS icon`);
|
||||
if (app.type=="widget" && !fileNames.includes(app.id+".wid.js")) ERROR(`Widget ${app.id} has no entrypoint`);
|
||||
for (const key in app) {
|
||||
if (!APP_KEYS.includes(key)) ERROR(`App ${app.id} has unknown key ${key}`);
|
||||
}
|
||||
});
|
||||
|
|
|
|||
|
|
@ -96,6 +96,7 @@
|
|||
<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="panel">
|
||||
<div class="panel-header">
|
||||
|
|
@ -134,7 +135,8 @@
|
|||
<h3>Utilities</h3>
|
||||
<p><button class="btn" id="settime">Set Bangle.js Time</button>
|
||||
<button class="btn" id="removeall">Remove all Apps</button>
|
||||
<button class="btn" id="installdefault">Install default apps</button></p>
|
||||
<button class="btn" id="installdefault">Install default apps</button>
|
||||
<button class="btn" id="installfavourite">Install favourite apps</button></p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
|
|||
117
js/index.js
117
js/index.js
|
|
@ -1,6 +1,8 @@
|
|||
var appJSON = []; // List of apps and info from apps.json
|
||||
var appsInstalled = []; // list of app JSON
|
||||
var files = []; // list of files on Bangle
|
||||
var favourites = []; // list of user favourite app
|
||||
const FAVOURITE = "favouriteapps.json";
|
||||
|
||||
httpGet("apps.json").then(apps=>{
|
||||
try {
|
||||
|
|
@ -18,7 +20,7 @@ httpGet("apps.json").then(apps=>{
|
|||
function showChangeLog(appid) {
|
||||
var app = appNameToApp(appid);
|
||||
function show(contents) {
|
||||
showPrompt(app.name+" Change Log",contents,{ok:true}).catch(()=>{});;
|
||||
showPrompt(app.name+" Change Log",contents,{ok:true}).catch(()=>{});
|
||||
}
|
||||
httpGet(`apps/${appid}/ChangeLog`).
|
||||
then(show).catch(()=>show("No Change Log available"));
|
||||
|
|
@ -142,6 +144,20 @@ function handleAppInterface(app) {
|
|||
});
|
||||
}
|
||||
|
||||
function handleAppFavourite(favourite, app){
|
||||
if (favourite) {
|
||||
favourites = favourites.concat([app.id]);
|
||||
} else {
|
||||
if ([ "boot","setting"].includes(app.id)) {
|
||||
showToast(app.name + ' is required, can\'t remove it' , 'warning');
|
||||
}else {
|
||||
favourites = favourites.filter(e => e != app.id);
|
||||
}
|
||||
}
|
||||
localStorage.setItem("favouriteapps.json", JSON.stringify(favourites));
|
||||
refreshLibrary();
|
||||
}
|
||||
|
||||
// =========================================== Top Navigation
|
||||
function showTab(tabname) {
|
||||
htmlToArray(document.querySelectorAll("#tab-navigate .tab-item")).forEach(tab => {
|
||||
|
|
@ -156,7 +172,7 @@ function showTab(tabname) {
|
|||
|
||||
// =========================================== Library
|
||||
|
||||
var chips = Array.from(document.querySelectorAll('.chip')).map(chip => chip.attributes.filterid.value)
|
||||
var chips = Array.from(document.querySelectorAll('.chip')).map(chip => chip.attributes.filterid.value);
|
||||
var hash = window.location.hash ? window.location.hash.slice(1) : '';
|
||||
|
||||
var activeFilter = !!~chips.indexOf(hash) ? hash : '';
|
||||
|
|
@ -165,27 +181,34 @@ var currentSearch = '';
|
|||
function refreshFilter(){
|
||||
var filtersContainer = document.querySelector("#librarycontainer .filter-nav");
|
||||
filtersContainer.querySelector('.active').classList.remove('active');
|
||||
if(activeFilter) filtersContainer.querySelector('.chip[filterid="'+activeFilter+'"]').classList.add('active')
|
||||
else filtersContainer.querySelector('.chip[filterid]').classList.add('active')
|
||||
if(activeFilter) filtersContainer.querySelector('.chip[filterid="'+activeFilter+'"]').classList.add('active');
|
||||
else filtersContainer.querySelector('.chip[filterid]').classList.add('active');
|
||||
}
|
||||
function refreshLibrary() {
|
||||
var panelbody = document.querySelector("#librarycontainer .panel-body");
|
||||
var visibleApps = appJSON;
|
||||
|
||||
if (activeFilter) {
|
||||
visibleApps = visibleApps.filter(app => app.tags && app.tags.split(',').includes(activeFilter));
|
||||
if ( activeFilter == "favourites" ) {
|
||||
visibleApps = visibleApps.filter(app => app.id && (favourites.filter( e => e == app.id).length));
|
||||
}else{
|
||||
visibleApps = visibleApps.filter(app => app.tags && app.tags.split(',').includes(activeFilter));
|
||||
}
|
||||
}
|
||||
|
||||
if (currentSearch) {
|
||||
visibleApps = visibleApps.filter(app => app.name.toLowerCase().includes(currentSearch) || app.tags.includes(currentSearch));
|
||||
}
|
||||
|
||||
favourites = (localStorage.getItem(FAVOURITE)) === null ? JSON.parse('["boot","launch","setting"]') : JSON.parse(localStorage.getItem("favouriteapps.json"));
|
||||
|
||||
panelbody.innerHTML = visibleApps.map((app,idx) => {
|
||||
var appInstalled = appsInstalled.find(a=>a.id==app.id);
|
||||
var version = getVersionInfo(app, appInstalled);
|
||||
var versionInfo = version.text;
|
||||
if (versionInfo) versionInfo = " <small>("+versionInfo+")</small>";
|
||||
var readme = `<a href="#" onclick="showReadme('${app.id}')">Read more...</a>`;
|
||||
var favourite = favourites.find(e => e == app.id);
|
||||
return `<div class="tile column col-6 col-sm-12 col-xs-12">
|
||||
<div class="tile-icon">
|
||||
<figure class="avatar"><img src="apps/${app.icon?`${app.id}/${app.icon}`:"unknown.png"}" alt="${escapeHtml(app.name)}"></figure><br/>
|
||||
|
|
@ -195,7 +218,8 @@ function refreshLibrary() {
|
|||
<p class="tile-subtitle">${escapeHtml(app.description)}${app.readme?`<br/>${readme}`:""}</p>
|
||||
<a href="https://github.com/espruino/BangleApps/tree/master/apps/${app.id}" target="_blank" class="link-github"><img src="img/github-icon-sml.png" alt="See the code on GitHub"/></a>
|
||||
</div>
|
||||
<div class="tile-action">
|
||||
<div class="tile-action">
|
||||
<button class="btn btn-link btn-action btn-lg ${!app.custom?"text-error":"d-hide"}" appid="${app.id}" title="Favorite"><i class="icon"></i>${favourite?"♥":"♡"}</button>
|
||||
<button class="btn btn-link btn-action btn-lg ${(appInstalled&&app.interface)?"":"d-hide"}" appid="${app.id}" title="Download data from app"><i class="icon icon-download"></i></button>
|
||||
<button class="btn btn-link btn-action btn-lg ${app.allow_emulator?"":"d-hide"}" appid="${app.id}" title="Try in Emulator"><i class="icon icon-share"></i></button>
|
||||
<button class="btn btn-link btn-action btn-lg ${version.canUpdate?"":"d-hide"}" appid="${app.id}" title="Update App"><i class="icon icon-refresh"></i></button>
|
||||
|
|
@ -232,7 +256,7 @@ function refreshLibrary() {
|
|||
// upload
|
||||
icon.classList.remove("icon-upload");
|
||||
icon.classList.add("loading");
|
||||
uploadApp(app)
|
||||
uploadApp(app);
|
||||
} else if (icon.classList.contains("icon-menu")) {
|
||||
// custom HTML update
|
||||
icon.classList.remove("icon-menu");
|
||||
|
|
@ -250,6 +274,10 @@ function refreshLibrary() {
|
|||
updateApp(app);
|
||||
} else if (icon.classList.contains("icon-download")) {
|
||||
handleAppInterface(app);
|
||||
} else if ( button.innerText == String.fromCharCode(0x2661)) {
|
||||
handleAppFavourite(true, app);
|
||||
} else if ( button.innerText == String.fromCharCode(0x2665) ) {
|
||||
handleAppFavourite(false, app);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
|
@ -262,17 +290,17 @@ refreshLibrary();
|
|||
function uploadApp(app) {
|
||||
return getInstalledApps().then(()=>{
|
||||
if (appsInstalled.some(i => i.id === app.id)) {
|
||||
return updateApp(app)
|
||||
return updateApp(app);
|
||||
}
|
||||
Comms.uploadApp(app).then((appJSON) => {
|
||||
Progress.hide({ sticky: true })
|
||||
Progress.hide({ sticky: true });
|
||||
if (appJSON) {
|
||||
appsInstalled.push(appJSON)
|
||||
appsInstalled.push(appJSON);
|
||||
}
|
||||
showToast(app.name + ' Uploaded!', 'success')
|
||||
showToast(app.name + ' Uploaded!', 'success');
|
||||
}).catch(err => {
|
||||
Progress.hide({ sticky: true })
|
||||
showToast('Upload failed, ' + err, 'error')
|
||||
Progress.hide({ sticky: true });
|
||||
showToast('Upload failed, ' + err, 'error');
|
||||
}).finally(()=>{
|
||||
refreshMyApps();
|
||||
refreshLibrary();
|
||||
|
|
@ -286,8 +314,8 @@ function removeApp(app) {
|
|||
return showPrompt("Delete","Really remove '"+app.name+"'?").then(() => {
|
||||
return getInstalledApps().then(()=>{
|
||||
// a = from appid.info, app = from apps.json
|
||||
return Comms.removeApp(appsInstalled.find(a => a.id === app.id))
|
||||
})
|
||||
return Comms.removeApp(appsInstalled.find(a => a.id === app.id));
|
||||
});
|
||||
}).then(()=>{
|
||||
appsInstalled = appsInstalled.filter(a=>a.id!=app.id);
|
||||
showToast(app.name+" removed successfully","success");
|
||||
|
|
@ -315,13 +343,13 @@ function updateApp(app) {
|
|||
if (app.custom) return customApp(app);
|
||||
return getInstalledApps().then(() => {
|
||||
// a = from appid.info, app = from apps.json
|
||||
let remove = appsInstalled.find(a => a.id === app.id)
|
||||
let remove = appsInstalled.find(a => a.id === app.id);
|
||||
// no need to remove files which will be overwritten anyway
|
||||
remove.files = remove.files.split(',')
|
||||
.filter(f => f !== app.id + '.info')
|
||||
.filter(f => !app.storage.some(s => s.name === f))
|
||||
.join(',')
|
||||
return Comms.removeApp(remove)
|
||||
.join(',');
|
||||
return Comms.removeApp(remove);
|
||||
}).then(()=>{
|
||||
showToast(`Updating ${app.name}...`);
|
||||
appsInstalled = appsInstalled.filter(a=>a.id!=app.id);
|
||||
|
|
@ -397,7 +425,7 @@ return `<div class="tile column col-6 col-sm-12 col-xs-12">
|
|||
// check icon to figure out what we should do
|
||||
if (icon.classList.contains("icon-delete")) removeApp(app);
|
||||
if (icon.classList.contains("icon-refresh")) updateApp(app);
|
||||
if (icon.classList.contains("icon-download")) handleAppInterface(app)
|
||||
if (icon.classList.contains("icon-download")) handleAppInterface(app);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
@ -405,7 +433,7 @@ return `<div class="tile column col-6 col-sm-12 col-xs-12">
|
|||
let haveInstalledApps = false;
|
||||
function getInstalledApps(refresh) {
|
||||
if (haveInstalledApps && !refresh) {
|
||||
return Promise.resolve(appsInstalled)
|
||||
return Promise.resolve(appsInstalled);
|
||||
}
|
||||
showLoadingIndicator("myappscontainer");
|
||||
// Get apps and files
|
||||
|
|
@ -453,7 +481,7 @@ filtersContainer.addEventListener('click', ({ target }) => {
|
|||
activeFilter = target.getAttribute('filterid') || '';
|
||||
refreshFilter();
|
||||
refreshLibrary();
|
||||
window.location.hash = activeFilter
|
||||
window.location.hash = activeFilter;
|
||||
});
|
||||
|
||||
var librarySearchInput = document.querySelector("#searchform input");
|
||||
|
|
@ -526,7 +554,7 @@ document.getElementById("installdefault").addEventListener("click",event=>{
|
|||
upload();
|
||||
}).catch(function() {
|
||||
Progress.hide({sticky:true});
|
||||
reject()
|
||||
reject();
|
||||
});
|
||||
}
|
||||
upload();
|
||||
|
|
@ -541,3 +569,48 @@ document.getElementById("installdefault").addEventListener("click",event=>{
|
|||
showToast("App Install failed, "+err,"error");
|
||||
});
|
||||
});
|
||||
|
||||
// Install all favoutrie apps in one go
|
||||
document.getElementById("installfavourite").addEventListener("click",event=>{
|
||||
var defaultApps, appCount;
|
||||
asyncLocalStorage.getItem(FAVOURITE).then(json=>{
|
||||
defaultApps = JSON.parse(json);
|
||||
defaultApps = defaultApps.map( appid => appJSON.find(app=>app.id==appid) );
|
||||
if (defaultApps.some(x=>x===undefined))
|
||||
throw "Not all apps found";
|
||||
appCount = defaultApps.length;
|
||||
return showPrompt("Install Defaults","Remove everything and install favourite apps?");
|
||||
}).then(() => {
|
||||
return Comms.removeAllApps();
|
||||
}).then(()=>{
|
||||
Progress.hide({sticky:true});
|
||||
appsInstalled = [];
|
||||
showToast(`Existing apps removed. Installing ${appCount} apps...`);
|
||||
return new Promise((resolve,reject) => {
|
||||
function upload() {
|
||||
var app = defaultApps.shift();
|
||||
if (app===undefined) return resolve();
|
||||
Progress.show({title:`${app.name} (${appCount-defaultApps.length}/${appCount})`,sticky:true});
|
||||
Comms.uploadApp(app,"skip_reset").then((appJSON) => {
|
||||
Progress.hide({sticky:true});
|
||||
if (appJSON) appsInstalled.push(appJSON);
|
||||
showToast(`(${appCount-defaultApps.length}/${appCount}) ${app.name} Uploaded`);
|
||||
upload();
|
||||
}).catch(function() {
|
||||
Progress.hide({sticky:true});
|
||||
reject();
|
||||
});
|
||||
}
|
||||
upload();
|
||||
});
|
||||
}).then(()=>{
|
||||
return Comms.setTime();
|
||||
}).then(()=>{
|
||||
showToast("Favourites apps successfully installed!","success");
|
||||
return getInstalledApps(true);
|
||||
}).catch(err=>{
|
||||
Progress.hide({sticky:true});
|
||||
showToast("App Install failed, "+err,"error");
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
|||
1
js/ui.js
1
js/ui.js
|
|
@ -86,6 +86,7 @@ function showToast(message, type) {
|
|||
var style = "toast-primary";
|
||||
if (type=="success") style = "toast-success";
|
||||
else if (type=="error") style = "toast-error";
|
||||
else if (type=="warning") style = "toast-warning";
|
||||
else if (type!==undefined) console.log("showToast: unknown toast "+type);
|
||||
var toastcontainer = document.getElementById("toastcontainer");
|
||||
var msgDiv = htmlElement(`<div class="toast ${style}"></div>`);
|
||||
|
|
|
|||
13
js/utils.js
13
js/utils.js
|
|
@ -67,3 +67,16 @@ function getVersionInfo(appListing, appInstalled) {
|
|||
canUpdate : canUpdate
|
||||
}
|
||||
}
|
||||
|
||||
const asyncLocalStorage = {
|
||||
setItem: function (key, value) {
|
||||
return Promise.resolve().then(function () {
|
||||
localStorage.setItem(key, value);
|
||||
});
|
||||
},
|
||||
getItem: function (key) {
|
||||
return Promise.resolve().then(function () {
|
||||
return localStorage.getItem(key);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
|
|
|||
Loading…
Reference in New Issue