Merge branch 'espruino:master' into master
|
@ -22,7 +22,7 @@
|
|||
<body>
|
||||
<header class="navbar-primary navbar">
|
||||
<section class="navbar-section" >
|
||||
<a href="https://banglejs.com" target="_blank" class="navbar-brand mr-2" ><img src="img/banglejs-logo-sml.png" alt="Bangle.js">
|
||||
<a href="https://banglejs.com" target="_blank" class="navbar-brand mr-2"><img src="img/banglejs-logo-small.svg" alt="Bangle.js">
|
||||
<div>App Loader</div></a>
|
||||
<!-- <a href="#" class="btn btn-link">...</a> -->
|
||||
</section>
|
||||
|
|
|
@ -144,7 +144,7 @@
|
|||
],
|
||||
"no-constant-condition": "off",
|
||||
"no-delete-var": "off",
|
||||
"no-empty": "off",
|
||||
"no-empty": ["warn", { "allowEmptyCatch": true }],
|
||||
"no-global-assign": "off",
|
||||
"no-inner-declarations": "off",
|
||||
"no-prototype-builtins": "off",
|
||||
|
|
|
@ -1 +1,2 @@
|
|||
0.01: Initial version
|
||||
0.02: Add settings page; Add line break to update message
|
|
@ -1,10 +1,13 @@
|
|||
# a_dndtoggle - Toggle Quiet Mode of the watch
|
||||
|
||||
When Quiet mode is off, just start this app to set quiet mode. Start it again to turn off quiet mode.
|
||||
|
||||
Use the app settings to choose which quiet mode you prefer ("Alarms" or "Silent"). Default is "Silent".
|
||||
|
||||
Work in progress.
|
||||
|
||||
#ToDo
|
||||
Settings page, current status indicator.
|
||||
Current status indicator
|
||||
|
||||
## Creator
|
||||
|
||||
|
|
|
@ -6,11 +6,14 @@ let current = 0|bSettings.quiet;
|
|||
//1 alarms
|
||||
//2 silent
|
||||
|
||||
const dndSettings =
|
||||
require('Storage').readJSON("a_dndtoggle.settings.json", true) || {};
|
||||
|
||||
console.log("old: " + current);
|
||||
|
||||
switch (current) {
|
||||
case 0:
|
||||
bSettings.quiet = 2;
|
||||
bSettings.quiet = dndSettings.mode || 2;
|
||||
Bangle.buzz();
|
||||
setTimeout('Bangle.buzz();',500);
|
||||
break;
|
||||
|
@ -29,7 +32,7 @@ switch (current) {
|
|||
|
||||
console.log("new: " + bSettings.quiet);
|
||||
|
||||
E.showMessage(modeNames[current] + " -> " + modeNames[bSettings.quiet]);
|
||||
E.showMessage(modeNames[current] + " -> \n" + modeNames[bSettings.quiet]);
|
||||
setTimeout('exitApp();', 2000);
|
||||
|
||||
|
||||
|
|
|
@ -2,14 +2,18 @@
|
|||
"id": "a_dndtoggle",
|
||||
"name": "a_dndtoggle - Toggle Quiet Mode of the watch",
|
||||
"shortName": "A_DND Toggle",
|
||||
"version": "0.01",
|
||||
"version": "0.02",
|
||||
"description": "Toggle Quiet Mode of the watch just by starting this app.",
|
||||
"icon": "a_dndtoggle.png",
|
||||
"type": "app",
|
||||
"tags": "tool",
|
||||
"supports": ["BANGLEJS","BANGLEJS2"],
|
||||
"data" : [
|
||||
{"name":"a_dndtoggle.settings.json"}
|
||||
],
|
||||
"storage": [
|
||||
{"name":"a_dndtoggle.app.js","url":"a_dndtoggle.app.js"},
|
||||
{"name":"a_dndtoggle.settings.js","url":"settings.js"},
|
||||
{"name":"a_dndtoggle.img","url":"app-icon.js","evaluate":true}
|
||||
],
|
||||
"readme": "README.md"
|
||||
|
|
|
@ -0,0 +1,33 @@
|
|||
(function(back) {
|
||||
|
||||
const settings =
|
||||
require('Storage').readJSON("a_dndtoggle.settings.json", true) || {};
|
||||
|
||||
function updateSettings() {
|
||||
require('Storage').writeJSON("a_dndtoggle.settings.json", settings);
|
||||
}
|
||||
|
||||
function buildMainMenu(){
|
||||
// 0-Noisy is only a placeholder so that the other values map to the Bangle quiet mode options
|
||||
const modes = [/*LANG*/"Noisy",/*LANG*/"Alarms",/*LANG*/"Silent"];
|
||||
let mainmenu = {
|
||||
'': { 'title': 'A_DND Toggle' },
|
||||
'< Back': back,
|
||||
/*LANG*/"Quiet Mode": {
|
||||
value: settings.mode || 2,
|
||||
min: 1, // don't allow choosing 0-Noisy
|
||||
max: modes.length - 1,
|
||||
format: v => modes[v],
|
||||
onchange: v => {
|
||||
settings.mode = v;
|
||||
updateSettings();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return mainmenu;
|
||||
}
|
||||
|
||||
E.showMenu(buildMainMenu());
|
||||
});
|
||||
|
|
@ -104,7 +104,6 @@ Bangle.on('lcdPower',on=>{
|
|||
if (on) {
|
||||
secondInterval = setInterval(draw, 1000);
|
||||
draw(); // draw immediately
|
||||
}else{
|
||||
}
|
||||
});
|
||||
Bangle.on('lock',on=>{
|
||||
|
|
|
@ -6,3 +6,4 @@
|
|||
0.06: Formatting
|
||||
0.07: Added potato GLaDOS and quote functionality when you tap her
|
||||
0.08: Fixed drawing issues with the quotes and added more
|
||||
0.09: Minor code improvements
|
||||
|
|
|
@ -270,8 +270,7 @@ function queueDraw() {
|
|||
|
||||
|
||||
function draw() {
|
||||
if (pause){}
|
||||
else{
|
||||
if (!pause){
|
||||
// get date
|
||||
var d = new Date();
|
||||
var da = d.toString().split(" ");
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
"id": "aptsciclk",
|
||||
"name": "Apeture Science Clock",
|
||||
"shortName":"AptSci Clock",
|
||||
"version": "0.08",
|
||||
"version": "0.09",
|
||||
"description": "A clock based on the portal series",
|
||||
"icon": "app.png",
|
||||
"screenshots": [{"url":"screenshot.png"}],
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
0.01: New App!
|
|
@ -0,0 +1,21 @@
|
|||
# Auto Reset
|
||||
|
||||
Sets a timeout to load the clock face. The timeout is stopped and started again upon user input.
|
||||
|
||||
## Usage
|
||||
|
||||
Install with app loader and Auto Reset will run in background. If you don't interact with the watch it will time out to the clock face after 10 minutes.
|
||||
|
||||
## TODO
|
||||
|
||||
- Add settings page
|
||||
- set how many minutes the timeout should count down.
|
||||
- whitelist/blacklist for apps.
|
||||
|
||||
## Requests
|
||||
|
||||
Mention @thyttan in an issue on the espruino/BangleApps repo for bug reports and feature requests.
|
||||
|
||||
## Creator
|
||||
|
||||
thyttan
|
After Width: | Height: | Size: 1.4 KiB |
|
@ -0,0 +1,20 @@
|
|||
{
|
||||
let timeoutAutoreset;
|
||||
let resetTimeoutAutoreset = (force)=>{
|
||||
if (timeoutAutoreset) clearTimeout(timeoutAutoreset);
|
||||
setTimeout(()=>{ // Short outer timeout to make sure we have time to leave clock face before checking `Bangle.CLOCK!=1`.
|
||||
if (Bangle.CLOCK!=1) { // Only add timeout if not already on clock face.
|
||||
timeoutAutoreset = setTimeout(()=>{
|
||||
if (Bangle.CLOCK!=1) Bangle.showClock();
|
||||
}, 10*60*1000);
|
||||
}
|
||||
},200);
|
||||
};
|
||||
|
||||
Bangle.on('touch', resetTimeoutAutoreset);
|
||||
Bangle.on('swipe', resetTimeoutAutoreset);
|
||||
Bangle.on('message', resetTimeoutAutoreset);
|
||||
setWatch(resetTimeoutAutoreset, BTN, {repeat:true, edge:'rising'});
|
||||
|
||||
if (Bangle.CLOCK!=1) resetTimeoutAutoreset();
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
{ "id": "autoreset",
|
||||
"name": "Auto Reset",
|
||||
"version":"0.01",
|
||||
"description": "Sets a timeout to load the clock face. The timeout is stopped and started again upon user input.",
|
||||
"icon": "app.png",
|
||||
"type": "bootloader",
|
||||
"tags": "system",
|
||||
"supports" : ["BANGLEJS2"],
|
||||
"readme": "README.md",
|
||||
"storage": [
|
||||
{"name":"autoreset.boot.js","url":"boot.js"}
|
||||
]
|
||||
}
|
|
@ -1,3 +1,4 @@
|
|||
0.01: Added app
|
||||
0.02: Removed unneeded squares
|
||||
0.03: Added settings with fullscreen option
|
||||
0.03: Added setting for fullscreen option
|
||||
0.04: Added settings to hide unused squares and show date
|
||||
|
|
|
@ -1,11 +1,15 @@
|
|||
var settings = Object.assign({
|
||||
fullscreen: false,
|
||||
fullscreen: false,
|
||||
hidesq: false,
|
||||
showdate: false,
|
||||
}, require('Storage').readJSON("binaryclk.json", true) || {});
|
||||
|
||||
function draw() {
|
||||
|
||||
var dt = new Date();
|
||||
var h = dt.getHours(), m = dt.getMinutes();
|
||||
var h = dt.getHours(), m = dt.getMinutes(), d = dt.getDate();
|
||||
const t = [];
|
||||
|
||||
t[0] = Math.floor(h/10);
|
||||
t[1] = Math.floor(h%10);
|
||||
t[2] = Math.floor(m/10);
|
||||
|
@ -17,25 +21,44 @@ function draw() {
|
|||
let i = 0;
|
||||
var gap = 8;
|
||||
var mgn = 20;
|
||||
|
||||
if (settings.fullscreen) {
|
||||
gap = 12;
|
||||
mgn = 0;
|
||||
}
|
||||
|
||||
const sq = 29;
|
||||
var pos = sq + gap;
|
||||
|
||||
for (let r = 3; r >= 0; r--) {
|
||||
for (let c = 0; c < 4; c++) {
|
||||
if (t[c] & Math.pow(2, r)) {
|
||||
g.fillRect(mgn/2 + gap + c * pos, mgn + gap + i * pos, mgn/2 + gap + c * pos + sq, mgn + gap + i * pos + sq);
|
||||
g.fillRect(Math.floor(mgn/2) + gap + c * pos, mgn + gap + i * pos, Math.floor(mgn/2) + gap + c * pos + sq, mgn + gap + i * pos + sq);
|
||||
} else {
|
||||
g.drawRect(mgn/2 + gap + c * pos, mgn + gap + i * pos, mgn/2 + gap + c * pos + sq, mgn + gap + i * pos + sq);
|
||||
g.drawRect(Math.floor(mgn/2) + gap + c * pos, mgn + gap + i * pos, Math.floor(mgn/2) + gap + c * pos + sq, mgn + gap + i * pos + sq);
|
||||
}
|
||||
}
|
||||
i++;
|
||||
}
|
||||
g.clearRect(mgn/2 + gap, mgn + gap, mgn/2 + gap + sq, mgn + 2 * gap + 2 * sq);
|
||||
g.clearRect(mgn/2 + 3 * gap + 2 * sq, mgn + gap, mgn/2 + 3 * gap + 3 * sq, mgn + gap + sq);
|
||||
|
||||
var c1sqhide = 0;
|
||||
var c3sqhide = 0;
|
||||
|
||||
if (settings.hidesq) {
|
||||
c1sqhide = 2;
|
||||
c3sqhide = 1;
|
||||
}
|
||||
|
||||
if (settings.hidesq) {
|
||||
g.clearRect(Math.floor(mgn/2), mgn, Math.floor(mgn/2) + pos, mgn + c1sqhide * pos);
|
||||
g.clearRect(Math.floor(mgn/2) + 2 * pos + gap, mgn, Math.floor(mgn/2) + 3 * pos, mgn + c3sqhide * pos);
|
||||
}
|
||||
if (settings.showdate) {
|
||||
g.setFontAlign(0, 0);
|
||||
g.setFont("Vector",20);
|
||||
g.drawRect(Math.floor(mgn/2) + gap, mgn + gap, Math.floor(mgn/2) + gap + sq, mgn + gap + sq);
|
||||
g.drawString(d, Math.ceil(mgn/2) + gap + Math.ceil(sq/2) + 1, mgn + gap + Math.ceil(sq/2) + 1);
|
||||
}
|
||||
}
|
||||
|
||||
g.clear();
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
{
|
||||
"id": "binaryclk",
|
||||
"name": "Bin Clock",
|
||||
"version": "0.03",
|
||||
"description": "Clock face to show binary time in 24 hr format",
|
||||
"version": "0.04",
|
||||
"description": "Clock face to show binary time in 24 hour format",
|
||||
"icon": "app-icon.png",
|
||||
"screenshots": [{"url":"screenshot.png"}],
|
||||
"type": "clock",
|
||||
|
|
Before Width: | Height: | Size: 3.2 KiB After Width: | Height: | Size: 16 KiB |
|
@ -2,6 +2,8 @@
|
|||
var FILE = "binaryclk.json";
|
||||
var settings = Object.assign({
|
||||
fullscreen: false,
|
||||
hidesq: false,
|
||||
showdate: false,
|
||||
}, require('Storage').readJSON(FILE, true) || {});
|
||||
|
||||
function writeSettings() {
|
||||
|
@ -16,7 +18,21 @@
|
|||
onchange: v => {
|
||||
settings.fullscreen = v;
|
||||
writeSettings();
|
||||
}
|
||||
},
|
||||
},
|
||||
'Hide Squares': {
|
||||
value: settings.hidesq,
|
||||
onchange: v => {
|
||||
settings.hidesq = v;
|
||||
writeSettings();
|
||||
},
|
||||
},
|
||||
'Show Date': {
|
||||
value: settings.showdate,
|
||||
onchange: v => {
|
||||
settings.showdate = v;
|
||||
writeSettings();
|
||||
},
|
||||
},
|
||||
});
|
||||
})
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
0.01: New App!
|
||||
0.02: Add rep info to time screen
|
||||
0.03: Add option to pause/resume workout (Bangle.js 1 only)
|
||||
0.04: Add possibility of creating a custom exercise
|
|
@ -0,0 +1,96 @@
|
|||
# C25K
|
||||
|
||||
Unofficial app for the Couch to 5k training plan.
|
||||
From being a couch-potato to running 5k in 8 weeks!
|
||||
|
||||
Each week has 3 training days, ideally with rest days between them.
|
||||
|
||||
Each day's programme consists of running for a certain time with occasional walking/resting phases.
|
||||
When walking is part of the programme, the (run+walk) stages are repeated a number of times.
|
||||
|
||||

|
||||

|
||||

|
||||
|
||||
## Features
|
||||
|
||||
- Show remaining time in seconds for each phase
|
||||
- Vibrates on phase changes
|
||||
- Keeps screen on to allow quickly glancing at the time while running
|
||||
- Shows time on button press
|
||||
|
||||
## Usage
|
||||
|
||||
If you know the week and day of the programme you'd like to start, set `Week` and `Day` to the appropriate values in the main menu and press `Start`.
|
||||
|
||||
**Example**:
|
||||
To start the programme of the **second day** of **week 4**:
|
||||

|
||||
|
||||
---
|
||||
|
||||
Alternatively, you can go to the `View plan` menu to look at all the programmes and select the one you'd like to start.
|
||||
|
||||
**Example**:
|
||||
Go to the `View plan` menu:
|
||||

|
||||
|
||||
Select the programme to start it:
|
||||

|
||||
|
||||
---
|
||||
|
||||
The format of the `View menu` is `w{week}d{day}(r:{run mins}|w:{walk mins}|x{number of reps})`.
|
||||
|
||||
For example `w6d1(r:6|w:3|x2)` means:
|
||||
`it's the programme of day 1 on week 6`,
|
||||
`it consists of running for 6 minutes`,
|
||||
`followed by walking for 3`,
|
||||
`done 2 times back to back`.
|
||||
|
||||
---
|
||||
|
||||
### Create a custom excercise
|
||||
|
||||
Under the `Custom run` menu, it's possible to create a custom excercise.
|
||||

|
||||
|
||||
Some important details/limitations:
|
||||
|
||||
- To disable walking: set `walk` to `0`
|
||||
- When walking is set to `0`, the repetition count is set to `1`.
|
||||
- When repetition is set to `2` or higher, `walk` is set to `1`.
|
||||
|
||||
**Unfortunately, the value in the menu do not update to reflect the changes, so I recommend setting the values with the rules above in mind.**
|
||||
|
||||
---
|
||||
|
||||
### Show extra info:
|
||||
|
||||
If you ever need to peek at the time, just press the middle (or only) physical button on the watch:
|
||||

|
||||
|
||||
This view also shows `current rep / total reps` at the top.
|
||||
|
||||
---
|
||||
|
||||
### Pause/resume workout:
|
||||
|
||||
**This is currently only available on Bangle.js 1.**
|
||||
|
||||
Press the top button to pause or to resume the active programme:
|
||||

|
||||
|
||||
---
|
||||
|
||||
## Disclaimer
|
||||
|
||||
This app was hacked together in a day with no JS knowledge.
|
||||
It's probably inefficient and buggy, but it does what I needed it to do: allow me to follow the C25K programme without a phone.
|
||||
|
||||
The app was designed with a Bangle.js 1 in mind, as that's the one I have.
|
||||
It *should* work fine on the Bangle.js 2, but I couldn't test it on real hardware.
|
||||
|
||||
---
|
||||
|
||||
Made with <3 by [Erovia](https://github.com/Erovia/BangleApps/tree/c25k)
|
|
@ -0,0 +1 @@
|
|||
require("heatshrink").decompress(atob("mEw4X/AoPk9G9gsj14lZhWq0AEBgtVqALmhQJBAQMFBIICCBc4ADBQYLnAQQKEBcibETQIABHggLiAEQqEh/wgACCBcpXDBAIKDBcqJDh//BQYLkHwg7GBcY7FU5ALgAEQA="))
|
|
@ -0,0 +1,290 @@
|
|||
var week = 1; // Stock plan: programme week
|
||||
var day = 1; // Stock plan: programe day
|
||||
var run = 1; // Custom plan: running time
|
||||
var walk = 0; // Custom plan: walking time
|
||||
var reps = 1; // Custom plan: repetition count
|
||||
|
||||
var time; // To store the date
|
||||
|
||||
var loop; // To store how many times we will have to do a countdown
|
||||
var rep; // The current rep counter
|
||||
var counter; // The seconds counter
|
||||
var currentMode; // Either "run" or "walk"
|
||||
var mainInterval; // Ticks every second, checking if a new countdown is needed
|
||||
var activityInterval; // Ticks every second, doing the countdown
|
||||
var extraInfoWatch; // Watch for button presses to show additional info
|
||||
var paused = false; // Track pause state
|
||||
var pauseOrResumeWatch; // Watch for button presses to pause/resume countdown
|
||||
var defaultFontSize = (process.env.HWVERSION == 2) ? 7 : 9; // Default font size, Banglejs 2 has smaller
|
||||
var activityBgColour; // Background colour of current activity
|
||||
var currentActivity; // To store the current activity
|
||||
|
||||
function outOfTime() {
|
||||
buzz();
|
||||
|
||||
// Once we're done
|
||||
if (loop == 0) {
|
||||
clearWatch(extraInfoWatch); // Don't watch for button presses anymore
|
||||
if (pauseOrResumeWatch) clearWatch(pauseOrResumeWatch); // Don't watch for button presses anymore
|
||||
g.setBgColor("#75C0E0"); // Blue background for the "Done" text
|
||||
drawText("Done", defaultFontSize); // Write "Done" to screen
|
||||
g.reset();
|
||||
setTimeout(E.showMenu, 5000, mainmenu); // Show the main menu again after 5secs
|
||||
clearInterval(mainInterval); // Stop the main interval from starting a new activity
|
||||
mainInterval = undefined;
|
||||
currentMode = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
// Buzz 3 times on state transitions
|
||||
function buzz() {
|
||||
Bangle.buzz(500)
|
||||
.then(() => new Promise(resolve => setTimeout(resolve, 200)))
|
||||
.then(() => Bangle.buzz(500))
|
||||
.then(() => new Promise(resolve => setTimeout(resolve, 200)))
|
||||
.then(() => Bangle.buzz(500));
|
||||
}
|
||||
|
||||
function drawText(text, size){
|
||||
g.clear();
|
||||
g.setFontAlign(0, 0); // center font
|
||||
g.setFont("6x8", size);
|
||||
g.drawString(text, g.getWidth() / 2, g.getHeight() / 2);
|
||||
}
|
||||
|
||||
function countDown() {
|
||||
if (!paused) {
|
||||
var text = "";
|
||||
var size = defaultFontSize;
|
||||
if (time) {
|
||||
var total = ("walk" in currentActivity) ? currentActivity.repetition : 1;
|
||||
text += rep + "/" + total + "\n"; // Show the current/total rep count when time is shown
|
||||
size -= 2; // Use smaller font size to fit everything nicely on the screen
|
||||
}
|
||||
text += (currentMode === "run") ? "Run\n" + counter : "Walk\n" + counter; // Switches output text
|
||||
if (time) text += "\n" + time;
|
||||
drawText(text, size); // draw the current mode and seconds
|
||||
Bangle.setLCDPower(1); // keep the watch LCD lit up
|
||||
|
||||
counter--; // Reduce the seconds
|
||||
|
||||
// If the current activity is done
|
||||
if (counter < 0) {
|
||||
clearInterval(activityInterval);
|
||||
activityInterval = undefined;
|
||||
outOfTime();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function startTimer() {
|
||||
// If something is already running, do nothing
|
||||
if (activityInterval) return;
|
||||
|
||||
// Switches between the two modes
|
||||
if (!currentMode || currentMode === "walk") {
|
||||
currentMode = "run";
|
||||
rep++; // Increase the rep counter every time a "run" activity starts
|
||||
counter = currentActivity.run * 60;
|
||||
activityBgColour = "#ff5733"; // Red background for running
|
||||
}
|
||||
else {
|
||||
currentMode = "walk";
|
||||
counter = currentActivity.walk * 60;
|
||||
activityBgColour = "#4da80a"; // Green background for walking
|
||||
|
||||
}
|
||||
|
||||
g.setBgColor(activityBgColour);
|
||||
countDown();
|
||||
if (!activityInterval) {
|
||||
loop--; // Reduce the number of iterations
|
||||
activityInterval = setInterval(countDown, 1000); // Start a new activity
|
||||
}
|
||||
}
|
||||
|
||||
function showTime() {
|
||||
if (time) return; // If clock is already shown, don't do anything even if the button was pressed again
|
||||
// Get the time and format it with a leading 0 if necessary
|
||||
var d = new Date();
|
||||
var h = d.getHours();
|
||||
var m = d.getMinutes();
|
||||
time = h + ":" + m.toString().padStart(2, 0);
|
||||
setTimeout(function() { time = undefined; }, 5000); // Hide clock after 5secs
|
||||
}
|
||||
|
||||
// Populate the PLAN menu
|
||||
function populatePlan() {
|
||||
for (var i = 0; i < PLAN.length; i++) {
|
||||
for (var j = 0; j < PLAN[i].length; j++) {
|
||||
// Ever line will have the following format:
|
||||
// w{week}d{day}(r:{run mins}|w:{walk mins}|x{number of reps})
|
||||
var name = "w" + (i + 1) + "d" + (j + 1);
|
||||
if (process.env.HWVERSION == 2) name += "\n"; // Print in 2 lines to accomodate the Bangle.js 2 screen
|
||||
name += "(r:" + PLAN[i][j].run;
|
||||
if ("walk" in PLAN[i][j]) name += "|w:" + PLAN[i][j].walk;
|
||||
if ("repetition" in PLAN[i][j]) name += "|x" + PLAN[i][j].repetition;
|
||||
name += ")";
|
||||
// Each menu item will have a function that start the program at the selected day
|
||||
planmenu[name] = getFunc(i, j);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Helper function to generate functions for the activePlan menu
|
||||
function getFunc(i, j) {
|
||||
return function() {
|
||||
currentActivity = PLAN[i][j];
|
||||
startActivity();
|
||||
};
|
||||
}
|
||||
|
||||
function startActivity() {
|
||||
loop = ("walk" in currentActivity) ? currentActivity.repetition * 2 : 1;
|
||||
rep = 0;
|
||||
|
||||
E.showMenu(); // Hide the main menu
|
||||
extraInfoWatch = setWatch(showTime, (process.env.HWVERSION == 2) ? BTN1 : BTN2, {repeat: true}); // Show the clock on button press
|
||||
if (process.env.HWVERSION == 1) pauseOrResumeWatch = setWatch(pauseOrResumeActivity, BTN1, {repeat: true}); // Pause or resume on button press (Bangle.js 1 only)
|
||||
buzz();
|
||||
mainInterval = setInterval(function() {startTimer();}, 1000); // Check every second if we need to do something
|
||||
}
|
||||
|
||||
// Pause or resume current activity
|
||||
function pauseOrResumeActivity() {
|
||||
paused = !paused;
|
||||
buzz();
|
||||
if (paused) {
|
||||
g.setBgColor("#fdd835"); // Yellow background for pause screen
|
||||
drawText("Paused", (process.env.HWVERSION == 2) ? defaultFontSize - 3 : defaultFontSize - 2); // Although the font size is configured here, this feature does not work on Bangle.js 2 as the only physical button is tied to the extra info screen already
|
||||
}
|
||||
else {
|
||||
g.setBgColor(activityBgColour);
|
||||
}
|
||||
}
|
||||
|
||||
const PLAN = [
|
||||
[
|
||||
{"run": 1, "walk": 1.5, "repetition": 8},
|
||||
{"run": 1, "walk": 1.5, "repetition": 8},
|
||||
{"run": 1, "walk": 1.5, "repetition": 8},
|
||||
],
|
||||
[
|
||||
{"run": 1.5, "walk": 2, "repetition": 6},
|
||||
{"run": 1.5, "walk": 2, "repetition": 6},
|
||||
{"run": 1.5, "walk": 2, "repetition": 6},
|
||||
],
|
||||
[
|
||||
{"run": 2, "walk": 2, "repetition": 5},
|
||||
{"run": 2.5, "walk": 2.5, "repetition": 4},
|
||||
{"run": 2.5, "walk": 2.5, "repetition": 4},
|
||||
],
|
||||
[
|
||||
{"run": 3, "walk": 2, "repetition": 5},
|
||||
{"run": 3, "walk": 2, "repetition": 5},
|
||||
{"run": 4, "walk": 2.5, "repetition": 3},
|
||||
],
|
||||
[
|
||||
{"run": 5, "walk": 2, "repetition": 3},
|
||||
{"run": 8, "walk": 5, "repetition": 2},
|
||||
{"run": 20},
|
||||
],
|
||||
[
|
||||
{"run": 6, "walk": 3, "repetition": 2},
|
||||
{"run": 10, "walk": 3, "repetition": 2},
|
||||
{"run": 25},
|
||||
],
|
||||
[
|
||||
{"run": 25},
|
||||
{"run": 25},
|
||||
{"run": 25},
|
||||
],
|
||||
[
|
||||
{"run": 30},
|
||||
{"run": 30},
|
||||
{"run": 30},
|
||||
],
|
||||
];
|
||||
|
||||
var customRun = {"run": 1};
|
||||
|
||||
// Main menu
|
||||
var mainmenu = {
|
||||
"": { "title": "-- C25K --" },
|
||||
"Week": {
|
||||
value: week,
|
||||
min: 1, max: PLAN.length, step: 1,
|
||||
onchange : v => { week = v; }
|
||||
},
|
||||
"Day": {
|
||||
value: day,
|
||||
min: 1, max: 3, step: 1,
|
||||
onchange: v => { day = v; }
|
||||
},
|
||||
"View plan": function() { E.showMenu(planmenu); },
|
||||
"Custom run": function() { E.showMenu(custommenu); },
|
||||
"Start": function() {
|
||||
currentActivity = PLAN[week - 1][day -1];
|
||||
startActivity();
|
||||
},
|
||||
"Exit": function() { load(); },
|
||||
};
|
||||
|
||||
// Plan view
|
||||
var planmenu = {
|
||||
"": { title: "-- Plan --" },
|
||||
"< Back": function() { E.showMenu(mainmenu);},
|
||||
};
|
||||
|
||||
// Custom view
|
||||
var custommenu = {
|
||||
"": { title : "-- Cust. run --" },
|
||||
"< Back": function() { E.showMenu(mainmenu);},
|
||||
"Run (mins)": {
|
||||
value: run,
|
||||
min: 1, max: 150, step: 1,
|
||||
wrap: true,
|
||||
onchange: v => { customRun.run = v; }
|
||||
},
|
||||
"Walk (mins)": {
|
||||
value: walk,
|
||||
min: 0, max: 10, step: 1,
|
||||
onchange: v => {
|
||||
if (v > 0) {
|
||||
if (reps == 1) { reps = 2; } // Walking only makes sense with multiple reps
|
||||
customRun.repetition = reps;
|
||||
customRun.walk = v;
|
||||
}
|
||||
else {
|
||||
// If no walking, delete both the reps and walk data
|
||||
delete customRun.repetition;
|
||||
delete customRun.walk;
|
||||
}
|
||||
walk = v;
|
||||
}
|
||||
},
|
||||
"Reps": {
|
||||
value: reps,
|
||||
min: 1, max: 10, step: 1,
|
||||
onchange: v => {
|
||||
if (v > 1) {
|
||||
if (walk == 0) { walk = 1; } // Multiple reps only make sense with walking phases
|
||||
customRun.walk = walk;
|
||||
customRun.repetition = v;
|
||||
}
|
||||
else {
|
||||
// If no multiple reps, delete both the reps and walk data
|
||||
delete customRun.repetition;
|
||||
delete customRun.walk;
|
||||
}
|
||||
reps = v;
|
||||
}
|
||||
},
|
||||
"Start": function() { currentActivity = customRun; startActivity(); }
|
||||
};
|
||||
|
||||
// Populate the activePlan menu view
|
||||
populatePlan();
|
||||
// Actually display the menu
|
||||
E.showMenu(mainmenu);
|
After Width: | Height: | Size: 376 B |
After Width: | Height: | Size: 2.7 KiB |
After Width: | Height: | Size: 2.7 KiB |
After Width: | Height: | Size: 2.3 KiB |
After Width: | Height: | Size: 3.0 KiB |
After Width: | Height: | Size: 3.0 KiB |
After Width: | Height: | Size: 5.7 KiB |
After Width: | Height: | Size: 3.0 KiB |
After Width: | Height: | Size: 2.3 KiB |
After Width: | Height: | Size: 3.4 KiB |
|
@ -0,0 +1,30 @@
|
|||
{
|
||||
"id": "c25k",
|
||||
"name": "C25K",
|
||||
"icon": "app.png",
|
||||
"version":"0.04",
|
||||
"description": "Unofficial app for the Couch to 5k training plan",
|
||||
"readme": "README.md",
|
||||
"type": "app",
|
||||
"tags": "running,c25k,tool,outdoors,exercise",
|
||||
"allow_emulator": true,
|
||||
"supports": [
|
||||
"BANGLEJS",
|
||||
"BANGLEJS2"
|
||||
],
|
||||
"storage": [
|
||||
{"name": "c25k.app.js", "url": "app.js"},
|
||||
{"name": "c25k.img", "url": "app-icon.js", "evaluate": true}
|
||||
],
|
||||
"screenshots": [
|
||||
{"url": "c25k-scrn1.png"},
|
||||
{"url": "c25k-scrn2.png"},
|
||||
{"url": "c25k-scrn3.png"},
|
||||
{"url": "c25k-scrn4.png"},
|
||||
{"url": "c25k-scrn5.png"},
|
||||
{"url": "c25k-scrn6.png"},
|
||||
{"url": "c25k-scrn7.png"},
|
||||
{"url": "c25k-scrn8.png"},
|
||||
{"url": "c25k-scrn9.png"}
|
||||
]
|
||||
}
|
|
@ -1,3 +1,4 @@
|
|||
0.01: Car Crazy is now avialable for testing in beta!
|
||||
0.02: 10 Levels are now added making the game harder as it goes along. Some of the levels include multiple cars and faster cars. More levels coming soon.
|
||||
0.03: Settings are now added so that you can reset your high score.
|
||||
0.04: Minor code improvements.
|
||||
|
|
|
@ -66,10 +66,8 @@ function moveEnemyPosition(){
|
|||
enemyPositonCenterX2 = 120;
|
||||
}else if((randomRoadPositionIndicator2 == 3)){
|
||||
enemyPositonCenterX2 = 155;
|
||||
}else if(level == 7||level == 8){
|
||||
|
||||
}
|
||||
}
|
||||
} // TODO: else if(level == 7)
|
||||
}
|
||||
|
||||
function collision(){
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
"id": "carcrazy",
|
||||
"name": "Car Crazy",
|
||||
"shortName": "Car Crazy",
|
||||
"version": "0.03",
|
||||
"version": "0.04",
|
||||
"description": "A simple car game where you try to avoid the other cars by tilting your wrist left and right. Hold down button 2 to start.",
|
||||
"icon": "carcrash.png",
|
||||
"tags": "game",
|
||||
|
|
|
@ -1,2 +1,3 @@
|
|||
0.01: Simple app to display loyalty cards
|
||||
0.02: Hiding widgets while showing the code
|
||||
0.03: Added option to use max brightness when showing code
|
||||
|
|
|
@ -14,6 +14,14 @@
|
|||
Bangle.loadWidgets();
|
||||
Bangle.drawWidgets();
|
||||
|
||||
// get brightness
|
||||
let brightness;
|
||||
|
||||
function loadBrightness() {
|
||||
const getBrightness = require('Storage').readJSON("setting.json", 1) || {};
|
||||
brightness = getBrightness.brightness || 0.1;
|
||||
}
|
||||
|
||||
//may make it configurable in the future
|
||||
const WHITE=-1
|
||||
const BLACK=0
|
||||
|
@ -89,6 +97,10 @@ function printLinearCode(binary) {
|
|||
}
|
||||
|
||||
function showCode(card) {
|
||||
// set to full bright when the setting is true
|
||||
if(settings.fullBrightness) {
|
||||
Bangle.setLCDBrightness(1);
|
||||
}
|
||||
widget_utils.hide();
|
||||
E.showScroller();
|
||||
// keeping it on rising edge would come back twice..
|
||||
|
@ -129,6 +141,10 @@ function showCode(card) {
|
|||
}
|
||||
|
||||
function showCard(card) {
|
||||
// reset brightness to old value after maxing it out
|
||||
if(settings.fullBrightness) {
|
||||
Bangle.setLCDBrightness(brightness);
|
||||
}
|
||||
var lines = [];
|
||||
var bodyFont = fontBig;
|
||||
if(!card) return;
|
||||
|
@ -208,4 +224,7 @@ function showList() {
|
|||
back : () => load()
|
||||
});
|
||||
}
|
||||
if(settings.fullBrightness) {
|
||||
loadBrightness();
|
||||
}
|
||||
showList();
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"id": "cards",
|
||||
"name": "Cards",
|
||||
"version": "0.02",
|
||||
"version": "0.03",
|
||||
"description": "Display loyalty cards",
|
||||
"icon": "app.png",
|
||||
"screenshots": [{"url":"screenshot_cards_overview.png"}, {"url":"screenshot_cards_card1.png"}, {"url":"screenshot_cards_card2.png"}, {"url":"screenshot_cards_barcode.png"}, {"url":"screenshot_cards_qrcode.png"}],
|
||||
|
|
|
@ -14,6 +14,13 @@
|
|||
updateSettings();
|
||||
}
|
||||
},
|
||||
/*LANG*/"Full Brightness" : {
|
||||
value : !!settings.fullBrightness,
|
||||
onchange: v => {
|
||||
settings.fullBrightness = v;
|
||||
updateSettings();
|
||||
}
|
||||
}
|
||||
};
|
||||
E.showMenu(mainmenu);
|
||||
})
|
||||
|
|
|
@ -7,3 +7,4 @@
|
|||
Improve connection code
|
||||
0.07: Make Bangle.js 2 compatible
|
||||
0.08: Convert Yes/No On/Off in settings to checkboxes
|
||||
0.09: Automatically reconnect on error
|
||||
|
|
|
@ -226,7 +226,7 @@ function getSensorBatteryLevel(gatt) {
|
|||
function connection_setup() {
|
||||
mySensor.screenInit = true;
|
||||
E.showMessage("Scanning for CSC sensor...");
|
||||
NRF.requestDevice({ filters: [{services:["1816"]}]}).then(function(d) {
|
||||
NRF.requestDevice({ filters: [{services:["1816"]}], maxInterval: 100}).then(function(d) {
|
||||
device = d;
|
||||
E.showMessage("Found device");
|
||||
return device.gatt.connect();
|
||||
|
@ -249,6 +249,7 @@ function connection_setup() {
|
|||
}).catch(function(e) {
|
||||
E.showMessage(e.toString(), "ERROR");
|
||||
console.log(e);
|
||||
setTimeout(connection_setup, 1000);
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
"id": "cscsensor",
|
||||
"name": "Cycling speed sensor",
|
||||
"shortName": "CSCSensor",
|
||||
"version": "0.08",
|
||||
"version": "0.09",
|
||||
"description": "Read BLE enabled cycling speed and cadence sensor and display readings on watch",
|
||||
"icon": "icons8-cycling-48.png",
|
||||
"tags": "outdoors,exercise,ble,bluetooth,bike,cycle,bicycle",
|
||||
|
|
|
@ -5,3 +5,4 @@
|
|||
0.05: extraneous comments and code removed
|
||||
display improved
|
||||
now supports Adjust Clock widget, if installed
|
||||
0.06: minor code improvements
|
||||
|
|
|
@ -223,9 +223,8 @@ function fixTime() {
|
|||
Bangle.on("GPS",function cb(g) {
|
||||
Bangle.setGPSPower(0,"time");
|
||||
Bangle.removeListener("GPS",cb);
|
||||
if (!g.time || (g.time.getFullYear()<2000) ||
|
||||
(g.time.getFullYear()>2200)) {
|
||||
} else {
|
||||
if (g.time && (g.time.getFullYear()>=2000) &&
|
||||
(g.time.getFullYear()<=2200)) {
|
||||
// We have a GPS time. Set time
|
||||
setTime(g.time.getTime()/1000);
|
||||
}
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
"id": "doztime",
|
||||
"name": "Dozenal Digital Time",
|
||||
"shortName": "Dozenal Digital",
|
||||
"version": "0.05",
|
||||
"version": "0.06",
|
||||
"description": "A dozenal Holocene calendar and dozenal diurnal digital clock",
|
||||
"icon": "app.png",
|
||||
"type": "clock",
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
node_modules/
|
||||
res/
|
||||
|
||||
fallout_clock.code-workspace
|
||||
|
||||
package.json
|
||||
package-lock.json
|
|
@ -0,0 +1,5 @@
|
|||
0.10: (20240125) Basic Working Clock.
|
||||
0.11: (20240125) Widgets Added. Improved Interval Loop.
|
||||
0.12: (20240221) Fix: Month Reporting Wrong.
|
||||
0.20: (20240223) Created as a Package.
|
||||
0.21: (20240223) Added StandardJS and NPM.
|
|
@ -0,0 +1,21 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) 2024 Zachary D. Skelton <zskelton@skeltonnetworks.com>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
|
@ -0,0 +1,29 @@
|
|||
# Fallout Clock
|
||||
|
||||
Inspired by the aesthetic of the Fallout series, this clock face looks to emulate the color and feel of a PipBoy.
|
||||
|
||||

|
||||
|
||||
## Usage
|
||||
|
||||
You can also go into Settings, and choose it as the default clock under **Select Clock**.
|
||||
|
||||
## Planned Features:
|
||||
- Display Steps as Health
|
||||
- Display Heartrate
|
||||
- Brighter Color when the backlight is not on.
|
||||
- Configurable Settings
|
||||
|
||||
## Controls
|
||||
|
||||
Zero Settings, Zero Configuration. Install and add as your clockface.
|
||||
|
||||
## Requests
|
||||
|
||||
To request new features, add [an issue](https://github.com/zskelton/fallout_clock/issues).
|
||||
|
||||
## Creator
|
||||
|
||||
Zachary D. Skelton <zskelton@skeltonnetworks.com>\
|
||||
[Skelton Networks](https://skeltonnetworks.com)\
|
||||
[Github](https://github.com/zskelton)
|
|
@ -0,0 +1 @@
|
|||
atob("MDDDAb88//9u/1r/1/YZrgAAit4kkkkkkkkkkAAVIkkkkkkkkkkkkkkkkkkAAAARJJIkkkkkkkkkkkkkkkAAAACJJJJUkkkkkkkkkkkkkAAAAARJJJJAAkkkkkkkkkkkAAAAACpJJJKgAAkkkkkkkkkgAAAAAVJJJJIAAAEkkkkkkkkAAAAACpJfpJUAAAAkkkkkkkgAAAAABJf/9JAAAAAEkkkkkkAAAAAARJdf/+gAAAAAkkkkkgAAAAAC//dL//gAAAAAEkkkkAAAAYADpJJL//8AAAAAAkkkkAAAD8AdJJJL///gAAAAAkkkgAAADr/pJJL////0AAAAAEkkgAAABJJL/pfb////gAAAAAkkAAAADpJeu22X////4AAAAAkkAAAADpL1tttuf/7f8AAAAAkgAAAAb/+ttttuSSS7/AAAAAEgAAAAdeyttttySSSb/gAAAAEgAAAC9eWtttuaySSf/2SSSSkgAAAVfxtttttyySX//9JJJQAAAAAJetttttttyST//9JJJUAAAABJeOaNyNutySW//9JJKgAAAARJdu6N1tvRySS3/JJJUAAAACJJVuVu1tzRyST2/JJKAAAAAVJL1ttyttuNuSWW7pJKgAAACpJLxtt6NtttuSS27pJUAAAAVJJLxtt6ttttuSWT9JKgAAAAJJJLxttzNtttuSST9JIAAAAiJJJL1ttttt2NuSSS9JUAAAA2222212xtty3RySSS9KgAAAEgAAAAZ6OW2tu1ySST9QAAAAEgAAAAaW1ttu2VySSXKAAAAAEkAAAACtu221ttySbdKgAAAAEkAAAADNty1ttuST9JUAAAAAkkAAAAAVty1ttuSXpKAAAAAAkkgAAAACtttttyT9JIAAAAAEkkkAAAAARttttyfdJUAAAAAEkkkAAAAACtttuSzJKgAAAAAkkkkgAAAAAWtuSSfpQAAAAAEkkkkkAAAAADa2yT9JAAAAAAkkkkkkgAAAAD7e3/pKgAAAAAkkkkkkkAAAAVL/9JJUAAAAAEkkkkkkkgAAARJJJJKAAAAAEkkkkkkkkkAAAJJJJJIAAAAAkkkkkkkkkkkACpJJJJUAAAAEkkkkkkkkkkkgCJJJJKgAAAEkkkkkkkkkkkkklJJJJQAAAEkkkkkkkkkkkkkkkkpJJAAEkkkkkkkkk=")
|
|
@ -0,0 +1,141 @@
|
|||
/* global Bangle, Graphics, g */
|
||||
|
||||
// NAME: Fallout Clock (Bangle.js 2)
|
||||
// DOCS: https://www.espruino.com/ReferenceBANGLEJS2
|
||||
// AUTHOR: Zachary D. Skelton <zskelton@bws-solutions.com>
|
||||
// VERSION: 0.1.0 (24JAN2024) - Creating [ Maj.Min.Bug ] REF: https://semver.org/
|
||||
// LICENSE: MIT License (2024) [ https://opensource.org/licenses/MIT ]
|
||||
|
||||
/* THEME COLORS */
|
||||
// Dark Full - #000000 - (0,0.00,0)
|
||||
// Dark Half - #002f00 - (0,0.18,0)
|
||||
// Dark Zero - #005f00 - (0,0.37,0)
|
||||
// Light Zero - #008e00 - (0,0.55,0)
|
||||
// Light Half - #00bf00 - (0,0.75,0)
|
||||
// Light Full - #00ee00 - (0,0.93,0)
|
||||
|
||||
/* FONTS */
|
||||
// Font: Good Time Rg - https://www.dafont.com/good-times.font
|
||||
// Large = 50px
|
||||
Graphics.prototype.setLargeFont = function () {
|
||||
this.setFontCustom(
|
||||
atob('AAAAAAAAAAAAAAAAAAAAAABAAAAAAAB8AAAAAAA/gAAAAAAP4AAAAAAD+AAAAAAA/gAAAAAAP4AAAAAAB8AAAAAAAGAAAAAAAAAAAAAAAAAAAAAAAADAAAAAAADwAAAAAAD8AAAAAAD/AAAAAAD/wAAAAAD/8AAAAAH/8AAAAAH/8AAAAAH/4AAAAAH/4AAAAAH/4AAAAAH/4AAAAAP/4AAAAAP/wAAAAAP/wAAAAAP/wAAAAAP/wAAAAAH/wAAAAAB/wAAAAAAfgAAAAAAHgAAAAAABgAAAAAAAAAAAAAAAAAAOAAAAAAB//AAAAAB//8AAAAB///wAAAA////AAAAf///4AAAP////AAAH////4AAD/+B//AAB/8AD/wAAf8AAP+AAP+AAB/gAD/AAAP8AA/gAAB/AAf4AAAf4AH8AAAD+AB/AAAA/gAfwAAAP4AH8AAAD+AB/AAAA/gAfwAAAP4AH8AAAD+AB/AAAA/gAfwAAAP4AH8AAAD+AB/gAAB/gAP4AAAfwAD/AAAP8AA/4AAH+AAH/AAD/gAB/8AD/wAAP/4H/8AAB////+AAAP////AAAB////gAAAP///wAAAB///wAAAAH//wAAAAAf/wAAAAAAOAAAAAAAAAAAAAfgAAAAAAH8AAAAAAB/AAAAAAAfwAAAAAAH8AAAAAAB/AAAAAAAfwAAAAAAH+AAAAAAB/wAAAAAAf/////wAD/////8AA//////AAH/////wAA/////8AAH/////AAAf////wAAA////8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP/wAH8AA//8AB/AA///AAfwAf//wAH8AH//8AB/AD///AAfwA///wAH8Af//8AB/AH8B/AAfwB+AfwAH8AfgH8AB/AH4B/AAfwB+AfwAH8AfgH8AB/AH4B/AAfwB+AfwAH8AfgH8AB/AH4B/AAfwB+AfwAH8AfgH8AB/AH4B/AAfwB+AfwAH8AfgH8AB/AH4B/AAfwB+AfwAH8AfgH8AB/AH4B/AAfwD+AfwAH+B/AH8AB///wB/AAf//8AfwAD///AH8AA///gB/AAH//wAfwAA//4AH8AAH/8AB/AAAf8AAPwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD8AB/AAAB/AAfwAAAfwAH8AAAH8AB/AAAB/AAfwAAAfwAH8AAAH8AB/AAAB/AAfwAAAfwAH8AfAH8AB/AH4B/AAfwB+AfwAH8AfgH8AB/AH4B/AAfwB+AfwAH8AfgH8AB/AH4B/AAfwB+AfwAH8AfgH8AB/AH4B/AAfwB+AfwAH8AfgH8AB/AH4B/AAfwB+AfwAH8AfgH8AB/AH4B/AAfwB+AfwAH8A/gH8AB/gP4D/AAf8H/A/wAH///8/8AA/////+AAP/////gAB/////4AAf/8//8AAD/+P/+AAAf/B//AAAA/AH/AAAAAAAMAAAAAAAAAAAAAAAAAAAAAAAAAAAAH///AAAAB///8AAAAf///gAAAH///8AAAB////gAAAf///4AAAH////AAAAAAD/wAAAAAAP8AAAAAAB/AAAAAAAfwAAAAAAD+AAAAAAA/gAAAAAAP4AAAAAAD+AAAAAAA/gAAAAAAP4AAAAAAD+AAAAAAA/gAAAAAAP4AAAAAAD+AAAAAAA/gAAAAAAP4AAAAAAD+AAAAAAA/gAAAAAAP4AAAAAAD+AAAH/////8AB//////AAf/////wAH/////8AB//////AAf/////wAH/////8AAAAAP4AAAAAAD+AAAAAAA/gAAAAAAP4AAAAAAD+AAAAAAA/AAAAAAAAAAAAAAAAAAAAH///AD8AB///4B/AAf//+AfwAH///gH8AB///4B/AAf//+AfwAH///gH8AB///4B/AAfwB+AfwAH8AfgH8AB/AH4B/AAfwB+AfwAH8AfgH8AB/AH4B/AAfwB+AfwAH8AfgH8AB/AH4B/AAfwB+AfwAH8AfgH8AB/AH4B/AAfwB+AfwAH8AfgH8AB/AH4B/AAfwB+AfwAH8AfgH8AB/AH4B/AAfwB+AfwAH8AfgH8AB/AH4B/AAfwB+AfwAH8AfgH8AB/AH8D/AAfwB/h/wAH8Af//8AB/AD//+AAfwA///gAH8AH//wAB/AA//4AAfgAH/8AAAAAAf8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHwAAAAAB//4AAAAB///wAAAB////AAAA////4AAAf////AAAP////4AAH/////AAB//fv/wAA/8H4f+AAP+B+D/gAH/AfgP8AB/gH4D/AAfwB+AfwAH8AfgH8AB/AH4B/AAfwB+AfwAH8AfgH8AB/AH4B/AAfwB+AfwAH8AfgH8AB/AH4B/AAfwB+AfwAH8AfgH8AB/AH4B/AAfwB+AfwAH8AfgH8AB/AH4B/AAfwB+AfwAH8AfgH8AB/AH4B/AAfwB+A/wAH8Afwf8AB/AH///AAfwA///gAH8AP//4AB/AD//8AAfwAf/+AAH8AD//AAAAAAP/gAAAAAAeAAAAAAAAAAAAAAAAAAAAAAAAAAAAH4AAAAAAB/AAAABAAfwAAAAwAH8AAAAcAB/AAAAfAAfwAAAPwAH8AAAH8AB/AAAD/AAfwAAD/wAH8AAB/8AB/AAA//AAfwAAf/gAH8AAP/gAB/AAP/wAAfwAH/4AAH8AD/8AAB/AB/8AAAfwB/+AAAH8A//AAAB/Af/gAAAfwP/gAAAH8P/wAAAB/H/4AAAAfz/8AAAAH9/8AAAAB//+AAAAAf//AAAAAH//gAAAAB//gAAAAAf/wAAAAAH/4AAAAAB/8AAAAAAf8AAAAAAH+AAAAAAB/AAAAAAAPgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP4AAAD/gP/gAAB/+H/8AAA//z//gAAf////8AAP/////gAD/////4AB//////AAf+P/B/wAH+A/gP8AB/AP4B/AAfwB+AfwAH8AfgH8AB/AH4B/AAfwB+AfwAH8AfgH8AB/AH4B/AAfwB+AfwAH8AfgH8AB/AH4B/AAfwB+AfwAH8AfgH8AB/AH4B/AAfwB+AfwAH8AfgH8AB/AH4B/AAfwB+AfwAH8AfgH8AB/AH4B/AAfwB+AfwAH8A/gH8AB/gP8D/AAf+P/h/wAH/////8AA/////+AAP/////gAB/////wAAP/8//4AAB/+H/8AAAH+A/+AAAAAAD+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwAAAAAAH/4AAAAAH//gAAAAD//8AAAAB///AD8AA///4B/AAP///AfwAH///wH8AB/4f8B/AAf4B/AfwAH8APwH8AB/AD8B/AAfwA/AfwAH8APwH8AB/AD8B/AAfwA/AfwAH8APwH8AB/AD8B/AAfwA/AfwAH8APwH8AB/AD8B/AAfwA/AfwAH8APwH8AB/AD8B/AAfwA/AfwAH8APwH8AB/AD8B/AAfwA/AfwAH+APwP8AB/wD8D/AAP+A/B/gAD/wPx/4AAf/j9/+AAH/////AAA/////gAAH////4AAA////8AAAH///8AAAAf//+AAAAA//8AAAAAAfwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAEAAAAD4AHwAAAB/AD+AAAAfwA/gAAAH8AP4AAAB/AD+AAAAfwA/gAAAD4AHwAAAAMAAYAAAAAAAAAAAAAAAAA'),
|
||||
46,
|
||||
atob('DRYqEykpKiwsJi0rDQ=='),
|
||||
50 | 65536
|
||||
)
|
||||
return this
|
||||
}
|
||||
|
||||
// Medium = 16px ()
|
||||
Graphics.prototype.setMediumFont = function () {
|
||||
this.setFontCustom(
|
||||
atob('AAAAAAAADwAAAB8AAAAPAAAAGwAAAb8AAB/9AAH/kAAv+QAA/4AAAPQAAAACvkAAH//wAD///AC/Qf4A/AA/APQAHwDwAA8A9AAfAPwAPwC+Qf4AP//8AB//9AAG/4AAoAAAAPAAAAD4AAAA////AL///wAv//8AAAAAAPAL/wDwH/8A8D//APA9DwDwPA8A8DwPAPA8DwDwPA8A9DwPAP78DwD/+A8AL+APAPAADwDwAA8A8BQPAPA8DwDwPA8A8DwPAPA8DwDwPA8A9DwfAP7/vwD///8AP+v8AAUBUACqqQAA//9AAP//wABVW8AAAAPAAAADwAAAA8AAAAPAAAADwAD///8A////AP///wAAA8AAAAKAAAAAAAD//A8A//wPAP/8DwDwPA8A8DwPAPA8DwDwPA8A8DwPAPA8DwDwPR8A8D+/APAv/gCgC/gAC//gAD///AC///4A/Tx/APg8LwDwPA8A8DwPAPA8DwDwPA8A8D0fAPA/vwDwL/4AoAv4AAAAQABQAAAA8AAHAPAAHwDwAL8A8AP/APAf+ADwf9AA8v9AAP/4AAD/4AAA/0AAAP0AAAAAAEAAL9v4AL///gD//78A+H0vAPA8DwDwPA8A8DwPAPA8DwDwPA8A9D0fAP7/vwD///8AP9v8AC/4AAC//g8A/r8PAPQfDwDwDw8A8A8PAPAPDwDwDw8A+A8fAP5PfwC///4AL//8AAb/4AAAAAAAAAAAAAA8DwAAfB8AADwPAA=='),
|
||||
46,
|
||||
atob('BAcNBg0NDg4ODA4OBA=='),
|
||||
16 | 131072
|
||||
)
|
||||
return this
|
||||
}
|
||||
|
||||
/* VARIABLES */
|
||||
// Const
|
||||
const H = g.getHeight()
|
||||
const W = g.getWidth()
|
||||
// Mutable
|
||||
let timer = null
|
||||
|
||||
/* UTILITY FUNCTIONS */
|
||||
// Return String of Current Time
|
||||
function getCurrentTime () {
|
||||
try {
|
||||
const d = new Date()
|
||||
const h = d.getHours()
|
||||
const m = d.getMinutes()
|
||||
return `${h}:${m.toString().padStart(2, 0)}`
|
||||
} catch (e) {
|
||||
console.log(e)
|
||||
return '0:00'
|
||||
}
|
||||
}
|
||||
|
||||
// Return String of Current Date
|
||||
function getCurrentDate () {
|
||||
try {
|
||||
const d = new Date()
|
||||
const year = d.getFullYear()
|
||||
const month = d.getMonth()
|
||||
const day = d.getDate()
|
||||
const display = `${month + 1}.${day.toString().padStart(2, 0)}.${year}`
|
||||
return display
|
||||
} catch (e) {
|
||||
console.log(e)
|
||||
return '0.0.0000'
|
||||
}
|
||||
}
|
||||
|
||||
// Set A New Draw for the Next Minute
|
||||
function setNextDraw () {
|
||||
console.log('tick')
|
||||
// Clear Timeout
|
||||
if (timer) {
|
||||
clearInterval(timer)
|
||||
}
|
||||
// Calculate time until next minute
|
||||
const d = new Date()
|
||||
const s = d.getSeconds()
|
||||
const ms = d.getMilliseconds()
|
||||
const delay = 60000 - (s * 1000) - ms
|
||||
// Set Timeout
|
||||
timer = setInterval(draw, delay)
|
||||
}
|
||||
|
||||
function draw () {
|
||||
// Reset Variables
|
||||
g.reset()
|
||||
// Set Background Color
|
||||
g.setBgColor(0, 0, 0)
|
||||
// Draw Background
|
||||
g.setColor(0, 0, 0)
|
||||
g.fillRect(0, 0, W, H)
|
||||
// Set Font for Time
|
||||
g.setColor(0, 0.93, 0)
|
||||
g.setLargeFont()
|
||||
g.setFontAlign(0, 0)
|
||||
// Draw Time
|
||||
const time = getCurrentTime()
|
||||
g.drawString(time, W / 2, H / 2, true /* clear background */)
|
||||
// Set Font for Date
|
||||
g.setColor(0, 0.75, 0)
|
||||
g.setMediumFont()
|
||||
g.setFontAlign(0, 1)
|
||||
// Draw Date
|
||||
const dateStr = getCurrentDate()
|
||||
g.drawString(dateStr, W / 2, H - 45, true)
|
||||
// Draw Border
|
||||
g.setColor(0, 0.93, 0)
|
||||
g.drawLine(5, 36, W - 5, 36)
|
||||
g.drawLine(5, H - 9, W - 5, H - 9)
|
||||
g.setColor(0, 0.18, 0)
|
||||
g.fillRect(0, 27, W, 32)
|
||||
g.fillRect(0, H, W, H - 5)
|
||||
// Draw Widgets
|
||||
Bangle.drawWidgets()
|
||||
// Schedule Next Draw
|
||||
setNextDraw()
|
||||
}
|
||||
|
||||
/* MAIN LOOP */
|
||||
function main () {
|
||||
// Clear Screen
|
||||
g.clear()
|
||||
// Set as Clock to Enable Launcher Screen on BTN1
|
||||
Bangle.setUI('clock')
|
||||
// Load Widgets
|
||||
Bangle.loadWidgets()
|
||||
// Draw Clock
|
||||
draw()
|
||||
}
|
||||
|
||||
/* BOOT CODE */
|
||||
main()
|
After Width: | Height: | Size: 7.8 KiB |
|
@ -0,0 +1,18 @@
|
|||
{
|
||||
"id":"fallout_clock",
|
||||
"name":"Fallout Clock",
|
||||
"version":"0.21",
|
||||
"description":"A simple clock for the Fallout fan",
|
||||
"icon":"icon.png",
|
||||
"type":"clock",
|
||||
"tags": "clock,fallout,green,retro",
|
||||
"supports": ["BANGLEJS2"],
|
||||
"readme": "README.md",
|
||||
"storage": [
|
||||
{"name":"fallout_clock.app.js", "url":"clock.js"},
|
||||
{"name":"fallout_clock.img", "url":"app-icon.js", "evaluate":true}
|
||||
],
|
||||
"screenshots": [
|
||||
{"url":"./screenshot.png", "name":"Fallout Clock Screenshot"}
|
||||
]
|
||||
}
|
After Width: | Height: | Size: 7.8 KiB |
After Width: | Height: | Size: 2.2 KiB |
After Width: | Height: | Size: 2.2 KiB |
|
@ -127,4 +127,11 @@
|
|||
* Interface: disable/enable waypoints detection and elevation
|
||||
* Touching the screen when sleeping will wake up but not change screen
|
||||
* Removing footways (if bicycles not allowed) to reduce resource usage
|
||||
* Switching screen will clear the screen immediately so you can know your screen touch has been received
|
||||
* Switching screen will clear the screen immediately so you can know your screen touch has been received
|
||||
|
||||
0.24:
|
||||
* Fix for loading very large files (> 65kb)
|
||||
* New menu on file selection : reverse path without waiting for file to load
|
||||
* Fix for files converted from maps2gpx : path was not reduced in size correctly
|
||||
* Experimental ski mode : have a ski slopes map
|
||||
* Fix for path projection display when lost and zoomed out
|
||||
|
|
|
@ -33,7 +33,7 @@ It provides the following features :
|
|||
|
||||
### Preparing the file
|
||||
|
||||
You first need to have a trace file in *gpx* format.
|
||||
You typically want to use a trace file in *gpx* format.
|
||||
Usually I download from [komoot](https://www.komoot.com/) or I export
|
||||
from google maps using [mapstogpx](https://mapstogpx.com/). [Brouter](https://brouter.damsy.net) is
|
||||
also a nice open source option.
|
||||
|
@ -48,6 +48,11 @@ Just click the disk icon and select your gpx file.
|
|||
This will request additional information from openstreetmap.
|
||||
Your path will be displayed in svg.
|
||||
|
||||
Note that it is also possible to just hit the shift key and drag the mouse on the map
|
||||
to download a map (with no trace). If you want a map, hitting the "ski" checkbox
|
||||
before selecting the area will parse openstreetmap data in order to get the pistes and
|
||||
the lifts. Colors will correspond to difficulty levels.
|
||||
|
||||
### Starting Gipy
|
||||
|
||||
At start you will have a menu for selecting your trace (if more than one).
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
|
||||
urgent TODO:
|
||||
- is path projection wrong for high res ?
|
||||
|
||||
medium TODO:
|
||||
|
||||
- prefetch tiles ?
|
||||
- update documentation to reflect new display ?
|
||||
|
|
157
apps/gipy/app.js
|
@ -95,26 +95,30 @@ function compute_eta(hour, minutes, approximate_speed, remaining_distance) {
|
|||
}
|
||||
|
||||
class TilesOffsets {
|
||||
constructor(buffer, offset) {
|
||||
let type_size = Uint8Array(buffer, offset, 1)[0];
|
||||
constructor(filename, offset) {
|
||||
let header = E.toArrayBuffer(s.read(filename, offset, 4));
|
||||
let type_size = Uint8Array(header, 0, 1)[0];
|
||||
offset += 1;
|
||||
this.entry_size = Uint8Array(buffer, offset, 1)[0];
|
||||
this.entry_size = Uint8Array(header, 1, 1)[0];
|
||||
offset += 1;
|
||||
let non_empty_tiles_number = Uint16Array(buffer, offset, 1)[0];
|
||||
let non_empty_tiles_number = Uint16Array(header, 2, 1)[0];
|
||||
offset += 2;
|
||||
this.non_empty_tiles = Uint16Array(buffer, offset, non_empty_tiles_number);
|
||||
|
||||
let bytes = (type_size==24)?3:2;
|
||||
let buffer = E.toArrayBuffer(s.read(filename, offset, 2*non_empty_tiles_number+bytes*non_empty_tiles_number));
|
||||
this.non_empty_tiles = Uint16Array(buffer, 0, non_empty_tiles_number);
|
||||
offset += 2 * non_empty_tiles_number;
|
||||
if (type_size == 24) {
|
||||
this.non_empty_tiles_ends = Uint24Array(
|
||||
buffer,
|
||||
offset,
|
||||
2*non_empty_tiles_number,
|
||||
non_empty_tiles_number
|
||||
);
|
||||
offset += 3 * non_empty_tiles_number;
|
||||
} else if (type_size == 16) {
|
||||
this.non_empty_tiles_ends = Uint16Array(
|
||||
buffer,
|
||||
offset,
|
||||
2*non_empty_tiles_number,
|
||||
non_empty_tiles_number
|
||||
);
|
||||
offset += 2 * non_empty_tiles_number;
|
||||
|
@ -153,27 +157,29 @@ class TilesOffsets {
|
|||
}
|
||||
|
||||
class Map {
|
||||
constructor(buffer, offset, filename) {
|
||||
constructor(filename, offset) {
|
||||
// header
|
||||
let color_array = Uint8Array(buffer, offset, 3);
|
||||
|
||||
let header = E.toArrayBuffer(s.read(filename, offset, 43));
|
||||
let color_array = Uint8Array(header, 0, 3);
|
||||
this.color = [
|
||||
color_array[0] / 255,
|
||||
color_array[1] / 255,
|
||||
color_array[2] / 255,
|
||||
];
|
||||
offset += 3;
|
||||
this.first_tile = Int32Array(buffer, offset, 2); // absolute tile id of first tile
|
||||
this.first_tile = Int32Array(header, 3, 2); // absolute tile id of first tile
|
||||
offset += 2 * 4;
|
||||
this.grid_size = Uint32Array(buffer, offset, 2); // tiles width and height
|
||||
this.grid_size = Uint32Array(header, 11, 2); // tiles width and height
|
||||
offset += 2 * 4;
|
||||
this.start_coordinates = Float64Array(buffer, offset, 2); // min x and y coordinates
|
||||
this.start_coordinates = Float64Array(header, 19, 2); // min x and y coordinates
|
||||
offset += 2 * 8;
|
||||
let side_array = Float64Array(buffer, offset, 1); // side of a tile
|
||||
let side_array = Float64Array(header, 35, 1); // side of a tile
|
||||
this.side = side_array[0];
|
||||
offset += 8;
|
||||
|
||||
// tiles offsets
|
||||
let res = new TilesOffsets(buffer, offset);
|
||||
let res = new TilesOffsets(filename, offset);
|
||||
this.tiles_offsets = res[0];
|
||||
offset = res[1];
|
||||
|
||||
|
@ -246,7 +252,7 @@ class Map {
|
|||
let tile_y = absolute_tile_y - this.first_tile[1];
|
||||
let side = img.getWidth() - 6;
|
||||
|
||||
let thick = this.color[0] != 0;
|
||||
let thick = this.color[0] == 1;
|
||||
img.setColor(this.color[0], this.color[1], this.color[2]);
|
||||
|
||||
let tile_num = tile_x + tile_y * this.grid_size[0];
|
||||
|
@ -296,26 +302,25 @@ class Map {
|
|||
}
|
||||
|
||||
class Interests {
|
||||
constructor(buffer, offset) {
|
||||
this.first_tile = Int32Array(buffer, offset, 2); // absolute tile id of first tile
|
||||
constructor(filename, offset) {
|
||||
let header = E.toArrayBuffer(s.read(filename, offset, 40));
|
||||
this.first_tile = Int32Array(header, 0, 2); // absolute tile id of first tile
|
||||
offset += 2 * 4;
|
||||
this.grid_size = Uint32Array(buffer, offset, 2); // tiles width and height
|
||||
this.grid_size = Uint32Array(header, 8, 2); // tiles width and height
|
||||
offset += 2 * 4;
|
||||
this.start_coordinates = Float64Array(buffer, offset, 2); // min x and y coordinates
|
||||
this.start_coordinates = Float64Array(header, 16, 2); // min x and y coordinates
|
||||
offset += 2 * 8;
|
||||
let side_array = Float64Array(buffer, offset, 1); // side of a tile
|
||||
let side_array = Float64Array(header, 32, 1); // side of a tile
|
||||
this.side = side_array[0];
|
||||
offset += 8;
|
||||
|
||||
let res = new TilesOffsets(buffer, offset);
|
||||
let res = new TilesOffsets(filename, offset);
|
||||
offset = res[1];
|
||||
this.offsets = res[0];
|
||||
let end = this.offsets.end_offset();
|
||||
this.binary_interests = new Uint8Array(end);
|
||||
let binary_interests = Uint8Array(buffer, offset, end);
|
||||
for (let i = 0; i < end; i++) {
|
||||
this.binary_interests[i] = binary_interests[i];
|
||||
}
|
||||
let buffer = E.toArrayBuffer(s.read(filename, offset, end));
|
||||
this.binary_interests = Uint8Array(buffer);
|
||||
offset += end;
|
||||
return [this, offset];
|
||||
}
|
||||
|
@ -374,14 +379,15 @@ class Status {
|
|||
this.scale_factor = diagonal_third / maps[0].side; // multiply geo coordinates by this to get pixels coordinates
|
||||
|
||||
if (this.path !== null) {
|
||||
let r = [0];
|
||||
//TODO: float32 ??
|
||||
let r = new Float64Array(this.path.len);
|
||||
// let's do a reversed prefix computations on all distances:
|
||||
// loop on all segments in reversed order
|
||||
let previous_point = null;
|
||||
for (let i = this.path.len - 1; i >= 0; i--) {
|
||||
let point = this.path.point(i);
|
||||
if (previous_point !== null) {
|
||||
r.unshift(r[0] + point.distance(previous_point));
|
||||
r[i] = r[i+1] + point.distance(previous_point);
|
||||
}
|
||||
previous_point = point;
|
||||
}
|
||||
|
@ -634,6 +640,10 @@ class Status {
|
|||
this.display();
|
||||
}
|
||||
display_direction() {
|
||||
let scale_factor = this.scale_factor;
|
||||
if (!this.zoomed_in) {
|
||||
scale_factor *= 3/5;
|
||||
}
|
||||
//TODO: go towards point on path at 20 meter
|
||||
if (this.current_segment === null) {
|
||||
return;
|
||||
|
@ -658,7 +668,7 @@ class Status {
|
|||
this.displayed_position,
|
||||
this.adjusted_cos_direction,
|
||||
this.adjusted_sin_direction,
|
||||
this.scale_factor
|
||||
scale_factor
|
||||
);
|
||||
|
||||
let cos1 = Math.cos(full_angle + 0.6 + Math.PI / 2);
|
||||
|
@ -1076,6 +1086,9 @@ class Status {
|
|||
let half_width = width / 2;
|
||||
let half_height = height / 2 + Y_OFFSET;
|
||||
let scale_factor = this.scale_factor;
|
||||
if (!this.zoomed_in) {
|
||||
scale_factor *= 3/5;
|
||||
}
|
||||
|
||||
if (this.path !== null) {
|
||||
// compute coordinate for projection on path
|
||||
|
@ -1141,8 +1154,11 @@ function load_gps(filename) {
|
|||
.drawString(filename, 0, g.getHeight() - 30);
|
||||
g.flip();
|
||||
|
||||
let buffer = s.readArrayBuffer(filename);
|
||||
let file_size = buffer.length;
|
||||
let file_size;
|
||||
{
|
||||
let buffer = s.readArrayBuffer(filename);
|
||||
file_size = buffer.length;
|
||||
}
|
||||
let offset = 0;
|
||||
|
||||
let path = null;
|
||||
|
@ -1150,60 +1166,51 @@ function load_gps(filename) {
|
|||
let maps = [];
|
||||
let interests = null;
|
||||
while (offset < file_size) {
|
||||
let block_type = Uint8Array(buffer, offset, 1)[0];
|
||||
let block_type = Uint8Array(E.toArrayBuffer(s.read(filename, offset, 1)))[0];
|
||||
offset += 1;
|
||||
if (block_type == 0) {
|
||||
// it's a map
|
||||
console.log("loading map");
|
||||
let res = new Map(buffer, offset, filename);
|
||||
let res = new Map(filename, offset);
|
||||
let map = res[0];
|
||||
offset = res[1];
|
||||
maps.push(map);
|
||||
} else if (block_type == 2) {
|
||||
console.log("loading path");
|
||||
let res = new Path(buffer, offset);
|
||||
let res = new Path(filename, offset);
|
||||
path = res[0];
|
||||
offset = res[1];
|
||||
} else if (block_type == 3) {
|
||||
console.log("loading interests");
|
||||
let res = new Interests(buffer, offset);
|
||||
let res = new Interests(filename, offset);
|
||||
interests = res[0];
|
||||
offset = res[1];
|
||||
} else if (block_type == 4) {
|
||||
console.log("loading heights");
|
||||
let heights_number = path.points.length / 2;
|
||||
heights = Int16Array(buffer, offset, heights_number);
|
||||
let buffer = E.toArrayBuffer(s.read(filename, offset, heights_number*2));
|
||||
heights = Int16Array(buffer);
|
||||
offset += 2 * heights_number;
|
||||
} else {
|
||||
console.log("todo : block type", block_type);
|
||||
}
|
||||
}
|
||||
|
||||
// checksum file size
|
||||
if (offset != file_size) {
|
||||
console.log("invalid file size", file_size, "expected", offset);
|
||||
let msg = "invalid file\nsize " + file_size + "\ninstead of" + offset;
|
||||
E.showAlert(msg).then(function () {
|
||||
E.showAlert();
|
||||
start_gipy(path, maps, interests, heights);
|
||||
});
|
||||
} else {
|
||||
start_gipy(path, maps, interests, heights);
|
||||
}
|
||||
start_gipy(path, maps, interests, heights);
|
||||
}
|
||||
|
||||
class Path {
|
||||
constructor(buffer, offset) {
|
||||
let points_number = Uint16Array(buffer, offset, 1)[0];
|
||||
constructor(filename, offset) {
|
||||
let points_number = Uint16Array(E.toArrayBuffer(s.read(filename, offset, 2)))[0];
|
||||
offset += 2;
|
||||
let waypoints_len = Math.ceil(points_number / 8.0);
|
||||
let buffer = E.toArrayBuffer(s.read(filename, offset, points_number * 16 + waypoints_len));
|
||||
|
||||
// path points
|
||||
this.points = Float64Array(buffer, offset, points_number * 2);
|
||||
this.points = Float64Array(buffer, 0, 2 * points_number);
|
||||
offset += 8 * points_number * 2;
|
||||
|
||||
// path waypoints
|
||||
let waypoints_len = Math.ceil(points_number / 8.0);
|
||||
this.waypoints = Uint8Array(buffer, offset, waypoints_len);
|
||||
this.waypoints = Uint8Array(buffer, points_number * 2, waypoints_len);
|
||||
offset += waypoints_len;
|
||||
|
||||
return [this, offset];
|
||||
|
@ -1370,14 +1377,47 @@ function drawMenu() {
|
|||
menu["Exit"] = function () {
|
||||
load();
|
||||
};
|
||||
Bangle.setLCDBrightness(settings.brightness);
|
||||
Bangle.setLocked(false);
|
||||
E.showMenu(menu);
|
||||
}
|
||||
|
||||
|
||||
function ask_options(fn) {
|
||||
g.clear();
|
||||
let height = g.getHeight();
|
||||
let width = g.getWidth();
|
||||
g.drawRect(10, 10, width - 10, height / 2 - 10);
|
||||
g.drawRect(10, height/2 + 10, width - 10, height - 10);
|
||||
g.setFont("Vector:30").setFontAlign(0,0).drawString("Forward", width/2, height/4);
|
||||
g.drawString("Backward", width/2, 3*height/4);
|
||||
g.flip();
|
||||
|
||||
function options_select(b, xy) {
|
||||
end = false;
|
||||
if (xy.y < height / 2 - 10) {
|
||||
g.setColor(0, 0, 0).fillRect(10, 10, width - 10, height / 2 - 10);
|
||||
g.setColor(1, 1, 1).setFont("Vector:30").setFontAlign(0,0).drawString("Forward", width/2, height/4);
|
||||
end = true;
|
||||
} else if (xy.y > height/2 + 10) {
|
||||
g.setColor(0, 0, 0).fillRect(10, height/2 + 10, width - 10, height - 10);
|
||||
g.setColor(1, 1, 1).setFont("Vector:30").setFontAlign(0,0).drawString("Backward", width/2, 3*height/4);
|
||||
go_backwards = true;
|
||||
end = true;
|
||||
}
|
||||
if (end) {
|
||||
g.flip();
|
||||
Bangle.removeListener("touch", options_select);
|
||||
console.log("loading", fn);
|
||||
load_gps(fn);
|
||||
}
|
||||
}
|
||||
Bangle.on("touch", options_select);
|
||||
}
|
||||
|
||||
function start(fn) {
|
||||
E.showMenu();
|
||||
console.log("loading", fn);
|
||||
|
||||
load_gps(fn);
|
||||
ask_options(fn);
|
||||
}
|
||||
|
||||
function start_gipy(path, maps, interests, heights) {
|
||||
|
@ -1387,7 +1427,9 @@ function start_gipy(path, maps, interests, heights) {
|
|||
NRF.sleep(); // disable bluetooth completely
|
||||
}
|
||||
|
||||
console.log("creating status");
|
||||
status = new Status(path, maps, interests, heights);
|
||||
console.log("done creating status");
|
||||
|
||||
setWatch(
|
||||
function () {
|
||||
|
@ -1491,6 +1533,7 @@ function start_gipy(path, maps, interests, heights) {
|
|||
});
|
||||
|
||||
if (simulated) {
|
||||
console.log("un-comment simulator to use it");
|
||||
// status.starting_time = getTime();
|
||||
// // let's keep the screen on in simulations
|
||||
// Bangle.setLCDTimeout(0);
|
||||
|
@ -1518,8 +1561,8 @@ function start_gipy(path, maps, interests, heights) {
|
|||
// if (point_index >= status.path.len / 2 - 1) {
|
||||
// return;
|
||||
// }
|
||||
// let p1 = status.path.point(2 * point_index); // use these to approximately follow path
|
||||
// let p2 = status.path.point(2 * (point_index + 1));
|
||||
// let p1 = status.path.point(8 * point_index); // use these to approximately follow path
|
||||
// let p2 = status.path.point(8 * (point_index + 1));
|
||||
// //let p1 = status.path.point(point_index); // use these to strictly follow path
|
||||
// //let p2 = status.path.point(point_index + 1);
|
||||
|
||||
|
|
|
@ -55,6 +55,9 @@
|
|||
<label for="autodetect_waypoints">detect waypoints</label>
|
||||
<input type="checkbox" id="include_elevation" name="include_elevation" checked/>
|
||||
<label for="include_elevation">include elevation</label>
|
||||
<input type="checkbox" id="ski" name="ski"/>
|
||||
<label for="ski">ski</label>
|
||||
|
||||
<div id="status"></div>
|
||||
<div id="svgmap"></div>
|
||||
|
||||
|
@ -140,7 +143,9 @@ if ((e.which !== 1) && (e.button !== 1)) { return; }
|
|||
this._map.containerPointToLatLng(this._point));
|
||||
let south_west = bounds.getSouthWest();
|
||||
let north_east = bounds.getNorthEast();
|
||||
gps_data = gps_from_area(south_west.lng, south_west.lat, north_east.lng, north_east.lat);
|
||||
let ski = document.getElementById("ski").checked;
|
||||
console.log("ski", ski);
|
||||
gps_data = gps_from_area(south_west.lng, south_west.lat, north_east.lng, north_east.lat, ski);
|
||||
document.getElementById('gps_file').value = "";
|
||||
display_polygon(this._map);
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
"id": "gipy",
|
||||
"name": "Gipy",
|
||||
"shortName": "Gipy",
|
||||
"version": "0.23",
|
||||
"version": "0.24",
|
||||
"description": "Follow gpx files using the gps. Don't get lost in your bike trips and hikes.",
|
||||
"allow_emulator":false,
|
||||
"icon": "gipy.png",
|
||||
|
|
|
@ -53,9 +53,10 @@ export function load_gps_from_string(input: string, autodetect_waypoints: boolea
|
|||
* @param {number} ymin
|
||||
* @param {number} xmax
|
||||
* @param {number} ymax
|
||||
* @param {boolean} ski
|
||||
* @returns {Gps}
|
||||
*/
|
||||
export function gps_from_area(xmin: number, ymin: number, xmax: number, ymax: number): Gps;
|
||||
export function gps_from_area(xmin: number, ymin: number, xmax: number, ymax: number, ski: boolean): Gps;
|
||||
/**
|
||||
*/
|
||||
export class Gps {
|
||||
|
@ -75,15 +76,15 @@ export interface InitOutput {
|
|||
readonly get_gps_content: (a: number, b: number) => void;
|
||||
readonly request_map: (a: number, b: number, c: number, d: number, e: number, f: number, g: number, h: number, i: number, j: number, k: number, l: number, m: number, n: number, o: number, p: number, q: number) => number;
|
||||
readonly load_gps_from_string: (a: number, b: number, c: number) => number;
|
||||
readonly gps_from_area: (a: number, b: number, c: number, d: number) => number;
|
||||
readonly gps_from_area: (a: number, b: number, c: number, d: number, e: number) => number;
|
||||
readonly __wbindgen_malloc: (a: number) => number;
|
||||
readonly __wbindgen_realloc: (a: number, b: number, c: number) => number;
|
||||
readonly __wbindgen_export_2: WebAssembly.Table;
|
||||
readonly wasm_bindgen__convert__closures__invoke1_mut__hef038f7a61abd0f6: (a: number, b: number, c: number) => void;
|
||||
readonly wasm_bindgen__convert__closures__invoke1_mut__hc18aa489d857d6a0: (a: number, b: number, c: number) => void;
|
||||
readonly __wbindgen_add_to_stack_pointer: (a: number) => number;
|
||||
readonly __wbindgen_free: (a: number, b: number) => void;
|
||||
readonly __wbindgen_exn_store: (a: number) => void;
|
||||
readonly wasm_bindgen__convert__closures__invoke2_mut__h545ed49cfafdda52: (a: number, b: number, c: number, d: number) => void;
|
||||
readonly wasm_bindgen__convert__closures__invoke2_mut__h41c3b5af183df3b2: (a: number, b: number, c: number, d: number) => void;
|
||||
}
|
||||
|
||||
export type SyncInitInput = BufferSource | WebAssembly.Module;
|
||||
|
|
|
@ -205,7 +205,7 @@ function makeMutClosure(arg0, arg1, dtor, f) {
|
|||
return real;
|
||||
}
|
||||
function __wbg_adapter_24(arg0, arg1, arg2) {
|
||||
wasm.wasm_bindgen__convert__closures__invoke1_mut__hef038f7a61abd0f6(arg0, arg1, addHeapObject(arg2));
|
||||
wasm.wasm_bindgen__convert__closures__invoke1_mut__hc18aa489d857d6a0(arg0, arg1, addHeapObject(arg2));
|
||||
}
|
||||
|
||||
function _assertClass(instance, klass) {
|
||||
|
@ -373,10 +373,11 @@ export function load_gps_from_string(input, autodetect_waypoints) {
|
|||
* @param {number} ymin
|
||||
* @param {number} xmax
|
||||
* @param {number} ymax
|
||||
* @param {boolean} ski
|
||||
* @returns {Gps}
|
||||
*/
|
||||
export function gps_from_area(xmin, ymin, xmax, ymax) {
|
||||
const ret = wasm.gps_from_area(xmin, ymin, xmax, ymax);
|
||||
export function gps_from_area(xmin, ymin, xmax, ymax, ski) {
|
||||
const ret = wasm.gps_from_area(xmin, ymin, xmax, ymax, ski);
|
||||
return Gps.__wrap(ret);
|
||||
}
|
||||
|
||||
|
@ -388,7 +389,7 @@ function handleError(f, args) {
|
|||
}
|
||||
}
|
||||
function __wbg_adapter_86(arg0, arg1, arg2, arg3) {
|
||||
wasm.wasm_bindgen__convert__closures__invoke2_mut__h545ed49cfafdda52(arg0, arg1, addHeapObject(arg2), addHeapObject(arg3));
|
||||
wasm.wasm_bindgen__convert__closures__invoke2_mut__h41c3b5af183df3b2(arg0, arg1, addHeapObject(arg2), addHeapObject(arg3));
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -475,6 +476,17 @@ function getImports() {
|
|||
const ret = fetch(getObject(arg0));
|
||||
return addHeapObject(ret);
|
||||
};
|
||||
imports.wbg.__wbg_signal_31753ac644b25fbb = function(arg0) {
|
||||
const ret = getObject(arg0).signal;
|
||||
return addHeapObject(ret);
|
||||
};
|
||||
imports.wbg.__wbg_new_6396e586b56e1dff = function() { return handleError(function () {
|
||||
const ret = new AbortController();
|
||||
return addHeapObject(ret);
|
||||
}, arguments) };
|
||||
imports.wbg.__wbg_abort_064ae59cda5cd244 = function(arg0) {
|
||||
getObject(arg0).abort();
|
||||
};
|
||||
imports.wbg.__wbg_new_2d0053ee81e4dd2a = function() { return handleError(function () {
|
||||
const ret = new Headers();
|
||||
return addHeapObject(ret);
|
||||
|
@ -515,17 +527,6 @@ function getImports() {
|
|||
const ret = getObject(arg0).fetch(getObject(arg1));
|
||||
return addHeapObject(ret);
|
||||
};
|
||||
imports.wbg.__wbg_signal_31753ac644b25fbb = function(arg0) {
|
||||
const ret = getObject(arg0).signal;
|
||||
return addHeapObject(ret);
|
||||
};
|
||||
imports.wbg.__wbg_new_6396e586b56e1dff = function() { return handleError(function () {
|
||||
const ret = new AbortController();
|
||||
return addHeapObject(ret);
|
||||
}, arguments) };
|
||||
imports.wbg.__wbg_abort_064ae59cda5cd244 = function(arg0) {
|
||||
getObject(arg0).abort();
|
||||
};
|
||||
imports.wbg.__wbg_newwithstrandinit_05d7180788420c40 = function() { return handleError(function (arg0, arg1, arg2) {
|
||||
const ret = new Request(getStringFromWasm0(arg0, arg1), getObject(arg2));
|
||||
return addHeapObject(ret);
|
||||
|
@ -694,8 +695,8 @@ function getImports() {
|
|||
const ret = wasm.memory;
|
||||
return addHeapObject(ret);
|
||||
};
|
||||
imports.wbg.__wbindgen_closure_wrapper2354 = function(arg0, arg1, arg2) {
|
||||
const ret = makeMutClosure(arg0, arg1, 299, __wbg_adapter_24);
|
||||
imports.wbg.__wbindgen_closure_wrapper2356 = function(arg0, arg1, arg2) {
|
||||
const ret = makeMutClosure(arg0, arg1, 293, __wbg_adapter_24);
|
||||
return addHeapObject(ret);
|
||||
};
|
||||
|
||||
|
|
|
@ -10,12 +10,12 @@ export function get_polyline(a: number, b: number): void;
|
|||
export function get_gps_content(a: number, b: number): void;
|
||||
export function request_map(a: number, b: number, c: number, d: number, e: number, f: number, g: number, h: number, i: number, j: number, k: number, l: number, m: number, n: number, o: number, p: number, q: number): number;
|
||||
export function load_gps_from_string(a: number, b: number, c: number): number;
|
||||
export function gps_from_area(a: number, b: number, c: number, d: number): number;
|
||||
export function gps_from_area(a: number, b: number, c: number, d: number, e: number): number;
|
||||
export function __wbindgen_malloc(a: number): number;
|
||||
export function __wbindgen_realloc(a: number, b: number, c: number): number;
|
||||
export const __wbindgen_export_2: WebAssembly.Table;
|
||||
export function wasm_bindgen__convert__closures__invoke1_mut__hef038f7a61abd0f6(a: number, b: number, c: number): void;
|
||||
export function wasm_bindgen__convert__closures__invoke1_mut__hc18aa489d857d6a0(a: number, b: number, c: number): void;
|
||||
export function __wbindgen_add_to_stack_pointer(a: number): number;
|
||||
export function __wbindgen_free(a: number, b: number): void;
|
||||
export function __wbindgen_exn_store(a: number): void;
|
||||
export function wasm_bindgen__convert__closures__invoke2_mut__h545ed49cfafdda52(a: number, b: number, c: number, d: number): void;
|
||||
export function wasm_bindgen__convert__closures__invoke2_mut__h41c3b5af183df3b2(a: number, b: number, c: number, d: number): void;
|
||||
|
|
|
@ -8,3 +8,4 @@
|
|||
0.08: Handling exceptions
|
||||
0.09: Add option for showing battery high mark
|
||||
0.10: Fix background color
|
||||
0.11: Minor code improvements
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
"name": "A Battery Widget (with percentage) - Hanks Mod",
|
||||
"shortName":"H Battery Widget",
|
||||
"icon": "widget.png",
|
||||
"version":"0.10",
|
||||
"version":"0.11",
|
||||
"type": "widget",
|
||||
"supports": ["BANGLEJS", "BANGLEJS2"],
|
||||
"readme": "README.md",
|
||||
|
|
|
@ -24,8 +24,7 @@
|
|||
var s = width - 1;
|
||||
var x = this.x;
|
||||
var y = this.y;
|
||||
if ((typeof x === 'undefined') || (typeof y === 'undefined')) {
|
||||
} else {
|
||||
if (x !== undefined && y !== undefined) {
|
||||
g.setBgColor(COLORS.white);
|
||||
g.clearRect(old_x, old_y, old_x + width, old_y + height);
|
||||
|
||||
|
|
|
@ -3,3 +3,4 @@
|
|||
0.03: Update clock_info to avoid a redraw
|
||||
0.04: Fix clkinfo -- use .get instead of .show
|
||||
0.05: Use clock_info module as an app
|
||||
0.06: Fix month in date (jan 0 -> 1, etc)
|
|
@ -112,7 +112,7 @@ function getTime(){
|
|||
|
||||
function getDate(){
|
||||
var date = new Date();
|
||||
return twoD(date.getDate()) + "." + twoD(date.getMonth());
|
||||
return twoD(date.getDate()) + "." + twoD(date.getMonth() + 1);
|
||||
}
|
||||
|
||||
function getDay(){
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"id": "linuxclock",
|
||||
"name": "Linux Clock",
|
||||
"version": "0.05",
|
||||
"version": "0.06",
|
||||
"description": "A Linux inspired clock.",
|
||||
"readme": "README.md",
|
||||
"icon": "app.png",
|
||||
|
|
|
@ -790,7 +790,8 @@ var locales = {
|
|||
trans: { yes: "ja", Yes: "Ja", no: "nei", No: "Nei", ok: "ok", on: "på", off: "av", "< Back": "< Tilbake", "Delete": "Slett", "Mark Unread": "Merk som ulest" }
|
||||
},
|
||||
"ca_ES": {
|
||||
lang: "es_ES",
|
||||
lang: "ca_ES",
|
||||
icon: "🇪🇺",
|
||||
decimal_point: ",",
|
||||
thousands_sep: ".",
|
||||
currency_symbol: "€",
|
||||
|
@ -800,7 +801,7 @@ var locales = {
|
|||
temperature: "°C",
|
||||
ampm: { 0: "", 1: "" },
|
||||
timePattern: { 0: "%HH:%MM:%SS", 1: "%HH:%MM" },
|
||||
datePattern: { 0: "%A, %d de %B %Y", "1": "%d/%m/%y" },
|
||||
datePattern: { 0: "%d %B %Y", "1": "%d/%m/%y" },
|
||||
abmonth: "gen.,febr.,març,abr.,maig,juny,jul.,ag.,set.,oct.,nov.,des.",
|
||||
month: "gener,febrer,març,abril,maig,juny,juliol,agost,setembre,octobre,novembre,desembre",
|
||||
abday: "dg.,dl.,dt.,dc.,dj.,dv.,ds.",
|
||||
|
|
|
@ -101,4 +101,6 @@
|
|||
0.72: Nav message updastes don't automatically launch navigation menu unless they're new
|
||||
0.73: Add sharp left+right nav icons
|
||||
0.74: Add option for driving on left (affects roundabout icons in navigation)
|
||||
0.75: Handle text with images in messages list by just displaying the first line
|
||||
0.75: Handle text with images in messages list by just displaying the first line
|
||||
0.76: Swipe up/down on a shown message to show the next newer/older message.
|
||||
|
||||
|
|
|
@ -38,6 +38,7 @@ When a message is shown, you'll see a screen showing the message title and text.
|
|||
* The 'back-arrow' button (or physical button on Bangle.js 2) goes back to Messages, marking the current message as read.
|
||||
* The top-left icon shows more options, for instance deleting the message of marking unread
|
||||
* On Bangle.js 2 you can tap on the message body to view a scrollable version of the title and text (or can use the top-left icon + `View Message`)
|
||||
- On Bangle.js 2 swipe up/down to show newer/older message
|
||||
* If shown, the 'tick' button:
|
||||
* **Android** opens the notification on the phone
|
||||
* **iOS** responds positively to the notification (accept call/etc)
|
||||
|
|
|
@ -289,7 +289,8 @@ function showMessageSettings(msg) {
|
|||
}
|
||||
|
||||
function showMessage(msgid) {
|
||||
var msg = MESSAGES.find(m=>m.id==msgid);
|
||||
let idx = MESSAGES.findIndex(m=>m.id==msgid);
|
||||
var msg = MESSAGES[idx];
|
||||
if (updateLabelsInterval) {
|
||||
clearInterval(updateLabelsInterval);
|
||||
updateLabelsInterval=undefined;
|
||||
|
@ -389,9 +390,11 @@ function showMessage(msgid) {
|
|||
{type:"h",fillx:1, c: footer}
|
||||
]},{back:goBack});
|
||||
|
||||
Bangle.swipeHandler = lr => {
|
||||
Bangle.swipeHandler = (lr,ud) => {
|
||||
if (lr>0 && posHandler) posHandler();
|
||||
if (lr<0 && negHandler) negHandler();
|
||||
if (ud>0 && idx<MESSAGES.length-1) showMessage(MESSAGES[idx+1].id);
|
||||
if (ud<0 && idx>0) showMessage(MESSAGES[idx-1].id);
|
||||
};
|
||||
Bangle.on("swipe", Bangle.swipeHandler);
|
||||
g.reset().clearRect(Bangle.appRect);
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
"id": "messagegui",
|
||||
"name": "Message UI",
|
||||
"shortName": "Messages",
|
||||
"version": "0.75",
|
||||
"version": "0.76",
|
||||
"description": "Default app to display notifications from iOS and Gadgetbridge/Android",
|
||||
"icon": "app.png",
|
||||
"type": "app",
|
||||
|
|
|
@ -6,3 +6,4 @@
|
|||
0.06: Support fastloading
|
||||
0.07: Fix fastloading support - ensure drag handler's restored after
|
||||
menu display/fastload removes it
|
||||
0.08: Add setting for initial page to display
|
||||
|
|
|
@ -78,7 +78,7 @@ function drawTimers() {
|
|||
}, 1000 - (timers[idx].t % 1000));
|
||||
}
|
||||
|
||||
E.showScroller({
|
||||
const s = E.showScroller({
|
||||
h : 40, c : timers.length+2,
|
||||
back : function() {load();},
|
||||
draw : (idx, r) => {
|
||||
|
@ -138,7 +138,7 @@ function timerMenu(idx) {
|
|||
}, 1000 - (a.t % 1000));
|
||||
}
|
||||
|
||||
E.showScroller({
|
||||
const s = E.showScroller({
|
||||
h : 40, c : 5,
|
||||
back : function() {
|
||||
clearInt();
|
||||
|
@ -328,9 +328,22 @@ function editTimer(idx, a) {
|
|||
setUI();
|
||||
}
|
||||
|
||||
function readJson() {
|
||||
let json = require("Storage").readJSON("multitimer.json", true) || {};
|
||||
|
||||
if (Array.isArray(json)) {
|
||||
// old format, convert
|
||||
json = { sw: json };
|
||||
require("Storage").writeJSON("multitimer.json", json);
|
||||
}
|
||||
if (!json.sw) json.sw = [];
|
||||
|
||||
return json;
|
||||
}
|
||||
|
||||
function drawSw() {
|
||||
layer = 1;
|
||||
const sw = require("Storage").readJSON("multitimer.json", true) || [];
|
||||
const sw = readJson().sw;
|
||||
|
||||
function updateTimers(idx) {
|
||||
if (!timerInt1[idx]) timerInt1[idx] = setTimeout(function() {
|
||||
|
@ -341,7 +354,7 @@ function drawSw() {
|
|||
}, 1000 - (sw[idx].t % 1000));
|
||||
}
|
||||
|
||||
E.showScroller({
|
||||
const s = E.showScroller({
|
||||
h : 40, c : sw.length+2,
|
||||
back : function() {load();},
|
||||
draw : (idx, r) => {
|
||||
|
@ -382,12 +395,13 @@ function drawSw() {
|
|||
|
||||
function swMenu(idx, a) {
|
||||
layer = -1;
|
||||
const sw = require("Storage").readJSON("multitimer.json", true) || [];
|
||||
const json = readJson();
|
||||
const sw = json.sw;
|
||||
if (sw[idx]) a = sw[idx];
|
||||
else {
|
||||
a = {"t" : 0, "on" : false, "msg" : ""};
|
||||
sw[idx] = a;
|
||||
require("Storage").writeJSON("multitimer.json", sw);
|
||||
require("Storage").writeJSON("multitimer.json", json);
|
||||
}
|
||||
|
||||
function updateTimer() {
|
||||
|
@ -408,7 +422,7 @@ function swMenu(idx, a) {
|
|||
}
|
||||
else delete a.msg;
|
||||
sw[idx] = a;
|
||||
require("Storage").writeJSON("multitimer.json", sw);
|
||||
require("Storage").writeJSON("multitimer.json", json);
|
||||
swMenu(idx, a);
|
||||
});
|
||||
}
|
||||
|
@ -420,7 +434,7 @@ function swMenu(idx, a) {
|
|||
setUI();
|
||||
}
|
||||
|
||||
E.showScroller({
|
||||
const s = E.showScroller({
|
||||
h : 40, c : 5,
|
||||
back : function() {
|
||||
clearInt();
|
||||
|
@ -458,7 +472,7 @@ function swMenu(idx, a) {
|
|||
select : (i) => {
|
||||
|
||||
function saveAndReload() {
|
||||
require("Storage").writeJSON("multitimer.json", sw);
|
||||
require("Storage").writeJSON("multitimer.json", json);
|
||||
s.draw();
|
||||
}
|
||||
|
||||
|
@ -707,5 +721,17 @@ function onDrag(e) {
|
|||
}
|
||||
}
|
||||
|
||||
drawTimers();
|
||||
switch (readJson().initialScreen) {
|
||||
case 1:
|
||||
drawSw();
|
||||
break;
|
||||
case 2:
|
||||
drawAlarms();
|
||||
break;
|
||||
case 0:
|
||||
case undefined:
|
||||
default:
|
||||
drawTimers();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"id": "multitimer",
|
||||
"name": "Multi Timer",
|
||||
"version": "0.07",
|
||||
"version": "0.08",
|
||||
"description": "Set timers and chronographs (stopwatches) and watch them count down in real time. Pause, create, edit, and delete timers and chronos, and add custom labels/messages. Also sets alarms.",
|
||||
"icon": "app.png",
|
||||
"screenshots": [
|
||||
|
@ -16,6 +16,7 @@
|
|||
{"name":"multitimer.app.js","url":"app.js"},
|
||||
{"name":"multitimer.boot.js","url":"boot.js"},
|
||||
{"name":"multitimer.alarm.js","url":"alarm.js"},
|
||||
{"name":"multitimer.settings.js","url":"settings.js"},
|
||||
{"name":"multitimer.img","url":"app-icon.js","evaluate":true}
|
||||
],
|
||||
"data": [{"name":"multitimer.json"}],
|
||||
|
|
|
@ -0,0 +1,32 @@
|
|||
(function(back) {
|
||||
const file = "multitimer.json";
|
||||
let json = require('Storage').readJSON(file, true) || {};
|
||||
if (Array.isArray(json)) {
|
||||
// old format, convert
|
||||
json = { sw: json };
|
||||
}
|
||||
if (!json.sw) json.sw = [];
|
||||
|
||||
function writeSettings() {
|
||||
require('Storage').writeJSON(file, json);
|
||||
}
|
||||
|
||||
const screens = ["Timers", "Chronos", "Alarms"];
|
||||
|
||||
E.showMenu({
|
||||
"": {
|
||||
"title": "multitimer"
|
||||
},
|
||||
"< Back": back,
|
||||
"Initial screen": {
|
||||
value: json.initialScreen || 0,
|
||||
min: 0,
|
||||
max: screens.length - 1,
|
||||
format: v => screens[v],
|
||||
onchange: v => {
|
||||
json.initialScreen = v;
|
||||
writeSettings();
|
||||
}
|
||||
},
|
||||
});
|
||||
});
|
|
@ -85,7 +85,7 @@ function novaOpenEyes(speed, white, animation) {
|
|||
scale: 2.2
|
||||
});
|
||||
}, speed * 5);
|
||||
} else {}
|
||||
}
|
||||
} else {
|
||||
|
||||
g.drawImage(novaEyesStage4(), -10, -10, {
|
||||
|
@ -126,7 +126,7 @@ function novaOpenEyes(speed, white, animation) {
|
|||
});
|
||||
open = true;
|
||||
}, speed * 5);
|
||||
} else {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -136,7 +136,7 @@ function novaCloseEyes(speed, white, animation) {
|
|||
g.drawImage(novaEyesStage0(), -10, -10, {
|
||||
scale: 2.2
|
||||
});
|
||||
} else {}
|
||||
}
|
||||
setTimeout(function() {
|
||||
g.drawImage(novaEyesStage1(), -10, -10, {
|
||||
scale: 2.2
|
||||
|
@ -164,7 +164,7 @@ function novaCloseEyes(speed, white, animation) {
|
|||
g.drawImage(novaEyesWhiteStage0(), -10, -10, {
|
||||
scale: 2.2
|
||||
});
|
||||
} else {}
|
||||
}
|
||||
setTimeout(function() {
|
||||
timedraw(true);
|
||||
g.drawImage(novaEyesTransStage1(), -10, -10, {
|
||||
|
|
|
@ -4,3 +4,4 @@
|
|||
0.04: Update cards to draw rounded on newer firmware. Make sure in-game menu can't be pulled up during end of game.
|
||||
0.05: add confirmation prompt to new game to prevent fat fingering new game during existing one.
|
||||
0.06: fix AI logic typo and add prompt to show what AI played each turn.
|
||||
0.07: Minor code improvements.
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
"name": "Red 7 Card Game",
|
||||
"shortName" : "Red 7",
|
||||
"icon": "icon.png",
|
||||
"version":"0.06",
|
||||
"version":"0.07",
|
||||
"description": "An implementation of the card game Red 7 for your watch. Play against the AI and be the last player still in the game to win!",
|
||||
"tags": "game",
|
||||
"supports":["BANGLEJS2"],
|
||||
|
|
|
@ -486,8 +486,7 @@ function canPlay(hand, palette, otherPalette) {
|
|||
} else {
|
||||
//Check if any palette play can win with rule.
|
||||
for(let h of hand.handCards) {
|
||||
if(h === c) {}
|
||||
else {
|
||||
if(h !== c) {
|
||||
clonePalette.addCard(c);
|
||||
if(isWinningCombo(c, clonePalette, otherPalette)) {
|
||||
return true;
|
||||
|
@ -531,8 +530,7 @@ class AI {
|
|||
} else {
|
||||
//Check if any palette play can win with rule.
|
||||
for(let h of this.hand.handCards) {
|
||||
if(h === c) {}
|
||||
else {
|
||||
if(h !== c) {
|
||||
clonePalette.addCard(h);
|
||||
if(isWinningCombo(c, clonePalette, otherPalette)) {
|
||||
ruleStack.addCard(c);
|
||||
|
|
|
@ -101,7 +101,8 @@ function getData() {
|
|||
uploadBtn.disabled = true;
|
||||
|
||||
Util.showModal("Loading...");
|
||||
Util.readStorageJSON(repJson, reps => {
|
||||
Util.readStorageJSON(repJson, reps_ => {
|
||||
reps = reps_;
|
||||
Util.hideModal();
|
||||
for(const rep of reps){
|
||||
renderRep(rep);
|
||||
|
|
|
@ -23,3 +23,5 @@
|
|||
0.20: Alarm dismiss and snooze events
|
||||
0.21: Fix crash in clock_info
|
||||
0.22: Dated event repeat option
|
||||
0.23: Allow buzzing forever when an alarm fires
|
||||
0.24: Emit alarmReload when alarms change (used by widalarm)
|
||||
|
|
|
@ -14,10 +14,10 @@ Global Settings
|
|||
---------------
|
||||
|
||||
- `Unlock at Buzz` - If `Yes` the alarm/timer will unlock the watch
|
||||
- `Delete Expired Timers` - Default for whether expired timers are removed after firing.
|
||||
- `Default Auto Snooze` - Default _Auto Snooze_ value for newly created alarms (_Alarms_ only)
|
||||
- `Default Snooze` - Default _Snooze_ value for newly created alarms/timers
|
||||
- `Default Repeat` - Default _Repeat_ value for newly created alarms (_Alarms_ only)
|
||||
- `Buzz Count` - The number of buzzes before the watch goes silent
|
||||
- `Buzz Count` - The number of buzzes before the watch goes silent, or "forever" to buzz until stopped.
|
||||
- `Buzz Interval` - The interval between one buzz and the next
|
||||
- `Default Alarm/Timer Pattern` - Default vibration pattern for newly created alarms/timers
|
||||
|
||||
|
|
|
@ -2,6 +2,26 @@
|
|||
<head>
|
||||
<link rel="stylesheet" href="../../css/spectre.min.css">
|
||||
<link rel="stylesheet" href="../../css/spectre-icons.min.css">
|
||||
<style>
|
||||
.multi-select {
|
||||
/* e.g. day <select> */
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
td.single-row {
|
||||
/* no border between single rowspans */
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.btn-center {
|
||||
justify-content: center;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.event-summary {
|
||||
text-align: center;
|
||||
}
|
||||
</style>
|
||||
<script src="../../core/lib/interface.js"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/ical.js/1.5.0/ical.min.js"></script>
|
||||
<script>
|
||||
|
@ -115,15 +135,17 @@ function renderAlarm(alarm, exists) {
|
|||
const localDate = alarm.date ? dateFromAlarm(alarm) : null;
|
||||
|
||||
const tr = document.createElement('tr');
|
||||
const tr2 = document.createElement('tr');
|
||||
tr.classList.add('event-row');
|
||||
tr.dataset.uid = alarm.id;
|
||||
|
||||
const tdType = document.createElement('td');
|
||||
tdType.type = "text";
|
||||
tdType.classList.add('event-summary');
|
||||
tr.appendChild(tdType);
|
||||
const inputTime = document.createElement('input');
|
||||
let type;
|
||||
if (localDate) {
|
||||
tdType.textContent = "Event";
|
||||
type = "Event";
|
||||
inputTime.type = "datetime-local";
|
||||
inputTime.value = localDate.toISOString().slice(0,16);
|
||||
inputTime.onchange = (e => {
|
||||
|
@ -139,18 +161,19 @@ function renderAlarm(alarm, exists) {
|
|||
inputTime.value = `${hours}:${mins}:${secs}`;
|
||||
|
||||
if (alarm.timer) {
|
||||
tdType.textContent = "Timer";
|
||||
type = "Timer";
|
||||
inputTime.onchange = e => {
|
||||
alarm.timer = hmsToMs(inputTime.value);
|
||||
// alarm.t is set on upload
|
||||
};
|
||||
} else {
|
||||
tdType.textContent = "Alarm";
|
||||
type = "Alarm";
|
||||
inputTime.onchange = e => {
|
||||
alarm.t = hmsToMs(inputTime.value);
|
||||
};
|
||||
}
|
||||
}
|
||||
tdType.textContent = type;
|
||||
if (!exists) {
|
||||
const asterisk = document.createElement('sup');
|
||||
asterisk.textContent = '*';
|
||||
|
@ -160,11 +183,33 @@ function renderAlarm(alarm, exists) {
|
|||
inputTime.classList.add('form-input');
|
||||
inputTime.dataset.uid = alarm.id;
|
||||
const tdTime = document.createElement('td');
|
||||
tr.appendChild(tdTime);
|
||||
tdTime.appendChild(inputTime);
|
||||
|
||||
let tdDays;
|
||||
if (type === "Alarm") {
|
||||
tdDays = document.createElement('td');
|
||||
const selectDays = document.createElement('select');
|
||||
selectDays.multiple = true;
|
||||
selectDays.classList.add('form-input', 'multi-select');
|
||||
selectDays.dataset.uid = alarm.id;
|
||||
const days = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
|
||||
const options = days.map((day, i) => {
|
||||
const option = document.createElement('option');
|
||||
option.value = day;
|
||||
option.text = day;
|
||||
option.selected = alarm.dow & (1 << i);
|
||||
if(day !== "Sun")
|
||||
selectDays.appendChild(option);
|
||||
return option;
|
||||
});
|
||||
selectDays.appendChild(options.find(o => o.text === "Sun"));
|
||||
selectDays.onchange = (e => {
|
||||
alarm.dow = options.reduce((bits, opt, i) => bits | (opt.selected ? 1 << i : 0), 0);
|
||||
});
|
||||
tdDays.appendChild(selectDays);
|
||||
}
|
||||
|
||||
const tdSummary = document.createElement('td');
|
||||
tr.appendChild(tdSummary);
|
||||
const inputSummary = document.createElement('input');
|
||||
inputSummary.type = "text";
|
||||
inputSummary.classList.add('event-summary');
|
||||
|
@ -181,9 +226,8 @@ function renderAlarm(alarm, exists) {
|
|||
inputSummary.onchange();
|
||||
|
||||
const tdOptions = document.createElement('td');
|
||||
tr.appendChild(tdOptions);
|
||||
|
||||
const onOffCheck = document.createElement('input');
|
||||
tdOptions.classList.add('btn-center');
|
||||
onOffCheck.type = 'checkbox';
|
||||
onOffCheck.checked = alarm.on;
|
||||
onOffCheck.onchange = e => {
|
||||
|
@ -199,8 +243,6 @@ function renderAlarm(alarm, exists) {
|
|||
tdOptions.appendChild(onOff);
|
||||
|
||||
const tdInfo = document.createElement('td');
|
||||
tr.appendChild(tdInfo);
|
||||
|
||||
const buttonDelete = document.createElement('button');
|
||||
buttonDelete.classList.add('btn');
|
||||
buttonDelete.classList.add('btn-action');
|
||||
|
@ -212,9 +254,22 @@ function renderAlarm(alarm, exists) {
|
|||
buttonDelete.onclick = (e => {
|
||||
alarms = alarms.filter(a => a !== alarm);
|
||||
document.getElementById('events').removeChild(tr);
|
||||
document.getElementById('events').removeChild(tr2);
|
||||
});
|
||||
|
||||
tr.appendChild(tdTime); tdTime.colSpan = 3; tdTime.classList.add('single-row');
|
||||
tr2.appendChild(tdType);
|
||||
tr2.appendChild(tdOptions);
|
||||
tr2.appendChild(tdInfo);
|
||||
if (tdDays) {
|
||||
tr.appendChild(tdDays); tdDays.rowSpan = 2;
|
||||
} else {
|
||||
tdSummary.colSpan = 2;
|
||||
}
|
||||
tr.appendChild(tdSummary); tdSummary.rowSpan = 2;
|
||||
|
||||
document.getElementById('events').appendChild(tr);
|
||||
document.getElementById('events').appendChild(tr2);
|
||||
document.getElementById('upload').disabled = false;
|
||||
}
|
||||
|
||||
|
@ -318,11 +373,9 @@ function onInit() {
|
|||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Type</th>
|
||||
<th>Date/Time</th>
|
||||
<th colspan="3">Time & Options</th>
|
||||
<th>Days</th>
|
||||
<th>Summary</th>
|
||||
<th>On?</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="events">
|
||||
|
|
|
@ -55,10 +55,7 @@ exports.getTimeToAlarm = function(alarm, time) {
|
|||
/// Force a reload of the current alarms and widget
|
||||
exports.reload = function() {
|
||||
eval(require("Storage").read("sched.boot.js"));
|
||||
if (global.WIDGETS && WIDGETS["alarm"]) {
|
||||
WIDGETS["alarm"].reload();
|
||||
Bangle.drawWidgets();
|
||||
}
|
||||
Bangle.emit("alarmReload");
|
||||
};
|
||||
// Factory that creates a new alarm with default values
|
||||
exports.newDefaultAlarm = function () {
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"id": "sched",
|
||||
"name": "Scheduler",
|
||||
"version": "0.22",
|
||||
"version": "0.24",
|
||||
"description": "Scheduling library for alarms and timers",
|
||||
"icon": "app.png",
|
||||
"type": "scheduler",
|
||||
|
|
|
@ -71,7 +71,7 @@ function showAlarm(alarm) {
|
|||
|
||||
const pattern = alarm.vibrate || (alarm.timer ? settings.defaultTimerPattern : settings.defaultAlarmPattern);
|
||||
require("buzz").pattern(pattern).then(() => {
|
||||
if (buzzCount--) {
|
||||
if (buzzCount == null || buzzCount--) {
|
||||
setTimeout(buzz, settings.buzzIntervalMillis);
|
||||
} else if (alarm.as) { // auto-snooze
|
||||
buzzCount = settings.buzzCount;
|
||||
|
|
|
@ -43,12 +43,13 @@
|
|||
},
|
||||
|
||||
/*LANG*/"Buzz Count": {
|
||||
value: settings.buzzCount,
|
||||
min: 5,
|
||||
value: settings.buzzCount == null ? 4 : settings.buzzCount,
|
||||
min: 4,
|
||||
max: 15,
|
||||
step: 1,
|
||||
format: v => v === 4 ? "Forever" : v,
|
||||
onchange: v => {
|
||||
settings.buzzCount = v;
|
||||
settings.buzzCount = v === 4 ? null : v;
|
||||
require("sched").setSettings(settings);
|
||||
}
|
||||
},
|
||||
|
|
|
@ -77,4 +77,5 @@ of 'Select Clock'
|
|||
calibration was done.
|
||||
0.67: Rename 'Wake on BTN1/Touch' to 'Wake on Button/Tap' on Bangle.js 2
|
||||
0.68: Fix syntax error
|
||||
0.69: Add option to wake on double tap
|
||||
0.69: Add option to wake on double tap
|
||||
0.70: Fix load() typo
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"id": "setting",
|
||||
"name": "Settings",
|
||||
"version": "0.69",
|
||||
"version": "0.70",
|
||||
"description": "A menu for setting up Bangle.js",
|
||||
"icon": "settings.png",
|
||||
"tags": "tool,system",
|
||||
|
|