Merge branch 'espruino:master' into master
|
@ -17,7 +17,7 @@ class TwoK {
|
|||
bh = Math.floor(h/4);
|
||||
bw = Math.floor(w/4);
|
||||
g.clearRect(0, 0, g.getWidth()-1, yo).setFontAlign(0, 0, 0);
|
||||
g.setFont("Vector", 16).setColor("#fff").drawString("Score:"+this.score.toString(), g.getWidth()/2, 8);
|
||||
g.setFont("Vector", 16).setColor(g.theme.fg).drawString("Score:"+this.score.toString(), g.getWidth()/2, 8);
|
||||
this.drawBRect(xo-3, yo-3, xo+w+2, yo+h+2, 4, "#a88", "#caa", false);
|
||||
for (y=0; y<4; ++y)
|
||||
for (x=0; x<4; ++x) {
|
||||
|
|
|
@ -0,0 +1,2 @@
|
|||
0.01: New app!
|
||||
0.02: Better support for watch themes
|
|
@ -2,7 +2,7 @@
|
|||
"name": "2047pp",
|
||||
"shortName":"2047pp",
|
||||
"icon": "app.png",
|
||||
"version":"0.01",
|
||||
"version":"0.02",
|
||||
"description": "Bangle version of a tile shifting game",
|
||||
"supports" : ["BANGLEJS","BANGLEJS2"],
|
||||
"allow_emulator": true,
|
||||
|
|
|
@ -1 +1,2 @@
|
|||
0.01: New App!
|
||||
0.02: Now keeps user input trace intact by changing how the screen is updated.
|
||||
|
|
|
@ -45,11 +45,39 @@ exports.getStrokes( (id,s) => Bangle.strokes[id] = Unistroke.new(s) );
|
|||
|
||||
var flashToggle = false;
|
||||
const R = Bangle.appRect;
|
||||
var Rx1;
|
||||
var Rx2;
|
||||
var Ry1;
|
||||
var Ry2;
|
||||
|
||||
function findMarker(strArr) {
|
||||
if (strArr.length == 0) {
|
||||
Rx1 = 4;
|
||||
Rx2 = 6*4;
|
||||
Ry1 = 8*4;
|
||||
Ry2 = 8*4 + 3;
|
||||
} else if (strArr.length <= 4) {
|
||||
Rx1 = (strArr[strArr.length-1].length)%7*6*4 + 4 ;
|
||||
Rx2 = (strArr[strArr.length-1].length)%7*6*4 + 6*4;
|
||||
Ry1 = (strArr.length)*(8*4) + Math.floor((strArr[strArr.length-1].length)/7)*(8*4);
|
||||
Ry2 = (strArr.length)*(8*4) + Math.floor((strArr[strArr.length-1].length)/7)*(8*4) + 3;
|
||||
} else {
|
||||
Rx1 = (strArr[strArr.length-1].length)%7*6*4 + 4 ;
|
||||
Rx2 = (strArr[strArr.length-1].length)%7*6*4 + 6*4;
|
||||
Ry1 = (4)*(8*4) + Math.floor((strArr[strArr.length-1].length)/7)*(8*4);
|
||||
Ry2 = (4)*(8*4) + Math.floor((strArr[strArr.length-1].length)/7)*(8*4) + 3;
|
||||
}
|
||||
//print(Rx1,Rx2,Ry1, Ry2);
|
||||
return {x:Rx1,y:Ry1,x2:Rx2,y2:Ry2};
|
||||
}
|
||||
|
||||
function draw(noclear) {
|
||||
g.reset();
|
||||
if (!noclear) g.clearRect(R);
|
||||
var l = g.setFont("6x8:4").wrapString(text+(flashToggle?"_":" "), R.w-8);
|
||||
var l = g.setFont("6x8:4").wrapString(text+' ', R.w-8);
|
||||
if (!l) l = [];
|
||||
//print(text+':');
|
||||
//print(l);
|
||||
if (!noclear) (flashToggle?(g.fillRect(findMarker(l))):(g.clearRect(findMarker(l))));
|
||||
if (l.length>4) l=l.slice(-4);
|
||||
g.drawString(l.join("\n"),R.x+4,R.y+4);
|
||||
}
|
||||
|
@ -80,6 +108,7 @@ exports.getStrokes( (id,s) => Bangle.strokes[id] = Unistroke.new(s) );
|
|||
var ch = o.stroke;
|
||||
if (ch=="\b") text = text.slice(0,-1);
|
||||
else text += ch;
|
||||
g.clearRect(R);
|
||||
}
|
||||
flashToggle = true;
|
||||
draw();
|
||||
|
@ -87,7 +116,7 @@ exports.getStrokes( (id,s) => Bangle.strokes[id] = Unistroke.new(s) );
|
|||
Bangle.on('stroke',strokeHandler);
|
||||
g.reset().clearRect(R);
|
||||
show();
|
||||
draw(true);
|
||||
draw(false);
|
||||
var flashInterval;
|
||||
|
||||
return new Promise((resolve,reject) => {
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{ "id": "kbswipe",
|
||||
"name": "Swipe keyboard",
|
||||
"version":"0.01",
|
||||
"version":"0.02",
|
||||
"description": "A library for text input via PalmOS style swipe gestures (beta!)",
|
||||
"icon": "app.png",
|
||||
"type":"textinput",
|
||||
|
|
|
@ -662,7 +662,7 @@ var locales = {
|
|||
thousands_sep: " ",
|
||||
currency_symbol: "kr",
|
||||
int_curr_symbol: "NOK",
|
||||
speed: "kmh",
|
||||
speed: "kmt",
|
||||
distance: { 0: "m", 1: "km" },
|
||||
temperature: "°C",
|
||||
ampm: { 0: "", 1: "" },
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
0.01: Initial version
|
|
@ -0,0 +1,20 @@
|
|||
# WARNING
|
||||
|
||||
This app uses the [Scheduler library](https://banglejs.com/apps/?id=sched) and requires a keyboard such as [Swipe keyboard](https://banglejs.com/apps/?id=kbswipe).
|
||||
|
||||
## Usage
|
||||
|
||||
* Select "New note" and use the onscreen keyboard to type.
|
||||
* Hit back button to exit back to the main menu. New notes are added to the main menu. If you don't type anything and you hit the back button, no new note will be saved.
|
||||
* Selecting a note from the main menu will allow you to edit, delete, or change the position of the note (1 being the top of the list).
|
||||
* By selecting "set as alarm" or "set as timer", you can also use this note as a custom message for alerts from alarms and timers. Once you hit save, the alarm or timer is set.
|
||||
* Any alarms or timers you set will appear under "edit alarms/timers." If the alarm/timer is set to a note, the note will appear on the top of the menu. If an alarm/timer is set without a custom message, it will simply say Alarm or Timer on the top of the menu.
|
||||
* On the alarm/timer alert, only the first 30 characters of the note will appear - any more and you run the risk of pushing the sleep/ok buttons off-screen.
|
||||
|
||||
## Images
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
|
@ -0,0 +1 @@
|
|||
E.toArrayBuffer(atob("MDCEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAO7u7u7u7u7u7u7u7u7u7u7u7gAAAAAAAO7u7u7u7u7u7u7u7u7u7u7u7gAAAAAAAO7u7u7u7u7u7u7u7u7u7u7u7gAAAAAAAO7u7u7u7u7u7u7u7u7u7u7u7gAAAAAAAO7u7u7u7u7u7u7u7u7u7u7u7gAAAAAAAO7u7u7u7u7u7u7u7u7u7u7u7gAAAAAAAO7u7u7u7u7u7u7u7u7u7u7u7gAAAAAAAO7u7u7u7u7u7u7u7u7u7u7u7gAAAAAAAO7u3d3d3d3d3d3d3d3d3d3u7gAAAAAAAO7u3d3d3d3d3d3d3d3d3d3u7gAAAAAAAO7u7u7u7u7u7u7u7u7u7u7u7gAAAAAAAO7u7u7u7u7u7u7u7u7u7u7u7gAAAAAAAO7u7u7u7u7u7u7u7u7u7u7u7gAAAAAAAO7u3d3d3d3d3d3d3d3d3d3u7gAAAAAAAO7u3d3d3d3d3d3d3d3d3d3u7gAAAAAAAO7u7u7u7u7u7u7u7u7u7u7u7gAAAAAAAO7u7u7u7u7u7u7u7u7u7u7u7gAAAAAAAO7u7u7u7u7u7u7u7u7u7u7u7gAAAAAAAO7u3d3d3d3d3d3u7u7u7u7u7gAAAAAAAO7u3d3d3d3d3d3u7u7u7u7u7gAAAAAAAO7u7u7u7u7u7u7u7u7u7u7u7gAAAAAAAO7u7u7u7u7u7u7u7u7u7u7u7gAAAAAAAO7u7u7u7u7u7u7u7u7u7u7u7gAAAAAAAO7u7u7u7u7u7u7u7u7u7u7u7gAAAAAAAO7u7u7u7u7u7u7u7u7u7u7u7gAAAAAAAO7u7u7u7u7u7u7u7u7u7u7u7gAAAAAAAO7u7u7u7u7u7u7u7u7/////7gAAAAAAAO7u7u7u7u7u7u7u7u7////+4AAAAAAAAO7u7u7u7u7u7u7u7u7////uAAAAAAAAAO7u7u7u7u7u7u7u7u7///7gAAAAAAAAAO7u7u7u7u7u7u7u7u7//+4AAAAAAAAAAO7u7u7u7u7u7u7u7u7//uAAAAAAAAAAAO7u7u7u7u7u7u7u7u7/7gAAAAAAAAAAAO7u7u7u7u7u7u7u7u7+4AAAAAAAAAAAAO7u7u7u7u7u7u7u7u7uAAAAAAAAAAAAAO7u7u7u7u7u7u7u7u7gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=="))
|
|
@ -0,0 +1,304 @@
|
|||
Bangle.loadWidgets();
|
||||
Bangle.drawWidgets();
|
||||
|
||||
var notes = require("Storage").readJSON("noteify.json", true) || [];
|
||||
var alarms = require("sched").getAlarms();
|
||||
msg = "";
|
||||
|
||||
function startNote(idx) {
|
||||
idx == undefined ? note = "" : note = notes[idx].note;
|
||||
require("textinput").input({text:note}).then(result => {
|
||||
if (result != "") {
|
||||
idx == undefined ? notes.push({"note" : result}) : notes[idx].note = result;
|
||||
require("Storage").write("noteify.json",JSON.stringify(notes));
|
||||
}
|
||||
showMainMenu();
|
||||
});
|
||||
}
|
||||
|
||||
function viewNote(idx) {
|
||||
var textY = 30;
|
||||
var textBound = g.stringMetrics(g.setColor(g.theme.fg).setFont("6x8:2").setFontAlign(-1, -1).drawString(g.wrapString(notes[idx].note, g.getWidth()).join("\n"), 0, textY)).height;
|
||||
Bangle.setUI({mode:"custom", drag:e=>{
|
||||
textY += e.dy;
|
||||
g.setClipRect(0, 30, g.getWidth(), g.getHeight());
|
||||
if (textY > 30) textY = 30;
|
||||
if (textY < textBound) textY = textBound;
|
||||
g.clearRect(0, 30, g.getWidth(), g.getHeight()).setColor(g.theme.fg).setFont("6x8:2").setFontAlign(-1, -1).drawString(g.wrapString(notes[idx].note, g.getWidth()).join("\n"), 0, textY);
|
||||
},back:()=>{
|
||||
Bangle.setUI();
|
||||
showEditMenu(idx);
|
||||
}});
|
||||
|
||||
}
|
||||
|
||||
function showMainMenu() {
|
||||
var mainMenu = {
|
||||
"" : { "title" : "Noteify" },
|
||||
"< Back" : function() { load(); },
|
||||
"New note" : function() {
|
||||
E.showMenu();
|
||||
startNote();
|
||||
},
|
||||
"Edit alarms/timers" : function() { showAlarmMenu(); },
|
||||
};
|
||||
|
||||
notes.forEach((a, idx) => {
|
||||
mainMenu[notes[idx].note.length > 12 ? notes[idx].note.substring(0, 12)+"..." : notes[idx].note] = function () { showEditMenu(idx);};
|
||||
});
|
||||
msg = "";
|
||||
E.showMenu(mainMenu);
|
||||
}
|
||||
|
||||
function showEditMenu(idx) {
|
||||
var moveNote = notes[idx].note;
|
||||
var editMenu = {
|
||||
"" : { "title" : notes[idx].note.length > 12 ? notes[idx].note.replace(/\n/g, " ").substring(0, 12)+"..." : notes[idx].note.replace(/\n/g, " ") },
|
||||
"View note" : function() {
|
||||
E.showMenu();
|
||||
viewNote(idx);
|
||||
},
|
||||
"Edit note" : function() {
|
||||
E.showMenu();
|
||||
startNote(idx);
|
||||
},
|
||||
"Delete note" : function() {
|
||||
notes.splice(idx,1);
|
||||
require("Storage").write("noteify.json",JSON.stringify(notes));
|
||||
showMainMenu();
|
||||
},
|
||||
"Set as alarm" : function() {
|
||||
//limit alarm msg to 30 chars
|
||||
msg = moveNote.substring(0, 30);
|
||||
editAlarm(-1);
|
||||
},
|
||||
"Set as timer" : function () {
|
||||
msg = moveNote.substring(0, 30);
|
||||
editTimer(-1);
|
||||
},
|
||||
"Change position" : {
|
||||
value : idx+1,
|
||||
min : 1,
|
||||
max : notes.length,
|
||||
wrap : true,
|
||||
onchange : function(v) {
|
||||
//save changes from change position
|
||||
if (v-1 != idx) {
|
||||
notes.splice(v-1, 0, notes.splice(idx, 1)[0]);
|
||||
require("Storage").write("noteify.json",JSON.stringify(notes));
|
||||
}
|
||||
},
|
||||
},
|
||||
"< Back" : function() {
|
||||
showMainMenu();
|
||||
},
|
||||
};
|
||||
E.showMenu(editMenu);
|
||||
}
|
||||
|
||||
function decodeTime(t) {
|
||||
t = 0|t; // sanitise
|
||||
var hrs = 0|(t/3600000);
|
||||
return { hrs : hrs, mins : Math.round((t-hrs*3600000)/60000) };
|
||||
}
|
||||
|
||||
// time in { hrs, mins } -> ms
|
||||
function encodeTime(o) {
|
||||
return o.hrs*3600000 + o.mins*60000;
|
||||
}
|
||||
|
||||
function formatTime(t) {
|
||||
var o = decodeTime(t);
|
||||
return o.hrs+":"+("0"+o.mins).substr(-2);
|
||||
}
|
||||
|
||||
function getCurrentTime() {
|
||||
var time = new Date();
|
||||
return (
|
||||
time.getHours() * 3600000 +
|
||||
time.getMinutes() * 60000 +
|
||||
time.getSeconds() * 1000
|
||||
);
|
||||
}
|
||||
|
||||
function saveAndReload() {
|
||||
require("sched").setAlarms(alarms);
|
||||
require("sched").reload();
|
||||
}
|
||||
|
||||
function showAlarmMenu() {
|
||||
const menu = {
|
||||
'': { 'title': 'Alarm/Timer' },
|
||||
'< Back' : ()=>{showMainMenu();},
|
||||
'New Alarm': ()=>editAlarm(-1),
|
||||
'New Timer': ()=>editTimer(-1)
|
||||
};
|
||||
alarms.forEach((alarm,idx)=>{
|
||||
var type,txt; // a leading space is currently required (JS error in Espruino 2v12)
|
||||
if (alarm.timer) {
|
||||
type = /*LANG*/"Timer";
|
||||
txt = " "+formatTime(alarm.timer);
|
||||
} else {
|
||||
type = /*LANG*/"Alarm";
|
||||
txt = " "+formatTime(alarm.t);
|
||||
}
|
||||
if (alarm.rp) txt += "\0"+atob("FBaBAAABgAAcAAHn//////wAHsABzAAYwAAMAADAAAAAAwAAMAADGAAzgAN4AD//////54AAOAABgAA=");
|
||||
// rename duplicate alarms
|
||||
if (menu[type+txt]) {
|
||||
var n = 2;
|
||||
while (menu[type+" "+n+txt]) n++;
|
||||
txt = type+" "+n+txt;
|
||||
} else txt = type+txt;
|
||||
// add to menu
|
||||
menu[txt] = {
|
||||
value : "\0"+atob(alarm.on?"EhKBAH//v/////////////5//x//j//H+eP+Mf/A//h//z//////////3//g":"EhKBAH//v//8AA8AA8AA8AA8AA8AA8AA8AA8AA8AA8AA8AA8AA8AA///3//g"),
|
||||
onchange : function() {
|
||||
if (alarm.timer) editTimer(idx, alarm);
|
||||
else editAlarm(idx, alarm);
|
||||
}
|
||||
};
|
||||
});
|
||||
if (WIDGETS["alarm"]) WIDGETS["alarm"].reload();
|
||||
return E.showMenu(menu);
|
||||
}
|
||||
|
||||
function editDOW(dow, onchange) {
|
||||
const menu = {
|
||||
'': { 'title': 'Days of Week' },
|
||||
'< Back' : () => onchange(dow)
|
||||
};
|
||||
for (var i = 0; i < 7; i++) (i => {
|
||||
var dayOfWeek = require("locale").dow({ getDay: () => i });
|
||||
menu[dayOfWeek] = {
|
||||
value: !!(dow&(1<<i)),
|
||||
format: v => v ? "Yes" : "No",
|
||||
onchange: v => v ? dow |= 1<<i : dow &= ~(1<<i),
|
||||
};
|
||||
})(i);
|
||||
E.showMenu(menu);
|
||||
}
|
||||
|
||||
function editAlarm(alarmIndex, alarm) {
|
||||
var newAlarm = alarmIndex<0;
|
||||
var a = {
|
||||
t : 12*3600000, // 12 o clock default
|
||||
on : true,
|
||||
rp : true,
|
||||
as : false,
|
||||
dow : 0b1111111,
|
||||
last : 0,
|
||||
vibrate : ".."
|
||||
};
|
||||
if (msg != "") a["msg"] = msg;
|
||||
if (!newAlarm) Object.assign(a, alarms[alarmIndex]);
|
||||
if (alarm) Object.assign(a,alarm);
|
||||
var t = decodeTime(a.t);
|
||||
|
||||
var alarmTitle = (a.msg == undefined) ? 'Alarm' : (a.msg.length > 12) ? a.msg.replace(/\n/g, " ").substring(0, 12)+"..." : msg.replace(/\n/g, " ").substring(0, 12)+"...";
|
||||
|
||||
const menu = {
|
||||
'': { 'title': alarmTitle },
|
||||
'< Back' : () => showAlarmMenu(),
|
||||
'Days': {
|
||||
value: "SMTWTFS".split("").map((d,n)=>a.dow&(1<<n)?d:".").join(""),
|
||||
onchange: () => editDOW(a.dow, d=>{a.dow=d;editAlarm(alarmIndex,a)})
|
||||
},
|
||||
'Hours': {
|
||||
value: t.hrs, min : 0, max : 23, wrap : true,
|
||||
onchange: v => t.hrs=v
|
||||
},
|
||||
'Minutes': {
|
||||
value: t.mins, min : 0, max : 59, wrap : true,
|
||||
onchange: v => t.mins=v
|
||||
},
|
||||
'Enabled': {
|
||||
value: a.on,
|
||||
format: v=>v?"On":"Off",
|
||||
onchange: v=>a.on=v
|
||||
},
|
||||
'Repeat': {
|
||||
value: a.rp,
|
||||
format: v=>v?"Yes":"No",
|
||||
onchange: v=>a.rp=v
|
||||
},
|
||||
'Vibrate': require("buzz_menu").pattern(a.vibrate, v => a.vibrate=v ),
|
||||
'Auto snooze': {
|
||||
value: a.as,
|
||||
format: v=>v?"Yes":"No",
|
||||
onchange: v=>a.as=v
|
||||
}
|
||||
};
|
||||
menu["Save"] = function() {
|
||||
a.t = encodeTime(t);
|
||||
a.last = (a.t < getCurrentTime()) ? (new Date()).getDate() : 0;
|
||||
a.last = 0;
|
||||
if (newAlarm) alarms.push(a);
|
||||
else alarms[alarmIndex] = a;
|
||||
saveAndReload();
|
||||
showMainMenu();
|
||||
};
|
||||
if (!newAlarm) {
|
||||
menu["Delete"] = function() {
|
||||
alarms.splice(alarmIndex,1);
|
||||
saveAndReload();
|
||||
showMainMenu();
|
||||
};
|
||||
}
|
||||
return E.showMenu(menu);
|
||||
}
|
||||
|
||||
function editTimer(alarmIndex, alarm) {
|
||||
var newAlarm = alarmIndex<0;
|
||||
var a = {
|
||||
timer : 5*60*1000, // 5 minutes
|
||||
on : true,
|
||||
rp : false,
|
||||
as : false,
|
||||
dow : 0b1111111,
|
||||
last : 0,
|
||||
vibrate : ".."
|
||||
};
|
||||
if (msg != "") a["msg"] = msg;
|
||||
if (!newAlarm) Object.assign(a, alarms[alarmIndex]);
|
||||
if (alarm) Object.assign(a,alarm);
|
||||
var t = decodeTime(a.timer);
|
||||
|
||||
var timerTitle = (a.msg == undefined) ? 'Timer' : (a.msg.length > 12) ? a.msg.replace(/\n/g, " ").substring(0, 12)+"..." : msg.replace(/\n/g, " ").substring(0, 12)+"...";
|
||||
|
||||
const menu = {
|
||||
'': { 'title': timerTitle },
|
||||
'< Back' : () => showMainMenu(),
|
||||
'Hours': {
|
||||
value: t.hrs, min : 0, max : 23, wrap : true,
|
||||
onchange: v => t.hrs=v
|
||||
},
|
||||
'Minutes': {
|
||||
value: t.mins, min : 0, max : 59, wrap : true,
|
||||
onchange: v => t.mins=v
|
||||
},
|
||||
'Enabled': {
|
||||
value: a.on,
|
||||
format: v=>v?"On":"Off",
|
||||
onchange: v=>a.on=v
|
||||
},
|
||||
'Vibrate': require("buzz_menu").pattern(a.vibrate, v => a.vibrate=v ),
|
||||
};
|
||||
menu["Save"] = function() {
|
||||
a.timer = encodeTime(t);
|
||||
a.t = getCurrentTime() + a.timer;
|
||||
if (newAlarm) alarms.push(a);
|
||||
else alarms[alarmIndex] = a;
|
||||
saveAndReload();
|
||||
showMainMenu();
|
||||
};
|
||||
if (!newAlarm) {
|
||||
menu["Delete"] = function() {
|
||||
alarms.splice(alarmIndex,1);
|
||||
saveAndReload();
|
||||
showMainMenu();
|
||||
};
|
||||
}
|
||||
return E.showMenu(menu);
|
||||
}
|
||||
|
||||
showMainMenu();
|
After Width: | Height: | Size: 284 B |
After Width: | Height: | Size: 3.4 KiB |
|
@ -0,0 +1,22 @@
|
|||
{
|
||||
"id": "noteify",
|
||||
"name": "Noteify",
|
||||
"version": "0.01",
|
||||
"description": "Write notes using an onscreen keyboard and use them as custom messages for alarms or timers.",
|
||||
"icon": "app.png",
|
||||
"tags": "tool,alarm",
|
||||
"supports": ["BANGLEJS2"],
|
||||
"readme": "README.md",
|
||||
"storage": [
|
||||
{"name":"noteify.app.js","url":"app.js"},
|
||||
{"name":"noteify.img","url":"app-icon.js","evaluate":true},
|
||||
{"name":"noteify.wid.js","url":"widget.js"}
|
||||
],
|
||||
"data": [{"name":"noteify.json"}],
|
||||
"dependencies": {"scheduler":"type","textinput":"type"},
|
||||
"screenshots": [
|
||||
{"url": "menu.png"},
|
||||
{"url": "note.png"},
|
||||
{"url": "timer-alert.png"}
|
||||
]
|
||||
}
|
After Width: | Height: | Size: 3.3 KiB |
After Width: | Height: | Size: 2.9 KiB |
|
@ -0,0 +1,8 @@
|
|||
WIDGETS["alarm"]={area:"tl",width:0,draw:function() {
|
||||
if (this.width) g.reset().drawImage(atob("GBgBAAAAAAAAABgADhhwDDwwGP8YGf+YMf+MM//MM//MA//AA//AA//AA//AA//AA//AB//gD//wD//wAAAAADwAABgAAAAAAAAA"),this.x,this.y);
|
||||
},reload:function() {
|
||||
// don't include library here as we're trying to use as little RAM as possible
|
||||
WIDGETS["alarm"].width = (require('Storage').readJSON('sched.json',1)||[]).some(alarm=>alarm.on&&(alarm.hidden!==false)) ? 24 : 0;
|
||||
}
|
||||
};
|
||||
WIDGETS["alarm"].reload();
|
|
@ -0,0 +1 @@
|
|||
0.01: New App!
|
|
@ -0,0 +1,22 @@
|
|||
# OpenWind
|
||||
|
||||
Receive and display data from a wireless [OpenWind](https://www.openwind.de/) sailing wind instrument on the Bangle.
|
||||
|
||||
## Usage
|
||||
|
||||
Upon startup, the app will attempt to automatically connect to the wind instrument. This typically only takes a few seconds.
|
||||
|
||||
## Features
|
||||
|
||||
The app displays the apparent wind direction (via a green dot) and speed (green numbers, in knots) relative to the mounting direction of the wind vane.
|
||||
If "True wind" is enabled in settings and a GPS fix is available, the true wind speed and direction (relative to the mounting direction of the vane) is
|
||||
additionally displayed in red. In this mode, the speed over ground in knots is also shown at the bottom left of the screen.
|
||||
|
||||
## Controls
|
||||
|
||||
There are no controls in the main app, but there are two settings in the settings app that can be changed:
|
||||
|
||||
* True wind: enables or disables true wind calculations; enabling this will turn on GPS inside the app
|
||||
* Mounting angle: mounting relative to the boat of the wind instrument (in degrees)
|
||||
|
||||

|
|
@ -0,0 +1 @@
|
|||
require("heatshrink").decompress(atob("mEwxH+AH4A/AH4A/AH4A/AH4A/AH4AzhMJF94wtF+QwsF/4APnAACF54wZFoYxNF7guHGBQv0GCwuJGBIvFACov/AD4vvd6Yv/GCoumGIwtpAH4A/AH4A/AH4A/AH4A/AH4A/AH4A/AHoA=="))
|
|
@ -0,0 +1,113 @@
|
|||
OW_CHAR_UUID = '0000cc91-0000-1000-8000-00805f9b34fb';
|
||||
require("Font7x11Numeric7Seg").add(Graphics);
|
||||
gatt = {};
|
||||
cx = g.getWidth()/2;
|
||||
cy = 24+(g.getHeight()-24)/2;
|
||||
w = (g.getWidth()-24)/2;
|
||||
|
||||
gps_course = { spd: 0 };
|
||||
|
||||
var settings = require("Storage").readJSON('openwindsettings.json', 1) || {};
|
||||
|
||||
i = 0;
|
||||
hullpoly = [];
|
||||
for (y=-1; y<=1; y+=0.1) {
|
||||
hullpoly[i++] = cx - (y<0 ? 1+y*0.15 : (Math.sqrt(1-0.7*y*y)-Math.sqrt(0.3))/(1-Math.sqrt(0.3)))*w*0.3;
|
||||
hullpoly[i++] = cy - y*w*0.7;
|
||||
}
|
||||
for (y=1; y>=-1; y-=0.1) {
|
||||
hullpoly[i++] = cx + (y<0 ? 1+y*0.15 : (Math.sqrt(1-0.7*y*y)-Math.sqrt(0.3))/(1-Math.sqrt(0.3)))*w*0.3;
|
||||
hullpoly[i++] = cy - y*w*0.7;
|
||||
}
|
||||
|
||||
function wind_updated(ev) {
|
||||
if (ev.target.uuid == "0xcc91") {
|
||||
awa = settings.mount_angle-ev.target.value.getInt16(1, true)*0.1;
|
||||
aws = ev.target.value.getInt16(3, true)*0.01;
|
||||
// console.log(awa, aws);
|
||||
if (gps_course.spd > 0) {
|
||||
wv = { // wind vector (in fixed reference frame)
|
||||
lon: Math.sin(Math.PI*(gps_course.course+awa)/180)*aws,
|
||||
lat: Math.cos(Math.PI*(gps_course.course+awa)/180)*aws
|
||||
};
|
||||
twv = { lon: wv.lon+gps_course.lon, lat: wv.lat+gps_course.lat };
|
||||
tws = Math.sqrt(Math.pow(twv.lon,2)+Math.pow(twv.lat, 2));
|
||||
twa = Math.atan2(twv.lat, twv.lon)*180/Math.PI-gps_course.course;
|
||||
if (twa<0) twa += 360;
|
||||
if (twa>360) twa -=360;
|
||||
}
|
||||
else {
|
||||
tws = -1;
|
||||
twa = 0;
|
||||
}
|
||||
draw_compass(awa,aws,twa,tws);
|
||||
}
|
||||
}
|
||||
|
||||
function draw_compass(awa, aws, twa, tws) {
|
||||
g.clearRect(0, 24, g.getWidth()-1, g.getHeight()-1);
|
||||
fh = w*0.15;
|
||||
g.setColor(0, 0, 1).fillPoly(hullpoly);
|
||||
g.setFontVector(fh).setColor(g.theme.fg);
|
||||
g.setFontAlign(0, 0, 0).drawString("0", cx, 24+fh/2);
|
||||
g.setFontAlign(0, 0, 1).drawString("90", g.getWidth()-12-fh, cy);
|
||||
g.setFontAlign(0, 0, 2).drawString("180", cx, g.getHeight()-fh/2);
|
||||
g.setFontAlign(0, 0, 3).drawString("270", 12+fh/2, cy);
|
||||
for (i=0; i<4; ++i) {
|
||||
a = i*Math.PI/2+Math.PI/4;
|
||||
g.drawLineAA(cx+Math.cos(a)*w*0.85, cy+Math.sin(a)*w*0.85, cx+Math.cos(a)*w*0.99, cy+Math.sin(a)*w*0.99);
|
||||
}
|
||||
g.setColor(0, 1, 0).fillCircle(cx+Math.sin(Math.PI*awa/180)*w*0.9, cy+Math.cos(Math.PI*awa/180)*w*0.9, w*0.1);
|
||||
if (tws>0) g.setColor(1, 0, 0).fillCircle(cx+Math.sin(Math.PI*twa/180)*w*0.9, cy+Math.cos(Math.PI*twa/180)*w*0.9, w*0.1);
|
||||
g.setColor(0, 1, 0).setFont("7x11Numeric7Seg",w*0.06);
|
||||
g.setFontAlign(0, 0, 0).drawString(aws.toFixed(1), cx, cy-0.32*w);
|
||||
if (tws>0) g.setColor(1, 0, 0).drawString(tws.toFixed(1), cx, cy+0.32*w);
|
||||
if (settings.truewind && typeof gps_course.spd!=='undefined') {
|
||||
spd = gps_course.spd/1.852;
|
||||
g.setColor(g.theme.fg).setFont("7x11Numeric7Seg", w*0.03).setFontAlign(-1, 1, 0).drawString(spd.toFixed(1), 1, g.getHeight()-1);
|
||||
}
|
||||
}
|
||||
|
||||
function parseDevice(d) {
|
||||
device = d;
|
||||
console.log("Found device");
|
||||
device.gatt.connect().then(function(ga) {
|
||||
console.log("Connected");
|
||||
gatt = ga;
|
||||
return ga.getPrimaryService("cc90");
|
||||
}).then(function(s) {
|
||||
return s.getCharacteristic("cc91");
|
||||
}).then(function(c) {
|
||||
c.on('characteristicvaluechanged', (event)=>wind_updated(event));
|
||||
return c.startNotifications();
|
||||
}).then(function() {
|
||||
console.log("Done!");
|
||||
}).catch(function(e) {
|
||||
console.log("ERROR"+e);
|
||||
});}
|
||||
|
||||
function connection_setup() {
|
||||
NRF.setScan();
|
||||
NRF.setScan(parseDevice, { filters: [{services:["cc90"]}], timeout: 2000});
|
||||
console.log("Scanning for OW sensor");
|
||||
}
|
||||
|
||||
if (settings.truewind) {
|
||||
Bangle.on('GPS',function(fix) {
|
||||
if (fix.fix && fix.satellites>3 && fix.speed>2) { // only uses fixes w/ more than 3 sats and speed > 2kph
|
||||
gps_course =
|
||||
{ lon: Math.sin(Math.PI*fix.course/180)*fix.speed/1.852,
|
||||
lat: Math.cos(Math.PI*fix.course/180)*fix.speed/1.852,
|
||||
spd: fix.speed,
|
||||
course: fix.course
|
||||
};
|
||||
}
|
||||
else gps_course.spd = -1;
|
||||
});
|
||||
Bangle.setGPSPower(1, "app");
|
||||
}
|
||||
|
||||
Bangle.loadWidgets();
|
||||
Bangle.drawWidgets();
|
||||
draw_compass(0, 0, 0, 0);
|
||||
connection_setup();
|
After Width: | Height: | Size: 497 B |
|
@ -0,0 +1,15 @@
|
|||
{ "id": "openwind",
|
||||
"name": "OpenWind",
|
||||
"shortName":"OpenWind",
|
||||
"version":"0.01",
|
||||
"description": "OpenWind",
|
||||
"icon": "openwind.png",
|
||||
"readme": "README.md",
|
||||
"tags": "ble,outdoors,gps,sailing",
|
||||
"supports" : ["BANGLEJS", "BANGLEJS2"],
|
||||
"storage": [
|
||||
{"name":"openwind.app.js","url":"app.js"},
|
||||
{"name":"openwind.img","url":"app-icon.js","evaluate":true},
|
||||
{"name":"openwind.settings.js", "url":"settings.js"}
|
||||
]
|
||||
}
|
After Width: | Height: | Size: 497 B |
After Width: | Height: | Size: 152 KiB |
|
@ -0,0 +1,44 @@
|
|||
// This file should contain exactly one function, which shows the app's settings
|
||||
/**
|
||||
* @param {function} back Use back() to return to settings menu
|
||||
*/
|
||||
const boolFormat = v => v ? /*LANG*/"On" : /*LANG*/"Off";
|
||||
(function(back) {
|
||||
const SETTINGS_FILE = 'openwindsettings.json'
|
||||
// initialize with default settings...
|
||||
let settings = {
|
||||
'truewind': false,
|
||||
'mount_angle': 0
|
||||
}
|
||||
// ...and overwrite them with any saved values
|
||||
// This way saved values are preserved if a new version adds more settings
|
||||
const storage = require('Storage')
|
||||
const saved = storage.readJSON(SETTINGS_FILE, 1) || {}
|
||||
for (const key in saved) {
|
||||
settings[key] = saved[key];
|
||||
}
|
||||
// creates a function to safe a specific setting, e.g. save('color')(1)
|
||||
function save(key) {
|
||||
return function (value) {
|
||||
settings[key] = value;
|
||||
storage.write(SETTINGS_FILE, settings);
|
||||
}
|
||||
}
|
||||
const menu = {
|
||||
'': { 'title': 'OpenWind' },
|
||||
'< Back': back,
|
||||
'True wind': {
|
||||
value: settings.truewind,
|
||||
format: boolFormat,
|
||||
onchange: save('truewind'),
|
||||
},
|
||||
'Mounting angle': {
|
||||
value: settings.mount_angle,
|
||||
min: 0,
|
||||
max: 355,
|
||||
step: 5,
|
||||
onchange: save('mount_angle'),
|
||||
}
|
||||
}
|
||||
E.showMenu(menu);
|
||||
})
|
|
@ -1,3 +1,4 @@
|
|||
0.01: New App!
|
||||
0.02: Fix scheduling of other alarms if there is a pending alarm from the past (fix #1667)
|
||||
0.03: Fix `getTimeToAlarm` for a timer already used at same day, don't set `last` for timers.
|
||||
0.04: Fix `getTimeToAlarm` to check for next dow if alarm.t lower currentTime.
|
||||
|
|
|
@ -37,9 +37,9 @@ exports.setAlarm = function(id, alarm) {
|
|||
exports.getTimeToAlarm = function(alarm, time) {
|
||||
if (!alarm) return undefined;
|
||||
if (!time) time = new Date();
|
||||
var active = alarm.on && (alarm.dow>>time.getDay())&1 && (!alarm.date || alarm.date==time.toISOString().substr(0,10));
|
||||
if (!active) return undefined;
|
||||
var currentTime = (time.getHours()*3600000)+(time.getMinutes()*60000)+(time.getSeconds()*1000);
|
||||
var active = alarm.on && (alarm.dow>>((time.getDay()+(alarm.t<currentTime))%7))&1 && (!alarm.date || alarm.date==time.toISOString().substr(0,10));
|
||||
if (!active) return undefined;
|
||||
var t = alarm.t-currentTime;
|
||||
if (alarm.last == time.getDate() || t < -60000) t += 86400000;
|
||||
return t;
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"id": "sched",
|
||||
"name": "Scheduler",
|
||||
"version": "0.03",
|
||||
"version": "0.04",
|
||||
"description": "Scheduling library for alarms and timers",
|
||||
"icon": "app.png",
|
||||
"type": "scheduler",
|
||||
|
|
|
@ -1,10 +1,74 @@
|
|||
#!/usr/bin/nodejs
|
||||
#!/usr/bin/env node
|
||||
/* Scans for strings that may be in English in each app, and
|
||||
outputs a list of strings that have been found.
|
||||
|
||||
See https://github.com/espruino/BangleApps/issues/1311
|
||||
*/
|
||||
|
||||
var childProcess = require('child_process');
|
||||
|
||||
let refresh = false;
|
||||
|
||||
function handleCliParameters ()
|
||||
{
|
||||
let usage = "USAGE: language_scan.js [options]";
|
||||
let die = function (message) {
|
||||
console.log(usage);
|
||||
console.log(message);
|
||||
process.exit(3);
|
||||
};
|
||||
let hadTURL = false,
|
||||
hadDEEPL = false;
|
||||
for(let i = 2; i < process.argv.length; i++)
|
||||
{
|
||||
const param = process.argv[i];
|
||||
switch(param)
|
||||
{
|
||||
case '-r':
|
||||
case '--refresh':
|
||||
refresh = true;
|
||||
break;
|
||||
case '--deepl':
|
||||
i++;
|
||||
let KEY = process.argv[i];
|
||||
if(KEY === '' || KEY === null || KEY === undefined)
|
||||
{
|
||||
die('--deepl requires a parameter: the API key to use');
|
||||
}
|
||||
process.env.DEEPL = KEY;
|
||||
hadDEEPL = true;
|
||||
break;
|
||||
case '--turl':
|
||||
i++;
|
||||
let URL = process.argv[i];
|
||||
if(URL === '' || URL === null || URL === undefined)
|
||||
{
|
||||
die('--turl requires a parameter: the URL to use');
|
||||
}
|
||||
process.env.TURL = URL;
|
||||
hadTURL = true;
|
||||
break;
|
||||
case '-h':
|
||||
case '--help':
|
||||
console.log(usage+"\n");
|
||||
console.log("Parameters:");
|
||||
console.log(" -h, --help Output this help text and exit");
|
||||
console.log(" -r, --refresh Auto-add new strings into lang/*.json");
|
||||
console.log(' --deepl KEY Enable DEEPL as auto-translation engine and');
|
||||
console.log(' use KEY as its API key. You also need to provide --turl');
|
||||
console.log(' --turl URL In combination with --deepl, use URL as the API base URL');
|
||||
process.exit(0);
|
||||
default:
|
||||
die("Unknown parameter: "+param);
|
||||
}
|
||||
}
|
||||
if((hadTURL !== false || hadDEEPL !== false) && hadTURL !== hadDEEPL)
|
||||
{
|
||||
die("Use of deepl requires both a --deepl API key and --turl URL");
|
||||
}
|
||||
}
|
||||
handleCliParameters();
|
||||
|
||||
let translate = false;
|
||||
if (process.env.DEEPL) {
|
||||
// Requires translate
|
||||
|
@ -64,6 +128,14 @@ try {
|
|||
} catch (e) {
|
||||
ERROR("apps.json not found");
|
||||
}
|
||||
if (appsFile.indexOf("---") === 0 && fs.existsSync(BASEDIR+"bin/create_apps_json.sh"))
|
||||
{
|
||||
console.log("apps.json has not been generated, running bin/create_apps_json.sh to build it...");
|
||||
childProcess.execFileSync(BASEDIR+'bin/create_apps_json.sh',[],{
|
||||
stdio: 'inherit'
|
||||
});
|
||||
appsFile = fs.readFileSync(BASEDIR+"apps.json").toString();
|
||||
}
|
||||
try{
|
||||
apps = JSON.parse(appsFile);
|
||||
} catch (e) {
|
||||
|
@ -234,6 +306,11 @@ for (let language of languages) {
|
|||
translations.GLOBAL[translationItem.str] = translation;
|
||||
resolve()
|
||||
}))
|
||||
} else if(refresh && !translate) {
|
||||
translationPromises.push(new Promise(async (resolve) => {
|
||||
translations.GLOBAL[translationItem.str] = translationItem.str;
|
||||
resolve()
|
||||
}))
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
|
@ -21,5 +21,6 @@
|
|||
{"code":"pl_PL","name":"Polish","url":"pl_PL.json"},
|
||||
{"code":"ro_RO","name":"Romanian","url":"ro_RO.json"},
|
||||
{"code":"sk_SK","name":"Slovak","url":"sk_SK.json"},
|
||||
{"code":"sl_SL","name":"Slovenian","url":"sl_SL.json"}
|
||||
{"code":"sl_SL","name":"Slovenian","url":"sl_SL.json"},
|
||||
{"code":"nn_NO","name":"Norwegian (Nynorsk)","url":"nn_NO.json"}
|
||||
]
|
||||
|
|
|
@ -0,0 +1,244 @@
|
|||
{
|
||||
"//": "Norwegian nynorsk language translations",
|
||||
"GLOBAL": {
|
||||
"//": "Translations that apply for all apps",
|
||||
"Alarms": "Alarmar",
|
||||
"Hours": "Timar",
|
||||
"Minutes": "Minutt",
|
||||
"Enabled": "Slått på",
|
||||
"New Alarm": "Ny alarm",
|
||||
"Save": "Lagre",
|
||||
"Back": "Tilbake",
|
||||
"Repeat": "Gjentaking",
|
||||
"Delete": "Slett",
|
||||
"ALARM!": "ALARM!",
|
||||
"Sleep": "Søvn",
|
||||
"circle 3": "sirkel 3",
|
||||
"circle 1": "sirkel 1",
|
||||
"music": "musikk",
|
||||
"week": "veke",
|
||||
"Keep Msgs": "Behald meldingar",
|
||||
"Auto snooze": "Automatisk slumring",
|
||||
"step length": "steglengde",
|
||||
"Circle": "Sirkel",
|
||||
"data": "data",
|
||||
"colorize icon": "fargelegg ikon",
|
||||
"min. confidence": "min. tillit",
|
||||
"show widgets": "vis widget",
|
||||
"valid period": "gyldi periode",
|
||||
"Heartrate": "Puls",
|
||||
"distance goal": "mål for distanse",
|
||||
"circle 4": "sirkel 4",
|
||||
"circle count": "antall sirklar",
|
||||
"minimum": "minimum",
|
||||
"maximum": "maksimum",
|
||||
"New Timer": "Ny nedteljing",
|
||||
"battery warn": "batteriåtvaring",
|
||||
"heartrate": "puls",
|
||||
"circle 2": "sirkel 2",
|
||||
"(repeat)": "(gjenta)",
|
||||
"weather circle": "værsirkel",
|
||||
"Delete All Messages": "Slett alle meldingar",
|
||||
"No Messages": "Ingen meldingar",
|
||||
"Show clocks": "Visa klokker",
|
||||
"STEPS": "STEG",
|
||||
"TAP right top/bottom": "TRYKK oppe/nede til høgre",
|
||||
"View Message": "Vis melding",
|
||||
"Mark Unread": "Marker ulest",
|
||||
"Are you sure": "Er du sikker",
|
||||
"Delete all messages": "Slett alle meldingar",
|
||||
"Record Run": "Rekordlaup",
|
||||
"Unread timer": "Ulest nedteljing",
|
||||
"Vibration": "Vibrering",
|
||||
"Utils": "Verkty",
|
||||
"Quiet Mode": "Stille modus",
|
||||
"Passkey BETA": "Passord BETA",
|
||||
"Dark BW": "Mørk BW",
|
||||
"BTNs 1:startlap 2:exit 3:reset": "BTN 1:start 2:avslutt 3:nullstill",
|
||||
"start&lap/reset, BTN1: EXIT": "start&runde/nullstill, BTN1: AVSLUTT",
|
||||
"BLE": "BLE",
|
||||
"Programmable": "Programmerbar",
|
||||
"Launcher Settings": "Innstillingar for oppstartsprogram",
|
||||
"Vector font size": "Storleik for vektorskrifttype",
|
||||
"Font": "Skrifttype",
|
||||
"Yes\ndefinitely": "Ja\ndefinitivt",
|
||||
"App Source\nNot found": "App-kjelde\nikkje funnet",
|
||||
"Make Connectable": "Gjer mogleg å kople til",
|
||||
"HID": "HID",
|
||||
"Bluetooth": "Bluetooth",
|
||||
"Apps": "Appar",
|
||||
"Piezo": "Piezo",
|
||||
"LCD": "LCD",
|
||||
"Foreground 2": "Forgrunn 2",
|
||||
"Light BW": "Lys BW",
|
||||
"Background": "Bakgrunn",
|
||||
"Remove": "Fjern",
|
||||
"Highlight BG": "Marker BG",
|
||||
"Customize": "Tilpass",
|
||||
"Highlight FG": "Marker FG",
|
||||
"Background 2": "Bakgrunn 2",
|
||||
"LCD Brightness": "Lyusstyrke på LCD-skjermen",
|
||||
"Add Device": "Legg til eining",
|
||||
"Wake on BTN1": "Vakne ved KNAPP1",
|
||||
"Wake on BTN2": "Vakne ved KNAPP2",
|
||||
"Twist Timeout": "Tidsavbrot for vridning",
|
||||
"Wake on Touch": "Vakne ved berøring",
|
||||
"LCD Timeout": "LCD tidsavbrot",
|
||||
"Foreground": "Forgrunn",
|
||||
"Connect device\nto add to\nwhitelist": "Kople til eining\nfor å leggje til\ni lista",
|
||||
"Wake on FaceUp": "Vakne på FaceUp",
|
||||
"Twist Threshold": "Terskel for vridning",
|
||||
"Wake on BTN3": "Vakne på BTN3",
|
||||
"Clock Style": "Klokkestil",
|
||||
"Time Zone": "Tidssone",
|
||||
"Twist Max Y": "Vridning Max Y",
|
||||
"Stay Connectable": "Opne for tilkopling",
|
||||
"This will remove everything": "Dette vil fjerne alt",
|
||||
"Turn Off": "Slå av",
|
||||
"Connectable": "Kan koplast til",
|
||||
"Flattening battery - this can take hours.\nLong-press button to cancel": "Flatar ut batteriet, dette kan ta fleire timar.\nHald inne knappen for å avbryte",
|
||||
"Reset to Defaults": "Nullstill",
|
||||
"Utilities": "Verkty",
|
||||
"Flatten Battery": "Flat ut batteriet",
|
||||
"Debug Info": "Feilsøkjingsinfo.",
|
||||
"Reset Settings": "Nullstill innstillingar",
|
||||
"Wake on Twist": "Vakne ved vridning",
|
||||
"Compact Storage": "Trykk saman lagring",
|
||||
"Log": "Logg",
|
||||
"Rewrite Settings": "Omskriving av innstillingar",
|
||||
"Compacting...\nTakes approx\n1 minute": "Trykkar saman lagring...\nTek ca.\n1 minutt",
|
||||
"Storage": "Lagring",
|
||||
"Second": "Sekund",
|
||||
"App Settings": "App-innstillingar",
|
||||
"Invalid settings": "Ugyldige innstillingar",
|
||||
"Minute": "Minutt",
|
||||
"Sleep Phase Alarm": "Søvnfase-alarm",
|
||||
"No app has settings": "Ingen appar har innstillingar",
|
||||
"Hour": "Time",
|
||||
"No Clocks Found": "Fant inga klokke",
|
||||
"Date": "Dato",
|
||||
"Month": "Månad",
|
||||
"Alarm": "Alarm",
|
||||
"Reset": "Nullstill",
|
||||
"Reset all widgets": "Nullstill alle widget",
|
||||
"TIMER": "TIMAR",
|
||||
"on": "på",
|
||||
"OFF": "AV",
|
||||
"Side": "Side",
|
||||
"Sort Order": "Sortering",
|
||||
"Left": "Venstre",
|
||||
"Right": "Høgre",
|
||||
"Reset All": "Nullstill alle",
|
||||
"Widgets": "Widget",
|
||||
"goal": "mål",
|
||||
"Vibrate": "Vibrer",
|
||||
"Message": "Melding",
|
||||
"Beep": "Lag lyd",
|
||||
"Disable": "Slå av",
|
||||
"Select Clock": "Vel klokke",
|
||||
"Locale": "Språk",
|
||||
"Alerts": "Varslingar",
|
||||
"System": "System",
|
||||
"Set Time": "Still tid",
|
||||
"Factory Reset": "Nullstill til fabrikkinnstillingar",
|
||||
"Messages": "Meldingar",
|
||||
"Timer": "Nedteljing",
|
||||
"BACK": "TILLBAKE",
|
||||
"Error in settings": "Feil i innstillingar",
|
||||
"Whitelist": "Tillatelsesliste",
|
||||
"ALARM": "ALARM",
|
||||
"Hide": "Skjul",
|
||||
"Connected": "Kopla til",
|
||||
"Show": "Vis",
|
||||
"On": "På",
|
||||
"Ok": "Ok",
|
||||
"No": "Nei",
|
||||
"Settings": "Innstillingar",
|
||||
"steps": "steg",
|
||||
"back": "tilbake",
|
||||
"Steps": "Steg",
|
||||
"Year": "År",
|
||||
"Yes": "Ja",
|
||||
"Loading": "Lastar",
|
||||
"Music": "Musikk",
|
||||
"color": "farge",
|
||||
"off": "av",
|
||||
"Off": "Av",
|
||||
"Theme": "Drakt",
|
||||
"Select App": "Vel App",
|
||||
"No Apps Found": "Fant inga appar",
|
||||
"Days of Week": "Vekedagar",
|
||||
"Days": "Dagar",
|
||||
"ALTITUDE (m)": "HØGDE (m)",
|
||||
"ZERO": "NULL",
|
||||
"No tokens": "Ingea token",
|
||||
"Not supported": "Ikkje støtta",
|
||||
"weather data": "vêrdata",
|
||||
"Uncalibrated\nturn 360° around": "Ikkje kalibrert\nsnu 360° grader",
|
||||
"RESET": "NULLSTILL",
|
||||
"Mark all read": "Marker alle som lest",
|
||||
"Min Font": "Minste skriftstorleik",
|
||||
"Small": "Liten",
|
||||
"Medium": "Medium",
|
||||
"Auto-Open Music": "Opne musikk automatisk",
|
||||
"Unlock Watch": "Lås opp klokke",
|
||||
"Flash Icon": "Blink Ikon",
|
||||
"Silent": "Stille",
|
||||
"Exit": "Avslutt",
|
||||
"Current Mode": "Gjeldande modus",
|
||||
"Switch Theme": "Byt drakt",
|
||||
"Edit Schedule": "Rediger tidsplan",
|
||||
"Switch to": "Byt til",
|
||||
"No apps": "Ingen appar",
|
||||
"Recorder": "Ta opp",
|
||||
"RECORD": "TA OPP",
|
||||
"File #": "Fil #",
|
||||
"View Tracks": "Sjå spor",
|
||||
"Time Period": "Tidsperiode",
|
||||
"Tracks": "Spor",
|
||||
"No Tracks found": "Fant inga spor",
|
||||
"Erase": "Slett",
|
||||
"Delete Track": "Slett spor",
|
||||
"Drawing": "Teikning",
|
||||
"Altitude (m)": "Høgd (m)",
|
||||
"Speed (m/s)": "Hastigheit (m/s)",
|
||||
"Notifications": "Varsel",
|
||||
"Snooze": "Slumre",
|
||||
"settings": "innstillingar",
|
||||
"Show date": "vis dato",
|
||||
"locale": "språk",
|
||||
"M": "M",
|
||||
"m.Y #W": "m.Y #W",
|
||||
"today": "i dag",
|
||||
"Border": "Omriss",
|
||||
"show": "vis",
|
||||
"Color": "Farge",
|
||||
"Marker": "Marker",
|
||||
"circle": "sirkel",
|
||||
"rectangle": "rektangel",
|
||||
"Connection\nlost": "Mista\ntilkopling",
|
||||
"Calculating": "Reknar ut",
|
||||
"Add Schedule": "Legg til tidsskjema",
|
||||
"LCD Settings": "LCD-innstillingar",
|
||||
"Today settings": "Innstillingar for i dag",
|
||||
"Cancel": "Avbryt",
|
||||
"red": "raud",
|
||||
"green": "grøn",
|
||||
"blue": "blå",
|
||||
"Track": "Spor",
|
||||
"none": "inga",
|
||||
"Plot Map": "Plott Kart",
|
||||
"Plot OpenStMap": "Plott OpenStMap",
|
||||
"Plot Alt": "Plott Høgde",
|
||||
"Plot Speed": "Plott Hastigheit",
|
||||
"Dist Pattern": "Avstandsmønster",
|
||||
"Step Pattern": "Stegmønster",
|
||||
"Time Pattern": "Tidsmønster",
|
||||
"Boxes": "Bokstar",
|
||||
"Start wday": "Start vdag",
|
||||
"Su color": "Su farge",
|
||||
"filled": "fylt",
|
||||
"Mrk.Color": "Mrk.Farge",
|
||||
"Mrk.Size": "Mrk.Storleik"
|
||||
}
|
||||
}
|