Merge branch 'espruino:master' into sleeplog
|
|
@ -3,4 +3,5 @@ apps/banglerun/rollup.config.js
|
|||
apps/schoolCalendar/fullcalendar/main.js
|
||||
apps/authentiwatch/qr_packed.js
|
||||
apps/qrcode/qr-scanner.umd.min.js
|
||||
apps/gipy/pkg/gpconv.js
|
||||
*.test.js
|
||||
|
|
|
|||
|
|
@ -1,3 +1,6 @@
|
|||
[submodule "EspruinoAppLoaderCore"]
|
||||
path = core
|
||||
url = https://github.com/espruino/EspruinoAppLoaderCore.git
|
||||
[submodule "webtools"]
|
||||
path = webtools
|
||||
url = https://github.com/espruino/EspruinoWebTools.git
|
||||
|
|
|
|||
19
README.md
|
|
@ -255,8 +255,11 @@ and which gives information about the app for the Launcher.
|
|||
// 'app' - an application
|
||||
// 'clock' - a clock - required for clocks to automatically start
|
||||
// 'widget' - a widget
|
||||
// 'module' - this provides a module that can be used with 'require'.
|
||||
// 'provides_modules' should be used if type:module is specified
|
||||
// 'bootloader' - an app that at startup (app.boot.js) but doesn't have a launcher entry for 'app.js'
|
||||
// 'settings' - apps that appear in Settings->Apps (with appname.settings.js) but that have no 'app.js'
|
||||
// 'clkinfo' - Provides a 'myapp.clkinfo.js' file that can be used to display info in clocks - see modules/clock_info.js
|
||||
// 'RAM' - code that runs and doesn't upload anything to storage
|
||||
// 'launch' - replacement 'Launcher'
|
||||
// 'textinput' - provides a 'textinput' library that allows text to be input on the Bangle
|
||||
|
|
@ -266,10 +269,21 @@ and which gives information about the app for the Launcher.
|
|||
// 'locale' - provides 'locale' library for language-specific date/distance/etc
|
||||
// (a version of 'locale' is included in the firmware)
|
||||
"tags": "", // comma separated tag list for searching
|
||||
// common types are:
|
||||
// 'clock' - it's a clock
|
||||
// 'widget' - it is (or provides) a widget
|
||||
// 'outdoors' - useful for outdoor activities
|
||||
// 'tool' - a useful utility (timer, calculator, etc)
|
||||
// 'game' - a game
|
||||
// 'bluetooth' - uses Bluetooth LE
|
||||
// 'system' - used by the system
|
||||
// 'clkinfo' - provides or uses clock_info module for data on your clock face (see modules/clock_info.js)
|
||||
"supports": ["BANGLEJS2"], // List of device IDs supported, either BANGLEJS or BANGLEJS2
|
||||
"dependencies" : { "notify":"type" } // optional, app 'types' we depend on (see "type" above)
|
||||
"dependencies" : { "messages":"app" } // optional, depend on a specific app ID
|
||||
// for instance this will use notify/notifyfs is they exist, or will pull in 'notify'
|
||||
"dependencies" : { "messageicons":"module" } // optional, depend on a specific library to be used with 'require'
|
||||
"provides_modules" : ["messageicons"] // optional, this app provides a module that can be used with 'require'
|
||||
"readme": "README.md", // if supplied, a link to a markdown-style text file
|
||||
// that contains more information about this app (usage, etc)
|
||||
// A 'Read more...' link will be added under the app
|
||||
|
|
@ -454,7 +468,10 @@ It should also add `myappid.json` to `data`, to make sure it is cleaned up when
|
|||
## Modules
|
||||
|
||||
You can include any of [Espruino's modules](https://www.espruino.com/Modules) as
|
||||
normal with `require("modulename")`. If you want to develop your own module for your
|
||||
normal with `require("modulename")`. To include [Bangle's modules](modules) for use in the Web
|
||||
IDE, [upload the modules to internal storage](modules#upload-the-module-to-the-bangles-internal-storage)
|
||||
or [change the IDE's search path](modules#change-the-web-ide-search-path-to-include-banglejs-modules).
|
||||
If you want to develop your own module for your
|
||||
app(s) then you can do that too. Just add the module into the `modules` folder
|
||||
then you can use it from your app as normal.
|
||||
|
||||
|
|
|
|||
|
|
@ -170,10 +170,10 @@
|
|||
</div>
|
||||
</footer>
|
||||
|
||||
<script src="https://www.puck-js.com/puck.js"></script>
|
||||
<script src="webtools/puck.js"></script>
|
||||
<script src="webtools/heatshrink.js"></script>
|
||||
<script src="core/lib/marked.min.js"></script>
|
||||
<script src="core/lib/espruinotools.js"></script>
|
||||
<script src="core/lib/heatshrink.js"></script>
|
||||
<script src="core/js/utils.js"></script>
|
||||
<script src="loader.js"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/jszip/3.7.1/jszip.min.js"></script> <!-- for backup.js -->
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
0.01: AdvCasio first version
|
||||
0.02: Remove un-needed fonts to improve memory usage
|
||||
0.03: Tell clock widgets to hide.
|
||||
0.04: Swipe down to see widgets, step counter now just uses getHealthStatus
|
||||
|
|
|
|||
|
|
@ -1,304 +1,160 @@
|
|||
const storage = require('Storage');
|
||||
|
||||
require("Font6x12").add(Graphics);
|
||||
require("Font8x12").add(Graphics);
|
||||
require("Font7x11Numeric7Seg").add(Graphics);
|
||||
|
||||
function bigThenSmall(big, small, x, y) {
|
||||
g.setFont("7x11Numeric7Seg", 2);
|
||||
g.drawString(big, x, y);
|
||||
x += g.stringWidth(big);
|
||||
g.setFont("8x12");
|
||||
g.drawString(small, x, y);
|
||||
g.setFont("7x11Numeric7Seg", 2);
|
||||
g.drawString(big, x, y);
|
||||
x += g.stringWidth(big);
|
||||
g.setFont("8x12");
|
||||
g.drawString(small, x, y);
|
||||
}
|
||||
|
||||
function getClockBg() {
|
||||
return require("heatshrink").decompress(atob("icVgf/ABv8v4DBx4CB+PH8F+nAGB48fwEHBwXjxwqBuPH//+nAGBBwIjCAwI2D/wGBgIyDI4QGDwAGBHYX/4AGBn4UFEYQpCEYYpCAAMfMhP4FIgABwJ8OEBIA=="));
|
||||
}
|
||||
|
||||
|
||||
// sun, cloud, rain, thunder
|
||||
var iconsWeather = [
|
||||
require("heatshrink").decompress(atob("i8Ugf/ACcfA434BA/AAwsAv0/8F/BAcDwEHHIpECFI3wn4GC/gOC+PAGoXggEH/+ODQgXBGQv/wAbBBAnguEACIn4gfxI4JXFwJmG/kPBA3jSynw")), require("heatshrink").decompress(atob("i0Ugf/AEXggIGE/0A/kPBAmBCIN/A4Y8CgAICwEHBYoUE/ACCj4sDn4CBC4YyDwBrDCgYA3A")), require("heatshrink").decompress(atob("h8Rgf/AAuBAgf8h4FDCwM/AgPA/gFC/0HgEBBQPwnEfDoWAg4jC/gOCAoQmBAQXjFIV//8f//4IQP4j/+gAIB4EcHII4CAoI+DLQJXF/AA==")), require("heatshrink").decompress(atob("h0Pgf/AA8fAYX+g4EC8EBAgXADAeAgAECgAOC/wrCDQIOBBYfwgAaC/kAn4EB/EAv4aDHAeBIg38"))
|
||||
];
|
||||
|
||||
|
||||
function getBackgroundImage() {
|
||||
return require("heatshrink").decompress(atob("2GwghC/AH4A/AH4AMl////wAwURiQECgUzmcxBQQCBiYUBBARW+LAcCAgcPBYgFBkAIFG7kQiAKIiIKBgISOAAJBD//zKQfxK4vyAoMQCgn/ERBhBBYR5BAwR1DB4Y2DgYPCGIQRCCQcP+EfGJI0FEgRSCGAQCCX4JXCkAhDn4lI+HyK4YWBFIPzJYJXHAIMSK4cwJ4I3CAYMzA4cfcRMBdwytBK4i6FK4IUCMgYAEGIITBK4cCaAPwgJXB+fzK4sAgYtCK5EfA4pXR+AmBaIZYCK6KcCAwSjDEYXx/8vK5QRCK4kPK6cDkJREBIMBfgIrDK5svUAIQBAwIaCK4w+DK4YGBK7IaBboIuCK4gFCJwYBBiBCCCgQhHHYgGDgArBK5IGDAYMgJ4Xwn53BGgLVDmBXKAAinDLpJXCAAYhHR4YODn/wJIPyTYZXDE4RXD+ECNILIDAIPwj4xIAAYNCR4fyVIYLFA4KEBBAglKAGUCmcykEAiMQBIURBYM/BgIUEgcz+bTKAH4A/AH4A/AHP/AGY1d+BWCh5X/LCpW1K74fgG/5X/AH5X/K9Bg/K63wK/5XWgBX/K6pWBK/5XU+BWBh5J/K6auCK/5XTVwRfFAH5XOKwRX/K6auDh5I/K6SuDWP5XSVwYADWX6vXK/5XQWQpW/K6auDJP5XWV35XT+Cu/K7Ku/K65H/K6hW/K7EPI35XWIv5XWAH5X/K/4A/K/5X/K/4A/K9cAAH4A/AFzz/AHRX/K/5X/AH5X/K/5X/AH5X/K/4A/K/5X/K/4A/K/5X/K/4A/K/5X/AH5X/K/5X/AH5X/K/5X/AH5X/K/4A/K/5X/K/4A/K/5X/K/4A/K/5X/AH5X/K/5X/AH5X/K/5X/AH5X/K/4A/K/5X/K/4A/K/5X/K/4A/K/5X/AH5X/K/5X/AH5X/K/5X/AH5X/K/4A/K/5X/K/4A/K/5X/K/4A/K/5X/AH5X/K/5X/AH5X/K/40VAH4A/AFzLb+EPDm4AdK/5X/K+PwgEAHy5X9HgMAK/5XXH6xX/H65X/K/5X/K98AK7sAgBX3DjBWFO644DSTHwGzJXED4RXaDoLqcK7weWDIQcXK8I6YK77KXK4o8DPbY6ZK7qvDDy6vdR7JXDh60EDyw5BAIRXYSwjMbAgIhUDwJZCHwJX0GwjRWNwIAEHSwBCDSpXFH4pXzDS5XIEARXVSYbQEDaYzCK+6vcKaxXNDypX9HwQkbHS40COSpXKK2A6CHgRXcPIhX0SwpXYVuQ6EgBX/K644YODBXkSDJX/K/5X/DtRX6gA3YOkRWbLDZX4KwYA/AG8F5vdABncKH4AGhpRJAYXNAgPAKP4AF5vMJwoDBAQIKE6BR/AAvc5vO9wAB7oCB9veAoPcAoPcK+kwh8AgcA98An//gH/+sD//wCISgBJ4IABAYpaC9vdK4UP/9AAQNQr/zgHwEYNQFYQAh+EP+FegH+A4QBCMQIKBAAPNK4yxBA4RXCV4YZBE4IjChwCDmApCK8VdmHggHgFYf0SQJXE5nMK4anCAoYHC5pXCaQJXBop+BqAGEK7f/AAQeEKwQrBqCtDAILjBCQfNK4JTCAYZXF7qvD//gV4S2DgEFFIYAECgIACMC8PKoIBB8n1K4ivF5vc5xOCWYZbBAYavHU4RXCr4pEAEMDfoNQGoMEgEwYQPwAoIBBAAPM5ipC7oDCVIIAE7hXCD4SdBiEP+gGBgihCFYIAz5pXBAAnN7oIB7nc5gOBK4QA/K4pNCWgSpCBInNK/4AGhncKIStC7gCBA4QAC4BR/AAysCABZW/AHwA="));
|
||||
return require("heatshrink").decompress(atob("2GwwkGIf4AfgMRkUiiIHCiMRiAMDAwYCCBAYVDAHMv/4ACkBIBAgPxBgM/BYXyAoICBCowA5gRADKQUDKAYMCmYCBiBXBCo4A5J4MxiMSKQUf+YBBBgSiBgc/kBXBBAMyCoK2CK/btCiUhfAJLCkBkDiMQgBXDCoUvNAJX+AAU/+MB/8wAQIAC+cQK5hoDgIEBBIQFEAYIPHBIgBBAQQIDBwZXSKIMxgJaBgEjmZYCmBXLgLBBkkAgUhiMxBIM0iMSCoMRkZECkQJEichBINDiETAgISBiQTDK6MvJAXzVIQrBBYMCK5E/K4kwGIJXFgdAMgQQBiYiCDgU0HQSlCgMikIEBEAMTDYJXQ+UikYDBj6nCAAMTWoJ6BK4oVEK4c0oQ+BK4MjAgMDJoJXHNYJXHBwa0BohcDY4QAKgJQE+LzBNwJVBkQMEkBXBCoyvFJAVAKISaBiMiHQRIDkVBoSyCK5CvBAgavNDAJAC+cQn5DCgSpBl4MDgBXBgCsBCoYoMLAKREgIKDBJIdKK5oA/AH4A/AH4A/ADUBIH4APiAFEi1mAGUADrkRKwUGK2ZXes1gK2xXfD8A3/K/4AWgxX/ACtga2AwIHLkAgCwvJw6RcDgIABK+w4cK/I4dsEGP5BXtSAQ6BV/5XSG4RX/K6Y3fK+42CK/5XTGwcGK/5XSVwY5cK+o1DAAayYsAhDsCv4K7BTBK4YeYK7CyFVzJXFFIpXtVwYiYK/rmZKYYDDELJXXG4YiaK/Y0aKgQAEK+gkdKt5XGKzqv5GTpX6ETlgK4xWrKTyxKVthXmAGRX/K/5X/AH5X/K/4gBAH4A/AFz/uAH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AHNggEGHfEAgAEHKyQXVK0qTCAggbUK+6SDAApzXK/5BRDYZX3KxBBSYqxXngyvaV25XEd4ZCSsAcBAoRZ2dQZXBLwgaQCIYeCAGirCS4YGCDSJXCC6ZaodYICBZzSw4S4I+XDgSv4K4rzCK/47RAQTMaWHI9YV3TscV3aVagByBK3SwCSqyt8AAQ+XK/4A/AH4A/AH4A3gAA/AH4AuZbdggwc3ADpX/K/5XxsEAgA+XK/o8BgBX/K64/WK/4/XK/5X/K/5XvgBX/K64cYHrw4CSTFggCuXK4oDCEQJXYDS6ScDgg4CPKyRCAAZX0HAgBDK+LlYK4oeBAwZ9aK+lgAoQGBgyvzDIIDBK66sCG4JXYCwIBDK7ADCK+xZCHwJXzGoQ8BK7DpBAAaSXSgRXZO4okCK+IaXV4oABEILSWSYjRCHSo3BDSxXEAAIcBAISvyKawcIAYIGCK/4cUH4YlaHS0AHgI1XOg5YBPrY6WHgRXfAGRXDHzBX8VoJX/K68ADjRX6sBX/K/5X/K8wdcK/UAG7B0iKzZYbK/BWDAH4A/hWpzWhIf4ASgOpzIAB0EAhhH/AB8ZzGJ1WazMA4pH/AB+pxOZxOpzVMqA2ugUzmcgD7cKVYOqzGqpnRFw8ykchK8kviEBmQFBgMiFocSCAcSkUQAgMikRsHhWqxOq0Ut4mqBw0DC4IxBD4wpBHAQMCA4cCGJIAFj8hDIQuBkMTCwU/AYQJBiUxFoPxiIVDK4kyxUz4cxl+KK5MfDQXyD4UCmMSmAEBAQQHDgMTmIxHAAqpBmaqCFwMDEYZRBgEjCQQBB+USK5E/ns/0Uzwc6K48ykYkCK4IfCc4I4CK4QHEBAYAMiICBmYuDmQEBh8iAgRXCLISvJO4MqwcklEiK5CADV4oaBV4oHEK6Eve4JNCbwRfCiMTFoMDkMRSAJXCD49azWp0UqzWayJXIQwcAO4cCkMCFIJOCA4XxK6KPBkR6DTwYyBAwYPEAggfFzORpWK1OZyAOHJ4QfERAUSEgQxIIIgAr1URWIOZzOgGtwAhgMZzWq1OaIv4ASKgOqzTkvAEmq1WgFtQA=="));
|
||||
}
|
||||
|
||||
function getRocketSequences() {
|
||||
return {
|
||||
1: require("heatshrink").decompress(atob("qFGwkCkQAiiEBEkUgKQhPhE8ogCE8YhCiQoEE7pKEPIgncTQ4neEwpQCPoh1eJYYwCJ7QmHKAh1hZIpOjPAUBJ0ZQCTzEhExZ1lPAZ1kKDQmOJ65O2E65OPOy5O2E64mPOyxO/J2wnPJyx2QJ35O/J2khE0p2POq52PEy4nOiQnlOrEhiSfMJrEggQnLJzB1CPBQmZkInMEzBQDPBImbPBR1ZEoRMCZYImhgQgEE0BzFKAgmaDwLDFKAbqdYQwHBOrcgDgLBFJrsiiRNGYbpLBY4Ymhd4omkkUhE0pQEEwUBJjrHBd4QmCdzoiBDwYrCPLyZHF4QnagQeCE8UgJwYniJwgnIOzwfFO0wJCJzMQE4gyFEzR2FBQombkInDQI4AakAnBTYS+ZE5BMDE0LEES7YnLE0R3FAEQA=")),
|
||||
2: require("heatshrink").decompress(atob("qFGwkCkQAikMAgIliKYon/AA0gEAQniEwIhCAgYndEIjqBE8CaGKogmgKAp1fKAgncExBQBBQR1gKAp7BJ0IndExR4CE0idaOpYnbExqeYJxxPYEx0BJ0x2XExx2XJ20QE6xONJi5OPGwJOlBwLFkLoLFlBwJOkOwJOlE4JOkTjBOOE/52Pdi5OPEy7FnE5wmXE5xOZT5gmYEoMiiB1lgR4KTLAkDPBJ1WIAYDDKA4mWJwchDwYEDTjQiDJQh4GYLAhHFosSJy6OCTIxaEEywbBKYwjEEzMgUQxQFBogAURwZOGOjTKJdTYnOEryfHE0JQEfIpQgYQMAgJLeAgrtfTI4ndgSaFE4h0bdQkSZQpOfEAgIBO0AnEdrh2FJAb1EdbInEBIpObOwhOEEzYnFXzZ2HE4QlhE4QlDFMKcDYooniO0QnDT0YnCE0ciA")),
|
||||
3: require("heatshrink").decompress(atob("qFGwkEogAjiMUEkVAKYgnhPYolgOQIniOYZ4FOcLqBE8CaGKojpgKAomhEYUQE7gmHKAIxCE0QkCPYR1gZIgnZExR4CJ0idmE7ZONYzImNgEUJ0p3YJRh2ZJJwnXOpQhBdkpaETsMEGQhOhE7jFLUYpOfTzgmKE4hOiE4hOigEUJ0rvCEywnPEqx2OTjBOOE7ImOTsqeZE5zFYoJOmT5kBJzEAih4LdK5mBAQInKOqoYDEgR4JEypHDEYbxJOq5ABdgZ7CEzZOEJQgnGihOYEIzJFTionCKYxWGEy9ADAYnGUIYmWog/EdBFAEy7KIKAwnjKwLqWE5pMeT48CVQpQfgMjKEtEiAnfEQJQCgJSCTcB6FJzkEdYcUE8FAdQghDOzonKTjh2EZAidcDoInHJzodBOwx/BE8JxcOwsAOwQmhJgSXDObwnFEwUUO0LFGE8aeiE4YmiokQE0tE")),
|
||||
4: require("heatshrink").decompress(atob("qFGwkCkQAjiMSEkRTFE/4AGkMAgQCBE8MgEIYEDE7whDdQIngTQxVEE0ChFTjxQFE7jnFKAgxCOsBQFZgJ1gE7wmKPAROkTrTEHGAwnYiBHJFAaeXOoyXBEQZPac5AsFgJOhAoh2XJwwnFKoROdE4J9GJzwnIiQmVkInPAC0QE5AJFE64mHY5DFdE4SBEYr5JDJ0hKDJ0jCZJxoACgInmKLAmOTq5OOEy5OPTsxOYE5wmXO5wlYkAnMOqshiRNCgR4LOC8CkJCCEzxHDAgYnJOqpAEDoZ4HEyodDEQpQHdCsQOwwFHEyzoCPYzJGEy0gEwaZGA4acVEQSjHKAomXkQYEYAwlZeRKYDE8gjCYa7zJEwcCkImfKAb4FAD0hdTh4LgRSBOcR0CJz0gYYrrgN4QnEYrxOEE4bEeiAnGF4J2idL6VDE8ohBE0gnFE0J0BE4QGBiROgdIQABgJ2hJoTtjYgZSEE8ScgE4omikUQTcQADA=")),
|
||||
5: require("heatshrink").decompress(atob("qFGwkCkQAikMAgIliKYonhiAnjkEATIIniEwIhCAgYndEIhQFYUZVEE0BQFOr5QEeQQmiKAL1DOr5QEE7ROCDgZVEAoInZDwchFQQoDPAJOdEQYrBdrZFDOYwncEJDsDVIpOXgJxEE4pObEAgGFgJOaE48BaIhOZJ5ZObY5ROcE441CE6xOGPAwtCJzpGCJ0hHDkI1DJzwoEJzInLFg52dUo5O/J35OzE54mWOx4mXJxx1XE54mXkUhExkSJzCfMOrAlBPBiZXgQDBAQQmgJgh4JOqoYEFYwmaDoZzEFgh1YDgkiiAFEKAroXJJAGFiQmVkCNDTIz5EJy57HKAomXkQYEJoqaYeRadEJrAnJEQUAgJPiAoYmeT4cCkAnBE0BKCJkT1EkDCeJYYiDOkLDFFL5wBE4guCPDhEBEwQiDY70CkInDiQnCJzkhOwhKDdzp2Idb4nEE0B0Bdo4niE0J0CeYhOhgESUYYnidsgnEE0KeCE0gnDE0ciA")),
|
||||
6: require("heatshrink").decompress(atob("qFGwkCkQA/ABEgKQZPhEwgABEsAoGJkBxBE8JKEAowAbJIhQEgLDiPooAdKA4ncTZAndSwhQEFoInaJQkSKAwlZdgwnfSgYADE4h1ZDwInlcggnIOzAdCE8i7EY5J3XDgYhGd4pOZEI52bSYwGCOAJ2bYIodEOzZOFFAjFcEwwAIE6xOHABBO/J34ndEyx2PJ00BJ00SJ0p1XE54mXOxxO/J5wmYgQnMOrB2BPBgkWiJ1CPBbBYAYR4KiTAXRwIrFTjgZDJYZ4IEyoiEIwrDcEJJQFOqwiBDARxFFwgmXkAYDEogsBF4QmXEQJ7GUYYkBEzDKJAgYmdEQbKFEzonEKYgngJwgmfZggmjKQghgiBRGkBzeTgUikJRgc47LDErTnDEAkQJzkCJwYnEJzonEJIaddOwhJEJzgdBE4hYEJzieJADgnEE0KUCXzoAGkJLEiB2hOgQDBT0TsDT0YmlE4YmjkQ=")),
|
||||
7: require("heatshrink").decompress(atob("qFGwkCkQAhkIpBiQlhkBSEJ8InlEIIoFE7whEE8pQFE7giBJQoneI4MCTYhQDE7YdCYYondEQYnEPwZ1bE5BQCJzonHkR2ZEAkBE4pNBE7zHFYrYhFUgonaXAQeEEwruZEYcgiROHJ7AfDAwxOeAAURiAmHE65HIOzwmOJ35OPE6xOPO35O/J35O/J1gnPEyx2PEy5OOOq5OnE5xOYO5omZgJQMJrQnLiQnagR4JOq5nCDgZ1fEYRLDE5DoZkUQNoZ4GOrJKGAoomXOw7lCAwYmYDgJSEAAUBA4QDBJzB6FOQrDXJwTJFdLjJKE9jDYZRAmkKAwmhKAgmiKAYmBkApdJIgjCKYIncOQYvJYTovGE84lagR2DE4xOakBOEgJXFOjYnEJAbtdOwggEkAmbDgInDE0B0BE4QgcE5AkiXYbpCOLonGYo4nhPMYnCUEgnBY0kiA==")),
|
||||
8: require("heatshrink").decompress(atob("qFGwkCkQA/ABBSEJ8MgE4kBEsBPFE7xMCOIJ3hOYgFEE7rCGE70gE4pQBiAndYQwjBUohOZD4ZQFE7YkBE5AICYbZ2GE7sggJRCAA8iYzZOITroALE7EhExh4CAC0QExpPXOponZExx2XJ24nWdh52XdhzF/Yu5O/J35O0E55OXOx5O/J2omXE5x1XO54mYgQnMJrR4LOrciiAmiJgR4KEzIjDPBAlYiAiEeI51YkEBE4J5CD4KceTQQcBJgRQFdTZDCJIjDcNIqhGdTQmCkByFTTInDKgoAEE7ZEEJwhPdE1R1FE0InEE0R3DEwTGcDwomEE7hKFPYqafE8ROCE5DJbE5B/IEqh2ED4gnCJrMCJwgnEiB2bE4qeFEzUggQmIBQLEaEQImHLIImaE4YfcOw4lEFMLECS7onJO8wmkE4QljAAIA==")),
|
||||
};
|
||||
}
|
||||
|
||||
let rocketSequence = 1;
|
||||
let settings = storage.readJSON("cassioWatch.settings.json", true) || {};
|
||||
let rocketSpeed = settings.rocketSpeed || 700;
|
||||
delete settings;
|
||||
|
||||
// schedule a draw for the next minute
|
||||
let rocketInterval;
|
||||
var drawTimeout;
|
||||
function queueDraw() {
|
||||
if (drawTimeout) clearTimeout(drawTimeout);
|
||||
drawTimeout = setTimeout(function() {
|
||||
drawTimeout = undefined;
|
||||
draw();
|
||||
}, 60000 - (Date.now() % 60000));
|
||||
if (drawTimeout) clearTimeout(drawTimeout);
|
||||
drawTimeout = setTimeout(function() {
|
||||
drawTimeout = undefined;
|
||||
draw();
|
||||
}, 60000 - (Date.now() % 60000));
|
||||
}
|
||||
|
||||
|
||||
function clearIntervals() {
|
||||
if (rocketInterval) clearInterval(rocketInterval);
|
||||
rocketInterval = undefined;
|
||||
if (drawTimeout) clearTimeout(drawTimeout);
|
||||
drawTimeout = undefined;
|
||||
if (rocketInterval) clearInterval(rocketInterval);
|
||||
rocketInterval = undefined;
|
||||
if (drawTimeout) clearTimeout(drawTimeout);
|
||||
drawTimeout = undefined;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////
|
||||
// TIMER FUNC
|
||||
//
|
||||
var timer_time = 0;
|
||||
var alreadyListenTouch = false;
|
||||
function initTouchTimer () {
|
||||
if (alreadyListenTouch) return;
|
||||
alreadyListenTouch = true;
|
||||
|
||||
Bangle.on('swipe', function(dirX,dirY) {
|
||||
if (canTouch === false) return;
|
||||
var njson = getDataJson();
|
||||
if (!njson) return;
|
||||
|
||||
if (dirX === -1) {
|
||||
timer_time = 0;
|
||||
delete njson.timer;
|
||||
setDataJson(njson);
|
||||
}
|
||||
else if (dirX === 1) {
|
||||
var now = new Date().getTime();
|
||||
njson.timer = now + (timer_time * 1000 * 60);
|
||||
Bangle.setLocked(true);
|
||||
setDataJson(njson);
|
||||
Bangle.buzz(200, 0);
|
||||
timer_time = 0;
|
||||
}
|
||||
else if (dirY === -1) {
|
||||
if (canTouch === false || njson.timer) return;
|
||||
timer_time = timer_time + 5;
|
||||
}
|
||||
else if (dirY === 1) {
|
||||
if (canTouch === false || njson.timer) return;
|
||||
timer_time = timer_time - 5;
|
||||
}
|
||||
draw();
|
||||
});
|
||||
}
|
||||
setTimeout(() => {
|
||||
initTouchTimer ();
|
||||
});
|
||||
|
||||
function getTimerTime() {
|
||||
// if timer_time !== -1, take it
|
||||
if (timer_time !== 0) {
|
||||
return timer_time + "m";
|
||||
} else {
|
||||
// else, show diff between njsontime and now
|
||||
var njson = getDataJson();
|
||||
if (!njson) return false;
|
||||
var now = new Date().getTime();
|
||||
var diff = Math.round((njson.timer - now) / (1000 * 60));
|
||||
//console.log(123, njson, diff, now, njson.timer - now);
|
||||
if (diff > 0) return diff + "m";
|
||||
else if (njson.timer) {
|
||||
Bangle.buzz(1000, 1);
|
||||
console.log("END OF TIMER");
|
||||
delete njson.timer;
|
||||
setDataJson(njson);
|
||||
return false;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
// if diff is <0, delete timer from json
|
||||
}
|
||||
}
|
||||
function drawTimer() {
|
||||
//g.drawString(getTimerTime(), 100, 100);
|
||||
g.setFont("8x12", 2);
|
||||
var t = 97;
|
||||
var l = 105;
|
||||
var time = getTimerTime();
|
||||
if (time || timer_time !== 0) g.drawString(time, l+5, t+0);
|
||||
if (time && timer_time === 0) g.drawImage(getClockBg(), l-20, t+2, { scale: 1 });
|
||||
}
|
||||
|
||||
|
||||
////////////////////////////////////////////
|
||||
// DATA READING
|
||||
//
|
||||
function getDataJson(){
|
||||
var res = {"tasks":"", "weather":[]};
|
||||
try {
|
||||
res = storage.readJSON('advcasio.data.json');
|
||||
} catch(ex) {
|
||||
return res;
|
||||
}
|
||||
return res;
|
||||
}
|
||||
function setDataJson(resJson){
|
||||
try {
|
||||
res = storage.writeJSON('advcasio.data.json', resJson);
|
||||
} catch(ex) {
|
||||
return res;
|
||||
}
|
||||
return res;
|
||||
}
|
||||
var dataJson = getDataJson();
|
||||
|
||||
////////////////////////////////////////////
|
||||
// WEATHER!
|
||||
//
|
||||
function drawWeather(arr) {
|
||||
g.setFont("6x8", 1);
|
||||
var p = {l: 8, tText: 40, tIcon:20, decal:25};
|
||||
var today = new Date().getTime();
|
||||
var yesterday = today - (1000 * 60 * 60 * 24);
|
||||
var testday = today + (1000 * 60 * 60 * 24 * 2);
|
||||
//12h auj > 12h hier qui est sup a 0h auj
|
||||
//23h59 hier est sup a 0h auj
|
||||
var j = 0;
|
||||
for(var i = 0; i<arr.length;i++) {
|
||||
if (arr[i][2] > yesterday && j < 4) {
|
||||
g.drawString(arr[i][0], p.l + p.decal*j + 4, p.tText);
|
||||
g.drawImage(iconsWeather[arr[i][1]], p.l + p.decal*j, p.tIcon, { scale: 1 });
|
||||
j++
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
////////////////////////////////////////////
|
||||
// DRAWING FUNCS
|
||||
//
|
||||
function drawTasks(str) {
|
||||
g.setFont("6x8", 1);
|
||||
var t = 57;
|
||||
var l = 0;
|
||||
g.drawString(str, l+5, t+0);
|
||||
}
|
||||
|
||||
function drawSteps() {
|
||||
g.setFont("8x12", 2);
|
||||
var t = 132;
|
||||
var l = 150;
|
||||
g.drawString(getSteps(), l+5, t+0);
|
||||
}
|
||||
|
||||
|
||||
function drawClock() {
|
||||
g.setFont("7x11Numeric7Seg", 3);
|
||||
g.clearRect(80, 57, 170, 96);
|
||||
g.setColor(255, 255, 255);
|
||||
var l = 77;
|
||||
var t = 57;
|
||||
var w = 170;
|
||||
var h = 116;
|
||||
g.drawRect(l, t, w, h);
|
||||
g.fillRect(l, t, w, h);
|
||||
g.setColor(0, 0, 0);
|
||||
g.drawString(require("locale").time(new Date(), 1), 76, 60);
|
||||
|
||||
// day
|
||||
//g.setFont("8x12", 1);
|
||||
//g.setFont("9x18", 1);
|
||||
//g.drawString(require("locale").dow(new Date(), 2).toUpperCase(), 25, 136);
|
||||
g.setFont("8x12", 2);
|
||||
g.drawString(require("locale").dow(new Date(), 2), 18, 130);
|
||||
|
||||
// month
|
||||
g.setFont("8x12");
|
||||
g.drawString(require("locale").month(new Date(), 2).toUpperCase(), 80, 127);
|
||||
|
||||
// day nb
|
||||
g.setFont("8x12", 2);
|
||||
const time = new Date().getDate();
|
||||
g.drawString(time < 10 ? "0" + time : time, 78, 137);
|
||||
g.setFont("7x11Numeric7Seg", 3);
|
||||
g.clearRect(80, 57, 170, 96);
|
||||
g.setColor(0, 255, 255);
|
||||
g.drawRect(80, 57, 170, 96);
|
||||
g.fillRect(80, 57, 170, 96);
|
||||
g.setColor(0, 0, 0);
|
||||
g.drawString(require("locale").time(new Date(), 1), 70, 60);
|
||||
g.setFont("8x12", 2);
|
||||
g.drawString(require("locale").dow(new Date(), 2).toUpperCase(), 18, 130);
|
||||
g.setFont("8x12");
|
||||
g.drawString(require("locale").month(new Date(), 2).toUpperCase(), 80, 126);
|
||||
g.setFont("8x12", 2);
|
||||
const time = new Date().getDate();
|
||||
g.drawString(time < 10 ? "0" + time : time, 78, 137);
|
||||
}
|
||||
|
||||
function drawBattery() {
|
||||
bigThenSmall(E.getBattery(), "%", 140, 23);
|
||||
bigThenSmall(E.getBattery(), "%", 135, 21);
|
||||
}
|
||||
|
||||
function drawRocket() {
|
||||
let Rocket = getRocketSequences();
|
||||
g.clearRect(5, 62, 63, 115);
|
||||
g.setColor(0, 255, 255);
|
||||
g.drawRect(5, 62, 63, 115);
|
||||
g.fillRect(5, 62, 63, 115);
|
||||
g.drawImage(Rocket[rocketSequence], 5, 65, { scale: 0.7 });
|
||||
g.setColor(0, 0, 0);
|
||||
rocketSequence = rocketSequence + 1;
|
||||
if(rocketSequence > 8) rocketSequence = 1;
|
||||
}
|
||||
|
||||
function getTemperature(){
|
||||
try {
|
||||
var weatherJson = storage.readJSON('weather.json');
|
||||
var weather = weatherJson.weather;
|
||||
return Math.round(weather.temp-273.15);
|
||||
|
||||
} catch(ex) {
|
||||
print(ex)
|
||||
return "?"
|
||||
}
|
||||
}
|
||||
|
||||
function getSteps() {
|
||||
var steps = 0;
|
||||
try{
|
||||
if (WIDGETS.wpedom !== undefined) {
|
||||
steps = WIDGETS.wpedom.getSteps();
|
||||
} else if (WIDGETS.activepedom !== undefined) {
|
||||
steps = WIDGETS.activepedom.getSteps();
|
||||
} else {
|
||||
steps = Bangle.getHealthStatus("day").steps;
|
||||
}
|
||||
} catch(ex) {
|
||||
// In case we failed, we can only show 0 steps.
|
||||
return "? k";
|
||||
}
|
||||
|
||||
steps = Math.round(steps/1000);
|
||||
return steps + "k";
|
||||
var steps = Bangle.getHealthStatus("day").steps;
|
||||
steps = Math.round(steps/1000);
|
||||
return steps + "k";
|
||||
}
|
||||
|
||||
|
||||
|
||||
function draw() {
|
||||
|
||||
queueDraw();
|
||||
queueDraw();
|
||||
|
||||
g.reset();
|
||||
g.clear();
|
||||
g.setColor(255, 255, 255);
|
||||
g.fillRect(0, 0, g.getWidth(), g.getHeight());
|
||||
let background = getBackgroundImage();
|
||||
g.drawImage(background, 0, 0, { scale: 1 });
|
||||
|
||||
|
||||
g.setColor(0, 0, 0);
|
||||
if(dataJson && dataJson.weather) drawWeather(dataJson.weather);
|
||||
if(dataJson && dataJson.tasks) drawTasks(dataJson.tasks);
|
||||
|
||||
g.clear(1);
|
||||
g.setColor(0, 255, 255);
|
||||
g.fillRect(0, 0, g.getWidth(), g.getHeight());
|
||||
let background = getBackgroundImage();
|
||||
g.drawImage(background, 0, 0, { scale: 1 });
|
||||
g.setColor(0, 0, 0);
|
||||
g.setFont("6x12");
|
||||
g.drawString("Launching Process", 30, 20);
|
||||
g.setFont("8x12");
|
||||
g.drawString("ACTIVATE", 40, 35);
|
||||
|
||||
g.setFontAlign(0,-1);
|
||||
g.setFont("8x12", 2);
|
||||
g.setFontAlign(0,-1);
|
||||
g.setFont("8x12", 2);
|
||||
g.drawString(getTemperature(), 155, 132);
|
||||
g.drawString(Math.round(Bangle.getHealthStatus("last").bpm), 109, 98);
|
||||
g.drawString(getSteps(), 158, 98);
|
||||
|
||||
drawSteps();
|
||||
g.setFontAlign(-1,-1);
|
||||
drawClock();
|
||||
drawBattery();
|
||||
drawTimer();
|
||||
// Hide widgets
|
||||
for (let wd of WIDGETS) {wd.draw=()=>{};wd.area="";}
|
||||
g.setFontAlign(-1,-1);
|
||||
drawClock();
|
||||
drawRocket();
|
||||
drawBattery();
|
||||
|
||||
// Hide widgets
|
||||
for (let wd of WIDGETS) {wd.draw=()=>{};wd.area="";}
|
||||
}
|
||||
|
||||
// save batt power, does not seem to work although...
|
||||
var canTouch = true;
|
||||
Bangle.on("lcdPower", (on) => {
|
||||
if (on) {
|
||||
draw();
|
||||
} else {
|
||||
canTouch = false;
|
||||
clearIntervals();
|
||||
}
|
||||
if (on) {
|
||||
draw();
|
||||
} else {
|
||||
clearIntervals();
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
Bangle.on("lock", (locked) => {
|
||||
clearIntervals();
|
||||
draw();
|
||||
if (!locked) {
|
||||
canTouch = true;
|
||||
} else {
|
||||
canTouch = false;
|
||||
}
|
||||
clearIntervals();
|
||||
draw();
|
||||
if (!locked) {
|
||||
rocketInterval = setInterval(drawRocket, rocketSpeed);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
Bangle.setUI("clock");
|
||||
|
||||
// Load widgets, but don't show them
|
||||
Bangle.loadWidgets();
|
||||
|
||||
g.reset();
|
||||
g.clear();
|
||||
require("widget_utils").swipeOn(); // hide widgets, make them visible with a swipe
|
||||
g.clear(1);
|
||||
draw();
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
{ "id": "advcasio",
|
||||
"name": "Advanced Casio Clock",
|
||||
"shortName":"advcasio",
|
||||
"version":"0.03",
|
||||
"version":"0.04",
|
||||
"description": "An over-engineered clock inspired by Casio watches. It has a 4 days weather, a timer using swipe and a scratchpad. Can be updated using a dedicated webapp.",
|
||||
"icon": "app.png",
|
||||
"tags": "clock",
|
||||
|
|
@ -12,7 +12,7 @@
|
|||
{ "url": "screenshot-clock-3.jpg" },
|
||||
{ "url": "screenshot-webapp.jpg" }
|
||||
],
|
||||
"supports" : ["BANGLEJS", "BANGLEJS2"],
|
||||
"supports" : ["BANGLEJS", "BANGLEJS2"],
|
||||
"readme": "README.md",
|
||||
"allow_emulator":true,
|
||||
"storage": [
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@
|
|||
"description": "Simple agenda",
|
||||
"icon": "agenda.png",
|
||||
"screenshots": [{"url":"screenshot_agenda_overview.png"}, {"url":"screenshot_agenda_event1.png"}, {"url":"screenshot_agenda_event2.png"}],
|
||||
"tags": "agenda",
|
||||
"tags": "agenda,clkinfo",
|
||||
"supports": ["BANGLEJS","BANGLEJS2"],
|
||||
"readme": "README.md",
|
||||
"allow_emulator": true,
|
||||
|
|
|
|||
|
|
@ -14,3 +14,4 @@
|
|||
0.14: Fix timeout of http function not being cleaned up
|
||||
0.15: Allow method/body/headers to be specified for `http` (needs Gadgetbridge 0.68.0b or later)
|
||||
0.16: Bangle.http now fails immediately if there is no Bluetooth connection (fix #2152)
|
||||
0.17: Now kick off Calendar sync as soon as connected to Gadgetbridge
|
||||
|
|
|
|||
|
|
@ -91,10 +91,6 @@
|
|||
sched.reload();
|
||||
},
|
||||
//TODO perhaps move those in a library (like messages), used also for viewing events?
|
||||
//simple package with events all together
|
||||
"calendarevents" : function() {
|
||||
require("Storage").writeJSON("android.calendar.json", event.events);
|
||||
},
|
||||
//add and remove events based on activity on phone (pebble-like)
|
||||
"calendar" : function() {
|
||||
var cal = require("Storage").readJSON("android.calendar.json",true);
|
||||
|
|
@ -109,7 +105,7 @@
|
|||
"calendar-" : function() {
|
||||
var cal = require("Storage").readJSON("android.calendar.json",true);
|
||||
//if any of those happen we are out of sync!
|
||||
if (!cal || !Array.isArray(cal)) return;
|
||||
if (!cal || !Array.isArray(cal)) cal = [];
|
||||
cal = cal.filter(e=>e.id!=event.id);
|
||||
require("Storage").writeJSON("android.calendar.json", cal);
|
||||
},
|
||||
|
|
@ -170,7 +166,10 @@
|
|||
|
||||
// Battery monitor
|
||||
function sendBattery() { gbSend({ t: "status", bat: E.getBattery(), chg: Bangle.isCharging()?1:0 }); }
|
||||
NRF.on("connect", () => setTimeout(sendBattery, 2000));
|
||||
NRF.on("connect", () => setTimeout(function() {
|
||||
sendBattery();
|
||||
GB({t:"force_calendar_sync_start"}); // send a list of our calendar entries to start off the sync process
|
||||
}, 2000));
|
||||
Bangle.on("charging", sendBattery);
|
||||
if (!settings.keep)
|
||||
NRF.on("disconnect", () => require("messages").clearAll()); // remove all messages on disconnect
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
"id": "android",
|
||||
"name": "Android Integration",
|
||||
"shortName": "Android",
|
||||
"version": "0.16",
|
||||
"version": "0.17",
|
||||
"description": "Display notifications/music/etc sent from the Gadgetbridge app on Android. This replaces the old 'Gadgetbridge' Bangle.js widget.",
|
||||
"icon": "app.png",
|
||||
"tags": "tool,system,messages,notifications,gadgetbridge",
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@
|
|||
"version": "0.08",
|
||||
"description": "A clock based on the portal series",
|
||||
"icon": "app.png",
|
||||
"screenshots": [{"url":"screenshot.png"}],
|
||||
"type": "clock",
|
||||
"tags": "clock",
|
||||
"supports": ["BANGLEJS2"],
|
||||
|
|
|
|||
|
After Width: | Height: | Size: 5.4 KiB |
|
|
@ -0,0 +1 @@
|
|||
0.01: First version
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
# BarWatch - an experimental watch
|
||||
|
||||
For too long the watches have shown the time with digits or hands. No more!
|
||||
With this stylish watch the time is represented by bars. Up to 24 as the day goes by.
|
||||
Practical? Not really, but a different look!
|
||||
|
|
@ -0,0 +1 @@
|
|||
require("heatshrink").decompress(atob("l0uwkE/4A/AH4A/AB0gicQmUB+EPgEigExh8gj8A+ECAgMQn4WCgcACyotWC34W/C34W/CycACw0wgYWFBYIWCAAc/+YGHCAgNFACkxl8hGYwAMLYUvCykQC34WycoIW/C34W0gAWTmUjkUzkbmSAFY="))
|
||||
|
|
@ -0,0 +1,76 @@
|
|||
// timeout used to update every minute
|
||||
var drawTimeout;
|
||||
|
||||
// schedule a draw for the next minute
|
||||
function queueDraw() {
|
||||
if (drawTimeout) clearTimeout(drawTimeout);
|
||||
drawTimeout = setTimeout(function() {
|
||||
drawTimeout = undefined;
|
||||
draw();
|
||||
}, 60000 - (Date.now() % 60000));
|
||||
}
|
||||
|
||||
|
||||
function draw() {
|
||||
g.reset();
|
||||
|
||||
if(g.theme.dark){
|
||||
g.setColor(1,1,1);
|
||||
}else{
|
||||
g.setColor(0,0,0);
|
||||
}
|
||||
|
||||
// work out how to display the current time
|
||||
var d = new Date();
|
||||
var h = d.getHours(), m = d.getMinutes();
|
||||
|
||||
// hour bars
|
||||
var bx_offset = 10, by_offset = 35;
|
||||
var b_width = 8, b_height = 60;
|
||||
var b_space = 5;
|
||||
|
||||
for(var i=0; i<h; i++){
|
||||
if(i > 11){
|
||||
by_offset = 105;
|
||||
}
|
||||
var iter = i % 12;
|
||||
//console.log(iter);
|
||||
g.fillRect(bx_offset+(b_width*(iter+1))+(b_space*iter),
|
||||
by_offset,
|
||||
bx_offset+(b_width*iter)+(b_space*iter),
|
||||
by_offset+b_height);
|
||||
}
|
||||
|
||||
// minute bar
|
||||
if(h > 11){
|
||||
by_offset = 105;
|
||||
}
|
||||
var m_bar = h % 12;
|
||||
if(m != 0){
|
||||
g.fillRect(bx_offset+(b_width*(m_bar+1))+(b_space*m_bar),
|
||||
by_offset+b_height-m,
|
||||
bx_offset+(b_width*m_bar)+(b_space*m_bar),
|
||||
by_offset+b_height);
|
||||
}
|
||||
|
||||
// queue draw in one minute
|
||||
queueDraw();
|
||||
}
|
||||
|
||||
// Clear the screen once, at startup
|
||||
g.clear();
|
||||
// draw immediately at first
|
||||
draw();
|
||||
// Stop updates when LCD is off, restart when on
|
||||
Bangle.on('lcdPower',on=>{
|
||||
if (on) {
|
||||
draw(); // draw immediately, queue redraw
|
||||
} else { // stop draw timer
|
||||
if (drawTimeout) clearTimeout(drawTimeout);
|
||||
drawTimeout = undefined;
|
||||
}
|
||||
});
|
||||
|
||||
Bangle.setUI("clock");
|
||||
Bangle.loadWidgets();
|
||||
Bangle.drawWidgets();
|
||||
|
After Width: | Height: | Size: 973 B |
|
|
@ -0,0 +1,18 @@
|
|||
{
|
||||
"id": "barwatch",
|
||||
"name": "BarWatch",
|
||||
"shortName":"BarWatch",
|
||||
"version":"0.01",
|
||||
"description": "A watch that displays the time using bars. One bar for each hour.",
|
||||
"readme": "README.md",
|
||||
"icon": "screenshot.png",
|
||||
"tags": "clock",
|
||||
"type": "clock",
|
||||
"allow_emulator":true,
|
||||
"screenshots" : [ { "url": "screenshot.png" } ],
|
||||
"supports" : ["BANGLEJS2"],
|
||||
"storage": [
|
||||
{"name":"barwatch.app.js","url":"app.js"},
|
||||
{"name":"barwatch.img","url":"app-icon.js","evaluate":true}
|
||||
]
|
||||
}
|
||||
|
After Width: | Height: | Size: 1.3 KiB |
|
|
@ -59,3 +59,5 @@
|
|||
Add patch to ensure that compass heading is corrected on pre-2v15.68 firmware
|
||||
Ensure clock is only fast-loaded if it doesn't contain widgets
|
||||
0.52: Ensure heading patch for pre-2v15.68 firmware applies to getCompass
|
||||
0.53: Add polyfills for pre-2v15.135 firmware for Bangle.load and Bangle.showClock
|
||||
0.54: Fix for invalid version comparison in polyfill
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ of the time. */
|
|||
E.showMessage(/*LANG*/"Updating boot0...");
|
||||
var s = require('Storage').readJSON('setting.json',1)||{};
|
||||
var BANGLEJS2 = process.env.HWVERSION==2; // Is Bangle.js 2
|
||||
var FWVERSION = parseFloat(process.env.VERSION.replace("v","").replace(/\.(\d\d)$/,".0$1"));
|
||||
var boot = "", bootPost = "";
|
||||
if (require('Storage').hash) { // new in 2v11 - helps ensure files haven't changed
|
||||
var CRC = E.CRC32(require('Storage').read('setting.json'))+require('Storage').hash(/\.boot\.js/)+E.CRC32(process.env.GIT_COMMIT);
|
||||
|
|
@ -77,11 +78,17 @@ if (s.passkey!==undefined && s.passkey.length==6) boot+=`NRF.setSecurity({passke
|
|||
if (s.whitelist) boot+=`NRF.on('connect', function(addr) { if (!(require('Storage').readJSON('setting.json',1)||{}).whitelist.includes(addr)) NRF.disconnect(); });\n`;
|
||||
if (s.rotate) boot+=`g.setRotation(${s.rotate&3},${s.rotate>>2});\n` // screen rotation
|
||||
// ================================================== FIXING OLDER FIRMWARES
|
||||
// 2v15.68 and before had compass heading inverted.
|
||||
if (process.version.replace("v","")<215.68)
|
||||
if (FWVERSION<215.068) // 2v15.68 and before had compass heading inverted.
|
||||
boot += `Bangle.on('mag',e=>{if(!isNaN(e.heading))e.heading=360-e.heading;});
|
||||
Bangle.getCompass=(c=>(()=>{e=c();if(!isNaN(e.heading))e.heading=360-e.heading;return e;}))(Bangle.getCompass);`;
|
||||
|
||||
// deleting stops us getting confused by our own decl. builtins can't be deleted
|
||||
// this is a polyfill without fastloading capability
|
||||
delete Bangle.showClock;
|
||||
if (!Bangle.showClock) boot += `Bangle.showClock = ()=>{load(".bootcde")};\n`;
|
||||
delete Bangle.load;
|
||||
if (!Bangle.load) boot += `Bangle.load = load;\n`;
|
||||
|
||||
// ================================================== BOOT.JS
|
||||
// Append *.boot.js files
|
||||
// These could change bleServices/bleServiceOptions if needed
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"id": "boot",
|
||||
"name": "Bootloader",
|
||||
"version": "0.52",
|
||||
"version": "0.54",
|
||||
"description": "This is needed by Bangle.js to automatically load the clock, menu, widgets and settings",
|
||||
"icon": "bootloader.png",
|
||||
"type": "bootloader",
|
||||
|
|
|
|||
|
|
@ -34,3 +34,9 @@
|
|||
Prevent mixing of BT and internal HRM events if both are enabled
|
||||
Always use a grace period (default 0 ms) to decouple some connection steps
|
||||
Device not found errors now utilize increasing timeouts
|
||||
0.15: Fix recording internal sensor
|
||||
Handle fallback to internal sensor consistently if BT bpm is 0
|
||||
Power internal sensor down if not needed for fallback
|
||||
0.16: Set powerdownRequested correctly on BTHRM power on
|
||||
Additional logging on errors
|
||||
Add debug option for disabling active scanning
|
||||
|
|
|
|||
|
|
@ -17,5 +17,6 @@
|
|||
"gracePeriodConnect": 0,
|
||||
"gracePeriodService": 0,
|
||||
"gracePeriodRequest": 0,
|
||||
"bonding": false
|
||||
"bonding": false,
|
||||
"active": true
|
||||
}
|
||||
|
|
|
|||
|
|
@ -106,7 +106,7 @@ exports.enable = () => {
|
|||
var bpm = (flags & 1) ? (dv.getUint16(1) / 100 /* ? */ ) : dv.getUint8(1); // 8 or 16 bit
|
||||
supportedCharacteristics["0x2a37"].active = bpm > 0;
|
||||
log("BTHRM BPM " + supportedCharacteristics["0x2a37"].active);
|
||||
if (supportedCharacteristics["0x2a37"].active) stopFallback();
|
||||
switchFallback();
|
||||
if (bpmTimeout) clearTimeout(bpmTimeout);
|
||||
bpmTimeout = setTimeout(()=>{
|
||||
bpmTimeout = undefined;
|
||||
|
|
@ -148,14 +148,14 @@ exports.enable = () => {
|
|||
battery = lastReceivedData["0x180f"]["0x2a19"];
|
||||
}
|
||||
|
||||
if (settings.replace){
|
||||
if (settings.replace && bpm > 0){
|
||||
var repEvent = {
|
||||
bpm: bpm,
|
||||
confidence: (sensorContact || sensorContact === undefined)? 100 : 0,
|
||||
src: "bthrm"
|
||||
};
|
||||
|
||||
log("Emitting aggregated HRM", repEvent);
|
||||
log("Emitting HRM_R(bt)", repEvent);
|
||||
Bangle.emit("HRM_R", repEvent);
|
||||
}
|
||||
|
||||
|
|
@ -255,7 +255,7 @@ exports.enable = () => {
|
|||
var retry = function() {
|
||||
log("Retry");
|
||||
|
||||
if (!currentRetryTimeout){
|
||||
if (!currentRetryTimeout && !powerdownRequested){
|
||||
|
||||
var clampedTime = retryTime < 100 ? 100 : retryTime;
|
||||
|
||||
|
|
@ -281,13 +281,13 @@ exports.enable = () => {
|
|||
log("Disconnect: " + reason);
|
||||
log("GATT", gatt);
|
||||
log("Characteristics", characteristics);
|
||||
|
||||
|
||||
var retryTimeResetNeeded = true;
|
||||
retryTimeResetNeeded &= reason != "Connection Timeout";
|
||||
retryTimeResetNeeded &= reason != "No device found matching filters";
|
||||
clearRetryTimeout(retryTimeResetNeeded);
|
||||
supportedCharacteristics["0x2a37"].active = false;
|
||||
startFallback();
|
||||
if (!powerdownRequested) startFallback();
|
||||
blockInit = false;
|
||||
if (settings.warnDisconnect && !buzzing){
|
||||
buzzing = true;
|
||||
|
|
@ -369,7 +369,7 @@ exports.enable = () => {
|
|||
|
||||
var initBt = function () {
|
||||
log("initBt with blockInit: " + blockInit);
|
||||
if (blockInit){
|
||||
if (blockInit && !powerdownRequested){
|
||||
retry();
|
||||
return;
|
||||
}
|
||||
|
|
@ -387,8 +387,14 @@ exports.enable = () => {
|
|||
return;
|
||||
}
|
||||
log("Requesting device with filters", filters);
|
||||
promise = NRF.requestDevice({ filters: filters, active: true });
|
||||
|
||||
try {
|
||||
promise = NRF.requestDevice({ filters: filters, active: settings.active });
|
||||
} catch (e){
|
||||
log("Error during initial request:", e);
|
||||
onDisconnect(e);
|
||||
return;
|
||||
}
|
||||
|
||||
if (settings.gracePeriodRequest){
|
||||
log("Add " + settings.gracePeriodRequest + "ms grace period after request");
|
||||
}
|
||||
|
|
@ -454,7 +460,7 @@ exports.enable = () => {
|
|||
} else {
|
||||
log("Start bonding");
|
||||
return gatt.startBonding()
|
||||
.then(() => console.log(gatt.getSecurityStatus()));
|
||||
.then(() => log("Security status" + gatt.getSecurityStatus()));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
@ -508,6 +514,8 @@ exports.enable = () => {
|
|||
});
|
||||
};
|
||||
|
||||
var powerdownRequested = false;
|
||||
|
||||
Bangle.setBTHRMPower = function(isOn, app) {
|
||||
// Do app power handling
|
||||
if (!app) app="?";
|
||||
|
|
@ -518,11 +526,14 @@ exports.enable = () => {
|
|||
isOn = Bangle._PWR.BTHRM.length;
|
||||
// so now we know if we're really on
|
||||
if (isOn) {
|
||||
powerdownRequested = false;
|
||||
switchFallback();
|
||||
if (!Bangle.isBTHRMConnected()) initBt();
|
||||
} else { // not on
|
||||
log("Power off for " + app);
|
||||
powerdownRequested = true;
|
||||
clearRetryTimeout(true);
|
||||
stopFallback();
|
||||
if (gatt) {
|
||||
if (gatt.connected){
|
||||
log("Disconnect with gatt", gatt);
|
||||
|
|
@ -544,9 +555,11 @@ exports.enable = () => {
|
|||
// register a listener for original HRM events and emit as HRM_int
|
||||
Bangle.on("HRM", (e) => {
|
||||
e.modified = true;
|
||||
log("Emitting HRM_int", e);
|
||||
Bangle.emit("HRM_int", e);
|
||||
if (fallbackActive){
|
||||
// if fallback to internal HRM is active, emit as HRM_R to which everyone listens
|
||||
log("Emitting HRM_R(int)", e);
|
||||
Bangle.emit("HRM_R", e);
|
||||
}
|
||||
});
|
||||
|
|
@ -572,6 +585,11 @@ exports.enable = () => {
|
|||
log("setHRMPower for " + app + ": " + (isOn?"on":"off"));
|
||||
if (settings.enabled){
|
||||
Bangle.setBTHRMPower(isOn, app);
|
||||
if (Bangle._PWR && Bangle._PWR.HRM && Object.keys(Bangle._PWR.HRM).length == 0) {
|
||||
Bangle._PWR.BTHRM = [];
|
||||
Bangle.setBTHRMPower(0);
|
||||
if (!isOn) stopFallback();
|
||||
}
|
||||
}
|
||||
if ((settings.enabled && !settings.replace) || !settings.enabled){
|
||||
Bangle.origSetHRMPower(isOn, app);
|
||||
|
|
@ -627,7 +645,11 @@ exports.enable = () => {
|
|||
E.on("kill", ()=>{
|
||||
if (gatt && gatt.connected){
|
||||
log("Got killed, trying to disconnect");
|
||||
gatt.disconnect().then(()=>log("Disconnected on kill")).catch((e)=>log("Error during disconnnect on kill", e));
|
||||
try {
|
||||
gatt.disconnect().then(()=>log("Disconnected on kill")).catch((e)=>log("Error during disconnnect promise on kill", e));
|
||||
} catch (e) {
|
||||
log("Error during disconnnect on kill", e)
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
"id": "bthrm",
|
||||
"name": "Bluetooth Heart Rate Monitor",
|
||||
"shortName": "BT HRM",
|
||||
"version": "0.14",
|
||||
"version": "0.16",
|
||||
"description": "Overrides Bangle.js's build in heart rate monitor with an external Bluetooth one.",
|
||||
"icon": "app.png",
|
||||
"type": "app",
|
||||
|
|
|
|||
|
|
@ -38,35 +38,32 @@
|
|||
recorders.hrmint = function() {
|
||||
var active = false;
|
||||
var bpmTimeout;
|
||||
var bpm = "", bpmConfidence = "", src="";
|
||||
var bpm = "", bpmConfidence = "";
|
||||
function onHRM(h) {
|
||||
bpmConfidence = h.confidence;
|
||||
bpm = h.bpm;
|
||||
srv = h.src;
|
||||
if (h.bpm > 0){
|
||||
active = true;
|
||||
print("active" + h.bpm);
|
||||
if (bpmTimeout) clearTimeout(bpmTimeout);
|
||||
bpmTimeout = setTimeout(()=>{
|
||||
print("inactive");
|
||||
active = false;
|
||||
},3000);
|
||||
}
|
||||
}
|
||||
return {
|
||||
name : "HR int",
|
||||
fields : ["Heartrate", "Confidence"],
|
||||
fields : ["Int Heartrate", "Int Confidence"],
|
||||
getValues : () => {
|
||||
var r = [bpm,bpmConfidence,src];
|
||||
bpm = ""; bpmConfidence = ""; src="";
|
||||
var r = [bpm,bpmConfidence];
|
||||
bpm = ""; bpmConfidence = "";
|
||||
return r;
|
||||
},
|
||||
start : () => {
|
||||
Bangle.origOn('HRM', onHRM);
|
||||
Bangle.on('HRM_int', onHRM);
|
||||
if (Bangle.origSetHRMPower) Bangle.origSetHRMPower(1,"recorder");
|
||||
},
|
||||
stop : () => {
|
||||
Bangle.removeListener('HRM', onHRM);
|
||||
Bangle.removeListener('HRM_int', onHRM);
|
||||
if (Bangle.origSetHRMPower) Bangle.origSetHRMPower(0,"recorder");
|
||||
},
|
||||
draw : (x,y) => g.setColor(( Bangle.origIsHRMOn && Bangle.origIsHRMOn() && active)?"#0f0":"#8f8").drawImage(atob("DAwBAAAAMMeef+f+f+P8H4DwBgAA"),x,y)
|
||||
|
|
|
|||
|
|
@ -102,6 +102,12 @@
|
|||
writeSettings("bonding",v);
|
||||
}
|
||||
},
|
||||
'Use active scanning': {
|
||||
value: !!settings.active,
|
||||
onchange: v => {
|
||||
writeSettings("active",v);
|
||||
}
|
||||
},
|
||||
'Grace periods': function() { E.showMenu(submenu_grace); }
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@
|
|||
"icon": "app.png",
|
||||
"screenshots": [{"url":"screenshot.png"}, {"url":"screenshot_2.png"}, {"url":"screenshot_3.png"}, {"url":"screenshot_4.png"}],
|
||||
"type": "clock",
|
||||
"tags": "clock",
|
||||
"tags": "clock,clkinfo",
|
||||
"supports": ["BANGLEJS2"],
|
||||
"allow_emulator": true,
|
||||
"storage": [
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
"shortName":"Calibration",
|
||||
"icon": "calibration.png",
|
||||
"version":"0.03",
|
||||
"description": "A simple calibration app for the touchscreen",
|
||||
"description": "(NOT RECOMMENDED) A simple calibration app for the touchscreen. Please use the Touchscreen Calibration in the Settings app instead.",
|
||||
"supports": ["BANGLEJS","BANGLEJS2"],
|
||||
"readme": "README.md",
|
||||
"tags": "tool",
|
||||
|
|
|
|||
|
|
@ -10,3 +10,4 @@
|
|||
0.9: Remove ESLint spaces
|
||||
0.10: Show daily steps, heartrate and the temperature if weather information is available.
|
||||
0.11: Tell clock widgets to hide.
|
||||
0.12: Swipe down to see widgets, step counter now just uses getHealthStatus
|
||||
|
|
|
|||
|
|
@ -8,4 +8,5 @@ It displays current temperature,day,steps,battery.heartbeat and weather.
|
|||
|
||||
|
||||
**To-do**:
|
||||
Align and change size of some elements.
|
||||
|
||||
* Align and change size of some elements
|
||||
|
|
|
|||
|
|
@ -91,7 +91,6 @@ function getTemperature(){
|
|||
var weatherJson = storage.readJSON('weather.json');
|
||||
var weather = weatherJson.weather;
|
||||
return Math.round(weather.temp-273.15);
|
||||
|
||||
} catch(ex) {
|
||||
print(ex)
|
||||
return "?"
|
||||
|
|
@ -99,20 +98,7 @@ function getTemperature(){
|
|||
}
|
||||
|
||||
function getSteps() {
|
||||
var steps = 0;
|
||||
try{
|
||||
if (WIDGETS.wpedom !== undefined) {
|
||||
steps = WIDGETS.wpedom.getSteps();
|
||||
} else if (WIDGETS.activepedom !== undefined) {
|
||||
steps = WIDGETS.activepedom.getSteps();
|
||||
} else {
|
||||
steps = Bangle.getHealthStatus("day").steps;
|
||||
}
|
||||
} catch(ex) {
|
||||
// In case we failed, we can only show 0 steps.
|
||||
return "? k";
|
||||
}
|
||||
|
||||
var steps = Bangle.getHealthStatus("day").steps;
|
||||
steps = Math.round(steps/1000);
|
||||
return steps + "k";
|
||||
}
|
||||
|
|
@ -121,8 +107,7 @@ function getSteps() {
|
|||
function draw() {
|
||||
queueDraw();
|
||||
|
||||
g.reset();
|
||||
g.clear();
|
||||
g.clear(1);
|
||||
g.setColor(0, 255, 255);
|
||||
g.fillRect(0, 0, g.getWidth(), g.getHeight());
|
||||
let background = getBackgroundImage();
|
||||
|
|
@ -143,9 +128,6 @@ function draw() {
|
|||
drawClock();
|
||||
drawRocket();
|
||||
drawBattery();
|
||||
|
||||
// Hide widgets
|
||||
for (let wd of WIDGETS) {wd.draw=()=>{};wd.area="";}
|
||||
}
|
||||
|
||||
Bangle.on("lcdPower", (on) => {
|
||||
|
|
@ -169,7 +151,6 @@ Bangle.setUI("clock");
|
|||
|
||||
// Load widgets, but don't show them
|
||||
Bangle.loadWidgets();
|
||||
|
||||
g.reset();
|
||||
g.clear();
|
||||
require("widget_utils").swipeOn(); // hide widgets, make them visible with a swipe
|
||||
g.clear(1);
|
||||
draw();
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
"description": "Animated Clock with Space Cassio Watch Style",
|
||||
"screenshots": [{ "url": "screens/screen_night.png" },{ "url": "screens/screen_day.png" }],
|
||||
"icon": "app.png",
|
||||
"version": "0.11",
|
||||
"version": "0.12",
|
||||
"type": "clock",
|
||||
"tags": "clock, weather, cassio, retro",
|
||||
"supports": ["BANGLEJS2"],
|
||||
|
|
|
|||
|
|
@ -30,3 +30,4 @@
|
|||
0.15: Use Bangle.setUI({remove:...}) to allow loading the launcher without a full reset on 2v16
|
||||
0.16: Fix const error
|
||||
Use widget_utils if available
|
||||
0.17: Load circles from clkinfo
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
let clock_info = require("clock_info");
|
||||
let locale = require("locale");
|
||||
let storage = require("Storage");
|
||||
Graphics.prototype.setFontRobotoRegular50NumericOnly = function(scale) {
|
||||
|
|
@ -18,6 +19,7 @@ let settings = Object.assign(
|
|||
storage.readJSON(SETTINGS_FILE, true) || {}
|
||||
);
|
||||
|
||||
//TODO deprecate this (and perhaps use in the clkinfo module)
|
||||
// Load step goal from health app and pedometer widget as fallback
|
||||
if (settings.stepGoal == undefined) {
|
||||
let d = storage.readJSON("health.json", true) || {};
|
||||
|
|
@ -29,7 +31,7 @@ if (settings.stepGoal == undefined) {
|
|||
}
|
||||
}
|
||||
|
||||
let timerHrm;
|
||||
let timerHrm; //TODO deprecate this
|
||||
let drawTimeout;
|
||||
|
||||
/*
|
||||
|
|
@ -44,10 +46,9 @@ let showWidgets = settings.showWidgets || false;
|
|||
let circleCount = settings.circleCount || 3;
|
||||
let showBigWeather = settings.showBigWeather || false;
|
||||
|
||||
let hrtValue;
|
||||
let hrtValue; //TODO deprecate this
|
||||
let now = Math.round(new Date().getTime() / 1000);
|
||||
|
||||
|
||||
// layout values:
|
||||
let colorFg = g.theme.dark ? '#fff' : '#000';
|
||||
let colorBg = g.theme.dark ? '#000' : '#fff';
|
||||
|
|
@ -91,8 +92,20 @@ let circleFontSmall = circleCount == 3 ? "Vector:14" : "Vector:10";
|
|||
let circleFont = circleCount == 3 ? "Vector:15" : "Vector:11";
|
||||
let circleFontBig = circleCount == 3 ? "Vector:16" : "Vector:12";
|
||||
let iconOffset = circleCount == 3 ? 6 : 8;
|
||||
let defaultCircleTypes = ["steps", "hr", "battery", "weather"];
|
||||
let defaultCircleTypes = ["Bangle/Steps", "Bangle/HRM", "Bangle/Battery", "weather"];
|
||||
|
||||
let circleInfoNum = [
|
||||
0, // circle1
|
||||
0, // circle2
|
||||
0, // circle3
|
||||
0, // circle4
|
||||
];
|
||||
let circleItemNum = [
|
||||
0, // circle1
|
||||
1, // circle2
|
||||
2, // circle3
|
||||
3, // circle4
|
||||
];
|
||||
|
||||
function hideWidgets() {
|
||||
/*
|
||||
|
|
@ -111,7 +124,7 @@ function hideWidgets() {
|
|||
function draw() {
|
||||
g.clear(true);
|
||||
let widgetUtils;
|
||||
|
||||
|
||||
try {
|
||||
widgetUtils = require("widget_utils");
|
||||
} catch (e) {
|
||||
|
|
@ -177,6 +190,15 @@ function drawCircle(index) {
|
|||
let w = getCircleXPosition(type);
|
||||
|
||||
switch (type) {
|
||||
case "weather":
|
||||
drawWeather(w);
|
||||
break;
|
||||
case "sunprogress":
|
||||
case "sunProgress":
|
||||
drawSunProgress(w);
|
||||
break;
|
||||
//TODO those are going to be deprecated, keep for backwards compatibility for now
|
||||
//ideally all data should come from some clkinfo
|
||||
case "steps":
|
||||
drawSteps(w);
|
||||
break;
|
||||
|
|
@ -189,13 +211,6 @@ function drawCircle(index) {
|
|||
case "battery":
|
||||
drawBattery(w);
|
||||
break;
|
||||
case "weather":
|
||||
drawWeather(w);
|
||||
break;
|
||||
case "sunprogress":
|
||||
case "sunProgress":
|
||||
drawSunProgress(w);
|
||||
break;
|
||||
case "temperature":
|
||||
drawTemperature(w);
|
||||
break;
|
||||
|
|
@ -205,9 +220,12 @@ function drawCircle(index) {
|
|||
case "altitude":
|
||||
drawAltitude(w);
|
||||
break;
|
||||
//end deprecated
|
||||
case "empty":
|
||||
// we draw nothing here
|
||||
return;
|
||||
default:
|
||||
drawClkInfo(index, w);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -304,6 +322,102 @@ function getImage(graphic, color) {
|
|||
}
|
||||
}
|
||||
|
||||
function drawWeather(w) {
|
||||
if (!w) w = getCircleXPosition("weather");
|
||||
let weather = getWeather();
|
||||
let tempString = weather ? locale.temp(weather.temp - 273.15) : undefined;
|
||||
let code = weather ? weather.code : -1;
|
||||
|
||||
drawCircleBackground(w);
|
||||
|
||||
let color = getCircleColor("weather");
|
||||
let percent;
|
||||
let data = settings.weatherCircleData;
|
||||
switch (data) {
|
||||
case "humidity":
|
||||
let humidity = weather ? weather.hum : undefined;
|
||||
if (humidity >= 0) {
|
||||
percent = humidity / 100;
|
||||
drawGauge(w, h3, percent, color);
|
||||
}
|
||||
break;
|
||||
case "wind":
|
||||
if (weather) {
|
||||
let wind = locale.speed(weather.wind).match(/^(\D*\d*)(.*)$/);
|
||||
if (wind[1] >= 0) {
|
||||
if (wind[2] == "kmh") {
|
||||
wind[1] = windAsBeaufort(wind[1]);
|
||||
}
|
||||
// wind goes from 0 to 12 (see https://en.wikipedia.org/wiki/Beaufort_scale)
|
||||
percent = wind[1] / 12;
|
||||
drawGauge(w, h3, percent, color);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case "empty":
|
||||
break;
|
||||
}
|
||||
|
||||
drawInnerCircleAndTriangle(w);
|
||||
|
||||
writeCircleText(w, tempString ? tempString : "?");
|
||||
|
||||
if (code > 0) {
|
||||
let icon = getWeatherIconByCode(code);
|
||||
if (icon) g.drawImage(getImage(icon, getCircleIconColor("weather", color, percent)), w - iconOffset, h3 + radiusOuter - iconOffset);
|
||||
} else {
|
||||
g.drawString("?", w, h3 + radiusOuter);
|
||||
}
|
||||
}
|
||||
|
||||
function drawSunProgress(w) {
|
||||
if (!w) w = getCircleXPosition("sunprogress");
|
||||
let percent = getSunProgress();
|
||||
|
||||
// sunset icons:
|
||||
let sunSetDown = atob("EBCBAAAAAAABgAAAAAATyAZoBCB//gAAAAAGYAPAAYAAAAAA");
|
||||
let sunSetUp = atob("EBCBAAAAAAABgAAAAAATyAZoBCB//gAAAAABgAPABmAAAAAA");
|
||||
|
||||
drawCircleBackground(w);
|
||||
|
||||
let color = getCircleColor("sunprogress");
|
||||
|
||||
drawGauge(w, h3, percent, color);
|
||||
|
||||
drawInnerCircleAndTriangle(w);
|
||||
|
||||
let icon = sunSetDown;
|
||||
let text = "?";
|
||||
let times = getSunData();
|
||||
if (times != undefined) {
|
||||
let sunRise = Math.round(times.sunrise.getTime() / 1000);
|
||||
let sunSet = Math.round(times.sunset.getTime() / 1000);
|
||||
if (!isDay()) {
|
||||
// night
|
||||
if (now > sunRise) {
|
||||
// after sunRise
|
||||
let upcomingSunRise = sunRise + 60 * 60 * 24;
|
||||
text = formatSeconds(upcomingSunRise - now);
|
||||
} else {
|
||||
text = formatSeconds(sunRise - now);
|
||||
}
|
||||
icon = sunSetUp;
|
||||
} else {
|
||||
// day, approx sunrise tomorrow:
|
||||
text = formatSeconds(sunSet - now);
|
||||
icon = sunSetDown;
|
||||
}
|
||||
}
|
||||
|
||||
writeCircleText(w, text);
|
||||
|
||||
g.drawImage(getImage(icon, getCircleIconColor("sunprogress", color, percent)), w - iconOffset, h3 + radiusOuter - iconOffset);
|
||||
}
|
||||
|
||||
/*
|
||||
* Deprecated but nice as references for clkinfo
|
||||
*/
|
||||
|
||||
function drawSteps(w) {
|
||||
if (!w) w = getCircleXPosition("steps");
|
||||
let steps = getSteps();
|
||||
|
|
@ -406,99 +520,6 @@ function drawBattery(w) {
|
|||
|
||||
g.drawImage(getImage(powerIcon, getCircleIconColor("battery", color, percent)), w - iconOffset, h3 + radiusOuter - iconOffset);
|
||||
}
|
||||
|
||||
function drawWeather(w) {
|
||||
if (!w) w = getCircleXPosition("weather");
|
||||
let weather = getWeather();
|
||||
let tempString = weather ? locale.temp(weather.temp - 273.15) : undefined;
|
||||
let code = weather ? weather.code : -1;
|
||||
|
||||
drawCircleBackground(w);
|
||||
|
||||
let color = getCircleColor("weather");
|
||||
let percent;
|
||||
let data = settings.weatherCircleData;
|
||||
switch (data) {
|
||||
case "humidity":
|
||||
let humidity = weather ? weather.hum : undefined;
|
||||
if (humidity >= 0) {
|
||||
percent = humidity / 100;
|
||||
drawGauge(w, h3, percent, color);
|
||||
}
|
||||
break;
|
||||
case "wind":
|
||||
if (weather) {
|
||||
let wind = locale.speed(weather.wind).match(/^(\D*\d*)(.*)$/);
|
||||
if (wind[1] >= 0) {
|
||||
if (wind[2] == "kmh") {
|
||||
wind[1] = windAsBeaufort(wind[1]);
|
||||
}
|
||||
// wind goes from 0 to 12 (see https://en.wikipedia.org/wiki/Beaufort_scale)
|
||||
percent = wind[1] / 12;
|
||||
drawGauge(w, h3, percent, color);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case "empty":
|
||||
break;
|
||||
}
|
||||
|
||||
drawInnerCircleAndTriangle(w);
|
||||
|
||||
writeCircleText(w, tempString ? tempString : "?");
|
||||
|
||||
if (code > 0) {
|
||||
let icon = getWeatherIconByCode(code);
|
||||
if (icon) g.drawImage(getImage(icon, getCircleIconColor("weather", color, percent)), w - iconOffset, h3 + radiusOuter - iconOffset);
|
||||
} else {
|
||||
g.drawString("?", w, h3 + radiusOuter);
|
||||
}
|
||||
}
|
||||
|
||||
function drawSunProgress(w) {
|
||||
if (!w) w = getCircleXPosition("sunprogress");
|
||||
let percent = getSunProgress();
|
||||
|
||||
// sunset icons:
|
||||
let sunSetDown = atob("EBCBAAAAAAABgAAAAAATyAZoBCB//gAAAAAGYAPAAYAAAAAA");
|
||||
let sunSetUp = atob("EBCBAAAAAAABgAAAAAATyAZoBCB//gAAAAABgAPABmAAAAAA");
|
||||
|
||||
drawCircleBackground(w);
|
||||
|
||||
let color = getCircleColor("sunprogress");
|
||||
|
||||
drawGauge(w, h3, percent, color);
|
||||
|
||||
drawInnerCircleAndTriangle(w);
|
||||
|
||||
let icon = sunSetDown;
|
||||
let text = "?";
|
||||
let times = getSunData();
|
||||
if (times != undefined) {
|
||||
let sunRise = Math.round(times.sunrise.getTime() / 1000);
|
||||
let sunSet = Math.round(times.sunset.getTime() / 1000);
|
||||
if (!isDay()) {
|
||||
// night
|
||||
if (now > sunRise) {
|
||||
// after sunRise
|
||||
let upcomingSunRise = sunRise + 60 * 60 * 24;
|
||||
text = formatSeconds(upcomingSunRise - now);
|
||||
} else {
|
||||
text = formatSeconds(sunRise - now);
|
||||
}
|
||||
icon = sunSetUp;
|
||||
} else {
|
||||
// day, approx sunrise tomorrow:
|
||||
text = formatSeconds(sunSet - now);
|
||||
icon = sunSetDown;
|
||||
}
|
||||
}
|
||||
|
||||
writeCircleText(w, text);
|
||||
|
||||
g.drawImage(getImage(icon, getCircleIconColor("sunprogress", color, percent)), w - iconOffset, h3 + radiusOuter - iconOffset);
|
||||
}
|
||||
|
||||
function drawTemperature(w) {
|
||||
if (!w) w = getCircleXPosition("temperature");
|
||||
|
||||
|
|
@ -577,6 +598,113 @@ function drawAltitude(w) {
|
|||
});
|
||||
}
|
||||
|
||||
function shortValue(v) {
|
||||
if (isNaN(v)) return '-';
|
||||
if (v <= 999) return v;
|
||||
if (v >= 1000 && v < 10000) {
|
||||
v = Math.floor(v / 100) * 100;
|
||||
return (v / 1000).toFixed(1).replace(/\.0$/, '') + 'k';
|
||||
}
|
||||
if (v >= 10000) {
|
||||
v = Math.floor(v / 1000) * 1000;
|
||||
return (v / 1000).toFixed(1).replace(/\.0$/, '') + 'k';
|
||||
}
|
||||
}
|
||||
|
||||
function getSteps() {
|
||||
if (Bangle.getHealthStatus) {
|
||||
return Bangle.getHealthStatus("day").steps;
|
||||
}
|
||||
if (WIDGETS && WIDGETS.wpedom !== undefined) {
|
||||
return WIDGETS.wpedom.getSteps();
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
function getPressureValue(type) {
|
||||
return new Promise((resolve) => {
|
||||
if (Bangle.getPressure) {
|
||||
if (!pressureLocked) {
|
||||
pressureLocked = true;
|
||||
if (pressureCache && pressureCache[type]) {
|
||||
resolve(pressureCache[type]);
|
||||
}
|
||||
Bangle.getPressure().then(function(d) {
|
||||
pressureLocked = false;
|
||||
if (d) {
|
||||
pressureCache = d;
|
||||
if (d[type]) {
|
||||
resolve(d[type]);
|
||||
}
|
||||
}
|
||||
}).catch(() => {});
|
||||
} else {
|
||||
if (pressureCache && pressureCache[type]) {
|
||||
resolve(pressureCache[type]);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/*
|
||||
* end deprecated
|
||||
*/
|
||||
|
||||
var menu = null;
|
||||
function reloadMenu() {
|
||||
menu = clock_info.load();
|
||||
for(var i=1; i<5; i++)
|
||||
if(settings['circle'+i].includes("/")) {
|
||||
let parts = settings['circle'+i].split("/");
|
||||
let infoName = parts[0], itemName = parts[1];
|
||||
let infoNum = menu.findIndex(e=>e.name==infoName);
|
||||
let itemNum = 0;
|
||||
//suppose unnamed are varying (like timers or events), pick the first
|
||||
if(itemName)
|
||||
itemNum = menu[infoNum].items.findIndex(it=>it.name==itemName);
|
||||
circleInfoNum[i-1] = infoNum;
|
||||
circleItemNum[i-1] = itemNum;
|
||||
}
|
||||
}
|
||||
//reload periodically for changes?
|
||||
reloadMenu();
|
||||
|
||||
function drawEmpty(img, w, color) {
|
||||
drawGauge(w, h3, 0, color);
|
||||
drawInnerCircleAndTriangle(w);
|
||||
writeCircleText(w, "?");
|
||||
if(img)
|
||||
g.setColor(getGradientColor(color, 0))
|
||||
.drawImage(img, w - iconOffset, h3 + radiusOuter - iconOffset, {scale: 16/24});
|
||||
}
|
||||
|
||||
function drawClkInfo(index, w) {
|
||||
var info = menu[circleInfoNum[index-1]];
|
||||
var type = settings['circle'+index];
|
||||
if (!w) w = getCircleXPosition(type);
|
||||
drawCircleBackground(w);
|
||||
const color = getCircleColor(type);
|
||||
if(!info || !info.items.length) {
|
||||
drawEmpty(info? info.img : null, w, color);
|
||||
return;
|
||||
}
|
||||
var item = info.items[circleItemNum[index-1]];
|
||||
//TODO do hide()+get() here
|
||||
item.show();
|
||||
item.hide();
|
||||
item=item.get();
|
||||
var img = item.img;
|
||||
if(!img) img = info.img;
|
||||
let percent = (item.v-item.min) / item.max;
|
||||
if(isNaN(percent)) percent = 1; //fill it up
|
||||
drawGauge(w, h3, percent, color);
|
||||
drawInnerCircleAndTriangle(w);
|
||||
writeCircleText(w, item.text);
|
||||
g.setColor(getCircleIconColor(type, color, percent))
|
||||
.drawImage(img, w - iconOffset, h3 + radiusOuter - iconOffset, {scale: 16/24});
|
||||
}
|
||||
|
||||
/*
|
||||
* wind goes from 0 to 12 (see https://en.wikipedia.org/wiki/Beaufort_scale)
|
||||
*/
|
||||
|
|
@ -770,125 +898,15 @@ function writeCircleText(w, content) {
|
|||
g.drawString(content, w, h3);
|
||||
}
|
||||
|
||||
function shortValue(v) {
|
||||
if (isNaN(v)) return '-';
|
||||
if (v <= 999) return v;
|
||||
if (v >= 1000 && v < 10000) {
|
||||
v = Math.floor(v / 100) * 100;
|
||||
return (v / 1000).toFixed(1).replace(/\.0$/, '') + 'k';
|
||||
}
|
||||
if (v >= 10000) {
|
||||
v = Math.floor(v / 1000) * 1000;
|
||||
return (v / 1000).toFixed(1).replace(/\.0$/, '') + 'k';
|
||||
}
|
||||
}
|
||||
|
||||
function getSteps() {
|
||||
if (Bangle.getHealthStatus) {
|
||||
return Bangle.getHealthStatus("day").steps;
|
||||
}
|
||||
if (WIDGETS && WIDGETS.wpedom !== undefined) {
|
||||
return WIDGETS.wpedom.getSteps();
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
function getWeather() {
|
||||
let jsonWeather = storage.readJSON('weather.json');
|
||||
return jsonWeather && jsonWeather.weather ? jsonWeather.weather : undefined;
|
||||
}
|
||||
|
||||
function enableHRMSensor() {
|
||||
Bangle.setHRMPower(1, "circleclock");
|
||||
if (hrtValue == undefined) {
|
||||
hrtValue = '...';
|
||||
drawHeartRate();
|
||||
}
|
||||
}
|
||||
|
||||
let pressureLocked = false;
|
||||
let pressureCache;
|
||||
|
||||
function getPressureValue(type) {
|
||||
return new Promise((resolve) => {
|
||||
if (Bangle.getPressure) {
|
||||
if (!pressureLocked) {
|
||||
pressureLocked = true;
|
||||
if (pressureCache && pressureCache[type]) {
|
||||
resolve(pressureCache[type]);
|
||||
}
|
||||
Bangle.getPressure().then(function(d) {
|
||||
pressureLocked = false;
|
||||
if (d) {
|
||||
pressureCache = d;
|
||||
if (d[type]) {
|
||||
resolve(d[type]);
|
||||
}
|
||||
}
|
||||
}).catch(() => {});
|
||||
} else {
|
||||
if (pressureCache && pressureCache[type]) {
|
||||
resolve(pressureCache[type]);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function onLock(isLocked) {
|
||||
if (!isLocked) {
|
||||
draw();
|
||||
if (isCircleEnabled("hr")) {
|
||||
enableHRMSensor();
|
||||
}
|
||||
} else {
|
||||
Bangle.setHRMPower(0, "circleclock");
|
||||
}
|
||||
}
|
||||
Bangle.on('lock', onLock);
|
||||
|
||||
function onHRM(hrm) {
|
||||
if (isCircleEnabled("hr")) {
|
||||
if (hrm.confidence >= (settings.confidence)) {
|
||||
hrtValue = hrm.bpm;
|
||||
if (Bangle.isLCDOn()) {
|
||||
drawHeartRate();
|
||||
}
|
||||
}
|
||||
// Let us wait before we overwrite "good" HRM values:
|
||||
if (Bangle.isLCDOn()) {
|
||||
if (timerHrm) clearTimeout(timerHrm);
|
||||
timerHrm = setTimeout(() => {
|
||||
hrtValue = '...';
|
||||
drawHeartRate();
|
||||
}, settings.hrmValidity * 1000);
|
||||
}
|
||||
}
|
||||
}
|
||||
Bangle.on('HRM', onHRM);
|
||||
|
||||
function onCharging(charging) {
|
||||
if (isCircleEnabled("battery")) drawBattery();
|
||||
}
|
||||
Bangle.on('charging', onCharging);
|
||||
|
||||
|
||||
if (isCircleEnabled("hr")) {
|
||||
enableHRMSensor();
|
||||
}
|
||||
|
||||
Bangle.setUI({
|
||||
mode : "clock",
|
||||
remove : function() {
|
||||
// Called to unload all of the clock app
|
||||
Bangle.removeListener('charging', onCharging);
|
||||
Bangle.removeListener('lock', onLock);
|
||||
Bangle.removeListener('HRM', onHRM);
|
||||
|
||||
Bangle.setHRMPower(0, "circleclock");
|
||||
|
||||
if (timerHrm) clearTimeout(timerHrm);
|
||||
timerHrm = undefined;
|
||||
if (drawTimeout) clearTimeout(drawTimeout);
|
||||
drawTimeout = undefined;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,17 +1,11 @@
|
|||
{
|
||||
"minHR": 40,
|
||||
"maxHR": 200,
|
||||
"confidence": 0,
|
||||
"stepGoal": 10000,
|
||||
"stepDistanceGoal": 8000,
|
||||
"stepLength": 0.8,
|
||||
"batteryWarn": 30,
|
||||
"showWidgets": false,
|
||||
"weatherCircleData": "humidity",
|
||||
"circleCount": 3,
|
||||
"circle1": "hr",
|
||||
"circle2": "steps",
|
||||
"circle3": "battery",
|
||||
"circle1": "Bangle/HRM",
|
||||
"circle2": "Bangle/Steps",
|
||||
"circle3": "Bangle/Battery",
|
||||
"circle4": "weather",
|
||||
"circle1color": "green-red",
|
||||
"circle2color": "#0000ff",
|
||||
|
|
@ -21,7 +15,15 @@
|
|||
"circle2colorizeIcon": true,
|
||||
"circle3colorizeIcon": true,
|
||||
"circle4colorizeIcon": false,
|
||||
"hrmValidity": 60,
|
||||
"updateInterval": 60,
|
||||
"showBigWeather": false
|
||||
"showBigWeather": false,
|
||||
|
||||
"minHR": 40,
|
||||
"maxHR": 200,
|
||||
"confidence": 0,
|
||||
"stepGoal": 10000,
|
||||
"stepDistanceGoal": 8000,
|
||||
"stepLength": 0.8,
|
||||
"hrmValidity": 60
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
{ "id": "circlesclock",
|
||||
"name": "Circles clock",
|
||||
"shortName":"Circles clock",
|
||||
"version":"0.16",
|
||||
"version":"0.17",
|
||||
"description": "A clock with three or four circles for different data at the bottom in a probably familiar style",
|
||||
"icon": "app.png",
|
||||
"screenshots": [{"url":"screenshot-dark.png"}, {"url":"screenshot-light.png"}, {"url":"screenshot-dark-4.png"}, {"url":"screenshot-light-4.png"}],
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
(function(back) {
|
||||
const SETTINGS_FILE = "circlesclock.json";
|
||||
const storage = require('Storage');
|
||||
const clock_info = require("clock_info");
|
||||
let settings = Object.assign(
|
||||
storage.readJSON("circlesclock.default.json", true) || {},
|
||||
storage.readJSON(SETTINGS_FILE, true) || {}
|
||||
|
|
@ -11,8 +12,25 @@
|
|||
storage.write(SETTINGS_FILE, settings);
|
||||
}
|
||||
|
||||
const valuesCircleTypes = ["empty", "steps", "stepsDist", "hr", "battery", "weather", "sunprogress", "temperature", "pressure", "altitude"];
|
||||
const namesCircleTypes = ["empty", "steps", "distance", "heart", "battery", "weather", "sun", "temperature", "pressure", "altitude"];
|
||||
//const valuesCircleTypes = ["empty", "steps", "stepsDist", "hr", "battery", "weather", "sunprogress", "temperature", "pressure", "altitude", "timer"];
|
||||
//const namesCircleTypes = ["empty", "steps", "distance", "heart", "battery", "weather", "sun", "temperature", "pressure", "altitude", "timer"];
|
||||
var valuesCircleTypes = ["empty","weather", "sunprogress"];
|
||||
var namesCircleTypes = ["empty","weather", "sun"];
|
||||
clock_info.load().forEach(e=>{
|
||||
//TODO filter for hasRange and other
|
||||
if(!e.items.length || !e.items[0].name) {
|
||||
//suppose unnamed are varying (like timers or events), pick the first
|
||||
item = e.items[0];
|
||||
valuesCircleTypes = valuesCircleTypes.concat([e.name+"/"]);
|
||||
namesCircleTypes = namesCircleTypes.concat([e.name]);
|
||||
} else {
|
||||
let values = e.items.map(i=>e.name+"/"+i.name);
|
||||
let names =e.name=="Bangle" ? e.items.map(i=>i.name) : values;
|
||||
valuesCircleTypes = valuesCircleTypes.concat(values);
|
||||
namesCircleTypes = namesCircleTypes.concat(names);
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
const valuesColors = ["", "#ff0000", "#00ff00", "#0000ff", "#ffff00", "#ff00ff",
|
||||
"#00ffff", "#fff", "#000", "green-red", "red-green", "fg"];
|
||||
|
|
@ -36,8 +54,6 @@
|
|||
/*LANG*/'circle 2': ()=>showCircleMenu(2),
|
||||
/*LANG*/'circle 3': ()=>showCircleMenu(3),
|
||||
/*LANG*/'circle 4': ()=>showCircleMenu(4),
|
||||
/*LANG*/'heartrate': ()=>showHRMenu(),
|
||||
/*LANG*/'steps': ()=>showStepMenu(),
|
||||
/*LANG*/'battery warn': {
|
||||
value: settings.batteryWarn,
|
||||
min: 10,
|
||||
|
|
@ -78,91 +94,6 @@
|
|||
E.showMenu(menu);
|
||||
}
|
||||
|
||||
function showHRMenu() {
|
||||
let menu = {
|
||||
'': { 'title': /*LANG*/'Heartrate' },
|
||||
/*LANG*/'< Back': ()=>showMainMenu(),
|
||||
/*LANG*/'minimum': {
|
||||
value: settings.minHR,
|
||||
min: 0,
|
||||
max : 250,
|
||||
step: 5,
|
||||
format: x => {
|
||||
return x + " bpm";
|
||||
},
|
||||
onchange: x => save('minHR', x),
|
||||
},
|
||||
/*LANG*/'maximum': {
|
||||
value: settings.maxHR,
|
||||
min: 20,
|
||||
max : 250,
|
||||
step: 5,
|
||||
format: x => {
|
||||
return x + " bpm";
|
||||
},
|
||||
onchange: x => save('maxHR', x),
|
||||
},
|
||||
/*LANG*/'min. confidence': {
|
||||
value: settings.confidence,
|
||||
min: 0,
|
||||
max : 100,
|
||||
step: 10,
|
||||
format: x => {
|
||||
return x + "%";
|
||||
},
|
||||
onchange: x => save('confidence', x),
|
||||
},
|
||||
/*LANG*/'valid period': {
|
||||
value: settings.hrmValidity,
|
||||
min: 10,
|
||||
max : 1800,
|
||||
step: 10,
|
||||
format: x => {
|
||||
return x + "s";
|
||||
},
|
||||
onchange: x => save('hrmValidity', x),
|
||||
},
|
||||
};
|
||||
E.showMenu(menu);
|
||||
}
|
||||
|
||||
function showStepMenu() {
|
||||
let menu = {
|
||||
'': { 'title': /*LANG*/'Steps' },
|
||||
/*LANG*/'< Back': ()=>showMainMenu(),
|
||||
/*LANG*/'goal': {
|
||||
value: settings.stepGoal,
|
||||
min: 1000,
|
||||
max : 50000,
|
||||
step: 500,
|
||||
format: x => {
|
||||
return x;
|
||||
},
|
||||
onchange: x => save('stepGoal', x),
|
||||
},
|
||||
/*LANG*/'distance goal': {
|
||||
value: settings.stepDistanceGoal,
|
||||
min: 1000,
|
||||
max : 50000,
|
||||
step: 500,
|
||||
format: x => {
|
||||
return x;
|
||||
},
|
||||
onchange: x => save('stepDistanceGoal', x),
|
||||
},
|
||||
/*LANG*/'step length': {
|
||||
value: settings.stepLength,
|
||||
min: 0.1,
|
||||
max : 1.5,
|
||||
step: 0.01,
|
||||
format: x => {
|
||||
return x;
|
||||
},
|
||||
onchange: x => save('stepLength', x),
|
||||
}
|
||||
};
|
||||
E.showMenu(menu);
|
||||
}
|
||||
function showCircleMenu(circleId) {
|
||||
const circleName = "circle" + circleId;
|
||||
const colorKey = circleName + "color";
|
||||
|
|
@ -192,6 +123,5 @@
|
|||
E.showMenu(menu);
|
||||
}
|
||||
|
||||
|
||||
showMainMenu();
|
||||
});
|
||||
|
|
|
|||
|
|
@ -0,0 +1 @@
|
|||
0.01: New App!
|
||||
|
After Width: | Height: | Size: 661 B |
|
|
@ -0,0 +1,33 @@
|
|||
(function() {
|
||||
// get today's sunlight times for lat/lon
|
||||
var sunrise, sunset;
|
||||
|
||||
function calculate() {
|
||||
var SunCalc = require("https://raw.githubusercontent.com/mourner/suncalc/master/suncalc.js");
|
||||
const locale = require("locale");
|
||||
var location = require("Storage").readJSON("mylocation.json",1)||{};
|
||||
location.lat = location.lat||51.5072;
|
||||
location.lon = location.lon||0.1276;
|
||||
location.location = location.location||"London";
|
||||
var times = SunCalc.getTimes(new Date(), location.lat, location.lon);
|
||||
sunrise = locale.time(times.sunrise,1);
|
||||
sunset = locale.time(times.sunset,1);
|
||||
/* do we want to re-calculate this every day? Or we just assume
|
||||
that 'show' will get called once a day? */
|
||||
}
|
||||
|
||||
return {
|
||||
name: "Bangle",
|
||||
items: [
|
||||
{ name : "Sunrise",
|
||||
get : () => ({ text : sunrise,
|
||||
img : atob("GBiBAAAAAAAAAAAAAAAYAAA8AAB+AAD/AAAAAAAAAAAAAAAYAAAYAAQYIA4AcAYAYAA8AAB+AAD/AAH/gD///D///AAAAAAAAAAAAA==") }),
|
||||
show : calculate, hide : () => {}
|
||||
}, { name : "Sunset",
|
||||
get : () => ({ text : sunset,
|
||||
img : atob("GBiBAAAAAAAAAAAAAAB+AAA8AAAYAAAYAAAAAAAAAAAAAAAYAAAYAAQYIA4AcAYAYAA8AAB+AAD/AAH/gD///D///AAAAAAAAAAAAA==") }),
|
||||
show : calculate, hide : () => {}
|
||||
}
|
||||
]
|
||||
};
|
||||
})
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
{ "id": "clkinfosunrise",
|
||||
"name": "Sunrise Clockinfo",
|
||||
"version":"0.01",
|
||||
"description": "For clocks that display 'clockinfo' (messages that can be cycled through using the clock_info module) this displays sunrise and sunset based on the location from the 'My Location' app",
|
||||
"icon": "app.png",
|
||||
"type": "clkinfo",
|
||||
"tags": "clkinfo,sunrise",
|
||||
"supports" : ["BANGLEJS2"],
|
||||
"storage": [
|
||||
{"name":"sunrise.clkinfo.js","url":"clkinfo.js"}
|
||||
]
|
||||
}
|
||||
|
|
@ -12,6 +12,5 @@
|
|||
"storage": [
|
||||
{"name":"demoapp.app.js","url":"app.js"},
|
||||
{"name":"demoapp.img","url":"app-icon.js","evaluate":true}
|
||||
],
|
||||
"sortorder": -9
|
||||
]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,3 +3,4 @@
|
|||
0.03: Made the code shorter and somewhat more readable by writing some functions. Also made it work as a library where it returns the text once finished. The keyboard is now made to exit correctly when the 'back' event is called. The keyboard now uses theme colors correctly, although it still looks best with dark theme. The numbers row is now solidly green - except for highlights.
|
||||
0.04: Now displays the opened text string at launch.
|
||||
0.05: Now scrolls text when string gets longer than screen width.
|
||||
0.06: The code is now more reliable and the input snappier. Widgets will be drawn if present.
|
||||
|
|
|
|||
|
|
@ -1,12 +1,9 @@
|
|||
//Keep banglejs screen on for 100 sec at 0.55 power level for development purposes
|
||||
//Bangle.setLCDTimeout(30);
|
||||
//Bangle.setLCDPower(1);
|
||||
|
||||
exports.input = function(options) {
|
||||
options = options||{};
|
||||
var text = options.text;
|
||||
if ("string"!=typeof text) text="";
|
||||
|
||||
|
||||
var R = Bangle.appRect;
|
||||
var BGCOLOR = g.theme.bg;
|
||||
var HLCOLOR = g.theme.fg;
|
||||
var ABCCOLOR = g.toColor(1,0,0);//'#FF0000';
|
||||
|
|
@ -17,35 +14,38 @@ exports.input = function(options) {
|
|||
var SMALLFONTWIDTH = parseInt(SMALLFONT.charAt(0)*parseInt(SMALLFONT.charAt(-1)));
|
||||
|
||||
var ABC = 'abcdefghijklmnopqrstuvwxyz'.toUpperCase();
|
||||
var ABCPADDING = (g.getWidth()-6*ABC.length)/2;
|
||||
var ABCPADDING = ((R.x+R.w)-6*ABC.length)/2;
|
||||
|
||||
var NUM = ' 1234567890!?,.- ';
|
||||
var NUMHIDDEN = ' 1234567890!?,.- ';
|
||||
var NUMPADDING = (g.getWidth()-6*NUM.length)/2;
|
||||
var NUMPADDING = ((R.x+R.w)-6*NUM.length)/2;
|
||||
|
||||
var rectHeight = 40;
|
||||
|
||||
|
||||
var delSpaceLast;
|
||||
|
||||
function drawAbcRow() {
|
||||
g.clear();
|
||||
try { // Draw widgets if they are present in the current app.
|
||||
if (WIDGETS) Bangle.drawWidgets();
|
||||
} catch (_) {}
|
||||
g.setFont(SMALLFONT);
|
||||
g.setColor(ABCCOLOR);
|
||||
g.drawString(ABC, ABCPADDING, g.getHeight()/2);
|
||||
g.fillRect(0, g.getHeight()-26, g.getWidth(), g.getHeight());
|
||||
g.setFontAlign(-1, -1, 0);
|
||||
g.drawString(ABC, ABCPADDING, (R.y+R.h)/2);
|
||||
g.fillRect(0, (R.y+R.h)-26, (R.x+R.w), (R.y+R.h));
|
||||
}
|
||||
|
||||
function drawNumRow() {
|
||||
g.setFont(SMALLFONT);
|
||||
g.setColor(NUMCOLOR);
|
||||
g.drawString(NUM, NUMPADDING, g.getHeight()/4);
|
||||
g.setFontAlign(-1, -1, 0);
|
||||
g.drawString(NUM, NUMPADDING, (R.y+R.h)/4);
|
||||
|
||||
g.fillRect(NUMPADDING, g.getHeight()-rectHeight*4/3, g.getWidth()-NUMPADDING, g.getHeight()-rectHeight*2/3);
|
||||
g.fillRect(NUMPADDING, (R.y+R.h)-rectHeight*4/3, (R.x+R.w)-NUMPADDING, (R.y+R.h)-rectHeight*2/3);
|
||||
}
|
||||
|
||||
function updateTopString() {
|
||||
"ram"
|
||||
g.setColor(BGCOLOR);
|
||||
g.fillRect(0,4+20,176,13+20);
|
||||
g.setColor(0.2,0,0);
|
||||
|
|
@ -54,13 +54,10 @@ exports.input = function(options) {
|
|||
g.setColor(0.7,0,0);
|
||||
g.fillRect(rectLen+5,4+20,rectLen+10,13+20);
|
||||
g.setColor(1,1,1);
|
||||
g.setFontAlign(-1, -1, 0);
|
||||
g.drawString(text.length<=27? text.substr(-27, 27) : '<- '+text.substr(-24,24), 5, 5+20);
|
||||
}
|
||||
|
||||
drawAbcRow();
|
||||
drawNumRow();
|
||||
updateTopString();
|
||||
|
||||
var abcHL;
|
||||
var abcHLPrev = -10;
|
||||
var numHL;
|
||||
|
|
@ -68,194 +65,182 @@ exports.input = function(options) {
|
|||
var type = '';
|
||||
var typePrev = '';
|
||||
var largeCharOffset = 6;
|
||||
|
||||
|
||||
function resetChars(char, HLPrev, typePadding, heightDivisor, rowColor) {
|
||||
"ram"
|
||||
"ram";
|
||||
// Small character in list
|
||||
g.setColor(rowColor);
|
||||
g.setFont(SMALLFONT);
|
||||
g.drawString(char, typePadding + HLPrev*6, g.getHeight()/heightDivisor);
|
||||
g.setFontAlign(-1, -1, 0);
|
||||
g.drawString(char, typePadding + HLPrev*6, (R.y+R.h)/heightDivisor);
|
||||
// Large character
|
||||
g.setColor(BGCOLOR);
|
||||
g.fillRect(0,g.getHeight()/3,176,g.getHeight()/3+24);
|
||||
//g.drawString(charSet.charAt(HLPrev), typePadding + HLPrev*6 -largeCharOffset, g.getHeight()/3);; //Old implementation where I find the shape and place of letter to remove instead of just a rectangle.
|
||||
g.fillRect(0,(R.y+R.h)/3,176,(R.y+R.h)/3+24);
|
||||
//g.drawString(charSet.charAt(HLPrev), typePadding + HLPrev*6 -largeCharOffset, (R.y+R.h)/3);; //Old implementation where I find the shape and place of letter to remove instead of just a rectangle.
|
||||
// mark in the list
|
||||
}
|
||||
function showChars(char, HL, typePadding, heightDivisor) {
|
||||
"ram"
|
||||
"ram";
|
||||
// mark in the list
|
||||
g.setColor(HLCOLOR);
|
||||
g.setFont(SMALLFONT);
|
||||
if (char != 'del' && char != 'space') g.drawString(char, typePadding + HL*6, g.getHeight()/heightDivisor);
|
||||
g.setFontAlign(-1, -1, 0);
|
||||
if (char != 'del' && char != 'space') g.drawString(char, typePadding + HL*6, (R.y+R.h)/heightDivisor);
|
||||
// show new large character
|
||||
g.setFont(BIGFONT);
|
||||
g.drawString(char, typePadding + HL*6 -largeCharOffset, g.getHeight()/3);
|
||||
g.drawString(char, typePadding + HL*6 -largeCharOffset, (R.y+R.h)/3);
|
||||
g.setFont(SMALLFONT);
|
||||
}
|
||||
|
||||
|
||||
function initDraw() {
|
||||
//var R = Bangle.appRect; // To make sure it's properly updated. Not sure if this is needed.
|
||||
drawAbcRow();
|
||||
drawNumRow();
|
||||
updateTopString();
|
||||
}
|
||||
initDraw();
|
||||
//setTimeout(initDraw, 0); // So Bangle.appRect reads the correct environment. It would draw off to the side sometimes otherwise.
|
||||
|
||||
function changeCase(abcHL) {
|
||||
g.setColor(BGCOLOR);
|
||||
g.drawString(ABC, ABCPADDING, g.getHeight()/2);
|
||||
g.setFontAlign(-1, -1, 0);
|
||||
g.drawString(ABC, ABCPADDING, (R.y+R.h)/2);
|
||||
if (ABC.charAt(abcHL) == ABC.charAt(abcHL).toUpperCase()) ABC = ABC.toLowerCase();
|
||||
else ABC = ABC.toUpperCase();
|
||||
g.setColor(ABCCOLOR);
|
||||
g.drawString(ABC, ABCPADDING, g.getHeight()/2);
|
||||
g.drawString(ABC, ABCPADDING, (R.y+R.h)/2);
|
||||
}
|
||||
return new Promise((resolve,reject) => {
|
||||
// Interpret touch input
|
||||
// Interpret touch input
|
||||
Bangle.setUI({
|
||||
mode: 'custom',
|
||||
back: ()=>{
|
||||
Bangle.setUI();
|
||||
g.clearRect(Bangle.appRect);
|
||||
resolve(text);
|
||||
},
|
||||
drag: function(event) {
|
||||
mode: 'custom',
|
||||
back: ()=>{
|
||||
Bangle.setUI();
|
||||
g.clearRect(Bangle.appRect);
|
||||
resolve(text);
|
||||
},
|
||||
drag: function(event) {
|
||||
"ram";
|
||||
// ABCDEFGHIJKLMNOPQRSTUVWXYZ
|
||||
// Choose character by draging along red rectangle at bottom of screen
|
||||
if (event.y >= ( (R.y+R.h) - 12 )) {
|
||||
// Translate x-position to character
|
||||
if (event.x < ABCPADDING) { abcHL = 0; }
|
||||
else if (event.x >= 176-ABCPADDING) { abcHL = 25; }
|
||||
else { abcHL = Math.floor((event.x-ABCPADDING)/6); }
|
||||
|
||||
// ABCDEFGHIJKLMNOPQRSTUVWXYZ
|
||||
// Choose character by draging along red rectangle at bottom of screen
|
||||
if (event.y >= ( g.getHeight() - 12 )) {
|
||||
// Translate x-position to character
|
||||
if (event.x < ABCPADDING) { abcHL = 0; }
|
||||
else if (event.x >= 176-ABCPADDING) { abcHL = 25; }
|
||||
else { abcHL = Math.floor((event.x-ABCPADDING)/6); }
|
||||
// Datastream for development purposes
|
||||
//print(event.x, event.y, event.b, ABC.charAt(abcHL), ABC.charAt(abcHLPrev));
|
||||
|
||||
// Datastream for development purposes
|
||||
//print(event.x, event.y, event.b, ABC.charAt(abcHL), ABC.charAt(abcHLPrev));
|
||||
// Unmark previous character and mark the current one...
|
||||
// Handling switching between letters and numbers/punctuation
|
||||
if (typePrev != 'abc') resetChars(NUM.charAt(numHLPrev), numHLPrev, NUMPADDING, 4, NUMCOLOR);
|
||||
|
||||
// Unmark previous character and mark the current one...
|
||||
// Handling switching between letters and numbers/punctuation
|
||||
if (typePrev != 'abc') resetChars(NUM.charAt(numHLPrev), numHLPrev, NUMPADDING, 4, NUMCOLOR);
|
||||
|
||||
if (abcHL != abcHLPrev) {
|
||||
resetChars(ABC.charAt(abcHLPrev), abcHLPrev, ABCPADDING, 2, ABCCOLOR);
|
||||
showChars(ABC.charAt(abcHL), abcHL, ABCPADDING, 2);
|
||||
if (abcHL != abcHLPrev) {
|
||||
resetChars(ABC.charAt(abcHLPrev), abcHLPrev, ABCPADDING, 2, ABCCOLOR);
|
||||
showChars(ABC.charAt(abcHL), abcHL, ABCPADDING, 2);
|
||||
}
|
||||
// Print string at top of screen
|
||||
if (event.b == 0) {
|
||||
text = text + ABC.charAt(abcHL);
|
||||
updateTopString();
|
||||
|
||||
// Autoswitching letter case
|
||||
if (ABC.charAt(abcHL) == ABC.charAt(abcHL).toUpperCase()) changeCase(abcHL);
|
||||
}
|
||||
// Update previous character to current one
|
||||
abcHLPrev = abcHL;
|
||||
typePrev = 'abc';
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
// 12345678901234567890
|
||||
// Choose number or puctuation by draging on green rectangle
|
||||
else if ((event.y < ( g.getHeight() - 12 )) && (event.y > ( g.getHeight() - 52 ))) {
|
||||
// Translate x-position to character
|
||||
if (event.x < NUMPADDING) { numHL = 0; }
|
||||
else if (event.x > 176-NUMPADDING) { numHL = NUM.length-1; }
|
||||
else { numHL = Math.floor((event.x-NUMPADDING)/6); }
|
||||
|
||||
// Datastream for development purposes
|
||||
//print(event.x, event.y, event.b, NUM.charAt(numHL), NUM.charAt(numHLPrev));
|
||||
|
||||
// Unmark previous character and mark the current one...
|
||||
// Handling switching between letters and numbers/punctuation
|
||||
if (typePrev != 'num') resetChars(ABC.charAt(abcHLPrev), abcHLPrev, ABCPADDING, 2, ABCCOLOR);
|
||||
|
||||
if (numHL != numHLPrev) {
|
||||
resetChars(NUM.charAt(numHLPrev), numHLPrev, NUMPADDING, 4, NUMCOLOR);
|
||||
showChars(NUM.charAt(numHL), numHL, NUMPADDING, 4);
|
||||
}
|
||||
// Print string at top of screen
|
||||
if (event.b == 0) {
|
||||
g.setColor(HLCOLOR);
|
||||
// Backspace if releasing before list of numbers/punctuation
|
||||
if (event.x < NUMPADDING) {
|
||||
// show delete sign
|
||||
showChars('del', 0, g.getWidth()/2 +6 -27 , 4);
|
||||
delSpaceLast = 1;
|
||||
text = text.slice(0, -1);
|
||||
updateTopString();
|
||||
//print(text);
|
||||
}
|
||||
// Append space if releasing after list of numbers/punctuation
|
||||
else if (event.x > g.getWidth()-NUMPADDING) {
|
||||
//show space sign
|
||||
showChars('space', 0, g.getWidth()/2 +6 -6*3*5/2 , 4);
|
||||
delSpaceLast = 1;
|
||||
text = text + ' ';
|
||||
updateTopString();
|
||||
//print(text);
|
||||
}
|
||||
// Append selected number/punctuation
|
||||
else {
|
||||
text = text + NUMHIDDEN.charAt(numHL);
|
||||
// Print string at top of screen
|
||||
if (event.b == 0) {
|
||||
text = text + ABC.charAt(abcHL);
|
||||
updateTopString();
|
||||
|
||||
// Autoswitching letter case
|
||||
if ((text.charAt(text.length-1) == '.') || (text.charAt(text.length-1) == '!')) changeCase();
|
||||
if (ABC.charAt(abcHL) == ABC.charAt(abcHL).toUpperCase()) changeCase(abcHL);
|
||||
}
|
||||
// Update previous character to current one
|
||||
abcHLPrev = abcHL;
|
||||
typePrev = 'abc';
|
||||
}
|
||||
// Update previous character to current one
|
||||
numHLPrev = numHL;
|
||||
typePrev = 'num';
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
// Make a space or backspace by swiping right or left on screen above green rectangle
|
||||
else if (event.y > 20+4) {
|
||||
if (event.b == 0) {
|
||||
g.setColor(HLCOLOR);
|
||||
if (event.x < g.getWidth()/2) {
|
||||
resetChars(ABC.charAt(abcHLPrev), abcHLPrev, ABCPADDING, 2, ABCCOLOR);
|
||||
|
||||
// 12345678901234567890
|
||||
// Choose number or puctuation by draging on green rectangle
|
||||
else if ((event.y < ( (R.y+R.h) - 12 )) && (event.y > ( (R.y+R.h) - 52 ))) {
|
||||
// Translate x-position to character
|
||||
if (event.x < NUMPADDING) { numHL = 0; }
|
||||
else if (event.x > 176-NUMPADDING) { numHL = NUM.length-1; }
|
||||
else { numHL = Math.floor((event.x-NUMPADDING)/6); }
|
||||
|
||||
// Datastream for development purposes
|
||||
//print(event.x, event.y, event.b, NUM.charAt(numHL), NUM.charAt(numHLPrev));
|
||||
|
||||
// Unmark previous character and mark the current one...
|
||||
// Handling switching between letters and numbers/punctuation
|
||||
if (typePrev != 'num') resetChars(ABC.charAt(abcHLPrev), abcHLPrev, ABCPADDING, 2, ABCCOLOR);
|
||||
|
||||
if (numHL != numHLPrev) {
|
||||
resetChars(NUM.charAt(numHLPrev), numHLPrev, NUMPADDING, 4, NUMCOLOR);
|
||||
|
||||
// show delete sign
|
||||
showChars('del', 0, g.getWidth()/2 +6 -27 , 4);
|
||||
delSpaceLast = 1;
|
||||
|
||||
// Backspace and draw string upper right corner
|
||||
text = text.slice(0, -1);
|
||||
updateTopString();
|
||||
if (text.length==0) changeCase(abcHL);
|
||||
//print(text, 'undid');
|
||||
showChars(NUM.charAt(numHL), numHL, NUMPADDING, 4);
|
||||
}
|
||||
else {
|
||||
resetChars(ABC.charAt(abcHLPrev), abcHLPrev, ABCPADDING, 2, ABCCOLOR);
|
||||
resetChars(NUM.charAt(numHLPrev), numHLPrev, NUMPADDING, 4, NUMCOLOR);
|
||||
// Print string at top of screen
|
||||
if (event.b == 0) {
|
||||
g.setColor(HLCOLOR);
|
||||
// Backspace if releasing before list of numbers/punctuation
|
||||
if (event.x < NUMPADDING) {
|
||||
// show delete sign
|
||||
showChars('del', 0, (R.x+R.w)/2 +6 -27 , 4);
|
||||
delSpaceLast = 1;
|
||||
text = text.slice(0, -1);
|
||||
updateTopString();
|
||||
//print(text);
|
||||
}
|
||||
// Append space if releasing after list of numbers/punctuation
|
||||
else if (event.x > (R.x+R.w)-NUMPADDING) {
|
||||
//show space sign
|
||||
showChars('space', 0, (R.x+R.w)/2 +6 -6*3*5/2 , 4);
|
||||
delSpaceLast = 1;
|
||||
text = text + ' ';
|
||||
updateTopString();
|
||||
//print(text);
|
||||
}
|
||||
// Append selected number/punctuation
|
||||
else {
|
||||
text = text + NUMHIDDEN.charAt(numHL);
|
||||
updateTopString();
|
||||
|
||||
//show space sign
|
||||
showChars('space', 0, g.getWidth()/2 +6 -6*3*5/2 , 4);
|
||||
delSpaceLast = 1;
|
||||
// Autoswitching letter case
|
||||
if ((text.charAt(text.length-1) == '.') || (text.charAt(text.length-1) == '!')) changeCase();
|
||||
}
|
||||
}
|
||||
// Update previous character to current one
|
||||
numHLPrev = numHL;
|
||||
typePrev = 'num';
|
||||
}
|
||||
|
||||
// Append space and draw string upper right corner
|
||||
text = text + NUMHIDDEN.charAt(0);
|
||||
updateTopString();
|
||||
//print(text, 'made space');
|
||||
// Make a space or backspace by swiping right or left on screen above green rectangle
|
||||
else if (event.y > 20+4) {
|
||||
if (event.b == 0) {
|
||||
g.setColor(HLCOLOR);
|
||||
if (event.x < (R.x+R.w)/2) {
|
||||
resetChars(ABC.charAt(abcHLPrev), abcHLPrev, ABCPADDING, 2, ABCCOLOR);
|
||||
resetChars(NUM.charAt(numHLPrev), numHLPrev, NUMPADDING, 4, NUMCOLOR);
|
||||
|
||||
// show delete sign
|
||||
showChars('del', 0, (R.x+R.w)/2 +6 -27 , 4);
|
||||
delSpaceLast = 1;
|
||||
|
||||
// Backspace and draw string upper right corner
|
||||
text = text.slice(0, -1);
|
||||
updateTopString();
|
||||
if (text.length==0) changeCase(abcHL);
|
||||
//print(text, 'undid');
|
||||
}
|
||||
else {
|
||||
resetChars(ABC.charAt(abcHLPrev), abcHLPrev, ABCPADDING, 2, ABCCOLOR);
|
||||
resetChars(NUM.charAt(numHLPrev), numHLPrev, NUMPADDING, 4, NUMCOLOR);
|
||||
|
||||
//show space sign
|
||||
showChars('space', 0, (R.x+R.w)/2 +6 -6*3*5/2 , 4);
|
||||
delSpaceLast = 1;
|
||||
|
||||
// Append space and draw string upper right corner
|
||||
text = text + NUMHIDDEN.charAt(0);
|
||||
updateTopString();
|
||||
//print(text, 'made space');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
/* return new Promise((resolve,reject) => {
|
||||
Bangle.setUI({mode:"custom", back:()=>{
|
||||
Bangle.setUI();
|
||||
g.clearRect(Bangle.appRect);
|
||||
Bangle.setUI();
|
||||
resolve(text);
|
||||
}});
|
||||
}); */
|
||||
|
||||
});
|
||||
});
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{ "id": "dragboard",
|
||||
"name": "Dragboard",
|
||||
"version":"0.05",
|
||||
"version":"0.06",
|
||||
"description": "A library for text input via swiping keyboard",
|
||||
"icon": "app.png",
|
||||
"type":"textinput",
|
||||
|
|
|
|||
|
|
@ -16,3 +16,11 @@
|
|||
0.16: Use default Bangle formatter for booleans
|
||||
0.17: Bangle 2: Fast loading on exit to clock face. Added option for exit to
|
||||
clock face by timeout.
|
||||
0.18: Bangle 2: Move interactions inside setUI. Replace "one click exit" with
|
||||
back-functionality through setUI, adding the red back button as well. Hardware
|
||||
button to exit is no longer an option.
|
||||
0.19: Bangle 2: Utilize new Bangle.load(), Bangle.showClock() functions to
|
||||
facilitate 'fast switching' of apps where available.
|
||||
0.20: Bangle 2: Revert use of Bangle.load() to classic load() calls since
|
||||
widgets would still be loaded when they weren't supposed to.
|
||||
|
||||
|
|
|
|||
|
|
@ -1,52 +1,47 @@
|
|||
{ // must be inside our own scope here so that when we are unloaded everything disappears
|
||||
|
||||
/* Desktop launcher
|
||||
*
|
||||
*/
|
||||
/* Desktop launcher
|
||||
*
|
||||
*/
|
||||
|
||||
let settings = Object.assign({
|
||||
showClocks: true,
|
||||
showLaunchers: true,
|
||||
direct: false,
|
||||
oneClickExit: false,
|
||||
swipeExit: false,
|
||||
timeOut: "Off"
|
||||
}, require('Storage').readJSON("dtlaunch.json", true) || {});
|
||||
let settings = Object.assign({
|
||||
showClocks: true,
|
||||
showLaunchers: true,
|
||||
direct: false,
|
||||
swipeExit: false,
|
||||
timeOut: "Off"
|
||||
}, require('Storage').readJSON("dtlaunch.json", true) || {});
|
||||
|
||||
if (settings.oneClickExit) {
|
||||
var buttonWatch = setWatch(_=> returnToClock(), BTN1, {edge: 'falling'});
|
||||
}
|
||||
|
||||
let s = require("Storage");
|
||||
let s = require("Storage");
|
||||
var apps = s.list(/\.info$/).map(app=>{
|
||||
let a=s.readJSON(app,1);
|
||||
return a && {
|
||||
name:a.name, type:a.type, icon:a.icon, sortorder:a.sortorder, src:a.src
|
||||
};}).filter(
|
||||
app=>app && (app.type=="app" || (app.type=="clock" && settings.showClocks) || (app.type=="launch" && settings.showLaunchers) || !app.type));
|
||||
let a=s.readJSON(app,1);
|
||||
return a && {
|
||||
name:a.name, type:a.type, icon:a.icon, sortorder:a.sortorder, src:a.src
|
||||
};}).filter(
|
||||
app=>app && (app.type=="app" || (app.type=="clock" && settings.showClocks) || (app.type=="launch" && settings.showLaunchers) || !app.type));
|
||||
|
||||
apps.sort((a,b)=>{
|
||||
let n=(0|a.sortorder)-(0|b.sortorder);
|
||||
if (n) return n; // do sortorder first
|
||||
if (a.name<b.name) return -1;
|
||||
if (a.name>b.name) return 1;
|
||||
return 0;
|
||||
});
|
||||
apps.forEach(app=>{
|
||||
apps.sort((a,b)=>{
|
||||
let n=(0|a.sortorder)-(0|b.sortorder);
|
||||
if (n) return n; // do sortorder first
|
||||
if (a.name<b.name) return -1;
|
||||
if (a.name>b.name) return 1;
|
||||
return 0;
|
||||
});
|
||||
apps.forEach(app=>{
|
||||
if (app.icon)
|
||||
app.icon = s.read(app.icon); // should just be a link to a memory area
|
||||
});
|
||||
|
||||
let Napps = apps.length;
|
||||
let Npages = Math.ceil(Napps/4);
|
||||
let maxPage = Npages-1;
|
||||
let selected = -1;
|
||||
let oldselected = -1;
|
||||
let page = 0;
|
||||
const XOFF = 24;
|
||||
const YOFF = 30;
|
||||
let Napps = apps.length;
|
||||
let Npages = Math.ceil(Napps/4);
|
||||
let maxPage = Npages-1;
|
||||
let selected = -1;
|
||||
let oldselected = -1;
|
||||
let page = 0;
|
||||
const XOFF = 24;
|
||||
const YOFF = 30;
|
||||
|
||||
let drawIcon= function(p,n,selected) {
|
||||
let drawIcon= function(p,n,selected) {
|
||||
let x = (n%2)*72+XOFF;
|
||||
let y = n>1?72+YOFF:YOFF;
|
||||
(selected?g.setColor(g.theme.fgH):g.setColor(g.theme.bg)).fillRect(x+11,y+3,x+60,y+52);
|
||||
|
|
@ -70,103 +65,91 @@ let drawIcon= function(p,n,selected) {
|
|||
}
|
||||
}
|
||||
g.drawString(line.trim(),x+36,y+54+lineY*8);
|
||||
};
|
||||
};
|
||||
|
||||
let drawPage = function(p){
|
||||
let drawPage = function(p){
|
||||
g.reset();
|
||||
g.clearRect(0,24,175,175);
|
||||
let O = 88+YOFF/2-12*(Npages/2);
|
||||
for (let j=0;j<Npages;j++){
|
||||
let y = O+j*12;
|
||||
g.setColor(g.theme.fg);
|
||||
if (j==page) g.fillCircle(XOFF/2,y,4);
|
||||
else g.drawCircle(XOFF/2,y,4);
|
||||
let y = O+j*12;
|
||||
g.setColor(g.theme.fg);
|
||||
if (j==page) g.fillCircle(XOFF/2,y,4);
|
||||
else g.drawCircle(XOFF/2,y,4);
|
||||
}
|
||||
for (let i=0;i<4;i++) {
|
||||
if (!apps[p*4+i]) return i;
|
||||
drawIcon(p,i,selected==i && !settings.direct);
|
||||
if (!apps[p*4+i]) return i;
|
||||
drawIcon(p,i,selected==i && !settings.direct);
|
||||
}
|
||||
g.flip();
|
||||
};
|
||||
};
|
||||
|
||||
Bangle.loadWidgets();
|
||||
//g.clear();
|
||||
Bangle.drawWidgets();
|
||||
drawPage(0);
|
||||
Bangle.loadWidgets();
|
||||
drawPage(0);
|
||||
|
||||
let swipeListenerDt = function(dirLeftRight, dirUpDown){
|
||||
let swipeListenerDt = function(dirLeftRight, dirUpDown){
|
||||
updateTimeoutToClock();
|
||||
selected = 0;
|
||||
oldselected=-1;
|
||||
if(settings.swipeExit && dirLeftRight==1) returnToClock();
|
||||
if(settings.swipeExit && dirLeftRight==1) Bangle.showClock();
|
||||
if (dirUpDown==-1||dirLeftRight==-1){
|
||||
++page; if (page>maxPage) page=0;
|
||||
drawPage(page);
|
||||
++page; if (page>maxPage) page=0;
|
||||
drawPage(page);
|
||||
} else if (dirUpDown==1||(dirLeftRight==1 && !settings.swipeExit)){
|
||||
--page; if (page<0) page=maxPage;
|
||||
drawPage(page);
|
||||
--page; if (page<0) page=maxPage;
|
||||
drawPage(page);
|
||||
}
|
||||
};
|
||||
Bangle.on("swipe",swipeListenerDt);
|
||||
};
|
||||
|
||||
let isTouched = function(p,n){
|
||||
let isTouched = function(p,n){
|
||||
if (n<0 || n>3) return false;
|
||||
let x1 = (n%2)*72+XOFF; let y1 = n>1?72+YOFF:YOFF;
|
||||
let x2 = x1+71; let y2 = y1+81;
|
||||
return (p.x>x1 && p.y>y1 && p.x<x2 && p.y<y2);
|
||||
};
|
||||
};
|
||||
|
||||
let touchListenerDt = function(_,p){
|
||||
let touchListenerDt = function(_,p){
|
||||
updateTimeoutToClock();
|
||||
let i;
|
||||
for (i=0;i<4;i++){
|
||||
if((page*4+i)<Napps){
|
||||
if (isTouched(p,i)) {
|
||||
drawIcon(page,i,true && !settings.direct);
|
||||
if (selected>=0 || settings.direct) {
|
||||
if (selected!=i && !settings.direct){
|
||||
drawIcon(page,selected,false);
|
||||
} else {
|
||||
load(apps[page*4+i].src);
|
||||
}
|
||||
}
|
||||
selected=i;
|
||||
break;
|
||||
if((page*4+i)<Napps){
|
||||
if (isTouched(p,i)) {
|
||||
drawIcon(page,i,true && !settings.direct);
|
||||
if (selected>=0 || settings.direct) {
|
||||
if (selected!=i && !settings.direct){
|
||||
drawIcon(page,selected,false);
|
||||
} else {
|
||||
load(apps[page*4+i].src);
|
||||
}
|
||||
}
|
||||
selected=i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if ((i==4 || (page*4+i)>Napps) && selected>=0) {
|
||||
drawIcon(page,selected,false);
|
||||
selected=-1;
|
||||
drawIcon(page,selected,false);
|
||||
selected=-1;
|
||||
}
|
||||
};
|
||||
Bangle.on("touch",touchListenerDt);
|
||||
};
|
||||
|
||||
const returnToClock = function() {
|
||||
Bangle.setUI();
|
||||
if (buttonWatch) {
|
||||
clearWatch(buttonWatch);
|
||||
delete buttonWatch;
|
||||
}
|
||||
if (timeoutToClock) {
|
||||
clearTimeout(timeoutToClock);
|
||||
delete timeoutToClock;
|
||||
}
|
||||
Bangle.removeListener("swipe", swipeListenerDt);
|
||||
Bangle.removeListener("touch", touchListenerDt);
|
||||
var apps = [];
|
||||
delete apps;
|
||||
delete returnToClock;
|
||||
setTimeout(eval, 0, s.read(".bootcde"));
|
||||
};
|
||||
Bangle.setUI({
|
||||
mode : 'custom',
|
||||
back : Bangle.showClock,
|
||||
swipe : swipeListenerDt,
|
||||
touch : touchListenerDt,
|
||||
remove : ()=>{if (timeoutToClock) clearTimeout(timeoutToClock);}
|
||||
});
|
||||
|
||||
// taken from Icon Launcher with minor alterations
|
||||
var timeoutToClock;
|
||||
const updateTimeoutToClock = function(){
|
||||
if (settings.timeOut!="Off"){
|
||||
let time=parseInt(settings.timeOut); //the "s" will be trimmed by the parseInt
|
||||
if (timeoutToClock) clearTimeout(timeoutToClock);
|
||||
timeoutToClock = setTimeout(returnToClock,time*1000);
|
||||
}
|
||||
};
|
||||
updateTimeoutToClock();
|
||||
// taken from Icon Launcher with minor alterations
|
||||
let timeoutToClock;
|
||||
const updateTimeoutToClock = function(){
|
||||
if (settings.timeOut!="Off"){
|
||||
let time=parseInt(settings.timeOut); //the "s" will be trimmed by the parseInt
|
||||
if (timeoutToClock) clearTimeout(timeoutToClock);
|
||||
timeoutToClock = setTimeout(Bangle.showClock,time*1000);
|
||||
}
|
||||
};
|
||||
updateTimeoutToClock();
|
||||
|
||||
} // end of app scope
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"id": "dtlaunch",
|
||||
"name": "Desktop Launcher",
|
||||
"version": "0.17",
|
||||
"version": "0.20",
|
||||
"description": "Desktop style App Launcher with six (four for Bangle 2) apps per page - fast access if you have lots of apps installed.",
|
||||
"screenshots": [{"url":"shot1.png"},{"url":"shot2.png"},{"url":"shot3.png"}],
|
||||
"icon": "icon.png",
|
||||
|
|
|
|||
|
|
@ -5,7 +5,6 @@
|
|||
showClocks: true,
|
||||
showLaunchers: true,
|
||||
direct: false,
|
||||
oneClickExit:false,
|
||||
swipeExit: false,
|
||||
timeOut: "Off"
|
||||
}, require('Storage').readJSON(FILE, true) || {});
|
||||
|
|
@ -47,13 +46,6 @@
|
|||
writeSettings();
|
||||
}
|
||||
},
|
||||
/*LANG*/'One click exit': {
|
||||
value: settings.oneClickExit,
|
||||
onchange: v => {
|
||||
settings.oneClickExit = v;
|
||||
writeSettings();
|
||||
}
|
||||
},
|
||||
/*LANG*/'Time Out': { // Adapted from Icon Launcher
|
||||
value: timeOutChoices.indexOf(settings.timeOut),
|
||||
min: 0,
|
||||
|
|
@ -63,6 +55,6 @@
|
|||
settings.timeOut = timeOutChoices[v];
|
||||
writeSettings();
|
||||
}
|
||||
},
|
||||
}
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -5,3 +5,4 @@
|
|||
0.03: Improve bootloader update safety. Now sets unsafeFlash:1 to allow flash with 2v11 and later
|
||||
Add CRC checks for common bootloaders that we know don't work
|
||||
0.04: Include a precompiled bootloader for easy bootloader updates
|
||||
0.05: Rename Bootloader->DFU and add explanation to avoid confusion with Bootloader app
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
<link rel="stylesheet" href="../../css/spectre.min.css">
|
||||
</head>
|
||||
<body>
|
||||
<p>This tool allows you to update the bootloader on <a href="https://www.espruino.com/Bangle.js2">Bangle.js 2</a> devices
|
||||
<p>This tool allows you to update the firmware on <a href="https://www.espruino.com/Bangle.js2">Bangle.js 2</a> devices
|
||||
from within the App Loader.</p>
|
||||
|
||||
<div id="fw-unknown">
|
||||
|
|
@ -12,27 +12,41 @@
|
|||
<a href="https://www.espruino.com/Bangle.js#firmware-updates" target="_blank">see the Bangle.js 1 instructions</a></b></p>
|
||||
</div>
|
||||
<ul>
|
||||
<p>Your current firmware version is <span id="fw-version" style="font-weight:bold">unknown</span> and bootloader is <span id="boot-version" style="font-weight:bold">unknown</span></p>
|
||||
<p>Your current firmware version is <span id="fw-version" style="font-weight:bold">unknown</span> and DFU is <span id="boot-version" style="font-weight:bold">unknown</span></p>
|
||||
</ul>
|
||||
<div id="fw-ok" style="display:none">
|
||||
<p>If you have an early (KickStarter or developer) Bangle.js device and still have the old 2v10.x bootloader, the Firmware Update
|
||||
will fail with a message about the bootloader version. If so, please <a href="bootloader_espruino_2v12_banglejs2.hex" class="fw-link">click here to update to bootloader 2v12</a> and then click the 'Upload' button that appears.</p>
|
||||
<p>If you have an early (KickStarter or developer) Bangle.js device and still have the old 2v10.x DFU, the Firmware Update
|
||||
will fail with a message about the DFU version. If so, please <a href="bootloader_espruino_2v12_banglejs2.hex" class="fw-link">click here to update to DFU 2v12</a> and then click the 'Upload' button that appears.</p>
|
||||
<div id="latest-firmware" style="display:none">
|
||||
<p>The currently available Espruino firmware releases are:</p>
|
||||
<ul id="latest-firmware-list">
|
||||
</ul>
|
||||
<p>To update, click a link above and then click the 'Upload' button that appears.</p>
|
||||
</div>
|
||||
<a href="#" id="advanced-btn">Advanced ▼</a>
|
||||
|
||||
<p><a href="#" id="info-btn">What is DFU? ▼</a></p>
|
||||
<div id="info-div" style="display:none">
|
||||
<p><b>What is DFU?</b></p>
|
||||
<p><b>DFU</b> stands for <b>Device Firmware Update</b>. This is the first
|
||||
bit of code that runs when Bangle.js starts, and it is able to update the
|
||||
Bangle.js firmware. Normally you would update firmware via this Firmware
|
||||
Updater app, but if for some reason Bangle.js will not boot, you can
|
||||
<a href="https://www.espruino.com/Bangle.js2#firmware-updates">always use DFU to to the update manually</a>.</p>
|
||||
<p>DFU is itself a bootloader, but here we're calling it DFU to avoid confusion
|
||||
with the Bootloader app in the app loader (which prepares Bangle.js for running apps).</p>
|
||||
</div>
|
||||
|
||||
<p><a href="#" id="advanced-btn">Advanced ▼</a></p>
|
||||
<div id="advanced-div" style="display:none">
|
||||
<p><b>Advanced</b></p>
|
||||
<p>Firmware updates via this tool work differently to the NRF Connect method mentioned on
|
||||
<a href="https://www.espruino.com/Bangle.js2#firmware-updates">the Bangle.js 2 page</a>. Firmware
|
||||
is uploaded to a file on the Bangle. Once complete the Bangle reboots and the bootloader copies
|
||||
is uploaded to a file on the Bangle. Once complete the Bangle reboots and DFU copies
|
||||
the new firmware into internal Storage.</p>
|
||||
<p>In addition to the links above, you can upload a hex or zip file directly below. This file should be an <code>.app_hex</code>
|
||||
file, *not* the normal <code>.hex</code> (as that contains the bootloader as well).</p>
|
||||
file, *not* the normal <code>.hex</code> (as that contains the DFU as well).</p>
|
||||
<p><b>DANGER!</b> No verification is performed on uploaded ZIP or HEX files - you could
|
||||
potentially overwrite your bootloader with the wrong binary and brick your Bangle.</p>
|
||||
potentially overwrite your DFU with the wrong binary and brick your Bangle.</p>
|
||||
<input class="form-input" type="file" id="fileLoader" accept=".hex,.app_hex,.zip"/><br>
|
||||
</div>
|
||||
<p><button id="upload" class="btn btn-primary" style="display:none">Upload</button></p>
|
||||
|
|
@ -73,7 +87,7 @@ function onInit(device) {
|
|||
document.getElementById("fw-ok").style = "";
|
||||
}
|
||||
Puck.eval("E.CRC32(E.memoryArea(0xF7000,0x7000))", crc => {
|
||||
console.log("Bootloader CRC = "+crc);
|
||||
console.log("DFU CRC = "+crc);
|
||||
var version = `unknown (CRC ${crc})`;
|
||||
var ok = true;
|
||||
if (crc==1339551013) { version = "2v10.219"; ok = false; }
|
||||
|
|
@ -299,8 +313,8 @@ function createJS_app(binary, startAddress, endAddress) {
|
|||
bin32[3] = VERSION; // VERSION! Use this to test ourselves
|
||||
console.log("CRC 0x"+bin32[2].toString(16));
|
||||
hexJS = "";//`\x10if (E.CRC32(E.memoryArea(${startAddress},${endAddress-startAddress}))==${bin32[2]}) { print("FIRMWARE UP TO DATE!"); load();}\n`;
|
||||
hexJS += `\x10if (E.CRC32(E.memoryArea(0xF7000,0x7000))==1339551013) { print("BOOTLOADER 2v10.219 needs update"); load();}\n`;
|
||||
hexJS += `\x10if (E.CRC32(E.memoryArea(0xF7000,0x7000))==1207580954) { print("BOOTLOADER 2v10.236 needs update"); load();}\n`;
|
||||
hexJS += `\x10if (E.CRC32(E.memoryArea(0xF7000,0x7000))==1339551013) { print("DFU 2v10.219 needs update"); load();}\n`;
|
||||
hexJS += `\x10if (E.CRC32(E.memoryArea(0xF7000,0x7000))==1207580954) { print("DFU 2v10.236 needs update"); load();}\n`;
|
||||
hexJS += '\x10var s = require("Storage");\n';
|
||||
hexJS += '\x10s.erase(".firmware");\n';
|
||||
var CHUNKSIZE = 2048;
|
||||
|
|
@ -320,7 +334,7 @@ function createJS_app(binary, startAddress, endAddress) {
|
|||
function createJS_bootloader(binary, startAddress, endAddress) {
|
||||
var crc = CRC32(binary);
|
||||
console.log("CRC 0x"+crc.toString(16));
|
||||
hexJS = `\x10if (E.CRC32(E.memoryArea(${startAddress},${endAddress-startAddress}))==${crc}) { print("BOOTLOADER UP TO DATE!"); load();}\n`;
|
||||
hexJS = `\x10if (E.CRC32(E.memoryArea(${startAddress},${endAddress-startAddress}))==${crc}) { print("DFU UP TO DATE!"); load();}\n`;
|
||||
hexJS += `\x10var _fw = new Uint8Array(${binary.length})\n`;
|
||||
var CHUNKSIZE = 1024;
|
||||
for (var i=0;i<binary.length;i+=CHUNKSIZE) {
|
||||
|
|
@ -330,14 +344,14 @@ function createJS_bootloader(binary, startAddress, endAddress) {
|
|||
hexJS += '\x10_fw.set(atob("'+chunk+'"), 0x'+(i).toString(16)+');\n';
|
||||
}
|
||||
hexJS += `\x10(function() { if (E.CRC32(_fw)!=${crc}) throw "Invalid CRC: 0x"+E.CRC32(_fw).toString(16);\n`;
|
||||
hexJS += 'E.showMessage("Flashing Bootloader...")\n';
|
||||
hexJS += 'E.showMessage("Flashing DFU...")\n';
|
||||
hexJS += 'E.setFlags({unsafeFlash:1})\n';
|
||||
hexJS += 'var f = require("Flash");\n';
|
||||
for (var i=startAddress;i<endAddress;i+=4096)
|
||||
hexJS += 'f.erasePage(0x'+i.toString(16)+');\n';
|
||||
hexJS += `f.write(_fw,${startAddress});\n`;
|
||||
hexJS += `})()\n`;
|
||||
log("Bootloader ready for upload");
|
||||
log("DFU ready for upload");
|
||||
}
|
||||
|
||||
function hexFileLoaded(hexString) {
|
||||
|
|
@ -383,7 +397,7 @@ function hexFileLoaded(hexString) {
|
|||
});
|
||||
|
||||
if (startAddress == 0xf7000) {
|
||||
console.log("Bootloader - Writing to internal flash");
|
||||
console.log("DFU - Writing to internal flash");
|
||||
createJS_bootloader(new Uint8Array(binary.buffer, HEADER_LEN), startAddress, endAddress);
|
||||
} else {
|
||||
console.log("App - Writing to external flash");
|
||||
|
|
@ -406,6 +420,10 @@ function handleUpload() {
|
|||
|
||||
document.getElementById('fileLoader').addEventListener('change', handleFileSelect, false);
|
||||
document.getElementById("upload").addEventListener("click", handleUpload);
|
||||
document.getElementById("info-btn").addEventListener("click", function() {
|
||||
document.getElementById("info-btn").style = "display:none";
|
||||
document.getElementById("info-div").style = "";
|
||||
});
|
||||
document.getElementById("advanced-btn").addEventListener("click", function() {
|
||||
document.getElementById("advanced-btn").style = "display:none";
|
||||
document.getElementById("advanced-div").style = "";
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"id": "fwupdate",
|
||||
"name": "Firmware Update",
|
||||
"version": "0.04",
|
||||
"version": "0.05",
|
||||
"description": "Uploads new Espruino firmwares to Bangle.js 2",
|
||||
"icon": "app.png",
|
||||
"type": "RAM",
|
||||
|
|
@ -10,5 +10,5 @@
|
|||
"custom": "custom.html",
|
||||
"customConnect": true,
|
||||
"storage": [],
|
||||
"sortorder": 20
|
||||
"sortorder": -11
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,65 @@
|
|||
0.01: Initial code
|
||||
|
||||
0.05:
|
||||
* We now buzz before reaching a waypoint.
|
||||
* Display is only updated when not locked.
|
||||
* We detect leaving path and finding path again.
|
||||
* We display remaining distance to next point.
|
||||
|
||||
0.06:
|
||||
* Special display for points with steep turns.
|
||||
* Buzz on points with steep turns and unlock.
|
||||
* Losing gps is now displayed.
|
||||
|
||||
0.07:
|
||||
* We now use orientation to detect current segment
|
||||
when segments overlap going in both directions.
|
||||
* File format is now versioned.
|
||||
|
||||
0.08:
|
||||
* Don't use gps course anymore but figure it from previous positions.
|
||||
* Bugfix: path colors are back.
|
||||
* Always buzz when reaching waypoint even if unlocked.
|
||||
|
||||
0.09:
|
||||
* We now display interest points.
|
||||
* Menu to choose which file to load.
|
||||
|
||||
0.10:
|
||||
* Display performances enhancement.
|
||||
* Waypoints information is embedded in file and extracted from comments on
|
||||
points.
|
||||
* Bugfix in map display (last segment was missing + wrong colors).
|
||||
* Waypoint detections using OSM + sharp angles
|
||||
* New algorith for direction detection
|
||||
|
||||
0.11:
|
||||
* Better fonts (more free space, still readable).
|
||||
* Display direction to nearest point when lost.
|
||||
* Display average speed.
|
||||
* Turn off gps when locked and between points
|
||||
|
||||
0.12:
|
||||
* Bugfix in speed computation.
|
||||
* Bugfix in current segment detection.
|
||||
* Bugfix : lost direction.
|
||||
* Larger fonts.
|
||||
* Detecting next point correctly when going back.
|
||||
|
||||
0.13:
|
||||
* Bugfix in lost direction.
|
||||
* Buzzing 100m ahead instead of 50m.
|
||||
* Detect sharp turns.
|
||||
* Display instant speed.
|
||||
* New instant speed algorithm.
|
||||
* Bugfix for remaining distance when going back.
|
||||
|
||||
0.14:
|
||||
* Detect starting distance to compute a good average speed.
|
||||
* Settings
|
||||
* Account for breaks in average speed.
|
||||
|
||||
0.15:
|
||||
* Record traveled distance to get a good average speed.
|
||||
* Breaks (low speed) will not count in average speed.
|
||||
* Bugfix in average speed.
|
||||
|
|
@ -0,0 +1,109 @@
|
|||
# Gipy
|
||||
|
||||
Gipy allows you to follow gpx traces on your watch.
|
||||
|
||||

|
||||
|
||||
|
||||
It is for now meant for bicycling and not hiking
|
||||
(it uses your movement to figure out your orientation
|
||||
and walking is too slow).
|
||||
|
||||
It is untested on Banglejs1. If you can try it, you would be welcome.
|
||||
|
||||
This software is not perfect but surprisingly useful.
|
||||
|
||||
## Features
|
||||
|
||||
It provides the following features :
|
||||
|
||||
- display the path with current position from gps
|
||||
- detects and buzzes if you leave the path
|
||||
- buzzes before sharp turns
|
||||
- buzzes before nodes with comments
|
||||
(for example when you need to turn in https://mapstogpx.com/)
|
||||
- display instant / average speed
|
||||
- display distance to next node
|
||||
- display additional data from openstreetmap :
|
||||
- water points
|
||||
- toilets
|
||||
- artwork
|
||||
- bakeries
|
||||
|
||||
optionally it can also:
|
||||
|
||||
- try to turn off gps between crossroads to save battery
|
||||
|
||||
## Usage
|
||||
|
||||
### Preparing the file
|
||||
|
||||
You first need to have 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/).
|
||||
|
||||
Note that *mapstogpx* has a super nice feature in its advanced settings.
|
||||
You can turn on 'next turn info' and be warned by the watch when you need to turn.
|
||||
|
||||
Once you have your gpx file you need to convert it to *gpc* which is my custom file format.
|
||||
They are smaller than gpx and reduce the number of computations left to be done on the watch.
|
||||
|
||||
Just click the disk icon and select your gpx file.
|
||||
This will request additional information from openstreetmap.
|
||||
Your path will be displayed in svg.
|
||||
|
||||
### Starting Gipy
|
||||
|
||||
Once you start gipy you will have a menu for selecting your trace (if more than one).
|
||||
Choose the one you want and here you go :
|
||||
|
||||

|
||||
|
||||
On your screen you can see :
|
||||
|
||||
- yourself (the big black dot)
|
||||
- the path (the top of the screen is in front of you)
|
||||
- if needed a projection of yourself on the path (small black dot)
|
||||
- extremities of segments as white dots
|
||||
- turning points as doubled white dots
|
||||
- some text on the left (from top to bottom) :
|
||||
* current time
|
||||
* left distance till end of current segment
|
||||
* distance from start of path / path length
|
||||
* average speed / instant speed
|
||||
- interest points from openstreetmap as color dots :
|
||||
* red : bakery
|
||||
* deep blue : water point
|
||||
* cyan : toilets (often doubles as water point)
|
||||
* green : artwork
|
||||
- a *turn* indicator on the top right when you reach a turning point
|
||||
- a *gps* indicator (blinking) on the top right if you lose gps signal
|
||||
- a *lost* indicator on the top right if you stray too far away from path
|
||||
- a black segment extending from you when you are lost, indicating the rough direction of where to go
|
||||
|
||||
### Settings
|
||||
|
||||
Few settings for now (feel free to suggest me more) :
|
||||
|
||||
- keep gps alive : if turned off, will try to save battery by turning the gps off on long segments
|
||||
- max speed : used to compute how long to turn the gps off
|
||||
|
||||
### Caveats
|
||||
|
||||
It is good to use but you should know :
|
||||
|
||||
- the gps might take a long time to start initially (see the assisted gps update app).
|
||||
- gps signal is noisy : there is therefore a small delay for instant speed. sometimes you may jump somewhere else.
|
||||
- your gpx trace has been decimated and approximated : the **REAL PATH** might be **A FEW METERS AWAY**
|
||||
- sometimes the watch will tell you that you are lost but you are in fact on the path.
|
||||
- battery saving by turning off gps is not very well tested (disabled by default).
|
||||
- buzzing does not always work: when there is a high load on the watch, the buzzes might just never happen :-(.
|
||||
- buzzes are not strong enough to be always easily noticed.
|
||||
- be careful when **GOING DOWNHILL AT VERY HIGH SPEED**. I already missed a few turning points and by the time I realized it,
|
||||
I had to go back uphill by quite a distance.
|
||||
|
||||
## Creator
|
||||
|
||||
Feel free to give me feedback : is it useful for you ? what other features would you like ?
|
||||
|
||||
frederic.wagner@imag.fr
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
|
||||
* bugs
|
||||
|
||||
- when exactly on turn, distance to next point is still often 50m
|
||||
-----> it does not buzz very often on turns
|
||||
|
||||
- when going backwards we have a tendencing to get a wrong current_segment
|
||||
|
||||
* additional features
|
||||
|
||||
- config screen
|
||||
- are we on foot (and should use compass)
|
||||
|
||||
- we need to buzz 200m before sharp turns (or even better, 30seconds)
|
||||
(and look at more than next point)
|
||||
|
||||
- display distance to next water/toilet ?
|
||||
- dynamic map rescale
|
||||
- display scale (100m)
|
||||
|
||||
- compress path ?
|
||||
|
||||
* misc
|
||||
|
||||
- code is becoming messy
|
||||
|
|
@ -0,0 +1 @@
|
|||
require("heatshrink").decompress(atob("mEwwkBiIA/AE8VqoAGCy1RiN3CyYuBi93uIXJIBV3AAIuMBY4XjQ5YXPRAIAEOwIABPBC4LF54wGF6IwFC5jWGIwxIJC4xJFgDuJJAxJFC6TEIJBzEHGCIYPGA5JQC44YPGBBJKY4gwRfQL4DGCL4GGCAXPGAxGBAAJIMGAwWCGCoWGC55HHJB5HIC8pGDSChfXC5AWIL5ynOC45GJC4h3IIyYwCFxwADgB1SC44uSC4guSAH4Ab"))
|
||||
|
|
@ -0,0 +1,766 @@
|
|||
let simulated = false;
|
||||
let file_version = 3;
|
||||
let code_key = 47490;
|
||||
|
||||
var settings = Object.assign(
|
||||
{
|
||||
keep_gps_alive: true,
|
||||
max_speed: 35,
|
||||
},
|
||||
require("Storage").readJSON("gipy.json", true) || {}
|
||||
);
|
||||
|
||||
let interests_colors = [
|
||||
0xf800, // Bakery, red
|
||||
0x001f, // DrinkingWater, blue
|
||||
0x07ff, // Toilets, cyan
|
||||
0x07e0, // Artwork, green
|
||||
];
|
||||
|
||||
function binary_search(array, x) {
|
||||
let start = 0,
|
||||
end = array.length - 1;
|
||||
|
||||
while (start <= end) {
|
||||
let mid = Math.floor((start + end) / 2);
|
||||
if (array[mid] < x) start = mid + 1;
|
||||
else end = mid - 1;
|
||||
}
|
||||
return start;
|
||||
}
|
||||
|
||||
class Status {
|
||||
constructor(path) {
|
||||
this.path = path;
|
||||
this.on_path = false; // are we on the path or lost ?
|
||||
this.position = null; // where we are
|
||||
this.adjusted_cos_direction = null; // cos of where we look at
|
||||
this.adjusted_sin_direction = null; // sin of where we look at
|
||||
this.current_segment = null; // which segment is closest
|
||||
this.reaching = null; // which waypoint are we reaching ?
|
||||
this.distance_to_next_point = null; // how far are we from next point ?
|
||||
this.paused_time = 0.0; // how long did we stop (stops don't count in avg speed)
|
||||
this.paused_since = getTime();
|
||||
|
||||
let r = [0];
|
||||
// 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));
|
||||
}
|
||||
previous_point = point;
|
||||
}
|
||||
this.remaining_distances = r; // how much distance remains at start of each segment
|
||||
this.starting_time = this.paused_since; // time we start
|
||||
this.advanced_distance = 0.0;
|
||||
this.gps_coordinates_counter = 0; // how many coordinates did we receive
|
||||
this.old_points = [];
|
||||
this.old_times = [];
|
||||
}
|
||||
new_position_reached(position) {
|
||||
// we try to figure out direction by looking at previous points
|
||||
// instead of the gps course which is not very nice.
|
||||
this.gps_coordinates_counter += 1;
|
||||
let now = getTime();
|
||||
this.old_points.push(position);
|
||||
this.old_times.push(now);
|
||||
|
||||
if (this.old_points.length == 1) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let last_point = this.old_points[this.old_points.length - 1];
|
||||
let oldest_point = this.old_points[0];
|
||||
|
||||
// every 7 points we count the distance
|
||||
if (this.gps_coordinates_counter % 7 == 0) {
|
||||
let distance = last_point.distance(oldest_point);
|
||||
if (distance < 150.0) {
|
||||
// to avoid gps glitches
|
||||
this.advanced_distance += distance;
|
||||
}
|
||||
}
|
||||
|
||||
if (this.old_points.length == 8) {
|
||||
let p1 = this.old_points[0]
|
||||
.plus(this.old_points[1])
|
||||
.plus(this.old_points[2])
|
||||
.plus(this.old_points[3])
|
||||
.times(1 / 4);
|
||||
let p2 = this.old_points[4]
|
||||
.plus(this.old_points[5])
|
||||
.plus(this.old_points[6])
|
||||
.plus(this.old_points[7])
|
||||
.times(1 / 4);
|
||||
let t1 = (this.old_times[1] + this.old_times[2]) / 2;
|
||||
let t2 = (this.old_times[5] + this.old_times[6]) / 2;
|
||||
this.instant_speed = p1.distance(p2) / (t2 - t1);
|
||||
this.old_points.shift();
|
||||
this.old_times.shift();
|
||||
} else {
|
||||
this.instant_speed =
|
||||
oldest_point.distance(last_point) / (now - this.old_times[0]);
|
||||
|
||||
// update paused time if we are too slow
|
||||
if (this.instant_speed < 2) {
|
||||
if (this.paused_since === null) {
|
||||
this.paused_since = now;
|
||||
}
|
||||
} else {
|
||||
if (this.paused_since !== null) {
|
||||
this.paused_time += now - this.paused_since;
|
||||
this.paused_since = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
// let's just take angle of segment between newest point and a point a bit before
|
||||
let previous_index = this.old_points.length - 3;
|
||||
if (previous_index < 0) {
|
||||
previous_index = 0;
|
||||
}
|
||||
let diff = position.minus(this.old_points[previous_index]);
|
||||
let angle = Math.atan2(diff.lat, diff.lon);
|
||||
return angle;
|
||||
}
|
||||
update_position(new_position, maybe_direction) {
|
||||
let direction = this.new_position_reached(new_position);
|
||||
if (direction === null) {
|
||||
if (maybe_direction === null) {
|
||||
return;
|
||||
} else {
|
||||
direction = maybe_direction;
|
||||
}
|
||||
}
|
||||
|
||||
this.adjusted_cos_direction = Math.cos(-direction - Math.PI / 2.0);
|
||||
this.adjusted_sin_direction = Math.sin(-direction - Math.PI / 2.0);
|
||||
cos_direction = Math.cos(direction);
|
||||
sin_direction = Math.sin(direction);
|
||||
this.position = new_position;
|
||||
|
||||
// detect segment we are on now
|
||||
let res = this.path.nearest_segment(
|
||||
this.position,
|
||||
Math.max(0, this.current_segment - 1),
|
||||
Math.min(this.current_segment + 2, this.path.len - 1),
|
||||
cos_direction,
|
||||
sin_direction
|
||||
);
|
||||
let orientation = res[0];
|
||||
let next_segment = res[1];
|
||||
|
||||
if (this.is_lost(next_segment)) {
|
||||
// it did not work, try anywhere
|
||||
res = this.path.nearest_segment(
|
||||
this.position,
|
||||
0,
|
||||
this.path.len - 1,
|
||||
cos_direction,
|
||||
sin_direction
|
||||
);
|
||||
orientation = res[0];
|
||||
next_segment = res[1];
|
||||
}
|
||||
// now check if we strayed away from path or back to it
|
||||
let lost = this.is_lost(next_segment);
|
||||
if (this.on_path == lost) {
|
||||
// if status changes
|
||||
if (lost) {
|
||||
Bangle.buzz(); // we lost path
|
||||
setTimeout(() => Bangle.buzz(), 500);
|
||||
setTimeout(() => Bangle.buzz(), 1000);
|
||||
setTimeout(() => Bangle.buzz(), 1500);
|
||||
}
|
||||
this.on_path = !lost;
|
||||
}
|
||||
|
||||
this.current_segment = next_segment;
|
||||
|
||||
// check if we are nearing the next point on our path and alert the user
|
||||
let next_point = this.current_segment + (1 - orientation);
|
||||
this.distance_to_next_point = Math.ceil(
|
||||
this.position.distance(this.path.point(next_point))
|
||||
);
|
||||
|
||||
// disable gps when far from next point and locked
|
||||
if (Bangle.isLocked() && !settings.keep_gps_alive) {
|
||||
let time_to_next_point =
|
||||
(this.distance_to_next_point * 3.6) / settings.max_speed;
|
||||
if (time_to_next_point > 60) {
|
||||
Bangle.setGPSPower(false, "gipy");
|
||||
setTimeout(function () {
|
||||
Bangle.setGPSPower(true, "gipy");
|
||||
}, time_to_next_point);
|
||||
}
|
||||
}
|
||||
if (this.reaching != next_point && this.distance_to_next_point <= 100) {
|
||||
this.reaching = next_point;
|
||||
let reaching_waypoint = this.path.is_waypoint(next_point);
|
||||
if (reaching_waypoint) {
|
||||
Bangle.buzz();
|
||||
setTimeout(() => Bangle.buzz(), 500);
|
||||
setTimeout(() => Bangle.buzz(), 1000);
|
||||
setTimeout(() => Bangle.buzz(), 1500);
|
||||
if (Bangle.isLocked()) {
|
||||
Bangle.setLocked(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
// re-display
|
||||
this.display(orientation);
|
||||
}
|
||||
remaining_distance(orientation) {
|
||||
let remaining_in_correct_orientation =
|
||||
this.remaining_distances[this.current_segment + 1] +
|
||||
this.position.distance(this.path.point(this.current_segment + 1));
|
||||
|
||||
if (orientation == 0) {
|
||||
return remaining_in_correct_orientation;
|
||||
} else {
|
||||
return this.remaining_distances[0] - remaining_in_correct_orientation;
|
||||
}
|
||||
}
|
||||
is_lost(segment) {
|
||||
let distance_to_nearest = this.position.distance_to_segment(
|
||||
this.path.point(segment),
|
||||
this.path.point(segment + 1)
|
||||
);
|
||||
return distance_to_nearest > 50;
|
||||
}
|
||||
display(orientation) {
|
||||
g.clear();
|
||||
this.display_map();
|
||||
|
||||
this.display_interest_points();
|
||||
this.display_stats(orientation);
|
||||
Bangle.drawWidgets();
|
||||
}
|
||||
display_interest_points() {
|
||||
// this is the algorithm in case we have a lot of interest points
|
||||
// let's draw all points for 5 segments centered on current one
|
||||
let starting_group = Math.floor(Math.max(this.current_segment - 2, 0) / 3);
|
||||
let ending_group = Math.floor(
|
||||
Math.min(this.current_segment + 2, this.path.len - 2) / 3
|
||||
);
|
||||
let starting_bucket = binary_search(
|
||||
this.path.interests_starts,
|
||||
starting_group
|
||||
);
|
||||
let ending_bucket = binary_search(
|
||||
this.path.interests_starts,
|
||||
ending_group + 0.5
|
||||
);
|
||||
// we have 5 points per bucket
|
||||
let end_index = Math.min(
|
||||
this.path.interests_types.length - 1,
|
||||
ending_bucket * 5
|
||||
);
|
||||
for (let i = starting_bucket * 5; i <= end_index; i++) {
|
||||
let index = this.path.interests_on_path[i];
|
||||
let interest_point = this.path.interest_point(index);
|
||||
let color = this.path.interest_color(i);
|
||||
let c = interest_point.coordinates(
|
||||
this.position,
|
||||
this.adjusted_cos_direction,
|
||||
this.adjusted_sin_direction
|
||||
);
|
||||
g.setColor(color).fillCircle(c[0], c[1], 5);
|
||||
}
|
||||
}
|
||||
display_stats(orientation) {
|
||||
let remaining_distance = this.remaining_distance(orientation);
|
||||
let rounded_distance = Math.round(remaining_distance / 100) / 10;
|
||||
let total = Math.round(this.remaining_distances[0] / 100) / 10;
|
||||
let now = new Date();
|
||||
let minutes = now.getMinutes().toString();
|
||||
if (minutes.length < 2) {
|
||||
minutes = "0" + minutes;
|
||||
}
|
||||
let hours = now.getHours().toString();
|
||||
g.setFont("6x8:2")
|
||||
.setFontAlign(-1, -1, 0)
|
||||
.setColor(g.theme.fg)
|
||||
.drawString(hours + ":" + minutes, 0, 30);
|
||||
|
||||
g.setFont("6x8:2").drawString(
|
||||
"" + this.distance_to_next_point + "m",
|
||||
0,
|
||||
g.getHeight() - 49
|
||||
);
|
||||
|
||||
let point_time = this.old_times[this.old_times.length - 1];
|
||||
let done_in = point_time - this.starting_time - this.paused_time;
|
||||
let approximate_speed = Math.round(
|
||||
(this.advanced_distance * 3.6) / done_in
|
||||
);
|
||||
let approximate_instant_speed = Math.round(this.instant_speed * 3.6);
|
||||
|
||||
g.setFont("6x8:2")
|
||||
.setFontAlign(-1, -1, 0)
|
||||
.drawString(
|
||||
"" + approximate_speed + "km/h (in." + approximate_instant_speed + ")",
|
||||
0,
|
||||
g.getHeight() - 15
|
||||
);
|
||||
|
||||
g.setFont("6x8:2").drawString(
|
||||
"" + rounded_distance + "/" + total,
|
||||
0,
|
||||
g.getHeight() - 32
|
||||
);
|
||||
|
||||
if (this.distance_to_next_point <= 100) {
|
||||
if (this.path.is_waypoint(this.reaching)) {
|
||||
g.setColor(0.0, 1.0, 0.0)
|
||||
.setFont("6x15")
|
||||
.drawString("turn", g.getWidth() - 50, 30);
|
||||
}
|
||||
}
|
||||
if (!this.on_path) {
|
||||
g.setColor(1.0, 0.0, 0.0)
|
||||
.setFont("6x15")
|
||||
.drawString("lost", g.getWidth() - 55, 35);
|
||||
}
|
||||
}
|
||||
display_map() {
|
||||
// don't display all segments, only those neighbouring current segment
|
||||
// this is most likely to be the correct display
|
||||
// while lowering the cost a lot
|
||||
//
|
||||
// note that all code is inlined here to speed things up from 400ms to 200ms
|
||||
let start = Math.max(this.current_segment - 4, 0);
|
||||
let end = Math.min(this.current_segment + 6, this.path.len);
|
||||
let pos = this.position;
|
||||
let cos = this.adjusted_cos_direction;
|
||||
let sin = this.adjusted_sin_direction;
|
||||
let points = this.path.points;
|
||||
let cx = pos.lon;
|
||||
let cy = pos.lat;
|
||||
let half_width = g.getWidth() / 2;
|
||||
let half_height = g.getHeight() / 2;
|
||||
let previous_x = null;
|
||||
let previous_y = null;
|
||||
for (let i = start; i < end; i++) {
|
||||
let tx = (points[2 * i] - cx) * 40000.0;
|
||||
let ty = (points[2 * i + 1] - cy) * 40000.0;
|
||||
let rotated_x = tx * cos - ty * sin;
|
||||
let rotated_y = tx * sin + ty * cos;
|
||||
let x = half_width - Math.round(rotated_x); // x is inverted
|
||||
let y = half_height + Math.round(rotated_y);
|
||||
if (previous_x !== null) {
|
||||
if (i == this.current_segment + 1) {
|
||||
g.setColor(0.0, 1.0, 0.0);
|
||||
} else {
|
||||
g.setColor(1.0, 0.0, 0.0);
|
||||
}
|
||||
g.drawLine(previous_x, previous_y, x, y);
|
||||
|
||||
if (this.path.is_waypoint(i - 1)) {
|
||||
g.setColor(g.theme.fg);
|
||||
g.fillCircle(previous_x, previous_y, 6);
|
||||
g.setColor(g.theme.bg);
|
||||
g.fillCircle(previous_x, previous_y, 5);
|
||||
}
|
||||
g.setColor(g.theme.fg);
|
||||
g.fillCircle(previous_x, previous_y, 4);
|
||||
g.setColor(g.theme.bg);
|
||||
g.fillCircle(previous_x, previous_y, 3);
|
||||
}
|
||||
|
||||
previous_x = x;
|
||||
previous_y = y;
|
||||
}
|
||||
|
||||
if (this.path.is_waypoint(end - 1)) {
|
||||
g.setColor(g.theme.fg);
|
||||
g.fillCircle(previous_x, previous_y, 6);
|
||||
g.setColor(g.theme.bg);
|
||||
g.fillCircle(previous_x, previous_y, 5);
|
||||
}
|
||||
g.setColor(g.theme.fg);
|
||||
g.fillCircle(previous_x, previous_y, 4);
|
||||
g.setColor(g.theme.bg);
|
||||
g.fillCircle(previous_x, previous_y, 3);
|
||||
|
||||
// now display ourselves
|
||||
g.setColor(g.theme.fgH);
|
||||
g.fillCircle(half_width, half_height, 5);
|
||||
|
||||
// display old points for direction debug
|
||||
// for (let i = 0; i < this.old_points.length; i++) {
|
||||
// let tx = (this.old_points[i].lon - cx) * 40000.0;
|
||||
// let ty = (this.old_points[i].lat - cy) * 40000.0;
|
||||
// let rotated_x = tx * cos - ty * sin;
|
||||
// let rotated_y = tx * sin + ty * cos;
|
||||
// let x = half_width - Math.round(rotated_x); // x is inverted
|
||||
// let y = half_height + Math.round(rotated_y);
|
||||
// g.setColor((i + 1) / 4.0, 0.0, 0.0);
|
||||
// g.fillCircle(x, y, 3);
|
||||
// }
|
||||
|
||||
// display current-segment's projection for debug
|
||||
let projection = pos.closest_segment_point(
|
||||
this.path.point(this.current_segment),
|
||||
this.path.point(this.current_segment + 1)
|
||||
);
|
||||
|
||||
let tx = (projection.lon - cx) * 40000.0;
|
||||
let ty = (projection.lat - cy) * 40000.0;
|
||||
let rotated_x = tx * cos - ty * sin;
|
||||
let rotated_y = tx * sin + ty * cos;
|
||||
let x = half_width - Math.round(rotated_x); // x is inverted
|
||||
let y = half_height + Math.round(rotated_y);
|
||||
g.setColor(g.theme.fg);
|
||||
g.fillCircle(x, y, 4);
|
||||
|
||||
// display direction to next point if lost
|
||||
if (!this.on_path) {
|
||||
let next_point = this.path.point(this.current_segment + 1);
|
||||
let diff = next_point.minus(this.position);
|
||||
let angle = Math.atan2(diff.lat, diff.lon);
|
||||
let tx = Math.cos(angle) * 50.0;
|
||||
let ty = Math.sin(angle) * 50.0;
|
||||
let rotated_x = tx * cos - ty * sin;
|
||||
let rotated_y = tx * sin + ty * cos;
|
||||
let x = half_width - Math.round(rotated_x); // x is inverted
|
||||
let y = half_height + Math.round(rotated_y);
|
||||
g.setColor(g.theme.fgH).drawLine(half_width, half_height, x, y);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function load_gpc(filename) {
|
||||
let buffer = require("Storage").readArrayBuffer(filename);
|
||||
let offset = 0;
|
||||
|
||||
// header
|
||||
let header = Uint16Array(buffer, offset, 5);
|
||||
offset += 5 * 2;
|
||||
let key = header[0];
|
||||
let version = header[1];
|
||||
let points_number = header[2];
|
||||
if (key != code_key || version > file_version) {
|
||||
E.showMessage("Invalid gpc file");
|
||||
load();
|
||||
}
|
||||
|
||||
// path points
|
||||
let points = Float64Array(buffer, offset, points_number * 2);
|
||||
offset += 8 * points_number * 2;
|
||||
|
||||
// path waypoints
|
||||
let waypoints_len = Math.ceil(points_number / 8.0);
|
||||
let waypoints = Uint8Array(buffer, offset, waypoints_len);
|
||||
offset += waypoints_len;
|
||||
|
||||
// interest points
|
||||
let interests_number = header[3];
|
||||
let interests_coordinates = Float64Array(
|
||||
buffer,
|
||||
offset,
|
||||
interests_number * 2
|
||||
);
|
||||
offset += 8 * interests_number * 2;
|
||||
let interests_types = Uint8Array(buffer, offset, interests_number);
|
||||
offset += interests_number;
|
||||
|
||||
// interests on path
|
||||
let interests_on_path_number = header[4];
|
||||
let interests_on_path = Uint16Array(buffer, offset, interests_on_path_number);
|
||||
offset += 2 * interests_on_path_number;
|
||||
let starts_length = Math.ceil(interests_on_path_number / 5.0);
|
||||
let interests_starts = Uint16Array(buffer, offset, starts_length);
|
||||
offset += 2 * starts_length;
|
||||
|
||||
return [
|
||||
points,
|
||||
waypoints,
|
||||
interests_coordinates,
|
||||
interests_types,
|
||||
interests_on_path,
|
||||
interests_starts,
|
||||
];
|
||||
}
|
||||
|
||||
class Path {
|
||||
constructor(arrays) {
|
||||
this.points = arrays[0];
|
||||
this.waypoints = arrays[1];
|
||||
this.interests_coordinates = arrays[2];
|
||||
this.interests_types = arrays[3];
|
||||
this.interests_on_path = arrays[4];
|
||||
this.interests_starts = arrays[5];
|
||||
}
|
||||
|
||||
is_waypoint(point_index) {
|
||||
let i = Math.floor(point_index / 8);
|
||||
let subindex = point_index % 8;
|
||||
let r = this.waypoints[i] & (1 << subindex);
|
||||
return r != 0;
|
||||
}
|
||||
|
||||
// execute op on all segments.
|
||||
// start is index of first wanted segment
|
||||
// end is 1 after index of last wanted segment
|
||||
on_segments(op, start, end) {
|
||||
let previous_point = null;
|
||||
for (let i = start; i < end + 1; i++) {
|
||||
let point = new Point(this.points[2 * i], this.points[2 * i + 1]);
|
||||
if (previous_point !== null) {
|
||||
op(previous_point, point, i);
|
||||
}
|
||||
previous_point = point;
|
||||
}
|
||||
}
|
||||
|
||||
// return point at given index
|
||||
point(index) {
|
||||
let lon = this.points[2 * index];
|
||||
let lat = this.points[2 * index + 1];
|
||||
return new Point(lon, lat);
|
||||
}
|
||||
|
||||
interest_point(index) {
|
||||
let lon = this.interests_coordinates[2 * index];
|
||||
let lat = this.interests_coordinates[2 * index + 1];
|
||||
return new Point(lon, lat);
|
||||
}
|
||||
|
||||
interest_color(index) {
|
||||
return interests_colors[this.interests_types[index]];
|
||||
}
|
||||
|
||||
// return index of segment which is nearest from point.
|
||||
// we need a direction because we need there is an ambiguity
|
||||
// for overlapping segments which are taken once to go and once to come back.
|
||||
// (in the other direction).
|
||||
nearest_segment(point, start, end, cos_direction, sin_direction) {
|
||||
// we are going to compute two min distances, one for each direction.
|
||||
let indices = [0, 0];
|
||||
let mins = [Number.MAX_VALUE, Number.MAX_VALUE];
|
||||
this.on_segments(
|
||||
function (p1, p2, i) {
|
||||
// we use the dot product to figure out if oriented correctly
|
||||
// let distance = point.fake_distance_to_segment(p1, p2);
|
||||
|
||||
let projection = point.closest_segment_point(p1, p2);
|
||||
let distance = point.fake_distance(projection);
|
||||
|
||||
// let d = projection.minus(point).times(40000.0);
|
||||
// let rotated_x = d.lon * acos - d.lat * asin;
|
||||
// let rotated_y = d.lon * asin + d.lat * acos;
|
||||
// let x = g.getWidth() / 2 - Math.round(rotated_x); // x is inverted
|
||||
// let y = g.getHeight() / 2 + Math.round(rotated_y);
|
||||
//
|
||||
let diff = p2.minus(p1);
|
||||
let dot = cos_direction * diff.lon + sin_direction * diff.lat;
|
||||
let orientation = +(dot < 0); // index 0 is good orientation
|
||||
// g.setColor(0.0, 0.0 + orientation, 1.0 - orientation).fillCircle(
|
||||
// x,
|
||||
// y,
|
||||
// 10
|
||||
// );
|
||||
if (distance <= mins[orientation]) {
|
||||
mins[orientation] = distance;
|
||||
indices[orientation] = i - 1;
|
||||
}
|
||||
},
|
||||
start,
|
||||
end
|
||||
);
|
||||
// by default correct orientation (0) wins
|
||||
// but if other one is really closer, return other one
|
||||
if (mins[1] < mins[0] / 10.0) {
|
||||
return [1, indices[1]];
|
||||
} else {
|
||||
return [0, indices[0]];
|
||||
}
|
||||
}
|
||||
get len() {
|
||||
return this.points.length / 2;
|
||||
}
|
||||
}
|
||||
|
||||
class Point {
|
||||
constructor(lon, lat) {
|
||||
this.lon = lon;
|
||||
this.lat = lat;
|
||||
}
|
||||
coordinates(current_position, cos_direction, sin_direction) {
|
||||
let translated = this.minus(current_position).times(40000.0);
|
||||
let rotated_x =
|
||||
translated.lon * cos_direction - translated.lat * sin_direction;
|
||||
let rotated_y =
|
||||
translated.lon * sin_direction + translated.lat * cos_direction;
|
||||
return [
|
||||
g.getWidth() / 2 - Math.round(rotated_x), // x is inverted
|
||||
g.getHeight() / 2 + Math.round(rotated_y),
|
||||
];
|
||||
}
|
||||
minus(other_point) {
|
||||
let xdiff = this.lon - other_point.lon;
|
||||
let ydiff = this.lat - other_point.lat;
|
||||
return new Point(xdiff, ydiff);
|
||||
}
|
||||
plus(other_point) {
|
||||
return new Point(this.lon + other_point.lon, this.lat + other_point.lat);
|
||||
}
|
||||
length_squared(other_point) {
|
||||
let d = this.minus(other_point);
|
||||
return d.lon * d.lon + d.lat * d.lat;
|
||||
}
|
||||
times(scalar) {
|
||||
return new Point(this.lon * scalar, this.lat * scalar);
|
||||
}
|
||||
dot(other_point) {
|
||||
return this.lon * other_point.lon + this.lat * other_point.lat;
|
||||
}
|
||||
distance(other_point) {
|
||||
//see https://www.movable-type.co.uk/scripts/latlong.html
|
||||
const R = 6371e3; // metres
|
||||
const phi1 = (this.lat * Math.PI) / 180;
|
||||
const phi2 = (other_point.lat * Math.PI) / 180;
|
||||
const deltaphi = ((other_point.lat - this.lat) * Math.PI) / 180;
|
||||
const deltalambda = ((other_point.lon - this.lon) * Math.PI) / 180;
|
||||
|
||||
const a =
|
||||
Math.sin(deltaphi / 2) * Math.sin(deltaphi / 2) +
|
||||
Math.cos(phi1) *
|
||||
Math.cos(phi2) *
|
||||
Math.sin(deltalambda / 2) *
|
||||
Math.sin(deltalambda / 2);
|
||||
const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
|
||||
|
||||
return R * c; // in meters
|
||||
}
|
||||
fake_distance(other_point) {
|
||||
return Math.sqrt(this.length_squared(other_point));
|
||||
}
|
||||
closest_segment_point(v, w) {
|
||||
// from : https://stackoverflow.com/questions/849211/shortest-distance-between-a-point-and-a-line-segment
|
||||
// Return minimum distance between line segment vw and point p
|
||||
let l2 = v.length_squared(w); // i.e. |w-v|^2 - avoid a sqrt
|
||||
if (l2 == 0.0) {
|
||||
return v; // v == w case
|
||||
}
|
||||
// Consider the line extending the segment, parameterized as v + t (w - v).
|
||||
// We find projection of point p onto the line.
|
||||
// It falls where t = [(p-v) . (w-v)] / |w-v|^2
|
||||
// We clamp t from [0,1] to handle points outside the segment vw.
|
||||
let t = Math.max(0, Math.min(1, this.minus(v).dot(w.minus(v)) / l2));
|
||||
return v.plus(w.minus(v).times(t)); // Projection falls on the segment
|
||||
}
|
||||
distance_to_segment(v, w) {
|
||||
let projection = this.closest_segment_point(v, w);
|
||||
return this.distance(projection);
|
||||
}
|
||||
fake_distance_to_segment(v, w) {
|
||||
let projection = this.closest_segment_point(v, w);
|
||||
return this.fake_distance(projection);
|
||||
}
|
||||
}
|
||||
|
||||
Bangle.loadWidgets();
|
||||
|
||||
let fake_gps_point = 0.0;
|
||||
function simulate_gps(status) {
|
||||
if (fake_gps_point > status.path.len - 1) {
|
||||
return;
|
||||
}
|
||||
let point_index = Math.floor(fake_gps_point);
|
||||
if (point_index >= status.path.len) {
|
||||
return;
|
||||
}
|
||||
//let p1 = status.path.point(0);
|
||||
//let n = status.path.len;
|
||||
//let p2 = status.path.point(n - 1);
|
||||
let p1 = status.path.point(point_index);
|
||||
let p2 = status.path.point(point_index + 1);
|
||||
|
||||
let alpha = fake_gps_point - point_index;
|
||||
let pos = p1.times(1 - alpha).plus(p2.times(alpha));
|
||||
let old_pos = status.position;
|
||||
|
||||
fake_gps_point += 0.05; // advance simulation
|
||||
status.update_position(pos, null);
|
||||
}
|
||||
|
||||
function drawMenu() {
|
||||
const menu = {
|
||||
"": { title: "choose trace" },
|
||||
};
|
||||
var files = require("Storage").list(".gpc");
|
||||
for (var i = 0; i < files.length; ++i) {
|
||||
menu[files[i]] = start.bind(null, files[i]);
|
||||
}
|
||||
menu["Exit"] = function () {
|
||||
load();
|
||||
};
|
||||
E.showMenu(menu);
|
||||
}
|
||||
|
||||
function start(fn) {
|
||||
E.showMenu();
|
||||
console.log("loading", fn);
|
||||
|
||||
// let path = new Path(load_gpx("test.gpx"));
|
||||
let path = new Path(load_gpc(fn));
|
||||
let status = new Status(path);
|
||||
|
||||
if (simulated) {
|
||||
status.position = new Point(status.path.point(0));
|
||||
setInterval(simulate_gps, 500, status);
|
||||
} else {
|
||||
// let's display start while waiting for gps signal
|
||||
let p1 = status.path.point(0);
|
||||
let p2 = status.path.point(1);
|
||||
let diff = p2.minus(p1);
|
||||
let direction = Math.atan2(diff.lat, diff.lon);
|
||||
Bangle.setLocked(false);
|
||||
status.update_position(p1, direction);
|
||||
|
||||
let frame = 0;
|
||||
let set_coordinates = function (data) {
|
||||
frame += 1;
|
||||
// 0,0 coordinates are considered invalid since we sometimes receive them out of nowhere
|
||||
let valid_coordinates =
|
||||
!isNaN(data.lat) &&
|
||||
!isNaN(data.lon) &&
|
||||
(data.lat != 0.0 || data.lon != 0.0);
|
||||
if (valid_coordinates) {
|
||||
status.update_position(new Point(data.lon, data.lat), null);
|
||||
}
|
||||
let gps_status_color;
|
||||
if (frame % 2 == 0 || valid_coordinates) {
|
||||
gps_status_color = g.theme.bg;
|
||||
} else {
|
||||
gps_status_color = g.theme.fg;
|
||||
}
|
||||
g.setColor(gps_status_color)
|
||||
.setFont("6x8:2")
|
||||
.drawString("gps", g.getWidth() - 40, 30);
|
||||
};
|
||||
|
||||
Bangle.setGPSPower(true, "gipy");
|
||||
Bangle.on("GPS", set_coordinates);
|
||||
Bangle.on("lock", function (on) {
|
||||
if (!on) {
|
||||
Bangle.setGPSPower(true, "gipy"); // activate gps when unlocking
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
let files = require("Storage").list(".gpc");
|
||||
if (files.length <= 1) {
|
||||
if (files.length == 0) {
|
||||
load();
|
||||
} else {
|
||||
start(files[0]);
|
||||
}
|
||||
} else {
|
||||
drawMenu();
|
||||
}
|
||||
|
After Width: | Height: | Size: 1.6 KiB |
|
|
@ -0,0 +1,196 @@
|
|||
<html>
|
||||
<head>
|
||||
<link rel="stylesheet" href="../../css/spectre.min.css">
|
||||
<style>
|
||||
svg { width:95% }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<p>Please select a gpx file to be converted to gpc and loaded.</p>
|
||||
|
||||
|
||||
gpx file : <input type="file" is="gpx_file" id="fileInput" accept=".gpx">
|
||||
<br>
|
||||
gpc filename : <input type="text" id="gpc_file" name="gpc_file" maxlength="24">.gpc (max 24 characters)
|
||||
<br>
|
||||
<input type="checkbox" id="osm" name="osm" checked>
|
||||
<label for="osm">fetch interests from openstreetmap</label>
|
||||
<table>
|
||||
<tr>
|
||||
<th><bold>OpenstreetMap <a href="https://wiki.openstreetmap.org/wiki/Tags">NODE Tags</a></bold></th>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>color</th><th>key</th><th>value</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<th style="color:red">red</th><th><input type="text" id="key1" name="key1" value="shop"></th><th><input type="text" id="value1" name="value1" value="bakery"></th>
|
||||
</tr>
|
||||
<tr>
|
||||
<th style="color:blue">blue</th><th><input type="text" id="key2" name="key2" value="amenity"></th><th><input type="text" id="value2" name="value2" value="drinking_water"></th>
|
||||
</tr>
|
||||
<tr>
|
||||
<th style="color:cyan">cyan</th><th><input type="text" id="key3" name="key3" value="amenity"></th><th><input type="text" id="value3" name="value3" value="toilets"></th>
|
||||
</tr>
|
||||
<tr>
|
||||
<th style="color:green">green</th><th><input type="text" id="key4" name="key4" value="tourism"></th><th><input type="text" id="value4" name="value4" value="artwork"></th>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<p>nice tags could be :
|
||||
shop/bicycle, amenity/bank, shop/supermarket, leisure/picnic_table, tourism/information, amenity/pharmacy
|
||||
</p>
|
||||
|
||||
<input type="button" id="convert" name="convert" value="Convert" disabled>
|
||||
|
||||
|
||||
<input type="button" id="upload" name="upload" value="Upload" disabled>
|
||||
<div id="status"></div>
|
||||
<div id="map"></div>
|
||||
|
||||
<script src="../../core/lib/interface.js"></script>
|
||||
<script>
|
||||
function onInit() {
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
<script type="module">
|
||||
|
||||
function vec_to_string(vec) {
|
||||
let final_string = '';
|
||||
for (let i = 0 ; i < vec.length ; i++) {
|
||||
final_string += String.fromCharCode(vec[i]);
|
||||
}
|
||||
return final_string;
|
||||
}
|
||||
|
||||
import init, { convert_gpx_strings, convert_gpx_strings_no_osm, get_gpc, get_svg } from "./pkg/gpconv.js";
|
||||
console.log("imported wasm");
|
||||
|
||||
let osm_checkbox = document.querySelector("input[name=osm]");
|
||||
let with_osm = true;
|
||||
|
||||
osm_checkbox.addEventListener('change', function() {
|
||||
if (this.checked) {
|
||||
with_osm = true;
|
||||
document.getElementById('key1').disabled = false;
|
||||
document.getElementById('key2').disabled = false;
|
||||
document.getElementById('key3').disabled = false;
|
||||
document.getElementById('key4').disabled = false;
|
||||
document.getElementById('value1').disabled = false;
|
||||
document.getElementById('value2').disabled = false;
|
||||
document.getElementById('value3').disabled = false;
|
||||
document.getElementById('value4').disabled = false;
|
||||
} else {
|
||||
with_osm = false;
|
||||
document.getElementById('key1').disabled = true;
|
||||
document.getElementById('key2').disabled = true;
|
||||
document.getElementById('key3').disabled = true;
|
||||
document.getElementById('key4').disabled = true;
|
||||
document.getElementById('value1').disabled = true;
|
||||
document.getElementById('value2').disabled = true;
|
||||
document.getElementById('value3').disabled = true;
|
||||
document.getElementById('value4').disabled = true;
|
||||
}
|
||||
});
|
||||
|
||||
let status = document.getElementById("status");
|
||||
let gpx_content = null;
|
||||
let gpc_filename = null;
|
||||
let gpc_content = null;
|
||||
|
||||
document
|
||||
.getElementById("fileInput")
|
||||
.addEventListener("change", function selectedFileChanged() {
|
||||
document.getElementById('convert').disabled = true;
|
||||
document.getElementById('upload').disabled = true;
|
||||
if (this.files.length === 0) {
|
||||
console.log("No file selected.");
|
||||
return;
|
||||
}
|
||||
status.innerHTML = "reading file";
|
||||
|
||||
let gpx_filename = this.files[0].name;
|
||||
if (gpc_filename === null || gpc_filename == "") {
|
||||
if (gpx_filename.length <= 28) {
|
||||
gpc_filename = gpx_filename.slice(0, gpx_filename.length - 4);
|
||||
document.getElementById('gpc_file').value = gpc_filename;
|
||||
}
|
||||
}
|
||||
|
||||
const reader = new FileReader();
|
||||
reader.onload = function fileReadCompleted() {
|
||||
console.log("reading file completed");
|
||||
status.innerHTML = "file reading completed";
|
||||
gpx_content = reader.result;
|
||||
document.getElementById('convert').disabled = false;
|
||||
};
|
||||
reader.readAsText(this.files[0]);
|
||||
});
|
||||
|
||||
document
|
||||
.getElementById("convert")
|
||||
.addEventListener('click', function() {
|
||||
console.log("starting conversion");
|
||||
document.getElementById('convert').disabled = true;
|
||||
document.getElementById('upload').disabled = true;
|
||||
status.innerHTML = "please wait, converting file";
|
||||
init().then(() => {
|
||||
let gpc_svg;
|
||||
if (with_osm) {
|
||||
let key1 = document.getElementById('key1').value;
|
||||
let key2 = document.getElementById('key2').value;
|
||||
let key3 = document.getElementById('key3').value;
|
||||
let key4 = document.getElementById('key4').value;
|
||||
let value1 = document.getElementById('value1').value;
|
||||
let value2 = document.getElementById('value2').value;
|
||||
let value3 = document.getElementById('value3').value;
|
||||
let value4 = document.getElementById('value4').value;
|
||||
gpc_svg = convert_gpx_strings(gpx_content, key1, value1, key2, value2, key3, value3, key4, value4);
|
||||
} else {
|
||||
gpc_svg = convert_gpx_strings_no_osm(gpx_content);
|
||||
}
|
||||
gpc_svg.then(gs => {
|
||||
status.innerHTML = "file converted";
|
||||
let svg = get_svg(gs);
|
||||
let svg_string = vec_to_string(svg);
|
||||
let img = document.getElementById("map");
|
||||
img.innerHTML = svg_string;
|
||||
gpc_content = get_gpc(gs);
|
||||
if (gpc_filename !== null) {
|
||||
document.getElementById('upload').disabled = false;
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
document
|
||||
.getElementById("gpc_file")
|
||||
.addEventListener('change', function() {
|
||||
gpc_filename = document.getElementById("gpc_file").value;
|
||||
if (gpc_filename == "") {
|
||||
document.getElementById("upload").disabled = true;
|
||||
} else {
|
||||
if (gpc_content !== null) {
|
||||
document.getElementById("upload").disabled = false;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
document
|
||||
.getElementById("upload")
|
||||
.addEventListener('click', function() {
|
||||
status.innerHTML = "uploading file";
|
||||
console.log("uploading");
|
||||
let gpc_string = vec_to_string(gpc_content);
|
||||
Util.writeStorage(gpc_filename + ".gpc", gpc_string, () => {
|
||||
status.innerHTML = `${gpc_filename}.gpc uploaded`;
|
||||
console.log("DONE");
|
||||
});
|
||||
});
|
||||
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
{
|
||||
"id": "gipy",
|
||||
"name": "Gipy",
|
||||
"shortName": "Gipy",
|
||||
"version": "0.15",
|
||||
"description": "Follow gpx files",
|
||||
"allow_emulator":false,
|
||||
"icon": "gipy.png",
|
||||
"type": "app",
|
||||
"tags": "tool,outdoors,gps",
|
||||
"screenshots": [],
|
||||
"supports": ["BANGLEJS2"],
|
||||
"readme": "README.md",
|
||||
"interface": "interface.html",
|
||||
"storage": [
|
||||
{"name":"gipy.app.js","url":"app.js"},
|
||||
{"name":"gipy.settings.js","url":"settings.js"},
|
||||
{"name":"gipy.img","url":"app-icon.js","evaluate":true}
|
||||
],
|
||||
"data": [
|
||||
{"name":"gipy.json"}
|
||||
]
|
||||
}
|
||||
|
|
@ -0,0 +1,75 @@
|
|||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
/**
|
||||
* @param {GpcSvg} gpcsvg
|
||||
* @returns {Uint8Array}
|
||||
*/
|
||||
export function get_gpc(gpcsvg: GpcSvg): Uint8Array;
|
||||
/**
|
||||
* @param {GpcSvg} gpcsvg
|
||||
* @returns {Uint8Array}
|
||||
*/
|
||||
export function get_svg(gpcsvg: GpcSvg): Uint8Array;
|
||||
/**
|
||||
* @param {string} input_str
|
||||
* @returns {Promise<GpcSvg>}
|
||||
*/
|
||||
export function convert_gpx_strings_no_osm(input_str: string): Promise<GpcSvg>;
|
||||
/**
|
||||
* @param {string} input_str
|
||||
* @param {string} key1
|
||||
* @param {string} value1
|
||||
* @param {string} key2
|
||||
* @param {string} value2
|
||||
* @param {string} key3
|
||||
* @param {string} value3
|
||||
* @param {string} key4
|
||||
* @param {string} value4
|
||||
* @returns {Promise<GpcSvg>}
|
||||
*/
|
||||
export function convert_gpx_strings(input_str: string, key1: string, value1: string, key2: string, value2: string, key3: string, value3: string, key4: string, value4: string): Promise<GpcSvg>;
|
||||
/**
|
||||
*/
|
||||
export class GpcSvg {
|
||||
free(): void;
|
||||
}
|
||||
|
||||
export type InitInput = RequestInfo | URL | Response | BufferSource | WebAssembly.Module;
|
||||
|
||||
export interface InitOutput {
|
||||
readonly memory: WebAssembly.Memory;
|
||||
readonly __wbg_gpcsvg_free: (a: number) => void;
|
||||
readonly get_gpc: (a: number, b: number) => void;
|
||||
readonly get_svg: (a: number, b: number) => void;
|
||||
readonly convert_gpx_strings_no_osm: (a: number, b: number) => number;
|
||||
readonly convert_gpx_strings: (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, r: 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 _dyn_core__ops__function__FnMut__A____Output___R_as_wasm_bindgen__closure__WasmClosure___describe__invoke__h0601691a32604cdd: (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__h25ed812378167476: (a: number, b: number, c: number, d: number) => void;
|
||||
}
|
||||
|
||||
export type SyncInitInput = BufferSource | WebAssembly.Module;
|
||||
/**
|
||||
* Instantiates the given `module`, which can either be bytes or
|
||||
* a precompiled `WebAssembly.Module`.
|
||||
*
|
||||
* @param {SyncInitInput} module
|
||||
*
|
||||
* @returns {InitOutput}
|
||||
*/
|
||||
export function initSync(module: SyncInitInput): InitOutput;
|
||||
|
||||
/**
|
||||
* If `module_or_path` is {RequestInfo} or {URL}, makes a request and
|
||||
* for everything else, calls `WebAssembly.instantiate` directly.
|
||||
*
|
||||
* @param {InitInput | Promise<InitInput>} module_or_path
|
||||
*
|
||||
* @returns {Promise<InitOutput>}
|
||||
*/
|
||||
export default function init (module_or_path?: InitInput | Promise<InitInput>): Promise<InitOutput>;
|
||||
|
|
@ -0,0 +1,645 @@
|
|||
|
||||
let wasm;
|
||||
|
||||
const heap = new Array(32).fill(undefined);
|
||||
|
||||
heap.push(undefined, null, true, false);
|
||||
|
||||
function getObject(idx) { return heap[idx]; }
|
||||
|
||||
let heap_next = heap.length;
|
||||
|
||||
function dropObject(idx) {
|
||||
if (idx < 36) return;
|
||||
heap[idx] = heap_next;
|
||||
heap_next = idx;
|
||||
}
|
||||
|
||||
function takeObject(idx) {
|
||||
const ret = getObject(idx);
|
||||
dropObject(idx);
|
||||
return ret;
|
||||
}
|
||||
|
||||
let WASM_VECTOR_LEN = 0;
|
||||
|
||||
let cachedUint8Memory0 = new Uint8Array();
|
||||
|
||||
function getUint8Memory0() {
|
||||
if (cachedUint8Memory0.byteLength === 0) {
|
||||
cachedUint8Memory0 = new Uint8Array(wasm.memory.buffer);
|
||||
}
|
||||
return cachedUint8Memory0;
|
||||
}
|
||||
|
||||
const cachedTextEncoder = new TextEncoder('utf-8');
|
||||
|
||||
const encodeString = (typeof cachedTextEncoder.encodeInto === 'function'
|
||||
? function (arg, view) {
|
||||
return cachedTextEncoder.encodeInto(arg, view);
|
||||
}
|
||||
: function (arg, view) {
|
||||
const buf = cachedTextEncoder.encode(arg);
|
||||
view.set(buf);
|
||||
return {
|
||||
read: arg.length,
|
||||
written: buf.length
|
||||
};
|
||||
});
|
||||
|
||||
function passStringToWasm0(arg, malloc, realloc) {
|
||||
|
||||
if (realloc === undefined) {
|
||||
const buf = cachedTextEncoder.encode(arg);
|
||||
const ptr = malloc(buf.length);
|
||||
getUint8Memory0().subarray(ptr, ptr + buf.length).set(buf);
|
||||
WASM_VECTOR_LEN = buf.length;
|
||||
return ptr;
|
||||
}
|
||||
|
||||
let len = arg.length;
|
||||
let ptr = malloc(len);
|
||||
|
||||
const mem = getUint8Memory0();
|
||||
|
||||
let offset = 0;
|
||||
|
||||
for (; offset < len; offset++) {
|
||||
const code = arg.charCodeAt(offset);
|
||||
if (code > 0x7F) break;
|
||||
mem[ptr + offset] = code;
|
||||
}
|
||||
|
||||
if (offset !== len) {
|
||||
if (offset !== 0) {
|
||||
arg = arg.slice(offset);
|
||||
}
|
||||
ptr = realloc(ptr, len, len = offset + arg.length * 3);
|
||||
const view = getUint8Memory0().subarray(ptr + offset, ptr + len);
|
||||
const ret = encodeString(arg, view);
|
||||
|
||||
offset += ret.written;
|
||||
}
|
||||
|
||||
WASM_VECTOR_LEN = offset;
|
||||
return ptr;
|
||||
}
|
||||
|
||||
function isLikeNone(x) {
|
||||
return x === undefined || x === null;
|
||||
}
|
||||
|
||||
let cachedInt32Memory0 = new Int32Array();
|
||||
|
||||
function getInt32Memory0() {
|
||||
if (cachedInt32Memory0.byteLength === 0) {
|
||||
cachedInt32Memory0 = new Int32Array(wasm.memory.buffer);
|
||||
}
|
||||
return cachedInt32Memory0;
|
||||
}
|
||||
|
||||
const cachedTextDecoder = new TextDecoder('utf-8', { ignoreBOM: true, fatal: true });
|
||||
|
||||
cachedTextDecoder.decode();
|
||||
|
||||
function getStringFromWasm0(ptr, len) {
|
||||
return cachedTextDecoder.decode(getUint8Memory0().subarray(ptr, ptr + len));
|
||||
}
|
||||
|
||||
function addHeapObject(obj) {
|
||||
if (heap_next === heap.length) heap.push(heap.length + 1);
|
||||
const idx = heap_next;
|
||||
heap_next = heap[idx];
|
||||
|
||||
heap[idx] = obj;
|
||||
return idx;
|
||||
}
|
||||
|
||||
function debugString(val) {
|
||||
// primitive types
|
||||
const type = typeof val;
|
||||
if (type == 'number' || type == 'boolean' || val == null) {
|
||||
return `${val}`;
|
||||
}
|
||||
if (type == 'string') {
|
||||
return `"${val}"`;
|
||||
}
|
||||
if (type == 'symbol') {
|
||||
const description = val.description;
|
||||
if (description == null) {
|
||||
return 'Symbol';
|
||||
} else {
|
||||
return `Symbol(${description})`;
|
||||
}
|
||||
}
|
||||
if (type == 'function') {
|
||||
const name = val.name;
|
||||
if (typeof name == 'string' && name.length > 0) {
|
||||
return `Function(${name})`;
|
||||
} else {
|
||||
return 'Function';
|
||||
}
|
||||
}
|
||||
// objects
|
||||
if (Array.isArray(val)) {
|
||||
const length = val.length;
|
||||
let debug = '[';
|
||||
if (length > 0) {
|
||||
debug += debugString(val[0]);
|
||||
}
|
||||
for(let i = 1; i < length; i++) {
|
||||
debug += ', ' + debugString(val[i]);
|
||||
}
|
||||
debug += ']';
|
||||
return debug;
|
||||
}
|
||||
// Test for built-in
|
||||
const builtInMatches = /\[object ([^\]]+)\]/.exec(toString.call(val));
|
||||
let className;
|
||||
if (builtInMatches.length > 1) {
|
||||
className = builtInMatches[1];
|
||||
} else {
|
||||
// Failed to match the standard '[object ClassName]'
|
||||
return toString.call(val);
|
||||
}
|
||||
if (className == 'Object') {
|
||||
// we're a user defined class or Object
|
||||
// JSON.stringify avoids problems with cycles, and is generally much
|
||||
// easier than looping through ownProperties of `val`.
|
||||
try {
|
||||
return 'Object(' + JSON.stringify(val) + ')';
|
||||
} catch (_) {
|
||||
return 'Object';
|
||||
}
|
||||
}
|
||||
// errors
|
||||
if (val instanceof Error) {
|
||||
return `${val.name}: ${val.message}\n${val.stack}`;
|
||||
}
|
||||
// TODO we could test for more things here, like `Set`s and `Map`s.
|
||||
return className;
|
||||
}
|
||||
|
||||
function makeMutClosure(arg0, arg1, dtor, f) {
|
||||
const state = { a: arg0, b: arg1, cnt: 1, dtor };
|
||||
const real = (...args) => {
|
||||
// First up with a closure we increment the internal reference
|
||||
// count. This ensures that the Rust closure environment won't
|
||||
// be deallocated while we're invoking it.
|
||||
state.cnt++;
|
||||
const a = state.a;
|
||||
state.a = 0;
|
||||
try {
|
||||
return f(a, state.b, ...args);
|
||||
} finally {
|
||||
if (--state.cnt === 0) {
|
||||
wasm.__wbindgen_export_2.get(state.dtor)(a, state.b);
|
||||
|
||||
} else {
|
||||
state.a = a;
|
||||
}
|
||||
}
|
||||
};
|
||||
real.original = state;
|
||||
|
||||
return real;
|
||||
}
|
||||
function __wbg_adapter_24(arg0, arg1, arg2) {
|
||||
wasm._dyn_core__ops__function__FnMut__A____Output___R_as_wasm_bindgen__closure__WasmClosure___describe__invoke__h0601691a32604cdd(arg0, arg1, addHeapObject(arg2));
|
||||
}
|
||||
|
||||
function _assertClass(instance, klass) {
|
||||
if (!(instance instanceof klass)) {
|
||||
throw new Error(`expected instance of ${klass.name}`);
|
||||
}
|
||||
return instance.ptr;
|
||||
}
|
||||
|
||||
function getArrayU8FromWasm0(ptr, len) {
|
||||
return getUint8Memory0().subarray(ptr / 1, ptr / 1 + len);
|
||||
}
|
||||
/**
|
||||
* @param {GpcSvg} gpcsvg
|
||||
* @returns {Uint8Array}
|
||||
*/
|
||||
export function get_gpc(gpcsvg) {
|
||||
try {
|
||||
const retptr = wasm.__wbindgen_add_to_stack_pointer(-16);
|
||||
_assertClass(gpcsvg, GpcSvg);
|
||||
wasm.get_gpc(retptr, gpcsvg.ptr);
|
||||
var r0 = getInt32Memory0()[retptr / 4 + 0];
|
||||
var r1 = getInt32Memory0()[retptr / 4 + 1];
|
||||
var v0 = getArrayU8FromWasm0(r0, r1).slice();
|
||||
wasm.__wbindgen_free(r0, r1 * 1);
|
||||
return v0;
|
||||
} finally {
|
||||
wasm.__wbindgen_add_to_stack_pointer(16);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {GpcSvg} gpcsvg
|
||||
* @returns {Uint8Array}
|
||||
*/
|
||||
export function get_svg(gpcsvg) {
|
||||
try {
|
||||
const retptr = wasm.__wbindgen_add_to_stack_pointer(-16);
|
||||
_assertClass(gpcsvg, GpcSvg);
|
||||
wasm.get_svg(retptr, gpcsvg.ptr);
|
||||
var r0 = getInt32Memory0()[retptr / 4 + 0];
|
||||
var r1 = getInt32Memory0()[retptr / 4 + 1];
|
||||
var v0 = getArrayU8FromWasm0(r0, r1).slice();
|
||||
wasm.__wbindgen_free(r0, r1 * 1);
|
||||
return v0;
|
||||
} finally {
|
||||
wasm.__wbindgen_add_to_stack_pointer(16);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} input_str
|
||||
* @returns {Promise<GpcSvg>}
|
||||
*/
|
||||
export function convert_gpx_strings_no_osm(input_str) {
|
||||
const ptr0 = passStringToWasm0(input_str, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
|
||||
const len0 = WASM_VECTOR_LEN;
|
||||
const ret = wasm.convert_gpx_strings_no_osm(ptr0, len0);
|
||||
return takeObject(ret);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} input_str
|
||||
* @param {string} key1
|
||||
* @param {string} value1
|
||||
* @param {string} key2
|
||||
* @param {string} value2
|
||||
* @param {string} key3
|
||||
* @param {string} value3
|
||||
* @param {string} key4
|
||||
* @param {string} value4
|
||||
* @returns {Promise<GpcSvg>}
|
||||
*/
|
||||
export function convert_gpx_strings(input_str, key1, value1, key2, value2, key3, value3, key4, value4) {
|
||||
const ptr0 = passStringToWasm0(input_str, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
|
||||
const len0 = WASM_VECTOR_LEN;
|
||||
const ptr1 = passStringToWasm0(key1, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
|
||||
const len1 = WASM_VECTOR_LEN;
|
||||
const ptr2 = passStringToWasm0(value1, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
|
||||
const len2 = WASM_VECTOR_LEN;
|
||||
const ptr3 = passStringToWasm0(key2, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
|
||||
const len3 = WASM_VECTOR_LEN;
|
||||
const ptr4 = passStringToWasm0(value2, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
|
||||
const len4 = WASM_VECTOR_LEN;
|
||||
const ptr5 = passStringToWasm0(key3, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
|
||||
const len5 = WASM_VECTOR_LEN;
|
||||
const ptr6 = passStringToWasm0(value3, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
|
||||
const len6 = WASM_VECTOR_LEN;
|
||||
const ptr7 = passStringToWasm0(key4, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
|
||||
const len7 = WASM_VECTOR_LEN;
|
||||
const ptr8 = passStringToWasm0(value4, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
|
||||
const len8 = WASM_VECTOR_LEN;
|
||||
const ret = wasm.convert_gpx_strings(ptr0, len0, ptr1, len1, ptr2, len2, ptr3, len3, ptr4, len4, ptr5, len5, ptr6, len6, ptr7, len7, ptr8, len8);
|
||||
return takeObject(ret);
|
||||
}
|
||||
|
||||
function handleError(f, args) {
|
||||
try {
|
||||
return f.apply(this, args);
|
||||
} catch (e) {
|
||||
wasm.__wbindgen_exn_store(addHeapObject(e));
|
||||
}
|
||||
}
|
||||
function __wbg_adapter_69(arg0, arg1, arg2, arg3) {
|
||||
wasm.wasm_bindgen__convert__closures__invoke2_mut__h25ed812378167476(arg0, arg1, addHeapObject(arg2), addHeapObject(arg3));
|
||||
}
|
||||
|
||||
/**
|
||||
*/
|
||||
export class GpcSvg {
|
||||
|
||||
static __wrap(ptr) {
|
||||
const obj = Object.create(GpcSvg.prototype);
|
||||
obj.ptr = ptr;
|
||||
|
||||
return obj;
|
||||
}
|
||||
|
||||
__destroy_into_raw() {
|
||||
const ptr = this.ptr;
|
||||
this.ptr = 0;
|
||||
|
||||
return ptr;
|
||||
}
|
||||
|
||||
free() {
|
||||
const ptr = this.__destroy_into_raw();
|
||||
wasm.__wbg_gpcsvg_free(ptr);
|
||||
}
|
||||
}
|
||||
|
||||
async function load(module, imports) {
|
||||
if (typeof Response === 'function' && module instanceof Response) {
|
||||
if (typeof WebAssembly.instantiateStreaming === 'function') {
|
||||
try {
|
||||
return await WebAssembly.instantiateStreaming(module, imports);
|
||||
|
||||
} catch (e) {
|
||||
if (module.headers.get('Content-Type') != 'application/wasm') {
|
||||
console.warn("`WebAssembly.instantiateStreaming` failed because your server does not serve wasm with `application/wasm` MIME type. Falling back to `WebAssembly.instantiate` which is slower. Original error:\n", e);
|
||||
|
||||
} else {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const bytes = await module.arrayBuffer();
|
||||
return await WebAssembly.instantiate(bytes, imports);
|
||||
|
||||
} else {
|
||||
const instance = await WebAssembly.instantiate(module, imports);
|
||||
|
||||
if (instance instanceof WebAssembly.Instance) {
|
||||
return { instance, module };
|
||||
|
||||
} else {
|
||||
return instance;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function getImports() {
|
||||
const imports = {};
|
||||
imports.wbg = {};
|
||||
imports.wbg.__wbindgen_object_drop_ref = function(arg0) {
|
||||
takeObject(arg0);
|
||||
};
|
||||
imports.wbg.__wbg_gpcsvg_new = function(arg0) {
|
||||
const ret = GpcSvg.__wrap(arg0);
|
||||
return addHeapObject(ret);
|
||||
};
|
||||
imports.wbg.__wbindgen_string_get = function(arg0, arg1) {
|
||||
const obj = getObject(arg1);
|
||||
const ret = typeof(obj) === 'string' ? obj : undefined;
|
||||
var ptr0 = isLikeNone(ret) ? 0 : passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
|
||||
var len0 = WASM_VECTOR_LEN;
|
||||
getInt32Memory0()[arg0 / 4 + 1] = len0;
|
||||
getInt32Memory0()[arg0 / 4 + 0] = ptr0;
|
||||
};
|
||||
imports.wbg.__wbindgen_string_new = function(arg0, arg1) {
|
||||
const ret = getStringFromWasm0(arg0, arg1);
|
||||
return addHeapObject(ret);
|
||||
};
|
||||
imports.wbg.__wbindgen_object_clone_ref = function(arg0) {
|
||||
const ret = getObject(arg0);
|
||||
return addHeapObject(ret);
|
||||
};
|
||||
imports.wbg.__wbg_fetch_386f87a3ebf5003c = function(arg0) {
|
||||
const ret = fetch(getObject(arg0));
|
||||
return addHeapObject(ret);
|
||||
};
|
||||
imports.wbg.__wbindgen_cb_drop = function(arg0) {
|
||||
const obj = takeObject(arg0).original;
|
||||
if (obj.cnt-- == 1) {
|
||||
obj.a = 0;
|
||||
return true;
|
||||
}
|
||||
const ret = false;
|
||||
return ret;
|
||||
};
|
||||
imports.wbg.__wbg_fetch_749a56934f95c96c = function(arg0, arg1) {
|
||||
const ret = getObject(arg0).fetch(getObject(arg1));
|
||||
return addHeapObject(ret);
|
||||
};
|
||||
imports.wbg.__wbg_instanceof_Response_eaa426220848a39e = function(arg0) {
|
||||
let result;
|
||||
try {
|
||||
result = getObject(arg0) instanceof Response;
|
||||
} catch {
|
||||
result = false;
|
||||
}
|
||||
const ret = result;
|
||||
return ret;
|
||||
};
|
||||
imports.wbg.__wbg_url_74285ddf2747cb3d = function(arg0, arg1) {
|
||||
const ret = getObject(arg1).url;
|
||||
const ptr0 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
|
||||
const len0 = WASM_VECTOR_LEN;
|
||||
getInt32Memory0()[arg0 / 4 + 1] = len0;
|
||||
getInt32Memory0()[arg0 / 4 + 0] = ptr0;
|
||||
};
|
||||
imports.wbg.__wbg_status_c4ef3dd591e63435 = function(arg0) {
|
||||
const ret = getObject(arg0).status;
|
||||
return ret;
|
||||
};
|
||||
imports.wbg.__wbg_headers_fd64ad685cf22e5d = function(arg0) {
|
||||
const ret = getObject(arg0).headers;
|
||||
return addHeapObject(ret);
|
||||
};
|
||||
imports.wbg.__wbg_text_1169d752cc697903 = function() { return handleError(function (arg0) {
|
||||
const ret = getObject(arg0).text();
|
||||
return addHeapObject(ret);
|
||||
}, arguments) };
|
||||
imports.wbg.__wbg_newwithstrandinit_05d7180788420c40 = function() { return handleError(function (arg0, arg1, arg2) {
|
||||
const ret = new Request(getStringFromWasm0(arg0, arg1), getObject(arg2));
|
||||
return addHeapObject(ret);
|
||||
}, arguments) };
|
||||
imports.wbg.__wbg_new_2d0053ee81e4dd2a = function() { return handleError(function () {
|
||||
const ret = new Headers();
|
||||
return addHeapObject(ret);
|
||||
}, arguments) };
|
||||
imports.wbg.__wbg_append_de37df908812970d = function() { return handleError(function (arg0, arg1, arg2, arg3, arg4) {
|
||||
getObject(arg0).append(getStringFromWasm0(arg1, arg2), getStringFromWasm0(arg3, arg4));
|
||||
}, arguments) };
|
||||
imports.wbg.__wbindgen_is_object = function(arg0) {
|
||||
const val = getObject(arg0);
|
||||
const ret = typeof(val) === 'object' && val !== null;
|
||||
return ret;
|
||||
};
|
||||
imports.wbg.__wbg_newnoargs_b5b063fc6c2f0376 = function(arg0, arg1) {
|
||||
const ret = new Function(getStringFromWasm0(arg0, arg1));
|
||||
return addHeapObject(ret);
|
||||
};
|
||||
imports.wbg.__wbg_next_579e583d33566a86 = function(arg0) {
|
||||
const ret = getObject(arg0).next;
|
||||
return addHeapObject(ret);
|
||||
};
|
||||
imports.wbg.__wbindgen_is_function = function(arg0) {
|
||||
const ret = typeof(getObject(arg0)) === 'function';
|
||||
return ret;
|
||||
};
|
||||
imports.wbg.__wbg_value_1ccc36bc03462d71 = function(arg0) {
|
||||
const ret = getObject(arg0).value;
|
||||
return addHeapObject(ret);
|
||||
};
|
||||
imports.wbg.__wbg_iterator_6f9d4f28845f426c = function() {
|
||||
const ret = Symbol.iterator;
|
||||
return addHeapObject(ret);
|
||||
};
|
||||
imports.wbg.__wbg_new_0b9bfdd97583284e = function() {
|
||||
const ret = new Object();
|
||||
return addHeapObject(ret);
|
||||
};
|
||||
imports.wbg.__wbg_self_6d479506f72c6a71 = function() { return handleError(function () {
|
||||
const ret = self.self;
|
||||
return addHeapObject(ret);
|
||||
}, arguments) };
|
||||
imports.wbg.__wbg_window_f2557cc78490aceb = function() { return handleError(function () {
|
||||
const ret = window.window;
|
||||
return addHeapObject(ret);
|
||||
}, arguments) };
|
||||
imports.wbg.__wbg_globalThis_7f206bda628d5286 = function() { return handleError(function () {
|
||||
const ret = globalThis.globalThis;
|
||||
return addHeapObject(ret);
|
||||
}, arguments) };
|
||||
imports.wbg.__wbg_global_ba75c50d1cf384f4 = function() { return handleError(function () {
|
||||
const ret = global.global;
|
||||
return addHeapObject(ret);
|
||||
}, arguments) };
|
||||
imports.wbg.__wbindgen_is_undefined = function(arg0) {
|
||||
const ret = getObject(arg0) === undefined;
|
||||
return ret;
|
||||
};
|
||||
imports.wbg.__wbg_call_97ae9d8645dc388b = function() { return handleError(function (arg0, arg1) {
|
||||
const ret = getObject(arg0).call(getObject(arg1));
|
||||
return addHeapObject(ret);
|
||||
}, arguments) };
|
||||
imports.wbg.__wbg_call_168da88779e35f61 = function() { return handleError(function (arg0, arg1, arg2) {
|
||||
const ret = getObject(arg0).call(getObject(arg1), getObject(arg2));
|
||||
return addHeapObject(ret);
|
||||
}, arguments) };
|
||||
imports.wbg.__wbg_next_aaef7c8aa5e212ac = function() { return handleError(function (arg0) {
|
||||
const ret = getObject(arg0).next();
|
||||
return addHeapObject(ret);
|
||||
}, arguments) };
|
||||
imports.wbg.__wbg_done_1b73b0672e15f234 = function(arg0) {
|
||||
const ret = getObject(arg0).done;
|
||||
return ret;
|
||||
};
|
||||
imports.wbg.__wbg_new_9962f939219f1820 = function(arg0, arg1) {
|
||||
try {
|
||||
var state0 = {a: arg0, b: arg1};
|
||||
var cb0 = (arg0, arg1) => {
|
||||
const a = state0.a;
|
||||
state0.a = 0;
|
||||
try {
|
||||
return __wbg_adapter_69(a, state0.b, arg0, arg1);
|
||||
} finally {
|
||||
state0.a = a;
|
||||
}
|
||||
};
|
||||
const ret = new Promise(cb0);
|
||||
return addHeapObject(ret);
|
||||
} finally {
|
||||
state0.a = state0.b = 0;
|
||||
}
|
||||
};
|
||||
imports.wbg.__wbg_resolve_99fe17964f31ffc0 = function(arg0) {
|
||||
const ret = Promise.resolve(getObject(arg0));
|
||||
return addHeapObject(ret);
|
||||
};
|
||||
imports.wbg.__wbg_then_11f7a54d67b4bfad = function(arg0, arg1) {
|
||||
const ret = getObject(arg0).then(getObject(arg1));
|
||||
return addHeapObject(ret);
|
||||
};
|
||||
imports.wbg.__wbg_then_cedad20fbbd9418a = function(arg0, arg1, arg2) {
|
||||
const ret = getObject(arg0).then(getObject(arg1), getObject(arg2));
|
||||
return addHeapObject(ret);
|
||||
};
|
||||
imports.wbg.__wbg_buffer_3f3d764d4747d564 = function(arg0) {
|
||||
const ret = getObject(arg0).buffer;
|
||||
return addHeapObject(ret);
|
||||
};
|
||||
imports.wbg.__wbg_newwithbyteoffsetandlength_d9aa266703cb98be = function(arg0, arg1, arg2) {
|
||||
const ret = new Uint8Array(getObject(arg0), arg1 >>> 0, arg2 >>> 0);
|
||||
return addHeapObject(ret);
|
||||
};
|
||||
imports.wbg.__wbg_new_8c3f0052272a457a = function(arg0) {
|
||||
const ret = new Uint8Array(getObject(arg0));
|
||||
return addHeapObject(ret);
|
||||
};
|
||||
imports.wbg.__wbg_stringify_d6471d300ded9b68 = function() { return handleError(function (arg0) {
|
||||
const ret = JSON.stringify(getObject(arg0));
|
||||
return addHeapObject(ret);
|
||||
}, arguments) };
|
||||
imports.wbg.__wbg_get_765201544a2b6869 = function() { return handleError(function (arg0, arg1) {
|
||||
const ret = Reflect.get(getObject(arg0), getObject(arg1));
|
||||
return addHeapObject(ret);
|
||||
}, arguments) };
|
||||
imports.wbg.__wbg_has_8359f114ce042f5a = function() { return handleError(function (arg0, arg1) {
|
||||
const ret = Reflect.has(getObject(arg0), getObject(arg1));
|
||||
return ret;
|
||||
}, arguments) };
|
||||
imports.wbg.__wbg_set_bf3f89b92d5a34bf = function() { return handleError(function (arg0, arg1, arg2) {
|
||||
const ret = Reflect.set(getObject(arg0), getObject(arg1), getObject(arg2));
|
||||
return ret;
|
||||
}, arguments) };
|
||||
imports.wbg.__wbindgen_debug_string = function(arg0, arg1) {
|
||||
const ret = debugString(getObject(arg1));
|
||||
const ptr0 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
|
||||
const len0 = WASM_VECTOR_LEN;
|
||||
getInt32Memory0()[arg0 / 4 + 1] = len0;
|
||||
getInt32Memory0()[arg0 / 4 + 0] = ptr0;
|
||||
};
|
||||
imports.wbg.__wbindgen_throw = function(arg0, arg1) {
|
||||
throw new Error(getStringFromWasm0(arg0, arg1));
|
||||
};
|
||||
imports.wbg.__wbindgen_memory = function() {
|
||||
const ret = wasm.memory;
|
||||
return addHeapObject(ret);
|
||||
};
|
||||
imports.wbg.__wbindgen_closure_wrapper947 = function(arg0, arg1, arg2) {
|
||||
const ret = makeMutClosure(arg0, arg1, 147, __wbg_adapter_24);
|
||||
return addHeapObject(ret);
|
||||
};
|
||||
|
||||
return imports;
|
||||
}
|
||||
|
||||
function initMemory(imports, maybe_memory) {
|
||||
|
||||
}
|
||||
|
||||
function finalizeInit(instance, module) {
|
||||
wasm = instance.exports;
|
||||
init.__wbindgen_wasm_module = module;
|
||||
cachedInt32Memory0 = new Int32Array();
|
||||
cachedUint8Memory0 = new Uint8Array();
|
||||
|
||||
|
||||
return wasm;
|
||||
}
|
||||
|
||||
function initSync(module) {
|
||||
const imports = getImports();
|
||||
|
||||
initMemory(imports);
|
||||
|
||||
if (!(module instanceof WebAssembly.Module)) {
|
||||
module = new WebAssembly.Module(module);
|
||||
}
|
||||
|
||||
const instance = new WebAssembly.Instance(module, imports);
|
||||
|
||||
return finalizeInit(instance, module);
|
||||
}
|
||||
|
||||
async function init(input) {
|
||||
if (typeof input === 'undefined') {
|
||||
input = new URL('gpconv_bg.wasm', import.meta.url);
|
||||
}
|
||||
const imports = getImports();
|
||||
|
||||
if (typeof input === 'string' || (typeof Request === 'function' && input instanceof Request) || (typeof URL === 'function' && input instanceof URL)) {
|
||||
input = fetch(input);
|
||||
}
|
||||
|
||||
initMemory(imports);
|
||||
|
||||
const { instance, module } = await load(await input, imports);
|
||||
|
||||
return finalizeInit(instance, module);
|
||||
}
|
||||
|
||||
export { initSync }
|
||||
export default init;
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
export const memory: WebAssembly.Memory;
|
||||
export function __wbg_gpcsvg_free(a: number): void;
|
||||
export function get_gpc(a: number, b: number): void;
|
||||
export function get_svg(a: number, b: number): void;
|
||||
export function convert_gpx_strings_no_osm(a: number, b: number): number;
|
||||
export function convert_gpx_strings(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, r: 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 _dyn_core__ops__function__FnMut__A____Output___R_as_wasm_bindgen__closure__WasmClosure___describe__invoke__h0601691a32604cdd(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__h25ed812378167476(a: number, b: number, c: number, d: number): void;
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
{
|
||||
"name": "gpconv",
|
||||
"version": "0.1.0",
|
||||
"files": [
|
||||
"gpconv_bg.wasm",
|
||||
"gpconv.js",
|
||||
"gpconv.d.ts"
|
||||
],
|
||||
"module": "gpconv.js",
|
||||
"types": "gpconv.d.ts",
|
||||
"sideEffects": false
|
||||
}
|
||||
|
After Width: | Height: | Size: 2.8 KiB |
|
After Width: | Height: | Size: 3.0 KiB |
|
|
@ -0,0 +1,38 @@
|
|||
(function (back) {
|
||||
var FILE = "gipy.json";
|
||||
// Load settings
|
||||
var settings = Object.assign(
|
||||
{
|
||||
keep_gps_alive: false,
|
||||
max_speed: 35,
|
||||
},
|
||||
require("Storage").readJSON(FILE, true) || {}
|
||||
);
|
||||
|
||||
function writeSettings() {
|
||||
require("Storage").writeJSON(FILE, settings);
|
||||
}
|
||||
|
||||
// Show the menu
|
||||
E.showMenu({
|
||||
"": { title: "Gipy" },
|
||||
"< Back": () => back(),
|
||||
"keep gps alive": {
|
||||
value: !!settings.keep_gps_alive, // !! converts undefined to false
|
||||
format: (v) => (v ? "Yes" : "No"),
|
||||
onchange: (v) => {
|
||||
settings.keep_gps_alive = v;
|
||||
writeSettings();
|
||||
},
|
||||
},
|
||||
"max speed": {
|
||||
value: 35 | settings.max_speed, // 0| converts undefined to 0
|
||||
min: 0,
|
||||
max: 130,
|
||||
onchange: (v) => {
|
||||
settings.max_speed = v;
|
||||
writeSettings();
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
|
|
@ -5,7 +5,7 @@
|
|||
"description": "An application that displays information about altitude, lat/lon, satellites and time",
|
||||
"icon": "gps-info.png",
|
||||
"type": "app",
|
||||
"tags": "gps",
|
||||
"tags": "gps,outdoors",
|
||||
"supports": ["BANGLEJS","BANGLEJS2"],
|
||||
"storage": [
|
||||
{"name":"gpsinfo.app.js","url":"gps-info.js"},
|
||||
|
|
|
|||
|
|
@ -8,3 +8,8 @@
|
|||
Fix widget adding listeners more than once
|
||||
0.07: Show checkered flag for target markers
|
||||
Single waypoints are now shown in the compass view
|
||||
0.08: Better handle state in widget
|
||||
Slightly faster drawing by doing some caching
|
||||
Reconstruct battery voltage by using calibrated batFullVoltage
|
||||
Averaging for smoothing compass headings
|
||||
Save state if route or waypoint has been chosen
|
||||
|
|
|
|||
|
|
@ -1,48 +1,69 @@
|
|||
|
||||
{ //run in own scope for fast switch
|
||||
const STORAGE = require("Storage");
|
||||
const showWidgets = true;
|
||||
let numberOfSlices=4;
|
||||
const BAT_FULL = require("Storage").readJSON("setting.json").batFullVoltage || 0.3144;
|
||||
|
||||
let init = function(){
|
||||
global.screen = 1;
|
||||
global.drawTimeout = undefined;
|
||||
global.lastDrawnScreen = 0;
|
||||
global.firstDraw = true;
|
||||
global.slices = [];
|
||||
global.maxScreens = 1;
|
||||
global.scheduleDraw = false;
|
||||
|
||||
if (showWidgets){
|
||||
Bangle.loadWidgets();
|
||||
}
|
||||
WIDGETS.gpstrek.start(false);
|
||||
if (!WIDGETS.gpstrek.getState().numberOfSlices) WIDGETS.gpstrek.getState().numberOfSlices = 3;
|
||||
};
|
||||
|
||||
let state = WIDGETS.gpstrek.getState();
|
||||
WIDGETS.gpstrek.start(false);
|
||||
let cleanup = function(){
|
||||
if (global.drawTimeout) clearTimeout(global.drawTimeout);
|
||||
delete global.screen;
|
||||
delete global.drawTimeout;
|
||||
delete global.lastDrawnScreen;
|
||||
delete global.firstDraw;
|
||||
delete global.slices;
|
||||
delete global.maxScreens;
|
||||
};
|
||||
|
||||
function parseNumber(toParse){
|
||||
init();
|
||||
scheduleDraw = true;
|
||||
|
||||
let parseNumber = function(toParse){
|
||||
if (toParse.includes(".")) return parseFloat(toParse);
|
||||
return parseFloat("" + toParse + ".0");
|
||||
}
|
||||
};
|
||||
|
||||
function parseWaypoint(filename, offset, result){
|
||||
let parseWaypoint = function(filename, offset, result){
|
||||
result.lat = parseNumber(STORAGE.read(filename, offset, 11));
|
||||
result.lon = parseNumber(STORAGE.read(filename, offset += 11, 12));
|
||||
return offset + 12;
|
||||
}
|
||||
};
|
||||
|
||||
function parseWaypointWithElevation(filename, offset, result){
|
||||
let parseWaypointWithElevation = function (filename, offset, result){
|
||||
offset = parseWaypoint(filename, offset, result);
|
||||
result.alt = parseNumber(STORAGE.read(filename, offset, 6));
|
||||
return offset + 6;
|
||||
}
|
||||
};
|
||||
|
||||
function parseWaypointWithName(filename, offset, result){
|
||||
let parseWaypointWithName = function(filename, offset, result){
|
||||
offset = parseWaypoint(filename, offset, result);
|
||||
return parseName(filename, offset, result);
|
||||
}
|
||||
};
|
||||
|
||||
function parseName(filename, offset, result){
|
||||
let parseName = function(filename, offset, result){
|
||||
let nameLength = STORAGE.read(filename, offset, 2) - 0;
|
||||
result.name = STORAGE.read(filename, offset += 2, nameLength);
|
||||
return offset + nameLength;
|
||||
}
|
||||
};
|
||||
|
||||
function parseWaypointWithElevationAndName(filename, offset, result){
|
||||
let parseWaypointWithElevationAndName = function(filename, offset, result){
|
||||
offset = parseWaypointWithElevation(filename, offset, result);
|
||||
return parseName(filename, offset, result);
|
||||
}
|
||||
};
|
||||
|
||||
function getEntry(filename, offset, result){
|
||||
let getEntry = function(filename, offset, result){
|
||||
result.fileOffset = offset;
|
||||
let type = STORAGE.read(filename, offset++, 1);
|
||||
if (type == "") return -1;
|
||||
|
|
@ -68,12 +89,12 @@ function getEntry(filename, offset, result){
|
|||
result.fileLength = offset - result.fileOffset;
|
||||
//print(result);
|
||||
return offset;
|
||||
}
|
||||
};
|
||||
|
||||
const labels = ["N","NE","E","SE","S","SW","W","NW"];
|
||||
const loc = require("locale");
|
||||
|
||||
function matchFontSize(graphics, text, height, width){
|
||||
let matchFontSize = function(graphics, text, height, width){
|
||||
graphics.setFontVector(height);
|
||||
let metrics;
|
||||
let size = 1;
|
||||
|
|
@ -81,13 +102,19 @@ function matchFontSize(graphics, text, height, width){
|
|||
size -= 0.05;
|
||||
graphics.setFont("Vector",Math.floor(height*size));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
function getDoubleLineSlice(title1,title2,provider1,provider2,refreshTime){
|
||||
let getDoubleLineSlice = function(title1,title2,provider1,provider2,refreshTime){
|
||||
let lastDrawn = Date.now() - Math.random()*refreshTime;
|
||||
let lastValue1 = 0;
|
||||
let lastValue2 = 0;
|
||||
return {
|
||||
refresh: function (){
|
||||
return Date.now() - lastDrawn > (Bangle.isLocked()?(refreshTime?refreshTime:5000):(refreshTime?refreshTime*2:10000));
|
||||
let bigChange1 = (Math.abs(lastValue1 - provider1()) > 1);
|
||||
let bigChange2 = (Math.abs(lastValue2 - provider2()) > 1);
|
||||
let refresh = (Bangle.isLocked()?(refreshTime?refreshTime*5:10000):(refreshTime?refreshTime*2:1000));
|
||||
let old = (Date.now() - lastDrawn) > refresh;
|
||||
return (bigChange1 || bigChange2) && old;
|
||||
},
|
||||
draw: function (graphics, x, y, height, width){
|
||||
lastDrawn = Date.now();
|
||||
|
|
@ -95,29 +122,29 @@ function getDoubleLineSlice(title1,title2,provider1,provider2,refreshTime){
|
|||
if (typeof title2 == "function") title2 = title2();
|
||||
graphics.clearRect(x,y,x+width,y+height);
|
||||
|
||||
let value = provider1();
|
||||
matchFontSize(graphics, title1 + value, Math.floor(height*0.5), width);
|
||||
lastValue1 = provider1();
|
||||
matchFontSize(graphics, title1 + lastValue1, Math.floor(height*0.5), width);
|
||||
graphics.setFontAlign(-1,-1);
|
||||
graphics.drawString(title1, x+2, y);
|
||||
graphics.setFontAlign(1,-1);
|
||||
graphics.drawString(value, x+width, y);
|
||||
graphics.drawString(lastValue1, x+width, y);
|
||||
|
||||
value = provider2();
|
||||
matchFontSize(graphics, title2 + value, Math.floor(height*0.5), width);
|
||||
lastValue2 = provider2();
|
||||
matchFontSize(graphics, title2 + lastValue2, Math.floor(height*0.5), width);
|
||||
graphics.setFontAlign(-1,-1);
|
||||
graphics.drawString(title2, x+2, y+(height*0.5));
|
||||
graphics.setFontAlign(1,-1);
|
||||
graphics.drawString(value, x+width, y+(height*0.5));
|
||||
graphics.drawString(lastValue2, x+width, y+(height*0.5));
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
function getTargetSlice(targetDataSource){
|
||||
let getTargetSlice = function(targetDataSource){
|
||||
let nameIndex = 0;
|
||||
let lastDrawn = Date.now() - Math.random()*3000;
|
||||
return {
|
||||
refresh: function (){
|
||||
return Date.now() - lastDrawn > (Bangle.isLocked()?10000:3000);
|
||||
return Date.now() - lastDrawn > (Bangle.isLocked()?3000:10000);
|
||||
},
|
||||
draw: function (graphics, x, y, height, width){
|
||||
lastDrawn = Date.now();
|
||||
|
|
@ -174,9 +201,9 @@ function getTargetSlice(targetDataSource){
|
|||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
function drawCompass(graphics, x, y, height, width, increment, start){
|
||||
let drawCompass = function(graphics, x, y, height, width, increment, start){
|
||||
graphics.setFont12x20();
|
||||
graphics.setFontAlign(0,-1);
|
||||
graphics.setColor(graphics.theme.fg);
|
||||
|
|
@ -197,14 +224,19 @@ function drawCompass(graphics, x, y, height, width, increment, start){
|
|||
xpos+=increment*15;
|
||||
if (xpos > width + 20) break;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
function getCompassSlice(compassDataSource){
|
||||
let getCompassSlice = function(compassDataSource){
|
||||
let lastDrawn = Date.now() - Math.random()*2000;
|
||||
let lastDrawnValue = 0;
|
||||
const buffers = 4;
|
||||
let buf = [];
|
||||
return {
|
||||
refresh : function (){return Bangle.isLocked()?(Date.now() - lastDrawn > 2000):true;},
|
||||
refresh : function (){
|
||||
let bigChange = (Math.abs(lastDrawnValue - compassDataSource.getCourse()) > 2);
|
||||
let old = (Bangle.isLocked()?(Date.now() - lastDrawn > 2000):true);
|
||||
return bigChange && old;
|
||||
},
|
||||
draw: function (graphics, x,y,height,width){
|
||||
lastDrawn = Date.now();
|
||||
const max = 180;
|
||||
|
|
@ -212,12 +244,14 @@ function getCompassSlice(compassDataSource){
|
|||
|
||||
graphics.clearRect(x,y,x+width,y+height);
|
||||
|
||||
var start = compassDataSource.getCourse() - 90;
|
||||
if (isNaN(compassDataSource.getCourse())) start = -90;
|
||||
lastDrawnValue = compassDataSource.getCourse();
|
||||
|
||||
var start = lastDrawnValue - 90;
|
||||
if (isNaN(lastDrawnValue)) start = -90;
|
||||
if (start<0) start+=360;
|
||||
start = start % 360;
|
||||
|
||||
if (state.acc && compassDataSource.getCourseType() == "MAG"){
|
||||
if (WIDGETS.gpstrek.getState().acc && compassDataSource.getCourseType() == "MAG"){
|
||||
drawCompass(graphics,0,y+width*0.05,height-width*0.05,width,increment,start);
|
||||
} else {
|
||||
drawCompass(graphics,0,y,height,width,increment,start);
|
||||
|
|
@ -226,7 +260,8 @@ function getCompassSlice(compassDataSource){
|
|||
|
||||
if (compassDataSource.getPoints){
|
||||
for (let p of compassDataSource.getPoints()){
|
||||
var bpos = p.bearing - compassDataSource.getCourse();
|
||||
g.reset();
|
||||
var bpos = p.bearing - lastDrawnValue;
|
||||
if (bpos>180) bpos -=360;
|
||||
if (bpos<-180) bpos +=360;
|
||||
bpos+=120;
|
||||
|
|
@ -251,6 +286,7 @@ function getCompassSlice(compassDataSource){
|
|||
}
|
||||
if (compassDataSource.getMarkers){
|
||||
for (let m of compassDataSource.getMarkers()){
|
||||
g.reset();
|
||||
g.setColor(m.fillcolor);
|
||||
let mpos = m.xpos * width;
|
||||
if (m.xpos < 0.05) mpos = Math.floor(width*0.05);
|
||||
|
|
@ -263,9 +299,9 @@ function getCompassSlice(compassDataSource){
|
|||
graphics.setColor(g.theme.fg);
|
||||
graphics.fillRect(x,y,Math.floor(width*0.05),y+height);
|
||||
graphics.fillRect(Math.ceil(width*0.95),y,width,y+height);
|
||||
if (state.acc && compassDataSource.getCourseType() == "MAG") {
|
||||
let xh = E.clip(width*0.5-height/2+(((state.acc.x+1)/2)*height),width*0.5 - height/2, width*0.5 + height/2);
|
||||
let yh = E.clip(y+(((state.acc.y+1)/2)*height),y,y+height);
|
||||
if (WIDGETS.gpstrek.getState().acc && compassDataSource.getCourseType() == "MAG") {
|
||||
let xh = E.clip(width*0.5-height/2+(((WIDGETS.gpstrek.getState().acc.x+1)/2)*height),width*0.5 - height/2, width*0.5 + height/2);
|
||||
let yh = E.clip(y+(((WIDGETS.gpstrek.getState().acc.y+1)/2)*height),y,y+height);
|
||||
|
||||
graphics.fillRect(width*0.5 - height/2, y, width*0.5 + height/2, y + Math.floor(width*0.05));
|
||||
|
||||
|
|
@ -287,44 +323,48 @@ function getCompassSlice(compassDataSource){
|
|||
graphics.drawRect(Math.floor(width*0.05),y,Math.ceil(width*0.95),y+height);
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
function radians(a) {
|
||||
let radians = function(a) {
|
||||
return a*Math.PI/180;
|
||||
}
|
||||
};
|
||||
|
||||
function degrees(a) {
|
||||
var d = a*180/Math.PI;
|
||||
let degrees = function(a) {
|
||||
let d = a*180/Math.PI;
|
||||
return (d+360)%360;
|
||||
}
|
||||
};
|
||||
|
||||
function bearing(a,b){
|
||||
let bearing = function(a,b){
|
||||
if (!a || !b || !a.lon || !a.lat || !b.lon || !b.lat) return Infinity;
|
||||
var delta = radians(b.lon-a.lon);
|
||||
var alat = radians(a.lat);
|
||||
var blat = radians(b.lat);
|
||||
var y = Math.sin(delta) * Math.cos(blat);
|
||||
var x = Math.cos(alat)*Math.sin(blat) -
|
||||
let delta = radians(b.lon-a.lon);
|
||||
let alat = radians(a.lat);
|
||||
let blat = radians(b.lat);
|
||||
let y = Math.sin(delta) * Math.cos(blat);
|
||||
let x = Math.cos(alat)*Math.sin(blat) -
|
||||
Math.sin(alat)*Math.cos(blat)*Math.cos(delta);
|
||||
return Math.round(degrees(Math.atan2(y, x)));
|
||||
}
|
||||
};
|
||||
|
||||
function distance(a,b){
|
||||
let distance = function(a,b){
|
||||
if (!a || !b || !a.lon || !a.lat || !b.lon || !b.lat) return Infinity;
|
||||
var x = radians(a.lon-b.lon) * Math.cos(radians((a.lat+b.lat)/2));
|
||||
var y = radians(b.lat-a.lat);
|
||||
let x = radians(a.lon-b.lon) * Math.cos(radians((a.lat+b.lat)/2));
|
||||
let y = radians(b.lat-a.lat);
|
||||
return Math.round(Math.sqrt(x*x + y*y) * 6371000);
|
||||
}
|
||||
};
|
||||
|
||||
function triangle (x, y, width, height){
|
||||
let getAveragedCompass = function(){
|
||||
return Math.round(WIDGETS.gpstrek.getState().avgComp);
|
||||
};
|
||||
|
||||
let triangle = function(x, y, width, height){
|
||||
return [
|
||||
Math.round(x),Math.round(y),
|
||||
Math.round(x+width * 0.5), Math.round(y+height),
|
||||
Math.round(x-width * 0.5), Math.round(y+height)
|
||||
];
|
||||
}
|
||||
};
|
||||
|
||||
function onSwipe(dir){
|
||||
let onSwipe = function(dir){
|
||||
if (dir < 0) {
|
||||
nextScreen();
|
||||
} else if (dir > 0) {
|
||||
|
|
@ -332,9 +372,9 @@ function onSwipe(dir){
|
|||
} else {
|
||||
nextScreen();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
function setButtons(){
|
||||
let setButtons = function(){
|
||||
let options = {
|
||||
mode: "custom",
|
||||
swipe: onSwipe,
|
||||
|
|
@ -342,9 +382,9 @@ function setButtons(){
|
|||
touch: nextScreen
|
||||
};
|
||||
Bangle.setUI(options);
|
||||
}
|
||||
};
|
||||
|
||||
function getApproxFileSize(name){
|
||||
let getApproxFileSize = function(name){
|
||||
let currentStart = STORAGE.getStats().totalBytes;
|
||||
let currentSize = 0;
|
||||
for (let i = currentStart; i > 500; i/=2){
|
||||
|
|
@ -358,9 +398,9 @@ function getApproxFileSize(name){
|
|||
currentSize += currentDiff;
|
||||
}
|
||||
return currentSize;
|
||||
}
|
||||
};
|
||||
|
||||
function parseRouteData(filename, progressMonitor){
|
||||
let parseRouteData = function(filename, progressMonitor){
|
||||
let routeInfo = {};
|
||||
|
||||
routeInfo.filename = filename;
|
||||
|
|
@ -406,40 +446,40 @@ function parseRouteData(filename, progressMonitor){
|
|||
|
||||
set(routeInfo, 0);
|
||||
return routeInfo;
|
||||
}
|
||||
};
|
||||
|
||||
function hasPrev(route){
|
||||
let hasPrev = function(route){
|
||||
if (route.mirror) return route.index < (route.count - 1);
|
||||
return route.index > 0;
|
||||
}
|
||||
};
|
||||
|
||||
function hasNext(route){
|
||||
let hasNext = function(route){
|
||||
if (route.mirror) return route.index > 0;
|
||||
return route.index < (route.count - 1);
|
||||
}
|
||||
};
|
||||
|
||||
function next(route){
|
||||
let next = function(route){
|
||||
if (!hasNext(route)) return;
|
||||
if (route.mirror) set(route, --route.index);
|
||||
if (!route.mirror) set(route, ++route.index);
|
||||
}
|
||||
};
|
||||
|
||||
function set(route, index){
|
||||
let set = function(route, index){
|
||||
route.currentWaypoint = {};
|
||||
route.index = index;
|
||||
getEntry(route.filename, route.refs[index], route.currentWaypoint);
|
||||
}
|
||||
};
|
||||
|
||||
function prev(route){
|
||||
let prev = function(route){
|
||||
if (!hasPrev(route)) return;
|
||||
if (route.mirror) set(route, ++route.index);
|
||||
if (!route.mirror) set(route, --route.index);
|
||||
}
|
||||
};
|
||||
|
||||
let lastMirror;
|
||||
let cachedLast;
|
||||
|
||||
function getLast(route){
|
||||
let getLast = function(route){
|
||||
let wp = {};
|
||||
if (lastMirror != route.mirror){
|
||||
if (route.mirror) getEntry(route.filename, route.refs[0], wp);
|
||||
|
|
@ -448,14 +488,14 @@ function getLast(route){
|
|||
cachedLast = wp;
|
||||
}
|
||||
return cachedLast;
|
||||
}
|
||||
};
|
||||
|
||||
function removeMenu(){
|
||||
let removeMenu = function(){
|
||||
E.showMenu();
|
||||
switchNav();
|
||||
}
|
||||
};
|
||||
|
||||
function showProgress(progress, title, max){
|
||||
let showProgress = function(progress, title, max){
|
||||
//print("Progress",progress,max)
|
||||
let message = title? title: "Loading";
|
||||
if (max){
|
||||
|
|
@ -466,17 +506,17 @@ function showProgress(progress, title, max){
|
|||
for (let i = dots; i < 4; i++) message += " ";
|
||||
}
|
||||
E.showMessage(message);
|
||||
}
|
||||
};
|
||||
|
||||
function handleLoading(c){
|
||||
let handleLoading = function(c){
|
||||
E.showMenu();
|
||||
state.route = parseRouteData(c, showProgress);
|
||||
state.waypoint = null;
|
||||
WIDGETS.gpstrek.getState().route = parseRouteData(c, showProgress);
|
||||
WIDGETS.gpstrek.getState().waypoint = null;
|
||||
WIDGETS.gpstrek.getState().route.mirror = false;
|
||||
removeMenu();
|
||||
state.route.mirror = false;
|
||||
}
|
||||
};
|
||||
|
||||
function showRouteSelector (){
|
||||
let showRouteSelector = function(){
|
||||
var menu = {
|
||||
"" : {
|
||||
back : showRouteMenu,
|
||||
|
|
@ -488,9 +528,9 @@ function showRouteSelector (){
|
|||
});
|
||||
|
||||
E.showMenu(menu);
|
||||
}
|
||||
};
|
||||
|
||||
function showRouteMenu(){
|
||||
let showRouteMenu = function(){
|
||||
var menu = {
|
||||
"" : {
|
||||
"title" : "Route",
|
||||
|
|
@ -499,48 +539,48 @@ function showRouteMenu(){
|
|||
"Select file" : showRouteSelector
|
||||
};
|
||||
|
||||
if (state.route){
|
||||
if (WIDGETS.gpstrek.getState().route){
|
||||
menu.Mirror = {
|
||||
value: state && state.route && !!state.route.mirror || false,
|
||||
value: WIDGETS.gpstrek.getState() && WIDGETS.gpstrek.getState().route && !!WIDGETS.gpstrek.getState().route.mirror || false,
|
||||
onchange: v=>{
|
||||
state.route.mirror = v;
|
||||
WIDGETS.gpstrek.getState().route.mirror = v;
|
||||
}
|
||||
};
|
||||
menu['Select closest waypoint'] = function () {
|
||||
if (state.currentPos && state.currentPos.lat){
|
||||
setClosestWaypoint(state.route, null, showProgress); removeMenu();
|
||||
if (WIDGETS.gpstrek.getState().currentPos && WIDGETS.gpstrek.getState().currentPos.lat){
|
||||
setClosestWaypoint(WIDGETS.gpstrek.getState().route, null, showProgress); removeMenu();
|
||||
} else {
|
||||
E.showAlert("No position").then(()=>{E.showMenu(menu);});
|
||||
}
|
||||
};
|
||||
menu['Select closest waypoint (not visited)'] = function () {
|
||||
if (state.currentPos && state.currentPos.lat){
|
||||
setClosestWaypoint(state.route, state.route.index, showProgress); removeMenu();
|
||||
if (WIDGETS.gpstrek.getState().currentPos && WIDGETS.gpstrek.getState().currentPos.lat){
|
||||
setClosestWaypoint(WIDGETS.gpstrek.getState().route, WIDGETS.gpstrek.getState().route.index, showProgress); removeMenu();
|
||||
} else {
|
||||
E.showAlert("No position").then(()=>{E.showMenu(menu);});
|
||||
}
|
||||
};
|
||||
menu['Select waypoint'] = {
|
||||
value : state.route.index,
|
||||
min:1,max:state.route.count,step:1,
|
||||
onchange : v => { set(state.route, v-1); }
|
||||
value : WIDGETS.gpstrek.getState().route.index,
|
||||
min:1,max:WIDGETS.gpstrek.getState().route.count,step:1,
|
||||
onchange : v => { set(WIDGETS.gpstrek.getState().route, v-1); }
|
||||
};
|
||||
menu['Select waypoint as current position'] = function (){
|
||||
state.currentPos.lat = state.route.currentWaypoint.lat;
|
||||
state.currentPos.lon = state.route.currentWaypoint.lon;
|
||||
state.currentPos.alt = state.route.currentWaypoint.alt;
|
||||
WIDGETS.gpstrek.getState().currentPos.lat = WIDGETS.gpstrek.getState().route.currentWaypoint.lat;
|
||||
WIDGETS.gpstrek.getState().currentPos.lon = WIDGETS.gpstrek.getState().route.currentWaypoint.lon;
|
||||
WIDGETS.gpstrek.getState().currentPos.alt = WIDGETS.gpstrek.getState().route.currentWaypoint.alt;
|
||||
removeMenu();
|
||||
};
|
||||
}
|
||||
|
||||
if (state.route && hasPrev(state.route))
|
||||
menu['Previous waypoint'] = function() { prev(state.route); removeMenu(); };
|
||||
if (state.route && hasNext(state.route))
|
||||
menu['Next waypoint'] = function() { next(state.route); removeMenu(); };
|
||||
if (WIDGETS.gpstrek.getState().route && hasPrev(WIDGETS.gpstrek.getState().route))
|
||||
menu['Previous waypoint'] = function() { prev(WIDGETS.gpstrek.getState().route); removeMenu(); };
|
||||
if (WIDGETS.gpstrek.getState().route && hasNext(WIDGETS.gpstrek.getState().route))
|
||||
menu['Next waypoint'] = function() { next(WIDGETS.gpstrek.getState().route); removeMenu(); };
|
||||
E.showMenu(menu);
|
||||
}
|
||||
};
|
||||
|
||||
function showWaypointSelector(){
|
||||
let showWaypointSelector = function(){
|
||||
let waypoints = require("waypoints").load();
|
||||
var menu = {
|
||||
"" : {
|
||||
|
|
@ -550,41 +590,41 @@ function showWaypointSelector(){
|
|||
|
||||
waypoints.forEach((wp,c)=>{
|
||||
menu[waypoints[c].name] = function (){
|
||||
state.waypoint = waypoints[c];
|
||||
state.waypointIndex = c;
|
||||
state.route = null;
|
||||
WIDGETS.gpstrek.getState().waypoint = waypoints[c];
|
||||
WIDGETS.gpstrek.getState().waypointIndex = c;
|
||||
WIDGETS.gpstrek.getState().route = null;
|
||||
removeMenu();
|
||||
};
|
||||
});
|
||||
|
||||
E.showMenu(menu);
|
||||
}
|
||||
};
|
||||
|
||||
function showCalibrationMenu(){
|
||||
let showCalibrationMenu = function(){
|
||||
let menu = {
|
||||
"" : {
|
||||
"title" : "Calibration",
|
||||
back : showMenu,
|
||||
},
|
||||
"Barometer (GPS)" : ()=>{
|
||||
if (!state.currentPos || isNaN(state.currentPos.alt)){
|
||||
if (!WIDGETS.gpstrek.getState().currentPos || isNaN(WIDGETS.gpstrek.getState().currentPos.alt)){
|
||||
E.showAlert("No GPS altitude").then(()=>{E.showMenu(menu);});
|
||||
} else {
|
||||
state.calibAltDiff = state.altitude - state.currentPos.alt;
|
||||
E.showAlert("Calibrated Altitude Difference: " + state.calibAltDiff.toFixed(0)).then(()=>{removeMenu();});
|
||||
WIDGETS.gpstrek.getState().calibAltDiff = WIDGETS.gpstrek.getState().altitude - WIDGETS.gpstrek.getState().currentPos.alt;
|
||||
E.showAlert("Calibrated Altitude Difference: " + WIDGETS.gpstrek.getState().calibAltDiff.toFixed(0)).then(()=>{removeMenu();});
|
||||
}
|
||||
},
|
||||
"Barometer (Manual)" : {
|
||||
value : Math.round(state.currentPos && (state.currentPos.alt != undefined && !isNaN(state.currentPos.alt)) ? state.currentPos.alt: state.altitude),
|
||||
value : Math.round(WIDGETS.gpstrek.getState().currentPos && (WIDGETS.gpstrek.getState().currentPos.alt != undefined && !isNaN(WIDGETS.gpstrek.getState().currentPos.alt)) ? WIDGETS.gpstrek.getState().currentPos.alt: WIDGETS.gpstrek.getState().altitude),
|
||||
min:-2000,max: 10000,step:1,
|
||||
onchange : v => { state.calibAltDiff = state.altitude - v; }
|
||||
onchange : v => { WIDGETS.gpstrek.getState().calibAltDiff = WIDGETS.gpstrek.getState().altitude - v; }
|
||||
},
|
||||
"Reset Compass" : ()=>{ Bangle.resetCompass(); removeMenu();},
|
||||
};
|
||||
E.showMenu(menu);
|
||||
}
|
||||
};
|
||||
|
||||
function showWaypointMenu(){
|
||||
let showWaypointMenu = function(){
|
||||
let menu = {
|
||||
"" : {
|
||||
"title" : "Waypoint",
|
||||
|
|
@ -593,21 +633,21 @@ function showWaypointMenu(){
|
|||
"Select waypoint" : showWaypointSelector,
|
||||
};
|
||||
E.showMenu(menu);
|
||||
}
|
||||
};
|
||||
|
||||
function showBackgroundMenu(){
|
||||
let showBackgroundMenu = function(){
|
||||
let menu = {
|
||||
"" : {
|
||||
"title" : "Background",
|
||||
back : showMenu,
|
||||
},
|
||||
"Start" : ()=>{ E.showPrompt("Start?").then((v)=>{ if (v) {WIDGETS.gpstrek.start(true); removeMenu();} else {showMenu();}}).catch(()=>{E.showMenu(mainmenu);});},
|
||||
"Stop" : ()=>{ E.showPrompt("Stop?").then((v)=>{ if (v) {WIDGETS.gpstrek.stop(true); removeMenu();} else {showMenu();}}).catch(()=>{E.showMenu(mainmenu);});},
|
||||
"Start" : ()=>{ E.showPrompt("Start?").then((v)=>{ if (v) {WIDGETS.gpstrek.start(true); removeMenu();} else {showMenu();}}).catch(()=>{showMenu();});},
|
||||
"Stop" : ()=>{ E.showPrompt("Stop?").then((v)=>{ if (v) {WIDGETS.gpstrek.stop(true); removeMenu();} else {showMenu();}}).catch(()=>{showMenu();});},
|
||||
};
|
||||
E.showMenu(menu);
|
||||
}
|
||||
};
|
||||
|
||||
function showMenu(){
|
||||
let showMenu = function(){
|
||||
var mainmenu = {
|
||||
"" : {
|
||||
"title" : "Main",
|
||||
|
|
@ -617,50 +657,55 @@ function showMenu(){
|
|||
"Waypoint" : showWaypointMenu,
|
||||
"Background" : showBackgroundMenu,
|
||||
"Calibration": showCalibrationMenu,
|
||||
"Reset" : ()=>{ E.showPrompt("Do Reset?").then((v)=>{ if (v) {WIDGETS.gpstrek.resetState(); removeMenu();} else {E.showMenu(mainmenu);}});},
|
||||
"Reset" : ()=>{ E.showPrompt("Do Reset?").then((v)=>{ if (v) {WIDGETS.gpstrek.resetState(); removeMenu();} else {E.showMenu(mainmenu);}}).catch(()=>{E.showMenu(mainmenu);});},
|
||||
"Info rows" : {
|
||||
value : numberOfSlices,
|
||||
value : WIDGETS.gpstrek.getState().numberOfSlices,
|
||||
min:1,max:6,step:1,
|
||||
onchange : v => { setNumberOfSlices(v); }
|
||||
onchange : v => { WIDGETS.gpstrek.getState().numberOfSlices = v; }
|
||||
},
|
||||
};
|
||||
|
||||
E.showMenu(mainmenu);
|
||||
}
|
||||
};
|
||||
|
||||
let scheduleDraw = true;
|
||||
|
||||
function switchMenu(){
|
||||
screen = 0;
|
||||
scheduleDraw = false;
|
||||
showMenu();
|
||||
}
|
||||
let switchMenu = function(){
|
||||
stopDrawing();
|
||||
showMenu();
|
||||
};
|
||||
|
||||
function drawInTimeout(){
|
||||
setTimeout(()=>{
|
||||
let stopDrawing = function(){
|
||||
if (drawTimeout) clearTimeout(drawTimeout);
|
||||
scheduleDraw = false;
|
||||
};
|
||||
|
||||
let drawInTimeout = function(){
|
||||
if (global.drawTimeout) clearTimeout(drawTimeout);
|
||||
drawTimeout = setTimeout(()=>{
|
||||
drawTimeout = undefined;
|
||||
draw();
|
||||
if (scheduleDraw)
|
||||
setTimeout(drawInTimeout, 0);
|
||||
},0);
|
||||
}
|
||||
},50);
|
||||
};
|
||||
|
||||
function switchNav(){
|
||||
let switchNav = function(){
|
||||
if (!screen) screen = 1;
|
||||
setButtons();
|
||||
scheduleDraw = true;
|
||||
firstDraw = true;
|
||||
drawInTimeout();
|
||||
}
|
||||
};
|
||||
|
||||
function nextScreen(){
|
||||
let nextScreen = function(){
|
||||
screen++;
|
||||
if (screen > maxScreens){
|
||||
screen = 1;
|
||||
}
|
||||
}
|
||||
drawInTimeout();
|
||||
};
|
||||
|
||||
function setClosestWaypoint(route, startindex, progress){
|
||||
if (startindex >= state.route.count) startindex = state.route.count - 1;
|
||||
if (!state.currentPos.lat){
|
||||
let setClosestWaypoint = function(route, startindex, progress){
|
||||
if (startindex >= WIDGETS.gpstrek.getState().route.count) startindex = WIDGETS.gpstrek.getState().route.count - 1;
|
||||
if (!WIDGETS.gpstrek.getState().currentPos.lat){
|
||||
set(route, startindex);
|
||||
return;
|
||||
}
|
||||
|
|
@ -670,7 +715,7 @@ function setClosestWaypoint(route, startindex, progress){
|
|||
if (progress && (i % 5 == 0)) progress(i-(startindex?startindex:0), "Searching", route.count);
|
||||
let wp = {};
|
||||
getEntry(route.filename, route.refs[i], wp);
|
||||
let curDist = distance(state.currentPos, wp);
|
||||
let curDist = distance(WIDGETS.gpstrek.getState().currentPos, wp);
|
||||
if (curDist < minDist){
|
||||
minDist = curDist;
|
||||
minIndex = i;
|
||||
|
|
@ -679,30 +724,28 @@ function setClosestWaypoint(route, startindex, progress){
|
|||
}
|
||||
}
|
||||
set(route, minIndex);
|
||||
}
|
||||
|
||||
let screen = 1;
|
||||
};
|
||||
|
||||
const finishIcon = atob("CggB//meZmeZ+Z5n/w==");
|
||||
|
||||
const compassSliceData = {
|
||||
getCourseType: function(){
|
||||
return (state.currentPos && state.currentPos.course) ? "GPS" : "MAG";
|
||||
return (WIDGETS.gpstrek.getState().currentPos && WIDGETS.gpstrek.getState().currentPos.course) ? "GPS" : "MAG";
|
||||
},
|
||||
getCourse: function (){
|
||||
if(compassSliceData.getCourseType() == "GPS") return state.currentPos.course;
|
||||
return state.compassHeading?state.compassHeading:undefined;
|
||||
if(compassSliceData.getCourseType() == "GPS") return WIDGETS.gpstrek.getState().currentPos.course;
|
||||
return getAveragedCompass();
|
||||
},
|
||||
getPoints: function (){
|
||||
let points = [];
|
||||
if (state.currentPos && state.currentPos.lon && state.route && state.route.currentWaypoint){
|
||||
points.push({bearing:bearing(state.currentPos, state.route.currentWaypoint), color:"#0f0"});
|
||||
if (WIDGETS.gpstrek.getState().currentPos && WIDGETS.gpstrek.getState().currentPos.lon && WIDGETS.gpstrek.getState().route && WIDGETS.gpstrek.getState().route.currentWaypoint){
|
||||
points.push({bearing:bearing(WIDGETS.gpstrek.getState().currentPos, WIDGETS.gpstrek.getState().route.currentWaypoint), color:"#0f0"});
|
||||
}
|
||||
if (state.currentPos && state.currentPos.lon && state.route){
|
||||
points.push({bearing:bearing(state.currentPos, getLast(state.route)), icon: finishIcon});
|
||||
if (WIDGETS.gpstrek.getState().currentPos && WIDGETS.gpstrek.getState().currentPos.lon && WIDGETS.gpstrek.getState().route){
|
||||
points.push({bearing:bearing(WIDGETS.gpstrek.getState().currentPos, getLast(WIDGETS.gpstrek.getState().route)), icon: finishIcon});
|
||||
}
|
||||
if (state.currentPos && state.currentPos.lon && state.waypoint){
|
||||
points.push({bearing:bearing(state.currentPos, state.waypoint), icon: finishIcon});
|
||||
if (WIDGETS.gpstrek.getState().currentPos && WIDGETS.gpstrek.getState().currentPos.lon && WIDGETS.gpstrek.getState().waypoint){
|
||||
points.push({bearing:bearing(WIDGETS.gpstrek.getState().currentPos, WIDGETS.gpstrek.getState().waypoint), icon: finishIcon});
|
||||
}
|
||||
return points;
|
||||
},
|
||||
|
|
@ -714,79 +757,74 @@ const compassSliceData = {
|
|||
const waypointData = {
|
||||
icon: atob("EBCBAAAAAAAAAAAAcIB+zg/uAe4AwACAAAAAAAAAAAAAAAAA"),
|
||||
getProgress: function() {
|
||||
return (state.route.index + 1) + "/" + state.route.count;
|
||||
return (WIDGETS.gpstrek.getState().route.index + 1) + "/" + WIDGETS.gpstrek.getState().route.count;
|
||||
},
|
||||
getTarget: function (){
|
||||
if (distance(state.currentPos,state.route.currentWaypoint) < 30 && hasNext(state.route)){
|
||||
next(state.route);
|
||||
if (distance(WIDGETS.gpstrek.getState().currentPos,WIDGETS.gpstrek.getState().route.currentWaypoint) < 30 && hasNext(WIDGETS.gpstrek.getState().route)){
|
||||
next(WIDGETS.gpstrek.getState().route);
|
||||
Bangle.buzz(1000);
|
||||
}
|
||||
return state.route.currentWaypoint;
|
||||
return WIDGETS.gpstrek.getState().route.currentWaypoint;
|
||||
},
|
||||
getStart: function (){
|
||||
return state.currentPos;
|
||||
return WIDGETS.gpstrek.getState().currentPos;
|
||||
}
|
||||
};
|
||||
|
||||
const finishData = {
|
||||
icon: atob("EBABAAA/4DmgJmAmYDmgOaAmYD/gMAAwADAAMAAwAAAAAAA="),
|
||||
getTarget: function (){
|
||||
if (state.route) return getLast(state.route);
|
||||
if (state.waypoint) return state.waypoint;
|
||||
if (WIDGETS.gpstrek.getState().route) return getLast(WIDGETS.gpstrek.getState().route);
|
||||
if (WIDGETS.gpstrek.getState().waypoint) return WIDGETS.gpstrek.getState().waypoint;
|
||||
},
|
||||
getStart: function (){
|
||||
return state.currentPos;
|
||||
return WIDGETS.gpstrek.getState().currentPos;
|
||||
}
|
||||
};
|
||||
|
||||
let sliceHeight;
|
||||
function setNumberOfSlices(number){
|
||||
numberOfSlices = number;
|
||||
sliceHeight = Math.floor((g.getHeight()-(showWidgets?24:0))/numberOfSlices);
|
||||
}
|
||||
|
||||
let slices = [];
|
||||
let maxScreens = 1;
|
||||
setNumberOfSlices(3);
|
||||
let getSliceHeight = function(number){
|
||||
return Math.floor(Bangle.appRect.h/WIDGETS.gpstrek.getState().numberOfSlices);
|
||||
};
|
||||
|
||||
let compassSlice = getCompassSlice(compassSliceData);
|
||||
let waypointSlice = getTargetSlice(waypointData);
|
||||
let finishSlice = getTargetSlice(finishData);
|
||||
let eleSlice = getDoubleLineSlice("Up","Down",()=>{
|
||||
return loc.distance(state.up,3) + "/" + (state.route ? loc.distance(state.route.up,3):"---");
|
||||
return loc.distance(WIDGETS.gpstrek.getState().up,3) + "/" + (WIDGETS.gpstrek.getState().route ? loc.distance(WIDGETS.gpstrek.getState().route.up,3):"---");
|
||||
},()=>{
|
||||
return loc.distance(state.down,3) + "/" + (state.route ? loc.distance(state.route.down,3): "---");
|
||||
return loc.distance(WIDGETS.gpstrek.getState().down,3) + "/" + (WIDGETS.gpstrek.getState().route ? loc.distance(WIDGETS.gpstrek.getState().route.down,3): "---");
|
||||
});
|
||||
|
||||
let statusSlice = getDoubleLineSlice("Speed","Alt",()=>{
|
||||
let speed = 0;
|
||||
if (state.currentPos && state.currentPos.speed) speed = state.currentPos.speed;
|
||||
if (WIDGETS.gpstrek.getState().currentPos && WIDGETS.gpstrek.getState().currentPos.speed) speed = WIDGETS.gpstrek.getState().currentPos.speed;
|
||||
return loc.speed(speed,2);
|
||||
},()=>{
|
||||
let alt = Infinity;
|
||||
if (!isNaN(state.altitude)){
|
||||
alt = isNaN(state.calibAltDiff) ? state.altitude : (state.altitude - state.calibAltDiff);
|
||||
if (!isNaN(WIDGETS.gpstrek.getState().altitude)){
|
||||
alt = isNaN(WIDGETS.gpstrek.getState().calibAltDiff) ? WIDGETS.gpstrek.getState().altitude : (WIDGETS.gpstrek.getState().altitude - WIDGETS.gpstrek.getState().calibAltDiff);
|
||||
}
|
||||
if (state.currentPos && state.currentPos.alt) alt = state.currentPos.alt;
|
||||
if (WIDGETS.gpstrek.getState().currentPos && WIDGETS.gpstrek.getState().currentPos.alt) alt = WIDGETS.gpstrek.getState().currentPos.alt;
|
||||
if (isNaN(alt)) return "---";
|
||||
return loc.distance(alt,3);
|
||||
});
|
||||
|
||||
let status2Slice = getDoubleLineSlice("Compass","GPS",()=>{
|
||||
return (state.compassHeading?Math.round(state.compassHeading):"---") + "°";
|
||||
return getAveragedCompass() + "°";
|
||||
},()=>{
|
||||
let course = "---°";
|
||||
if (state.currentPos && state.currentPos.course) course = state.currentPos.course + "°";
|
||||
if (WIDGETS.gpstrek.getState().currentPos && WIDGETS.gpstrek.getState().currentPos.course) course = WIDGETS.gpstrek.getState().currentPos.course + "°";
|
||||
return course;
|
||||
},200);
|
||||
|
||||
let healthSlice = getDoubleLineSlice("Heart","Steps",()=>{
|
||||
return state.bpm;
|
||||
return WIDGETS.gpstrek.getState().bpm || "---";
|
||||
},()=>{
|
||||
return state.steps;
|
||||
return !isNaN(WIDGETS.gpstrek.getState().steps)? WIDGETS.gpstrek.getState().steps: "---";
|
||||
});
|
||||
|
||||
let system2Slice = getDoubleLineSlice("Bat","",()=>{
|
||||
return (Bangle.isCharging()?"+":"") + E.getBattery().toFixed(0)+"% " + NRF.getBattery().toFixed(2) + "V";
|
||||
return (Bangle.isCharging()?"+":"") + E.getBattery().toFixed(0)+"% " + (analogRead(D3)*4.2/BAT_FULL).toFixed(2) + "V";
|
||||
},()=>{
|
||||
return "";
|
||||
});
|
||||
|
|
@ -798,17 +836,17 @@ let systemSlice = getDoubleLineSlice("RAM","Storage",()=>{
|
|||
return (STORAGE.getFree()/1024).toFixed(0)+"kB";
|
||||
});
|
||||
|
||||
function updateSlices(){
|
||||
let updateSlices = function(){
|
||||
slices = [];
|
||||
slices.push(compassSlice);
|
||||
|
||||
if (state.currentPos && state.currentPos.lat && state.route && state.route.currentWaypoint && state.route.index < state.route.count - 1) {
|
||||
if (WIDGETS.gpstrek.getState().currentPos && WIDGETS.gpstrek.getState().currentPos.lat && WIDGETS.gpstrek.getState().route && WIDGETS.gpstrek.getState().route.currentWaypoint && WIDGETS.gpstrek.getState().route.index < WIDGETS.gpstrek.getState().route.count - 1) {
|
||||
slices.push(waypointSlice);
|
||||
}
|
||||
if (state.currentPos && state.currentPos.lat && (state.route || state.waypoint)) {
|
||||
if (WIDGETS.gpstrek.getState().currentPos && WIDGETS.gpstrek.getState().currentPos.lat && (WIDGETS.gpstrek.getState().route || WIDGETS.gpstrek.getState().waypoint)) {
|
||||
slices.push(finishSlice);
|
||||
}
|
||||
if ((state.route && state.route.down !== undefined) || state.down != undefined) {
|
||||
if ((WIDGETS.gpstrek.getState().route && WIDGETS.gpstrek.getState().route.down !== undefined) || WIDGETS.gpstrek.getState().down != undefined) {
|
||||
slices.push(eleSlice);
|
||||
}
|
||||
slices.push(statusSlice);
|
||||
|
|
@ -816,42 +854,44 @@ function updateSlices(){
|
|||
slices.push(healthSlice);
|
||||
slices.push(systemSlice);
|
||||
slices.push(system2Slice);
|
||||
maxScreens = Math.ceil(slices.length/numberOfSlices);
|
||||
}
|
||||
maxScreens = Math.ceil(slices.length/WIDGETS.gpstrek.getState().numberOfSlices);
|
||||
};
|
||||
|
||||
function clear() {
|
||||
g.clearRect(0,(showWidgets ? 24 : 0), g.getWidth(),g.getHeight());
|
||||
}
|
||||
let lastDrawnScreen;
|
||||
let firstDraw = true;
|
||||
let clear = function() {
|
||||
g.clearRect(Bangle.appRect);
|
||||
};
|
||||
|
||||
function draw(){
|
||||
if (!screen) return;
|
||||
let ypos = showWidgets ? 24 : 0;
|
||||
let draw = function(){
|
||||
if (!global.screen) return;
|
||||
let ypos = Bangle.appRect.y;
|
||||
|
||||
let firstSlice = (screen-1)*numberOfSlices;
|
||||
let firstSlice = (screen-1)*WIDGETS.gpstrek.getState().numberOfSlices;
|
||||
|
||||
updateSlices();
|
||||
|
||||
let force = lastDrawnScreen != screen || firstDraw;
|
||||
if (force){
|
||||
clear();
|
||||
if (showWidgets){
|
||||
Bangle.drawWidgets();
|
||||
}
|
||||
}
|
||||
if (firstDraw) Bangle.drawWidgets();
|
||||
lastDrawnScreen = screen;
|
||||
|
||||
for (let slice of slices.slice(firstSlice,firstSlice + numberOfSlices)) {
|
||||
let sliceHeight = getSliceHeight();
|
||||
for (let slice of slices.slice(firstSlice,firstSlice + WIDGETS.gpstrek.getState().numberOfSlices)) {
|
||||
g.reset();
|
||||
if (!slice.refresh || slice.refresh() || force) slice.draw(g,0,ypos,sliceHeight,g.getWidth());
|
||||
ypos += sliceHeight+1;
|
||||
g.drawLine(0,ypos-1,g.getWidth(),ypos-1);
|
||||
}
|
||||
|
||||
if (scheduleDraw){
|
||||
drawInTimeout();
|
||||
}
|
||||
firstDraw = false;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
switchNav();
|
||||
|
||||
g.clear();
|
||||
clear();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"id": "gpstrek",
|
||||
"name": "GPS Trekking",
|
||||
"version": "0.07",
|
||||
"version": "0.08",
|
||||
"description": "Helper for tracking the status/progress during hiking. Do NOT depend on this for navigation!",
|
||||
"icon": "icon.png",
|
||||
"screenshots": [{"url":"screen1.png"},{"url":"screen2.png"},{"url":"screen3.png"},{"url":"screen4.png"}],
|
||||
|
|
|
|||
|
|
@ -1,6 +1,28 @@
|
|||
(() => {
|
||||
const SAMPLES=5;
|
||||
function initState(){
|
||||
//cleanup volatile state here
|
||||
state = {};
|
||||
state.compassSamples = new Array(SAMPLES).fill(0);
|
||||
state.lastSample = 0;
|
||||
state.sampleIndex = 0;
|
||||
state.currentPos={};
|
||||
state.steps = 0;
|
||||
state.calibAltDiff = 0;
|
||||
state.numberOfSlices = 3;
|
||||
state.steps = 0;
|
||||
state.up = 0;
|
||||
state.down = 0;
|
||||
state.saved = 0;
|
||||
state.avgComp = 0;
|
||||
}
|
||||
|
||||
const STORAGE=require('Storage');
|
||||
let state = STORAGE.readJSON("gpstrek.state.json")||{};
|
||||
let state = STORAGE.readJSON("gpstrek.state.json");
|
||||
if (!state) {
|
||||
state = {};
|
||||
initState();
|
||||
}
|
||||
let bgChanged = false;
|
||||
|
||||
function saveState(){
|
||||
|
|
@ -8,12 +30,13 @@ function saveState(){
|
|||
STORAGE.writeJSON("gpstrek.state.json", state);
|
||||
}
|
||||
|
||||
E.on("kill",()=>{
|
||||
if (bgChanged){
|
||||
function onKill(){
|
||||
if (bgChanged || state.route || state.waypoint){
|
||||
saveState();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
E.on("kill", onKill);
|
||||
|
||||
function onPulse(e){
|
||||
state.bpm = e.bpm;
|
||||
|
|
@ -23,27 +46,47 @@ function onGPS(fix) {
|
|||
if(fix.fix) state.currentPos = fix;
|
||||
}
|
||||
|
||||
function onMag(e) {
|
||||
if (!state.compassHeading) state.compassHeading = e.heading;
|
||||
let radians = function(a) {
|
||||
return a*Math.PI/180;
|
||||
};
|
||||
|
||||
//if (a+180)mod 360 == b then
|
||||
//return (a+b)/2 mod 360 and ((a+b)/2 mod 360) + 180 (they are both the solution, so you may choose one depending if you prefer counterclockwise or clockwise direction)
|
||||
//else
|
||||
//return arctan( (sin(a)+sin(b)) / (cos(a)+cos(b) )
|
||||
let degrees = function(a) {
|
||||
let d = a*180/Math.PI;
|
||||
return (d+360)%360;
|
||||
};
|
||||
|
||||
/*
|
||||
let average;
|
||||
let a = radians(compassHeading);
|
||||
let b = radians(e.heading);
|
||||
if ((a+180) % 360 == b){
|
||||
average = ((a+b)/2 % 360); //can add 180 depending on rotation
|
||||
} else {
|
||||
average = Math.atan( (Math.sin(a)+Math.sin(b))/(Math.cos(a)+Math.cos(b)) );
|
||||
function average(samples){
|
||||
let s = 0;
|
||||
let c = 0;
|
||||
for (let h of samples){
|
||||
s += Math.sin(radians(h));
|
||||
c += Math.cos(radians(h));
|
||||
}
|
||||
s /= samples.length;
|
||||
c /= samples.length;
|
||||
let result = degrees(Math.atan(s/c));
|
||||
|
||||
if (c < 0) result += 180;
|
||||
if (s < 0 && c > 0) result += 360;
|
||||
|
||||
result%=360;
|
||||
return result;
|
||||
}
|
||||
|
||||
function onMag(e) {
|
||||
if (!isNaN(e.heading)){
|
||||
if (Bangle.isLocked() || (Bangle.getGPSFix() && Bangle.getGPSFix().lon))
|
||||
state.avgComp = e.heading;
|
||||
else {
|
||||
state.compassSamples[state.sampleIndex++] = e.heading;
|
||||
state.lastSample = Date.now();
|
||||
if (state.sampleIndex > SAMPLES - 1){
|
||||
state.sampleIndex = 0;
|
||||
let avg = average(state.compassSamples);
|
||||
state.avgComp = average([state.avgComp,avg]);
|
||||
}
|
||||
}
|
||||
}
|
||||
print("Angle",compassHeading,e.heading, average);
|
||||
compassHeading = (compassHeading + degrees(average)) % 360;
|
||||
*/
|
||||
state.compassHeading = Math.round(e.heading);
|
||||
}
|
||||
|
||||
function onStep(e) {
|
||||
|
|
@ -73,6 +116,16 @@ function onAcc (e){
|
|||
state.acc = e;
|
||||
}
|
||||
|
||||
function update(){
|
||||
if (state.active){
|
||||
start(false);
|
||||
}
|
||||
if (state.active == !(WIDGETS.gpstrek.width)) {
|
||||
if(WIDGETS.gpstrek) WIDGETS.gpstrek.width = state.active?24:0;
|
||||
Bangle.drawWidgets();
|
||||
}
|
||||
}
|
||||
|
||||
function start(bg){
|
||||
Bangle.removeListener('GPS', onGPS);
|
||||
Bangle.removeListener("HRM", onPulse);
|
||||
|
|
@ -94,9 +147,9 @@ function start(bg){
|
|||
if (bg){
|
||||
if (!state.active) bgChanged = true;
|
||||
state.active = true;
|
||||
update();
|
||||
saveState();
|
||||
}
|
||||
Bangle.drawWidgets();
|
||||
}
|
||||
|
||||
function stop(bg){
|
||||
|
|
@ -114,22 +167,10 @@ function stop(bg){
|
|||
Bangle.removeListener("step", onStep);
|
||||
Bangle.removeListener("pressure", onPressure);
|
||||
Bangle.removeListener('accel', onAcc);
|
||||
E.removeListener("kill", onKill);
|
||||
}
|
||||
update();
|
||||
saveState();
|
||||
Bangle.drawWidgets();
|
||||
}
|
||||
|
||||
function initState(){
|
||||
//cleanup volatile state here
|
||||
state.currentPos={};
|
||||
state.steps = Bangle.getStepCount();
|
||||
state.calibAltDiff = 0;
|
||||
state.up = 0;
|
||||
state.down = 0;
|
||||
}
|
||||
|
||||
if (state.saved && state.saved < Date.now() - 60000){
|
||||
initState();
|
||||
}
|
||||
|
||||
if (state.active){
|
||||
|
|
@ -141,11 +182,15 @@ WIDGETS["gpstrek"]={
|
|||
width:state.active?24:0,
|
||||
resetState: initState,
|
||||
getState: function() {
|
||||
if (state.saved && Date.now() - state.saved > 60000 || !state){
|
||||
initState();
|
||||
}
|
||||
return state;
|
||||
},
|
||||
start:start,
|
||||
stop:stop,
|
||||
draw:function() {
|
||||
update();
|
||||
if (state.active){
|
||||
g.reset();
|
||||
g.drawImage(atob("GBiBAAAAAAAAAAAYAAAYAAAYAAA8AAA8AAB+AAB+AADbAADbAAGZgAGZgAMYwAMYwAcY4AYYYA5+cA3/sB/D+B4AeBAACAAAAAAAAA=="), this.x, this.y);
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@
|
|||
"description": "Integrates your BangleJS into HomeAssistant.",
|
||||
"icon": "ha.png",
|
||||
"type": "app",
|
||||
"tags": "tool",
|
||||
"tags": "tool,clkinfo",
|
||||
"readme": "README.md",
|
||||
"supports": ["BANGLEJS2"],
|
||||
"custom": "custom.html",
|
||||
|
|
|
|||
|
|
@ -7,3 +7,5 @@
|
|||
0.21: Add Settings
|
||||
0.22: Use default Bangle formatter for booleans
|
||||
0.23: Added note to configure position in "my location" if not done yet. Small fixes.
|
||||
0.24: Added fast load
|
||||
0.25: Minor code optimization
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
{ // must be inside our own scope here so that when we are unloaded everything disappears
|
||||
|
||||
// ------- Settings file
|
||||
const SETTINGSFILE = "hworldclock.json";
|
||||
var secondsMode;
|
||||
|
|
@ -153,15 +155,15 @@ function updatePos() {
|
|||
|
||||
function drawSeconds() {
|
||||
// get date
|
||||
var d = new Date();
|
||||
var da = d.toString().split(" ");
|
||||
let d = new Date();
|
||||
let da = d.toString().split(" ");
|
||||
|
||||
// default draw styles
|
||||
g.reset().setBgColor(g.theme.bg).setFontAlign(0, 0);
|
||||
|
||||
// draw time
|
||||
var time = da[4].split(":");
|
||||
var seconds = time[2];
|
||||
let time = da[4].split(":");
|
||||
let seconds = time[2];
|
||||
|
||||
g.setFont("5x9Numeric7Seg",primaryTimeFontSize - 3);
|
||||
if (g.theme.dark) {
|
||||
|
|
@ -184,15 +186,15 @@ function drawSeconds() {
|
|||
|
||||
function draw() {
|
||||
// get date
|
||||
var d = new Date();
|
||||
var da = d.toString().split(" ");
|
||||
let d = new Date();
|
||||
let da = d.toString().split(" ");
|
||||
|
||||
// default draw styles
|
||||
g.reset().setBgColor(g.theme.bg).setFontAlign(0, 0);
|
||||
|
||||
// draw time
|
||||
var time = da[4].split(":");
|
||||
var hours = time[0],
|
||||
let time = da[4].split(":");
|
||||
let hours = time[0],
|
||||
minutes = time[1];
|
||||
|
||||
|
||||
|
|
@ -223,7 +225,7 @@ function draw() {
|
|||
// am / PM ?
|
||||
if (_12hour){
|
||||
//do 12 hour stuff
|
||||
//var ampm = require("locale").medidian(new Date()); Not working
|
||||
//let ampm = require("locale").medidian(new Date()); Not working
|
||||
g.setFont("Vector", 17);
|
||||
g.drawString(ampm, xyCenterSeconds, yAmPm, true);
|
||||
}
|
||||
|
|
@ -232,14 +234,14 @@ function draw() {
|
|||
|
||||
// draw Day, name of month, Date
|
||||
//DATE
|
||||
var localDate = require("locale").date(new Date(), 1);
|
||||
let localDate = require("locale").date(new Date(), 1);
|
||||
localDate = localDate.substring(0, localDate.length - 5);
|
||||
g.setFont("Vector", 17);
|
||||
g.drawString(require("locale").dow(new Date(), 1).toUpperCase() + ", " + localDate, xyCenter, yposDate, true);
|
||||
|
||||
g.setFont(font, primaryDateFontSize);
|
||||
// set gmt to UTC+0
|
||||
var gmt = new Date(d.getTime() + d.getTimezoneOffset() * 60 * 1000);
|
||||
let gmt = new Date(d.getTime() + d.getTimezoneOffset() * 60 * 1000);
|
||||
|
||||
// Loop through offset(s) and render
|
||||
offsets.forEach((offset, index) => {
|
||||
|
|
@ -249,7 +251,7 @@ function draw() {
|
|||
|
||||
|
||||
if (offsets.length === 1) {
|
||||
var date = [require("locale").dow(new Date(), 1), require("locale").date(new Date(), 1)];
|
||||
let date = [require("locale").dow(new Date(), 1), require("locale").date(new Date(), 1)];
|
||||
// For a single secondary timezone, draw it bigger and drop time zone to second line
|
||||
const xOffset = 30;
|
||||
g.setFont(font, secondaryTimeFontSize).drawString(`${hours}:${minutes}`, xyCenter, yposTime2, true);
|
||||
|
|
@ -295,8 +297,18 @@ g.clear();
|
|||
// Init the settings of the app
|
||||
loadMySettings();
|
||||
|
||||
// Show launcher when button pressed
|
||||
Bangle.setUI("clock");
|
||||
// Show launcher when middle button pressed
|
||||
Bangle.setUI({
|
||||
mode : "clock",
|
||||
remove : function() {
|
||||
// Called to unload all of the clock app
|
||||
if (PosInterval) clearInterval(PosInterval);
|
||||
PosInterval = undefined;
|
||||
if (drawTimeoutSeconds) clearTimeout(drawTimeoutSeconds);
|
||||
drawTimeoutSeconds = undefined;
|
||||
if (drawTimeout) clearTimeout(drawTimeout);
|
||||
drawTimeout = undefined;
|
||||
}});
|
||||
Bangle.loadWidgets();
|
||||
Bangle.drawWidgets();
|
||||
|
||||
|
|
@ -307,7 +319,7 @@ draw();
|
|||
|
||||
if (!Bangle.isLocked()) { // Initial state
|
||||
if (showSunInfo) {
|
||||
if (PosInterval != 0) clearInterval(PosInterval);
|
||||
if (PosInterval != 0 && typeof PosInterval != 'undefined') clearInterval(PosInterval);
|
||||
PosInterval = setInterval(updatePos, 60*10E3); // refesh every 10 mins
|
||||
updatePos();
|
||||
}
|
||||
|
|
@ -333,7 +345,7 @@ if (!Bangle.isLocked()) { // Initial state
|
|||
drawTimeout = undefined;
|
||||
|
||||
if (showSunInfo) {
|
||||
if (PosInterval != 0) clearInterval(PosInterval);
|
||||
if (PosInterval != 0 && typeof PosInterval != 'undefined') clearInterval(PosInterval);
|
||||
PosInterval = setInterval(updatePos, 60*60E3); // refesh every 60 mins
|
||||
updatePos();
|
||||
}
|
||||
|
|
@ -378,4 +390,5 @@ Bangle.on('lock',on=>{
|
|||
}
|
||||
draw(); // draw immediately, queue redraw
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
@ -2,7 +2,7 @@
|
|||
"id": "hworldclock",
|
||||
"name": "Hanks World Clock",
|
||||
"shortName": "Hanks World Clock",
|
||||
"version": "0.23",
|
||||
"version": "0.25",
|
||||
"description": "Current time zone plus up to three others",
|
||||
"allow_emulator":true,
|
||||
"icon": "app.png",
|
||||
|
|
|
|||
|
|
@ -12,3 +12,9 @@
|
|||
used Object.assing for the settings
|
||||
fix cache not deleted when "showClocks" options is changed
|
||||
added timeOut to return to the clock
|
||||
0.11: Cleanup timeout when changing to clock
|
||||
Reset timeout on swipe and drag
|
||||
0.12: Use Bangle.load and Bangle.showClock
|
||||
0.13: Fix automatic switch to clock
|
||||
0.14: Revert use of Bangle.load to classic load calls since widgets would
|
||||
still be loaded when they weren't supposed to.
|
||||
|
|
|
|||
|
|
@ -9,7 +9,6 @@
|
|||
timeOut:"Off"
|
||||
}, s.readJSON("iconlaunch.json", true) || {});
|
||||
|
||||
console.log(settings);
|
||||
if (!settings.fullscreen) {
|
||||
Bangle.loadWidgets();
|
||||
Bangle.drawWidgets();
|
||||
|
|
@ -133,6 +132,7 @@
|
|||
g.flip();
|
||||
const itemsN = Math.ceil(launchCache.apps.length / appsN);
|
||||
let onDrag = function(e) {
|
||||
updateTimeout();
|
||||
g.setColor(g.theme.fg);
|
||||
g.setBgColor(g.theme.bg);
|
||||
let dy = e.dy;
|
||||
|
|
@ -182,36 +182,27 @@
|
|||
drag: onDrag,
|
||||
touch: (_, e) => {
|
||||
if (e.y < R.y - 4) return;
|
||||
updateTimeout();
|
||||
let i = YtoIdx(e.y);
|
||||
selectItem(i, e);
|
||||
},
|
||||
swipe: (h,_) => { if(settings.swipeExit && h==1) { returnToClock(); } },
|
||||
swipe: (h,_) => { if(settings.swipeExit && h==1) { Bangle.showClock(); } },
|
||||
btn: _=> { if (settings.oneClickExit) Bangle.showClock(); },
|
||||
remove: function() {
|
||||
if (timeout) clearTimeout(timeout);
|
||||
}
|
||||
};
|
||||
|
||||
const returnToClock = function() {
|
||||
Bangle.setUI();
|
||||
delete launchCache;
|
||||
delete launchHash;
|
||||
delete drawItemAuto;
|
||||
delete drawText;
|
||||
delete selectItem;
|
||||
delete onDrag;
|
||||
delete drawItems;
|
||||
delete drawItem;
|
||||
delete returnToClock;
|
||||
delete idxToY;
|
||||
delete YtoIdx;
|
||||
delete settings;
|
||||
setTimeout(eval, 0, s.read(".bootcde"));
|
||||
};
|
||||
|
||||
|
||||
if (settings.oneClickExit) mode.btn = returnToClock;
|
||||
let timeout;
|
||||
const updateTimeout = function(){
|
||||
if (settings.timeOut!="Off"){
|
||||
let time=parseInt(settings.timeOut); //the "s" will be trimmed by the parseInt
|
||||
setTimeout(returnToClock,time*1000);
|
||||
}
|
||||
|
||||
if (timeout) clearTimeout(timeout);
|
||||
timeout = setTimeout(Bangle.showClock,time*1000);
|
||||
}
|
||||
};
|
||||
|
||||
updateTimeout();
|
||||
|
||||
Bangle.setUI(mode);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
"id": "iconlaunch",
|
||||
"name": "Icon Launcher",
|
||||
"shortName" : "Icon launcher",
|
||||
"version": "0.10",
|
||||
"version": "0.14",
|
||||
"icon": "app.png",
|
||||
"description": "A launcher inspired by smartphones, with an icon-only scrollable menu.",
|
||||
"tags": "tool,system,launcher",
|
||||
|
|
|
|||
|
|
@ -16,3 +16,5 @@
|
|||
Fix colorsetting in promises in generated code
|
||||
Some performance improvements by caching lookups
|
||||
Activate UI after first draw is complete to prevent drawing over launcher
|
||||
0.13: Use widget_utils swipeOn()
|
||||
Allows minification by combining all but picture data into one file
|
||||
|
|
|
|||
|
|
@ -1,7 +1,12 @@
|
|||
let unlockedDrawInterval = [];
|
||||
let lockedDrawInterval = [];
|
||||
let showWidgets = false;
|
||||
let firstDraw = true;
|
||||
let s = {};
|
||||
// unlocked draw intervals
|
||||
s.udi = [];
|
||||
// locked draw intervals
|
||||
s.ldi = [];
|
||||
// full draw
|
||||
s.fd = true;
|
||||
// performance log
|
||||
s.pl = {};
|
||||
|
||||
{
|
||||
let x = g.getWidth()/2;
|
||||
|
|
@ -21,12 +26,10 @@ let firstDraw = true;
|
|||
let precompiledJs = eval(require("Storage").read("imageclock.draw.js"));
|
||||
let settings = require('Storage').readJSON("imageclock.json", true) || {};
|
||||
|
||||
let performanceLog = {};
|
||||
|
||||
let startPerfLog = () => {};
|
||||
let endPerfLog = () => {};
|
||||
Bangle.printPerfLog = () => {print("Deactivated");};
|
||||
Bangle.resetPerfLog = () => {performanceLog = {};};
|
||||
Bangle.resetPerfLog = () => {s.pl = {};};
|
||||
|
||||
let colormap={
|
||||
"#000":0,
|
||||
|
|
@ -64,35 +67,37 @@ let firstDraw = true;
|
|||
if (settings.perflog){
|
||||
startPerfLog = function(name){
|
||||
let time = getTime();
|
||||
if (!performanceLog.start) performanceLog.start={};
|
||||
performanceLog.start[name] = time;
|
||||
if (!s.pl.start) s.pl.start={};
|
||||
s.pl.start[name] = time;
|
||||
};
|
||||
endPerfLog = function (name){
|
||||
endPerfLog = function (name, once){
|
||||
let time = getTime();
|
||||
if (!performanceLog.last) performanceLog.last={};
|
||||
let duration = time - performanceLog.start[name];
|
||||
performanceLog.last[name] = duration;
|
||||
if (!performanceLog.cum) performanceLog.cum={};
|
||||
if (!performanceLog.cum[name]) performanceLog.cum[name] = 0;
|
||||
performanceLog.cum[name] += duration;
|
||||
if (!performanceLog.count) performanceLog.count={};
|
||||
if (!performanceLog.count[name]) performanceLog.count[name] = 0;
|
||||
performanceLog.count[name]++;
|
||||
if (!s.pl.start[name]) return;
|
||||
if (!s.pl.last) s.pl.last={};
|
||||
let duration = time - s.pl.start[name];
|
||||
s.pl.last[name] = duration;
|
||||
if (!s.pl.cum) s.pl.cum={};
|
||||
if (!s.pl.cum[name]) s.pl.cum[name] = 0;
|
||||
s.pl.cum[name] += duration;
|
||||
if (!s.pl.count) s.pl.count={};
|
||||
if (!s.pl.count[name]) s.pl.count[name] = 0;
|
||||
s.pl.count[name]++;
|
||||
if (once){s.pl.start[name] = undefined}
|
||||
};
|
||||
|
||||
Bangle.printPerfLog = function(){
|
||||
let result = "";
|
||||
let keys = [];
|
||||
for (let c in performanceLog.cum){
|
||||
for (let c in s.pl.cum){
|
||||
keys.push(c);
|
||||
}
|
||||
keys.sort();
|
||||
for (let k of keys){
|
||||
print(k, "last:", (performanceLog.last[k] * 1000).toFixed(0), "average:", (performanceLog.cum[k]/performanceLog.count[k]*1000).toFixed(0), "count:", performanceLog.count[k], "total:", (performanceLog.cum[k] * 1000).toFixed(0));
|
||||
print(k, "last:", (s.pl.last[k] * 1000).toFixed(0), "average:", (s.pl.cum[k]/s.pl.count[k]*1000).toFixed(0), "count:", s.pl.count[k], "total:", (s.pl.cum[k] * 1000).toFixed(0));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
startPerfLog("fullDraw");
|
||||
startPerfLog("loadFunctions");
|
||||
|
||||
let delayTimeouts = {};
|
||||
|
|
@ -609,15 +614,22 @@ let firstDraw = true;
|
|||
|
||||
promise.then(()=>{
|
||||
let currentDrawingTime = Date.now();
|
||||
if (showWidgets){
|
||||
restoreWidgetDraw();
|
||||
}
|
||||
lastDrawTime = Date.now() - start;
|
||||
isDrawing=false;
|
||||
firstDraw=false;
|
||||
s.fd=false;
|
||||
requestRefresh = false;
|
||||
endPerfLog("initialDraw");
|
||||
if (!Bangle.uiRemove) setUi();
|
||||
endPerfLog("fullDraw", true);
|
||||
|
||||
if (!Bangle.uiRemove){
|
||||
setUi();
|
||||
let orig = Bangle.drawWidgets;
|
||||
Bangle.drawWidgets = ()=>{};
|
||||
Bangle.loadWidgets();
|
||||
Bangle.drawWidgets = orig;
|
||||
require("widget_utils").swipeOn();
|
||||
Bangle.drawWidgets();
|
||||
}
|
||||
}).catch((e)=>{
|
||||
print("Error during drawing", e);
|
||||
});
|
||||
|
|
@ -701,16 +713,16 @@ let firstDraw = true;
|
|||
|
||||
let handleLock = function(isLocked, forceRedraw){
|
||||
//print("isLocked", Bangle.isLocked());
|
||||
for (let i of unlockedDrawInterval){
|
||||
for (let i of s.udi){
|
||||
//print("Clearing unlocked", i);
|
||||
clearInterval(i);
|
||||
}
|
||||
for (let i of lockedDrawInterval){
|
||||
for (let i of s.ldi){
|
||||
//print("Clearing locked", i);
|
||||
clearInterval(i);
|
||||
}
|
||||
unlockedDrawInterval = [];
|
||||
lockedDrawInterval = [];
|
||||
s.udi = [];
|
||||
s.ldi = [];
|
||||
|
||||
if (!isLocked){
|
||||
if (forceRedraw || !redrawEvents || (redrawEvents.includes("unlock"))){
|
||||
|
|
@ -726,7 +738,7 @@ let firstDraw = true;
|
|||
initialDraw(watchfaceResources, watchface);
|
||||
},unlockedRedraw, (v)=>{
|
||||
//print("New matched unlocked interval", v);
|
||||
unlockedDrawInterval.push(v);
|
||||
s.udi.push(v);
|
||||
}, lastDrawTime);
|
||||
if (!events || events.includes("HRM")) Bangle.setHRMPower(1, "imageclock");
|
||||
if (!events || events.includes("pressure")) Bangle.setBarometerPower(1, 'imageclock');
|
||||
|
|
@ -744,43 +756,13 @@ let firstDraw = true;
|
|||
initialDraw(watchfaceResources, watchface);
|
||||
},lockedRedraw, (v)=>{
|
||||
//print("New matched locked interval", v);
|
||||
lockedDrawInterval.push(v);
|
||||
s.ldi.push(v);
|
||||
}, lastDrawTime);
|
||||
Bangle.setHRMPower(0, "imageclock");
|
||||
Bangle.setBarometerPower(0, 'imageclock');
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
let showWidgetsChanged = false;
|
||||
|
||||
let restoreWidgetDraw = function(){
|
||||
require("widget_utils").show();
|
||||
Bangle.drawWidgets();
|
||||
};
|
||||
|
||||
let handleSwipe = function(lr, ud){
|
||||
if (!showWidgets && ud == 1){
|
||||
//print("Enable widgets");
|
||||
restoreWidgetDraw();
|
||||
showWidgetsChanged = true;
|
||||
}
|
||||
if (showWidgets && ud == -1){
|
||||
//print("Disable widgets");
|
||||
clearWidgetsDraw();
|
||||
firstDraw = true;
|
||||
showWidgetsChanged = true;
|
||||
}
|
||||
if (showWidgetsChanged){
|
||||
showWidgetsChanged = false;
|
||||
//print("Draw after widget change");
|
||||
showWidgets = ud == 1;
|
||||
initialDraw();
|
||||
}
|
||||
};
|
||||
|
||||
Bangle.on('swipe', handleSwipe);
|
||||
|
||||
if (!events || events.includes("pressure")){
|
||||
Bangle.on('pressure', handlePressure);
|
||||
try{
|
||||
|
|
@ -799,14 +781,6 @@ let firstDraw = true;
|
|||
if (!events || events.includes("charging")) {
|
||||
Bangle.on('charging', handleCharging);
|
||||
}
|
||||
|
||||
let originalWidgetDraw = {};
|
||||
let originalWidgetArea = {};
|
||||
|
||||
let clearWidgetsDraw = function(){
|
||||
//print("Clear widget draw calls");
|
||||
require("widget_utils").hide();
|
||||
}
|
||||
|
||||
handleLock(Bangle.isLocked(), true);
|
||||
|
||||
|
|
@ -819,7 +793,6 @@ let firstDraw = true;
|
|||
Bangle.setHRMPower(0, "imageclock");
|
||||
Bangle.setBarometerPower(0, 'imageclock');
|
||||
|
||||
Bangle.removeListener('swipe', handleSwipe);
|
||||
Bangle.removeListener('lock', handleLock);
|
||||
Bangle.removeListener('charging', handleCharging);
|
||||
Bangle.removeListener('HRM', handleHrm);
|
||||
|
|
@ -829,31 +802,22 @@ let firstDraw = true;
|
|||
if (initialDrawTimeoutUnlocked) clearTimeout(initialDrawTimeoutUnlocked);
|
||||
if (initialDrawTimeoutLocked) clearTimeout(initialDrawTimeoutLocked);
|
||||
|
||||
for (let i of global.unlockedDrawInterval){
|
||||
for (let i of global.s.udi){
|
||||
//print("Clearing unlocked", i);
|
||||
clearInterval(i);
|
||||
}
|
||||
delete global.unlockedDrawInterval;
|
||||
for (let i of global.lockedDrawInterval){
|
||||
for (let i of global.s.ldi){
|
||||
//print("Clearing locked", i);
|
||||
clearInterval(i);
|
||||
}
|
||||
delete global.lockedDrawInterval;
|
||||
delete global.showWidgets;
|
||||
delete global.firstDraw;
|
||||
|
||||
delete Bangle.printPerfLog;
|
||||
if (settings.perflog){
|
||||
delete Bangle.resetPerfLog;
|
||||
delete performanceLog;
|
||||
}
|
||||
|
||||
cleanupDelays();
|
||||
restoreWidgetDraw();
|
||||
require("widget_utils").show();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
Bangle.loadWidgets();
|
||||
clearWidgetsDraw();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,8 +4,8 @@
|
|||
</head>
|
||||
<body>
|
||||
|
||||
<script src="../../core/lib/heatshrink.js"></script>
|
||||
<script src="../../core/lib/imageconverter.js"></script>
|
||||
<script src="../../webtools/heatshrink.js"></script>
|
||||
<script src="../../webtools/imageconverter.js"></script>
|
||||
<script src="../../core/lib/customize.js"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/jszip/3.7.1/jszip.js"></script>
|
||||
|
||||
|
|
@ -25,6 +25,8 @@
|
|||
<label for="timeoutwrap">Wrap draw calls in timeouts (Slower, more RAM use, better interactivity)</label></br>
|
||||
<input type="checkbox" id="forceOrigPlane" name="mode" disabled="true"/>
|
||||
<label for="forceOrigPlane">Force use of direct drawing (Even faster, but will produce visible artifacts on not optimized watch faces)</label></br>
|
||||
<input type="checkbox" id="separateFiles" name="mode"/>
|
||||
<label for="separateFiles">Do not create combined app flle (slower but more flexible for debugging, incompatible with minification)</label></br>
|
||||
<input type="checkbox" id="debugprints" name="mode"/>
|
||||
<label for="debugprints">Add debug prints to generated code</label></br>
|
||||
</p>
|
||||
|
|
@ -32,7 +34,7 @@
|
|||
<p>Select watchface folder:</br><input type="file" id="fileLoader" name="files[]" multiple directory="" webkitdirectory="" moxdirectory="" /></p>
|
||||
<p><b>or</b></p>
|
||||
<p>Select watchface zip file: </br><input type="file" id="zipLoader" name="zip"/></p><br/>
|
||||
|
||||
|
||||
<button id="btnUpload" class="btn btn-primary">Upload to watch</button></br>
|
||||
<button id="btnSave" class="btn btn-secondary">Save resources file</button></br>
|
||||
<button id="btnSaveFace" class="btn btn-secondary">Save face file</button></br>
|
||||
|
|
@ -55,15 +57,15 @@
|
|||
var expectedFiles = 0;
|
||||
var rootZip = new JSZip();
|
||||
var resourcesZip = rootZip.folder("resources");
|
||||
|
||||
|
||||
function isNativeFormat(){
|
||||
return document.getElementById("useNative").checked;
|
||||
}
|
||||
|
||||
|
||||
function addDebug(){
|
||||
return document.getElementById("debugprints").checked;
|
||||
}
|
||||
|
||||
|
||||
function convertAmazfitTime(time){
|
||||
var result = {};
|
||||
if (time.Hours){
|
||||
|
|
@ -88,7 +90,7 @@
|
|||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
function convertAmazfitDate(date){
|
||||
var result = {};
|
||||
if (date.MonthAndDay.Separate.Day) result.Day = convertAmazfitNumber(date.MonthAndDay.Separate.Day, "Day");
|
||||
|
|
@ -98,11 +100,11 @@
|
|||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
var filesToMove={};
|
||||
|
||||
|
||||
var zipChangePromise = Promise.resolve();
|
||||
|
||||
|
||||
function performFileChanges(){
|
||||
var promise = Promise.resolve();
|
||||
//rename all files to just numbers without leading zeroes
|
||||
|
|
@ -111,7 +113,7 @@
|
|||
var tmp = resultJson[c];
|
||||
delete resultJson[c];
|
||||
resultJson[Number(c)] = tmp;
|
||||
|
||||
|
||||
async function modZip(c){
|
||||
console.log("Async modification of ", c)
|
||||
var fileRegex = new RegExp(c + ".*");
|
||||
|
|
@ -120,27 +122,27 @@
|
|||
console.log("Filedata is", fileData);
|
||||
var extension = resourcesZip.file(fileRegex)[0].name.match(/\.[^.]*$/);
|
||||
var newName = Number(c) + extension;
|
||||
|
||||
|
||||
console.log("Renaming to", newName);
|
||||
resourcesZip.remove(c + extension);
|
||||
resourcesZip.file(newName, fileData);
|
||||
}
|
||||
promise = promise.then(modZip(c));
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
console.log("File moves:", filesToMove);
|
||||
|
||||
|
||||
for (var c in filesToMove){
|
||||
var tmp = resultJson[c];
|
||||
console.log("Handle filemove", c, filesToMove[c], tmp);
|
||||
|
||||
|
||||
var element = resultJson;
|
||||
var path = filesToMove[c];
|
||||
|
||||
|
||||
|
||||
|
||||
async function modZip(c){
|
||||
console.log("Async modification of ", c)
|
||||
var fileRegex = new RegExp(c + ".*");
|
||||
|
|
@ -149,13 +151,13 @@
|
|||
console.log("Filedata is", fileData);
|
||||
var extension = resourcesZip.file(fileRegex)[0].name.match(/\.[^.]*$/);
|
||||
var newName = Number(c) + extension;
|
||||
|
||||
|
||||
console.log("Copying to", newName);
|
||||
resourcesZip.file(filesToMove[c].join("/") + extension, fileData);
|
||||
}
|
||||
promise = promise.then(modZip(c));
|
||||
|
||||
|
||||
|
||||
|
||||
for (var i = 0; i< path.length; i++){
|
||||
if (!element[path[i]]) element[path[i]] = {};
|
||||
if (i == path.length - 1){
|
||||
|
|
@ -164,7 +166,7 @@
|
|||
element = element[path[i]];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
promise.then(()=>{
|
||||
document.getElementById('btnUpload').disabled = true;
|
||||
|
|
@ -172,7 +174,7 @@
|
|||
console.log("After moves", resultJson);
|
||||
return promise;
|
||||
};
|
||||
|
||||
|
||||
function convertAmazfitMultistate(multistate, value, minValue, maxValue){
|
||||
var result = {
|
||||
MultiState: {
|
||||
|
|
@ -188,18 +190,18 @@
|
|||
if (multistate.ImageIndexOff) filesToMove[multistate.ImageIndexOff] = ["status", value, "off"];
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
function convertAmazfitStatus(status){
|
||||
var result = {};
|
||||
|
||||
|
||||
if (status.Alarm) result.Alarm = convertAmazfitMultistate(status.Alarm,"Alarm");
|
||||
if (status.Bluetooth) result.Bluetooth = convertAmazfitMultistate(status.Bluetooth,"Bluetooth");
|
||||
if (status.DoNotDisturb) result.DoNotDisturb = convertAmazfitMultistate(status.DoNotDisturb,"Notifications");
|
||||
if (status.Lock) result.Lock = convertAmazfitMultistate(status.Lock,"Lock");
|
||||
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
function convertAmazfitNumber(element, value, minValue, maxValue){
|
||||
var number = {};
|
||||
var result = {
|
||||
|
|
@ -233,10 +235,10 @@
|
|||
if (maxValue !== undefined) number.MinValue = minValue;
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
function moveWeatherIcons(icon){
|
||||
filesToMove[icon.ImageIndex + 0] = ["weather", "fallback"];
|
||||
|
||||
|
||||
// Light clouds
|
||||
filesToMove[icon.ImageIndex + 1] = ["weather", 801];
|
||||
// Cloudy, possible rain
|
||||
|
|
@ -282,7 +284,7 @@
|
|||
// Very heavy shower
|
||||
filesToMove[icon.ImageIndex + 22] = ["weather", 531];
|
||||
}
|
||||
|
||||
|
||||
function convertAmazfitTemperature(temp){
|
||||
var result = {};
|
||||
result = convertAmazfitNumber(temp.Number, "WeatherTemperature");
|
||||
|
|
@ -294,15 +296,15 @@
|
|||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
function convertAmazfitWeather(weather){
|
||||
var result = {};
|
||||
|
||||
|
||||
if (weather.Temperature && weather.Temperature.Current){
|
||||
if (!result.Temperature) result.Temperature = {};
|
||||
result.Temperature.Current = convertAmazfitTemperature(weather.Temperature.Current);
|
||||
}
|
||||
|
||||
|
||||
if (weather.Temperature && weather.Temperature.Today){
|
||||
if (!result.Temperature) result.Temperature = {};
|
||||
if (weather.Temperature.Today.Separate){
|
||||
|
|
@ -327,10 +329,10 @@
|
|||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
function convertAmazfitActivity(activity){
|
||||
var result = {};
|
||||
|
||||
|
||||
if (activity.Steps){
|
||||
result.Steps = convertAmazfitNumber(activity.Steps, "Steps");
|
||||
}
|
||||
|
|
@ -339,7 +341,7 @@
|
|||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
function convertAmazfitScale(scale, value, minValue, maxValue){
|
||||
var result = {};
|
||||
result.Scale = {
|
||||
|
|
@ -356,10 +358,10 @@
|
|||
Y: c.Y
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
function convertAmazfitStepsProgress(steps){
|
||||
var result = {};
|
||||
if (steps.GoalImage){
|
||||
|
|
@ -378,7 +380,7 @@
|
|||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
function convertAmazfitBattery(battery){
|
||||
var result = {};
|
||||
if (battery.Scale){
|
||||
|
|
@ -389,7 +391,7 @@
|
|||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
function convertAmazfitImage(image){
|
||||
var result = {
|
||||
Image: {
|
||||
|
|
@ -401,11 +403,11 @@
|
|||
};
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
function convertAmazfitColor(color){
|
||||
return "#" + color.substring(2);
|
||||
}
|
||||
|
||||
|
||||
function convertAmazfitHand(hand, rotationValue, minRotationValue, maxRotationValue){
|
||||
var result = {
|
||||
Filled: !hand.OnlyBorder,
|
||||
|
|
@ -418,18 +420,18 @@
|
|||
MaxRotationValue: maxRotationValue,
|
||||
MinRotationValue: minRotationValue
|
||||
};
|
||||
|
||||
|
||||
result.Vertices = []
|
||||
for (var c of hand.Shape){
|
||||
result.Vertices.push(c);
|
||||
}
|
||||
return { Poly: result };
|
||||
}
|
||||
|
||||
|
||||
function convertAmazfitAnalog(analog, face){
|
||||
var result = {
|
||||
};
|
||||
|
||||
|
||||
if (analog.Hours){
|
||||
result.Hours = {};
|
||||
result.Hours.Hand = convertAmazfitHand(analog.Hours, "Hour12Analog", 0, 12);
|
||||
|
|
@ -464,14 +466,14 @@
|
|||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
function restructureAmazfitFormat(dataString){
|
||||
console.log("Amazfit data:", dataString);
|
||||
|
||||
|
||||
|
||||
|
||||
var json = JSON.parse(dataString);
|
||||
faceJson = json;
|
||||
|
||||
|
||||
|
||||
var result = {};
|
||||
result.Properties = {};
|
||||
|
|
@ -479,8 +481,8 @@
|
|||
result.Properties.Redraw.Unlocked = 60000;
|
||||
result.Properties.Redraw.Locked = 60000;
|
||||
result.Properties.Redraw.Clear = true;
|
||||
|
||||
|
||||
|
||||
|
||||
if (json.Background){
|
||||
result.Background = json.Background;
|
||||
result.Background.Image.ImagePath = [];
|
||||
|
|
@ -491,32 +493,32 @@
|
|||
result.Time = convertAmazfitTime(json.Time);
|
||||
if (json.AnalogDialFace) result.Time.Plane = 1;
|
||||
}
|
||||
|
||||
|
||||
if (json.Date){
|
||||
result.Date = convertAmazfitDate(json.Date);
|
||||
if (json.AnalogDialFace) result.Date.Plane = 1;
|
||||
}
|
||||
|
||||
|
||||
if (json.Status){
|
||||
result.Status = convertAmazfitStatus(json.Status);
|
||||
if (json.AnalogDialFace) result.Status.Plane = 1;
|
||||
}
|
||||
|
||||
|
||||
if (json.Weather){
|
||||
result.Weather = convertAmazfitWeather(json.Weather);
|
||||
if (json.AnalogDialFace) result.Weather.Plane = 1;
|
||||
}
|
||||
|
||||
|
||||
if (json.Activity){
|
||||
result.Activity = convertAmazfitActivity(json.Activity);
|
||||
if (json.AnalogDialFace) result.Activity.Plane = 1;
|
||||
}
|
||||
|
||||
|
||||
if (json.StepsProgress){
|
||||
result.StepsProgress = convertAmazfitStepsProgress(json.StepsProgress);
|
||||
if (json.AnalogDialFace) result.StepsProgress.Plane = 1;
|
||||
}
|
||||
|
||||
|
||||
if (json.Battery){
|
||||
result.Battery = convertAmazfitBattery(json.Battery);
|
||||
if (json.AnalogDialFace) result.Battery.Plane = 1;
|
||||
|
|
@ -529,7 +531,7 @@
|
|||
return result;
|
||||
|
||||
}
|
||||
|
||||
|
||||
function parseFaceJson(jsonString){
|
||||
if (isNativeFormat()){
|
||||
return JSON.parse(jsonString);
|
||||
|
|
@ -537,7 +539,7 @@
|
|||
return restructureAmazfitFormat(jsonString);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function combineProperty(name, source, target){
|
||||
if (source[name] && target[name]){
|
||||
if (Array.isArray(target[name])){
|
||||
|
|
@ -556,7 +558,7 @@
|
|||
if (typeof element == "string" || typeof element == "number") return [];
|
||||
for (var c in element){
|
||||
var next = element[c];
|
||||
|
||||
|
||||
combineProperty("X",element,next);
|
||||
combineProperty("Y",element,next);
|
||||
combineProperty("Width",element,next);
|
||||
|
|
@ -571,7 +573,7 @@
|
|||
combineProperty("MaxRotationValue",element,next);
|
||||
if (typeof element.Plane == "number") next.Plane = element.Plane;
|
||||
next.Layer = element.Layer ? (element.Layer) : "" + c;
|
||||
|
||||
|
||||
if (["MultiState","Image","CodedImage","Number","Circle","Poly","Rect","Scale"].includes(c)){
|
||||
result.push({type:c, value: next});
|
||||
} else {
|
||||
|
|
@ -580,12 +582,12 @@
|
|||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
function convertToCode(elements, properties, wrapInTimeouts, forceUseOrigPlane){
|
||||
var code = "(function (wr, wf) {\n";
|
||||
code += "var lc;\n";
|
||||
code += "var p = Promise.resolve();\n";
|
||||
|
||||
|
||||
//get mapped by layer
|
||||
var counter = 0;
|
||||
var planes = {};
|
||||
|
|
@ -606,20 +608,20 @@
|
|||
}
|
||||
if (!planeNumbers.includes(0)) planeNumbers.push(0);
|
||||
planeNumbers.sort().reverse();
|
||||
|
||||
|
||||
console.log("Found planes", planes, "with numbers", planeNumbers)
|
||||
|
||||
|
||||
code += "p0 = g;\n";
|
||||
|
||||
|
||||
for (var planeIndex = 0; planeIndex < planeNumbers.length; planeIndex++){
|
||||
var layers = planes[planeNumbers[planeIndex]];
|
||||
var plane = planeNumbers[planeIndex];
|
||||
|
||||
|
||||
var lastSetColor;
|
||||
var lastSetBgColor;
|
||||
|
||||
|
||||
if (plane != 0) code += "if (!p" + plane + ") p" + plane + " = Graphics.createArrayBuffer(g.getWidth(),g.getHeight(),4,{msb:true});\n";
|
||||
|
||||
|
||||
if (properties.Redraw && properties.Redraw.Clear){
|
||||
if (wrapInTimeouts && (plane != 0 || forceUseOrigPlane)){
|
||||
code += "p = p.then(()=>delay(0)).then(()=>{\n";
|
||||
|
|
@ -632,31 +634,31 @@
|
|||
code += 'endPerfLog("initialDraw_g.clear");'+ "\n";
|
||||
code += "});\n";
|
||||
}
|
||||
|
||||
|
||||
var previousPlane = plane + 1;
|
||||
if (previousPlane < planeNumbers.length){
|
||||
code += "p = p.then(()=>{\n";
|
||||
|
||||
|
||||
if (addDebug()) code += 'print("Copying of plane ' + previousPlane + ' to display");'+"\n";
|
||||
//code += "g.drawImage(p" + i + ".asImage());";
|
||||
code += "p0.drawImage({width: p" + previousPlane + ".getWidth(), height: p" + previousPlane + ".getHeight(), bpp: p" + previousPlane + ".getBPP(), buffer: p" + previousPlane + ".buffer, palette: palette});\n";
|
||||
code += "});\n";
|
||||
}
|
||||
|
||||
|
||||
console.log("Got layers", layers);
|
||||
for (var layername in layers){
|
||||
var layerElements = layers[layername];
|
||||
|
||||
|
||||
console.log("Layer elements", layername, layerElements);
|
||||
//code for whole layer
|
||||
|
||||
|
||||
if (addDebug()) code += 'print("Starting layer ' + layername + '");' + "\n";
|
||||
|
||||
|
||||
var checkForLayerChange = false;
|
||||
var checkcode = "";
|
||||
|
||||
|
||||
if (!(properties.Redraw && properties.Redraw.Clear)){
|
||||
checkcode = 'firstDraw';
|
||||
checkcode = 's.fd';
|
||||
for (var i = 0; i< layerElements.length; i++){
|
||||
var layerElement = layerElements[i];
|
||||
var referencedElement = elements[layerElements[i].index];
|
||||
|
|
@ -664,38 +666,38 @@
|
|||
console.log("Check for change:", layerElement, referencedElement);
|
||||
if (layerElement.element.Value){
|
||||
if (elementType == "MultiState" && layerElement.element.Value) {
|
||||
checkcode += '| isChangedMultistate(wf.Collapsed[' + layerElement.index + '].value)';
|
||||
checkcode += '| isChangedMultistate(wf.c[' + layerElement.index + '].value)';
|
||||
} else {
|
||||
checkcode += '| isChangedNumber(wf.Collapsed[' + layerElement.index + '].value)';
|
||||
checkcode += '| isChangedNumber(wf.c[' + layerElement.index + '].value)';
|
||||
}
|
||||
checkForLayerChange = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
//code for elements
|
||||
for (var i = 0; i< layerElements.length; i++){
|
||||
var elementIndex = layerElements[i].index;
|
||||
var c = elements[elementIndex];
|
||||
console.log("convert to code", c);
|
||||
|
||||
|
||||
var condition = "";
|
||||
if (checkcode.length > 0 && checkForLayerChange){
|
||||
if (condition.length > 0) condition += " && ";
|
||||
condition = '(' + checkcode + ')';
|
||||
}
|
||||
|
||||
|
||||
if (c.value.HideOn && c.value.HideOn.includes("Lock")){
|
||||
if (condition.length > 0) condition += " && ";
|
||||
condition = '!Bangle.isLocked()';
|
||||
}
|
||||
|
||||
|
||||
if (c.value.Type == "Once"){
|
||||
if (condition.length > 0) condition += " && ";
|
||||
condition += "firstDraw";
|
||||
condition += "s.fd";
|
||||
}
|
||||
|
||||
|
||||
var planeName = "p" + plane;
|
||||
var colorsetting = "";
|
||||
if (c.value.ForegroundColor && lastSetColor != c.value.ForegroundColor){
|
||||
|
|
@ -712,7 +714,7 @@
|
|||
else
|
||||
colorsetting += planeName + ".setBgColor(\"" + c.value.BackgroundColor + "\");\n";
|
||||
}
|
||||
|
||||
|
||||
if (addDebug()) code += 'print("Element condition is ' + condition + '");' + "\n";
|
||||
code += (condition.length > 0 ? "if (" + condition + "){\n" : "");
|
||||
if (wrapInTimeouts && (plane != 0 || forceUseOrigPlane)){
|
||||
|
|
@ -722,18 +724,18 @@
|
|||
}
|
||||
code += "" + colorsetting;
|
||||
if (addDebug()) code += 'print("Drawing element ' + elementIndex + ' with type ' + c.type + ' on plane ' + planeName + '");' + "\n";
|
||||
code += "draw" + c.type + "(" + planeName + ", wr, wf.Collapsed[" + elementIndex + "].value);\n";
|
||||
code += "draw" + c.type + "(" + planeName + ", wr, wf.c[" + elementIndex + "].value);\n";
|
||||
|
||||
code += "});\n";
|
||||
code += (condition.length > 0 ? "}\n" : "");
|
||||
}
|
||||
}
|
||||
console.log("Current plane is", plane);
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
code += "return p;})";
|
||||
console.log("Code:", code);
|
||||
return code
|
||||
|
|
@ -742,14 +744,14 @@
|
|||
function postProcess(){
|
||||
moveData(resultJson);
|
||||
console.log("Created data file", resourceDataString, resourceDataOffset, resultJson);
|
||||
|
||||
|
||||
var properties = faceJson.Properties;
|
||||
faceJson = { Properties: properties, Collapsed: collapseTree(faceJson,{X:0,Y:0})};
|
||||
faceJson = { Properties: properties, c: collapseTree(faceJson,{X:0,Y:0})};
|
||||
console.log("After collapsing", faceJson);
|
||||
precompiledJs = convertToCode(faceJson.Collapsed, properties, document.getElementById('timeoutwrap').checked, document.getElementById('forceOrigPlane').checked);
|
||||
precompiledJs = convertToCode(faceJson.c, properties, document.getElementById('timeoutwrap').checked, document.getElementById('forceOrigPlane').checked);
|
||||
console.log("After precompiling", precompiledJs);
|
||||
}
|
||||
|
||||
|
||||
function convertJsToJson(imgstr){
|
||||
var E = {};
|
||||
E.toArrayBuffer = (s)=>s;
|
||||
|
|
@ -768,7 +770,7 @@
|
|||
|
||||
function imageLoaded() {
|
||||
var options = {};
|
||||
|
||||
|
||||
options.diffusion = infoJson.diffusion ? infoJson.diffusion : "none";
|
||||
options.compression = false;
|
||||
options.alphaToColor = false;
|
||||
|
|
@ -779,12 +781,12 @@
|
|||
options.contrast = 0;
|
||||
options.mode = infoJson.color ? infoJson.color : "1bit";
|
||||
options.output = "object";
|
||||
|
||||
|
||||
console.log("Loaded image has path", this.path);
|
||||
var jsonPath = this.path.split("/");
|
||||
|
||||
|
||||
var forcedTransparentColorMatch = jsonPath[jsonPath.length-1].match(/.*\.t([^.]+)\..*/)
|
||||
|
||||
|
||||
var forcedTransparentColor;
|
||||
if (jsonPath[jsonPath.length-1].includes(".t.")){
|
||||
options.transparent = true;
|
||||
|
|
@ -792,13 +794,13 @@
|
|||
options.transparent = false;
|
||||
forcedTransparentColor = forcedTransparentColorMatch[1];
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
console.log("image has transparency", options.transparent);
|
||||
console.log("image has forced transparent color", forcedTransparentColor);
|
||||
jsonPath[jsonPath.length-1] = jsonPath[jsonPath.length-1].replace(/([^.]*)\..*/, "$1");
|
||||
console.log("Loaded image has json path", jsonPath);
|
||||
|
||||
|
||||
var canvas = document.getElementById("canvas")
|
||||
canvas.width = this.width*2;
|
||||
canvas.height = this.height;
|
||||
|
|
@ -819,7 +821,7 @@
|
|||
imgstr = imageconverter.RGBAtoString(rgba, options);
|
||||
var outputImageData = new ImageData(options.rgbaOut, options.width, options.height);
|
||||
ctx.putImageData(outputImageData,this.width,0);
|
||||
|
||||
|
||||
imgstr = convertJsToJson(imgstr);
|
||||
|
||||
// checkerboard for transparency on original image
|
||||
|
|
@ -827,9 +829,9 @@
|
|||
imageconverter.RGBAtoCheckerboard(imageData.data, {width:this.width,height:this.height});
|
||||
ctx.putImageData(imageData,0,0);
|
||||
|
||||
|
||||
|
||||
var currentElement = resultJson;
|
||||
|
||||
|
||||
for (var i = 0; i < jsonPath.length; i++){
|
||||
if (i == jsonPath.length - 1){
|
||||
var resultingObject = JSON.parse(imgstr);
|
||||
|
|
@ -841,18 +843,18 @@
|
|||
currentElement = currentElement[jsonPath[i]];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
handledFiles++;
|
||||
console.log("Expected:", expectedFiles, " handled:", handledFiles);
|
||||
|
||||
|
||||
if (handledFiles == expectedFiles){
|
||||
if (!isNativeFormat()) {
|
||||
performFileChanges().then(()=>{
|
||||
postProcess();
|
||||
|
||||
|
||||
rootZip.file("face.json", JSON.stringify(faceJson, null, 2));
|
||||
rootZip.file("info.json", JSON.stringify(infoJson, null, 2));
|
||||
|
||||
|
||||
document.getElementById('btnSave').disabled = false;
|
||||
document.getElementById('btnSaveFace').disabled = false;
|
||||
document.getElementById('btnSaveZip').disabled = false;
|
||||
|
|
@ -860,21 +862,21 @@
|
|||
});
|
||||
} else {
|
||||
postProcess();
|
||||
|
||||
|
||||
document.getElementById('btnSave').disabled = false;
|
||||
document.getElementById('btnSaveFace').disabled = false;
|
||||
document.getElementById('btnUpload').disabled = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function handleWatchFace(infoFile, faceFile, resourceFiles){
|
||||
if (isNativeFormat()){
|
||||
var reader = new FileReader();
|
||||
reader.path = infoFile.webkitRelativePath;
|
||||
reader.onload = function(event) {
|
||||
infoJson = JSON.parse(reader.result);
|
||||
|
||||
|
||||
handleFaceJson(faceFile, resourceFiles);
|
||||
};
|
||||
reader.readAsText(infoFile);
|
||||
|
|
@ -883,18 +885,18 @@
|
|||
handleFaceJson(faceFile, resourceFiles);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function handleFaceJson(faceFile, resourceFiles){
|
||||
var reader = new FileReader();
|
||||
reader.path = faceFile.webkitRelativePath;
|
||||
reader.onload = function(event) {
|
||||
faceJson = parseFaceJson(reader.result);
|
||||
|
||||
|
||||
handleResourceFiles(resourceFiles);
|
||||
};
|
||||
reader.readAsText(faceFile);
|
||||
}
|
||||
|
||||
|
||||
function handleResourceFiles(files){
|
||||
for (var current of files){
|
||||
console.log('Handle resource file ', current);
|
||||
|
|
@ -917,25 +919,25 @@
|
|||
reader.readAsDataURL(current);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function handleFileSelect(event) {
|
||||
handledFiles = 0;
|
||||
expectedFiles = undefined;
|
||||
|
||||
|
||||
document.getElementById('btnSave').disabled = true;
|
||||
document.getElementById('btnSaveZip').disabled = true;
|
||||
document.getElementById('btnSaveFace').disabled = true;
|
||||
document.getElementById('btnUpload').disabled = true;
|
||||
|
||||
|
||||
console.log("File select event", event);
|
||||
if (event.target.files.length == 0) return;
|
||||
result = "";
|
||||
resultJson= {};
|
||||
|
||||
|
||||
var resourceFiles = [];
|
||||
var faceFile;
|
||||
var infoFile;
|
||||
|
||||
|
||||
for (var current of event.target.files){
|
||||
console.log('Handle file ', current);
|
||||
if (isNativeFormat()){
|
||||
|
|
@ -970,10 +972,10 @@
|
|||
}
|
||||
}
|
||||
handleWatchFace(infoFile, faceFile, resourceFiles);
|
||||
|
||||
|
||||
};
|
||||
document.getElementById('fileLoader').addEventListener('change', handleFileSelect, false);
|
||||
|
||||
|
||||
function moveData(json){
|
||||
console.log("MoveData for", json);
|
||||
for (var k in json){
|
||||
|
|
@ -997,11 +999,11 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
document.getElementById("timeoutwrap").addEventListener("click", function() {
|
||||
document.getElementById("forceOrigPlane").disabled = !document.getElementById("timeoutwrap").checked;
|
||||
});
|
||||
|
||||
|
||||
document.getElementById("btnSave").addEventListener("click", function() {
|
||||
var h = document.createElement('a');
|
||||
h.href = 'data:text/json;charset=utf-8,' + encodeURI(JSON.stringify(resultJson));
|
||||
|
|
@ -1010,25 +1012,48 @@
|
|||
h.click();
|
||||
});
|
||||
document.getElementById("btnUpload").addEventListener("click", function() {
|
||||
|
||||
|
||||
console.log("Fetching app");
|
||||
fetch('app.js').then((r) => {
|
||||
console.log("Got response", r);
|
||||
return r.text();
|
||||
}
|
||||
).then((imageclockSrc) => {
|
||||
console.log("Got src", imageclockSrc)
|
||||
|
||||
if (!document.getElementById('separateFiles').checked){
|
||||
if (precompiledJs.length > 0){
|
||||
const replacementString = 'eval(require("Storage").read("imageclock.draw.js"))';
|
||||
console.log("Can replace:", imageclockSrc.includes(replacementString));
|
||||
imageclockSrc = imageclockSrc.replace(replacementString, precompiledJs);
|
||||
}
|
||||
imageclockSrc = imageclockSrc.replace('require("Storage").readJSON("imageclock.face.json")', JSON.stringify(faceJson));
|
||||
imageclockSrc = imageclockSrc.replace('require("Storage").readJSON("imageclock.resources.json")', JSON.stringify(resultJson));
|
||||
}
|
||||
var appDef = {
|
||||
id : "imageclock",
|
||||
storage:[
|
||||
{name:"imageclock.app.js", url:"app.js"},
|
||||
{name:"imageclock.resources.json", content: JSON.stringify(resultJson)},
|
||||
{name:"imageclock.img", url:"app-icon.js", evaluate:true},
|
||||
]
|
||||
};
|
||||
if (document.getElementById('separateFiles').checked){
|
||||
appDef.storage.push({name:"imageclock.app.js", url:"app.js"});
|
||||
if (precompiledJs.length > 0){
|
||||
appDef.storage.push({name:"imageclock.draw.js", content:precompiledJs});
|
||||
}
|
||||
appDef.storage.push({name:"imageclock.face.json", content: JSON.stringify(faceJson)});
|
||||
appDef.storage.push({name:"imageclock.resources.json", content: JSON.stringify(resultJson)});
|
||||
} else {
|
||||
appDef.storage.push({name:"imageclock.app.js", url:"pleaseminifycontent.js", content:imageclockSrc});
|
||||
}
|
||||
if (resourceDataString.length > 0){
|
||||
appDef.storage.push({name:"imageclock.resources.data", content: resourceDataString});
|
||||
}
|
||||
appDef.storage.push({name:"imageclock.draw.js", content: precompiledJs.length > 0 ? precompiledJs : "//empty"});
|
||||
appDef.storage.push({name:"imageclock.face.json", content: JSON.stringify(faceJson)});
|
||||
|
||||
console.log("Uploading app:", appDef);
|
||||
sendCustomizedApp(appDef);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
|
||||
function handleZipSelect(evt) {
|
||||
|
||||
|
|
@ -1040,18 +1065,18 @@
|
|||
document.getElementById('btnSaveZip').disabled = true;
|
||||
document.getElementById('btnUpload').disabled = true;
|
||||
JSZip.loadAsync(f).then(function(zip) {
|
||||
|
||||
|
||||
console.log("Zip loaded", zip);
|
||||
result = "";
|
||||
resultJson= {};
|
||||
|
||||
|
||||
var resourceFiles = [];
|
||||
|
||||
|
||||
var promise = zip.file("face.json").async("string").then((data)=>{
|
||||
console.log("face.json data", data);
|
||||
faceJson = parseFaceJson(data);
|
||||
});
|
||||
|
||||
|
||||
if (isNativeFormat()){
|
||||
promise = promise.then(zip.file("info.json").async("string").then((data)=>{
|
||||
console.log("info.json data", data);
|
||||
|
|
@ -1062,12 +1087,12 @@
|
|||
"color": "3bit",
|
||||
"transparent": true
|
||||
};
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
zip.folder("resources").forEach(function (relativePath, file){
|
||||
console.log("iterating over", relativePath);
|
||||
|
||||
|
||||
if (!file.dir){
|
||||
expectedFiles++;
|
||||
promise = promise.then(file.async("blob").then(function (blob) {
|
||||
|
|
@ -1083,10 +1108,10 @@
|
|||
reader.readAsDataURL(blob);
|
||||
}));
|
||||
}
|
||||
|
||||
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
||||
}, function (e) {
|
||||
console.log("Error reading " + f.name + ": " + e.message);
|
||||
});
|
||||
|
|
@ -1095,11 +1120,11 @@
|
|||
|
||||
console.log("Zip select event", evt);
|
||||
var files = evt.target.files;
|
||||
|
||||
|
||||
if (files.length > 1){
|
||||
alert("Only one file allowed");
|
||||
}
|
||||
|
||||
|
||||
handleFile(files[0]);
|
||||
}
|
||||
|
||||
|
|
@ -1113,7 +1138,7 @@
|
|||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
document.getElementById("btnSaveFace").addEventListener("click", function() {
|
||||
var h = document.createElement('a');
|
||||
h.href = 'data:text/json;charset=utf-8,' + encodeURI(JSON.stringify(faceJson));
|
||||
|
|
@ -1121,14 +1146,14 @@
|
|||
h.download = "face.json";
|
||||
h.click();
|
||||
});
|
||||
|
||||
|
||||
document.getElementById('zipLoader').addEventListener('change', handleZipSelect, false);
|
||||
document.getElementById('btnSaveZip').addEventListener('click', handleZipExport, false);
|
||||
document.getElementById('btnSave').disabled = true;
|
||||
document.getElementById('btnSaveFace').disabled = true;
|
||||
document.getElementById('btnSaveZip').disabled = true;
|
||||
document.getElementById('btnUpload').disabled = true;
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
</body>
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
"id": "imageclock",
|
||||
"name": "Imageclock",
|
||||
"shortName": "Imageclock",
|
||||
"version": "0.12",
|
||||
"version": "0.13",
|
||||
"type": "clock",
|
||||
"description": "BETA!!! File formats still subject to change --- This app is a highly customizable watchface. To use it, you need to select a watchface. You can build the watchfaces yourself without programming anything. All you need to do is write some json and create image files.",
|
||||
"icon": "app.png",
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@
|
|||
</div>
|
||||
|
||||
<script src="../../core/lib/customize.js"></script>
|
||||
<script src="../../core/lib/imageconverter.js"></script>
|
||||
<script src="../../webtools/imageconverter.js"></script>
|
||||
|
||||
<script>
|
||||
var faces = [];
|
||||
|
|
|
|||
|
|
@ -8,3 +8,4 @@
|
|||
0.08: Added more app identifiers, added 'cannot display' in case a message goes empty because of replacements
|
||||
0.09: Enable 'ams' on new firmwares (ams/ancs can now be enabled individually) (fix #1365)
|
||||
0.10: Added more bundleIds
|
||||
0.11: Added letters with caron to unicodeRemap, to properly display messages in Czech language
|
||||
|
|
|
|||
|
|
@ -127,18 +127,34 @@ E.on('notify',msg=>{
|
|||
'261':"a",
|
||||
'262':"C",
|
||||
'263':"c",
|
||||
'268':"C",
|
||||
'269':"c",
|
||||
'270':"D",
|
||||
'271':"d",
|
||||
'280':"E",
|
||||
'281':"e",
|
||||
'282':"E",
|
||||
'283':"e",
|
||||
'321':"L",
|
||||
'322':"l",
|
||||
'323':"N",
|
||||
'324':"n",
|
||||
'327':"N",
|
||||
'328':"n",
|
||||
'344':"R",
|
||||
'345':"r",
|
||||
'346':"S",
|
||||
'347':"s",
|
||||
'352':"S",
|
||||
'353':"s",
|
||||
'356':"T",
|
||||
'357':"t",
|
||||
'377':"Z",
|
||||
'378':"z",
|
||||
'379':"Z",
|
||||
'380':"z",
|
||||
'381':"Z",
|
||||
'382':"z",
|
||||
};
|
||||
var replacer = ""; //(n)=>print('Unknown unicode '+n.toString(16));
|
||||
//if (appNames[msg.appId]) msg.a
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"id": "ios",
|
||||
"name": "iOS Integration",
|
||||
"version": "0.10",
|
||||
"version": "0.11",
|
||||
"description": "Display notifications/music/etc from iOS devices",
|
||||
"icon": "app.png",
|
||||
"tags": "tool,system,ios,apple,messages,notifications",
|
||||
|
|
|
|||
|
|
@ -2,3 +2,4 @@
|
|||
0.02: Now keeps user input trace intact by changing how the screen is updated.
|
||||
0.03: Positioning of marker now takes the height of the widget field into account.
|
||||
0.04: Fix issue if going back without typing.
|
||||
0.05: Keep drag-function in ram, hopefully improving performance and input reliability somewhat.
|
||||
|
|
|
|||
|
|
@ -139,6 +139,7 @@ exports.getStrokes( (id,s) => Bangle.strokes[id] = Unistroke.new(s) );
|
|||
return new Promise((resolve,reject) => {
|
||||
var l;//last event
|
||||
Bangle.setUI({mode:"custom", drag:e=>{
|
||||
"ram";
|
||||
if (l) g.reset().setColor("#f00").drawLine(l.x,l.y,e.x,e.y);
|
||||
l = e.b ? e : 0;
|
||||
},touch:() => {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{ "id": "kbswipe",
|
||||
"name": "Swipe keyboard",
|
||||
"version":"0.04",
|
||||
"version":"0.05",
|
||||
"description": "A library for text input via PalmOS style swipe gestures (beta!)",
|
||||
"icon": "app.png",
|
||||
"type":"textinput",
|
||||
|
|
|
|||
|
|
@ -3,3 +3,4 @@
|
|||
0.03: Incorporated improvements from Peer David for accuracy, fix dark mode, widgets run in background
|
||||
0.04: Changed clock to use 12/24 hour format based on locale
|
||||
0.05: Tell clock widgets to hide.
|
||||
0.06: Widgets can now be made visible by swiping down (#2196)
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ function queueDraw() {
|
|||
|
||||
function draw() {
|
||||
queueDraw();
|
||||
|
||||
|
||||
// Fix theme to "light"
|
||||
g.setTheme({bg:"#fff", fg:"#000", dark:false}).clear();
|
||||
g.reset();
|
||||
|
|
@ -41,9 +41,6 @@ function draw() {
|
|||
yy = ("0"+((new Date()).getFullYear())).substr(-2);
|
||||
g.setFontCustom(font, 48, 8, 521);
|
||||
g.drawString(dd + ':' + mo + ':' + yy, 88, 120, true);
|
||||
|
||||
// Hide widgets
|
||||
for (let wd of WIDGETS) {wd.draw=()=>{};wd.area="";}
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -61,4 +58,5 @@ Bangle.setUI("clock");
|
|||
|
||||
// Load widgets but hide them
|
||||
Bangle.loadWidgets();
|
||||
require("widget_utils").swipeOn(); // hide widgets, make them visible with a swipe
|
||||
draw();
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
"name": "MacWatch2",
|
||||
"shortName":"MacWatch2",
|
||||
"icon": "app.png",
|
||||
"version":"0.05",
|
||||
"version":"0.06",
|
||||
"description": "Classic Mac Finder clock",
|
||||
"type": "clock",
|
||||
"tags": "clock",
|
||||
|
|
|
|||
|
|
@ -0,0 +1 @@
|
|||
0.01: Moved message icons from messages into standalone library
|
||||
|
After Width: | Height: | Size: 16 KiB |
|
After Width: | Height: | Size: 237 B |