Merge branch 'master' of github.com:espruino/BangleApps
|
@ -0,0 +1,3 @@
|
|||
.htaccess
|
||||
node_modules
|
||||
package-lock.json
|
|
@ -0,0 +1,3 @@
|
|||
language: node_js
|
||||
node_js:
|
||||
- "node"
|
|
@ -0,0 +1,478 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
version="1.1"
|
||||
id="svg4149"
|
||||
viewBox="0 0 19.191 30.044001"
|
||||
height="30.044001"
|
||||
width="19.191">
|
||||
<defs
|
||||
id="defs4151">
|
||||
<linearGradient
|
||||
id="linearGradient28799-4">
|
||||
<stop
|
||||
id="stop28801-13"
|
||||
offset="0"
|
||||
style="stop-color:#fefefc;stop-opacity:1;" />
|
||||
<stop
|
||||
style="stop-color:#fefefc;stop-opacity:1"
|
||||
offset="0.75733864"
|
||||
id="stop28807-8" />
|
||||
<stop
|
||||
id="stop28803-8"
|
||||
offset="1"
|
||||
style="stop-color:#d4d4d4;stop-opacity:1" />
|
||||
</linearGradient>
|
||||
<radialGradient
|
||||
r="14.572236"
|
||||
fy="137.66095"
|
||||
fx="223.19559"
|
||||
cy="137.66095"
|
||||
cx="223.19559"
|
||||
gradientTransform="matrix(1.0857794,-0.03431182,0.03943781,1.2479887,-154.54194,-26.542647)"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
id="radialGradient29032"
|
||||
xlink:href="#linearGradient28799-4" />
|
||||
<radialGradient
|
||||
r="14.572236"
|
||||
fy="137.66095"
|
||||
fx="223.19559"
|
||||
cy="137.66095"
|
||||
cx="223.19559"
|
||||
gradientTransform="matrix(0.81524244,-0.03431182,0.02961133,1.2479887,-129.43743,-26.542647)"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
id="radialGradient29648"
|
||||
xlink:href="#linearGradient28799-4" />
|
||||
<linearGradient
|
||||
id="linearGradient14392-7">
|
||||
<stop
|
||||
style="stop-color:#3e2a06;stop-opacity:1"
|
||||
offset="0"
|
||||
id="stop14394-8" />
|
||||
<stop
|
||||
style="stop-color:#ad780a;stop-opacity:1"
|
||||
offset="1"
|
||||
id="stop14396-6" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
xlink:href="#linearGradient14392-7"
|
||||
id="linearGradient14477"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
gradientTransform="matrix(0.87491199,0,0,0.81148755,60.367186,62.586953)"
|
||||
x1="442.03912"
|
||||
y1="371.54401"
|
||||
x2="490.12241"
|
||||
y2="293.58548" />
|
||||
<linearGradient
|
||||
xlink:href="#linearGradient14392-7"
|
||||
id="linearGradient14485"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
gradientTransform="translate(4e-6,9.6185359e-6)"
|
||||
x1="442.03912"
|
||||
y1="371.54401"
|
||||
x2="490.12241"
|
||||
y2="293.58548" />
|
||||
<clipPath
|
||||
clipPathUnits="userSpaceOnUse"
|
||||
id="clipPath14186-9">
|
||||
<path
|
||||
id="path14188-1"
|
||||
d="m 137.57703,281.0191 c 1.59929,-0.66295 3.3982,-0.78361 5.10074,-0.46963 1.70253,0.31398 3.31141,1.04948 4.74342,2.02239 2.86402,1.94583 4.98821,4.77774 7.02263,7.57952 4.67189,6.43406 9.16868,13.00227 13.24488,19.8293 3.30635,5.53766 6.34352,11.25685 10.16415,16.45304 2.49398,3.3919 5.3066,6.53947 7.813,9.92221 2.50639,3.38273 4.72794,7.05586 5.83931,11.11662 1.44411,5.27653 0.88463,11.09291 -1.62666,15.95302 -1.76663,3.41896 -4.47646,6.35228 -7.77242,8.33898 -3.29595,1.9867 -7.17064,3.01444 -11.01635,2.87021 -6.11413,-0.2293 -11.69944,-3.28515 -17.38362,-5.54906 -11.58097,-4.6125 -24.15978,-6.0594 -36.09666,-9.65174 -3.66859,-1.10404 -7.27582,-2.4107 -10.96988,-3.42629 -1.64125,-0.45122 -3.30866,-0.8482 -4.85875,-1.55144 -1.55008,-0.70325 -2.999548,-1.7491 -3.86171,-3.21675 -0.666391,-1.13439 -0.948386,-2.47002 -0.930187,-3.78554 0.0182,-1.31552 0.325889,-2.61453 0.773815,-3.85158 0.895851,-2.47409 2.343262,-4.71374 3.320162,-7.15696 1.59511,-3.98935 1.88169,-8.38839 1.66657,-12.67942 -0.21511,-4.29103 -0.91078,-8.54478 -1.20454,-12.83115 -0.13118,-1.91406 -0.18066,-3.85256 0.18479,-5.73598 0.36545,-1.88343 1.17577,-3.72459 2.55771,-5.05541 1.27406,-1.22693 2.96492,-1.95531 4.69643,-2.31651 1.73151,-0.3612 3.51533,-0.37747 5.28367,-0.33762 1.76833,0.0399 3.54067,0.13425 5.30351,-0.0106 1.76284,-0.14488 3.53347,-0.54055 5.06911,-1.41828 1.45996,-0.83447 2.65433,-2.0745 3.64374,-3.43424 0.9894,-1.35974 1.78909,-2.84573 2.60891,-4.31396 0.81983,-1.46823 1.66834,-2.93151 2.74157,-4.22611 1.07324,-1.2946 2.38923,-2.42304 3.94266,-3.06698"
|
||||
style="fill:#402c07;fill-opacity:1;stroke:none" />
|
||||
</clipPath>
|
||||
<clipPath
|
||||
clipPathUnits="userSpaceOnUse"
|
||||
id="clipPath15031-3">
|
||||
<path
|
||||
style="fill:none;stroke:#729fcf;stroke-width:0.125;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 248.06066,184.61218 c -2.60091,3.4397 -5.23338,6.85555 -7.89695,10.24696 -2.83301,3.60715 -5.70116,7.1866 -8.58729,10.75139 -3.54135,4.37407 -7.15155,8.79286 -9.44912,13.93045 -1.97407,4.41422 -2.91014,9.20748 -4.26498,13.84932 -1.53862,5.27153 -3.62627,10.37023 -5.97071,15.33612 -2.1648,4.58539 -4.54978,9.06293 -6.93891,13.53553 -1.73823,3.25407 -3.50515,6.58104 -4.10782,10.22071 -0.47627,2.87632 -0.19849,5.84423 0.53376,8.66626 0.73224,2.82202 1.90964,5.5106 3.23775,8.10601 5.66724,11.07504 14.17003,20.62168 24.24176,27.92472 4.57063,3.31418 9.46669,6.18109 14.60245,8.52595 2.78247,1.27041 5.71355,2.40436 8.77186,2.45744 1.52915,0.0265 3.0741,-0.22544 4.47434,-0.84055 1.40024,-0.6151 2.65068,-1.60373 3.48254,-2.88709 1.02278,-1.5779 1.36992,-3.53829 1.16461,-5.40743 -0.2053,-1.86914 -0.93484,-3.65294 -1.91324,-5.25873 -2.38997,-3.92251 -6.1652,-6.76055 -9.79642,-9.57343 -7.84055,-6.07358 -15.42466,-12.48038 -22.68212,-19.23996 -2.04911,-1.90854 -4.0984,-3.87759 -5.53019,-6.28412 -1.3943,-2.34352 -2.14763,-5.01375 -2.65783,-7.69253 -1.39963,-7.34872 -1.0404,-15.0827 1.45958,-22.13343 0.97802,-2.75834 2.27066,-5.39225 3.51815,-8.03965 2.1622,-4.58855 4.21133,-9.26299 7.04933,-13.46723 3.53268,-5.23334 8.24484,-9.67275 11.15147,-15.27803 2.45457,-4.7335 3.49353,-10.05742 4.36185,-15.31831 0.66531,-4.03089 1.24751,-8.07549 1.74613,-12.13037"
|
||||
id="path15033-2" />
|
||||
</clipPath>
|
||||
<clipPath
|
||||
clipPathUnits="userSpaceOnUse"
|
||||
id="clipPath14822-9">
|
||||
<path
|
||||
d="m 386.1875,285.32775 c -0.40516,-1.10369 -1.11845,-2.08156 -1.9907,-2.86987 -0.87226,-0.78832 -1.90049,-1.39229 -2.98278,-1.85155 -2.16459,-0.91852 -4.52053,-1.26149 -6.83152,-1.69556 -2.17919,-0.40931 -4.34179,-0.90631 -6.52782,-1.27734 -2.27136,-0.38551 -4.6179,-0.63213 -6.8653,-0.1253 -1.96583,0.44333 -3.7845,1.45879 -5.27172,2.81864 -1.48723,1.35984 -2.64911,3.0564 -3.48499,4.89007 -1.47218,3.22952 -1.93451,6.86503 -1.65394,10.40316 0.20882,2.63325 0.87532,5.34594 2.60877,7.33912 1.40065,1.61052 3.38733,2.61526 5.43398,3.22092 3.52502,1.04316 7.36663,0.98822 10.86038,-0.1553 5.76689,-1.93113 10.87568,-5.77387 14.33034,-10.77903 1.13861,-1.64963 2.11217,-3.44809 2.5532,-5.4034 0.33597,-1.48955 0.34831,-3.08112 -0.1779,-4.51456"
|
||||
id="path14824-1"
|
||||
style="fill:none;stroke:#ce5c00;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
|
||||
</clipPath>
|
||||
<clipPath
|
||||
clipPathUnits="userSpaceOnUse"
|
||||
id="clipPath14822-2-4">
|
||||
<path
|
||||
d="m 386.1875,285.32775 c -0.40516,-1.10369 -1.11845,-2.08156 -1.9907,-2.86987 -0.87226,-0.78832 -1.90049,-1.39229 -2.98278,-1.85155 -2.16459,-0.91852 -4.52053,-1.26149 -6.83152,-1.69556 -2.17919,-0.40931 -4.34179,-0.90631 -6.52782,-1.27734 -2.27136,-0.38551 -4.6179,-0.63213 -6.8653,-0.1253 -1.96583,0.44333 -3.7845,1.45879 -5.27172,2.81864 -1.48723,1.35984 -2.64911,3.0564 -3.48499,4.89007 -1.47218,3.22952 -1.93451,6.86503 -1.65394,10.40316 0.20882,2.63325 0.87532,5.34594 2.60877,7.33912 1.40065,1.61052 3.38733,2.61526 5.43398,3.22092 3.52502,1.04316 7.36663,0.98822 10.86038,-0.1553 5.76689,-1.93113 10.87568,-5.77387 14.33034,-10.77903 1.13861,-1.64963 2.11217,-3.44809 2.5532,-5.4034 0.33597,-1.48955 0.34831,-3.08112 -0.1779,-4.51456"
|
||||
id="path14824-6-5"
|
||||
style="fill:none;stroke:#ce5c00;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
|
||||
</clipPath>
|
||||
<linearGradient
|
||||
id="linearGradient14518-6">
|
||||
<stop
|
||||
id="stop14540-0"
|
||||
offset="0"
|
||||
style="stop-color:#110800;stop-opacity:1;" />
|
||||
<stop
|
||||
style="stop-color:#a65a00;stop-opacity:0.80000001;"
|
||||
offset="0.59066743"
|
||||
id="stop14542-5" />
|
||||
<stop
|
||||
style="stop-color:#ff921e;stop-opacity:0;"
|
||||
offset="1"
|
||||
id="stop14522-4" />
|
||||
</linearGradient>
|
||||
<clipPath
|
||||
clipPathUnits="userSpaceOnUse"
|
||||
id="clipPath14489-3">
|
||||
<path
|
||||
style="fill:url(#linearGradient14485);fill-opacity:1;stroke:none"
|
||||
d="m 516.00562,331.8756 c -0.6714,1.71905 -1.62682,3.32679 -2.81579,4.73826 -2.6238,3.11482 -6.268,5.17039 -9.89648,7.01985 -6.1886,3.15437 -12.60169,5.92177 -18.41964,9.71654 -3.89802,2.54249 -7.4959,5.52671 -10.86016,8.74238 -2.87719,2.75012 -5.60582,5.68745 -8.83247,8.01771 -3.25567,2.35122 -7.01915,4.05426 -10.99061,4.6502 -4.83026,0.72481 -9.82134,-0.21289 -14.29898,-2.16416 -3.13754,-1.36728 -6.15569,-3.3229 -7.96301,-6.22931 -1.81425,-2.91754 -2.22807,-6.48813 -2.23266,-9.92375 -0.008,-6.07666 1.11824,-12.09004 2.17848,-18.07349 0.88097,-4.97177 1.71949,-9.95483 2.26013,-14.97502 0.98337,-9.13118 0.9763,-18.35278 0.3199,-27.51327 -0.10993,-1.53416 -0.23754,-3.0832 -0.008,-4.60412 0.22922,-1.52092 0.85475,-3.0367 2.02069,-4.03986 1.07696,-0.9266 2.52093,-1.33598 3.93947,-1.4145 1.41854,-0.0785 2.83404,0.14655 4.23982,0.35197 3.31254,0.48405 6.65159,0.8649 9.88917,1.71656 2.04284,0.53738 4.03315,1.25925 6.0722,1.81081 3.40258,0.92039 6.96639,1.36144 10.46739,0.95192 3.76917,-0.44089 7.42987,-1.85678 11.22363,-1.76474 1.55658,0.0378 3.1015,0.33171 4.58649,0.79985 1.51539,0.47772 3.00914,1.16182 4.12281,2.29512 0.84639,0.8613 1.43579,1.94539 1.87872,3.06879 0.65982,1.67352 1.01492,3.457 1.16703,5.24945 0.13475,1.58788 0.11343,3.19441 0.41433,4.75933 0.49503,2.57458 1.84746,4.92305 3.52848,6.93494 1.68102,2.01189 3.68982,3.72048 5.69641,5.40783 1.99908,1.68103 4.0106,3.35469 6.16708,4.82839 1.0121,0.69165 2.05642,1.33949 3.01736,2.10062 0.96094,0.76113 1.84466,1.6468 2.44543,2.71535 0.81492,1.44944 1.06377,3.2077 0.68307,4.82635 h 2e-5"
|
||||
id="path14491-6" />
|
||||
</clipPath>
|
||||
<clipPath
|
||||
clipPathUnits="userSpaceOnUse"
|
||||
id="clipPath14481-1">
|
||||
<path
|
||||
style="fill:url(#linearGradient14485);fill-opacity:1;stroke:none"
|
||||
d="m 516.00562,331.8756 c -0.6714,1.71905 -1.62682,3.32679 -2.81579,4.73826 -2.6238,3.11482 -6.268,5.17039 -9.89648,7.01985 -6.1886,3.15437 -12.60169,5.92177 -18.41964,9.71654 -3.89802,2.54249 -7.4959,5.52671 -10.86016,8.74238 -2.87719,2.75012 -5.60582,5.68745 -8.83247,8.01771 -3.25567,2.35122 -7.01915,4.05426 -10.99061,4.6502 -4.83026,0.72481 -9.82134,-0.21289 -14.29898,-2.16416 -3.13754,-1.36728 -6.15569,-3.3229 -7.96301,-6.22931 -1.81425,-2.91754 -2.22807,-6.48813 -2.23266,-9.92375 -0.008,-6.07666 1.11824,-12.09004 2.17848,-18.07349 0.88097,-4.97177 1.71949,-9.95483 2.26013,-14.97502 0.98337,-9.13118 0.9763,-18.35278 0.3199,-27.51327 -0.10993,-1.53416 -0.23754,-3.0832 -0.008,-4.60412 0.22922,-1.52092 0.85475,-3.0367 2.02069,-4.03986 1.07696,-0.9266 2.52093,-1.33598 3.93947,-1.4145 1.41854,-0.0785 2.83404,0.14655 4.23982,0.35197 3.31254,0.48405 6.65159,0.8649 9.88917,1.71656 2.04284,0.53738 4.03315,1.25925 6.0722,1.81081 3.40258,0.92039 6.96639,1.36144 10.46739,0.95192 3.76917,-0.44089 7.42987,-1.85678 11.22363,-1.76474 1.55658,0.0378 3.1015,0.33171 4.58649,0.79985 1.51539,0.47772 3.00914,1.16182 4.12281,2.29512 0.84639,0.8613 1.43579,1.94539 1.87872,3.06879 0.65982,1.67352 1.01492,3.457 1.16703,5.24945 0.13475,1.58788 0.11343,3.19441 0.41433,4.75933 0.49503,2.57458 1.84746,4.92305 3.52848,6.93494 1.68102,2.01189 3.68982,3.72048 5.69641,5.40783 1.99908,1.68103 4.0106,3.35469 6.16708,4.82839 1.0121,0.69165 2.05642,1.33949 3.01736,2.10062 0.96094,0.76113 1.84466,1.6468 2.44543,2.71535 0.81492,1.44944 1.06377,3.2077 0.68307,4.82635 h 2e-5"
|
||||
id="path14483-3" />
|
||||
</clipPath>
|
||||
<clipPath
|
||||
clipPathUnits="userSpaceOnUse"
|
||||
id="clipPath14473-5">
|
||||
<path
|
||||
style="fill:url(#linearGradient14477);fill-opacity:1;stroke:none"
|
||||
d="m 511.82669,331.89986 c -0.60617,1.40358 -1.44178,2.70776 -2.46357,3.84504 -2.32524,2.58805 -5.50706,4.22246 -8.65855,5.69652 -5.42045,2.53534 -11.00071,4.7788 -16.11556,7.88485 -3.38301,2.05437 -6.53696,4.47221 -9.50169,7.09434 -2.52461,2.23286 -4.92977,4.6271 -7.72763,6.50627 -2.88313,1.93644 -6.17534,3.29888 -9.61582,3.77358 -4.21817,0.58201 -8.55335,-0.18335 -12.51035,-1.75619 -2.7091,-1.07682 -5.36625,-2.6185 -6.96693,-5.05501 -1.53813,-2.3413 -1.9333,-5.25173 -1.95338,-8.053 -0.0354,-4.93859 0.96501,-9.81816 1.90598,-14.66641 0.78215,-4.02995 1.52682,-8.0717 1.97741,-12.15204 0.81784,-7.40607 0.66114,-14.88536 0.27989,-22.32668 -0.064,-1.24906 -0.13361,-2.5105 0.0789,-3.74301 0.21256,-1.23251 0.73646,-2.45247 1.68171,-3.27147 0.93914,-0.8137 2.20952,-1.15942 3.45104,-1.2115 1.24152,-0.0521 2.47642,0.16381 3.70512,0.34927 2.88969,0.43617 5.81087,0.7091 8.65215,1.39297 1.78651,0.43 3.53091,1.02011 5.31265,1.46945 2.98632,0.75312 6.09645,1.10709 9.15804,0.77247 3.2941,-0.36004 6.50688,-1.50957 9.81969,-1.43207 1.35838,0.0318 2.70755,0.27142 4.01277,0.64907 1.31265,0.3798 2.61744,0.9202 3.6071,1.86246 0.72535,0.69061 1.25006,1.56936 1.64371,2.49029 0.57625,1.34814 0.88626,2.79994 1.02105,4.25986 0.11897,1.28855 0.10468,2.59405 0.36251,3.86214 0.4369,2.14878 1.63966,4.08139 3.11506,5.70353 1.4754,1.62214 3.22406,2.96752 4.9559,4.31247 1.75634,1.36398 3.50975,2.7398 5.39565,3.91818 0.88228,0.55128 1.79349,1.05932 2.63473,1.67142 0.84123,0.61209 1.61878,1.33907 2.14473,2.23668 0.68386,1.16711 0.90229,2.59857 0.59763,3.91652 h 2e-5"
|
||||
id="path14475-9" />
|
||||
</clipPath>
|
||||
<clipPath
|
||||
clipPathUnits="userSpaceOnUse"
|
||||
id="clipPath14656-7">
|
||||
<path
|
||||
d="m 304.84727,225.44951 c 6.01627,4.92338 9.77707,12.317 10.94319,20.00305 1.8254,12.03148 -2.34636,24.07488 -6.44591,35.53273 -0.81749,2.28481 -1.63978,4.59033 -1.96473,6.99513 -0.32495,2.4048 -0.11897,4.94825 1.02948,7.08594 1.30279,2.42497 3.73227,4.12469 6.36677,4.92292 2.58877,0.78438 5.39365,0.76103 8.01466,0.0922 2.62101,-0.66882 5.06097,-1.96774 7.18716,-3.63996 5.48002,-4.30997 8.75799,-10.98552 10.01043,-17.84394 1.25243,-6.85841 0.63908,-13.92879 -0.551,-20.7983 -1.62313,-9.36916 -4.30898,-18.53961 -7.74003,-27.40773 -2.51766,-6.50732 -5.46794,-12.91081 -9.61753,-18.52016 -4.04611,-5.46946 -9.16208,-10.08954 -13.10336,-15.63502 -2.73209,-3.84412 -4.89983,-8.13097 -8.1917,-11.50812 -1.64594,-1.68858 -3.57654,-3.13527 -5.76975,-4.00143 -2.19321,-0.86616 -4.66356,-1.12446 -6.93254,-0.48249 -1.50468,0.42573 -2.89193,1.23739 -4.03791,2.30136 -1.14598,1.06397 -2.05268,2.37637 -2.69275,3.80312 -1.28015,2.85349 -1.47719,6.11241 -0.96512,9.1977 0.66114,3.98343 2.44407,7.69333 4.46569,11.18873 2.27396,3.93169 4.918,7.71926 8.38442,10.65407 3.5985,3.04664 7.96161,5.07413 11.61053,8.0602"
|
||||
id="path14658-9"
|
||||
style="fill:none;stroke:#c17d11;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
|
||||
</clipPath>
|
||||
<clipPath
|
||||
id="clipPath29644-8"
|
||||
clipPathUnits="userSpaceOnUse">
|
||||
<path
|
||||
id="path29646-8"
|
||||
d="m 54.232441,122.36218 c -1.780966,0.097 -3.484616,0.91899 -4.787851,2.1367 -1.303235,1.21771 -2.221372,2.81176 -2.786181,4.50357 -1.129618,3.38363 -0.875481,7.05177 -0.618698,10.60973 0.23251,3.22162 0.470404,6.50533 1.676785,9.50158 0.60319,1.49813 1.450247,2.91021 2.580338,4.06395 1.130092,1.15374 2.551736,2.04189 4.118297,2.43447 1.468838,0.36809 3.038161,0.29183 4.482784,-0.16209 1.444623,-0.45391 2.763917,-1.27887 3.846236,-2.33791 1.57904,-1.54507 2.643262,-3.5662 3.253449,-5.68947 0.610186,-2.12328 0.784157,-4.35155 0.752401,-6.56053 -0.03974,-2.76435 -0.400909,-5.53851 -1.265755,-8.16439 -0.864846,-2.62588 -2.245742,-5.10327 -4.172795,-7.08561 -0.933308,-0.96009 -1.997766,-1.80513 -3.198586,-2.39747 -1.200819,-0.59233 -2.54344,-0.92535 -3.880424,-0.85253"
|
||||
style="fill:url(#radialGradient29648);fill-opacity:1;stroke:none" />
|
||||
</clipPath>
|
||||
<linearGradient
|
||||
id="linearGradient28469-0">
|
||||
<stop
|
||||
style="stop-color:#d2940a;stop-opacity:1"
|
||||
offset="0"
|
||||
id="stop28479-4" />
|
||||
<stop
|
||||
style="stop-color:#d89c08;stop-opacity:1"
|
||||
offset="0.75143719"
|
||||
id="stop28477-2" />
|
||||
<stop
|
||||
id="stop28485-7"
|
||||
offset="0.86579126"
|
||||
style="stop-color:#b67e07;stop-opacity:1;" />
|
||||
<stop
|
||||
id="stop28473-1"
|
||||
offset="1"
|
||||
style="stop-color:#946106;stop-opacity:1" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="linearGradient28275-8">
|
||||
<stop
|
||||
id="stop28277-1"
|
||||
offset="0"
|
||||
style="stop-color:#ad780a;stop-opacity:1" />
|
||||
<stop
|
||||
style="stop-color:#d89e08;stop-opacity:1"
|
||||
offset="0.11972899"
|
||||
id="stop28291-9" />
|
||||
<stop
|
||||
style="stop-color:#edb80b;stop-opacity:1"
|
||||
offset="0.25514477"
|
||||
id="stop28289-6" />
|
||||
<stop
|
||||
style="stop-color:#ebc80d;stop-opacity:1"
|
||||
offset="0.39194193"
|
||||
id="stop28287-6" />
|
||||
<stop
|
||||
style="stop-color:#f5d838;stop-opacity:1"
|
||||
offset="0.52741116"
|
||||
id="stop28285-7" />
|
||||
<stop
|
||||
style="stop-color:#f6d811;stop-opacity:1"
|
||||
offset="0.76906693"
|
||||
id="stop28283-7" />
|
||||
<stop
|
||||
id="stop28279-5"
|
||||
offset="1"
|
||||
style="stop-color:#f5cd31;stop-opacity:1" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="linearGradient29529-6">
|
||||
<stop
|
||||
id="stop29531-0"
|
||||
offset="0"
|
||||
style="stop-color:#3a2903;stop-opacity:1;" />
|
||||
<stop
|
||||
style="stop-color:#735208;stop-opacity:1"
|
||||
offset="0.55472803"
|
||||
id="stop29539-7" />
|
||||
<stop
|
||||
id="stop29533-8"
|
||||
offset="1"
|
||||
style="stop-color:#ac8c04;stop-opacity:1" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="linearGradient29477-3">
|
||||
<stop
|
||||
id="stop29479-5"
|
||||
offset="0"
|
||||
style="stop-color:#757574;stop-opacity:0" />
|
||||
<stop
|
||||
style="stop-color:#757574;stop-opacity:1;"
|
||||
offset="0.26291031"
|
||||
id="stop29487-5" />
|
||||
<stop
|
||||
style="stop-color:#757574;stop-opacity:1;"
|
||||
offset="0.5"
|
||||
id="stop29485-0" />
|
||||
<stop
|
||||
id="stop29481-3"
|
||||
offset="1"
|
||||
style="stop-color:#757574;stop-opacity:0" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="linearGradient29336-6">
|
||||
<stop
|
||||
id="stop29338-1"
|
||||
offset="0"
|
||||
style="stop-color:#646464;stop-opacity:0" />
|
||||
<stop
|
||||
style="stop-color:#646464;stop-opacity:0.5825243"
|
||||
offset="0.30628255"
|
||||
id="stop29344-7" />
|
||||
<stop
|
||||
id="stop29354-5"
|
||||
offset="0.47000006"
|
||||
style="stop-color:#646464;stop-opacity:1" />
|
||||
<stop
|
||||
style="stop-color:#646464;stop-opacity:0.25728154"
|
||||
offset="0.72834015"
|
||||
id="stop29356-3" />
|
||||
<stop
|
||||
id="stop29340-6"
|
||||
offset="1"
|
||||
style="stop-color:#646464;stop-opacity:0;" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="linearGradient28976-3">
|
||||
<stop
|
||||
id="stop28978-7"
|
||||
offset="0"
|
||||
style="stop-color:#747474;stop-opacity:1" />
|
||||
<stop
|
||||
style="stop-color:#8c8c8c;stop-opacity:1;"
|
||||
offset="0.125"
|
||||
id="stop29259-5" />
|
||||
<stop
|
||||
style="stop-color:#a4a4a4;stop-opacity:1;"
|
||||
offset="0.25"
|
||||
id="stop29257-0" />
|
||||
<stop
|
||||
style="stop-color:#d4d4d4;stop-opacity:1"
|
||||
offset="0.5"
|
||||
id="stop28984-4" />
|
||||
<stop
|
||||
id="stop28986-0"
|
||||
offset="0.61919296"
|
||||
style="stop-color:#d4d4d4;stop-opacity:1" />
|
||||
<stop
|
||||
id="stop28980-2"
|
||||
offset="1"
|
||||
style="stop-color:#7c7c7c;stop-opacity:1" />
|
||||
</linearGradient>
|
||||
<clipPath
|
||||
id="clipPath29028-5"
|
||||
clipPathUnits="userSpaceOnUse">
|
||||
<path
|
||||
id="path29030-7"
|
||||
d="m 85.75,122.36218 c -2.780425,1.91023 -5.110569,4.57487 -6.25,7.75 -1.436029,4.00163 -0.885838,8.48071 0.5,12.5 1.419488,4.11688 3.793788,8.04098 7.37932,10.51234 1.792766,1.23567 3.868091,2.08301 6.030402,2.33859 2.162311,0.25558 4.409274,-0.0949 6.340278,-1.10093 2.35312,-1.22596 4.14782,-3.37278 5.26217,-5.78076 1.11436,-2.40798 1.5888,-5.0701 1.73783,-7.71924 0.18989,-3.37546 -0.14047,-6.80646 -1.25,-10 -1.20527,-3.46909 -3.39005,-6.67055 -6.472754,-8.6666 -1.541351,-0.99803 -3.291947,-1.68356 -5.110883,-1.93515 -1.818936,-0.25158 -3.704766,-0.0633 -5.416363,0.60175 -0.975471,0.37901 -1.887438,0.9074 -2.75,1.5"
|
||||
style="fill:url(#radialGradient29032);fill-opacity:1;stroke:none" />
|
||||
</clipPath>
|
||||
<linearGradient
|
||||
id="linearGradient28935-8">
|
||||
<stop
|
||||
id="stop28937-7"
|
||||
offset="0"
|
||||
style="stop-color:#949494;stop-opacity:0.39215687;" />
|
||||
<stop
|
||||
style="stop-color:#949494;stop-opacity:1;"
|
||||
offset="0.5"
|
||||
id="stop28943-3" />
|
||||
<stop
|
||||
id="stop28939-8"
|
||||
offset="1"
|
||||
style="stop-color:#949494;stop-opacity:0.39215687;" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="linearGradient28853-5">
|
||||
<stop
|
||||
id="stop28855-3"
|
||||
offset="0"
|
||||
style="stop-color:#020204;stop-opacity:1" />
|
||||
<stop
|
||||
style="stop-color:#020204;stop-opacity:1"
|
||||
offset="0.73448181"
|
||||
id="stop28865-0" />
|
||||
<stop
|
||||
id="stop28857-9"
|
||||
offset="1"
|
||||
style="stop-color:#5c5c5c;stop-opacity:1;" />
|
||||
</linearGradient>
|
||||
<clipPath
|
||||
id="clipPath20"
|
||||
clipPathUnits="userSpaceOnUse">
|
||||
<path
|
||||
id="path18"
|
||||
d="M 0,0 H 1920 V 1080 H 0 Z" />
|
||||
</clipPath>
|
||||
<clipPath
|
||||
id="clipPath70"
|
||||
clipPathUnits="userSpaceOnUse">
|
||||
<path
|
||||
id="path68"
|
||||
d="M 0,0 H 1920 V 1080 H 0 Z" />
|
||||
</clipPath>
|
||||
<clipPath
|
||||
id="clipPath84"
|
||||
clipPathUnits="userSpaceOnUse">
|
||||
<path
|
||||
id="path82"
|
||||
d="M 0,0 H 1920 V 1080 H 0 Z" />
|
||||
</clipPath>
|
||||
</defs>
|
||||
<g
|
||||
transform="translate(0,-1022.3182)"
|
||||
id="layer1">
|
||||
<path
|
||||
id="path5206"
|
||||
d=""
|
||||
style="fill:#000000" />
|
||||
<path
|
||||
id="path5204"
|
||||
d=""
|
||||
style="fill:#000000" />
|
||||
<g
|
||||
style="stroke-width:9.38626099"
|
||||
id="g1740"
|
||||
transform="matrix(0.1420516,0,0,-0.1420516,103.61175,1146.9897)">
|
||||
<g
|
||||
transform="translate(-1082.1477,-40.602139)"
|
||||
style="stroke-width:9.38626099"
|
||||
id="g14">
|
||||
<g
|
||||
style="stroke-width:9.38626099"
|
||||
id="g16"
|
||||
clip-path="url(#clipPath20)">
|
||||
<g
|
||||
style="stroke-width:9.38626099"
|
||||
id="g22"
|
||||
transform="translate(485.7208,801.7501)">
|
||||
<path
|
||||
d="M 0,0 H -6.408 V 21.503 H 0 c 1.176,0 2.129,-0.953 2.129,-2.129 V 2.129 C 2.129,0.953 1.176,0 0,0"
|
||||
style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:9.38626099"
|
||||
id="path24" />
|
||||
</g>
|
||||
<g
|
||||
style="stroke-width:9.38626099"
|
||||
id="g26"
|
||||
transform="translate(467.9097,852.7021)">
|
||||
<path
|
||||
d="m 0,0 -1.811,-1.811 -10.287,10.287 1.811,1.811 c 1.424,1.425 3.734,1.425 5.159,0 L 0,5.159 C 1.425,3.734 1.425,1.425 0,0"
|
||||
style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:9.38626099"
|
||||
id="path28" />
|
||||
</g>
|
||||
<g
|
||||
style="stroke-width:9.38626099"
|
||||
id="g30"
|
||||
transform="translate(457.6224,762.014)">
|
||||
<path
|
||||
d="m 0,0 -1.811,1.811 10.287,10.287 1.811,-1.811 c 1.425,-1.425 1.425,-3.734 0,-5.159 L 5.159,0 C 3.734,-1.425 1.425,-1.425 0,0"
|
||||
style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:9.38626099"
|
||||
id="path32" />
|
||||
</g>
|
||||
<g
|
||||
style="stroke-width:9.38626099"
|
||||
id="g34"
|
||||
transform="translate(449.515,753.7302)">
|
||||
<path
|
||||
d="m 0,0 v 4.934 c 0,0 -24.07,-8.855 -32.093,-8.855 -8.023,0 -32.093,8.855 -32.093,8.855 V 0 c 0,-2.713 0.889,-5.101 2.238,-6.509 1.433,-7.677 3.125,-26.35 3.681,-32.625 0,-4.333 2.261,-7.845 5.052,-7.845 h 42.245 c 2.79,0 5.051,3.512 5.051,7.845 v 10e-4 c 0.556,6.275 2.248,24.947 3.682,32.624 C -0.889,-5.101 0,-2.714 0,0"
|
||||
style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:9.38626099"
|
||||
id="path36" />
|
||||
</g>
|
||||
<g
|
||||
style="stroke-width:9.38626099"
|
||||
id="g38"
|
||||
transform="translate(385.3291,871.2729)">
|
||||
<path
|
||||
d="m 0,0 v -4.934 c 0,0 24.07,8.855 32.093,8.855 8.023,0 32.093,-8.855 32.093,-8.855 V 0 c 0,2.713 -0.889,5.101 -2.238,6.509 -1.433,7.677 -3.125,26.35 -3.681,32.624 0,4.333 -2.261,7.846 -5.051,7.846 H 10.97 c -2.79,0 -5.051,-3.513 -5.051,-7.846 v 0 C 5.363,32.858 3.671,14.186 2.237,6.509 0.889,5.101 0,2.714 0,0"
|
||||
style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:9.38626099"
|
||||
id="path40" />
|
||||
</g>
|
||||
<g
|
||||
style="stroke-width:9.38626099"
|
||||
id="g42"
|
||||
transform="translate(371.6938,858.2298)">
|
||||
<path
|
||||
d="m 0,0 c -25.255,-25.255 -25.255,-66.201 0,-91.456 25.255,-25.255 66.201,-25.255 91.456,0 25.256,25.255 25.255,66.201 0,91.456 C 66.201,25.255 25.255,25.255 0,0 m 85.935,-85.935 c -22.206,-22.205 -58.208,-22.205 -80.413,0 -22.206,22.206 -22.206,58.208 -0.001,80.414 22.206,22.205 58.208,22.205 80.414,0 22.206,-22.206 22.206,-58.208 0,-80.414"
|
||||
style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:9.38626099"
|
||||
id="path44" />
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
<path
|
||||
d="m -665.93071,765.84586 h 2.41 v -34.464 h -2.41 z"
|
||||
style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:9.38626099"
|
||||
id="path76" />
|
||||
<g
|
||||
transform="translate(-1082.1477,-40.602139)"
|
||||
style="stroke-width:9.38626099"
|
||||
id="g78">
|
||||
<g
|
||||
style="stroke-width:9.38626099"
|
||||
id="g80"
|
||||
clip-path="url(#clipPath84)">
|
||||
<g
|
||||
style="stroke-width:9.38626099"
|
||||
id="g86"
|
||||
transform="translate(438.6725,783.7013)">
|
||||
<path
|
||||
d="m 0,0 c -1.568,-1.333 -3.448,-1.998 -5.643,-1.998 -3.761,0 -6.583,1.704 -8.464,5.113 l 2.058,1.235 c 0.666,-1.215 1.469,-2.156 2.409,-2.822 0.98,-0.744 2.312,-1.117 3.997,-1.117 3.683,0 5.525,1.567 5.525,4.703 0,1.371 -0.646,2.566 -1.939,3.585 -0.393,0.235 -0.951,0.5 -1.675,0.793 -0.726,0.294 -1.617,0.637 -2.675,1.029 -1.96,0.705 -3.37,1.411 -4.231,2.116 -1.255,1.019 -1.881,2.488 -1.881,4.408 0,1.646 0.626,3.057 1.881,4.232 1.214,1.214 2.8,1.822 4.76,1.822 2.704,0 4.996,-0.94 6.877,-2.821 L -0.764,18.75 c -1.332,1.332 -3.037,1.998 -5.114,1.998 -2.821,0 -4.232,-1.234 -4.232,-3.703 0,-1.097 0.431,-1.998 1.294,-2.703 0.705,-0.55 1.821,-1.079 3.35,-1.587 1.331,-0.471 2.429,-0.911 3.291,-1.323 0.862,-0.411 1.508,-0.774 1.94,-1.087 C 1.449,9.052 2.292,7.308 2.292,5.114 2.292,2.998 1.528,1.293 0,0"
|
||||
style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:9.38626099"
|
||||
id="path88" />
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
<path
|
||||
id="path264"
|
||||
style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:9.38626099"
|
||||
d="m -662.67221,771.89946 c 0,-1.134 -0.919,-2.053 -2.053,-2.053 -1.134,0 -2.054,0.919 -2.054,2.053 0,1.134 0.92,2.053 2.054,2.053 1.134,0 2.053,-0.919 2.053,-2.053" />
|
||||
<g
|
||||
style="stroke-width:9.38626099"
|
||||
id="g266"
|
||||
transform="translate(-662.03281,778.14876)">
|
||||
<path
|
||||
d="m 0,0 2.015,-1.321 21.656,33.028 -2.015,1.322 z"
|
||||
style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:9.38626099"
|
||||
id="path268" />
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 28 KiB |
|
@ -0,0 +1,7 @@
|
|||
App Loader ChangeLog
|
||||
====================
|
||||
|
||||
Changed for individual apps are listed in `apps/appname/ChangeLog`
|
||||
|
||||
* `Remove All Apps` now doesn't perform a reset before erase - fixes inability to update firmware if settings are wrong
|
||||
* Added optional `README.md` file for apps
|
270
README.md
|
@ -1,7 +1,14 @@
|
|||
Bangle.js App Loader (and Apps)
|
||||
================================
|
||||
|
||||
Try it live at [banglejs.com/apps](https://banglejs.com/apps)
|
||||
[](https://travis-ci.org/espruino/BangleApps)
|
||||
|
||||
* Try the **release version** at [banglejs.com/apps](https://banglejs.com/apps)
|
||||
* Try the **development version** at [github.io](https://espruino.github.io/BangleApps/)
|
||||
|
||||
**All software (including apps) in this repository is MIT Licensed - see [LICENSE](LICENSE)** By
|
||||
submitting code to this repository you confirm that you are happy with it being MIT licensed,
|
||||
and that it is not licensed in another way that would make this impossible.
|
||||
|
||||
## How does it work?
|
||||
|
||||
|
@ -12,15 +19,26 @@ it with the files it sees in the watch's storage.
|
|||
* To upload an app, BangleAppLoader checks the files that are
|
||||
listed in `apps.json`, loads them, and sends them over Web Bluetooth.
|
||||
|
||||
## Getting Started
|
||||
|
||||
Check out:
|
||||
|
||||
* [Building your first Bangle.js Application](https://www.espruino.com/Bangle.js+First+App)
|
||||
* [Adding an app to the Bangle.js App Loader](https://www.espruino.com/Bangle.js+App+Loader)
|
||||
* [Customising the App Loader](https://www.espruino.com/Bangle.js+App+Loader+Custom)
|
||||
|
||||
## What filenames are used
|
||||
|
||||
Filenames in storage are limited to 8 characters. To
|
||||
easily distinguish between file types, we use the following:
|
||||
|
||||
* `+stuff` is JSON for an app
|
||||
* `*stuff` is an image
|
||||
* `-stuff` is JS code
|
||||
* `=stuff` is JS code for stuff that is run at boot time - eg. handling settings or creating widgets on the clock screen
|
||||
* `stuff.info` is JSON that describes an app - this is auto-generated by the App Loader
|
||||
* `stuff.img` is an image
|
||||
* `stuff.app.js` is JS code for applications
|
||||
* `stuff.wid.js` is JS code for widgets
|
||||
* `stuff.settings.js` is JS code for the settings menu
|
||||
* `stuff.boot.js` is JS code that automatically gets run at boot time
|
||||
* `stuff.json` is used for JSON settings for an app
|
||||
|
||||
## Developing your own app
|
||||
|
||||
|
@ -31,35 +49,25 @@ easily distinguish between file types, we use the following:
|
|||
|
||||
## Adding your app to the menu
|
||||
|
||||
* Come up with a unique 7 character name, we'll assume `7chname`
|
||||
* Come up with a unique (all lowercase, nu spaces) name, we'll assume `7chname`. Bangle.js
|
||||
is limited to 28 char filenames and appends a file extension (eg `.js`) so please
|
||||
try and keep filenames short to avoid overflowing the buffer.
|
||||
* Create a folder called `apps/<id>`, lets assume `apps/7chname`
|
||||
* We'd recommend that you copy files from 'Example Applications' (below) as a base, or...
|
||||
* `apps/7chname/app.png` should be a 48px icon
|
||||
* Use http://www.espruino.com/Image+Converter to create `apps/7chname/app-icon.js`, using a 1 bit, 4 bit or 8 bit Web Palette "Image String"
|
||||
* Create an entry in `apps/7chname/app.json` as follows:
|
||||
|
||||
```
|
||||
{
|
||||
"name":"Short Name",
|
||||
"icon":"*7chname",
|
||||
"src":"-7chname"
|
||||
}
|
||||
```
|
||||
|
||||
See `app.json / widget.json` below for more info on the correct format.
|
||||
|
||||
* Create an entry in `apps.json` as follows:
|
||||
|
||||
```
|
||||
{ "id": "7chname",
|
||||
"name": "My app's human readable name",
|
||||
"shortName" : "Short Name",
|
||||
"icon": "app.png",
|
||||
"description": "A detailed description of my great app",
|
||||
"tags": "",
|
||||
"storage": [
|
||||
{"name":"+7chname","url":"app.json"},
|
||||
{"name":"-7chname","url":"app.js"},
|
||||
{"name":"*7chname","url":"app-icon.js","evaluate":true}
|
||||
{"name":"7chname.app.js","url":"app.js"},
|
||||
{"name":"7chname.img","url":"app-icon.js","evaluate":true}
|
||||
],
|
||||
},
|
||||
```
|
||||
|
@ -76,36 +84,34 @@ This is the best way to test...
|
|||
* Run your personal `Bangle App Loader` at https://\<your-github-username\>.github.io/BangleApps/index.html to load apps onto your device
|
||||
* Your apps should be inside it - if there are problems, check your web browser's 'developer console' for errors
|
||||
|
||||
**Note:** It's a great idea to get a local copy of the repository on your PC,
|
||||
then run `bin/sanitycheck.js` - it'll run through a bunch of common issues
|
||||
that there might be.
|
||||
|
||||
Be aware of the delay between commits and updates on github.io - it can take a few minutes (and a 'hard refresh' of your browser) for changes to take effect.
|
||||
|
||||
### Offline
|
||||
|
||||
You can add the following to the Espruino Web IDE:
|
||||
Using the 'Storage' icon in [the Web IDE](https://www.espruino.com/ide/)
|
||||
(4 discs), upload your files into the places described in your JSON:
|
||||
|
||||
```
|
||||
// replace with your 7chname app name
|
||||
var appname = "mygreat";
|
||||
* `app-icon.js` -> `7chname.img`
|
||||
|
||||
require("Storage").write('*'+appname,
|
||||
// place app-icon.js contents here
|
||||
);
|
||||
Now load `app.js` up in the editor, and click the down-arrow to the bottom
|
||||
right of the `Send to Espruino` icon. Click `Storage` and then either choose
|
||||
`7chname.app.js` (if you'd uploaded your app previously), or `New File`
|
||||
and then enter `7chname.app.js` as the name.
|
||||
|
||||
//
|
||||
require("Storage").write("+"+appname,{
|
||||
"name":"My Great App","type":"",
|
||||
"icon":"*"+appname,
|
||||
"src":"-"+appname,
|
||||
});
|
||||
Now, clicking the `Send to Espruino` icon will load the app directly into
|
||||
Espruino **and** will automatically run it.
|
||||
|
||||
require("Storage").write("-"+appname,`
|
||||
// place contents of app.js here
|
||||
// be aware of double-quoting templated strings
|
||||
`
|
||||
```
|
||||
|
||||
When you upload code this way, your app will be uploaded to Bangle.js's menu
|
||||
When you upload code this way, your app will even be uploaded to Bangle.js's menu
|
||||
without you having to use the `Bangle App Loader`
|
||||
|
||||
**Note:** Widgets need to be run inside a clock or app, so if you're
|
||||
developing a widget you need to go go `Settings` -> `Communications` -> `Load after saving`
|
||||
and set it to `Load default application`.
|
||||
|
||||
## Example Applications
|
||||
|
||||
To make the process easier we've come up with some example applications that you can use as a base
|
||||
|
@ -113,21 +119,23 @@ when creating your own. Just come up with a unique 7 character name, copy `apps/
|
|||
or `apps/_example_widget` to `apps/7chname`, and add `apps/_example_X/add_to_apps.json` to
|
||||
`apps.json`.
|
||||
|
||||
**If you're making a widget** please start the name with `wid` to make
|
||||
it easy to find!
|
||||
|
||||
### App Example
|
||||
|
||||
The app example is available in [`apps/_example_app`](apps/_example_app)
|
||||
|
||||
Apps are listed in the Bangle.js menu, accessible from a clock app via the middle button.
|
||||
|
||||
* `add_to_apps.json` - insert into `apps.json`, describes the widget to bootloader and loader
|
||||
* `add_to_apps.json` - insert into `apps.json`, describes the app to bootloader and loader
|
||||
* `app.png` - app icon - 48x48px
|
||||
* `app-icon.js` - JS version of the icon (made with http://www.espruino.com/Image+Converter) for use in Bangle.js's menu
|
||||
* `app.json` - short app name for Bangle.js menu and storage filenames
|
||||
* `app.js` - app code
|
||||
|
||||
#### `app-icon.js`
|
||||
|
||||
The icon image and short description is used in the menu entry as selection posibility.
|
||||
The icon image and short description is used in Bangle.js's launcher.
|
||||
|
||||
Use the Espruino [image converter](https://www.espruino.com/Image+Converter) and upload your `app.png` file.
|
||||
|
||||
|
@ -143,24 +151,43 @@ Follow this steps to create a readable icon as image string.
|
|||
Replace this line with the image converter output:
|
||||
|
||||
```
|
||||
require("heatshrink").decompress(atob("mEwwJC/AH4A/AH4AgA=="));
|
||||
require("heatshrink").decompress(atob("mEwwJC/AH4A/AH4AgA=="))
|
||||
```
|
||||
|
||||
Keep in mind to use this converter for creating images you like to draw with `g.drawImage()` with your app.
|
||||
You can also use this converter for creating images you like to draw with `g.drawImage()` with your app.
|
||||
|
||||
Apps that need widgets can call `Bangle.loadWidgets()` **once** at startup to load
|
||||
them, and then `Bangle.drawWidgets()` to draw them onto the screen whenever the app
|
||||
has call to completely clear the screen. Widgets themselves will update as and when needed.
|
||||
|
||||
### Widget Example
|
||||
|
||||
The widget example is available in [`apps/_example_widget`](apps/_example_widget)
|
||||
|
||||
* `add_to_apps.json` - insert into `apps.json`, describes the widget to bootloader and loader
|
||||
* `widget.json` - short widget name and storage names
|
||||
* `widget.js` - widget code
|
||||
|
||||
### `app.json` / `widget.json` format
|
||||
Widgets are just small bits of code that run whenever an app that supports them
|
||||
calls `Bangle.loadWidgets()`. If they want to display something in the 24px high
|
||||
widget bars at the top and bottom of the screen they can add themselves to
|
||||
the global `WIDGETS` array with:
|
||||
|
||||
This is the file that's loaded onto Bangle.js, which gives information
|
||||
about the app.
|
||||
```
|
||||
WIDGETS["mywidget"]={
|
||||
area:"tl", // tl (top left), tr (top right), bl (bottom left), br (bottom right)
|
||||
width: 24, // how wide is the widget? You can change this and call Bangle.drawWidgets() to re-layout
|
||||
draw:draw // called to draw the widget
|
||||
};
|
||||
```
|
||||
|
||||
When the widget is to be drawn, `x` and `y` values are set up in `WIDGETS["mywidget"]`
|
||||
and `draw` can then use `this.x` and `this.y` to figure out where it needs to draw to.
|
||||
|
||||
|
||||
### `app.info` format
|
||||
|
||||
This is the file that's **auto-generated** and loaded onto Bangle.js by the App Loader,
|
||||
and which gives information about the app for the Launcher.
|
||||
|
||||
```
|
||||
{
|
||||
|
@ -183,20 +210,31 @@ about the app.
|
|||
```
|
||||
{ "id": "appid", // 7 character app id
|
||||
"name": "Readable name", // readable name
|
||||
"shortName": "Short name", // short name for launcher
|
||||
"icon": "icon.png", // icon in apps/
|
||||
"description": "...", // long description
|
||||
"type":"...", // optional(if app) - 'app'/'widget'/'launch'
|
||||
"type":"...", // optional(if app) - 'app'/'widget'/'launch'/'bootloader'
|
||||
"tags": "", // comma separated tag list for searching
|
||||
|
||||
"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
|
||||
|
||||
"custom": "custom.html", // if supplied, apps/custom.html is loaded in an
|
||||
// iframe, and it must post back an 'app' structure
|
||||
// like this one with 'storage','name' and 'id' set up
|
||||
// see below for more info
|
||||
|
||||
"interface": "interface.html", // if supplied, apps/interface.html is loaded in an
|
||||
// iframe, and it may interact with the connected Bangle
|
||||
// to retrieve information from it
|
||||
// see below for more info
|
||||
|
||||
"allow_emulator":true, // if 'app.js' will run in the emulator, set to true to
|
||||
// add an icon to allow your app to be tested
|
||||
|
||||
"storage": [ // list of files to add to storage
|
||||
{"name":"-appid", // filename to use in storage
|
||||
{"name":"appid.js", // filename to use in storage
|
||||
"url":"", // URL of file to load (currently relative to apps/)
|
||||
"content":"..." // if supplied, this content is loaded directly
|
||||
"evaluate":true // if supplied, data isn't quoted into a String before upload
|
||||
|
@ -213,6 +251,120 @@ about the app.
|
|||
* tags is used for grouping apps in the library, separate multiple entries by comma. Known tags are `tool`, `system`, `clock`, `game`, `sound`, `gps`, `widget`, `launcher` or empty.
|
||||
* storage is used to identify the app files and how to handle them
|
||||
|
||||
### `apps.json`: `custom` element
|
||||
|
||||
Apps that can be customised need to define a `custom` element in `apps.json`,
|
||||
which names an HTML file in that app's folder.
|
||||
|
||||
When `custom` is defined, the 'upload' button is replaced by a customize
|
||||
button, and when clicked it opens the HTML page specified in an iframe.
|
||||
|
||||
In that HTML file you're then responsible for handling a button
|
||||
press and calling `sendCustomizedApp` with your own customised
|
||||
version of what's in `apps.json`:
|
||||
|
||||
```
|
||||
<html>
|
||||
<head>
|
||||
<link rel="stylesheet" href="../../css/spectre.min.css">
|
||||
</head>
|
||||
<body>
|
||||
<p><button id="upload" class="btn btn-primary">Upload</button></p>
|
||||
<script src="../../lib/customize.js"></script>
|
||||
<script>
|
||||
document.getElementById("upload").addEventListener("click", function() {
|
||||
sendCustomizedApp({
|
||||
id : "7chname",
|
||||
storage:[
|
||||
{name:"7chname.app.js", content:app_source_code},
|
||||
{name:"7chname.img", content:'require("heatshrink").decompress(atob("mEwg...4"))', evaluate:true},
|
||||
]
|
||||
});
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
This'll then be loaded in to the watch. See [apps/qrcode/grcode.html](the QR Code app)
|
||||
for a clean example.
|
||||
|
||||
### `apps.json`: `interface` element
|
||||
|
||||
Apps that create data that can be read back can define a `interface` element in `apps.json`,
|
||||
which names an HTML file in that app's folder.
|
||||
|
||||
When `interface` is defined, a `Download from App` button is added to
|
||||
the app's description, and when clicked it opens the HTML page specified
|
||||
in an iframe.
|
||||
|
||||
```
|
||||
<html>
|
||||
<head>
|
||||
<link rel="stylesheet" href="../../css/spectre.min.css">
|
||||
</head>
|
||||
<body>
|
||||
<script src="../../lib/interface.js"></script>
|
||||
<div id="t">Loading...</div>
|
||||
<script>
|
||||
function onInit() {
|
||||
Puck.eval("E.getTemperature()", temp=> {
|
||||
document.getElementById("t").innerHTML = temp;
|
||||
});
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
When the page is ready a function called `onInit` is called,
|
||||
and in that you can call `Puck.write` and `Puck.eval` to get
|
||||
the data you require from Bangle.js.
|
||||
|
||||
See [apps/gpsrec/interface.html](the GPS Recorder) for a full example.
|
||||
|
||||
### Adding configuration to the "Settings" menu
|
||||
|
||||
Apps (or widgets) can add their own settings to the "Settings" menu under "App/widget settings".
|
||||
To do so, the app needs to include a `settings.js` file, containing a single function
|
||||
that handles configuring the app.
|
||||
When the app settings are opened, this function is called with one
|
||||
argument, `back`: a callback to return to the settings menu.
|
||||
|
||||
Example `settings.js`
|
||||
```js
|
||||
// make sure to enclose the function in parentheses
|
||||
(function(back) {
|
||||
let settings = require('Storage').readJSON('app.settings.json',1)||{};
|
||||
function save(key, value) {
|
||||
settings[key] = value;
|
||||
require('Storage').write('app.settings.json',settings);
|
||||
}
|
||||
const appMenu = {
|
||||
'': {'title': 'App Settings'},
|
||||
'< Back': back,
|
||||
'Monkeys': {
|
||||
value: settings.monkeys||12,
|
||||
onchange: (m) => {save('monkeys', m)}
|
||||
}
|
||||
};
|
||||
E.showMenu(appMenu)
|
||||
})
|
||||
```
|
||||
In this example the app needs to add both `app.settings.js` and
|
||||
`app.settings.json` to `apps.json`:
|
||||
```json
|
||||
{ "id": "app",
|
||||
...
|
||||
"storage": [
|
||||
...
|
||||
{"name":"app.settings.js","url":"settings.js"},
|
||||
{"name":"app.settings.json","content":"{}"}
|
||||
]
|
||||
},
|
||||
```
|
||||
That way removing the app also cleans up `app.settings.json`.
|
||||
|
||||
## Coding hints
|
||||
|
||||
- use `g.setFont(.., size)` to multiply the font size, eg ("6x8",3) : "18x24"
|
||||
|
@ -227,7 +379,17 @@ about the app.
|
|||
|
||||
- using `g.setLCDBrightness()` can save you power during long periods with lcd on
|
||||
|
||||
- chaining graphics methodes, eg `g.setColor(0xFD20).setFontAlign(0,0).setfont("6x8",3)`
|
||||
- chaining graphics methods, eg `g.setColor(0xFD20).setFontAlign(0,0).setfont("6x8",3)`
|
||||
|
||||
### Misc Notes
|
||||
|
||||
- Need to save state? Use the `E.on('kill',...)` event to save JSON to a file called `7chname.json`, then load it at startup.
|
||||
|
||||
- 'Alarm' apps define a file called `alarm.js` which handles the actual alarm window.
|
||||
|
||||
- Locale is handled by `require("locale")`. An app may create a `locale` file in Storage which is
|
||||
a module that overwrites Bangle.js's default locale.
|
||||
|
||||
|
||||
### Graphic areas
|
||||
|
||||
|
@ -286,7 +448,7 @@ You can use `g.setColor(r,g,b)` OR `g.setColor(16bitnumber)` - some common 16 bi
|
|||
The [`testing`](testing) folder contains snippets of code that might be useful for your apps.
|
||||
|
||||
* `testing/colors.js` - 16 bit colors as name value pairs
|
||||
* `testing/gpstrack.js` - code to store a GPS track in Bagle.js storage and output it back to the console
|
||||
* `testing/gpstrack.js` - code to store a GPS track in Bangle.js storage and output it back to the console
|
||||
* `testing/map` - code for splitting an image into map tiles and then displaying them
|
||||
|
||||
## Credits
|
||||
|
|
55
appinfo.js
|
@ -1,55 +0,0 @@
|
|||
function toJS(txt) {
|
||||
return JSON.stringify(txt);
|
||||
}
|
||||
|
||||
var AppInfo = {
|
||||
getFiles : (app,fileGetter) => {
|
||||
return new Promise((resolve,reject) => {
|
||||
// Load all files
|
||||
Promise.all(app.storage.map(storageFile => {
|
||||
if (storageFile.content)
|
||||
return Promise.resolve(storageFile);
|
||||
else if (storageFile.url)
|
||||
return fileGetter(`apps/${app.id}/${storageFile.url}`).then(content => {
|
||||
return {
|
||||
name : storageFile.name,
|
||||
content : content,
|
||||
evaluate : storageFile.evaluate
|
||||
}});
|
||||
else return Promise.resolve();
|
||||
})).then(fileContents => { // now we just have a list of files + contents...
|
||||
// filter out empty files
|
||||
fileContents = fileContents.filter(x=>x!==undefined);
|
||||
// then map each file to a command to load into storage
|
||||
fileContents.forEach(storageFile => {
|
||||
// check if this is the JSON file
|
||||
if (storageFile.name[0]=="+") {
|
||||
storageFile.evaluate = true;
|
||||
var json = {};
|
||||
try {
|
||||
json = JSON.parse(storageFile.content);
|
||||
} catch (e) {
|
||||
reject(storageFile.name+" is not valid JSON");
|
||||
}
|
||||
if (app.version) json.version = app.version;
|
||||
json.files = fileContents.map(storageFile=>storageFile.name).join(",");
|
||||
storageFile.content = JSON.stringify(json);
|
||||
}
|
||||
// format ready for Espruino
|
||||
var js;
|
||||
if (storageFile.evaluate) {
|
||||
js = storageFile.content.trim();
|
||||
if (js.endsWith(";"))
|
||||
js = js.slice(0,-1);
|
||||
} else
|
||||
js = toJS(storageFile.content);
|
||||
storageFile.cmd = `\x10require('Storage').write(${toJS(storageFile.name)},${js});`;
|
||||
});
|
||||
resolve(fileContents);
|
||||
}).catch(err => reject(err));
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
if ("undefined"!=typeof module)
|
||||
module.exports = AppInfo;
|
|
@ -0,0 +1 @@
|
|||
0.01: New App!
|
|
@ -1,12 +1,13 @@
|
|||
// Create an entry in apps.json as follows:
|
||||
{ "id": "7chname",
|
||||
"name": "My app's human readable name",
|
||||
"shortName":"Short Name",
|
||||
"icon": "app.png",
|
||||
"version":"0.01",
|
||||
"description": "A detailed description of my great app",
|
||||
"tags": "",
|
||||
"storage": [
|
||||
{"name":"+7chname","url":"app.json"},
|
||||
{"name":"-7chname","url":"app.js"},
|
||||
{"name":"*7chname","url":"app-icon.js","evaluate":true}
|
||||
{"name":"7chname.app.js","url":"app.js"},
|
||||
{"name":"7chname.img","url":"app-icon.js","evaluate":true}
|
||||
]
|
||||
}
|
||||
|
|
|
@ -1,5 +0,0 @@
|
|||
{
|
||||
"name":"Short Name",
|
||||
"icon":"*7chname",
|
||||
"src":"-7chname"
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
0.01: New Widget!
|
|
@ -1,11 +1,13 @@
|
|||
// Create an entry in apps.json as follows:
|
||||
{ "id": "7chname",
|
||||
"name": "My widget's human readable name",
|
||||
"shortName":"Short Name",
|
||||
"icon": "widget.png",
|
||||
"version":"0.01",
|
||||
"description": "A detailed description of my great widget",
|
||||
"tags": "",
|
||||
"tags": "widget",
|
||||
"type": "widget",
|
||||
"storage": [
|
||||
{"name":"+7chname","url":"widget.json"},
|
||||
{"name":"-7chname","url":"widget.js"},
|
||||
{"name":"7chname.wid.js","url":"widget.js"}
|
||||
]
|
||||
}
|
||||
|
|
|
@ -1,16 +1,16 @@
|
|||
/* run widgets in their own function scope so they don't interfere with
|
||||
currently-running apps */
|
||||
(() => {
|
||||
// add the width
|
||||
var xpos = WIDGETPOS.tr-24;/*<the widget width>*/;
|
||||
WIDGETPOS.tr-= 28;/* the widget width plus some extra pixel to keep distance to others */;
|
||||
|
||||
// draw your widget at xpos
|
||||
function draw() {
|
||||
g.reset(); // reset the graphics context to defaults (color/font/etc)
|
||||
// add your code
|
||||
g.drawString("X", this.x, this.y);
|
||||
}
|
||||
|
||||
// add your widget
|
||||
WIDGETS["mywidget"]={draw:draw};
|
||||
|
||||
WIDGETS["mywidget"]={
|
||||
area:"tl", // tl (top left), tr (top right), bl (bottom left), br (bottom right)
|
||||
width: 28, // how wide is the widget? You can change this and call Bangle.drawWidgets() to re-layout
|
||||
draw:draw // called to draw the widget
|
||||
};
|
||||
})()
|
||||
|
|
|
@ -1,4 +0,0 @@
|
|||
{
|
||||
"name":"widgetname", "type":"widget",
|
||||
"src":"-7chname"
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
0.01: New App!
|
||||
0.02: Update version checker for new filename type
|
||||
0.03: Actual pixels as of 5 Mar 2020
|
||||
0.04: Actual pixels as of 9 Mar 2020
|
|
@ -0,0 +1 @@
|
|||
require("heatshrink").decompress(atob("mEwxH+AH4A/AH4AQqoAHFtovlFxQzOiEQF0QwJFwIwSFyIwIF6YuTGBQule7IvuEp150d5GBS+DSBwtO5wABGA4vUFxvIFwXO44wJF7hcEAAejYJQvYFpAwJF7ejRQgAHF7BcH44tLF47xGF6QtNF8l5vIqFA4gv/R/4vZABwv25ovudYwAHvIvfp+dFxlPFy4wHp9PvPHFo/HFwIvEFqYxHEINP43G4/H5vNAYIHBBgQuaGAgvEAA4vEFzIxDq0zh5YCAAvHh8zqwud/1lssPh+AF4+ABYIPBFroABnUPnPNFwvNnMPnQRDFzgvCh/OdgKMC5vOBIIvEGC4bESAeB5wAErqODGDIbGMAekFwekLw4wWDY9liAoBrpdEiASIFzdloIpBAAkQoITJF7aSERhQvUDhYATF/4v/F74A/AH4A5A="))
|
After Width: | Height: | Size: 1.6 KiB |
|
@ -1 +1,8 @@
|
|||
0.02: Modified for use with new bootloader and firmware
|
||||
0.03: add hour ticks, remove timers
|
||||
0.04: add day-date display
|
||||
0.07: make date and face bigger
|
||||
0.08: make dots bigger and date more readable
|
||||
0.09: center date, remove box around it, internal refactor to remove redundant code.
|
||||
0.10: remove debug, refactor seconds to show elapsed secs each time app is displayed
|
||||
0.11: shift face down for widget area, maximize face size, 0 pad single digit date, use locale for date
|
||||
|
|
|
@ -1,94 +1,151 @@
|
|||
const p = Math.PI/2;
|
||||
const PRad = Math.PI/180;
|
||||
// eliminate ide undefined errors
|
||||
let g;
|
||||
let Bangle;
|
||||
|
||||
let intervalRefMin = null;
|
||||
let intervalRefSec = null;
|
||||
// http://forum.espruino.com/conversations/345155/#comment15172813
|
||||
const locale = require('locale');
|
||||
const p = Math.PI / 2;
|
||||
const pRad = Math.PI / 180;
|
||||
const faceWidth = 100; // watch face radius (240/2 - 24px for widget area)
|
||||
const widgetHeight=24+1;
|
||||
let timer = null;
|
||||
let currentDate = new Date();
|
||||
const centerX = g.getWidth() / 2;
|
||||
const centerY = (g.getWidth() / 2) + widgetHeight/2;
|
||||
|
||||
let minuteDate = new Date();
|
||||
let secondDate = new Date();
|
||||
|
||||
function seconds(angle, r) {
|
||||
const a = angle*PRad;
|
||||
const x = 120+Math.sin(a)*r;
|
||||
const y = 120-Math.cos(a)*r;
|
||||
g.fillRect(x-1,y-1,x+1,y+1);
|
||||
}
|
||||
function hand(angle, r1,r2) {
|
||||
const a = angle*PRad;
|
||||
const seconds = (angle) => {
|
||||
const a = angle * pRad;
|
||||
const x = centerX + Math.sin(a) * faceWidth;
|
||||
const y = centerY - Math.cos(a) * faceWidth;
|
||||
|
||||
// if 15 degrees, make hour marker larger
|
||||
const radius = (angle % 15) ? 2 : 4;
|
||||
g.fillCircle(x, y, radius);
|
||||
};
|
||||
|
||||
const hand = (angle, r1, r2) => {
|
||||
const a = angle * pRad;
|
||||
const r3 = 3;
|
||||
g.fillPoly([
|
||||
120+Math.sin(a)*r1,
|
||||
120-Math.cos(a)*r1,
|
||||
120+Math.sin(a+p)*r3,
|
||||
120-Math.cos(a+p)*r3,
|
||||
120+Math.sin(a)*r2,
|
||||
120-Math.cos(a)*r2,
|
||||
120+Math.sin(a-p)*r3,
|
||||
120-Math.cos(a-p)*r3]);
|
||||
}
|
||||
|
||||
function drawAll() {
|
||||
g.fillPoly([
|
||||
Math.round(centerX + Math.sin(a) * r1),
|
||||
Math.round(centerY - Math.cos(a) * r1),
|
||||
Math.round(centerX + Math.sin(a + p) * r3),
|
||||
Math.round(centerY - Math.cos(a + p) * r3),
|
||||
Math.round(centerX + Math.sin(a) * r2),
|
||||
Math.round(centerY - Math.cos(a) * r2),
|
||||
Math.round(centerX + Math.sin(a - p) * r3),
|
||||
Math.round(centerY - Math.cos(a - p) * r3)
|
||||
]);
|
||||
};
|
||||
|
||||
const drawAll = () => {
|
||||
g.clear();
|
||||
secondDate = minuteDate = new Date();
|
||||
currentDate = new Date();
|
||||
// draw hands first
|
||||
onMinute();
|
||||
// draw seconds
|
||||
g.setColor(0,0,0.6);
|
||||
for (let i=0;i<60;i++)
|
||||
seconds(360*i/60, 90);
|
||||
const currentSec = currentDate.getSeconds();
|
||||
// draw all secs
|
||||
|
||||
for (let i = 0; i < 60; i++) {
|
||||
if (i > currentSec) {
|
||||
g.setColor(0, 0, 0.6);
|
||||
} else {
|
||||
g.setColor(0.3, 0.3, 1);
|
||||
}
|
||||
seconds((360 * i) / 60);
|
||||
}
|
||||
onSecond();
|
||||
}
|
||||
|
||||
function onSecond() {
|
||||
g.setColor(0,0,0.6);
|
||||
seconds(360*secondDate.getSeconds()/60, 90);
|
||||
g.setColor(1,0,0);
|
||||
secondDate = new Date();
|
||||
seconds(360*secondDate.getSeconds()/60, 90);
|
||||
g.setColor(1,1,1);
|
||||
};
|
||||
|
||||
}
|
||||
const resetSeconds = () => {
|
||||
g.setColor(0, 0, 0.6);
|
||||
for (let i = 0; i < 60; i++) {
|
||||
seconds((360 * i) / 60);
|
||||
}
|
||||
};
|
||||
|
||||
function onMinute() {
|
||||
g.setColor(0,0,0);
|
||||
hand(360*(minuteDate.getHours() + (minuteDate.getMinutes()/60))/12, -10, 50);
|
||||
hand(360*minuteDate.getMinutes()/60, -10, 82);
|
||||
minuteDate = new Date();
|
||||
g.setColor(1,1,1);
|
||||
hand(360*(minuteDate.getHours() + (minuteDate.getMinutes()/60))/12, -10, 50);
|
||||
hand(360*minuteDate.getMinutes()/60, -10, 82);
|
||||
if(minuteDate.getHours() >= 0 && minuteDate.getMinutes() === 0) {
|
||||
const onSecond = () => {
|
||||
g.setColor(0.3, 0.3, 1);
|
||||
seconds((360 * currentDate.getSeconds()) / 60);
|
||||
if (currentDate.getSeconds() === 59) {
|
||||
resetSeconds();
|
||||
onMinute();
|
||||
}
|
||||
g.setColor(1, 0.7, 0.2);
|
||||
currentDate = new Date();
|
||||
seconds((360 * currentDate.getSeconds()) / 60);
|
||||
g.setColor(1, 1, 1);
|
||||
};
|
||||
|
||||
const drawDate = () => {
|
||||
g.reset();
|
||||
g.setColor(1, 0, 0);
|
||||
g.setFont('6x8', 2);
|
||||
|
||||
const dayString = locale.dow(currentDate, true);
|
||||
// pad left date
|
||||
const dateString = (currentDate.getDate() < 10) ? '0' : '' + currentDate.getDate().toString();
|
||||
const dateDisplay = `${dayString}-${dateString}`;
|
||||
// console.log(`${dayString}|${dateString}`);
|
||||
// center date
|
||||
const l = (g.getWidth() - g.stringWidth(dateDisplay)) / 2;
|
||||
const t = centerY + 37;
|
||||
g.drawString(dateDisplay, l, t, true);
|
||||
// console.log(l, t);
|
||||
};
|
||||
const onMinute = () => {
|
||||
if (currentDate.getHours() === 0 && currentDate.getMinutes() === 0) {
|
||||
g.clear();
|
||||
resetSeconds();
|
||||
}
|
||||
// clear existing hands
|
||||
g.setColor(0, 0, 0);
|
||||
// Hour
|
||||
hand((360 * (currentDate.getHours() + currentDate.getMinutes() / 60)) / 12, -8, faceWidth - 35);
|
||||
// Minute
|
||||
hand((360 * currentDate.getMinutes()) / 60, -8, faceWidth - 10);
|
||||
|
||||
// get new date, then draw new hands
|
||||
currentDate = new Date();
|
||||
g.setColor(1, 0.9, 0.9);
|
||||
// Hour
|
||||
hand((360 * (currentDate.getHours() + currentDate.getMinutes() / 60)) / 12, -8, faceWidth - 35);
|
||||
g.setColor(1, 1, 0.9);
|
||||
// Minute
|
||||
hand((360 * currentDate.getMinutes()) / 60, -8, faceWidth - 10);
|
||||
if (currentDate.getHours() >= 0 && currentDate.getMinutes() === 0) {
|
||||
Bangle.buzz();
|
||||
}
|
||||
}
|
||||
drawDate();
|
||||
};
|
||||
|
||||
function clearTimers() {
|
||||
if(intervalRefMin) {clearInterval(intervalRefMin);}
|
||||
if(intervalRefSec) {clearInterval(intervalRefSec);}
|
||||
}
|
||||
const startTimers = () => {
|
||||
timer = setInterval(onSecond, 1000);
|
||||
};
|
||||
|
||||
function startTimers() {
|
||||
minuteDate = new Date();
|
||||
secondDate = new Date();
|
||||
intervalRefSec = setInterval(onSecond,1000);
|
||||
intervalRefMin = setInterval(onMinute,60*1000);
|
||||
drawAll();
|
||||
}
|
||||
|
||||
Bangle.on('lcdPower',function(on) {
|
||||
Bangle.on('lcdPower', (on) => {
|
||||
if (on) {
|
||||
g.clear();
|
||||
Bangle.drawWidgets();
|
||||
// g.clear();
|
||||
drawAll();
|
||||
startTimers();
|
||||
}else {
|
||||
clearTimers();
|
||||
Bangle.drawWidgets();
|
||||
} else {
|
||||
if (timer) {
|
||||
clearInterval(timer);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
g.clear();
|
||||
resetSeconds();
|
||||
startTimers();
|
||||
drawAll();
|
||||
Bangle.loadWidgets();
|
||||
Bangle.drawWidgets();
|
||||
drawAll();
|
||||
startTimers();
|
||||
|
||||
// Show launcher when middle button pressed
|
||||
setWatch(Bangle.showLauncher, BTN2, {repeat:false,edge:"falling"});
|
||||
setWatch(Bangle.showLauncher, BTN2, { repeat: false, edge: "falling" });
|
||||
|
|
|
@ -1,6 +0,0 @@
|
|||
{
|
||||
"name":"Analog Clock","type":"clock",
|
||||
"icon":"*aclock",
|
||||
"src":"-aclock",
|
||||
"sortorder":-10
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
0.01: New App!
|
||||
0.02: Fix issues with alarm scheduling
|
||||
0.03: More alarm scheduling issues
|
||||
0.04: Tweaks for variable size widget system
|
||||
0.05: Add alarm.boot.js and move code from the bootloader
|
|
@ -0,0 +1,60 @@
|
|||
// Chances are boot0.js got run already and scheduled *another*
|
||||
// 'load(alarm.js)' - so let's remove it first!
|
||||
clearInterval();
|
||||
|
||||
function formatTime(t) {
|
||||
var hrs = 0|t;
|
||||
var mins = Math.round((t-hrs)*60);
|
||||
return hrs+":"+("0"+mins).substr(-2);
|
||||
}
|
||||
|
||||
function getCurrentHr() {
|
||||
var time = new Date();
|
||||
return time.getHours()+(time.getMinutes()/60)+(time.getSeconds()/3600);
|
||||
}
|
||||
|
||||
function showAlarm(alarm) {
|
||||
var msg = formatTime(alarm.hr);
|
||||
var buzzCount = 10;
|
||||
if (alarm.msg)
|
||||
msg += "\n"+alarm.msg;
|
||||
E.showPrompt(msg,{
|
||||
title:"ALARM!",
|
||||
buttons : {"Sleep":true,"Ok":false} // default is sleep so it'll come back in 10 mins
|
||||
}).then(function(sleep) {
|
||||
buzzCount = 0;
|
||||
if (sleep) {
|
||||
alarm.hr += 10/60; // 10 minutes
|
||||
} else {
|
||||
alarm.last = (new Date()).getDate();
|
||||
if (!alarm.rp) alarm.on = false;
|
||||
}
|
||||
require("Storage").write("alarm.json",JSON.stringify(alarms));
|
||||
load();
|
||||
});
|
||||
function buzz() {
|
||||
Bangle.buzz(100).then(()=>{
|
||||
setTimeout(()=>{
|
||||
Bangle.buzz(100).then(function() {
|
||||
if (buzzCount--)
|
||||
setTimeout(buzz, 3000);
|
||||
});
|
||||
},100);
|
||||
});
|
||||
}
|
||||
buzz();
|
||||
}
|
||||
|
||||
// Check for alarms
|
||||
var day = (new Date()).getDate();
|
||||
var hr = getCurrentHr()+10000; // get current time - 10s in future to ensure we alarm if we've started the app a tad early
|
||||
var alarms = require("Storage").readJSON("alarm.json",1)||[];
|
||||
var active = alarms.filter(a=>a.on&&(a.hr<hr)&&(a.last!=day));
|
||||
if (active.length) {
|
||||
// if there's an alarm, show it
|
||||
active = active.sort((a,b)=>a.hr-b.hr);
|
||||
showAlarm(active[0]);
|
||||
} else {
|
||||
// otherwise just go back to default app
|
||||
setTimeout(load, 100);
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
require("heatshrink").decompress(atob("mEwwkGswAhiMRCCAREAo4eHBIQLEAgwYHsIJDiwHB5gACBpIhHCoYZEGA4gFCw4ABGA4HEjgXJ4IXGAwcUB4VEmf//8zogICoJIFAodMBoNDCoIADmgJB4gXIFwXDCwoABngwFC4guB4k/CQXwh4EC+YMCC44iBp4qDC4n/+gNBC41sEIJCEC4v/GAPGC4dhXYRdFC4xhCCYIXCdQRdDC5HzegQXCsxGHC45IDCwQXCUgwXHJAIXGRogXJSIIXcOw4XIPAYXcBwv/mEDBAwXOgtQC65QGC5vzoEAJAx3Nmk/mEABIiPN+dDAQIwFC4zXGFwKRCGAjvMFwQECGAgXI4YuGGAUvAgU8C4/EFwwGCAgdMC4p4EFwobFOwoXDJAIoEAApGBC4xIEABJGHGAapEAAqNBFwwXD4heI+YuBC5BIBVQhdHIw4wD5inFS4IKCCxFmigNCokzCoMzogICoIWIsMRjgPCAA3BiMWC48RBQIXJEgMRFxAJCCw4lEC44IECooOIBAaBJKwhgIAH4ACA=="))
|
|
@ -0,0 +1,104 @@
|
|||
Bangle.loadWidgets();
|
||||
Bangle.drawWidgets();
|
||||
|
||||
var alarms = require("Storage").readJSON("alarm.json",1)||[];
|
||||
/*alarms = [
|
||||
{ on : true,
|
||||
hr : 6.5, // hours + minutes/60
|
||||
msg : "Eat chocolate",
|
||||
last : 0, // last day of the month we alarmed on - so we don't alarm twice in one day!
|
||||
rp : true, // repeat
|
||||
}
|
||||
];*/
|
||||
|
||||
function formatTime(t) {
|
||||
var hrs = 0|t;
|
||||
var mins = Math.round((t-hrs)*60);
|
||||
return hrs+":"+("0"+mins).substr(-2);
|
||||
}
|
||||
|
||||
function getCurrentHr() {
|
||||
var time = new Date();
|
||||
return time.getHours()+(time.getMinutes()/60)+(time.getSeconds()/3600);
|
||||
}
|
||||
|
||||
function showMainMenu() {
|
||||
const menu = {
|
||||
'': { 'title': 'Alarms' },
|
||||
'New Alarm': ()=>editAlarm(-1)
|
||||
};
|
||||
alarms.forEach((alarm,idx)=>{
|
||||
txt = (alarm.on?"on ":"off ")+formatTime(alarm.hr);
|
||||
if (alarm.rp) txt += " (repeat)";
|
||||
menu[txt] = function() {
|
||||
editAlarm(idx);
|
||||
};
|
||||
});
|
||||
menu['< Back'] = ()=>{load();};
|
||||
return E.showMenu(menu);
|
||||
}
|
||||
|
||||
function editAlarm(alarmIndex) {
|
||||
var newAlarm = alarmIndex<0;
|
||||
var hrs = 12;
|
||||
var mins = 0;
|
||||
var en = true;
|
||||
var repeat = true;
|
||||
if (!newAlarm) {
|
||||
var a = alarms[alarmIndex];
|
||||
hrs = 0|a.hr;
|
||||
mins = Math.round((a.hr-hrs)*60);
|
||||
en = a.on;
|
||||
repeat = a.rp;
|
||||
}
|
||||
const menu = {
|
||||
'': { 'title': 'Alarms' },
|
||||
'Hours': {
|
||||
value: hrs,
|
||||
onchange: function(v){if (v<0)v=23;if (v>23)v=0;hrs=v;this.value=v;} // no arrow fn -> preserve 'this'
|
||||
},
|
||||
'Minutes': {
|
||||
value: mins,
|
||||
onchange: function(v){if (v<0)v=59;if (v>59)v=0;mins=v;this.value=v;} // no arrow fn -> preserve 'this'
|
||||
},
|
||||
'Enabled': {
|
||||
value: en,
|
||||
format: v=>v?"On":"Off",
|
||||
onchange: v=>en=v
|
||||
},
|
||||
'Repeat': {
|
||||
value: en,
|
||||
format: v=>v?"Yes":"No",
|
||||
onchange: v=>repeat=v
|
||||
}
|
||||
};
|
||||
function getAlarm() {
|
||||
var hr = hrs+(mins/60);
|
||||
var day = 0;
|
||||
// If alarm is for tomorrow not today (eg, in the past), set day
|
||||
if (hr < getCurrentHr())
|
||||
day = (new Date()).getDate();
|
||||
// Save alarm
|
||||
return {
|
||||
on : en, hr : hr,
|
||||
last : day, rp : repeat
|
||||
};
|
||||
}
|
||||
if (newAlarm) {
|
||||
menu["> New Alarm"] = function() {
|
||||
alarms.push(getAlarm());
|
||||
require("Storage").write("alarm.json",JSON.stringify(alarms));
|
||||
showMainMenu();
|
||||
};
|
||||
} else {
|
||||
menu["> Save"] = function() {
|
||||
alarms[alarmIndex] = getAlarm();
|
||||
require("Storage").write("alarm.json",JSON.stringify(alarms));
|
||||
showMainMenu();
|
||||
};
|
||||
}
|
||||
menu['< Back'] = showMainMenu;
|
||||
return E.showMenu(menu);
|
||||
}
|
||||
|
||||
showMainMenu();
|
After Width: | Height: | Size: 2.2 KiB |
|
@ -0,0 +1,24 @@
|
|||
// check for alarms
|
||||
(function() {
|
||||
var alarms = require('Storage').readJSON('alarm.json',1)||[];
|
||||
var time = new Date();
|
||||
var active = alarms.filter(a=>a.on&&(a.last!=time.getDate()));
|
||||
if (active.length) {
|
||||
active = active.sort((a,b)=>a.hr-b.hr);
|
||||
var hr = time.getHours()+(time.getMinutes()/60)+(time.getSeconds()/3600);
|
||||
if (!require('Storage').read("alarm.js")) {
|
||||
console.log("No alarm app!");
|
||||
require('Storage').write('alarm.json',"[]")
|
||||
} else {
|
||||
var t = 3600000*(active[0].hr-hr);
|
||||
if (t<1000) t=1000;
|
||||
/* execute alarm at the correct time. We avoid execing immediately
|
||||
since this code will get called AGAIN when alarm.js is loaded. alarm.js
|
||||
will then clearInterval() to get rid of this call so it can proceed
|
||||
normally. */
|
||||
setTimeout(function() {
|
||||
load("alarm.js");
|
||||
},t);
|
||||
}
|
||||
}
|
||||
})()
|
|
@ -0,0 +1,11 @@
|
|||
(() => {
|
||||
var alarms = require('Storage').readJSON('alarm.json',1)||[];
|
||||
alarms = alarms.filter(alarm=>alarm.on);
|
||||
if (!alarms.length) return; // no alarms, no widget!
|
||||
delete alarms;
|
||||
// add the widget
|
||||
WIDGETS["alarm"]={area:"tl",width:24,draw:function() {
|
||||
g.setColor(-1);
|
||||
g.drawImage(atob("GBgBAAAAAAAAABgADhhwDDwwGP8YGf+YMf+MM//MM//MA//AA//AA//AA//AA//AA//AB//gD//wD//wAAAAADwAABgAAAAAAAAA"),this.x,this.y);
|
||||
}};
|
||||
})()
|
|
@ -1 +1 @@
|
|||
require("heatshrink").decompress(atob("mEwxH+AH4A/AH4A/AH4Az4/HDDAACCywaSCpgeLBQOjzowRIxZPMBIWj0YvjNRKQSCpIWBEBq/VF6hrKdpwuKABYvXFywlDXjw5aF1gvKF0gv5F0ovvFxQxkF9ztLF8ItJF5XGAAIrfeJYtB4D7TFyQlEFwg8INgZtFF6wuCF4wAFBoPAF7ouNF8AfCF56ZFFyofEF9YgCF5z6GF9y+TC4SAFF9ZgNBgovZMAeczgRJBYovXMAtPAAIQIBYouXDAWjEYUrGBMrBYgvY/15vNVCtAADqoACCs4A/AH4A/AH4A/ABY")
|
||||
require("heatshrink").decompress(atob("mEwxH+AH4A/AH4A/AH4Az4/HDDAACCywaSCpgeLBQOjzowRIxZPMBIWj0YvjNRKQSCpIWBEBq/VF6hrKdpwuKABYvXFywlDXjw5aF1gvKF0gv5F0ovvFxQxkF9ztLF8ItJF5XGAAIrfeJYtB4D7TFyQlEFwg8INgZtFF6wuCF4wAFBoPAF7ouNF8AfCF56ZFFyofEF9YgCF5z6GF9y+TC4SAFF9ZgNBgovZMAeczgRJBYovXMAtPAAIQIBYouXDAWjEYUrGBMrBYgvY/15vNVCtAADqoACCs4A/AH4A/AH4A/ABY"))
|
||||
|
|
|
@ -1 +1 @@
|
|||
require("heatshrink").decompress(atob("mEwxH+AH4A/AH4A/AH4A/ACXJ5PKAQfKAAPJB4nDAAIsaEwQAJ5IrCAAYbFGyBWBFyooCGxQABPAyDEABYuHGxInFAAQGGLyoIGLxQvCFyAvJBIzjKdB6OLagYwDc5K/DFyIVBAAprHF5IsTHaAuKRoSOSABwvMXyZhPF5iNfF9nJp9PGB4vh5PD3u8X86PCFwO8AAS/mGQe9zAACF/CPdF4aPD3rwmGAu9FxRghegQuKL8IvBFxYwhFx6QdFpxedXIIuQLpHJ4YdCBoQDDFQosRLxAaB4YAEIRIQDFyQvEFhYwGCQgvX/wcOCYYuWDYSmCF6CfEF6mlz96vX+z+evVPCZwABCJYAJvVVAAVPAAYTNCJoAJFwYvPCY5gUAH4A/AH4A/AH4A2")
|
||||
require("heatshrink").decompress(atob("mEwxH+AH4A/AH4A/AH4A/ACXJ5PKAQfKAAPJB4nDAAIsaEwQAJ5IrCAAYbFGyBWBFyooCGxQABPAyDEABYuHGxInFAAQGGLyoIGLxQvCFyAvJBIzjKdB6OLagYwDc5K/DFyIVBAAprHF5IsTHaAuKRoSOSABwvMXyZhPF5iNfF9nJp9PGB4vh5PD3u8X86PCFwO8AAS/mGQe9zAACF/CPdF4aPD3rwmGAu9FxRghegQuKL8IvBFxYwhFx6QdFpxedXIIuQLpHJ4YdCBoQDDFQosRLxAaB4YAEIRIQDFyQvEFhYwGCQgvX/wcOCYYuWDYSmCF6CfEF6mlz96vX+z+evVPCZwABCJYAJvVVAAVPAAYTNCJoAJFwYvPCY5gUAH4A/AH4A/AH4A2"))
|
||||
|
|
|
@ -1 +1 @@
|
|||
require("heatshrink").decompress(atob("mEwxH+AH4A/AH4A/AGesAAQuuGAQ1jFQoAKF1wwdFyQycF6wwVFi4wWEiOBq1WqoABvQHBvWALsl6p4ADF4IIBGwSNjMAJdCAAVVGwQwPXjWBFoMrF4IwOFzVWp94lV4vIwNFzS8CvGi0YFCGBSNadohdCF9gAGdsgvuGJTvkGAgABFxv+wAwcAAQsLAAVPF9lPAAOIF1tWF7qMOp4DBxAABXtIADGAWB0oupGAeAvQABwJmGAwmlwQuZAAQuCF4SYDAgRtBBoeswKsDF7gsEF4+BqovfAANWF5VWFwIvZGAf+EAYuDBooOEF8ANJF/4vREIQv7BxIvZEIwvvBwQveEIIEDF9QA/AH4A/AH4ASA=")
|
||||
require("heatshrink").decompress(atob("mEwxH+AH4A/AH4A/AGesAAQuuGAQ1jFQoAKF1wwdFyQycF6wwVFi4wWEiOBq1WqoABvQHBvWALsl6p4ADF4IIBGwSNjMAJdCAAVVGwQwPXjWBFoMrF4IwOFzVWp94lV4vIwNFzS8CvGi0YFCGBSNadohdCF9gAGdsgvuGJTvkGAgABFxv+wAwcAAQsLAAVPF9lPAAOIF1tWF7qMOp4DBxAABXtIADGAWB0oupGAeAvQABwJmGAwmlwQuZAAQuCF4SYDAgRtBBoeswKsDF7gsEF4+BqovfAANWF5VWFwIvZGAf+EAYuDBooOEF8ANJF/4vREIQv7BxIvZEIwvvBwQveEIIEDF9QA/AH4A/AH4ASA="))
|
||||
|
|
|
@ -13,7 +13,7 @@ function next(e) {
|
|||
} while (current && current==last);
|
||||
g.clear();
|
||||
var n = 1 + (0|(Math.random()*3.9));
|
||||
var img = require("Storage").read("*"+current);
|
||||
var img = require("Storage").read("animals-"+current+".img");
|
||||
if (n==1)
|
||||
g.drawImage(img,120,120,{scale:4,rotate:Math.random()-0.5});
|
||||
else
|
||||
|
|
|
@ -1,5 +0,0 @@
|
|||
{
|
||||
"name":"Animals Game", "type":"app",
|
||||
"icon":"*animals",
|
||||
"src":"-animals"
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
0.01: Create astrocalc app
|
|
@ -0,0 +1,348 @@
|
|||
/**
|
||||
* Inspired by: https://www.timeanddate.com
|
||||
*/
|
||||
|
||||
const SunCalc = require("suncalc.js");
|
||||
|
||||
function drawMoon(phase, x, y) {
|
||||
const moonImgFiles = [
|
||||
"new",
|
||||
"waxing-crescent",
|
||||
"first-quarter",
|
||||
"waxing-gibbous",
|
||||
"full",
|
||||
"waning-gibbous",
|
||||
"last-quarter",
|
||||
"waning-crescent",
|
||||
];
|
||||
|
||||
img = require("Storage").read(`${moonImgFiles[phase]}.img`);
|
||||
// image width & height = 92px
|
||||
g.drawImage(img, x - parseInt(92 / 2), y);
|
||||
}
|
||||
|
||||
// linear interpolation between two values a and b
|
||||
// u controls amount of a/b and is in range [0.0,1.0]
|
||||
function lerp(a,b,u) {
|
||||
return (1-u) * a + u * b;
|
||||
}
|
||||
|
||||
function titlizeKey(key) {
|
||||
return (key[0].toUpperCase() + key.slice(1)).match(/[A-Z][a-z]+/g).join(" ");
|
||||
}
|
||||
|
||||
function dateToTimeString(date) {
|
||||
const hrs = ("0" + date.getHours()).substr(-2);
|
||||
const mins = ("0" + date.getMinutes()).substr(-2);
|
||||
const secs = ("0" + date.getMinutes()).substr(-2);
|
||||
|
||||
return `${hrs}:${mins}:${secs}`;
|
||||
}
|
||||
|
||||
function drawTitle(key) {
|
||||
const fontHeight = 16;
|
||||
const x = 0;
|
||||
const x2 = g.getWidth() - 1;
|
||||
const y = fontHeight + 26;
|
||||
const y2 = g.getHeight() - 1;
|
||||
const title = titlizeKey(key);
|
||||
|
||||
g.setFont("6x8", 2);
|
||||
g.setFontAlign(0,-1);
|
||||
g.drawString(title,(x+x2)/2,y-fontHeight-2);
|
||||
g.drawLine(x,y-2,x2,y-2);
|
||||
}
|
||||
|
||||
/**
|
||||
* @params {Number} angle Angle of point around a radius
|
||||
* @params {Number} radius Radius of the point to be drawn, default 2
|
||||
* @params {Object} color Color of the point
|
||||
* @params {Number} color.r Red 0-1
|
||||
* @params {Number} color.g Green 0-1
|
||||
* @params {Number} color.b Blue 0-1
|
||||
*/
|
||||
function drawPoint(angle, radius, color) {
|
||||
const pRad = Math.PI / 180;
|
||||
const faceWidth = 80; // watch face radius
|
||||
const centerPx = g.getWidth() / 2;
|
||||
|
||||
const a = angle * pRad;
|
||||
const x = centerPx + Math.sin(a) * faceWidth;
|
||||
const y = centerPx - Math.cos(a) * faceWidth;
|
||||
|
||||
if (!radius) radius = 2;
|
||||
|
||||
g.setColor(color.r, color.g, color.b);
|
||||
g.fillCircle(x, y + 20, radius);
|
||||
}
|
||||
|
||||
function drawPoints() {
|
||||
const startColor = {r: 140, g: 255, b: 255}; // light blue
|
||||
const endColor = {r: 0, g: 0, b: 140}; // dark turquoise
|
||||
|
||||
const steps = 60;
|
||||
const step_u = 1.0 / (steps / 2);
|
||||
let u = 0.0;
|
||||
|
||||
for (let i = 0; i < steps; i++) {
|
||||
const colR = lerp(startColor.r, endColor.r, u) / 255;
|
||||
const colG = lerp(startColor.g, endColor.g, u) / 255;
|
||||
const colB = lerp(startColor.b, endColor.b, u) / 255;
|
||||
const col = {r: colR, g: colG, b: colB};
|
||||
|
||||
if (i >= 0 && i <= 30) {
|
||||
u += step_u;
|
||||
} else {
|
||||
u -= step_u;
|
||||
}
|
||||
|
||||
drawPoint((360 * i) / steps, 2, col);
|
||||
}
|
||||
}
|
||||
|
||||
function drawData(title, obj, startX, startY) {
|
||||
g.clear();
|
||||
drawTitle(title);
|
||||
|
||||
let xPos, yPos;
|
||||
|
||||
if (typeof(startX) === "undefined" || startX === null) {
|
||||
// Center text
|
||||
g.setFontAlign(0,-1);
|
||||
xPos = (0 + g.getWidth() - 2) / 2;
|
||||
} else {
|
||||
xPos = startX;
|
||||
}
|
||||
|
||||
if (typeof(startY) === "undefined") {
|
||||
yPos = 5;
|
||||
} else {
|
||||
yPos = startY;
|
||||
}
|
||||
|
||||
g.setFont("6x8", 1);
|
||||
|
||||
Object.keys(obj).forEach((key) => {
|
||||
g.drawString(`${key}: ${obj[key]}`, xPos, yPos += 20);
|
||||
});
|
||||
|
||||
g.flip();
|
||||
}
|
||||
|
||||
function drawMoonPositionPage(gps, title) {
|
||||
const pos = SunCalc.getMoonPosition(new Date(), gps.lat, gps.lon);
|
||||
|
||||
const pageData = {
|
||||
Azimuth: pos.azimuth.toFixed(2),
|
||||
Altitude: pos.altitude.toFixed(2),
|
||||
Distance: `${pos.distance.toFixed(0)} km`,
|
||||
"Parallactic Ang": pos.parallacticAngle.toFixed(2),
|
||||
};
|
||||
const azimuthDegrees = parseInt(pos.azimuth * 180 / Math.PI);
|
||||
|
||||
drawData(title, pageData, null, 80);
|
||||
drawPoints();
|
||||
drawPoint(azimuthDegrees, 8, {r: 1, g: 1, b: 1});
|
||||
|
||||
let m = setWatch(() => {
|
||||
let m = moonIndexPageMenu(gps);
|
||||
}, BTN3, {repeat: false, edge: "falling"});
|
||||
}
|
||||
|
||||
function drawMoonIlluminationPage(gps, title) {
|
||||
const phaseNames = [
|
||||
"New Moon", "Waxing Crescent", "First Quarter", "Waxing Gibbous",
|
||||
"Full Moon", "Waning Gibbous", "Last Quater", "Waning Crescent",
|
||||
];
|
||||
|
||||
const phase = SunCalc.getMoonIllumination(new Date());
|
||||
const pageData = {
|
||||
Phase: phaseNames[phase.phase],
|
||||
};
|
||||
|
||||
drawData(title, pageData, null, 35);
|
||||
drawMoon(phase.phase, g.getWidth() / 2, g.getHeight() / 2);
|
||||
|
||||
let m = setWatch(() => {
|
||||
let m = moonIndexPageMenu(gps);
|
||||
}, BTN3, {repease: false, edge: "falling"});
|
||||
}
|
||||
|
||||
|
||||
function drawMoonTimesPage(gps, title) {
|
||||
const times = SunCalc.getMoonTimes(new Date(), gps.lat, gps.lon);
|
||||
|
||||
const pageData = {
|
||||
Rise: dateToTimeString(times.rise),
|
||||
Set: dateToTimeString(times.set),
|
||||
};
|
||||
|
||||
drawData(title, pageData, null, 105);
|
||||
drawPoints();
|
||||
|
||||
// Draw the moon rise position
|
||||
const risePos = SunCalc.getMoonPosition(times.rise, gps.lat, gps.lon);
|
||||
const riseAzimuthDegrees = parseInt(risePos.azimuth * 180 / Math.PI);
|
||||
drawPoint(riseAzimuthDegrees, 8, {r: 1, g: 1, b: 1});
|
||||
|
||||
// Draw the moon set position
|
||||
const setPos = SunCalc.getMoonPosition(times.set, gps.lat, gps.lon);
|
||||
const setAzimuthDegrees = parseInt(setPos.azimuth * 180 / Math.PI);
|
||||
drawPoint(setAzimuthDegrees, 8, {r: 1, g: 1, b: 1});
|
||||
|
||||
let m = setWatch(() => {
|
||||
let m = moonIndexPageMenu(gps);
|
||||
}, BTN3, {repease: false, edge: "falling"});
|
||||
}
|
||||
|
||||
function drawSunShowPage(gps, key, date) {
|
||||
const pos = SunCalc.getPosition(date, gps.lat, gps.lon);
|
||||
|
||||
const hrs = ("0" + date.getHours()).substr(-2);
|
||||
const mins = ("0" + date.getMinutes()).substr(-2);
|
||||
const secs = ("0" + date.getMinutes()).substr(-2);
|
||||
const time = `${hrs}:${mins}:${secs}`;
|
||||
|
||||
const azimuth = Number(pos.azimuth.toFixed(2));
|
||||
const azimuthDegrees = parseInt(pos.azimuth * 180 / Math.PI);
|
||||
const altitude = Number(pos.altitude.toFixed(2));
|
||||
|
||||
const pageData = {
|
||||
Time: time,
|
||||
Altitude: altitude,
|
||||
Azimumth: azimuth,
|
||||
Degrees: azimuthDegrees
|
||||
};
|
||||
|
||||
drawData(key, pageData, null, 85);
|
||||
|
||||
drawPoints();
|
||||
|
||||
// Draw the suns position
|
||||
drawPoint(azimuthDegrees, 8, {r: 1, g: 1, b: 0});
|
||||
|
||||
m = setWatch(() => {
|
||||
m = sunIndexPageMenu(gps);
|
||||
}, BTN3, {repeat: false, edge: "falling"});
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
function sunIndexPageMenu(gps) {
|
||||
const sunTimes = SunCalc.getTimes(new Date(), gps.lat, gps.lon);
|
||||
|
||||
const sunMenu = {
|
||||
"": {
|
||||
"title": "-- Sun --",
|
||||
},
|
||||
"Current Pos": () => {
|
||||
m = E.showMenu();
|
||||
drawSunShowPage(gps, "Current Pos", new Date());
|
||||
},
|
||||
};
|
||||
|
||||
Object.keys(sunTimes).sort().reduce((menu, key) => {
|
||||
const title = titlizeKey(key);
|
||||
menu[title] = () => {
|
||||
m = E.showMenu();
|
||||
drawSunShowPage(gps, key, sunTimes[key]);
|
||||
};
|
||||
return menu;
|
||||
}, sunMenu);
|
||||
|
||||
sunMenu["< Back"] = () => m = indexPageMenu(gps);
|
||||
|
||||
return E.showMenu(sunMenu);
|
||||
}
|
||||
|
||||
|
||||
function moonIndexPageMenu(gps) {
|
||||
const moonMenu = {
|
||||
"": {
|
||||
"title": "-- Moon --",
|
||||
},
|
||||
"Times": () => {
|
||||
m = E.showMenu();
|
||||
drawMoonTimesPage(gps, "Times");
|
||||
},
|
||||
"Position": () => {
|
||||
m = E.showMenu();
|
||||
drawMoonPositionPage(gps, "Position");
|
||||
},
|
||||
"Illumination": () => {
|
||||
m = E.showMenu();
|
||||
drawMoonIlluminationPage(gps, "Illumination");
|
||||
},
|
||||
"< Back": () => m = indexPageMenu(gps),
|
||||
};
|
||||
|
||||
return E.showMenu(moonMenu);
|
||||
}
|
||||
|
||||
function indexPageMenu(gps) {
|
||||
const menu = {
|
||||
"": {
|
||||
"title": "Select",
|
||||
},
|
||||
"Sun": () => {
|
||||
m = sunIndexPageMenu(gps);
|
||||
},
|
||||
"Moon": () => {
|
||||
m = moonIndexPageMenu(gps);
|
||||
},
|
||||
"< Exit": () => { load(); }
|
||||
};
|
||||
|
||||
return E.showMenu(menu);
|
||||
}
|
||||
|
||||
/**
|
||||
* GPS wait page, shows GPS locating animation until it gets a lock, then moves to the Sun page
|
||||
*/
|
||||
function drawGPSWaitPage() {
|
||||
const img = require("heatshrink").decompress(atob("mEwxH+AH4A/AH4AW43GF1wwsFwYwqFwowoFw4wmFxIwdE5YAPF/4vM5nN6YAE5vMF8YtHGIgvhFpQxKF7AuOGA4vXFyAwGF63MFyIABF6xeWMC4UDLwvNGpAJG5gwSdhIIDRBLyWCIgcJHAgJJDoouQF4vMQoICBBJoeGFx6GGACIfHL6YvaX6gvZeCIdFc4gAFXogvGFxgwFDwovQCAguOGAnMMBxeG5guTGAggGGAwNKFySREcA3N5vM5gDBdpQvXEY4AKXqovGGCKbFF7AwPZQwvZGJgtGF7vGdQItG5gSIF7gASF/44WEzgwRF0wwHF1AwFF1QwDF1gvwAH4A/AFAA=="))
|
||||
|
||||
g.clear();
|
||||
g.drawImage(img, 100, 50);
|
||||
g.setFont("6x8", 1);
|
||||
g.drawString("Astrocalc v0.01", 80, 105);
|
||||
g.drawString("Locating GPS", 85, 140);
|
||||
g.drawString("Please wait...", 80, 155);
|
||||
g.flip();
|
||||
|
||||
const DEBUG = false;
|
||||
if (DEBUG) {
|
||||
const gps = {
|
||||
"lat": 56.45783133333,
|
||||
"lon": -3.02188583333,
|
||||
"alt": 75.3,
|
||||
"speed": 0.070376,
|
||||
"course": NaN,
|
||||
"time":new Date(),
|
||||
"satellites": 4,
|
||||
"fix": 1
|
||||
};
|
||||
|
||||
m = indexPageMenu(gps);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
Bangle.on('GPS', (gps) => {
|
||||
if (gps.fix === 0) return;
|
||||
|
||||
Bangle.setGPSPower(0);
|
||||
Bangle.buzz();
|
||||
Bangle.setLCDPower(true);
|
||||
|
||||
m = indexPageMenu(gps);
|
||||
});
|
||||
}
|
||||
|
||||
function init() {
|
||||
Bangle.setGPSPower(1);
|
||||
drawGPSWaitPage();
|
||||
}
|
||||
|
||||
let m;
|
||||
init();
|
|
@ -0,0 +1 @@
|
|||
require("heatshrink").decompress(atob("mEwxH+AH4A/AH4AW43GF1wwsFwYwqFwowoFw4wmFxIwdE5YAPF/4vM5nN6YAE5vMF8YtHGIgvhFpQxKF7AuOGA4vXFyAwGF63MFyIABF6xeWMC4UDLwvNGpAJG5gwSdhIIDRBLyWCIgcJHAgJJDoouQF4vMQoICBBJoeGFx6GGACIfHL6YvaX6gvZeCIdFc4gAFXogvGFxgwFDwovQCAguOGAnMMBxeG5guTGAggGGAwNKFySREcA3N5vM5gDBdpQvXEY4AKXqovGGCKbFF7AwPZQwvZGJgtGF7vGdQItG5gSIF7gASF/44WEzgwRF0wwHF1AwFF1QwDF1gvwAH4A/AFAA=="))
|
After Width: | Height: | Size: 952 B |
|
@ -0,0 +1 @@
|
|||
require("heatshrink").decompress(atob("rlcgI1ygf4BZM/BZMD//wCxP/8AWJ/+ACxP+CxQ6ICwP/4AWJERAWCEQ4WCERAWCEQ4WDOg4WCNA4WD/gWKRYwWDHI4WDHIwWDHI4WDHIwWEOYwWDHIwWEKAwWD/4WKKAwWEKAoWEYgwWPM4wWEM4oWQM4oWEPwwWbPwoWESowW/C34WOZ1vACxP8Cyv4CxWACyoKFCwiUFCwhmGCwh9FCwhmGCwhmFCwhPGCwgKFCwg4GCwZPGCwg4GCwY4GCwgKGCwY4GCwZxGCwjBFCwghHCwQhHCwYhHCwQhHCwRlHCwSHHCwYKICwI3HCwQKJAFAA=="))
|
|
@ -0,0 +1 @@
|
|||
require("heatshrink").decompress(atob("rlcgJC/AD8B//4BRILJBQP/+AKGn4LC4AKFh4KC/4KFgYKD/gLFv4LD8AKEj4KD/+AEJAiGEIgiFIYhFFOAQADOghlDNA0HBQv+Q4wADRYZaFLgg4GHIg4GHIY4GHIhxFOYhxGOYgKHKARPHKARPHKAZPHKATBFYgoWKMw5nDMw5nCCyx9IPwQKIPwIW/C34WJZ1sDBQ/8CwM/BY/ACxkfBY+AgEBBQ/4CwJ+IBQJ+IPoJnIMwRnIMwJQIJ4RQIJ4JQIJ4RQIBQQ5HHAQ5HHAY5HHARzHOIRzHOIbEHYIIACLgpaDEQwhFEQohEIopDENAplERYwKGOgZwEBYoKIAH4AXA=="))
|
|
@ -0,0 +1 @@
|
|||
require("heatshrink").decompress(atob("rlcgI0xgP8BRP/4ALI/4WJv4WJj4WJg//CxA3BCxM/CxIhCCw4hCCxAhCCw4hCCxAKCCw5lBCxEDCxSHBCxA4DCw4KCCw44DCww4DCw5xCCw44DCw5PDCw0PCxQKDCwxPDCwzBDCyRmECwxmDCyRmDCwx9ECzoKDCwyUEC34W/CyDOtn4WJgYWVgIWKj4WVPwgWFSogWGM4gWGPwYWGM4gWGM4YWGKAgWGKAYWGHIgWGKAYWHHIYWGHIYWHHIYWGHIYWHOYYWHYgQWHEQYWHEQQWIEQQWHEQQWINAQWIRYIWIOgQWIHQIWJBYIWJAFI="))
|
|
@ -0,0 +1 @@
|
|||
require("heatshrink").decompress(atob("rlcgIGDh///4RHBQQLHg4KC/wKFgIKC//4BYt/BYfgBQkfBQf/wAsHFw4HCBwXwBQc/AwYLB4AhEIARIBEQn//gECgYiEIYJ2FIoQQBE4YzBDgd/NoguBNAUPKoo/BB4YhEEQIdCAYYiECQMHUwwHDEIweBLgMPWIwiBAQSlENwQTBDIQAFFQMDHAw5BOYN/HAwfB8ANCAAofCHA45B+EPHA4UBKQQAGMgMfUYQAFv+DJ45QCn5PHKAPDJ45QB/hmICwPnT4yhC/1/Mw5nBCxZmIM4P/PpB+BC34WEVZCsB/7CIYYIWWOX4WbfiwWL/gKHgf+n/ABY8/4YWJ/k/VhF/4LDIg/4j5nI/+APxEP+EPM48BCgN/KA5CBg5QHMwINCJ4/AgY5Hh4fBj45GHAKeBAQSfFMgIZCHAoqCv45GA4QOBEQsfDwQDDEIgSC/4iFv6dCg4iFj60Dn4iEEIKRCL4K5E/5uDh4QDDgKFEv4uDj4/EE4IRCDYIzEAwIvBAQKnFEQIADMIhFBAAayFNAIACMoZtDBYa9GFwbrHBQR2EBYoKEA=="))
|
|
@ -0,0 +1,328 @@
|
|||
/*
|
||||
(c) 2011-2015, Vladimir Agafonkin
|
||||
SunCalc is a JavaScript library for calculating sun/moon position and light phases.
|
||||
https://github.com/mourner/suncalc
|
||||
*/
|
||||
|
||||
(function () { 'use strict';
|
||||
|
||||
// shortcuts for easier to read formulas
|
||||
|
||||
var PI = Math.PI,
|
||||
sin = Math.sin,
|
||||
cos = Math.cos,
|
||||
tan = Math.tan,
|
||||
asin = Math.asin,
|
||||
atan = Math.atan2,
|
||||
acos = Math.acos,
|
||||
rad = PI / 180;
|
||||
|
||||
// sun calculations are based on http://aa.quae.nl/en/reken/zonpositie.html formulas
|
||||
|
||||
|
||||
// date/time constants and conversions
|
||||
|
||||
var dayMs = 1000 * 60 * 60 * 24,
|
||||
J1970 = 2440588,
|
||||
J2000 = 2451545;
|
||||
|
||||
function toJulian(date) { return date.valueOf() / dayMs - 0.5 + J1970; }
|
||||
function fromJulian(j) { return (j + 0.5 - J1970) * dayMs; }
|
||||
function toDays(date) { return toJulian(date) - J2000; }
|
||||
|
||||
|
||||
// general calculations for position
|
||||
|
||||
var e = rad * 23.4397; // obliquity of the Earth
|
||||
|
||||
function rightAscension(l, b) { return atan(sin(l) * cos(e) - tan(b) * sin(e), cos(l)); }
|
||||
function declination(l, b) { return asin(sin(b) * cos(e) + cos(b) * sin(e) * sin(l)); }
|
||||
|
||||
function azimuth(H, phi, dec) { return atan(sin(H), cos(H) * sin(phi) - tan(dec) * cos(phi)); }
|
||||
function altitude(H, phi, dec) { return asin(sin(phi) * sin(dec) + cos(phi) * cos(dec) * cos(H)); }
|
||||
|
||||
function siderealTime(d, lw) { return rad * (280.16 + 360.9856235 * d) - lw; }
|
||||
|
||||
function astroRefraction(h) {
|
||||
if (h < 0) // the following formula works for positive altitudes only.
|
||||
h = 0; // if h = -0.08901179 a div/0 would occur.
|
||||
|
||||
// formula 16.4 of "Astronomical Algorithms" 2nd edition by Jean Meeus (Willmann-Bell, Richmond) 1998.
|
||||
// 1.02 / tan(h + 10.26 / (h + 5.10)) h in degrees, result in arc minutes -> converted to rad:
|
||||
return 0.0002967 / Math.tan(h + 0.00312536 / (h + 0.08901179));
|
||||
}
|
||||
|
||||
// general sun calculations
|
||||
|
||||
function solarMeanAnomaly(d) { return rad * (357.5291 + 0.98560028 * d); }
|
||||
|
||||
function eclipticLongitude(M) {
|
||||
|
||||
var C = rad * (1.9148 * sin(M) + 0.02 * sin(2 * M) + 0.0003 * sin(3 * M)), // equation of center
|
||||
P = rad * 102.9372; // perihelion of the Earth
|
||||
|
||||
return M + C + P + PI;
|
||||
}
|
||||
|
||||
function sunCoords(d) {
|
||||
|
||||
var M = solarMeanAnomaly(d),
|
||||
L = eclipticLongitude(M);
|
||||
|
||||
return {
|
||||
dec: declination(L, 0),
|
||||
ra: rightAscension(L, 0)
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
var SunCalc = {};
|
||||
|
||||
|
||||
// calculates sun position for a given date and latitude/longitude
|
||||
|
||||
SunCalc.getPosition = function (date, lat, lng) {
|
||||
|
||||
var lw = rad * -lng,
|
||||
phi = rad * lat,
|
||||
d = toDays(date),
|
||||
|
||||
c = sunCoords(d),
|
||||
H = siderealTime(d, lw) - c.ra;
|
||||
|
||||
return {
|
||||
azimuth: azimuth(H, phi, c.dec),
|
||||
altitude: altitude(H, phi, c.dec)
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
// sun times configuration (angle, morning name, evening name)
|
||||
|
||||
var times = SunCalc.times = [
|
||||
[-0.833, 'sunrise', 'sunset' ],
|
||||
[ -0.3, 'sunriseEnd', 'sunsetStart' ],
|
||||
[ -6, 'dawn', 'dusk' ],
|
||||
[ -12, 'nauticalDawn', 'nauticalDusk'],
|
||||
[ -18, 'nightEnd', 'night' ],
|
||||
[ 6, 'goldenHourEnd', 'goldenHour' ]
|
||||
];
|
||||
|
||||
// adds a custom time to the times config
|
||||
|
||||
SunCalc.addTime = function (angle, riseName, setName) {
|
||||
times.push([angle, riseName, setName]);
|
||||
};
|
||||
|
||||
|
||||
// calculations for sun times
|
||||
|
||||
var J0 = 0.0009;
|
||||
|
||||
function julianCycle(d, lw) { return Math.round(d - J0 - lw / (2 * PI)); }
|
||||
|
||||
function approxTransit(Ht, lw, n) { return J0 + (Ht + lw) / (2 * PI) + n; }
|
||||
function solarTransitJ(ds, M, L) { return J2000 + ds + 0.0053 * sin(M) - 0.0069 * sin(2 * L); }
|
||||
|
||||
function hourAngle(h, phi, d) { return acos((sin(h) - sin(phi) * sin(d)) / (cos(phi) * cos(d))); }
|
||||
function observerAngle(height) { return -2.076 * Math.sqrt(height) / 60; }
|
||||
|
||||
// returns set time for the given sun altitude
|
||||
function getSetJ(h, lw, phi, dec, n, M, L) {
|
||||
|
||||
var w = hourAngle(h, phi, dec),
|
||||
a = approxTransit(w, lw, n);
|
||||
return solarTransitJ(a, M, L);
|
||||
}
|
||||
|
||||
|
||||
// calculates sun times for a given date, latitude/longitude, and, optionally,
|
||||
// the observer height (in meters) relative to the horizon
|
||||
|
||||
SunCalc.getTimes = function (date, lat, lng, height) {
|
||||
|
||||
height = height || 0;
|
||||
|
||||
var lw = rad * -lng,
|
||||
phi = rad * lat,
|
||||
|
||||
dh = observerAngle(height),
|
||||
|
||||
d = toDays(date),
|
||||
n = julianCycle(d, lw),
|
||||
ds = approxTransit(0, lw, n),
|
||||
|
||||
M = solarMeanAnomaly(ds),
|
||||
L = eclipticLongitude(M),
|
||||
dec = declination(L, 0),
|
||||
|
||||
Jnoon = solarTransitJ(ds, M, L),
|
||||
|
||||
i, len, time, h0, Jset, Jrise;
|
||||
|
||||
|
||||
var result = {
|
||||
solarNoon: new Date(fromJulian(Jnoon)),
|
||||
nadir: new Date(fromJulian(Jnoon - 0.5))
|
||||
};
|
||||
|
||||
for (i = 0, len = times.length; i < len; i += 1) {
|
||||
time = times[i];
|
||||
h0 = (time[0] + dh) * rad;
|
||||
|
||||
Jset = getSetJ(h0, lw, phi, dec, n, M, L);
|
||||
Jrise = Jnoon - (Jset - Jnoon);
|
||||
|
||||
result[time[1]] = new Date(fromJulian(Jrise) - (dayMs / 2));
|
||||
result[time[2]] = new Date(fromJulian(Jset) + (dayMs / 2));
|
||||
}
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
|
||||
// moon calculations, based on http://aa.quae.nl/en/reken/hemelpositie.html formulas
|
||||
|
||||
function moonCoords(d) { // geocentric ecliptic coordinates of the moon
|
||||
|
||||
var L = rad * (218.316 + 13.176396 * d), // ecliptic longitude
|
||||
M = rad * (134.963 + 13.064993 * d), // mean anomaly
|
||||
F = rad * (93.272 + 13.229350 * d), // mean distance
|
||||
|
||||
l = L + rad * 6.289 * sin(M), // longitude
|
||||
b = rad * 5.128 * sin(F), // latitude
|
||||
dt = 385001 - 20905 * cos(M); // distance to the moon in km
|
||||
|
||||
return {
|
||||
ra: rightAscension(l, b),
|
||||
dec: declination(l, b),
|
||||
dist: dt
|
||||
};
|
||||
}
|
||||
|
||||
SunCalc.getMoonPosition = function (date, lat, lng) {
|
||||
|
||||
var lw = rad * -lng,
|
||||
phi = rad * lat,
|
||||
d = toDays(date),
|
||||
|
||||
c = moonCoords(d),
|
||||
H = siderealTime(d, lw) - c.ra,
|
||||
h = altitude(H, phi, c.dec),
|
||||
// formula 14.1 of "Astronomical Algorithms" 2nd edition by Jean Meeus (Willmann-Bell, Richmond) 1998.
|
||||
pa = atan(sin(H), tan(phi) * cos(c.dec) - sin(c.dec) * cos(H));
|
||||
|
||||
h = h + astroRefraction(h); // altitude correction for refraction
|
||||
|
||||
return {
|
||||
azimuth: azimuth(H, phi, c.dec),
|
||||
altitude: h,
|
||||
distance: c.dist,
|
||||
parallacticAngle: pa
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
// calculations for illumination parameters of the moon,
|
||||
// based on http://idlastro.gsfc.nasa.gov/ftp/pro/astro/mphase.pro formulas and
|
||||
// Chapter 48 of "Astronomical Algorithms" 2nd edition by Jean Meeus (Willmann-Bell, Richmond) 1998.
|
||||
|
||||
// Function updated from gist: https://gist.github.com/endel/dfe6bb2fbe679781948c
|
||||
|
||||
SunCalc.getMoonIllumination = function (date) {
|
||||
let month = date.getMonth();
|
||||
let year = date.getFullYear();
|
||||
let day = date.getDate();
|
||||
|
||||
let c = 0;
|
||||
let e = 0;
|
||||
let jd = 0;
|
||||
let b = 0;
|
||||
|
||||
if (month < 3) {
|
||||
year--;
|
||||
month += 12;
|
||||
}
|
||||
|
||||
++month;
|
||||
c = 365.25 * year;
|
||||
e = 30.6 * month;
|
||||
jd = c + e + day - 694039.09; // jd is total days elapsed
|
||||
jd /= 29.5305882; // divide by the moon cycle
|
||||
b = parseInt(jd); // int(jd) -> b, take integer part of jd
|
||||
jd -= b; // subtract integer part to leave fractional part of original jd
|
||||
b = Math.round(jd * 8); // scale fraction from 0-8 and round
|
||||
|
||||
if (b >= 8) b = 0; // 0 and 8 are the same so turn 8 into 0
|
||||
|
||||
return {phase: b};
|
||||
};
|
||||
|
||||
|
||||
function hoursLater(date, h) {
|
||||
return new Date(date.valueOf() + h * dayMs / 24);
|
||||
}
|
||||
|
||||
// calculations for moon rise/set times are based on http://www.stargazing.net/kepler/moonrise.html article
|
||||
|
||||
SunCalc.getMoonTimes = function (date, lat, lng, inUTC) {
|
||||
var t = date;
|
||||
if (inUTC) t.setUTCHours(0, 0, 0, 0);
|
||||
else t.setHours(0, 0, 0, 0);
|
||||
|
||||
var hc = 0.133 * rad,
|
||||
h0 = SunCalc.getMoonPosition(t, lat, lng).altitude - hc,
|
||||
h1, h2, rise, set, a, b, xe, ye, d, roots, x1, x2, dx;
|
||||
|
||||
// go in 2-hour chunks, each time seeing if a 3-point quadratic curve crosses zero (which means rise or set)
|
||||
for (var i = 1; i <= 24; i += 2) {
|
||||
h1 = SunCalc.getMoonPosition(hoursLater(t, i), lat, lng).altitude - hc;
|
||||
h2 = SunCalc.getMoonPosition(hoursLater(t, i + 1), lat, lng).altitude - hc;
|
||||
|
||||
a = (h0 + h2) / 2 - h1;
|
||||
b = (h2 - h0) / 2;
|
||||
xe = -b / (2 * a);
|
||||
ye = (a * xe + b) * xe + h1;
|
||||
d = b * b - 4 * a * h1;
|
||||
roots = 0;
|
||||
|
||||
if (d >= 0) {
|
||||
dx = Math.sqrt(d) / (Math.abs(a) * 2);
|
||||
x1 = xe - dx;
|
||||
x2 = xe + dx;
|
||||
if (Math.abs(x1) <= 1) roots++;
|
||||
if (Math.abs(x2) <= 1) roots++;
|
||||
if (x1 < -1) x1 = x2;
|
||||
}
|
||||
|
||||
if (roots === 1) {
|
||||
if (h0 < 0) rise = i + x1;
|
||||
else set = i + x1;
|
||||
|
||||
} else if (roots === 2) {
|
||||
rise = i + (ye < 0 ? x2 : x1);
|
||||
set = i + (ye < 0 ? x1 : x2);
|
||||
}
|
||||
|
||||
if (rise && set) break;
|
||||
|
||||
h0 = h2;
|
||||
}
|
||||
|
||||
var result = {};
|
||||
|
||||
if (rise) result.rise = hoursLater(t, rise);
|
||||
if (set) result.set = hoursLater(t, set);
|
||||
|
||||
if (!rise && !set) result[ye > 0 ? 'alwaysUp' : 'alwaysDown'] = true;
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
|
||||
// export as Node module / AMD module / browser variable
|
||||
if (typeof exports === 'object' && typeof module !== 'undefined') module.exports = SunCalc;
|
||||
else if (typeof define === 'function' && define.amd) define(SunCalc);
|
||||
else global.SunCalc = SunCalc;
|
||||
|
||||
}());
|
|
@ -0,0 +1 @@
|
|||
require("heatshrink").decompress(atob("rlcgJC/ABHgBRN8BRMfwAKIg/4CxP/BRM/HBMH/wKIgP/4AhJ/ghJ/5PJ/5PJj4WJgf/+AWIv5mJHAIWJ/5mJHAJ9IHAIWJn59JHAJ9JJ4IWIh4WK/4WJJ4KUIYIKUJJ4IWIMwIWgMwIWIPoLCJCwLCICxYKBCxCUBC34W/Cya3WCxr8In78JgYWhj4WJgIWKPwP8SpXAM5IWJPwIWIKAIWJM4PgKBP+CxBQBCxA5CBRBQBYZA5CBRA5BSpA5CSpA5BCxJzBPxDEBPxIiBM5MDPxJFBM5IiBKBMBKBKLBKBMAhwKJAH4ABA="))
|
|
@ -0,0 +1 @@
|
|||
require("heatshrink").decompress(atob("rlcgI0xgP/wAKJ/wWI///+AKHv4LBEQ8fBQP8BQ0HBQP/8A3HAAQWGn4KCHIwhDHIwhE/AhJ//AEJJQGBQZQGMoQABRQsDCwhQFQ4RnHHAgWGBQhnFHAhnFHAoWFOIhnFHAp+FJ4oWEh4WKBQp+EJ4qVEYIgWRMwwWEMwoWLVghmFVgh9GCzYKGCwaUGC34W/CxzOtn4WJgYKF/wWK8AKCgIWKj4WVPwwWDSo38BQZnG4B+JCwhnGCwhnF/AKDKA2AKBIWEHIwKEKAqrDHI4KEHIp9EHIqUEHIxmEOYp9EYgxmEEQpmFEQoKFEQhmFEQhPGNAhPFRYg4GOggKHHQSIFBYghIAFQ="))
|
|
@ -0,0 +1 @@
|
|||
require("heatshrink").decompress(atob("rlcgJC/ABdwBRMD8ALJj+ABREB/wWJh/wBZN/4AKIg4iKn/4KBP/ERMfERMB/5FJj//NBP//hnJ/6LJ/45Jg45Kv45JCwI5Jn5zJPwI5JCwJQICwP/CxRQISoJQJSoLEICwRQICwJnICzJnIYYJ+JCzB+ICwKVJC34W/CxbOffgIWIfgXACxP8Cyv4CxWACyUDPpU/ShIWBPpIWBPpEHMxMAv5mJCwJPICwQKIYQI4IYQJPJCwI4ISgI4JSgIKICwI4Jn5xJSgLBIMwIhJg4hJMwIKJj4hJgJlJgE+BRMHBRIA+A"))
|
|
@ -0,0 +1 @@
|
|||
require("heatshrink").decompress(atob("rlcgI1yj/4BREH/4LJ/4LJj4LB8AKGgYKB/+ABY1/BQP+BQ0PCwQuHBQX/4A4IEQ8BCwYiGn4iJJ4YiHJ4QAB+CIGAAZoFBQn8MxCLHBQg5FMwY5GMwg5GCwo5EMwhzGPog5FCwxQECwv/PpJQFSghQFCwzEECyJnECwxnDVYoWFBQpnECwx+ECzp+DCwyVEC34W/CyDOt4AKCg4KF/gWDv4WQ/AWKwAWVBQcDShMAn5mJCwx9DCwxmEgJmJgEfJ5IWGBQasGHAisFJ4gWGHAh+FHAiVGBQhnFHAp+EOIhnGYIZnGEIpQEEIxnEEIpQEEIxQDMoo5EQ4o5FFgyKDBRAiBBRAApA="))
|
|
@ -1,5 +0,0 @@
|
|||
{
|
||||
"name":"Asteroids!","type":"app",
|
||||
"icon":"*astroid",
|
||||
"src":"-astroid"
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
0.01: Initial version of Balltastic released! Happy!
|
|
@ -0,0 +1 @@
|
|||
require("heatshrink").decompress(atob("mEwwkEogAIkUzmciBpIVIkYWBAAUyCx0hiIXFAAMkCxhUBC4fzDAYWLiAXFAAP//5KKoMRC4UTC4k/DAPzJJERiKcCC5H/GA4uBWwp6DC4YwHCwMBDI0SMAYwHoIWBiXxdIwYCMJCjBiM/C46VDC4M/GAkRgMf//ySAQAEgKrDC4lBgMCHIQXHSwxICIwIuBAAIXIGAYABiQXBkEBTYcgC473FkQXBiETTQZ4IgECC4cholCiJGDMAIXIWgIXCmMkC4JGDJBbEDC4UACwn/mAtGSYsxilCgIXFSAqDBkMRiIFBkcxiUiC4sxXowIBC4QGBkIXBiJ2EFwsDBIPyC4ILBgMRiUyiCmJgSCC+YXDgAXDR4YuEcAn/MAIXEmcgBoXyFwjIEMAQXFkIOCUgoXF+J3CC4cxBwR1IQQx3BkUzmUSBQKkFC5IuBkVDJAJeGRwLhHFwUkC4Mxl6lFC48gFwYXCmcTOwomBC4swYIMikU0C4UxkJ3FC40xFoIXCogXBmaxDC5MyCwUiogXDmIXTJASSBC4kRU4oXDkgXFmQwDNwIWEBoIXFJAYKBZggWFC4YWCC4g7BkIWBkYWBBYYXCkYXDJAYjDkQUEEYZGEGA4XIIwwwGDAQuOGAomCFo4uGGARoBE4ZOGFxAABBwgAICxAABCyxJBGJJFJJRgVNPggsMA="))
|
|
@ -0,0 +1,186 @@
|
|||
Bangle.setLCDBrightness(1);
|
||||
Bangle.setLCDMode("doublebuffered");
|
||||
|
||||
let points = 0;
|
||||
let level = 1;
|
||||
let levelSpeedStart = 0.8;
|
||||
let nextLevelPoints = 20;
|
||||
let levelSpeedFactor = 0.2;
|
||||
let counterWidth = 10;
|
||||
let gWidth = g.getWidth() - counterWidth;
|
||||
let gHeight = g.getHeight();
|
||||
let counter = 160;
|
||||
let counterMax = 160;
|
||||
let ballDims = 20;
|
||||
let ballx = g.getWidth() / 2 - ballDims;
|
||||
let bally = g.getHeight() / 2 - ballDims;
|
||||
let dotx = g.getWidth() / 2;
|
||||
let doty = g.getWidth() / 2;
|
||||
let ballBuzzTime = 5;
|
||||
let ballSpeedFactor = 40;
|
||||
let redrawspeed = 5;
|
||||
let dotwidth = 5;
|
||||
let running = false;
|
||||
let drawInterval;
|
||||
let xBuzzed = false;
|
||||
let yBuzzed = false;
|
||||
|
||||
let BALL = require("heatshrink").decompress(
|
||||
atob(
|
||||
"ikUyAROvkQ3v4405AIYHBGq9KpMhktz1/W7feAJAtBEZ9jhkhs0ZgkQ8lKxW+jAdB516627E4X8AIPWzelmolKlpJBjMFEYIpC4kQ0YBBqWKynTFYPe7gpE3ec6gnHkNFrXL7372u2E4WjhGCAIliqWrUIPeKoIpB7h9HoUoqWq999///FIJ3BhGDEIIBBgFBAoWCoUI3vY62aQIW7ymSJooLBEoIADwkQEYVhEoInEGIOjR4O1y/OrIrBUYdr198iH/74nF88cE4gpCA4MY8k59CzBAINrx2164nBtduufPWYIlF++/xkxNoMAAIJPBoSdB52a30ZkNGE4IvBoUpwkxLIOMyWEmAmE7+MqKbEsLLBH4P3zw1BAYJFBFIMY8sQ4cx44nB0tVHYITBEoO967lDgDDC1tVQ4QBD37xBjMmJ4I3BE4IxBPoOMuSrBHYL1BJYbrDvfPLoYBD889jMlEoMhkpJBwkRE4O+jB7B405LoJPEYYUx0xPG7/3vxvBmOnrXsdIOc6jxBE4JfBvfwHIafDFoMRgh3H99+zsUDIOMqWU2YlBAAO1/AnBToN76EhgpTBFYKPBGIIhBEovOrWliuc2YlBE4oABE4etu2UyVrpqJBMoKvBEIPnjvWze97ATBE4YPBEopRC64BC27nBzn0znTAIOlimtq21y4BCEoM1HYOMqIVBE44AB0tVCYIBEigVBE4U1GYIFBymywkwEoJzHABIRBMIIXBWoIDCqOEmOEiABCmIjPAA51BFoVSEoUwAIIZNA"
|
||||
)
|
||||
);
|
||||
|
||||
function reset() {
|
||||
g.clear();
|
||||
level = 1;
|
||||
points = 0;
|
||||
ballx = g.getWidth() / 2 - ballDims;
|
||||
bally = g.getHeight() / 2 - ballDims;
|
||||
counter = counterMax;
|
||||
createRandomDot();
|
||||
drawInterval = setInterval(play, redrawspeed);
|
||||
running = true;
|
||||
}
|
||||
|
||||
function collide() {
|
||||
try {
|
||||
Bangle.buzz(ballBuzzTime, 0.8);
|
||||
} catch (e) {}
|
||||
}
|
||||
|
||||
function createRandomDot() {
|
||||
dotx = Math.floor(
|
||||
Math.random() * Math.floor(gWidth - dotwidth / 2) + dotwidth / 2
|
||||
);
|
||||
doty = Math.floor(
|
||||
Math.random() * Math.floor(gHeight - dotwidth / 2) + dotwidth / 2
|
||||
);
|
||||
}
|
||||
|
||||
function checkIfDotEaten() {
|
||||
if (
|
||||
ballx + ballDims > dotx &&
|
||||
ballx <= dotx + dotwidth &&
|
||||
bally + ballDims > doty &&
|
||||
bally <= doty + dotwidth
|
||||
) {
|
||||
collide();
|
||||
createRandomDot();
|
||||
counter = counterMax;
|
||||
points++;
|
||||
|
||||
if (points % nextLevelPoints == 0) {
|
||||
level++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function drawLevelText() {
|
||||
g.setColor("#26b6c7");
|
||||
g.setFontAlign(0, 0);
|
||||
g.setFont("4x6", 5);
|
||||
g.drawString("Level " + level, 120, 80);
|
||||
}
|
||||
|
||||
function draw() {
|
||||
//bg
|
||||
g.setColor("#71c6cf");
|
||||
g.fillRect(0, 0, g.getWidth(), g.getHeight());
|
||||
|
||||
//counter
|
||||
drawCounter();
|
||||
|
||||
//draw level
|
||||
drawLevelText();
|
||||
|
||||
//dot
|
||||
g.setColor("#ff0000");
|
||||
g.fillCircle(dotx, doty, dotwidth);
|
||||
|
||||
//ball
|
||||
g.drawImage(BALL, ballx, bally);
|
||||
|
||||
g.flip();
|
||||
}
|
||||
|
||||
function drawCounter() {
|
||||
g.setColor("#000000");
|
||||
g.fillRect(g.getWidth() - counterWidth, 0, g.getWidth(), gHeight);
|
||||
|
||||
if(counter < 40 ) g.setColor("#fc0303");
|
||||
else if (counter < 80 ) g.setColor("#fc9803");
|
||||
else g.setColor("#0318fc");
|
||||
|
||||
g.fillRect(
|
||||
g.getWidth() - counterWidth,
|
||||
gHeight,
|
||||
g.getWidth(),
|
||||
gHeight - counter
|
||||
);
|
||||
}
|
||||
|
||||
function checkCollision() {
|
||||
if (ballx < 0) {
|
||||
ballx = 0;
|
||||
if (!xBuzzed) collide();
|
||||
xBuzzed = true;
|
||||
} else if (ballx > gWidth - ballDims) {
|
||||
ballx = gWidth - ballDims;
|
||||
if (!xBuzzed) collide();
|
||||
xBuzzed = true;
|
||||
} else {
|
||||
xBuzzed = false;
|
||||
}
|
||||
|
||||
if (bally < 0) {
|
||||
bally = 0;
|
||||
if (!yBuzzed) collide();
|
||||
yBuzzed = true;
|
||||
} else if (bally > gHeight - ballDims) {
|
||||
bally = gHeight - ballDims;
|
||||
if (!yBuzzed) collide();
|
||||
yBuzzed = true;
|
||||
} else {
|
||||
yBuzzed = false;
|
||||
}
|
||||
}
|
||||
|
||||
function count() {
|
||||
counter -= levelSpeedStart + level * levelSpeedFactor;
|
||||
if (counter <= 0) {
|
||||
running = false;
|
||||
clearInterval(drawInterval);
|
||||
setTimeout(function(){ E.showMessage("Press Button 1\nto restart.", "Gameover!");},50);
|
||||
}
|
||||
}
|
||||
|
||||
function accel(values) {
|
||||
ballx -= values.x * ballSpeedFactor;
|
||||
bally -= values.y * ballSpeedFactor;
|
||||
}
|
||||
|
||||
function play() {
|
||||
if (running) {
|
||||
accel(Bangle.getAccel());
|
||||
checkCollision();
|
||||
checkIfDotEaten();
|
||||
count();
|
||||
draw();
|
||||
}
|
||||
}
|
||||
|
||||
setTimeout(() => {
|
||||
reset();
|
||||
drawInterval = setInterval(play, redrawspeed);
|
||||
|
||||
setWatch(
|
||||
() => {
|
||||
if(!running) reset();
|
||||
},
|
||||
BTN1,
|
||||
{ repeat: true }
|
||||
);
|
||||
|
||||
running = true;
|
||||
}, 10);
|
After Width: | Height: | Size: 14 KiB |
|
@ -0,0 +1,4 @@
|
|||
0.01: Created Bar Clock
|
||||
0.02: Apply locale, 12-hour setting
|
||||
0.03: Fix dates drawing over each other at midnight
|
||||
0.04: Small bugfix
|
|
@ -0,0 +1 @@
|
|||
require("heatshrink").decompress(atob("mEwgJC/AD8Mgfwh/AhgFFngHBOIM8AovMDIXA5gFFDoUAmYjDAocMSoMz/4FF//P/g1CAopTLDAIABwAFGAH4AfA"))
|
|
@ -0,0 +1,166 @@
|
|||
/* jshint esversion: 6 */
|
||||
/**
|
||||
* A simple digital clock showing seconds as a bar
|
||||
**/
|
||||
{
|
||||
// Check settings for what type our clock should be
|
||||
const is12Hour = (require('Storage').readJSON('setting.json', 1) || {})['12hour']
|
||||
let locale = require('locale')
|
||||
{ // add some more info to locale
|
||||
let date = new Date()
|
||||
date.setFullYear(1111)
|
||||
date.setMonth(1, 3) // februari: months are zero-indexed
|
||||
const localized = locale.date(date, true)
|
||||
locale.dayFirst = /3.*2/.test(localized)
|
||||
locale.hasMeridian = (locale.meridian(date) !== '')
|
||||
}
|
||||
const screen = {
|
||||
width: g.getWidth(),
|
||||
height: g.getWidth(),
|
||||
middle: g.getWidth() / 2,
|
||||
center: g.getHeight() / 2,
|
||||
}
|
||||
|
||||
// hardcoded "settings"
|
||||
const settings = {
|
||||
time: {
|
||||
color: -1,
|
||||
font: '6x8',
|
||||
size: (is12Hour && locale.hasMeridian) ? 6 : 8,
|
||||
middle: screen.middle,
|
||||
center: screen.center,
|
||||
ampm: {
|
||||
color: -1,
|
||||
font: '6x8',
|
||||
size: 2,
|
||||
},
|
||||
},
|
||||
date: {
|
||||
color: -1,
|
||||
font: 'Vector',
|
||||
size: 20,
|
||||
middle: screen.height - 20, // at bottom of screen
|
||||
center: screen.center,
|
||||
},
|
||||
bar: {
|
||||
color: -1,
|
||||
top: 155, // just below time
|
||||
thickness: 6, // matches 24h time "pixel" size
|
||||
},
|
||||
}
|
||||
|
||||
const SECONDS_PER_MINUTE = 60
|
||||
|
||||
const timeText = function (date) {
|
||||
if (!is12Hour) {
|
||||
return locale.time(date, true)
|
||||
}
|
||||
const date12 = new Date(date.getTime())
|
||||
const hours = date12.getHours()
|
||||
if (hours === 0) {
|
||||
date12.setHours(12)
|
||||
} else if (hours > 12) {
|
||||
date12.setHours(hours - 12)
|
||||
}
|
||||
return locale.time(date12, true)
|
||||
}
|
||||
const ampmText = function (date) {
|
||||
return is12Hour ? locale.meridian(date) : ''
|
||||
}
|
||||
|
||||
const dateText = function (date) {
|
||||
const dayName = locale.dow(date, true),
|
||||
month = locale.month(date, true),
|
||||
day = date.getDate()
|
||||
const dayMonth = locale.dayFirst ? `${day} ${month}` : `${month} ${day}`
|
||||
return `${dayName} ${dayMonth}`
|
||||
}
|
||||
|
||||
const drawDateTime = function (date) {
|
||||
const t = settings.time
|
||||
g.setColor(t.color)
|
||||
g.setFont(t.font, t.size)
|
||||
g.setFontAlign(0, 0) // centered
|
||||
g.drawString(timeText(date), t.center, t.middle, true)
|
||||
if (is12Hour && locale.hasMeridian) {
|
||||
const a = settings.time.ampm
|
||||
g.setColor(a.color)
|
||||
g.setFont(a.font, a.size)
|
||||
g.setFontAlign(1, -1) // right top
|
||||
// at right edge of screen, aligned with time bottom
|
||||
const left = screen.width - a.size * 2,
|
||||
top = t.middle + t.size - a.size
|
||||
g.drawString(ampmText(date), left, top, true)
|
||||
}
|
||||
|
||||
const d = settings.date
|
||||
g.setColor(d.color)
|
||||
g.setFont(d.font, d.size)
|
||||
g.setFontAlign(0, 0) // centered
|
||||
g.drawString(dateText(date), d.center, d.middle, true)
|
||||
}
|
||||
|
||||
const drawBar = function (date) {
|
||||
const b = settings.bar
|
||||
const seconds = date.getSeconds()
|
||||
if (seconds === 0) {
|
||||
// zero-size rect stills draws one line of pixels, we don't want that
|
||||
return
|
||||
}
|
||||
const fraction = seconds / SECONDS_PER_MINUTE,
|
||||
width = fraction * screen.width
|
||||
g.setColor(b.color)
|
||||
g.fillRect(0, b.top, width, b.top + b.thickness)
|
||||
}
|
||||
|
||||
const clearScreen = function () {
|
||||
g.setColor(0)
|
||||
const timeTop = settings.time.middle - (settings.time.size * 4)
|
||||
g.fillRect(0, timeTop, screen.width, screen.height)
|
||||
}
|
||||
|
||||
let lastSeconds
|
||||
const tick = function () {
|
||||
g.reset()
|
||||
const date = new Date()
|
||||
const seconds = date.getSeconds()
|
||||
if (lastSeconds > seconds) {
|
||||
// new minute
|
||||
clearScreen()
|
||||
drawDateTime(date)
|
||||
}
|
||||
// the bar only gets larger, so drawing on top of the previous one is fine
|
||||
drawBar(date)
|
||||
|
||||
lastSeconds = seconds
|
||||
}
|
||||
|
||||
let iTick
|
||||
const start = function () {
|
||||
lastSeconds = 99 // force redraw
|
||||
tick()
|
||||
iTick = setInterval(tick, 1000)
|
||||
}
|
||||
const stop = function () {
|
||||
if (iTick) {
|
||||
clearInterval(iTick)
|
||||
iTick = undefined
|
||||
}
|
||||
}
|
||||
|
||||
// clean app screen
|
||||
g.clear()
|
||||
Bangle.loadWidgets()
|
||||
Bangle.drawWidgets()
|
||||
// Show launcher when middle button pressed
|
||||
setWatch(Bangle.showLauncher, BTN2, {repeat: false, edge: 'falling'})
|
||||
|
||||
Bangle.on('lcdPower', function (on) {
|
||||
if (on) {
|
||||
start()
|
||||
} else {
|
||||
stop()
|
||||
}
|
||||
})
|
||||
start()
|
||||
}
|
After Width: | Height: | Size: 159 B |
|
@ -1,7 +0,0 @@
|
|||
{
|
||||
"name":"Binary Clock",
|
||||
"type":"clock",
|
||||
"icon":"*bclock",
|
||||
"src":"-bclock"
|
||||
}
|
||||
|
|
@ -33,6 +33,7 @@
|
|||
<p>If ok, Click <button id="upload" class="btn btn-primary">Upload</button></p>
|
||||
</div>
|
||||
|
||||
<script src="../../lib/customize.js"></script>
|
||||
<script src="https://unpkg.com/leaflet@1.0.3/dist/leaflet.js"></script>
|
||||
<script src="https://code.jquery.com/jquery-3.2.1.min.js"></script>
|
||||
<script src="https://unpkg.com/osmtogeojson@2.2.12/osmtogeojson.js"></script>
|
||||
|
@ -195,21 +196,12 @@ Bangle.on('mag', function(m) {
|
|||
Bangle.setCompassPower(1);
|
||||
Bangle.setGPSPower(1);
|
||||
g.clear();`;
|
||||
var json = JSON.stringify({
|
||||
name:"Beer Compass",
|
||||
icon:"*beer",
|
||||
src:"-beer"
|
||||
});
|
||||
var icon = `require("heatshrink").decompress(atob("mEwghC/AB0O/4AG8AXNgYXHmAXl94XH+AXNn4XH/wXW+YX/C6oWHAAIXN7sz9vdAAoXN9sznvuAAXf/vuC53jC4Xd7wXQ93jn3u9vv9vt7wXT/4tBAgIXQ7wvCC4PgC5sO6czIQJfBC6PumaPDC6wwCC50NYAJcBVgIDBCxrAFbgYXP7yoDF6TADL4YXPVAIXCRyAXC7wXW9zwBC6cNC9zABC4gWQC653CR4fQC6x3TF6gXXI4M9d6wAEC9EN73dAAZfQgczAAkwC/4XXAH4"))`;
|
||||
|
||||
|
||||
window.postMessage({
|
||||
id : "beer",
|
||||
|
||||
sendCustomizedApp({
|
||||
storage:[
|
||||
{name:"-beer", content:app},
|
||||
{name:"+beer", content:json},
|
||||
{name:"*beer", content:icon, evaluate:true},
|
||||
{name:"beer.app.js", content:app},
|
||||
{name:"beer.img", content:icon, evaluate:true},
|
||||
]
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,6 +0,0 @@
|
|||
{
|
||||
"name":"Berlin Clock",
|
||||
"type":"clock",
|
||||
"icon":"*berlinc",
|
||||
"src":"-berlinc"
|
||||
}
|
|
@ -1,7 +0,0 @@
|
|||
|
||||
{
|
||||
"name": "BLE Scanner",
|
||||
"type":"app",
|
||||
"icon": "*blescan",
|
||||
"src": "-blescan"
|
||||
}
|
|
@ -1,7 +0,0 @@
|
|||
{
|
||||
"name":"Large Clock",
|
||||
"type":"clock",
|
||||
"icon":"*blobclk",
|
||||
"src":"-blobclk",
|
||||
"sortorder":-10
|
||||
}
|
|
@ -1,7 +0,0 @@
|
|||
{
|
||||
"name":"Bold Clock",
|
||||
"type":"clock",
|
||||
"icon":"*boldclk",
|
||||
"src":"-boldclk",
|
||||
"sortorder":-10
|
||||
}
|
|
@ -1,2 +1,15 @@
|
|||
0.02: Attempt to reset state of the interpreter better before loading an app
|
||||
0.03: Fix issue switching clockfaces via menu
|
||||
0.04: Add alarm functionality
|
||||
0.05: Add Welcome screen on boot
|
||||
0.06: Disable GPS time log messages, add (default=1) setting to hide log messages
|
||||
0.07: Fix issues with alarm scheduling
|
||||
0.08: Fix issues if BLE=off, 'Make Connectable' is chosen, and the loader resets Bangle.js (fix #108)
|
||||
0.09: Only check GPS for time after a fresh boot
|
||||
0.10: Stop users calling save() (fix #125)
|
||||
If Debug info is set to 'show' don't move to Terminal if connected!
|
||||
0.11: Added vibrate as beep workaround
|
||||
0.12: Add an event on BTN2 to open launcher when no clock detected (fix #147)
|
||||
0.13: Now automatically load *.boot.js at startup
|
||||
Move alarm code into alarm.boot.js
|
||||
0.14: Move welcome loaders to *.boot.js
|
||||
|
|
|
@ -1,40 +1,45 @@
|
|||
// This ALWAYS runs at boot
|
||||
E.setFlags({pretokenise:1});
|
||||
// All of this is just shim for older Bangles
|
||||
if (!Bangle.loadWidgets) {
|
||||
Bangle.loadWidgets = function(){
|
||||
global.WIDGETPOS={tl:32,tr:g.getWidth()-32,bl:32,br:g.getWidth()-32};
|
||||
global.WIDGETS={};
|
||||
require("Storage").list().filter(a=>a[0]=='=').forEach(widget=>eval(require("Storage").read(widget)));
|
||||
};
|
||||
Bangle.drawWidgets = function(){
|
||||
for(var w of WIDGETS)w.draw()
|
||||
};
|
||||
Bangle.showLauncher = function(){
|
||||
var l = require("Storage").list().filter(a=>a[0]=='+').map(app=>{
|
||||
try { return require("Storage").readJSON(app); } catch (e) {}
|
||||
}).find(app=>app.type=="launch");
|
||||
if (l) load(l.src);
|
||||
else E.showMessage("Launcher\nnot found");
|
||||
};
|
||||
var _load = load;
|
||||
global.load = function(x) {
|
||||
if (!x) _load(x);
|
||||
else setTimeout(function(){
|
||||
// attempt to remove any currently-running code
|
||||
delete Bangle.buzz;
|
||||
delete Bangle.beep;
|
||||
Bangle.setLCDOffset&&Bangle.setLCDOffset(0);
|
||||
Bangle.setLCDMode("direct");
|
||||
g.clear();
|
||||
clearInterval();
|
||||
clearWatch();
|
||||
Bangle.removeAllListeners();
|
||||
NRF.removeAllListeners();
|
||||
Bluetooth.removeAllListeners();
|
||||
E.removeAllListeners();
|
||||
for (var i in global) if (i!="g") delete global[i];
|
||||
setTimeout('eval(require("Storage").read("'+x+'"));',20);
|
||||
},20);
|
||||
// Load settings...
|
||||
var s = require('Storage').readJSON('setting.json',1)||{};
|
||||
if (s.ble!==false) {
|
||||
if (s.HID) { // Human interface device
|
||||
Bangle.HID = E.toUint8Array(atob("BQEJBqEBhQIFBxngKecVACUBdQGVCIEClQF1CIEBlQV1AQUIGQEpBZEClQF1A5EBlQZ1CBUAJXMFBxkAKXOBAAkFFQAm/wB1CJUCsQLABQwJAaEBhQEVACUBdQGVAQm1gQIJtoECCbeBAgm4gQIJzYECCeKBAgnpgQIJ6oECwA=="));
|
||||
NRF.setServices({}, {uart:true, hid:Bangle.HID});
|
||||
}
|
||||
}
|
||||
if (s.blerepl===false) { // If not programmable, force terminal off Bluetooth
|
||||
if (s.log) Terminal.setConsole(true); // if showing debug, force REPL onto terminal
|
||||
else E.setConsole(null,{force:true}); // on new (2v05+) firmware we have E.setConsole which allows a 'null' console
|
||||
} else {
|
||||
if (s.log && !NRF.getSecurityStatus().connected) Terminal.setConsole(); // if showing debug, put REPL on terminal (until connection)
|
||||
else Bluetooth.setConsole(true); // else if no debug, force REPL to Bluetooth
|
||||
}
|
||||
// we just reset, so BLE should be on.
|
||||
// Don't disconnect if something is already connected to us
|
||||
if (s.ble===false && !NRF.getSecurityStatus().connected) NRF.sleep();
|
||||
// Set time, vibrate, beep, etc
|
||||
if (!s.vibrate) Bangle.buzz=Promise.resolve;
|
||||
if (s.beep===false) Bangle.beep=Promise.resolve;
|
||||
else if (s.beep=="vib") Bangle.beep = function (time, freq) {
|
||||
return new Promise(function(resolve) {
|
||||
if ((0|freq)<=0) freq=4000;
|
||||
if ((0|time)<=0) time=200;
|
||||
if (time>5000) time=5000;
|
||||
analogWrite(D13,0.1,{freq:freq});
|
||||
setTimeout(function() {
|
||||
digitalWrite(D13,0);
|
||||
resolve();
|
||||
}, time);
|
||||
});
|
||||
};
|
||||
Bangle.setLCDTimeout(s.timeout);
|
||||
if (!s.timeout) Bangle.setLCDPower(1);
|
||||
E.setTimeZone(s.timezone);
|
||||
delete s;
|
||||
// stop users doing bad things!
|
||||
global.save = function() { throw new Error("You can't use save() on Bangle.js without overwriting the bootloader!"); }
|
||||
// Load *.boot.js files
|
||||
require('Storage').list(/\.boot\.js/).map(bootFile=>{
|
||||
eval(require('Storage').read(bootFile));
|
||||
});
|
||||
|
|
|
@ -1,22 +1,38 @@
|
|||
// This runs after a 'fresh' boot
|
||||
var settings;
|
||||
try {
|
||||
settings = require("Storage").readJSON('@setting');
|
||||
} catch (e) {
|
||||
settings = {}
|
||||
}
|
||||
var settings=require("Storage").readJSON('setting.json',1)||{};
|
||||
// load clock if specified
|
||||
var clockApp = settings.clock;
|
||||
if (clockApp) clockApp = require("Storage").read(clockApp)
|
||||
if (!clockApp) {
|
||||
var clockApps = require("Storage").list().filter(a=>a[0]=='+').map(app=>{
|
||||
try { return require("Storage").readJSON(app); }
|
||||
catch (e) {}
|
||||
}).filter(app=>app.type=="clock").sort((a, b) => a.sortorder - b.sortorder);
|
||||
var clockApps = require("Storage").list(/\.info$/).map(app=>require("Storage").readJSON(app,1)||{}).filter(app=>app.type=="clock").sort((a, b) => a.sortorder - b.sortorder);
|
||||
if (clockApps && clockApps.length > 0)
|
||||
clockApp = require("Storage").read(clockApps[0].src);
|
||||
delete clockApps;
|
||||
}
|
||||
if (clockApp) eval(clockApp);
|
||||
else E.showMessage("No Clock Found");
|
||||
delete clockApp;
|
||||
if (!clockApp) clockApp=`E.showMessage("No Clock Found");
|
||||
setWatch(() => {
|
||||
Bangle.showLauncher();
|
||||
}, BTN2, {repeat:false,edge:"falling"});)
|
||||
`;
|
||||
delete settings;
|
||||
// check to see if our clock is wrong - if it is use GPS time
|
||||
if ((new Date()).getFullYear()==1970) {
|
||||
E.showMessage("Searching for\nGPS time");
|
||||
Bangle.on('GPS',function cb(g) {
|
||||
Bangle.setGPSPower(0);
|
||||
Bangle.removeListener("GPS",cb);
|
||||
if (!g.time || (g.time.getFullYear()<2000) ||
|
||||
(g.time.getFullYear()==2250)) {
|
||||
// GPS receiver's time not set - just boot clock anyway
|
||||
eval(clockApp);delete clockApp;
|
||||
return;
|
||||
}
|
||||
// We have a GPS time. Set time and reboot (to load alarms properly)
|
||||
setTime(g.time.getTime()/1000);
|
||||
load();
|
||||
});
|
||||
Bangle.setGPSPower(1);
|
||||
} else {
|
||||
eval(clockApp);
|
||||
delete clockApp;
|
||||
}
|
||||
|
|
|
@ -1,3 +0,0 @@
|
|||
{
|
||||
"name":"Bootloader","type":"boot"
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
require("heatshrink").decompress(atob("mEwwglihGIxAWUwADBDCYTDhAXSFwQEGIxowBL4QXTx///AXWF6qnBwCTDO6EIF4KnEDwLWO/4QFx7FNdwQQEGwP4GBYUB/4QBDIYXMIgQAEDIIKCVwItJFggFEx4uKCAQUBX4QDC/B2KhASCAQP/AQQcDLpQlCLgQsCCoIGBC5IkCFon/xwxCDgIXJFwYxFHIR3ILwIkBCIeIFwQHBHgReIJAgCBOoP+MYZIHhB1EDgIRBA4ZIJC4LrEMYvoAgQXJxHvI4gtDC5OIF4QSDbYY3EC5QAKG4QXNPwg0BSBAJCIQhLCDwgXKIAwXUMo4XPFwrwKC4YOCUooVCR453DIxIXJU4IqDxwXJa45FDdgxnEC40IC4TbINQYXIRQZwDAAXv/xuBCwoXBVAgXDA4wXGSARcEC4o7BRwx4DOon+C4YiCLwxIDDAobDEYJGIGAYYBxDAD9AJDC5IwCDIYACJARGIDAapDaooWLDAZhEAoIWNMggADCqAAPA"))
|
|
@ -0,0 +1,73 @@
|
|||
function msToTime(duration) {
|
||||
var milliseconds = parseInt((duration % 1000) / 100),
|
||||
seconds = Math.floor((duration / 1000) % 60),
|
||||
minutes = Math.floor((duration / (1000 * 60)) % 60),
|
||||
hours = Math.floor((duration / (1000 * 60 * 60)) % 24);
|
||||
|
||||
hours = (hours < 10) ? "0" + hours : hours;
|
||||
minutes = (minutes < 10) ? "0" + minutes : minutes;
|
||||
seconds = (seconds < 10) ? "0" + seconds : seconds;
|
||||
|
||||
return hours + ":" + minutes + ":" + seconds;
|
||||
}
|
||||
|
||||
|
||||
var counter = 0;
|
||||
var started = false;
|
||||
|
||||
function drawInterface() {
|
||||
g.clear();
|
||||
g.setFontAlign(0, 0);
|
||||
g.setFont("6x8", 2);
|
||||
g.drawString("+5m", g.getWidth() - 30, 30);
|
||||
g.drawString("+30s", g.getWidth() - 30, g.getHeight() / 2);
|
||||
g.drawString("+5s", g.getWidth() - 30, g.getHeight() - 30);
|
||||
|
||||
g.setFontAlign(0, 0); // center font
|
||||
g.setFont("6x8", 3);
|
||||
// draw the current counter value
|
||||
|
||||
g.drawString(msToTime(counter * 1000), g.getWidth() / 2 - 30, g.getHeight() / 2);
|
||||
// optional - this keeps the watch LCD lit up
|
||||
g.flip();
|
||||
}
|
||||
|
||||
function countDown() {
|
||||
if (counter > 0) {
|
||||
if (started) {
|
||||
counter--;
|
||||
drawInterface();
|
||||
}
|
||||
} else {
|
||||
if (started) {
|
||||
Bangle.buzz();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
setWatch((p) => {
|
||||
if (p.time - p.lastTime < 0.1) {
|
||||
counter = 0;
|
||||
started = false;
|
||||
} else {
|
||||
counter += 60 * 5;
|
||||
}
|
||||
drawInterface();
|
||||
}, BTN1, { repeat: true });
|
||||
|
||||
setWatch(() => {
|
||||
counter += 30;
|
||||
drawInterface();
|
||||
}, BTN2, { repeat: true });
|
||||
|
||||
setWatch(() => {
|
||||
counter += 5;
|
||||
drawInterface();
|
||||
}, BTN3, { repeat: true });
|
||||
|
||||
Bangle.on('touch', function (button) {
|
||||
started = !started;
|
||||
});
|
||||
|
||||
var interval = setInterval(countDown, 1000);
|
||||
drawInterface();
|
After Width: | Height: | Size: 3.5 KiB |
|
@ -1 +0,0 @@
|
|||
0.02: Modified for use with new bootloader and firmware
|
|
@ -1,6 +0,0 @@
|
|||
{
|
||||
"name":"Clock 3x2 Pix",
|
||||
"type":"clock",
|
||||
"icon":"*clck3x2",
|
||||
"src":"-clck3x2"
|
||||
}
|
|
@ -1,5 +0,0 @@
|
|||
{
|
||||
"name":"Click Master",
|
||||
"icon": "*clickms",
|
||||
"src":"-clickms"
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
0.07: Submitted to App Loader
|
|
@ -0,0 +1 @@
|
|||
require("heatshrink").decompress(atob("kEgwkBiIA/ACBhLB6gqKB6g//B6I4DiDqCB40QB4MBAoIXDB40BAIIPNG44PLAoQvMB5RPEB5JvEBAav1f7wA/ABoA=="))
|
|
@ -0,0 +1,51 @@
|
|||
var fontsize = 3;
|
||||
var locale = require("locale");
|
||||
var marginTop = 40;
|
||||
var flag = false;
|
||||
var WeekDays = ["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"];
|
||||
|
||||
function drawAll(){
|
||||
g.clear();
|
||||
Bangle.loadWidgets();
|
||||
Bangle.drawWidgets();
|
||||
updateTime();
|
||||
updateRest(new Date());
|
||||
}
|
||||
|
||||
function updateRest(now){
|
||||
let date = locale.date(now,false);
|
||||
writeLine(WeekDays[now.getDay()],1);
|
||||
writeLine(date,2);
|
||||
}
|
||||
function updateTime(){
|
||||
if (!Bangle.isLCDOn()) return;
|
||||
let now = new Date();
|
||||
let h = now.getHours();
|
||||
let m = now.getMinutes();
|
||||
h = h>=10?h:"0"+h;
|
||||
m = m>=10?m:"0"+m;
|
||||
writeLine(h+":"+m,0);
|
||||
writeLine(flag?" ":"_",3);
|
||||
flag = !flag;
|
||||
if(now.getMinutes() == 0)
|
||||
updateRest(now);
|
||||
}
|
||||
function writeLineStart(line){
|
||||
g.drawString(">",4,marginTop+line*30);
|
||||
}
|
||||
function writeLine(str,line){
|
||||
g.setFont("6x8",fontsize);
|
||||
g.setColor(0,1,0);
|
||||
g.setFontAlign(-1,-1);
|
||||
g.clearRect(0,marginTop+line*30,((str.length+1)*20),marginTop+25+line*30);
|
||||
writeLineStart(line);
|
||||
g.drawString(str,25,marginTop+line*30);
|
||||
}
|
||||
|
||||
drawAll();
|
||||
Bangle.on('lcdPower',function(on) {
|
||||
if (on)
|
||||
drawAll();
|
||||
});
|
||||
var click = setInterval(updateTime, 1000);
|
||||
setWatch(Bangle.showLauncher, BTN2, {repeat:false,edge:"falling"});
|
After Width: | Height: | Size: 305 B |
|
@ -0,0 +1,3 @@
|
|||
0.02: Modified for use with new bootloader and firmware
|
||||
0.03: Added 'reset' so we don't get the font color from widgets
|
||||
0.04: Changed name from clck3x2 to clock2x3
|
|
@ -57,9 +57,9 @@ const pixels = [[[0,0], // digit on/off pixels
|
|||
let idTimeout = null;
|
||||
|
||||
function drawTime() {
|
||||
g.clear();
|
||||
g.clear(1);
|
||||
Bangle.drawWidgets();
|
||||
|
||||
g.reset();
|
||||
let d = Date();
|
||||
let h = d.getHours();
|
||||
let m = d.getMinutes();
|
||||
|
@ -76,8 +76,7 @@ function drawTime() {
|
|||
}
|
||||
|
||||
let t = d.getSeconds()*1000 + d.getMilliseconds();
|
||||
let delta = (60000 - t) % 60000; // time till next minute
|
||||
idTimeout = setTimeout(drawTime, delta);
|
||||
idTimeout = setTimeout(drawTime, 60000 - t); // time till next minute
|
||||
}
|
||||
|
||||
// special function to handle display switch on
|
|
@ -1 +1 @@
|
|||
require("heatshrink").decompress(atob("mEwgRC/AH4A/gED/k/5/wh/wgAFCBcg7NgAVBh/zDoYLkHaAFqAH4A/AH4AW"));
|
||||
require("heatshrink").decompress(atob("mEwgRC/AH4A/gED/k/5/wh/wgAFCBcg7NgAVBh/zDoYLkHaAFqAH4A/AH4AW"))
|
Before Width: | Height: | Size: 145 B After Width: | Height: | Size: 145 B |
|
@ -1,5 +0,0 @@
|
|||
{
|
||||
"name":"Clock-Tris",
|
||||
"icon":"*clotris",
|
||||
"src":"-clotris"
|
||||
}
|
|
@ -1,5 +0,0 @@
|
|||
{
|
||||
"name":"Compass","type":"app",
|
||||
"icon":"*compass",
|
||||
"src":"-compass"
|
||||
}
|
|
@ -1,6 +0,0 @@
|
|||
{
|
||||
"name": "Centerclock",
|
||||
"type": "clock",
|
||||
"icon": "*ctrclk",
|
||||
"src": "-ctrclk"
|
||||
}
|
|
@ -1,6 +0,0 @@
|
|||
{
|
||||
"name":"Cube",
|
||||
"type":"app",
|
||||
"icon":"*cube",
|
||||
"src":"-cube"
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
0.01: New Widget!
|
||||
0.02: Improved calculation, new image for app
|
||||
0.03: Improved display of number
|
|
@ -0,0 +1 @@
|
|||
require("heatshrink").decompress(atob("mEwgmIAH4A/AH4A/AEEAAAgGOC/4XLAgoGIDgYXTwEIBY4JEAw8YCIOAEY4+EAwwTCL44XNO5IX/C6i6LC8YABa5AXOF67vIwA5DAw5GDMhg7HjAXWIwQLFZIoGNC/4XKAH4A/AH4A/ADoA="))
|
|
@ -0,0 +1,67 @@
|
|||
g.clear();
|
||||
Bangle.loadWidgets();
|
||||
Bangle.drawWidgets();
|
||||
|
||||
const storage = require('Storage');
|
||||
let settings;
|
||||
|
||||
function updateSettings() {
|
||||
storage.write('daysleft.json', settings);
|
||||
}
|
||||
|
||||
function resetSettings() {
|
||||
settings = {
|
||||
day : 17,
|
||||
month : 6,
|
||||
year: 1981
|
||||
};
|
||||
updateSettings();
|
||||
}
|
||||
|
||||
settings = storage.readJSON('daysleft.json',1);
|
||||
if (!settings) resetSettings();
|
||||
|
||||
function showMenu() {
|
||||
const datemenu = {
|
||||
'': {
|
||||
'title': 'Set Date',
|
||||
'predraw': function() {
|
||||
datemenu.Date.value = settings.day;
|
||||
datemenu.Month.value = settings.month;
|
||||
datemenu.Year.value = settings.year;
|
||||
}
|
||||
},
|
||||
'Day': {
|
||||
value: settings.day,
|
||||
min: 1,
|
||||
max: 31,
|
||||
step: 1,
|
||||
onchange: v => {
|
||||
settings.day = v;
|
||||
updateSettings();
|
||||
}
|
||||
},
|
||||
'Month': {
|
||||
value: settings.month,
|
||||
min: 1,
|
||||
max: 12,
|
||||
step: 1,
|
||||
onchange: v => {
|
||||
settings.month = v;
|
||||
updateSettings();
|
||||
}
|
||||
},
|
||||
'Year': {
|
||||
value: settings.year,
|
||||
step: 1,
|
||||
onchange: v => {
|
||||
settings.year = v;
|
||||
updateSettings();
|
||||
}
|
||||
}
|
||||
};
|
||||
datemenu['-Exit-'] = ()=>{load();};
|
||||
return E.showMenu(datemenu);
|
||||
}
|
||||
|
||||
showMenu();
|
After Width: | Height: | Size: 468 B |
|
@ -0,0 +1,84 @@
|
|||
const storage = require('Storage');
|
||||
let settings;
|
||||
let height = 23;
|
||||
let width = 34;
|
||||
|
||||
var debug = 0; //1 = show debug info
|
||||
|
||||
//write settings to file
|
||||
function updateSettings() {
|
||||
storage.write('daysleft.json', settings);
|
||||
}
|
||||
|
||||
//Define standard settings
|
||||
function resetSettings() {
|
||||
settings = {
|
||||
day : 17,
|
||||
month : 6,
|
||||
year: 2020
|
||||
};
|
||||
updateSettings();
|
||||
}
|
||||
|
||||
settings = storage.readJSON('daysleft.json',1); //read storage
|
||||
if (!settings) resetSettings(); //if settings file was not found, set to standard
|
||||
|
||||
var dd = settings.day,
|
||||
mm = settings.month-1, //-1 because month is zero-based
|
||||
yy = settings.year;
|
||||
|
||||
const oneDay = 24 * 60 * 60 * 1000; // hours*minutes*seconds*milliseconds
|
||||
const targetDate = new Date(yy, mm, dd); //is 00:00
|
||||
const today = new Date(); //includes current time
|
||||
|
||||
const currentYear = today.getFullYear();
|
||||
const currentMonth = today.getMonth();
|
||||
const currentDay = today.getDate();
|
||||
const todayMorning = new Date (currentYear, currentMonth, currentDay, 0, 0, 0); //create date object with today, but 00:00:00
|
||||
|
||||
const diffDays = (targetDate - todayMorning) / oneDay; //calculate day difference
|
||||
|
||||
function drawWidget() {
|
||||
if (debug == 1) g.drawRect(this.x,this.y,this.x+width,this.y+height); //draw rectangle around widget area
|
||||
g.reset();
|
||||
|
||||
//define font size and string position
|
||||
//small if number has more than 3 digits (positive number)
|
||||
if (diffDays >= 1000) {
|
||||
g.setFont("6x8", 1);
|
||||
g.drawString(diffDays,this.x+10,this.y+7);
|
||||
}
|
||||
//large if number has 3 digits (positive number)
|
||||
if (diffDays <= 999 && diffDays >= 100) {
|
||||
g.setFont("6x8", 2);
|
||||
g.drawString(diffDays,this.x,this.y+4);
|
||||
}
|
||||
//large if number has 2 digits (positive number)
|
||||
if (diffDays <= 99 && diffDays >= 10) {
|
||||
g.setFont("6x8", 2);
|
||||
g.drawString(diffDays,this.x+6,this.y+4);
|
||||
}
|
||||
//large if number has 1 digit (positive number)
|
||||
if (diffDays <= 9 && diffDays >= 0) {
|
||||
g.setFont("6x8", 2);
|
||||
g.drawString(diffDays,this.x+13,this.y+4);
|
||||
}
|
||||
//large if number has 1 digit (negative number)
|
||||
if (diffDays <= -1 && diffDays >= -9) {
|
||||
g.setFont("6x8", 2);
|
||||
g.drawString(diffDays,this.x+5,this.y+4);
|
||||
}
|
||||
//large if number has 2 digits (negative number)
|
||||
if (diffDays <= -10 && diffDays >= -99) {
|
||||
g.setFont("6x8", 2);
|
||||
g.drawString(diffDays,this.x,this.y+4);
|
||||
}
|
||||
//large if number has 3 digits or more (negative number)
|
||||
if (diffDays <= -100) {
|
||||
g.setFont("6x8", 1);
|
||||
g.drawString(diffDays,this.x,this.y+7);
|
||||
}
|
||||
}
|
||||
|
||||
//draw widget
|
||||
WIDGETS["daysl"]={area:"tl",width:width,draw:drawWidget};
|
|
@ -0,0 +1,9 @@
|
|||
0.01: branched from simple clock and added seconds
|
||||
0.02: add timestamp (tst)
|
||||
0.03: fix timestamp round to whole number
|
||||
0.04: add iso datetime and move day of the week (d) / month names (m)
|
||||
0.05: add beats (@)
|
||||
0.06: tidy up
|
||||
0.07: add days in current month (md) and days since new moon (l)
|
||||
0.08: update icon
|
||||
0.09: Use localised month and day of the week from locale
|
|
@ -0,0 +1 @@
|
|||
require("heatshrink").decompress(atob("mEwwkEIf4A5/8wgf/AwUB/8gh/zA4QMCl/xA4cAichgIaBiEDgMgmECDQMAkMA+EgiYvDkQJBkcQgMQDwMggUiiECG4MikEBmQWCgURiEREQIXBCIMxkIIBAoMSiQ4BGoIABKgPykRSBI4JfC+c/iARBl8zmBfEAAUvIgIAUkbAtgalB+ADDBIKSBHgUgmYJCAAa6BmCoBAYMiBIMRC4UQmEAAoQvFmUDAYUSmcxWIKMBEQKrBOw0yh8wmcyj4nBIYQDB+cwBAQA/ABUxgUDkBqBgchkMiiUikMRgSOBkR3BkEhC4MgiQHBiADBC4UQAYMRiUxkECAAITBC4MSiUQF4MTiQTBBAIDBkcCiMxkUTAYIvCAH4A/AH4AKiIPPgMxiESgUQgECgMBdAMiiUgC48ikUBiEBiIXDGQURiIbBF48RkAvCEwIvCkERgQMBRHpDBOoRhBNoJOBJIkiKYMjgcTOoMhLQMQmMDDIMjQQInEC4MhiUSkQHCC4MAkAXCiUjiZ5UiR5jLwLaBAQJ1BAgIAMCgMxMwMgkciAoMjC5pqBRwPxCoMiiUyGBsgiBBBiESVAKzBf+YACA=="))
|
|
@ -0,0 +1,112 @@
|
|||
var locale = require("locale");
|
||||
/* jshint esversion: 6 */
|
||||
const timeFontSize = 4;
|
||||
const dateFontSize = 3;
|
||||
const smallFontSize = 2;
|
||||
const font = "6x8";
|
||||
|
||||
const xyCenter = g.getWidth() / 2;
|
||||
const yposTime = 50;
|
||||
const yposDate = 85;
|
||||
const yposTst = 115;
|
||||
const yposDml = 170;
|
||||
const yposDayMonth = 195;
|
||||
const yposGMT = 220;
|
||||
|
||||
// Check settings for what type our clock should be
|
||||
var is12Hour = (require("Storage").readJSON("setting.json",1)||{})["12hour"];
|
||||
|
||||
function getUTCTime(d) {
|
||||
return d.toUTCString().split(' ')[4].split(':').map(function(d){return Number(d)});
|
||||
}
|
||||
|
||||
function drawSimpleClock() {
|
||||
// get date
|
||||
var d = new Date();
|
||||
var da = d.toString().split(" ");
|
||||
var dutc = getUTCTime(d);
|
||||
|
||||
g.reset(); // default draw styles
|
||||
// drawSting centered
|
||||
g.setFontAlign(0, 0);
|
||||
|
||||
// draw time
|
||||
var time = da[4].split(":");
|
||||
var hours = time[0],
|
||||
minutes = time[1],
|
||||
seconds = time[2];
|
||||
|
||||
var meridian = "";
|
||||
if (is12Hour) {
|
||||
hours = parseInt(hours,10);
|
||||
meridian = "AM";
|
||||
if (hours == 0) {
|
||||
hours = 12;
|
||||
meridian = "AM";
|
||||
} else if (hours >= 12) {
|
||||
meridian = "PM";
|
||||
if (hours>12) hours -= 12;
|
||||
}
|
||||
hours = (" "+hours).substr(-2);
|
||||
}
|
||||
|
||||
// Time
|
||||
g.setFont(font, timeFontSize);
|
||||
g.drawString(`${hours}:${minutes}:${seconds}`, xyCenter, yposTime, true);
|
||||
g.setFont(font, smallFontSize);
|
||||
g.drawString(meridian, xyCenter + 102, yposTime + 10, true);
|
||||
|
||||
// Date String
|
||||
g.setFont(font, dateFontSize);
|
||||
g.drawString(`${d.getFullYear()}-${d.getMonth()+1}-${d.getDate()}`, xyCenter, yposDate, true);
|
||||
|
||||
// Timestamp
|
||||
var tst = Math.round(d.getTime());
|
||||
g.setFont(font, smallFontSize);
|
||||
g.drawString(`tst:${tst}`, xyCenter, yposTst, true);
|
||||
|
||||
//Days in month
|
||||
var dom = new Date(d.getFullYear(), d.getMonth()+1, 0).getDate();
|
||||
|
||||
//Days since full moon
|
||||
var knownnew = new Date(2020,02,24,09,28,0);
|
||||
|
||||
// Get millisecond difference and divide down to cycles
|
||||
var cycles = (d.getTime()-knownnew.getTime())/1000/60/60/24/29.53;
|
||||
|
||||
// Multiply decimal component back into days since new moon
|
||||
var sincenew = (cycles % 1)*29.53;
|
||||
|
||||
// Draw days in month and sime since new moon
|
||||
g.setFont(font, smallFontSize);
|
||||
g.drawString(`md:${dom} l:${sincenew.toFixed(2)}`, xyCenter, yposDml, true);
|
||||
|
||||
// draw Month name, Day of the week and beats
|
||||
var beats = Math.floor((((dutc[0] + 1) % 24) + dutc[1] / 60 + dutc[2] / 3600) * 1000 / 24);
|
||||
g.setFont(font, smallFontSize);
|
||||
g.drawString(`m:${locale.month(d,true)} d:${locale.dow(d,true)} @${beats}`, xyCenter, yposDayMonth, true);
|
||||
|
||||
// draw gmt
|
||||
var gmt = da[5];
|
||||
g.setFont(font, smallFontSize);
|
||||
g.drawString(gmt, xyCenter, yposGMT, true);
|
||||
}
|
||||
|
||||
// handle switch display on by pressing BTN1
|
||||
Bangle.on('lcdPower', function(on) {
|
||||
if (on) drawSimpleClock();
|
||||
});
|
||||
|
||||
// clean app screen
|
||||
g.clear();
|
||||
Bangle.loadWidgets();
|
||||
Bangle.drawWidgets();
|
||||
|
||||
// refesh every 100 milliseconds
|
||||
setInterval(drawSimpleClock, 100);
|
||||
|
||||
// draw now
|
||||
drawSimpleClock();
|
||||
|
||||
// Show launcher when middle button pressed
|
||||
setWatch(Bangle.showLauncher, BTN2, {repeat:false,edge:"falling"});
|
After Width: | Height: | Size: 2.5 KiB |
|
@ -9,6 +9,7 @@ var scenes = [
|
|||
y+=step;
|
||||
if (y>70) {
|
||||
clearInterval(i);
|
||||
|
||||
i = undefined;
|
||||
}
|
||||
g.clearRect(0,y-(step+1),240,y-1);
|
||||
|
@ -159,10 +160,13 @@ var scenes = [
|
|||
];
|
||||
var sceneNo = scenes.length-1;
|
||||
|
||||
var stop = scenes[sceneNo]();
|
||||
setInterval(function() {
|
||||
var stop;
|
||||
function next() {
|
||||
sceneNo++;
|
||||
if (sceneNo>=scenes.length) sceneNo=0;
|
||||
stop();
|
||||
if (stop) stop();
|
||||
clearInterval();
|
||||
stop = scenes[sceneNo]();
|
||||
}, 10000);
|
||||
setTimeout(next, 10000);
|
||||
}
|
||||
next()
|
||||
|
|
|
@ -1,5 +0,0 @@
|
|||
{
|
||||
"name":"Demo Loop",
|
||||
"icon":"*demoapp",
|
||||
"src":"-demoapp"
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
0.01: Based on the Analog Clock app, minimal dot interface
|
|
@ -0,0 +1 @@
|
|||
require("heatshrink").decompress(atob("mEwwkBIf4A/AGUJyAXtACeZBCAOJh/wC6IADC4gA/XEINJC64A/AHcP+ACD/4CBTB0Ph8A+ACBAIKoKC65HKC4gA/AAfACysM5gvjTBgNKC64A/AEWZBCAXdADa4XaH4A/AAgA=="))
|
|
@ -0,0 +1,162 @@
|
|||
let g;
|
||||
let Bangle;
|
||||
|
||||
const locale = require('locale');
|
||||
const p = Math.PI / 2;
|
||||
const pRad = Math.PI / 180;
|
||||
const faceWidth = 100; // watch face radius
|
||||
let timer = null;
|
||||
let currentDate = new Date();
|
||||
let hourRadius = 60;
|
||||
let minRadius = 80;
|
||||
const centerPx = g.getWidth() / 2;
|
||||
|
||||
const seconds = (angle) => {
|
||||
const a = angle * pRad;
|
||||
const x = centerPx + Math.sin(a) * faceWidth;
|
||||
const y = centerPx - Math.cos(a) * faceWidth;
|
||||
|
||||
// if 15 degrees, make hour marker larger
|
||||
const radius = (angle % 15) ? 2 : 4;
|
||||
g.fillCircle(x, y, radius);
|
||||
};
|
||||
|
||||
const hourDot = (angle,radius) => {
|
||||
const a = angle * pRad;
|
||||
const x = centerPx + Math.sin(a) * hourRadius;
|
||||
const y = centerPx - Math.cos(a) * hourRadius;
|
||||
g.fillCircle(x, y, radius);
|
||||
};
|
||||
|
||||
const minDot = (angle,radius) => {
|
||||
const a = angle * pRad;
|
||||
const x = centerPx + Math.sin(a) * minRadius;
|
||||
const y = centerPx - Math.cos(a) * minRadius;
|
||||
g.fillCircle(x, y, radius);
|
||||
};
|
||||
|
||||
const drawAll = () => {
|
||||
g.clear();
|
||||
currentDate = new Date();
|
||||
// draw hands first
|
||||
onMinute();
|
||||
// draw seconds
|
||||
const currentSec = currentDate.getSeconds();
|
||||
// draw all secs
|
||||
|
||||
for (let i = 0; i < 60; i++) {
|
||||
if (i > currentSec) {
|
||||
g.setColor(0, 0, 0.6);
|
||||
} else {
|
||||
g.setColor(0.3, 0.3, 1);
|
||||
}
|
||||
seconds((360 * i) / 60);
|
||||
}
|
||||
onSecond();
|
||||
};
|
||||
|
||||
const resetSeconds = () => {
|
||||
g.setColor(0, 0, 0.6);
|
||||
for (let i = 0; i < 60; i++) {
|
||||
seconds((360 * i) / 60);
|
||||
}
|
||||
};
|
||||
|
||||
const drawMin = () => {
|
||||
g.setColor(0.5, 0.5, 0.5);
|
||||
for (let i = 0; i < 60; i++) {
|
||||
minDot((360 * i) / 60,1);
|
||||
}
|
||||
};
|
||||
|
||||
const drawHour = () => {
|
||||
g.setColor(0.5, 0.5, 0.5);
|
||||
for (let i = 0; i < 12; i++) {
|
||||
hourDot((360 * 5 * i) / 60,1);
|
||||
}
|
||||
};
|
||||
|
||||
const onSecond = () => {
|
||||
g.setColor(0.3, 0.3, 1);
|
||||
seconds((360 * currentDate.getSeconds()) / 60);
|
||||
if (currentDate.getSeconds() === 59) {
|
||||
resetSeconds();
|
||||
onMinute();
|
||||
}
|
||||
g.setColor(1, 0.7, 0.2);
|
||||
currentDate = new Date();
|
||||
seconds((360 * currentDate.getSeconds()) / 60);
|
||||
g.setColor(1, 1, 1);
|
||||
};
|
||||
|
||||
const drawDate = () => {
|
||||
g.reset();
|
||||
g.setColor(1, 1, 1);
|
||||
g.setFont('6x8', 2);
|
||||
|
||||
const dayString = locale.dow(currentDate, true);
|
||||
// pad left date
|
||||
const dateString = ((currentDate.getDate() < 10) ? '0' : '') + currentDate.getDate().toString();
|
||||
const dateDisplay = `${dayString} ${dateString}`;
|
||||
// console.log(`${dayString}|${dateString}`);
|
||||
// center date
|
||||
const l = (g.getWidth() - g.stringWidth(dateDisplay)) / 2;
|
||||
const t = centerPx - 6 ;
|
||||
g.drawString(dateDisplay, l, t);
|
||||
// console.log(l, t);
|
||||
};
|
||||
const onMinute = () => {
|
||||
if (currentDate.getHours() === 0 && currentDate.getMinutes() === 0) {
|
||||
g.clear();
|
||||
resetSeconds();
|
||||
}
|
||||
// clear existing hands
|
||||
g.setColor(0, 0, 0);
|
||||
hourDot((360 * currentDate.getHours()) / 12,4);
|
||||
minDot((360 * currentDate.getMinutes()) / 60,3);
|
||||
|
||||
// Hour
|
||||
drawHour();
|
||||
// Minute
|
||||
drawMin();
|
||||
|
||||
// get new date, then draw new hands
|
||||
currentDate = new Date();
|
||||
g.setColor(1, 0, 0);
|
||||
// Hour
|
||||
hourDot((360 * currentDate.getHours()) / 12,4);
|
||||
g.setColor(1, 0.9, 0.9);
|
||||
// Minute
|
||||
minDot((360 * currentDate.getMinutes()) / 60,3);
|
||||
if (currentDate.getHours() >= 0 && currentDate.getMinutes() === 0) {
|
||||
Bangle.buzz();
|
||||
}
|
||||
drawDate();
|
||||
};
|
||||
|
||||
const startTimers = () => {
|
||||
timer = setInterval(onSecond, 1000);
|
||||
};
|
||||
|
||||
Bangle.on('lcdPower', (on) => {
|
||||
if (on) {
|
||||
// g.clear();
|
||||
drawAll();
|
||||
startTimers();
|
||||
Bangle.drawWidgets();
|
||||
} else {
|
||||
if (timer) {
|
||||
clearInterval(timer);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
g.clear();
|
||||
resetSeconds();
|
||||
startTimers();
|
||||
drawAll();
|
||||
Bangle.loadWidgets();
|
||||
Bangle.drawWidgets();
|
||||
|
||||
// Show launcher when middle button pressed
|
||||
setWatch(Bangle.showLauncher, BTN2, { repeat: false, edge: "falling" });
|
After Width: | Height: | Size: 1.8 KiB |
|
@ -31,7 +31,7 @@ function showMainMenu() {
|
|||
}
|
||||
|
||||
function eraseApp(app) {
|
||||
E.showMessage('Erasing ' + app.name + '...');
|
||||
E.showMessage('Erasing\n' + app.name + '...');
|
||||
storage.erase(app['']);
|
||||
storage.erase(app.icon);
|
||||
storage.erase(app.src);
|
||||
|
@ -44,7 +44,7 @@ function showAppMenu(app) {
|
|||
},
|
||||
'< Back': () => m = showApps(),
|
||||
'Erase': () => {
|
||||
E.showPrompt('Erase ' + app.name + '?').then((v) => {
|
||||
E.showPrompt('Erase\n' + app.name + '?').then((v) => {
|
||||
if (v) {
|
||||
Bangle.buzz(100, 1);
|
||||
eraseApp(app);
|
||||
|
@ -66,10 +66,10 @@ function showApps() {
|
|||
'< Back': () => m = showMainMenu(),
|
||||
};
|
||||
|
||||
var list = storage.list().filter((a)=> {
|
||||
return a[0]=='+' && a !== '+setting';
|
||||
var list = storage.list(/\.info$/).filter((a)=> {
|
||||
return a !== 'setting.info';
|
||||
}).sort().map((app) => {
|
||||
var ret = storage.readJSON(app);
|
||||
var ret = storage.readJSON(app,1)||{};
|
||||
ret[''] = app;
|
||||
return ret;
|
||||
});
|
||||
|
|
|
@ -1,5 +0,0 @@
|
|||
{
|
||||
"name":"App Manager","type":"app",
|
||||
"icon":"*files",
|
||||
"src":"-files"
|
||||
}
|