Merge branch 'rescalc' of https://github.com/stweedo/BangleApps into rescalc
|
@ -1 +1,3 @@
|
|||
0.01: New App!
|
||||
0.02: Increased Legibility, GUI rework
|
||||
0.03: 13 new chords
|
||||
|
|
|
@ -4,7 +4,8 @@ An app that simply describes finger placements on a Ukulele to form common chord
|
|||
|
||||
## Usage
|
||||
|
||||
Use the button to scroll through the available chords.
|
||||
Select a chord to view.
|
||||
Use the button to return to the chord selection menu.
|
||||
|
||||
## Creator
|
||||
|
||||
|
|
246
apps/Uke/app.js
|
@ -16,52 +16,12 @@ const cc = [
|
|||
|
||||
const dd = [
|
||||
"D",
|
||||
"22",
|
||||
"23",
|
||||
"22",
|
||||
"24",
|
||||
"x"
|
||||
];
|
||||
|
||||
const gg = [
|
||||
"G",
|
||||
"x",
|
||||
"21",
|
||||
"33",
|
||||
"22",
|
||||
];
|
||||
|
||||
const am = [
|
||||
"Am",
|
||||
"22",
|
||||
"x",
|
||||
"x",
|
||||
"x"
|
||||
];
|
||||
|
||||
const em = [
|
||||
"Em",
|
||||
"x",
|
||||
"43",
|
||||
"32",
|
||||
"21"
|
||||
];
|
||||
|
||||
const aa = [
|
||||
"A",
|
||||
"22",
|
||||
"11",
|
||||
"x",
|
||||
"x"
|
||||
];
|
||||
|
||||
const ff = [
|
||||
"F",
|
||||
"22",
|
||||
"x",
|
||||
"11",
|
||||
"x"
|
||||
];
|
||||
|
||||
var ee = [
|
||||
"E",
|
||||
"33",
|
||||
|
@ -70,14 +30,187 @@ var ee = [
|
|||
"11"
|
||||
];
|
||||
|
||||
const ff = [
|
||||
"F",
|
||||
"22",
|
||||
"x",
|
||||
"11",
|
||||
"x"
|
||||
];
|
||||
|
||||
const gg = [
|
||||
"G",
|
||||
"x",
|
||||
"21",
|
||||
"33",
|
||||
"22",
|
||||
];
|
||||
|
||||
const aa = [
|
||||
"A",
|
||||
"22",
|
||||
"11",
|
||||
"x",
|
||||
"x"
|
||||
];
|
||||
|
||||
const bb = [
|
||||
"B",
|
||||
"42",
|
||||
"43",
|
||||
"44",
|
||||
"21"
|
||||
];
|
||||
|
||||
const cm = [
|
||||
"Cm",
|
||||
"11",
|
||||
"x",
|
||||
"12",
|
||||
"34"
|
||||
];
|
||||
|
||||
const dm = [
|
||||
"Dm",
|
||||
"x",
|
||||
"22",
|
||||
"33",
|
||||
"11"
|
||||
];
|
||||
|
||||
const em = [
|
||||
"Em",
|
||||
"x",
|
||||
"43",
|
||||
"32",
|
||||
"21"
|
||||
];
|
||||
|
||||
const fm = [
|
||||
"Fm",
|
||||
"33",
|
||||
"11",
|
||||
"11",
|
||||
"11"
|
||||
];
|
||||
|
||||
const gm = [
|
||||
"Gm",
|
||||
"x",
|
||||
"22",
|
||||
"33",
|
||||
"11"
|
||||
];
|
||||
|
||||
const am = [
|
||||
"Am",
|
||||
"22",
|
||||
"23",
|
||||
"11",
|
||||
"x"
|
||||
];
|
||||
|
||||
const bm = [
|
||||
"Bm",
|
||||
"x",
|
||||
"43",
|
||||
"32",
|
||||
"21"
|
||||
];
|
||||
|
||||
const c7 = [
|
||||
"C7",
|
||||
"22",
|
||||
"33",
|
||||
"11",
|
||||
"x"
|
||||
];
|
||||
|
||||
const d7 = [
|
||||
"D7",
|
||||
"x",
|
||||
"22",
|
||||
"11",
|
||||
"23"
|
||||
];
|
||||
|
||||
const e7 = [
|
||||
"E7",
|
||||
"x",
|
||||
"11",
|
||||
"x",
|
||||
"x"
|
||||
];
|
||||
|
||||
const f7 = [
|
||||
"F7",
|
||||
"11",
|
||||
"22",
|
||||
"11",
|
||||
"11"
|
||||
];
|
||||
|
||||
const g7 = [
|
||||
"G7",
|
||||
"x",
|
||||
"x",
|
||||
"x",
|
||||
"11"
|
||||
];
|
||||
|
||||
const a7 = [
|
||||
"A7",
|
||||
"21",
|
||||
"21",
|
||||
"21",
|
||||
"32"
|
||||
];
|
||||
|
||||
const b7 = [
|
||||
"B7",
|
||||
"11",
|
||||
"22",
|
||||
"x",
|
||||
"23"
|
||||
];
|
||||
|
||||
|
||||
|
||||
var index = 0;
|
||||
var chords = [];
|
||||
|
||||
function init() {
|
||||
g.setFontAlign(0,0); // center font
|
||||
g.setFont("6x8",2); // bitmap font, 8x magnified
|
||||
chords.push(cc, dd, gg, am, em, aa, ff, ee);
|
||||
var menu = {
|
||||
"" : { "title" : "Uke Chords" },
|
||||
"C" : function() { draw(cc); },
|
||||
"D" : function() { draw(dd); },
|
||||
"E" : function() { draw(ee); },
|
||||
"F" : function() { draw(ff); },
|
||||
"G" : function() { draw(gg); },
|
||||
"A" : function() { draw(aa); },
|
||||
"B" : function() { draw(bb); },
|
||||
"C7" : function() { draw(c7); },
|
||||
"D7" : function() { draw(d7); },
|
||||
"E7" : function() { draw(e7); },
|
||||
"F7" : function() { draw(f7); },
|
||||
"G7" : function() { draw(g7); },
|
||||
"A7" : function() { draw(a7); },
|
||||
"B7" : function() { draw(b7); },
|
||||
"Cm" : function() { draw(cm); },
|
||||
"Dm" : function() { draw(dm); },
|
||||
"Em" : function() { draw(em); },
|
||||
"Fm" : function() { draw(fm); },
|
||||
"Gm" : function() { draw(gm); },
|
||||
"Am" : function() { draw(am); },
|
||||
"Bm" : function() { draw(bm); },
|
||||
"About" : function() {
|
||||
E.showMessage(
|
||||
"Created By:\nNovaDawn999", {
|
||||
title:"About"
|
||||
}
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
||||
function drawBase() {
|
||||
for (let i = 0; i < 4; i++) {
|
||||
|
@ -87,18 +220,18 @@ function drawBase() {
|
|||
}
|
||||
|
||||
function drawChord(chord) {
|
||||
g.drawString(chord[0], g.getWidth() * 0.5 + 2, 18);
|
||||
g.drawString(chord[0], g.getWidth() * 0.5 - (chord[0].length * 5), 16);
|
||||
for (let i = 0; i < chord.length; i++) {
|
||||
if (i === 0 || chord[i][0] === "x") {
|
||||
continue;
|
||||
}
|
||||
if (chord[i][0] === "0") {
|
||||
g.drawString(chord[i][1], x + (i - 1) * stringInterval + 1, y + fretHeight * chord[i][0], true);
|
||||
g.drawCircle(x + (i - 1) * stringInterval -1, y + fretHeight * chord[i][0], 8);
|
||||
g.drawString(chord[i][1], x + (i - 1) * stringInterval - 5, y + fretHeight * chord[i][0] + 2, true);
|
||||
g.drawCircle(x + (i - 1) * stringInterval -1, y + fretHeight * chord[i][0], 10);
|
||||
}
|
||||
else {
|
||||
g.drawString(chord[i][1], x + (i - 1) * stringInterval + 1, y -fingerOffset + fretHeight * chord[i][0], true);
|
||||
g.drawCircle(x + (i - 1) * stringInterval -1, y -fingerOffset + fretHeight * chord[i][0], 8);
|
||||
g.drawString(chord[i][1], x + (i - 1) * stringInterval -5, y -fingerOffset + fretHeight * chord[i][0] + 2, true);
|
||||
g.drawCircle(x + (i - 1) * stringInterval -1, y -fingerOffset + fretHeight * chord[i][0], 10);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -107,22 +240,19 @@ function buttonPress() {
|
|||
setWatch(() => {
|
||||
buttonPress();
|
||||
}, BTN);
|
||||
index++;
|
||||
if (index >= chords.length) { index = 0; }
|
||||
draw();
|
||||
E.showMenu(menu);
|
||||
}
|
||||
|
||||
function draw() {
|
||||
function draw(chord) {
|
||||
g.clear();
|
||||
drawBase();
|
||||
drawChord(chords[index]);
|
||||
drawChord(chord);
|
||||
}
|
||||
|
||||
|
||||
|
||||
function main() {
|
||||
init();
|
||||
draw();
|
||||
E.showMenu(menu);
|
||||
setWatch(() => {
|
||||
buttonPress();
|
||||
}, BTN);
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{ "id": "Uke",
|
||||
"name": "Uke Chords",
|
||||
"shortName":"Uke",
|
||||
"version":"0.01",
|
||||
"version":"0.03",
|
||||
"description": "Wrist mounted ukulele chords",
|
||||
"icon": "app.png",
|
||||
"tags": "uke, chords",
|
||||
|
|
|
@ -2,3 +2,4 @@
|
|||
0.02: Barometer altitude adjustment setting
|
||||
0.03: Use default Bangle formatter for booleans
|
||||
0.04: Add options for units in locale and recording GPS
|
||||
0.05: Allow toggling of "max" values (screen tap) and recording (button press)
|
||||
|
|
|
@ -12,7 +12,7 @@ const fontFactorB2 = 2/3;
|
|||
const colfg=g.theme.fg, colbg=g.theme.bg;
|
||||
const col1=colfg, colUncertain="#88f"; // if (lf.fix) g.setColor(col1); else g.setColor(colUncertain);
|
||||
|
||||
var altiGPS=0, altiBaro=0;
|
||||
var altiBaro=0;
|
||||
var hdngGPS=0, hdngCompass=0, calibrateCompass=false;
|
||||
|
||||
/*kalmanjs, Wouter Bulten, MIT, https://github.com/wouterbulten/kalmanjs */
|
||||
|
@ -183,7 +183,6 @@ var KalmanFilter = (function () {
|
|||
|
||||
var lf = {fix:0,satellites:0};
|
||||
var showMax = 0; // 1 = display the max values. 0 = display the cur fix
|
||||
var canDraw = 1;
|
||||
var time = ''; // Last time string displayed. Re displayed in background colour to remove before drawing new time.
|
||||
var sec; // actual seconds for testing purposes
|
||||
|
||||
|
@ -194,30 +193,9 @@ max.n = 0; // counter. Only start comparing for max after a certain number of
|
|||
|
||||
var emulator = (process.env.BOARD=="EMSCRIPTEN" || process.env.BOARD=="EMSCRIPTEN2")?1:0; // 1 = running in emulator. Supplies test values;
|
||||
|
||||
var wp = {}; // Waypoint to use for distance from cur position.
|
||||
var SATinView = 0;
|
||||
|
||||
function radians(a) {
|
||||
return a*Math.PI/180;
|
||||
}
|
||||
|
||||
function distance(a,b){
|
||||
var x = radians(a.lon-b.lon) * Math.cos(radians((a.lat+b.lat)/2));
|
||||
var y = radians(b.lat-a.lat);
|
||||
|
||||
// Distance in selected units
|
||||
var d = Math.sqrt(x*x + y*y) * 6371000;
|
||||
d = (d/parseFloat(cfg.dist)).toFixed(2);
|
||||
if ( d >= 100 ) d = parseFloat(d).toFixed(1);
|
||||
if ( d >= 1000 ) d = parseFloat(d).toFixed(0);
|
||||
|
||||
return d;
|
||||
}
|
||||
|
||||
function drawFix(dat) {
|
||||
|
||||
if (!canDraw) return;
|
||||
|
||||
g.clearRect(0,screenYstart,screenW,screenH);
|
||||
|
||||
var v = '';
|
||||
|
@ -227,7 +205,7 @@ function drawFix(dat) {
|
|||
v = (cfg.primSpd)?dat.speed.toString():dat.alt.toString();
|
||||
|
||||
// Primary Units
|
||||
u = (cfg.primSpd)?cfg.spd_unit:dat.alt_units;
|
||||
u = (showMax ? 'max ' : '') + (cfg.primSpd?cfg.spd_unit:dat.alt_units);
|
||||
|
||||
drawPrimary(v,u);
|
||||
|
||||
|
@ -260,14 +238,6 @@ function drawFix(dat) {
|
|||
}
|
||||
|
||||
|
||||
function drawClock() {
|
||||
if (!canDraw) return;
|
||||
g.clearRect(0,screenYstart,screenW,screenH);
|
||||
drawTime();
|
||||
g.reset();
|
||||
}
|
||||
|
||||
|
||||
function drawPrimary(n,u) {
|
||||
//if(emulator)console.log("\n1: " + n +" "+ u);
|
||||
var s=40; // Font size
|
||||
|
@ -337,16 +307,6 @@ function drawSats(sats) {
|
|||
g.setFont("6x8", 2);
|
||||
g.setFontAlign(1,1); //right, bottom
|
||||
g.drawString(sats,screenW,screenH);
|
||||
|
||||
g.setFontVector(18);
|
||||
g.setColor(col1);
|
||||
|
||||
if ( cfg.modeA == 1 ) {
|
||||
if ( showMax ) {
|
||||
g.setFontAlign(0,1); //centre, bottom
|
||||
g.drawString('MAX',120,164);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function onGPS(fix) {
|
||||
|
@ -367,7 +327,6 @@ function onGPS(fix) {
|
|||
|
||||
var sp = '---';
|
||||
var al = '---';
|
||||
var di = '---';
|
||||
var age = '---';
|
||||
|
||||
if (fix.fix) lf = fix;
|
||||
|
@ -412,10 +371,6 @@ function onGPS(fix) {
|
|||
al = Math.round(parseFloat(al)/parseFloat(cfg.alt));
|
||||
if (parseFloat(al) > parseFloat(max.alt) && max.n > 15 ) max.alt = parseFloat(al);
|
||||
|
||||
// Distance to waypoint
|
||||
di = distance(lf,wp);
|
||||
if (isNaN(di)) di = 0;
|
||||
|
||||
// Age of last fix (secs)
|
||||
age = Math.max(0,Math.round(getTime())-(lf.time.getTime()/1000));
|
||||
} else {
|
||||
|
@ -448,15 +403,7 @@ function onGPS(fix) {
|
|||
}
|
||||
}
|
||||
|
||||
function setButtons(){
|
||||
setWatch(_=>load(), BTN1);
|
||||
|
||||
onGPS(lf);
|
||||
}
|
||||
|
||||
|
||||
function updateClock() {
|
||||
if (!canDraw) return;
|
||||
drawTime();
|
||||
g.reset();
|
||||
|
||||
|
@ -545,6 +492,10 @@ function Compass_reading() {
|
|||
hdngCompass = Compass_heading.toFixed(0);
|
||||
}
|
||||
|
||||
function nextMode() {
|
||||
showMax = 1 - showMax;
|
||||
}
|
||||
|
||||
function start() {
|
||||
Bangle.setBarometerPower(1); // needs some time...
|
||||
g.clearRect(0,screenYstart,screenW,screenH);
|
||||
|
@ -556,10 +507,30 @@ function start() {
|
|||
Bangle.setCompassPower(1);
|
||||
if (!calibrateCompass) setInterval(Compass_reading,200);
|
||||
|
||||
setButtons();
|
||||
if (emulator) setInterval(updateClock, 2000);
|
||||
else setInterval(updateClock, 10000);
|
||||
|
||||
let createdRecording = false;
|
||||
Bangle.setUI({
|
||||
mode: "custom",
|
||||
touch: nextMode,
|
||||
btn: () => {
|
||||
const rec = WIDGETS["recorder"];
|
||||
if(rec){
|
||||
const active = rec.isRecording();
|
||||
if(active){
|
||||
createdRecording = true;
|
||||
rec.setRecording(false);
|
||||
}else{
|
||||
rec.setRecording(true, { force: createdRecording ? "append" : "new" });
|
||||
}
|
||||
}else{
|
||||
nextMode();
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
// can't delay loadWidgets til here - need to have already done so for recorder
|
||||
Bangle.drawWidgets();
|
||||
}
|
||||
|
||||
|
@ -571,6 +542,7 @@ if (cfg.record && WIDGETS["recorder"]) {
|
|||
|
||||
if (cfg.recordStopOnExit)
|
||||
E.on('kill', () => WIDGETS["recorder"].setRecording(false));
|
||||
|
||||
} else {
|
||||
start();
|
||||
}
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
"id": "bikespeedo",
|
||||
"name": "Bike Speedometer (beta)",
|
||||
"shortName": "Bike Speedometer",
|
||||
"version": "0.04",
|
||||
"version": "0.05",
|
||||
"description": "Shows GPS speed, GPS heading, Compass heading, GPS altitude and Barometer altitude from internal sources",
|
||||
"icon": "app.png",
|
||||
"screenshots": [{"url":"Screenshot.png"}],
|
||||
|
|
|
@ -66,3 +66,4 @@
|
|||
If settings.bootDebug is set, output timing for each section of .boot0
|
||||
0.56: Settings.log = 0,1,2,3 for off,display, log, both
|
||||
0.57: Handle the whitelist being disabled
|
||||
0.58: "Make Connectable" temporarily bypasses the whitelist
|
||||
|
|
|
@ -79,7 +79,7 @@ if (global.save) boot += `global.save = function() { throw new Error("You can't
|
|||
if (s.options) boot+=`Bangle.setOptions(${E.toJS(s.options)});\n`;
|
||||
if (s.brightness && s.brightness!=1) boot+=`Bangle.setLCDBrightness(${s.brightness});\n`;
|
||||
if (s.passkey!==undefined && s.passkey.length==6) boot+=`NRF.setSecurity({passkey:${E.toJS(s.passkey.toString())}, mitm:1, display:1});\n`;
|
||||
if (s.whitelist && !s.whitelist_disabled) boot+=`NRF.on('connect', function(addr) { if (!(require('Storage').readJSON('setting.json',1)||{}).whitelist.includes(addr)) NRF.disconnect(); });\n`;
|
||||
if (s.whitelist && !s.whitelist_disabled) boot+=`NRF.on('connect', function(addr) { if (!NRF.ignoreWhitelist && !(require('Storage').readJSON('setting.json',1)||{}).whitelist.includes(addr)) NRF.disconnect(); });\n`;
|
||||
if (s.rotate) boot+=`g.setRotation(${s.rotate&3},${s.rotate>>2});\n` // screen rotation
|
||||
// ================================================== FIXING OLDER FIRMWARES
|
||||
if (FWVERSION<215.068) // 2v15.68 and before had compass heading inverted.
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"id": "boot",
|
||||
"name": "Bootloader",
|
||||
"version": "0.57",
|
||||
"version": "0.58",
|
||||
"description": "This is needed by Bangle.js to automatically load the clock, menu, widgets and settings",
|
||||
"icon": "bootloader.png",
|
||||
"type": "bootloader",
|
||||
|
|
|
@ -0,0 +1,2 @@
|
|||
0.01: Initial release.
|
||||
0.02: Added compatibility to OpenTracks and added HRM Location
|
|
@ -0,0 +1,16 @@
|
|||
# BLE GATT HRM Service
|
||||
|
||||
Adds the GATT HRM Service to advertise the current HRM over Bluetooth.
|
||||
|
||||
## Usage
|
||||
|
||||
This boot code runs in the background and has no user interface.
|
||||
|
||||
## Creator
|
||||
|
||||
[Another Stranger](https://github.com/anotherstranger)
|
||||
|
||||
## Aknowledgements
|
||||
|
||||
Special thanks to [Jonathan Jefferies](https://github.com/jjok) for creating the
|
||||
bootgattbat app, which was the inspiration for this App!
|
After Width: | Height: | Size: 1.1 KiB |
|
@ -0,0 +1,70 @@
|
|||
(() => {
|
||||
function setupHRMAdvertising() {
|
||||
/*
|
||||
* This function prepares BLE heart rate Advertisement.
|
||||
*/
|
||||
|
||||
NRF.setAdvertising(
|
||||
{
|
||||
0x180d: undefined
|
||||
},
|
||||
{
|
||||
// We need custom Advertisement settings for Apps like OpenTracks
|
||||
connectable: true,
|
||||
discoverable: true,
|
||||
scannable: true,
|
||||
whenConnected: true,
|
||||
}
|
||||
);
|
||||
|
||||
NRF.setServices({
|
||||
0x180D: { // heart_rate
|
||||
0x2A37: { // heart_rate_measurement
|
||||
notify: true,
|
||||
value: [0x06, 0],
|
||||
},
|
||||
0x2A38: { // Sensor Location: Wrist
|
||||
value: 0x02,
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
function updateBLEHeartRate(hrm) {
|
||||
/*
|
||||
* Send updated heart rate measurement via BLE
|
||||
*/
|
||||
if (hrm === undefined || hrm.confidence < 50) return;
|
||||
try {
|
||||
NRF.updateServices({
|
||||
0x180D: {
|
||||
0x2A37: {
|
||||
value: [0x06, hrm.bpm],
|
||||
notify: true
|
||||
},
|
||||
0x2A38: {
|
||||
value: 0x02,
|
||||
}
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
if (error.message.includes("BLE restart")) {
|
||||
/*
|
||||
* BLE has to restart after service setup.
|
||||
*/
|
||||
NRF.disconnect();
|
||||
}
|
||||
else if (error.message.includes("UUID 0x2a37")) {
|
||||
/*
|
||||
* Setup service if it wasn't setup correctly for some reason
|
||||
*/
|
||||
setupHRMAdvertising();
|
||||
} else {
|
||||
console.log("[bootgatthrm]: Unexpected error occured while updating HRM over BLE! Error: " + error.message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
setupHRMAdvertising();
|
||||
Bangle.on("HRM", function (hrm) { updateBLEHeartRate(hrm); });
|
||||
})();
|
|
@ -0,0 +1,15 @@
|
|||
{
|
||||
"id": "bootgatthrm",
|
||||
"name": "BLE GATT HRM Service",
|
||||
"shortName": "BLE HRM Service",
|
||||
"version": "0.02",
|
||||
"description": "Adds the GATT HRM Service to advertise the measured HRM over Bluetooth.\n",
|
||||
"icon": "bluetooth.png",
|
||||
"type": "bootloader",
|
||||
"tags": "hrm,health,ble,bluetooth,gatt",
|
||||
"supports": ["BANGLEJS","BANGLEJS2"],
|
||||
"readme": "README.md",
|
||||
"storage": [
|
||||
{"name":"gatthrm.boot.js","url":"boot.js"}
|
||||
]
|
||||
}
|
|
@ -1,2 +1,3 @@
|
|||
0.01: Initial Creation
|
||||
0.02: Fixed some sleep bugs. Added a sleep mode toggle
|
||||
0.03: Reduce busy-loop and code
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
{
|
||||
"id": "chimer",
|
||||
"name": "Chimer",
|
||||
"version": "0.02",
|
||||
"description": "A fork of Hour Chime that adds extra features such as: \n - Buzz or beep on every 60, 30 or 15 minutes. \n - Reapeat Chime up to 3 times \n - Set hours to disable chime",
|
||||
"version": "0.03",
|
||||
"description": "A fork of Hour Chime that adds extra features such as: \n - Buzz or beep on every 60, 30 or 15 minutes. \n - Repeat Chime up to 3 times \n - Set hours to disable chime",
|
||||
"icon": "widget.png",
|
||||
"type": "widget",
|
||||
"tags": "widget",
|
||||
|
|
|
@ -16,16 +16,10 @@
|
|||
|
||||
var settings = readSettings();
|
||||
|
||||
function sleep(milliseconds) {
|
||||
const date = Date.now();
|
||||
let currentDate = null;
|
||||
do {
|
||||
currentDate = Date.now();
|
||||
} while (currentDate - date < milliseconds);
|
||||
}
|
||||
|
||||
function chime() {
|
||||
for (var i = 0; i < settings.repeat; i++) {
|
||||
let count = settings.repeat;
|
||||
|
||||
const chime1 = () => {
|
||||
if (settings.type === 1) {
|
||||
Bangle.buzz(100);
|
||||
} else if (settings.type === 2) {
|
||||
|
@ -33,8 +27,24 @@
|
|||
} else {
|
||||
return;
|
||||
}
|
||||
sleep(150);
|
||||
if (--count > 0)
|
||||
setTimeout(chime1, 150);
|
||||
};
|
||||
|
||||
chime1();
|
||||
}
|
||||
|
||||
function queueNextCheckMins(mins) {
|
||||
const now = new Date(),
|
||||
m = now.getMinutes(),
|
||||
s = now.getSeconds(),
|
||||
ms = now.getMilliseconds();
|
||||
|
||||
const mLeft = mins - (m + mins * 2) % mins,
|
||||
sLeft = mLeft * 60 - s,
|
||||
msLeft = sLeft * 1000 - ms;
|
||||
|
||||
setTimeout(check, msLeft);
|
||||
}
|
||||
|
||||
let lastHour = new Date().getHours();
|
||||
|
@ -45,88 +55,41 @@
|
|||
m = now.getMinutes(),
|
||||
s = now.getSeconds(),
|
||||
ms = now.getMilliseconds();
|
||||
if (
|
||||
(settings.sleep && h > settings.end) ||
|
||||
(settings.sleep && h >= settings.end && m !== 0) ||
|
||||
(settings.sleep && h < settings.start)
|
||||
if (settings.sleep && (
|
||||
h > settings.end ||
|
||||
(h >= settings.end && m !== 0) ||
|
||||
h < settings.start)
|
||||
) {
|
||||
var mLeft = 60 - m,
|
||||
sLeft = mLeft * 60 - s,
|
||||
msLeft = sLeft * 1000 - ms;
|
||||
setTimeout(check, msLeft);
|
||||
queueNextCheckMins(60);
|
||||
return;
|
||||
}
|
||||
if (settings.freq === 1) {
|
||||
if ((m !== lastMinute && m === 0) || (m !== lastMinute && m === 30))
|
||||
switch (settings.freq) {
|
||||
case 1:
|
||||
if (m !== lastMinute && m % 30 === 0)
|
||||
chime();
|
||||
lastHour = h;
|
||||
lastMinute = m;
|
||||
// check again in 30 minutes
|
||||
switch (true) {
|
||||
case m / 30 >= 1:
|
||||
var mLeft = 30 - (m - 30),
|
||||
sLeft = mLeft * 60 - s,
|
||||
msLeft = sLeft * 1000 - ms;
|
||||
queueNextCheckMins(30);
|
||||
break;
|
||||
case m / 30 < 1:
|
||||
var mLeft = 30 - m,
|
||||
sLeft = mLeft * 60 - s,
|
||||
msLeft = sLeft * 1000 - ms;
|
||||
break;
|
||||
}
|
||||
setTimeout(check, msLeft);
|
||||
} else if (settings.freq === 2) {
|
||||
if (
|
||||
(m !== lastMinute && m === 0) ||
|
||||
(m !== lastMinute && m === 15) ||
|
||||
(m !== lastMinute && m === 30) ||
|
||||
(m !== lastMinute && m === 45)
|
||||
)
|
||||
case 2:
|
||||
if (m !== lastMinute && m % 15 === 0)
|
||||
chime();
|
||||
lastHour = h;
|
||||
lastMinute = m;
|
||||
// check again in 15 minutes
|
||||
switch (true) {
|
||||
case m / 15 >= 3:
|
||||
var mLeft = 15 - (m - 45),
|
||||
sLeft = mLeft * 60 - s,
|
||||
msLeft = sLeft * 1000 - ms;
|
||||
queueNextCheckMins(15);
|
||||
break;
|
||||
case m / 15 >= 2:
|
||||
var mLeft = 15 - (m - 30),
|
||||
sLeft = mLeft * 60 - s,
|
||||
msLeft = sLeft * 1000 - ms;
|
||||
break;
|
||||
case m / 15 >= 1:
|
||||
var mLeft = 15 - (m - 15),
|
||||
sLeft = mLeft * 60 - s,
|
||||
msLeft = sLeft * 1000 - ms;
|
||||
break;
|
||||
case m / 15 < 1:
|
||||
var mLeft = 15 - m,
|
||||
sLeft = mLeft * 60 - s,
|
||||
msLeft = sLeft * 1000 - ms;
|
||||
break;
|
||||
}
|
||||
setTimeout(check, msLeft);
|
||||
} else if (settings.freq === 3) {
|
||||
case 3:
|
||||
// unreachable - not available in settings
|
||||
if (m !== lastMinute) chime();
|
||||
lastHour = h;
|
||||
lastMinute = m;
|
||||
// check again in 1 minute
|
||||
|
||||
var mLeft = 1,
|
||||
sLeft = mLeft * 60 - s,
|
||||
msLeft = sLeft * 1000 - ms;
|
||||
setTimeout(check, msLeft);
|
||||
} else {
|
||||
queueNextCheckMins(1);
|
||||
break;
|
||||
default:
|
||||
if (h !== lastHour && m === 0) chime();
|
||||
lastHour = h;
|
||||
// check again in 60 minutes
|
||||
var mLeft = 60 - m,
|
||||
sLeft = mLeft * 60 - s,
|
||||
msLeft = sLeft * 1000 - ms;
|
||||
setTimeout(check, msLeft);
|
||||
queueNextCheckMins(60);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,2 @@
|
|||
0.01: New App!
|
||||
0.02: More Chords, formatting, fret offset support.
|
|
@ -0,0 +1,12 @@
|
|||
# Guitar Chords
|
||||
|
||||
An app that simply describes finger placements on a Guitar to form common chords.
|
||||
|
||||
## Usage
|
||||
|
||||
Select a chord to view.
|
||||
Use the button to return to the chord selection menu.
|
||||
|
||||
## Creator
|
||||
|
||||
NovaDawn999
|
|
@ -0,0 +1 @@
|
|||
require("heatshrink").decompress(atob("mEwwkCkQA/AGMkoQXVptEFytEogwUCoIYBLqlUGIIXTopHVknUoXULylNpouUIoKmUUi0hoMUailEiMSCR/d7pdECx8tC4IYBolULqAWC7qLSFwfdiKMRC4dEoK6RFwYWBppdW7vSLiPd6gXPConVgIWCYYYtM9vdosUqgXOFwndilBqoGDLh/eqtEioXR9xHCoIXDO5SKEpvU6kVppeQ73kqgwB7wuNOwosEXqSlB9xFNR49RpwXV6pICIxhIF73ePAIXTAAgXOJApePGBQXPGA4XPGAxeOGBAWQDAouRDAgWUAH4AZ"))
|
|
@ -0,0 +1,340 @@
|
|||
const stringInterval = 24;
|
||||
const stringLength = 138;
|
||||
const fretHeight = 35;
|
||||
const fingerOffset = 17;
|
||||
const xOffset = 26;
|
||||
const yOffset = 34;
|
||||
|
||||
const cc = [
|
||||
"C",
|
||||
"0X",
|
||||
"33",
|
||||
"22",
|
||||
"x",
|
||||
"11",
|
||||
"x",
|
||||
"0"
|
||||
];
|
||||
|
||||
const dd = [
|
||||
"D",
|
||||
"0X",
|
||||
"0X",
|
||||
"x",
|
||||
"21",
|
||||
"33",
|
||||
"22",
|
||||
"0"
|
||||
];
|
||||
|
||||
const gg = [
|
||||
"G",
|
||||
"32",
|
||||
"21",
|
||||
"x",
|
||||
"x",
|
||||
"x",
|
||||
"33",
|
||||
"0"
|
||||
];
|
||||
|
||||
const am = [
|
||||
"Am",
|
||||
"0x",
|
||||
"x",
|
||||
"23",
|
||||
"22",
|
||||
"11",
|
||||
"x",
|
||||
"0"
|
||||
];
|
||||
|
||||
const em = [
|
||||
"Em",
|
||||
"x",
|
||||
"22",
|
||||
"23",
|
||||
"x",
|
||||
"x",
|
||||
"x",
|
||||
"0"
|
||||
];
|
||||
|
||||
const aa = [
|
||||
"A",
|
||||
"0X",
|
||||
"x",
|
||||
"21",
|
||||
"22",
|
||||
"23",
|
||||
"x",
|
||||
"0"
|
||||
];
|
||||
|
||||
var ee = [
|
||||
"E",
|
||||
"x",
|
||||
"22",
|
||||
"23",
|
||||
"11",
|
||||
"x",
|
||||
"x",
|
||||
"0"
|
||||
];
|
||||
|
||||
var dm = [
|
||||
"Dm",
|
||||
"0x",
|
||||
"0x",
|
||||
"x",
|
||||
"22",
|
||||
"33",
|
||||
"11",
|
||||
"0"
|
||||
];
|
||||
|
||||
var ff = [
|
||||
"F",
|
||||
"0x",
|
||||
"0x",
|
||||
"33",
|
||||
"22",
|
||||
"11",
|
||||
"11",
|
||||
"0"
|
||||
];
|
||||
|
||||
var b7 = [
|
||||
"B7",
|
||||
"0x",
|
||||
"22",
|
||||
"11",
|
||||
"23",
|
||||
"x",
|
||||
"24",
|
||||
"0"
|
||||
];
|
||||
|
||||
var cadd9 = [
|
||||
"Cadd9",
|
||||
"0x",
|
||||
"32",
|
||||
"21",
|
||||
"x",
|
||||
"33",
|
||||
"34",
|
||||
"0"
|
||||
];
|
||||
|
||||
var dadd11 = [
|
||||
"Dadd11",
|
||||
"0x",
|
||||
"33",
|
||||
"22",
|
||||
"x",
|
||||
"11",
|
||||
"x",
|
||||
"3"
|
||||
];
|
||||
|
||||
var csus2 = [
|
||||
"Csus2",
|
||||
"0x",
|
||||
"33",
|
||||
"x",
|
||||
"x",
|
||||
"11",
|
||||
"0x",
|
||||
"0"
|
||||
];
|
||||
|
||||
var gadd9 = [
|
||||
"Gadd9",
|
||||
"32",
|
||||
"0x",
|
||||
"x",
|
||||
"21",
|
||||
"x",
|
||||
"33",
|
||||
"0"
|
||||
];
|
||||
|
||||
var aadd9 = [
|
||||
"Aadd9",
|
||||
"11",
|
||||
"33",
|
||||
"34",
|
||||
"22",
|
||||
"x",
|
||||
"x",
|
||||
"5"
|
||||
];
|
||||
|
||||
var fsharp7add11 = [
|
||||
"F#7add11",
|
||||
"21",
|
||||
"43",
|
||||
"44",
|
||||
"32",
|
||||
"x",
|
||||
"x",
|
||||
"0"
|
||||
];
|
||||
|
||||
var d9 = [
|
||||
"D9",
|
||||
"0x",
|
||||
"22",
|
||||
"11",
|
||||
"23",
|
||||
"23",
|
||||
"0x",
|
||||
"4"
|
||||
];
|
||||
|
||||
var g7 = [
|
||||
"G7",
|
||||
"33",
|
||||
"22",
|
||||
"x",
|
||||
"x",
|
||||
"34",
|
||||
"11",
|
||||
"0"
|
||||
];
|
||||
|
||||
var bflatd = [
|
||||
"Bb/D",
|
||||
"0x",
|
||||
"33",
|
||||
"11",
|
||||
"11",
|
||||
"11",
|
||||
"0x",
|
||||
"3"
|
||||
];
|
||||
|
||||
var e7sharp9 = [
|
||||
"E7#9",
|
||||
"0x",
|
||||
"22",
|
||||
"11",
|
||||
"23",
|
||||
"34",
|
||||
"0x",
|
||||
"6"
|
||||
];
|
||||
|
||||
var a11 = [
|
||||
"A11 3rd fret",
|
||||
"33",
|
||||
"0x",
|
||||
"34",
|
||||
"22",
|
||||
"11",
|
||||
"0x",
|
||||
"0"
|
||||
];
|
||||
|
||||
var a9 = [
|
||||
"A9",
|
||||
"32",
|
||||
"0x",
|
||||
"33",
|
||||
"21",
|
||||
"34",
|
||||
"0x",
|
||||
"3"
|
||||
];
|
||||
|
||||
|
||||
|
||||
var index = 0;
|
||||
var chords = [];
|
||||
var menu = {
|
||||
"" : {
|
||||
"title" : "Guitar Chords"
|
||||
},
|
||||
"C" : function() { draw(cc); },
|
||||
"D" : function() { draw(dd); },
|
||||
"E" : function() { draw(ee); },
|
||||
"Em" : function() { draw(em); },
|
||||
"A" : function() { draw(aa); },
|
||||
"Am" : function() { draw(am); },
|
||||
"F" : function() { draw(ff); },
|
||||
"G" : function() { draw(gg); },
|
||||
"Dm" : function() { draw(dm); },
|
||||
"B7" : function () { draw(b7); },
|
||||
"Cadd9" : function () { draw(cadd9); },
|
||||
"Dadd11" : function () { draw(dadd11); },
|
||||
"Csus2" : function () { draw(csus2); },
|
||||
"Gadd9" : function () { draw(gadd9); },
|
||||
"Aadd9" : function () { draw(aadd9); },
|
||||
"F#7add11" : function () { draw(fsharp7add11); },
|
||||
"D9" : function () { draw(d9); },
|
||||
"G7" : function () { draw(g7); },
|
||||
"Bb/D" : function () { draw(bflatd); },
|
||||
"E7#9" : function () { draw(e7sharp9); },
|
||||
"A11" : function () { draw(a11); },
|
||||
"A9" : function () { draw(a9); },
|
||||
"About" : function() {
|
||||
E.showMessage(
|
||||
"Created By:\nNovaDawn999", {
|
||||
title:"About"
|
||||
}
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
||||
function drawBase() {
|
||||
for (let i = 0; i < 6; i++) {
|
||||
g.drawLine(xOffset + i * stringInterval, yOffset, xOffset + i * stringInterval, yOffset + stringLength);
|
||||
g.fillRect(xOffset- 1, yOffset + i * fretHeight - 1, xOffset + stringInterval * 5 + 1, yOffset + i * fretHeight + 1);
|
||||
}
|
||||
}
|
||||
|
||||
function drawChord(chord) {
|
||||
g.drawString(chord[0], g.getWidth() * 0.5 - (chord[0].length * 5), 16);
|
||||
for (let i = 0; i < chord.length - 1; i++) {
|
||||
if (i === 0 || chord[i][0] === "x") {
|
||||
continue;
|
||||
}
|
||||
if (chord[i][0] === "0") {
|
||||
g.drawString(chord[i][1], xOffset + (i - 1) * stringInterval - 5, yOffset + fretHeight * chord[i][0] + 2, true);
|
||||
g.drawCircle(xOffset + (i - 1) * stringInterval -1, yOffset + fretHeight * chord[i][0], 10);
|
||||
}
|
||||
else {
|
||||
g.drawString(chord[i][1], xOffset + (i - 1) * stringInterval -5, yOffset -fingerOffset + fretHeight * chord[i][0] + 2, true);
|
||||
g.drawCircle(xOffset + (i - 1) * stringInterval -1, yOffset -fingerOffset + fretHeight * chord[i][0], 10);
|
||||
}
|
||||
}
|
||||
if (chord[7] !== "0") {
|
||||
g.drawString(chord[7], 9, 50);
|
||||
}
|
||||
}
|
||||
|
||||
function buttonPress() {
|
||||
setWatch(() => {
|
||||
buttonPress();
|
||||
}, BTN);
|
||||
E.showMenu(menu);
|
||||
}
|
||||
|
||||
function draw(chord) {
|
||||
g.clear();
|
||||
drawBase();
|
||||
drawChord(chord);
|
||||
}
|
||||
|
||||
|
||||
|
||||
function main() {
|
||||
E.showMenu(menu);
|
||||
setWatch(() => {
|
||||
buttonPress();
|
||||
}, BTN);
|
||||
}
|
||||
|
||||
main();
|
After Width: | Height: | Size: 1.8 KiB |
|
@ -0,0 +1,14 @@
|
|||
{ "id": "guitar",
|
||||
"name": "Guitar Chords",
|
||||
"shortName":"Guitar",
|
||||
"version":"0.02",
|
||||
"description": "Wrist mounted guitar chords",
|
||||
"icon": "app.png",
|
||||
"tags": "guitar, chords",
|
||||
"supports" : ["BANGLEJS2"],
|
||||
"readme": "README.md",
|
||||
"storage": [
|
||||
{"name":"guitar.app.js","url":"app.js"},
|
||||
{"name":"guitar.img","url":"app-icon.js","evaluate":true}
|
||||
]
|
||||
}
|
|
@ -6,3 +6,4 @@
|
|||
0.06: Fixing refresh issues
|
||||
0.07: Fixed position after unlocking
|
||||
0.08: Handling exceptions
|
||||
0.09: Add option for showing battery high mark
|
||||
|
|
|
@ -8,6 +8,8 @@ Show the current battery level and charging status in the top right of the clock
|
|||
* Blue when charging
|
||||
* 40 pixels wide
|
||||
|
||||
The high-level marker (a little bar at the 100% point) can be toggled in settings.
|
||||
|
||||

|
||||
|
||||
## Creator
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
"name": "A Battery Widget (with percentage) - Hanks Mod",
|
||||
"shortName":"H Battery Widget",
|
||||
"icon": "widget.png",
|
||||
"version":"0.08",
|
||||
"version":"0.09",
|
||||
"type": "widget",
|
||||
"supports": ["BANGLEJS", "BANGLEJS2"],
|
||||
"readme": "README.md",
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
(function(){
|
||||
const intervalLow = 60000; // update time when not charging
|
||||
const intervalHigh = 2000; // update time when charging
|
||||
var old_l;
|
||||
|
||||
var old_x = this.x;
|
||||
var old_y = this.y;
|
||||
|
@ -22,33 +21,18 @@
|
|||
};
|
||||
|
||||
function draw() {
|
||||
if (typeof old_x === 'undefined') old_x = this.x;
|
||||
if (typeof old_y === 'undefined') old_y = this.y;
|
||||
var s = 29;
|
||||
var s = width - 1;
|
||||
var x = this.x;
|
||||
var y = this.y;
|
||||
if ((typeof x === 'undefined') || (typeof y === 'undefined')) {
|
||||
} else {
|
||||
g.clearRect(old_x, old_y, old_x + width, old_y + height);
|
||||
|
||||
const l = E.getBattery(); // debug: Math.floor(Math.random() * 101);
|
||||
let xl = x+4+l*(s-12)/100;
|
||||
if ((l != old_l) && (typeof old_l != 'undefined') ){ // Delete the old value from screen
|
||||
let xl_old = x+4+old_l*(s-12)/100;
|
||||
g.setColor(COLORS.white);
|
||||
// g.fillRect(x+2,y+5,x+s-6,y+18);
|
||||
g.fillRect(x,y,xl+4,y+16+3); //Clear
|
||||
g.setFontAlign(0,0);
|
||||
g.setFont('Vector',16);
|
||||
//g.fillRect(old_x,old_y,old_x+4+l*(s-12)/100,old_y+16+3); // clear (lazy)
|
||||
g.drawString(old_l, old_x + 14, old_y + 10);
|
||||
g.fillRect(x+4,y+14+3,xl_old,y+16+3); // charging bar
|
||||
|
||||
}
|
||||
old_l = l;
|
||||
//console.log(old_x);
|
||||
|
||||
g.setColor(levelColor(l));
|
||||
g.fillRect(x+4,y+14+3,xl,y+16+3); // charging bar
|
||||
g.fillRect((x+4+100*(s-12)/100)-1,y+14+3,x+4+100*(s-12)/100,y+16+3); // charging bar "full mark"
|
||||
// Show percentage
|
||||
g.setColor(COLORS.black);
|
||||
g.setFontAlign(0,0);
|
||||
|
@ -65,6 +49,8 @@
|
|||
|
||||
Bangle.on('charging',function(charging) { draw(); });
|
||||
var id = setInterval(()=>WIDGETS["hwid_a_battery_widget"].draw(), intervalLow);
|
||||
var width = 30;
|
||||
var height = 19;
|
||||
|
||||
WIDGETS["hwid_a_battery_widget"]={area:"tr",width:30,draw:draw};
|
||||
WIDGETS["hwid_a_battery_widget"]={area:"tr",width,draw:draw};
|
||||
})();
|
||||
|
|
|
@ -5,3 +5,7 @@
|
|||
0.05: Tell clock widgets to hide
|
||||
0.06: Fix exception when showing missing hiragana 'WO'
|
||||
0.07: Fix regression in bitmap selection on some code paths
|
||||
0.08: Speedup next/prev and fix autogenerated hiragana bitmaps
|
||||
0.09: Optimize loading and rendering times, introduce transition animations
|
||||
0.10: Swipe up/down for Hiragana/Katakana, right/left for next/prev letter
|
||||
0.11: Sort by 'AIUEO' instead of 'AEIOU', draw Widgets every minute :?
|
||||
|
|
|
@ -1,19 +1,25 @@
|
|||
# kanawatch
|
||||
|
||||
A simple watchface design with hiragana and katakana
|
||||
cards for learning.
|
||||
A simple watchface design perfect for learning hiragana and katakana.
|
||||
|
||||
## Changelog
|
||||
* Interact with the interface using swipes
|
||||
* Swipe up/down to switch between hiragana (H) and katakana (K)
|
||||
* Swipe right/left to display the next or previous letter
|
||||
* Tap to change accent color (always 24h, not configurable)
|
||||
* Non-intrustive transition animations
|
||||
* Low battery consumption
|
||||
|
||||
0.01: First release
|
||||
0.02: Improve battery life, sprite resolution, fix launcher issue and unaligned text bug
|
||||
0.03: Reduce code size, refresh once a minute and faster refresh
|
||||
0.04: Show a random kana every minute to improve learning
|
||||
## TODO
|
||||
|
||||
* Only render what needs to be repainted
|
||||
* Dont redraw the widgets if not necessary
|
||||
* Minigame to guess kata/hira phonem
|
||||
|
||||
## Author
|
||||
|
||||
Written by pancake in 2022, powered by insomnia
|
||||
Written by pancake in 2022, maintained during 2023 and powered by insomnia
|
||||
|
||||
## Screenshots
|
||||
|
||||

|
||||

|
||||

|
||||
|
|
|
@ -3,182 +3,214 @@ const stripe_pos = 40;
|
|||
const stripe2_pos = 110;
|
||||
const h = g.getHeight();
|
||||
const w = g.getWidth();
|
||||
const decompress = require("heatshrink").decompress;
|
||||
|
||||
/// /////////////////////////////////////////
|
||||
const katakana = {};
|
||||
const hiragana = {};
|
||||
|
||||
function benchStart() {
|
||||
return {
|
||||
now : +Date.now(),
|
||||
diff: function() {
|
||||
return (0+Date.now()) - this.now;
|
||||
}
|
||||
};
|
||||
}
|
||||
const startupTime = benchStart();
|
||||
function image(x,y,b) {
|
||||
return {
|
||||
bpp:1, width:x,height:y,
|
||||
buffer:require('heatshrink').decompress(atob(b))
|
||||
buffer: decompress(atob(b)),
|
||||
};
|
||||
}
|
||||
katakana['A'] = image(56, 51, "v//AAfwAon//AGF/wGT/gGM/A3F/BDEn/wJQoGCj4RB//gAxUB//AAwcDAwsH/+AAwcP/4tCAwMf/wGEn/8Awl/JYYGBKQkf/I9DAwJgBGwQGDGwRlBAwJsE+42DAwPzGwYGB+J7EQIIvDQIIFEAw5DEAwRDDgCIEAxCPBKIcAR4IhER4hnCLAg9BLAgoBAwgoBcQiCBMwj0BHogGBHogGBfoooEQQREFEIgGBAokAhAGFA=");
|
||||
katakana['I'] = image(54, 55, "AAkEAws+AokB/wGEg//Awk//gTE//gAwcPCYt/CYkDCYsfCYv//A0F4A0ECYg0BCYggBCYn/KwhBBGgl/EAgtBEAgMBEAZOBEAgMBEAYZB/+ABggTDBgQnDAoIaDJoIaDFgIABDQQFC74aBBgX8v4aBEwWBDQQgB/EHDQQ6BwEfGoX/+AJBDQMDWAKMBDQMPAQIaDiBFCPAgaDU4hrDDQiuDDX4acSAIaCA=");
|
||||
katakana['U'] = image(52, 55, "AAMP/gGE//ABlH/AAnvAon+Bk5EDv/vIgcHBkHPBgZwBBgn/Bi8B/+PBgcf/AMFw/wBgYEDgED/6qEv4MEKYK3F8AFDj7EED4LREv/4CQn/wASEFginBDAgfEDAIfDn67BC4YABH4QXBCQcHZoQkEEoYMCHAYlBFYZEBLwk/MgpQEAAw");
|
||||
katakana['E'] = image(58, 45, "h//AAfwgYGE/0AAwn/wE/AwngDgv4DjhDCv/wJQkf/gGEg//AwkB//AA4gc/Dn4cjbAv/34GF94GF/YGF/wcjwA=");
|
||||
katakana['O'] = image(57, 54, "AAcf+AGEh/8AwkH/wGEgf/AwkB/+AA4n/4AGEv/gAwk/GIsf/A4P/4AE+F/Awn4n4GE/kfAwn+h4cFg4GFwYGF4IGFKwYFBMQpxFAwJxEAwJxEAwJxEAwJxEK4JxEAwKqEMoQGE/o4En/8HAl//iqEAwKqEv/+VQgNBVQgNBcYgNBcYhLBcYhSCHAQKBAwI4CAwY4CD4IGBHASxBAYI4CAwY4CYwIGBHAQGBD4I4CBIJfCHASmDHAV/PYQ4Cj5QCHAUPLwQ4CgQGCOIgABOIgABHAIGEAAY=");
|
||||
katakana['KA'] = image(54, 54, "AAMP/AGEv/gAocB/+AAwcH/wTEj4arg//AAf+j4GE/F/AwnhAon/w4aZHAMP/hTEn/wKYn/4BTDgf/KYgQCDQYQCBIQQDBIQQCBIc/DQouCDQQuCEghJBEhITBH4RTBLoRTEBIJTGCAUPNwoTCDQQWBDoIuCj4TCJIX/CYQ/BZQInBH4U//0HwBTBGgPwXAXwh4PBXAXAv4PCZIIgBEYTJBn5SBDQXABAIzBCYJcCDQXwgbOCAwIDBQgI4CgEOJwIADkAGFA");
|
||||
katakana['KI'] = image(58, 55, "AAU+Awv/4AGEn/wAwkP/gGEgf/Dkk/CAc//4ABwAGBj4GC8ATBAAf4h4GE/woBAAmAAwvgFAYcIwAcD/BFDFARFD/kBIoYACv5FBAAcfRL94DgkfHgf/95EBD4RgDD4MHLwf8AogAd+CPFGwiJCS4XHJgSGB8CJEkCJJUwYABg5pDD4amTNwKmXYbgcDLoY=");
|
||||
katakana['KU'] = image(55, 55, "AAMHwAGEh/8Awkf/AGEv/wAwn/4AFDgf/EQkH/whF/4ACAwM/AoQQCBgY5BgIGDHIMHAwY5Bh4GD8AhEIAQFDIAIhBBIJACEIJpEj45CNIV/NgRpBDQIrBEoPgDQJlBEoQaDEoV/RwUP/wPBQ4Uf/gPBQ4QsBKAKSD8BvCSQXDDQYYBNYIaCGYIqBDQU//kPXoYYBj5QCEIPgj60DKoMcWga7FKoYABKogaDbojPBbojMDGob/ECYJBCbgYaDE4IaEPoIaDEAI1EbYQZECYgtBCZQGCLol/KwxxEAwJqEgIMFgIZEgA=");
|
||||
katakana['KE'] = image(60, 54, "AAMcAwsD/4HFn/wBxl/8AGEg/+BxkP/gOF//ABxcB/+AA4kf/BCGAAZOBv4HEIQIOGAwgOBh4OFGYIOFn4OFEgoOBAwvgh52BKgYDBOwJUDv5nBBwY6BAYM/BwIKBJgJjBBQSbCWoQVBRgK1D/4oDBwJJBWos/WIS1CgIVCJoRGBWowCCj61HYgpRCdIjEGLgTLEIwTLEfAv/GYqtBEghyBGYjoCAwwkDAwQVEYwYjEHQt/CopeBQgQOEIIgOBPgxeFgZ7FA");
|
||||
katakana['KO'] = image(49, 46, "v//AAYFF34FE74FE94FE+4FE/IFE/gFE/w0Dgf/AocB/+AAwf/4BHE8AFDn/wAocf/AFDh/8AocHGH4w6YZf7Aon9YYoFEejBhEAAIA=");
|
||||
katakana['SA'] = image(58, 53, "AAcD/wDBg4DC//AgEB/+AgE/+AKBv/ggEP/gGBj/4DgP/DnU//4A34CQ+DAIcEDAIcDDAQDDDAYDCDAYDD/4cDIgJADAAUfIAQACh4jCAAUHD4QACJwIfBAAQtBEYgGBI4QUDFQkP/4qEVYQvEAAIxCEIK5CBwV/AwsfAwocCAwYcCJogcBNIp3F");
|
||||
katakana['SI'] = image(56, 52, "gFwAwt+Awv/8AGF/gFDgP//4GGCocDAwIVDBoX/wAHCn4VFg4GB4AxEAwsfAworBEQYABv4GFj4DCjgrCBQYRFn/4JQfAIgIGD+F/JQcD/gGBMARQCOwcH/wNBCoUP/0PAwIrBj/8OwQGBn4fBGIIGCAQIlB+BcBAQKvDBIQRB8AfBIQUH4AXBP4RXBGgJmERoJsFAwv//yaFbYghBQIYaCeAi9FPQTZGdxKFCFASECFAZPBEIgNCJQaZEAwhDDAwRJDTAYGEQAiQBPIgAGA");
|
||||
katakana['SU'] = image(60, 51, "gH/AAYGBh4GD/AOG4AOF/gONDo+ABxAACgY7CAAd/+AGEg4OG//gAwkP/wGEgJCCAAcfKIQzEIQIzEIQozOj4zFEgIzFn4kHGYv/M4okIGYt/IQqXBFghuBHYs/bAY6DCwrJECod/HgYVB8ZLEcoMfLQYECCwYVB+BTBCwT7CCwYrBAYIKCCoQDC8BXBEIQSBNoQVBBYP4EAIoCOQPHCoYTB/xdBIwQ8B+6SET4N/dYn/4aCFFgKRFgC+EgPghivEAoI");
|
||||
katakana['SE'] = image(57, 53, "gEH/wGEgf/AwkB/+AA4n/4AGEv/gAwk/+AGEj/4AwkP/g4JjA4EBQQ4D/4DD4E/AwIuBv/vAoP/FwILCAAIuBv4GEBgn//wFEAwITEh//CgfwAwMfCIRGB/4BB/5xBAgJTBIQQGBwP/75CBAwOAD4JCBAwRmDDIKYBOIQGDOIQGDOIQbBAwSqBAwiqBAwiqBDYg4Cv4GCHAUfAwQ4Cg4GCHAUBbwbjnHAgADcYYADUYQxEEYq6CVwbDBdQi6CZQYqBAAZcCAwY1BEYi5DAAQ8CegfgA=");
|
||||
katakana['SO'] = image(52, 52, "gGAAol8AYUD/Ef4AGCn/3/wFCg/+v/wAwV/8//Bgk//AMD8f/FoQMBj/8Bgfg//gBgcPFoYMBFocP/kHFof/4AtDBgMDFoYMBFoYMBgIIBgADBwAtDj4dBHQQMCFoYqCHQQqCFoc/BIIPCCwQtDKYIpBB4IwDIAQwCh45CBIVAFgSmDFIaaDOIYfCVgYfBRYYfCTASTCUoY1BQgZPCD4l/D4kfH4g4BH4YYBH4gFBGQd//4yDBYIyDn4SEJQIlEBgRXEHAg+BFYZRGZYQADBYgAG");
|
||||
katakana['TA'] = image(55, 56, "AAMHwAGEh/8Awkf/AGEv/gAwn/4AFDgf/EQkH/4oF/4ACAwM/AoX+FAQGCHIMBCYY5BEIIAC+AhFIAIhDHIQFDF4IhBJQMHF4JDDNIUfHIRpCv5sCn/wDQJsCDwIaBEIIKBwEf/9gOAQaB/gbBFAIPB+YsC/AaB54RBFAIaBAIOAEoJvBOgPh/+DNAJWB+//DQPBQIZyBM4f4LQSQC8EPKAIpBFAMPPgKKCgEcYIZwBiAGDbohwEZ4bdEFILxFf4ghBXwLjEDQhLBCYoaEE4IaDdIQaDBgLBCDIRQENYYTIewRkEAwJCFHYicBOIkAEAhDBS4IAJ");
|
||||
katakana['TI'] = image(57, 54, "AAkGAwsfwAGE//gAocP//wBgn//gEBgIFBAAIeBAof/wAYBAwkHAof+gEDAwf4E4YAB4AGBv4TDAAM/AwoxDKQhABLQwiCAAV/MIglBMIglBHwRwDNARbF//3Awv7Awv9Awv+Awv/MQQAD34GF74GFKAUHOIYABSAJxGaYp4Uv54FP40/P4oGHQwQGKKgt/AwrUEMIQGEVYIGLg4bMFII+Fv5TGNAsPQgsHTIoAG");
|
||||
katakana['TU'] = image(54, 53, "AAMBwAGEj4FEgf8AYPwgFgn/4BIP/g+Av/ggEP/n/gP/4EAv/v/wQBFQP/z/4CAMAg/+DAMfEIICBDAN/FgN/8YYBBAIaBw4hDDQIVBAYMAn/wDAIhCCwIhDCwIBBwAIBHAIYBEIQYDBAIuBwAjBFQghCJgQhEAIIhDEYQPBh5HBM4IhDQQQhCwYeBCwMBCoSPB/0CIQQhBAQKWDvytBCYTBDv5tBZYYTCAAQTCAAYTFHAITEj4TF/4TEh4TFv4TEg//JgIMDMYIMEO4ImD/53BAAM/AwIsEEAgFBEAZNBIIgTCFocfJwo6BPgpHEgZAEgEOAogAGA==");
|
||||
katakana['TE'] = image(57, 51, "h//AAfwg4GE/kDAwn+gIGE/8AAwuAv4GE4E/Awngj4GFNWJNF/gGF/5UF/+/AwvfAwvvAwv3Awv7GJn8IQV/4BJEv59Fn/wAwkf/DJFEAYABg/+AwjJBAxbQBwAGFH4gGBH4gGIIwgGNG4IGEg//LYjyBAwiyBAxc/EQoGGFIJTLdYJvEgF+fIsYAwo=");
|
||||
katakana['TO'] = image(42, 54, "//AAgU/+AECh/8AgUD/4U/CgYPDn//wAUC/4VCCgIlDAgIKCCgIKCCgP//wUD//gCgQKCn/zBQQ+BDYP8CgMBEAQBBj4KBKYIKC54yBBQP7KYIKCG4QKB35YBBQIUCGQPjNAUD+BXDnB9Dgy8/CicAA=");
|
||||
katakana['MA'] = image(57, 50, "/4AE/l/A4s/AwvfAwoAN/YGF/oxGHokf/wGLh4GN/4GSg4GChgGDwARBAw3gAwv4Awo7BAwn/4ACBAwIKB+AGDgJtBAwcAUgOPAwYLB94GDgaFCAwTBDAwcfAwoyBAwgyBAwgyCAwgcBAwgyBNgL0ENgIADn6oHDijhFW4wcB4AGDKwPwBwl/fwzUJDgZOFgAGGngGFhADCA");
|
||||
katakana['MI'] = image(52, 53, "gPwAwkf/wFDgf///gAwU/AwIVCBgX//AME//8gEHAoQGCBgYGCv4GDFIMPBggoE4A2CCoIuCAweAAwc/BghYBMwswNw0PNwkBGAIbEG4gMCOoYMCOoQMDAwRnE4BYDKYQTEKYRuCKYY8GgCjDAAV+LAtgcTMDbYhTCHobICBwbBDBghZDZwmAZoYGCAogGBCYgiBEIidCBwQ2DS4QMCVYT2CSAb2DBoLpFn72EdJAA==");
|
||||
katakana['MU'] = image(59, 54, "AAMDwAHFv/AAwkf/gVF/4VG8AGEh4VHFgoVPFdZBdRogVBgP4CokBFogVBn/wTIkHEwYrCv4ODCoMP/wVDFIP/JYQVCBwgVBGYLICCoTIDCoQCBBwQhCn5RCCoR/DNoZCDDIRRDCoQODg4+CIQYvGCoZCCCoZRDAQV//4SBRAM//4ABwEfAgQAB/ARBAAkPAwvxAwv+Dgv/8YGF/gkD/xCB543DH4P5AoaBBewsAvgGFhgGFAAQ=");
|
||||
katakana['ME'] = image(55, 54, "AAcB8AGEgf/AwkP/wGEj/8Awk/+AGEv4iF//AFAuAAwcHFAsPFA34AYNwFAQvBgICCFAUHCAIoDDwQoDn4DBKIf/MYIoCDwIGB/5RBAwWDKIYGB456Dv//75RDAwP/JQQmBAwJ6Dj4GBOYYGCOYcP/5zEg//OYgGNDYw3BAwgvBAwaABAwgaBOARZC/wGDOoP8MQI1D+AGDFwPAAwJaBDAQNCJIc/AQJsBTYL3COQc/4ATBXoYdCSgU8J4SNCmCNCNQqoDAwQuBAwgFDFAITEAwK1DAAKZEAAIMFAA4=");
|
||||
katakana['MO'] = image(55, 49, "j//AAfAv4GFAon/wIGFgYFE/0HAwn8h4GE/AvF8A4Bv4DCAAQzBAocB/+AAwYxBCYkH/wGEh/8MIv4Awk/+AGEGyJfFAFP9AwpOBNuikeAwxfEHoLpFNoZACAwZABIgIACJYYABIAYGCIAYwCHIoABA=");
|
||||
katakana['NA'] = image(57, 55, "AAV/8AGEn/wAwkf/AGEh/8AwkH/wGEgf/AwkB/+AA4n/4A4rGoIAE/IGF/wGF/9/Awu/AwvfAwvvAwv3AwpQCOOqqEWLV/H4pGGn5GFAw0fJosfJooGGn4GGKgq6BLQoGEg4GFh4GFPoIpEDYIwFv5MFLQ4GFg6EFgaZFAAw");
|
||||
katakana['NI'] = image(56, 43, "h//AAf4A25+/AH4AuWggA5A=");
|
||||
katakana['NU'] = image(55, 51, "g//AAcAh4GFj4FD/0An4GD/kAv4GD/EADQnwgIGE8EDAwnAAwuAIIgvBAAcPF4IADn4vBAAd/8AGEFAIDBAQIsBFAMDCAIoDh4eBj4oCj4GBFAd/CIJRBgBZCAQIlD/+HQIIGD54oCNwZKDPQZPDOYRdDOYqmBOYi0BOYjCBBogGGYQSAEAwimDGATdDAwQTBH4JFBLIP8AwYTB+AqBAwITB4AGBE4bADBIJyBUIJ6CVgXgJAQzBg+BAoJkCgxcBCYRIEPArlEH4YGDO4ibBeQs+AokAsAGF");
|
||||
katakana['NE'] = image(61, 55, "AAX/4AGEg/+Bws/+AGEgP/wAHEh/8Cwt/8AGEgf/Bwsf/AMEAAYnBj4GDHwQOEDAMHA4hVBn4WFJIIADHwMPA4hgCAwZkFCQKCGBwpHBPQwOFFAJyGBwt/BwozBBwpwDGYiYEEgP+iAkF4IPDCoP8j7WCUAXhbwYVB/4RBU4n4QISfD54vBS4f+FASPD+AEB+AFB/IjBFIPnA4LzCGAfAeYIjBGAP4eYQCBwZuBeYUH/EfIwJRCAoIDBg6ACnCmDR4oqBDIKfEHgKuFS4g5CBwo8CWwqOCAAQ8DcYg8Vn48FAAo=");
|
||||
katakana['NO'] = image(47, 52, "AAcHAokP/gFDj/4Aod/+AFD//gAgUB//AAoUD/4oE/woJn4oLEQYoBwAoIh4oEj4oFJZ8HERU/EQhFEDgIiDH4JFDh4iEH4t/NAYcFHII/Dj4cEv4/DCwIcDCwIcDCwI5DCwhEBHIYQBKwf/GYYhBCwc/FoYKBFoYEBFoQKCE4RrBE4YFCHwQyBHAYnBJ4YFBcBN/AgcAPgYABA=");
|
||||
katakana['HA'] = image(62, 52, "AAP/wEH/gGCgf/gE/+AHCh4MB//AA4QMBCIQeD4ARCDwv4Dwt/8AeEgI4BDwkH/weFj4eEAgIeF8AeEAgQeEAgQeEAgQeEAgQeGMggeCMggeCQYiACQYYbCDwgbCIogbCIoZZDIoYTCMggTCEwn/CYJFDBYZFDBYYmDv4LBEwYDDg4aCh5JCDQYiDaIQWBNAQ5CMAYLDcgYmCCwgqCGIYTBFwL7EJIIWEAgPgh4WDNAPACwgMBCwiHB/wWEFwV/CwZVB/YWEDgPHXgYuBDwLbDKQPwh60CGwWAngGDgAFBkAHEsAFEAAQA==");
|
||||
katakana['HI'] = image(47, 51, "//AAgUB/+AAoUD/4QDg/+AocP/gFDj/4Aoc/+AFDv/gFw8BwIuDj+DFwf/FwcP/4uD///FwQKB/wuBJwIFBFwM/AoP8//PAgP/+IDCAAJdBAAXwg4FDEoQKCIIIgCLoQFBKYV//5qDB4aMuF1YFDFwIRDUIQAC+YFE8YFE44FEw4FEUgn+Aon8WwhKBXggA=");
|
||||
katakana['HU'] = image(49, 50, "/4AEv4FE34FE74FE94FE+4FE/YFE/oFE/w0Dg//AocD/+AAoUB//AI4ngAod/+AFDn4FEj/4Aon8AocPAokHHgg2BHhYFDHgJCLJBZCEAopIFAoxIEAoxOEApc/AojSBbwplEAoZxBAocPAojICBQhBCGYIFDBYRZCa4P/NYQuCPoYFBSoZGFZYsPAgYABA=");
|
||||
katakana['HE'] = image(61, 43, "AAMH8AHF/4HFh//wAOF/wOG/AHEv4eFg//DwoOBDwgOCDwk//YeEgf/x4eEn/8n4eDgP/4AeEj/8DAIeCBwPgLgkfDYIeECYQeDh4LBIwIeC//wDIIeCBYJdCDwV/BwIwBDwIOBCQYeBn4pCDwRIBIAQeCMIJPD/AOB4CED4BhBMwf/MISbD/kHPovwj4ODDwV/UYhYBKQJ2DRoIGDHQINEcARCCWYgGEDwIOFgb+FDwL2EDwQGFIQoeCBw0YA40AA==");
|
||||
katakana['HO'] = image(61, 54, "AAV/8AGEgf/Bwsf/AHF//AAwkH/wOFn/wAwkB/+AA4kP/g8Rg//AAngv4HFCYIAE/EfA4vAAwv+Eo3wn4HFwAGFJwZ5UgfAPIJzDn/x/+PEgR/BAoJzDP4N/8JzD//D/6KDFYI8BCwYrCCAItBPQOH/wWDCgIQBCwf/4P/wIWCCQIBDWgYBCZ4KJBE4LPDEYInBh5sBBgKLBNgQ0CJoIWB4ACCBgIiBBwP8EYU/TQLXBHQQECFAI8BCwIqB8DzCDYMPAgQbCMoI3BF4IRB44OBWwQUBv4TBJIV//InBHgQCBw4OBHgUH/EfNgKOCj0A3BsCQwNgeaSdCABA=");
|
||||
katakana['N'] = image(54, 50, "ggGFngFEgP+AwkPAws/AwkB/4GEh4GFn4Gaj///gNF/AGF4BEJAwITBgOAAwQTBh4GCnwJCCgVwLgRwMHAgTBHAgTGv4TEgYTFMIITEMAsHMBY0B+ClFCYiPFEAITEv//OIQMCTg3gBgggEDIIgDGYIgDMIJVDDAIABIIILCFoYYCJwZ0BHQgsBBgZnBBggnCKgYhBMIi3FgAFFgAA==");
|
||||
katakana['WA'] = image(51, 50, "/4Ay4A3E/AFCh4GBAoUBAoPgAwU///8AoUHBgOAD4nwAoUf//+AoUDGRYSBGQYSCGQd/94yDh/9GQZFB34yDn/zGQcPAgYSCG4YSBC4YSNv4SKJYJwDLwISEn5QDS4QSDDAJjDDAJ2DGIJ2DUYQ+DQYKcFFYYXBDASOCGIQFDGIQRCDwTaCG4YFBEgbHHN4hiFg6HEA=");
|
||||
katakana['WO'] = image(50, 52, "/4AE34FE94FE/YFE/wYYGocB/+AAwd/8AFDn/4AocP/gFDgf/KovADAnwDB43B45EE+IFE/F/KAkfBgmHAonhAonwDAn8h4MEN5X/N4l/N4k/KwkfRwgoBDwcHOohoBOoYFBEgY2BEgYFBEgYFBJIYXBFQYpBFQZ3CAoIWCKoQQCGwQLDHgR8CAoQdCAoQvCOYYFFn5gENgKREbYgAGA");
|
||||
katakana['RA'] = image(51, 50, "n//AAcHAongAon8j4GEwYFE+F/Aof+h4ME4IFE/BYr+4FE/wFE//fAon7BgpYE//vAon9CQo3Ev/gAocP/gFDgP/wASX+ASJgYSFXwJ2ECQivBDAoSEWIs//wFDbYIrDAoI+DAoIYDQ4IYCFIIABDALlDGIJhBewS/EJQQYCG4YkED4QFDD4JJF4AFDA");
|
||||
katakana['RI'] = image(43, 53, "AAf/7/4AgMf/f/AgMD/9/8AFBv/v/gEBh/9/+AgEB/+/+AKBn/3/wEBg/+//AFX4q3v4qDh/8FQQPBz4PDAYQvBEYQvCEYI/CGYRPBB4cfIYQpBB4cH/5TCDwJjD/4kCn4EBCgN/AgIUBDoP/FIJHBAAIyCDIYjBIYYaBQ4QaBJoZHDAAoA=");
|
||||
katakana['RU'] = image(61, 53, "AAUH/wHFn/wAgUB/+B/+AA4UP/gBBCgd/8ABBAwUD/4BBBwcf/ABBA4f/4ABBHQg8FHQI8/HksYHgwYBHgkPF4I8EvwlCHwOAg4gBEYI8CCIQjBHgITBCIP+HgU/CwIRBDAIgB4AMCAgMfEAIMBDAIOCBgQYCIwQMCPYJTBAQI8BBwUHEoN/8P/IYN/+AvBj4LBBwOAj/7BwZGB/4ABBwXAAQIODM4QOFHgIOC/4OBh4OCAYJGBv4OCn4OBHgJKBAYJkBIQISBaIYhCCwIOBSoTqBJQISBeYUHd4U+bYUwcAYAKA");
|
||||
katakana['RE'] = image(51, 51, "//AAocf/AFDgf/CQl/8AFDh/8AocB/+AAwc/+AFDg/+GX4ECgwyEgPgGQk+GQkP+IyDC4IyE//3GQc//gyDh//GQYYB8YyD//4GQc//wyDDAOBGQUH//gGQRvB/BlD/4DBGQU/CwIyCj4YBMoQkBBIIyBBAIYBGQIkBDAIDBGgIiD+AFBGoIyBv4eCGQIABJwQvBAAJnDEgTLCEgY8CIYLLDEgZVCAoZuBb4iaBfAj+EgE4AokAA");
|
||||
katakana['RO'] = image(50, 47, "/4AEn4FE94FE/YFE/wYF34YS4A1BgIYB+A8Cv/v/gFCj4YBAoUHDH4Y/DEbglDBQ8CAAYA==");
|
||||
katakana['YU'] = image(59, 46, "gP/AAX+A4M/A4fggEHAwf8BwIGD/4GBj4VFgYVGv4HDwEAh4GD+A+Eg46CAAf/4AGEj/4Coo6CCqJFBCot/KAIADh5QCQAhQBCrM/Myk/M3JQGh5QFMyIRBAH6NB");
|
||||
katakana['YO'] = image(50, 49, "v//AAefAonnAon5Aon+DDA1DgP/wA8E8AFDj/4AocHDFZjfDCJjxDD5WE/+/AonvAon7PgoYX/g3DAAQ");
|
||||
hiragana['A'] = image(52, 50, "gEB/wGEn/AAocD/gMcg//AAfgv4FD/wMYFIRNa54HDgYyCBgYsEBgX/+AGBHQYpBCQQaCh4JBJQPwgIdBBAP/wASB4H/j/8MIP8j5fBBIP/4P8gf+j/7/hVBj/jA4PH/C/Bn4RBv8Aj/3/Ef55FB/9/wI+D+/wj40BHwIWBL4QJB+BFBwAmB/4MBD4M/94MBD4JAB/4cBNYN/BgM//AsB/n/z4bBQgOHX4QVB/B3B/CQCAQTSC8BFCB4Q4CB4UAgIIBRQOAXojREn/gaIgAC");
|
||||
hiragana['I'] = image(58, 50, "v/gAgUggEf/AGCnkAg/+AwU/gEB/+AAwQZBDgcP/gcECQIcFCQIJCCol/4AGBgYLBj/wCokHCAIABFAIQCCon/DgQECn4cDCoItCAAI+BDggVCLoZeB+BgCCocPPQZUBwZdDJAQcEGAIcEGAIcEGQPDDghIBDggyBDggyBx4cBjxIC8aaCCAIyBLAMDM4IyBSARnC//HUIk/+IyBCASdBLAJKCGQOf/kDJQV/GQRKCJ4XgEYRPC/CoCDgOHNwl/8P/84jCDgM//5HCDgMHAwIjBgP8DwIsBQgYVBSQgVBaYZnCTIgtBbQhDCUAYkCfwYOCGIgAHA");
|
||||
hiragana['U'] = image(46, 50, "h//Aoc////8AFBAgIABgEDAofACwIAB/wWD//4CwgdBCIeAFQUfCwIADCwIAMj//+AEBv4tDAgQLBHAYFBAgf/8YFE54FECwRTB/wkCAoP7IAd/OgR2CKwcBQ4kH/hMEJYQcC4AWIh4WEn4tJg6EEj6EEVgIQDE4l/CAbABCAZqBBQgQDBQIQCXwIyCYYTIFeIhlCBQjxCLIQWBMgbdFvzYJ");
|
||||
hiragana['E'] = image(55, 50, "gF//4GE/4AB+AFBgIGC/+AgEDAwYNBg4FC/wGBh4GC/gGF/ArFFIQAD4BRVn42FLAIGEJQYGBLAhEBLAhEBLAf/8ArDBIIyEj5fCRYZYEEgJYEN4JNFDQouFDQKcBFwYGFMIIGDLQRJFAwgaBOYQuC8Y2DFwODAwcP/0HXAc//EPcQnAj5LCPAU/MwR4Cv5ECPAQ9CLoUBd4auE/guBVwf5PARaC+5qCAwXnJwSXB//HI4QGCw5ACAwUHNIn+gj/HAAg");
|
||||
hiragana['O'] = image(54, 50, "gEB/0AggGCg/4gE8AwUf8EA/gGCv+AB4QaDv/wDQn/CwIaCgP/4AaDgf/wAaCgPn/4PBAAXv/0HAwef/kfAoX+n/4v4GCAgPxCYfg/4jBAAWBGwQ1BgEDJoJQCJoJRBLYcPCAJrCgEcKAaGEHgSGDF4QPCJYYxCHoYMBn5YDBgoGBDIP8FQKiBDwabBFoIzCv/gEAJQCMwWfKAIbBh58BDQMH/l/4IaCh/xTgIaCn/P/BrD/8/4CGD/i3BDQfz/gaDv/P+AaCCAIaEHQQaDv/hGoV4h//g4VB8JnBa4ePZYRkBBwKNCbwPwCYR/C44CB4BtBfgSaD8ACBYQQWBAAYA==");
|
||||
hiragana['KA'] = image(55, 49, "gEH/AGEh/wAwkf8AGEn/AAwl/wEAhgGC/4CBngCBgP+AQP8AwMDAYIyDAYUPAwQ2CAwY2Cj/4gP/AAP4j/wgYGC/gGBg4GC/0/8EPAwsfCgd/4E/Awt/FIf/LgJmBE4IGCMwMf8JjBHwIPB4IDBgZmBv+DAYMHMwP/BQRfBOwIKCL4J2BOIQvBAgJxCGQIEBHAKPCCwIYDCwQBBQoRGBviIDIQJRC4AdCXAYdCKIcHboQ/CboY4BboghBboZKCFAYhBjAoDh/8nzME+CfBF4V/RgP/EgKVBwYGBFAMH/zIBFAQeBAwIoDboRRD4DrBJQUHAQJsDAAwA=");
|
||||
hiragana['KI'] = image(48, 50, "AAMB+AFDh4FL/AFDg4FIn//AAX4ArpHC/xNEAov/LQgFCDgYAlF4UfPx8/g/8CoQbBKgQhCAoMDFAkHAoeAh4FEDgQAB4E/FgIUBwE/HwQdBn/gAoM+AoPAAoMMAohFCAqIpCgI7C4BEBI4oICAoZfE4C9BAob2EAoISCaQgACA=");
|
||||
hiragana['KU'] = image(33, 45, "AAsB4ADC+ADC/wDBgf/wADMg//CYIDDh4DDD4UfAY/8AY34AZRDCh4DCg4DCgYbCgI/CgH/BgU/BgREBBgIQB8AMCFIRNDLoJ2Cv42DJwQdDFQIdDFQQdDFQIdDHYRkDgYhCgADDnwDChyzE");
|
||||
hiragana['KE'] = image(50, 49, "AAUB/0Ag/gAwN/wAICgEfBIIIBB4P4BAYPCh/wDAcD/gYE/4FBDAU/4AYEGIgOCDAQOBh//AAP+v+DAoX/7/AAof3+E/AoX9/gYD/9/gYFD/4YE/5QCGIJQDHYRvCJQU/N4JKCKAYYCKAQYWmAYEjwYEx6lDh/zUocDMgIYDv6cBKgUf/4yBBAMH/4eC4EBNQUfAQN/DYMPE4TjCAQQkCYgSJBDYLEBn7QCAQIbCE4UDDYP/PIV/CgLpD4EPP4UH+AkBAoIACCgIADh6LCAAMDAoYA==");
|
||||
hiragana['KO'] = image(52, 50, "h//AAX+gAFD//gBgn/BgvwBiWAAon4GwUBDIQACCQQFCn//4AFCg4lBCQc/DwYfBKQJdEDwYAB8CIihAFEgJJDIgQFEg5KEMgITEj/8D4hwED4JqEOIIfEv5eEg4fEFg0PHIwsEBigmFCYkOv65CJYPnbgn+ZgIAD8IMFewvgCYjRBE4IMDegQABIoUfAoK7HA==");
|
||||
hiragana['SA'] = image(51, 50, "AAMB/gFE/+AAwcf+AFDgf+DIl/4AFDg4fEgAfLgIfCj//AFQzCn/gLJYMELI5mEh6GGBgUHGAP4CAQ3COYILCBgUDIgYZBAoYmBn5REDwPgQQPgDAIVBj4fBJ4d+CQI1CgeAXhgSDKoYSEQQp1GQQpFBawXwD4IGBg42BaQngBgRlDBgmABgjzBRYZDCPIYvCv//MQoACA==");
|
||||
hiragana['SI'] = image(45, 50, "v/AAgUD/wKDj/wAof/wAECg/8BQc/8AbD/4bE/AbEFgcHFgk/FgcBFgkPDYhIgFgIKDFh8eFgn+FgcH/4sDv+/FgUD/osDn/vFgQ2BFgcf+YsD/+fFgUP/gsDv/HFgSKBLId/8IsCHgIXBSod/EIIKBwIhCv/4h4WBAQOAv/+IIP8AQIAC4AYBAAIkBn4KDJQIKDCwYpBCwRWCAoJhDAoK1DAAg=");
|
||||
hiragana['SU'] = image(52, 50, "AAUf8AFDgP+BjH/AYP/AAnvAon+BjJAUgf9BgZFB/4MDn4kEg4MFGIwMED4QME+E/+AyC/x0DFgPABwIMC/gMGDIn8gYMFv/4EwcP/+AKYf/BgRACBgYRB/4mCgF/AwJ6DBgoTCRohNDTZE/VAkP/gFDE4PAUQhGCI4YeEUIgYBD4gMBEpI4GgIFEAAo");
|
||||
hiragana['SE'] = image(56, 50, "AAcP/ADB//AAwP8AwkHA34FBAAn+A1JalmAGFvinFv4GF//PXghEBAwfBAwoNGEQP/+AGDn4GFh//8AGDg5PCgF/AYP/wAGEgj/CAwQADAw4mCAwZCCAAQ8BFQgGBAAQGBj4GFJQIGEJQIGEgYGFGIIGCIQQVDHQgACA");
|
||||
hiragana['SO'] = image(53, 50, "gP/AAXggEPAweAgF/AoX+gEDBgfwgEfCYoFD/EAg4MFAAQMCAAQwBBhQpBJQozBAAU/IAIACIYJUBAAV//gsJD4IsEn4sEOAn+NIn/+4FEAA39AwvvAwqQDAAP7UYhmCx5bDuBVB4BCDg5bEJ4JoEgJ1EEQKCESwIFEg5vEEA4TFh4TFv4TGYgiLBCYrFG/5dDd4YHCOQKkBDQjbDDQQwDWgR5DAwSGEEAgAEA==");
|
||||
hiragana['TA'] = image(52, 50, "gEP+AGE/4Mjgf/AAXAgE/AoX8BjUAgP+GYkf8AFDBhHnEIQMBEQQhBn/jFAWAgYMD/AMH/gMF4f/F4UH/kQGYd/KIIACg4VBBgmAQ4gMFUJcB/8DDQZgBv6iD/wuEn/gKIJGDEIl/4KCDC4KPE/+BBgYXBBgY5BAIImCj4MBTIKFB/wMBAAKSB8EPAwXnUYIMDCwLYD95RBEAIZCFQN/AwPBKISpBwEGQAgAGA==");
|
||||
hiragana['TI'] = image(51, 49, "gED/wGEv/AAocP/AFDgP/CQk/8AFDg/8Bgn/wAFDj/wBQYAqJ4M/LBZrMJYZ+Ch5aDv/f/4bCBQIABCoMDHAYTBv4+Ej4MEg4DB4IMCAoIcCwE/TwU/+ASBEQI8BVQJLCv/gS4cP/kBMgYWBjyoEgLbJEYYSCQQkHCQg2EHASCEv4SBgYOBOQ70BQoYrBEQIABFYR/DJASRED4YFCBgJDDA=");
|
||||
hiragana['TU'] = image(59, 45, "AAUP/4FFAAIGCAoX//EAg4GD//ACYYAB/kBAwgOBn4OFDgoOBAYX+BYP8j4GBwEAAgPDGwQ+C/F/BgIABCwOMLQl/+AGEg/+NIv/8BwF/gGEKwIqDAAM/HAYzDEhkfEgsDEgxJGh5JFHQPACqQrBCpkfCopXBCogcBCog5BK4jSCAwxtDDYK8EZIQcCAoQcDCYTjCJgQGCEYT0DIAYGGEgQGDEgRcEv5UEA=");
|
||||
hiragana['TE'] = image(57, 50, "/4AFv4GF34GF74GF94GF+4GF/YGF/oGF/w7Cn//4BCDAwOAAwpQEj4ZDAxP8AyUPAwwiFg4GMgZFFAw0BLQqlBNAkAv4GG8AGEn/wKgv4KhZGGHALeGH4oxNh4xFOJBjGEYt/VQwVFg//BwhOBAAI7Dv4GBHYYcBCwgcB/5CEDgQyFGYgrCUwkPKAwAC");
|
||||
hiragana['TO'] = image(46, 49, "gEH/AFDj/wAod/4AECgP/Cwn8C0cICwcDBoIWC/4NBCwMfEgV/4f/BoIWBv//LAMH/4AB8AWBAoWAgE/BQYlBDYUAh4FBHwQPEEIJQDFYJhCgYwCLQQqCDYQKDDYIKDn5xEEAYQB/x8JDYkDCAkPYIk/JoQWTAol/AocZQwR6B8aNCAAOPAgf+TIZqBAongT4QfCBYY9BW4R1BA=");
|
||||
hiragana['NA'] = image(55, 50, "AAd/wEAn4CBgH/BIXAgEB/wJEgf8AQIJCg/4AQIJBgEP+ACBBIMAj/gAQYsBEoIoCGwf/GwkB/8P/4AC4f+j4GDw/4n4GDj/wv4FC/0/8AMD/l/4IGD/H/wYGD+P/g4vELARtCMQRtDMQQKDL4YKCMQQKDMQQKDR4QKCTIYKCFYQ2bOoI2C4BgCGwWASAQ2BGQKJC8DNBBAIAB+DNBPYf4ZoKrDAgPwT4K7BAwRdBB4K3BVYIqCVYY6BAwKrB/0DVY3+v/hAwf8n4SBdIXwnxEBAwXgnBEBAwShBO4IbBSYSVCOYQAHA");
|
||||
hiragana['NI'] = image(57, 50, "AAMPwAGE//gAocf//wgFwgEH////kH/AZBAwP+gf+Bof/wP/gEDAwWAAIMBAwc/FgIGDj4sBv4GBE4P8HAIdBE4IqBAwYgBKAIGCKAYKBAwN/EYIGDn4jBAwZfBDAQfBLIPAAwZZBDgItENYN/CAIfBIAIGCLIRfDLIXwAwc/RQJmCHAPv/0PEoI4B+f/AwcH/P/w50D/l/wZ0CgP+j/BK4Q4Bg/gJoQ4BwIGBIwU/4EwAQI4CIYICCAYY/EJQMHHATcCbAQKEHARGBGgQqBCIc/D4IGDaITCDT4PAAQJfCQQRYDeQQGDSIIGEYYIGEE4IGEDgYFCcAQ+CGQZsCABAA=");
|
||||
hiragana['NU'] = image(58, 50, "gEP/AGEgf//wHE/4ABAwc/AwIPDh4OC8AGBg4GCEwUBAwX8Dod/EgoHC4AsF+BJFjAGDg4iEFgRfF/+AAwk/IwQjDFIgjDvAjDMYJlCgRHB4ABBFIUf/ABBFIXH/0HCoUf+BcBLwQpBCogpBCYIVDv+ACohNBn/wCoRxBCohNCMoIVBOIQVBAIJNCCAIVCEYIQBCoOAb4QtDCAQtC/gjCdIIXCN4QwBC4SVBDQIXBEYUP/gXBI4QEBHwPD/8ODgR/CwZNCCYN/8P/5/4GQOf+DtBKgXv/jtBKgX5/0PAwJxB/0/DAL8CvkDJYP/IYMMgFgg//fot/VYQACgYGFAAoA==");
|
||||
hiragana['NE'] = image(67, 45, "AAXwA43/4AHFn/8A4sPCA0B//+CAt///gA4kfCA0H/4QGA4IyFn4IBGQg5BIYsD//nCAt//F/CAkf/wzBCAYFBwH//BaE8ArBwBzFCAgNBLoQQCHIPADYIQD/6dBCAk/OQIQEHIQQEHIQkCCARaBO4YUCSYQQDHIQQFHIQQERQgQCLQQQEHIKBDCAPAn5fDCAP8gbNECAaJDCAbVECAPgvj+Gg72GdoqYFCAgHFKIoQDDA0AKIjODDA0ARYQAEhwHGAAIA==");
|
||||
hiragana['NO'] = image(54, 50, "h4GFn+AAocB/0IAwcH/F//4AB+Ef8IFC//A/+PAwcD/0fAoX8h/wDQk/4ITDAgMDAwcH/hGC/EAj/wIwXggF/4AGB/+AJIIFBGQJJCDQoWBDQf/wZlBDQIWBh41Dx5kE/0/Mgn4IgIGD8f8MgYaBL4IaEPQJrD/6RCGoRkCKAR/BKAgaBKAoaFNYoWCKIIaC8BKCDQWAIYQaCgJCCDQRyDDQRXDEoOBK4ahBW4K+CAgKcBDgLcBMwIwC/1/4JHBCYP5CoQwC4aND/atBRofDAgPgdQaSBHgX4hxXBHQXAhAOBAwKXCAAJlBbIIAH");
|
||||
hiragana['HA'] = image(50, 50, "AAMH/gFDgP/Bgl/4AFDj/wDBsH/4AD/oFE/9/AwoARJVXhAon4JQn+j4MEw4YLn4YEJTIfCAooYCAoX4DgQwCwBdEBgMDHoYMB//3Bgd/8AUC4A7BJQP//kHBwQGB4JYBFoX8KgMP/gGBz/+h//AIPjGAXA//wAoXwh/4DgX4gP8IgQnCF4QFBgOAEIKIEv6SCAA4A==");
|
||||
hiragana['HI'] = image(59, 50, "gP/AAOAA4U/AwPwAwUHAwP+CwYVC4AGCj4GB/AGCgYOCCod/AwPgGokH/g8GHQY8CHQYVCHQg8CwEfCAYEBgYQDAgV/JYYEBh5LDj/4GoJKEGoJLCAwP4JYZ9C/BLCNwSGDQgSGDOoaGDAwg6BEYQHDh//EomDAIP+ToaQBEIIvCKoJyCJgPH/yDCEIIVB4BNBMwIgB+CZCn/n4f+h5jBAQMw/+BOgKyCCoN/PIICBS4I0BCoQJBJQJqCBIP5NQfgD4KACn5tDGQSDEwADBTIJaBGQKZEDISvCToR8BeAQDBAQLbCb4RSCAAcHcQYACvwGFg45BAAj/DAAw=");
|
||||
hiragana['HU'] = image(55, 50, "gED/gGEg/4AwkP+EAhwGCj/ggF+AwU/4EB/wGCv+Ag4GD/4kBAwM//4AB84GBv4GC54GBAoX/x/+gIGDh/+gYFC/0P/kHAwX8AwMPAwX4j5cCGwJOBAwJIDj5jBv4QCAwIpBNoU/+AiBNoIGCJYJtBAwPhFwPANQXjAwOAgEEv+P/A2C/H+CoI2BTIIhBwY2Bh/xwH+UgUf+CwBUgSgBBYKkCn/gh/gToI1B4Ef4AvCBIM/4ZmCIAN/44oBSgKdCFAJ3CLAY0BUgQoBGgIGBEIUPAwSID+AGBQIZHBJQRECd4Q9DI4QvBJwQ2Cj4sBGATRBJwLcDFgTcDC4QGEEILqEAwIbDIARoCBgQAGA=");
|
||||
hiragana['HE'] = image(55, 50, "AAUf+AGEn/gAwl/4AECBQP/wAYC4EB/4YDwED/wYDwEH/gGCCIMP/AFBgIRBGwcDCIN/GwUH/EP/4bCDAP/AAI2C+4GCHwMfAoX/JgM/AwYjBv4GI8YGCFoN/wIGBgYCBFwIiBHYJfBNAPAn/8IwIGBwAaBh/wAwOD//4R4IfBg//+B2BDoJKB+AoBg/+JQPjOwMP/n/z/nQIMf/IOB76BBn/3/gVBMgN/94nBOQX/7/gAwKbBOwSOCHoJMCEIMH/v/CAJxBh/7/hcCF4X4KYLEC5/wj5KBEIOfGwJRCL4PzF4V/JIQvBCYJJCH4JxB4AGB/xCCFQIJDDoIMBBIRNBAQJdCIwKUCeAb5CPgQACSgIFDSgIFEAAg=");
|
||||
hiragana['HO'] = image(51, 50, "AAN+AokP+AFDgf+Bgl/4ASE/ASVv//AAX8h4FD/+BAonwn4FD/0HBgnAAogoBgP/HAk/8AFDg5LEgASM/gSFwADBFQIAC8E4Iof+/5FE5/wAof5/0fAwc/8YFD8f8PAYEB54MDJ4SRDJ4KRDj/gNYaoCLAYWBLAYWCLAQWCDYJvDgYSCCwV/NYQWBGQc/+AyDg4yBj4MBgYSBAQP4OwPwbIglBQAgpBBgZiBBgYYBBgY1CU4S0DFoIRCAAo=");
|
||||
hiragana['MA'] = image(55, 49, "gEP+AGEj/gAwk/4EAkAGCv+AgAPD/8AgYdCgP+EgkD/gdB/AGBg4DBv4GCj/w/wGCv////8AwQFB//4AwMBAwXwEQMDAwXgAwMHAwXAAwMPAwWAG4QvBLgQGBL4X/AwRfBKgIGCL4X8n/gLARUBn5YDMwM8NQaLBQYIoCAQSIDAQRZBRYaBDRYQhBFAIJCKIYyCDwKoBToZkBOAIJBPYKLCGwMH/h2CAwMfKoKKCI4PgSIYYB4afDJQMP/gpB+AhBMgIjB/AhC4EfAwIhCEoIGCwJdBaIIZBMgSkCjhMBgakBG4LICUgKDBAwQuBPgRKCjgGE4EQAwgEBAAIbBRAQACQgIDB");
|
||||
hiragana['MI'] = image(50, 50, "h+AAocD/gFDgP/CQl/4AFDn/gv//AAOP/E/AoXj/0HAoX4/+BAoX+DAuf+EfAoXn/gYD/P/gYEBG48f+AFDg5QMMYkf8BvE/BvE/wYE/4YEKAIYYgZSCDAMBJgQYCCgYDBFoYDBj4tCDAJlDDAMBGYYYBNYYYBn4xCg/4h6ECPgIHBPgfBDwaVBQgYvBToYYCFYauBaIIwB5/wcAfz/0PAoX8cAn/IgQFC55dBAoXxFILtC/grBGgL5BYIoAGA==");
|
||||
hiragana['MU'] = image(58, 50, "AAV/4AGEj/wAwkH/gGEgP/Aod+Dgv/wAcEj/gDgkH/AcEgP+Dgt/Dg3wn4mBHwYGBDAIyCAwP/8AGBAoQODh4GC/4sBgYGD/AcCAAO/IQQcC4IkCDgI7Bj5YBg//w/8EAIjCwIEBv/gMQPgLAMPFYP//h1BgZpC/4LCNwIxB4YoBFoIxB/AjBNIMH/v+n5UB/4qBn/fIoIJBv+PLYUPQwPhOIUD/gvBGYMH/3/BAX/457CBAP/84GBDgIlB/YGBCYJwB/qECDgKREwBCC34YBDgfvLYP+HIM/+YYCIwM/MoIYB/hGBMoQEBz4nBKQfDAwODGQXwKQQMB/P4j4GBAQP+ngtBUgIRBg6aBRwKiBwOAf4TNBAobjCAogAEA");
|
||||
hiragana['ME'] = image(57, 50, "gEP+AGEg/4AwkD/gGEgP+Dgv/Awt/wAGEn/Agf/BIUf8EP/40CHAMf/4tBAYP4AQImBCIP8n4GB4EH//+AwXgEwP/v4CB/EBAYIPBg4jBAwX8BYJFBCQRKDFYIGBJQJxBIgUfAQIrBAYMPCAIfBBQR8CAwR8DMAZ8Cv4GCGIQGDGIU/AwR8BAwKqCWoU/FoS1Cj4tCHASEBWogGBUAQKBAwItBHARpB8BlBBQKuCAQIKBO4SqCBQX8AwX4h/9/wGC/kP/n/DYSlCv+P/ArB4K+B4/4SIV+j/jWIX8n0P+JSBDoMOMwJWBAwOCMwM//ZOCMwI4C75nB/5bC45nBv+DAwPhTgXAb4PAoCfCQQifBYoYAHA");
|
||||
hiragana['MO'] = image(60, 50, "AAX//4GEv4HFj4GB/wGCg4GB//4AwMBAwX/4AcEDwcPAwYWBgYGDCwQVC54tCCoX8F4PgFYP4CYI+BgE//0P/gaB/ARB4F/4ApBwAVBg4OBj/8EgITB4AiB4InBBwQgBCAIOCPQPjD4MPJ4MH/0/+ALBwARB84kBBwQ0Bv/gBwc/+5bBj5tEHAR8Bn5lBBwInBBxY2CBwcDWIQOEGwIODJwIOFIoRKC4CNCBQP3AgKwCDIIOBKIQKB8/8IQJgBj4OB8E/MAfD/ytBEgX8J4KeBZwWDIgJCBCoP4ZgIzCAYIqBeYRQB8DnCK4gGBGoIDBwAyBF4IKCCQWBAwIVBEoPgF4RFBg/4F4Q2BAAQOBTwIADHoQADbIQAIA");
|
||||
hiragana['YA'] = image(54, 50, "gEf+AGEv/AAocB/4MEg/8DUv///Aj//wEDAwIcBAwMP//8BgIGBn//+IFBAwICB54GCDQQAC/0HAgXAn45BD4IDBn45Bv4MBAYPgGYJKCFAIbB8EAgf+DQRbEv/4LYYaBOQU/4EPCwIhCCYJrCgf8CYkP+BlBCYQaBv6GDOwQaECYIaEKwIaD4JWDgP+CYIaCg/4NQYTB8Z+BFwef+4aCMgN/74aCn/z/zXCIAOH/IaCh5CB44aBJoU+a4QyBwFwDQLGBCAOBX4adBGIJMBRIQaBUYI4CDQJnDFYJ7EDQKzCDQYECAA4");
|
||||
hiragana['YU'] = image(52, 49, "AAMf+AFDgP+Bgk/8AFDgYMM/gkD/4AC+EBAof/BkA5FhEAg45Cg/AgF/AQMBBIMP/4DB//gE4Xwn5dBn4GB74IBgY0Fv4FD8AfBAoYfB/gbBIAIiBg///A7B/+A/4rBCQIxBBAISB/ghBCQeBEoIMBCQI0BBgQSCDIYSB54MBgIlB+AMCj0H/0PBgIABHQQMBOgP4BgZBBBwTDCMYIMDKIIMRWQQmDAwUMYYqyBAoaxBN4IMEV4QMCcggMBWwbZCAweA");
|
||||
hiragana['YO'] = image(55, 50, "AAMHAwsP+AGEn/gAwl/4AFDgP/BgkD/whF/AGEj4oFEIsA/+AEIgoFg/8EIooFJQ3/JRcHJSgoGJQxEEg//FIkfAws/Cgv/AwUGJQX/HwMP8AoB74GBj/gh/+IoU/4BzBBQJBCJQIKBNQRzBv+AWoIIDJAP4SoMBIgIkBOYMDHoKTBAIIRBXgQBBB4IfBEIQYBFALgCCwMP/iVCJAXwJ4QfDcAX/4JRBSoRvBEIZ2DcAQGCFQIhBPoIYBcAQGBDAJqBCgQ6Bg7rIAAY=");
|
||||
hiragana['RA'] = image(48, 50, "gEP4AFDj//wAFE/gFE/4TCn4FBBgQFCBgQRC//gBgN/BYUP/EBAog3BGIIFCgH/BAIFCh4FEgQFEBoXwAqsfAoIuBAoROBEwIFBIwP+AoPnLIWALwZfBNQf/+AFE/AFBEIM/AoR6Bh/8OoIzBg4FBRgQFCL4UD/wlBAoikCAoM/W4QFBj5dCAoMGAohpDg4FEHYJ1EAog5DDgJWCb4Y/Cg7RDaARFCAoZFBAobiEeoruCAoQtCAoI+DAAgA=");
|
||||
hiragana['RI'] = image(40, 49, "ngEDn/AAg9/4Ef/AEBwF//4EBwP//4HBw4EB4F/x4EB8F/z4EB+H/n4EDAQIjBCwUPAgUAAgX+gEH/n//gEDHIMDAg3wAgP+AgvgAhBeBAhmAAiJ3BAhf8AgRUBAhBXBAAJtBAgSgCVgRcBAAJXCEwIEDj5SCBoJDCBAKSBBASSBXwKICAgQmCAgIcCv4SCAgI0DeAY=");
|
||||
hiragana['RU'] = image(51, 50, "gf/AAXAgF/AoX8gEPBgeAgIFD/EAn4MEg4FD8EACQoACn4lBAAUf/4FDDYOAAoQuBHwIACv/wDwgkEh/+DwoFDDw5ECDwRLDMwg5BLIZMBNgh/FGgIeB+AVB4AeBEYJmBBAJQBDgPBOocf/AoCVIU/Kwc/+5WDg/+Kwl/5/wh4mBh/4/A2CFgMOAoJDC8GBMgUHGAJQCCQKpCBgISBgf+SQMPCQN/4H/4YSBGIIwBCgMBDoTMCn/AEIROCLoKFEAIJvBTwZvCTAarFNIQFCXASyCYoYxBAoYAEA=");
|
||||
hiragana['RE'] = image(56, 50, "gEf8AGF+AGigP/wAGDg//GYQGBh//C4M/AYICB/AGDv///gGC+P/AwQKB+YGB/wNC+//w4GDBYMDAwn4AwQ3BFQIGF8AGF4AGFgAGEAYMDHwIGBAYIGDn5XBAwhlBAwd/Axh6CAwSPBAwMHAxEDAwqdBAwidDAw5IBOoQGDU4QGDUAIGE//fAwufCgrmCh4iCAwk4nwGE/EcAwbSBjAGFegReCUgIGJOYIUEQIYGCIYOAAwPgAwIAIA=");
|
||||
hiragana['RO'] = image(50, 50, "AAf4gEB/4AC8EAv4FC/kAj4MDwEHAofwDAgSBDAoACn/+AocfAokP/4FDE4OAApED//AAohJBAAI5BAocAIQIFEHghFCD4QFCBoU/KIQMBNQZ9BOAhOCQYYFE/B8CE4QFBM4JGB4YuDj/7AocD/xIE/+fP4c/84FDh/8QoZyBj5mE4aFDn5yEDAIFDGIIFDIgIXDDwKREv4eEv4eBiAFCDwMH+A8BIQLnEEgLnDSooqBQYQFCDgQ2DAoolCJAgAD");
|
||||
hiragana['WA'] = image(51, 50, "AAV/4AFDh/4AocB/4DBj/ggE/AQMD/0Ag/8DgWAgH/AQMP+ASB//AgISBAoIDC4Ef///+ASBh4FB/4SBgYFC+E/4IFC/8H/F///9//g/8f/3/x/+j/nAQPwv/j/H/wf+I4N/KAJlBv+P9/4MoMP/f9/xlBAIIqBwAUBn/vFwIdBg40BNIIOBIIR7B+BbC8B7BKoX4uAyCAwM+GQX5//f8IyCn/z/hHCK4N/4/8h/8/4EB/4lBF4P/z5wB8f+RYJjBPoPAFwO/BQP4IQX/wJkCTAUfVYf4gf4BgS4BbQRiCcgbSCAAILEcALkCAAM/DoYeCC4ZLBfoIeD/ASEDAhoBAoYlBDwcAg/ABggAEA=");
|
||||
hiragana['WO'] = image(50, 52, "/4AE34FE94FE/YFE/wYYGocB/+AAwd/8AFDn/4AocP/gFDgf/KovADAnwDB43B45EE+IFE/F/KAkfBgmHAonhAonwDAn8h4MEN5X/N4l/N4k/KwkfRwgoBDwcHOohoBOoYFBEgY2BEgYFBEgYFBJIYXBFQYpBFQZ3CAoIWCKoQQCGwQLDHgR8CAoQdCAoQvCOYYFFn5gENgKREbYgAGA"); // XXX there's no WO in hiragana, so we fill it with a copy of the katakana char
|
||||
hiragana['N'] = image(54, 50, "AAVgAYUP8EHwAGCv/Av4RD/8D/wFCgf8g/8DQf4j/4AwU/8E/+AaDwF//4VBgIfB/4GCD4MPAwcf+YFB/4jBn4FC/4jBAof/4AYC//n/+DBYeD/wZC/f/FgIrCGIQsCKYU/444CKYP/z4xCvxOBv+/8EBQQP4B4KFCCoJeCNIYPBQgQKBj53CAYSbBCYQDBHgJbCTYUDOQZHBM4QTBTYX/GQQxBP4Y8BDQRGBTYY4Eh5MDHgZTDAojdEbAYGEHgIGEv7/DHgIhFfAh1EEIg8GEIg8GTYYhDHhYAF");
|
||||
/// /////////////////////////////////////////
|
||||
|
||||
const katakana = {
|
||||
A: image(56, 51, "v//AAfwAon//AGF/wGT/gGM/A3F/BDEn/wJQoGCj4RB//gAxUB//AAwcDAwsH/+AAwcP/4tCAwMf/wGEn/8Awl/JYYGBKQkf/I9DAwJgBGwQGDGwRlBAwJsE+42DAwPzGwYGB+J7EQIIvDQIIFEAw5DEAwRDDgCIEAxCPBKIcAR4IhER4hnCLAg9BLAgoBAwgoBcQiCBMwj0BHogGBHogGBfoooEQQREFEIgGBAokAhAGFA="),
|
||||
I: image(54, 55, "AAkEAws+AokB/wGEg//Awk//gTE//gAwcPCYt/CYkDCYsfCYv//A0F4A0ECYg0BCYggBCYn/KwhBBGgl/EAgtBEAgMBEAZOBEAgMBEAYZB/+ABggTDBgQnDAoIaDJoIaDFgIABDQQFC74aBBgX8v4aBEwWBDQQgB/EHDQQ6BwEfGoX/+AJBDQMDWAKMBDQMPAQIaDiBFCPAgaDU4hrDDQiuDDX4acSAIaCA="),
|
||||
U: image(52, 55, "AAMP/gGE//ABlH/AAnvAon+Bk5EDv/vIgcHBkHPBgZwBBgn/Bi8B/+PBgcf/AMFw/wBgYEDgED/6qEv4MEKYK3F8AFDj7EED4LREv/4CQn/wASEFginBDAgfEDAIfDn67BC4YABH4QXBCQcHZoQkEEoYMCHAYlBFYZEBLwk/MgpQEAAw"),
|
||||
E: image(58, 45, "h//AAfwgYGE/0AAwn/wE/AwngDgv4DjhDCv/wJQkf/gGEg//AwkB//AA4gc/Dn4cjbAv/34GF94GF/YGF/wcjwA="),
|
||||
O: image(57, 54, "AAcf+AGEh/8AwkH/wGEgf/AwkB/+AA4n/4AGEv/gAwk/GIsf/A4P/4AE+F/Awn4n4GE/kfAwn+h4cFg4GFwYGF4IGFKwYFBMQpxFAwJxEAwJxEAwJxEAwJxEK4JxEAwKqEMoQGE/o4En/8HAl//iqEAwKqEv/+VQgNBVQgNBcYgNBcYhLBcYhSCHAQKBAwI4CAwY4CD4IGBHASxBAYI4CAwY4CYwIGBHAQGBD4I4CBIJfCHASmDHAV/PYQ4Cj5QCHAUPLwQ4CgQGCOIgABOIgABHAIGEAAY="),
|
||||
KA: image(54, 54, "AAMP/AGEv/gAocB/+AAwcH/wTEj4arg//AAf+j4GE/F/AwnhAon/w4aZHAMP/hTEn/wKYn/4BTDgf/KYgQCDQYQCBIQQDBIQQCBIc/DQouCDQQuCEghJBEhITBH4RTBLoRTEBIJTGCAUPNwoTCDQQWBDoIuCj4TCJIX/CYQ/BZQInBH4U//0HwBTBGgPwXAXwh4PBXAXAv4PCZIIgBEYTJBn5SBDQXABAIzBCYJcCDQXwgbOCAwIDBQgI4CgEOJwIADkAGFA"),
|
||||
KI: image(58, 55, "AAU+Awv/4AGEn/wAwkP/gGEgf/Dkk/CAc//4ABwAGBj4GC8ATBAAf4h4GE/woBAAmAAwvgFAYcIwAcD/BFDFARFD/kBIoYACv5FBAAcfRL94DgkfHgf/95EBD4RgDD4MHLwf8AogAd+CPFGwiJCS4XHJgSGB8CJEkCJJUwYABg5pDD4amTNwKmXYbgcDLoY="),
|
||||
KU: image(55, 55, "AAMHwAGEh/8Awkf/AGEv/wAwn/4AFDgf/EQkH/whF/4ACAwM/AoQQCBgY5BgIGDHIMHAwY5Bh4GD8AhEIAQFDIAIhBBIJACEIJpEj45CNIV/NgRpBDQIrBEoPgDQJlBEoQaDEoV/RwUP/wPBQ4Uf/gPBQ4QsBKAKSD8BvCSQXDDQYYBNYIaCGYIqBDQU//kPXoYYBj5QCEIPgj60DKoMcWga7FKoYABKogaDbojPBbojMDGob/ECYJBCbgYaDE4IaEPoIaDEAI1EbYQZECYgtBCZQGCLol/KwxxEAwJqEgIMFgIZEgA="),
|
||||
KE: image(60, 54, "AAMcAwsD/4HFn/wBxl/8AGEg/+BxkP/gOF//ABxcB/+AA4kf/BCGAAZOBv4HEIQIOGAwgOBh4OFGYIOFn4OFEgoOBAwvgh52BKgYDBOwJUDv5nBBwY6BAYM/BwIKBJgJjBBQSbCWoQVBRgK1D/4oDBwJJBWos/WIS1CgIVCJoRGBWowCCj61HYgpRCdIjEGLgTLEIwTLEfAv/GYqtBEghyBGYjoCAwwkDAwQVEYwYjEHQt/CopeBQgQOEIIgOBPgxeFgZ7FA"),
|
||||
KO: image(49, 46, "v//AAYFF34FE74FE94FE+4FE/IFE/gFE/w0Dgf/AocB/+AAwf/4BHE8AFDn/wAocf/AFDh/8AocHGH4w6YZf7Aon9YYoFEejBhEAAIA="),
|
||||
SA: image(58, 53, "AAcD/wDBg4DC//AgEB/+AgE/+AKBv/ggEP/gGBj/4DgP/DnU//4A34CQ+DAIcEDAIcDDAQDDDAYDCDAYDD/4cDIgJADAAUfIAQACh4jCAAUHD4QACJwIfBAAQtBEYgGBI4QUDFQkP/4qEVYQvEAAIxCEIK5CBwV/AwsfAwocCAwYcCJogcBNIp3F"),
|
||||
SI: image(56, 52, "gFwAwt+Awv/8AGF/gFDgP//4GGCocDAwIVDBoX/wAHCn4VFg4GB4AxEAwsfAworBEQYABv4GFj4DCjgrCBQYRFn/4JQfAIgIGD+F/JQcD/gGBMARQCOwcH/wNBCoUP/0PAwIrBj/8OwQGBn4fBGIIGCAQIlB+BcBAQKvDBIQRB8AfBIQUH4AXBP4RXBGgJmERoJsFAwv//yaFbYghBQIYaCeAi9FPQTZGdxKFCFASECFAZPBEIgNCJQaZEAwhDDAwRJDTAYGEQAiQBPIgAGA"),
|
||||
SU: image(60, 51, "gH/AAYGBh4GD/AOG4AOF/gONDo+ABxAACgY7CAAd/+AGEg4OG//gAwkP/wGEgJCCAAcfKIQzEIQIzEIQozOj4zFEgIzFn4kHGYv/M4okIGYt/IQqXBFghuBHYs/bAY6DCwrJECod/HgYVB8ZLEcoMfLQYECCwYVB+BTBCwT7CCwYrBAYIKCCoQDC8BXBEIQSBNoQVBBYP4EAIoCOQPHCoYTB/xdBIwQ8B+6SET4N/dYn/4aCFFgKRFgC+EgPghivEAoI"),
|
||||
SE: image(57, 53, "gEH/wGEgf/AwkB/+AA4n/4AGEv/gAwk/+AGEj/4AwkP/g4JjA4EBQQ4D/4DD4E/AwIuBv/vAoP/FwILCAAIuBv4GEBgn//wFEAwITEh//CgfwAwMfCIRGB/4BB/5xBAgJTBIQQGBwP/75CBAwOAD4JCBAwRmDDIKYBOIQGDOIQGDOIQbBAwSqBAwiqBAwiqBDYg4Cv4GCHAUfAwQ4Cg4GCHAUBbwbjnHAgADcYYADUYQxEEYq6CVwbDBdQi6CZQYqBAAZcCAwY1BEYi5DAAQ8CegfgA="),
|
||||
SO: image(52, 52, "gGAAol8AYUD/Ef4AGCn/3/wFCg/+v/wAwV/8//Bgk//AMD8f/FoQMBj/8Bgfg//gBgcPFoYMBFocP/kHFof/4AtDBgMDFoYMBFoYMBgIIBgADBwAtDj4dBHQQMCFoYqCHQQqCFoc/BIIPCCwQtDKYIpBB4IwDIAQwCh45CBIVAFgSmDFIaaDOIYfCVgYfBRYYfCTASTCUoY1BQgZPCD4l/D4kfH4g4BH4YYBH4gFBGQd//4yDBYIyDn4SEJQIlEBgRXEHAg+BFYZRGZYQADBYgAG"),
|
||||
TA: image(55, 56, "AAMHwAGEh/8Awkf/AGEv/gAwn/4AFDgf/EQkH/4oF/4ACAwM/AoX+FAQGCHIMBCYY5BEIIAC+AhFIAIhDHIQFDF4IhBJQMHF4JDDNIUfHIRpCv5sCn/wDQJsCDwIaBEIIKBwEf/9gOAQaB/gbBFAIPB+YsC/AaB54RBFAIaBAIOAEoJvBOgPh/+DNAJWB+//DQPBQIZyBM4f4LQSQC8EPKAIpBFAMPPgKKCgEcYIZwBiAGDbohwEZ4bdEFILxFf4ghBXwLjEDQhLBCYoaEE4IaDdIQaDBgLBCDIRQENYYTIewRkEAwJCFHYicBOIkAEAhDBS4IAJ"),
|
||||
TI: image(57, 54, "AAkGAwsfwAGE//gAocP//wBgn//gEBgIFBAAIeBAof/wAYBAwkHAof+gEDAwf4E4YAB4AGBv4TDAAM/AwoxDKQhABLQwiCAAV/MIglBMIglBHwRwDNARbF//3Awv7Awv9Awv+Awv/MQQAD34GF74GFKAUHOIYABSAJxGaYp4Uv54FP40/P4oGHQwQGKKgt/AwrUEMIQGEVYIGLg4bMFII+Fv5TGNAsPQgsHTIoAG"),
|
||||
TU: image(54, 53, "AAMBwAGEj4FEgf8AYPwgFgn/4BIP/g+Av/ggEP/n/gP/4EAv/v/wQBFQP/z/4CAMAg/+DAMfEIICBDAN/FgN/8YYBBAIaBw4hDDQIVBAYMAn/wDAIhCCwIhDCwIBBwAIBHAIYBEIQYDBAIuBwAjBFQghCJgQhEAIIhDEYQPBh5HBM4IhDQQQhCwYeBCwMBCoSPB/0CIQQhBAQKWDvytBCYTBDv5tBZYYTCAAQTCAAYTFHAITEj4TF/4TEh4TFv4TEg//JgIMDMYIMEO4ImD/53BAAM/AwIsEEAgFBEAZNBIIgTCFocfJwo6BPgpHEgZAEgEOAogAGA=="),
|
||||
TE: image(57, 51, "h//AAfwg4GE/kDAwn+gIGE/8AAwuAv4GE4E/Awngj4GFNWJNF/gGF/5UF/+/AwvfAwvvAwv3Awv7GJn8IQV/4BJEv59Fn/wAwkf/DJFEAYABg/+AwjJBAxbQBwAGFH4gGBH4gGIIwgGNG4IGEg//LYjyBAwiyBAxc/EQoGGFIJTLdYJvEgF+fIsYAwo="),
|
||||
TO: image(42, 54, "//AAgU/+AECh/8AgUD/4U/CgYPDn//wAUC/4VCCgIlDAgIKCCgIKCCgP//wUD//gCgQKCn/zBQQ+BDYP8CgMBEAQBBj4KBKYIKC54yBBQP7KYIKCG4QKB35YBBQIUCGQPjNAUD+BXDnB9Dgy8/CicAA="),
|
||||
MA: image(57, 50, "/4AE/l/A4s/AwvfAwoAN/YGF/oxGHokf/wGLh4GN/4GSg4GChgGDwARBAw3gAwv4Awo7BAwn/4ACBAwIKB+AGDgJtBAwcAUgOPAwYLB94GDgaFCAwTBDAwcfAwoyBAwgyBAwgyCAwgcBAwgyBNgL0ENgIADn6oHDijhFW4wcB4AGDKwPwBwl/fwzUJDgZOFgAGGngGFhADCA"),
|
||||
MI: image(52, 53, "gPwAwkf/wFDgf///gAwU/AwIVCBgX//AME//8gEHAoQGCBgYGCv4GDFIMPBggoE4A2CCoIuCAweAAwc/BghYBMwswNw0PNwkBGAIbEG4gMCOoYMCOoQMDAwRnE4BYDKYQTEKYRuCKYY8GgCjDAAV+LAtgcTMDbYhTCHobICBwbBDBghZDZwmAZoYGCAogGBCYgiBEIidCBwQ2DS4QMCVYT2CSAb2DBoLpFn72EdJAA=="),
|
||||
MU: image(59, 54, "AAMDwAHFv/AAwkf/gVF/4VG8AGEh4VHFgoVPFdZBdRogVBgP4CokBFogVBn/wTIkHEwYrCv4ODCoMP/wVDFIP/JYQVCBwgVBGYLICCoTIDCoQCBBwQhCn5RCCoR/DNoZCDDIRRDCoQODg4+CIQYvGCoZCCCoZRDAQV//4SBRAM//4ABwEfAgQAB/ARBAAkPAwvxAwv+Dgv/8YGF/gkD/xCB543DH4P5AoaBBewsAvgGFhgGFAAQ="),
|
||||
ME: image(55, 54, "AAcB8AGEgf/AwkP/wGEj/8Awk/+AGEv4iF//AFAuAAwcHFAsPFA34AYNwFAQvBgICCFAUHCAIoDDwQoDn4DBKIf/MYIoCDwIGB/5RBAwWDKIYGB456Dv//75RDAwP/JQQmBAwJ6Dj4GBOYYGCOYcP/5zEg//OYgGNDYw3BAwgvBAwaABAwgaBOARZC/wGDOoP8MQI1D+AGDFwPAAwJaBDAQNCJIc/AQJsBTYL3COQc/4ATBXoYdCSgU8J4SNCmCNCNQqoDAwQuBAwgFDFAITEAwK1DAAKZEAAIMFAA4="),
|
||||
MO: image(55, 49, "j//AAfAv4GFAon/wIGFgYFE/0HAwn8h4GE/AvF8A4Bv4DCAAQzBAocB/+AAwYxBCYkH/wGEh/8MIv4Awk/+AGEGyJfFAFP9AwpOBNuikeAwxfEHoLpFNoZACAwZABIgIACJYYABIAYGCIAYwCHIoABA="),
|
||||
NA: image(57, 55, "AAV/8AGEn/wAwkf/AGEh/8AwkH/wGEgf/AwkB/+AA4n/4A4rGoIAE/IGF/wGF/9/Awu/AwvfAwvvAwv3AwpQCOOqqEWLV/H4pGGn5GFAw0fJosfJooGGn4GGKgq6BLQoGEg4GFh4GFPoIpEDYIwFv5MFLQ4GFg6EFgaZFAAw"),
|
||||
NI: image(56, 43, "h//AAf4A25+/AH4AuWggA5A="),
|
||||
NU: image(55, 51, "g//AAcAh4GFj4FD/0An4GD/kAv4GD/EADQnwgIGE8EDAwnAAwuAIIgvBAAcPF4IADn4vBAAd/8AGEFAIDBAQIsBFAMDCAIoDh4eBj4oCj4GBFAd/CIJRBgBZCAQIlD/+HQIIGD54oCNwZKDPQZPDOYRdDOYqmBOYi0BOYjCBBogGGYQSAEAwimDGATdDAwQTBH4JFBLIP8AwYTB+AqBAwITB4AGBE4bADBIJyBUIJ6CVgXgJAQzBg+BAoJkCgxcBCYRIEPArlEH4YGDO4ibBeQs+AokAsAGF"),
|
||||
NE: image(61, 55, "AAX/4AGEg/+Bws/+AGEgP/wAHEh/8Cwt/8AGEgf/Bwsf/AMEAAYnBj4GDHwQOEDAMHA4hVBn4WFJIIADHwMPA4hgCAwZkFCQKCGBwpHBPQwOFFAJyGBwt/BwozBBwpwDGYiYEEgP+iAkF4IPDCoP8j7WCUAXhbwYVB/4RBU4n4QISfD54vBS4f+FASPD+AEB+AFB/IjBFIPnA4LzCGAfAeYIjBGAP4eYQCBwZuBeYUH/EfIwJRCAoIDBg6ACnCmDR4oqBDIKfEHgKuFS4g5CBwo8CWwqOCAAQ8DcYg8Vn48FAAo="),
|
||||
NO: image(47, 52, "AAcHAokP/gFDj/4Aod/+AFD//gAgUB//AAoUD/4oE/woJn4oLEQYoBwAoIh4oEj4oFJZ8HERU/EQhFEDgIiDH4JFDh4iEH4t/NAYcFHII/Dj4cEv4/DCwIcDCwIcDCwI5DCwhEBHIYQBKwf/GYYhBCwc/FoYKBFoYEBFoQKCE4RrBE4YFCHwQyBHAYnBJ4YFBcBN/AgcAPgYABA="),
|
||||
HA: image(62, 52, "AAP/wEH/gGCgf/gE/+AHCh4MB//AA4QMBCIQeD4ARCDwv4Dwt/8AeEgI4BDwkH/weFj4eEAgIeF8AeEAgQeEAgQeEAgQeEAgQeGMggeCMggeCQYiACQYYbCDwgbCIogbCIoZZDIoYTCMggTCEwn/CYJFDBYZFDBYYmDv4LBEwYDDg4aCh5JCDQYiDaIQWBNAQ5CMAYLDcgYmCCwgqCGIYTBFwL7EJIIWEAgPgh4WDNAPACwgMBCwiHB/wWEFwV/CwZVB/YWEDgPHXgYuBDwLbDKQPwh60CGwWAngGDgAFBkAHEsAFEAAQA=="),
|
||||
HI: image(47, 51, "//AAgUB/+AAoUD/4QDg/+AocP/gFDj/4Aoc/+AFDv/gFw8BwIuDj+DFwf/FwcP/4uD///FwQKB/wuBJwIFBFwM/AoP8//PAgP/+IDCAAJdBAAXwg4FDEoQKCIIIgCLoQFBKYV//5qDB4aMuF1YFDFwIRDUIQAC+YFE8YFE44FEw4FEUgn+Aon8WwhKBXggA="),
|
||||
HU: image(49, 50, "/4AEv4FE34FE74FE94FE+4FE/YFE/oFE/w0Dg//AocD/+AAoUB//AI4ngAod/+AFDn4FEj/4Aon8AocPAokHHgg2BHhYFDHgJCLJBZCEAopIFAoxIEAoxOEApc/AojSBbwplEAoZxBAocPAojICBQhBCGYIFDBYRZCa4P/NYQuCPoYFBSoZGFZYsPAgYABA="),
|
||||
HE: image(61, 43, "AAMH8AHF/4HFh//wAOF/wOG/AHEv4eFg//DwoOBDwgOCDwk//YeEgf/x4eEn/8n4eDgP/4AeEj/8DAIeCBwPgLgkfDYIeECYQeDh4LBIwIeC//wDIIeCBYJdCDwV/BwIwBDwIOBCQYeBn4pCDwRIBIAQeCMIJPD/AOB4CED4BhBMwf/MISbD/kHPovwj4ODDwV/UYhYBKQJ2DRoIGDHQINEcARCCWYgGEDwIOFgb+FDwL2EDwQGFIQoeCBw0YA40AA=="),
|
||||
HO: image(61, 54, "AAV/8AGEgf/Bwsf/AHF//AAwkH/wOFn/wAwkB/+AA4kP/g8Rg//AAngv4HFCYIAE/EfA4vAAwv+Eo3wn4HFwAGFJwZ5UgfAPIJzDn/x/+PEgR/BAoJzDP4N/8JzD//D/6KDFYI8BCwYrCCAItBPQOH/wWDCgIQBCwf/4P/wIWCCQIBDWgYBCZ4KJBE4LPDEYInBh5sBBgKLBNgQ0CJoIWB4ACCBgIiBBwP8EYU/TQLXBHQQECFAI8BCwIqB8DzCDYMPAgQbCMoI3BF4IRB44OBWwQUBv4TBJIV//InBHgQCBw4OBHgUH/EfNgKOCj0A3BsCQwNgeaSdCABA="),
|
||||
N: image(54, 50, "ggGFngFEgP+AwkPAws/AwkB/4GEh4GFn4Gaj///gNF/AGF4BEJAwITBgOAAwQTBh4GCnwJCCgVwLgRwMHAgTBHAgTGv4TEgYTFMIITEMAsHMBY0B+ClFCYiPFEAITEv//OIQMCTg3gBgggEDIIgDGYIgDMIJVDDAIABIIILCFoYYCJwZ0BHQgsBBgZnBBggnCKgYhBMIi3FgAFFgAA=="),
|
||||
WA: image(51, 50, "/4Ay4A3E/AFCh4GBAoUBAoPgAwU///8AoUHBgOAD4nwAoUf//+AoUDGRYSBGQYSCGQd/94yDh/9GQZFB34yDn/zGQcPAgYSCG4YSBC4YSNv4SKJYJwDLwISEn5QDS4QSDDAJjDDAJ2DGIJ2DUYQ+DQYKcFFYYXBDASOCGIQFDGIQRCDwTaCG4YFBEgbHHN4hiFg6HEA="),
|
||||
WO: image(50, 52, "/4AE34FE94FE/YFE/wYYGocB/+AAwd/8AFDn/4AocP/gFDgf/KovADAnwDB43B45EE+IFE/F/KAkfBgmHAonhAonwDAn8h4MEN5X/N4l/N4k/KwkfRwgoBDwcHOohoBOoYFBEgY2BEgYFBEgYFBJIYXBFQYpBFQZ3CAoIWCKoQQCGwQLDHgR8CAoQdCAoQvCOYYFFn5gENgKREbYgAGA"),
|
||||
RA: image(51, 50, "n//AAcHAongAon8j4GEwYFE+F/Aof+h4ME4IFE/BYr+4FE/wFE//fAon7BgpYE//vAon9CQo3Ev/gAocP/gFDgP/wASX+ASJgYSFXwJ2ECQivBDAoSEWIs//wFDbYIrDAoI+DAoIYDQ4IYCFIIABDALlDGIJhBewS/EJQQYCG4YkED4QFDD4JJF4AFDA"),
|
||||
RI: image(43, 53, "AAf/7/4AgMf/f/AgMD/9/8AFBv/v/gEBh/9/+AgEB/+/+AKBn/3/wEBg/+//AFX4q3v4qDh/8FQQPBz4PDAYQvBEYQvCEYI/CGYRPBB4cfIYQpBB4cH/5TCDwJjD/4kCn4EBCgN/AgIUBDoP/FIJHBAAIyCDIYjBIYYaBQ4QaBJoZHDAAoA="),
|
||||
RU: image(61, 53, "AAUH/wHFn/wAgUB/+B/+AA4UP/gBBCgd/8ABBAwUD/4BBBwcf/ABBA4f/4ABBHQg8FHQI8/HksYHgwYBHgkPF4I8EvwlCHwOAg4gBEYI8CCIQjBHgITBCIP+HgU/CwIRBDAIgB4AMCAgMfEAIMBDAIOCBgQYCIwQMCPYJTBAQI8BBwUHEoN/8P/IYN/+AvBj4LBBwOAj/7BwZGB/4ABBwXAAQIODM4QOFHgIOC/4OBh4OCAYJGBv4OCn4OBHgJKBAYJkBIQISBaIYhCCwIOBSoTqBJQISBeYUHd4U+bYUwcAYAKA"),
|
||||
RE: image(51, 51, "//AAocf/AFDgf/CQl/8AFDh/8AocB/+AAwc/+AFDg/+GX4ECgwyEgPgGQk+GQkP+IyDC4IyE//3GQc//gyDh//GQYYB8YyD//4GQc//wyDDAOBGQUH//gGQRvB/BlD/4DBGQU/CwIyCj4YBMoQkBBIIyBBAIYBGQIkBDAIDBGgIiD+AFBGoIyBv4eCGQIABJwQvBAAJnDEgTLCEgY8CIYLLDEgZVCAoZuBb4iaBfAj+EgE4AokAA"),
|
||||
RO: image(50, 47, "/4AEn4FE94FE/YFE/wYF34YS4A1BgIYB+A8Cv/v/gFCj4YBAoUHDH4Y/DEbglDBQ8CAAYA=="),
|
||||
YU: image(59, 46, "gP/AAX+A4M/A4fggEHAwf8BwIGD/4GBj4VFgYVGv4HDwEAh4GD+A+Eg46CAAf/4AGEj/4Coo6CCqJFBCot/KAIADh5QCQAhQBCrM/Myk/M3JQGh5QFMyIRBAH6NB"),
|
||||
YO: image(50, 49, "v//AAefAonnAon5Aon+DDA1DgP/wA8E8AFDj/4AocHDFZjfDCJjxDD5WE/+/AonvAon7PgoYX/g3DAAQ"),
|
||||
|
||||
};
|
||||
const hiragana = {
|
||||
|
||||
// hiragana
|
||||
|
||||
A: image(52, 50, "gEB/wGEn/AAocD/gMcg//AAfgv4FD/wMYFIRNa54HDgYyCBgYsEBgX/+AGBHQYpBCQQaCh4JBJQPwgIdBBAP/wASB4H/j/8MIP8j5fBBIP/4P8gf+j/7/hVBj/jA4PH/C/Bn4RBv8Aj/3/Ef55FB/9/wI+D+/wj40BHwIWBL4QJB+BFBwAmB/4MBD4M/94MBD4JAB/4cBNYN/BgM//AsB/n/z4bBQgOHX4QVB/B3B/CQCAQTSC8BFCB4Q4CB4UAgIIBRQOAXojREn/gaIgAC"),
|
||||
I:image(58, 50, "v/gAgUggEf/AGCnkAg/+AwU/gEB/+AAwQZBDgcP/gcECQIcFCQIJCCol/4AGBgYLBj/wCokHCAIABFAIQCCon/DgQECn4cDCoItCAAI+BDggVCLoZeB+BgCCocPPQZUBwZdDJAQcEGAIcEGAIcEGQPDDghIBDggyBDggyBx4cBjxIC8aaCCAIyBLAMDM4IyBSARnC//HUIk/+IyBCASdBLAJKCGQOf/kDJQV/GQRKCJ4XgEYRPC/CoCDgOHNwl/8P/84jCDgM//5HCDgMHAwIjBgP8DwIsBQgYVBSQgVBaYZnCTIgtBbQhDCUAYkCfwYOCGIgAHA"),
|
||||
U: image(46, 50, "h//Aoc////8AFBAgIABgEDAofACwIAB/wWD//4CwgdBCIeAFQUfCwIADCwIAMj//+AEBv4tDAgQLBHAYFBAgf/8YFE54FECwRTB/wkCAoP7IAd/OgR2CKwcBQ4kH/hMEJYQcC4AWIh4WEn4tJg6EEj6EEVgIQDE4l/CAbABCAZqBBQgQDBQIQCXwIyCYYTIFeIhlCBQjxCLIQWBMgbdFvzYJ"),
|
||||
E:image(55, 50, "gF//4GE/4AB+AFBgIGC/+AgEDAwYNBg4FC/wGBh4GC/gGF/ArFFIQAD4BRVn42FLAIGEJQYGBLAhEBLAhEBLAf/8ArDBIIyEj5fCRYZYEEgJYEN4JNFDQouFDQKcBFwYGFMIIGDLQRJFAwgaBOYQuC8Y2DFwODAwcP/0HXAc//EPcQnAj5LCPAU/MwR4Cv5ECPAQ9CLoUBd4auE/guBVwf5PARaC+5qCAwXnJwSXB//HI4QGCw5ACAwUHNIn+gj/HAAg"),
|
||||
O: image(54, 50, "gEB/0AggGCg/4gE8AwUf8EA/gGCv+AB4QaDv/wDQn/CwIaCgP/4AaDgf/wAaCgPn/4PBAAXv/0HAwef/kfAoX+n/4v4GCAgPxCYfg/4jBAAWBGwQ1BgEDJoJQCJoJRBLYcPCAJrCgEcKAaGEHgSGDF4QPCJYYxCHoYMBn5YDBgoGBDIP8FQKiBDwabBFoIzCv/gEAJQCMwWfKAIbBh58BDQMH/l/4IaCh/xTgIaCn/P/BrD/8/4CGD/i3BDQfz/gaDv/P+AaCCAIaEHQQaDv/hGoV4h//g4VB8JnBa4ePZYRkBBwKNCbwPwCYR/C44CB4BtBfgSaD8ACBYQQWBAAYA=="),
|
||||
|
||||
KA: image(55, 49, "gEH/AGEh/wAwkf8AGEn/AAwl/wEAhgGC/4CBngCBgP+AQP8AwMDAYIyDAYUPAwQ2CAwY2Cj/4gP/AAP4j/wgYGC/gGBg4GC/0/8EPAwsfCgd/4E/Awt/FIf/LgJmBE4IGCMwMf8JjBHwIPB4IDBgZmBv+DAYMHMwP/BQRfBOwIKCL4J2BOIQvBAgJxCGQIEBHAKPCCwIYDCwQBBQoRGBviIDIQJRC4AdCXAYdCKIcHboQ/CboY4BboghBboZKCFAYhBjAoDh/8nzME+CfBF4V/RgP/EgKVBwYGBFAMH/zIBFAQeBAwIoDboRRD4DrBJQUHAQJsDAAwA="),
|
||||
KI: image(48, 50, "AAMB+AFDh4FL/AFDg4FIn//AAX4ArpHC/xNEAov/LQgFCDgYAlF4UfPx8/g/8CoQbBKgQhCAoMDFAkHAoeAh4FEDgQAB4E/FgIUBwE/HwQdBn/gAoM+AoPAAoMMAohFCAqIpCgI7C4BEBI4oICAoZfE4C9BAob2EAoISCaQgACA="),
|
||||
KU: image(33, 45, "AAsB4ADC+ADC/wDBgf/wADMg//CYIDDh4DDD4UfAY/8AY34AZRDCh4DCg4DCgYbCgI/CgH/BgU/BgREBBgIQB8AMCFIRNDLoJ2Cv42DJwQdDFQIdDFQQdDFQIdDHYRkDgYhCgADDnwDChyzE"),
|
||||
KE: image(50, 49, "AAUB/0Ag/gAwN/wAICgEfBIIIBB4P4BAYPCh/wDAcD/gYE/4FBDAU/4AYEGIgOCDAQOBh//AAP+v+DAoX/7/AAof3+E/AoX9/gYD/9/gYFD/4YE/5QCGIJQDHYRvCJQU/N4JKCKAYYCKAQYWmAYEjwYEx6lDh/zUocDMgIYDv6cBKgUf/4yBBAMH/4eC4EBNQUfAQN/DYMPE4TjCAQQkCYgSJBDYLEBn7QCAQIbCE4UDDYP/PIV/CgLpD4EPP4UH+AkBAoIACCgIADh6LCAAMDAoYA=="),
|
||||
KO: image(52, 50, "h//AAX+gAFD//gBgn/BgvwBiWAAon4GwUBDIQACCQQFCn//4AFCg4lBCQc/DwYfBKQJdEDwYAB8CIihAFEgJJDIgQFEg5KEMgITEj/8D4hwED4JqEOIIfEv5eEg4fEFg0PHIwsEBigmFCYkOv65CJYPnbgn+ZgIAD8IMFewvgCYjRBE4IMDegQABIoUfAoK7HA=="),
|
||||
|
||||
SA:image(51, 50, "AAMB/gFE/+AAwcf+AFDgf+DIl/4AFDg4fEgAfLgIfCj//AFQzCn/gLJYMELI5mEh6GGBgUHGAP4CAQ3COYILCBgUDIgYZBAoYmBn5REDwPgQQPgDAIVBj4fBJ4d+CQI1CgeAXhgSDKoYSEQQp1GQQpFBawXwD4IGBg42BaQngBgRlDBgmABgjzBRYZDCPIYvCv//MQoACA=="),
|
||||
SI: image(45, 50, "v/AAgUD/wKDj/wAof/wAECg/8BQc/8AbD/4bE/AbEFgcHFgk/FgcBFgkPDYhIgFgIKDFh8eFgn+FgcH/4sDv+/FgUD/osDn/vFgQ2BFgcf+YsD/+fFgUP/gsDv/HFgSKBLId/8IsCHgIXBSod/EIIKBwIhCv/4h4WBAQOAv/+IIP8AQIAC4AYBAAIkBn4KDJQIKDCwYpBCwRWCAoJhDAoK1DAAg="),
|
||||
SU: image(52, 50, "AAUf8AFDgP+BjH/AYP/AAnvAon+BjJAUgf9BgZFB/4MDn4kEg4MFGIwMED4QME+E/+AyC/x0DFgPABwIMC/gMGDIn8gYMFv/4EwcP/+AKYf/BgRACBgYRB/4mCgF/AwJ6DBgoTCRohNDTZE/VAkP/gFDE4PAUQhGCI4YeEUIgYBD4gMBEpI4GgIFEAAo"),
|
||||
SE: image(56, 50, "AAcP/ADB//AAwP8AwkHA34FBAAn+A1JalmAGFvinFv4GF//PXghEBAwfBAwoNGEQP/+AGDn4GFh//8AGDg5PCgF/AYP/wAGEgj/CAwQADAw4mCAwZCCAAQ8BFQgGBAAQGBj4GFJQIGEJQIGEgYGFGIIGCIQQVDHQgACA"),
|
||||
SO: image(53, 50, "gP/AAXggEPAweAgF/AoX+gEDBgfwgEfCYoFD/EAg4MFAAQMCAAQwBBhQpBJQozBAAU/IAIACIYJUBAAV//gsJD4IsEn4sEOAn+NIn/+4FEAA39AwvvAwqQDAAP7UYhmCx5bDuBVB4BCDg5bEJ4JoEgJ1EEQKCESwIFEg5vEEA4TFh4TFv4TGYgiLBCYrFG/5dDd4YHCOQKkBDQjbDDQQwDWgR5DAwSGEEAgAEA=="),
|
||||
|
||||
TA: image(52, 50, "gEP+AGE/4Mjgf/AAXAgE/AoX8BjUAgP+GYkf8AFDBhHnEIQMBEQQhBn/jFAWAgYMD/AMH/gMF4f/F4UH/kQGYd/KIIACg4VBBgmAQ4gMFUJcB/8DDQZgBv6iD/wuEn/gKIJGDEIl/4KCDC4KPE/+BBgYXBBgY5BAIImCj4MBTIKFB/wMBAAKSB8EPAwXnUYIMDCwLYD95RBEAIZCFQN/AwPBKISpBwEGQAgAGA=="),
|
||||
TI: image(51, 49, "gED/wGEv/AAocP/AFDgP/CQk/8AFDg/8Bgn/wAFDj/wBQYAqJ4M/LBZrMJYZ+Ch5aDv/f/4bCBQIABCoMDHAYTBv4+Ej4MEg4DB4IMCAoIcCwE/TwU/+ASBEQI8BVQJLCv/gS4cP/kBMgYWBjyoEgLbJEYYSCQQkHCQg2EHASCEv4SBgYOBOQ70BQoYrBEQIABFYR/DJASRED4YFCBgJDDA="),
|
||||
TU: image(59, 45, "AAUP/4FFAAIGCAoX//EAg4GD//ACYYAB/kBAwgOBn4OFDgoOBAYX+BYP8j4GBwEAAgPDGwQ+C/F/BgIABCwOMLQl/+AGEg/+NIv/8BwF/gGEKwIqDAAM/HAYzDEhkfEgsDEgxJGh5JFHQPACqQrBCpkfCopXBCogcBCog5BK4jSCAwxtDDYK8EZIQcCAoQcDCYTjCJgQGCEYT0DIAYGGEgQGDEgRcEv5UEA="),
|
||||
TE: image(57, 50, "/4AFv4GF34GF74GF94GF+4GF/YGF/oGF/w7Cn//4BCDAwOAAwpQEj4ZDAxP8AyUPAwwiFg4GMgZFFAw0BLQqlBNAkAv4GG8AGEn/wKgv4KhZGGHALeGH4oxNh4xFOJBjGEYt/VQwVFg//BwhOBAAI7Dv4GBHYYcBCwgcB/5CEDgQyFGYgrCUwkPKAwAC"),
|
||||
TO: image(46, 49, "gEH/AFDj/wAod/4AECgP/Cwn8C0cICwcDBoIWC/4NBCwMfEgV/4f/BoIWBv//LAMH/4AB8AWBAoWAgE/BQYlBDYUAh4FBHwQPEEIJQDFYJhCgYwCLQQqCDYQKDDYIKDn5xEEAYQB/x8JDYkDCAkPYIk/JoQWTAol/AocZQwR6B8aNCAAOPAgf+TIZqBAongT4QfCBYY9BW4R1BA="),
|
||||
|
||||
NA: image(55, 49, "gEP+AGEj/gAwk/4EAkAGCv+AgAPD/8AgYdCgP+EgkD/gdB/AGBg4DBv4GCj/w/wGCv////8AwQFB//4AwMBAwXwEQMDAwXgAwMHAwXAAwMPAwWAG4QvBLgQGBL4X/AwRfBKgIGCL4X8n/gLARUBn5YDMwM8NQaLBQYIoCAQSIDAQRZBRYaBDRYQhBFAIJCKIYyCDwKoBToZkBOAIJBPYKLCGwMH/h2CAwMfKoKKCI4PgSIYYB4afDJQMP/gpB+AhBMgIjB/AhC4EfAwIhCEoIGCwJdBaIIZBMgSkCjhMBgakBG4LICUgKDBAwQuBPgRKCjgGE4EQAwgEBAAIbBRAQACQgIDB"),
|
||||
NI: image(50, 50, "h+AAocD/gFDgP/CQl/4AFDn/gv//AAOP/E/AoXj/0HAoX4/+BAoX+DAuf+EfAoXn/gYD/P/gYEBG48f+AFDg5QMMYkf8BvE/BvE/wYE/4YEKAIYYgZSCDAMBJgQYCCgYDBFoYDBj4tCDAJlDDAMBGYYYBNYYYBn4xCg/4h6ECPgIHBPgfBDwaVBQgYvBToYYCFYauBaIIwB5/wcAfz/0PAoX8cAn/IgQFC55dBAoXxFILtC/grBGgL5BYIoAGA=="),
|
||||
NU: image(58, 50, "AAV/4AGEj/wAwkH/gGEgP/Aod+Dgv/wAcEj/gDgkH/AcEgP+Dgt/Dg3wn4mBHwYGBDAIyCAwP/8AGBAoQODh4GC/4sBgYGD/AcCAAO/IQQcC4IkCDgI7Bj5YBg//w/8EAIjCwIEBv/gMQPgLAMPFYP//h1BgZpC/4LCNwIxB4YoBFoIxB/AjBNIMH/v+n5UB/4qBn/fIoIJBv+PLYUPQwPhOIUD/gvBGYMH/3/BAX/457CBAP/84GBDgIlB/YGBCYJwB/qECDgKREwBCC34YBDgfvLYP+HIM/+YYCIwM/MoIYB/hGBMoQEBz4nBKQfDAwODGQXwKQQMB/P4j4GBAQP+ngtBUgIRBg6aBRwKiBwOAf4TNBAobjCAogAEA"),
|
||||
NE: image(57, 50, "gEP+AGEg/4AwkD/gGEgP+Dgv/Awt/wAGEn/Agf/BIUf8EP/40CHAMf/4tBAYP4AQImBCIP8n4GB4EH//+AwXgEwP/v4CB/EBAYIPBg4jBAwX8BYJFBCQRKDFYIGBJQJxBIgUfAQIrBAYMPCAIfBBQR8CAwR8DMAZ8Cv4GCGIQGDGIU/AwR8BAwKqCWoU/FoS1Cj4tCHASEBWogGBUAQKBAwItBHARpB8BlBBQKuCAQIKBO4SqCBQX8AwX4h/9/wGC/kP/n/DYSlCv+P/ArB4K+B4/4SIV+j/jWIX8n0P+JSBDoMOMwJWBAwOCMwM//ZOCMwI4C75nB/5bC45nBv+DAwPhTgXAb4PAoCfCQQifBYoYAHA"),
|
||||
NO: image(60, 50, "AAX//4GEv4HFj4GB/wGCg4GB//4AwMBAwX/4AcEDwcPAwYWBgYGDCwQVC54tCCoX8F4PgFYP4CYI+BgE//0P/gaB/ARB4F/4ApBwAVBg4OBj/8EgITB4AiB4InBBwQgBCAIOCPQPjD4MPJ4MH/0/+ALBwARB84kBBwQ0Bv/gBwc/+5bBj5tEHAR8Bn5lBBwInBBxY2CBwcDWIQOEGwIODJwIOFIoRKC4CNCBQP3AgKwCDIIOBKIQKB8/8IQJgBj4OB8E/MAfD/ytBEgX8J4KeBZwWDIgJCBCoP4ZgIzCAYIqBeYRQB8DnCK4gGBGoIDBwAyBF4IKCCQWBAwIVBEoPgF4RFBg/4F4Q2BAAQOBTwIADHoQADbIQAIA"),
|
||||
|
||||
HA: image(55, 50, "AAd/wEAn4CBgH/BIXAgEB/wJEgf8AQIJCg/4AQIJBgEP+ACBBIMAj/gAQYsBEoIoCGwf/GwkB/8P/4AC4f+j4GDw/4n4GDj/wv4FC/0/8AMD/l/4IGD/H/wYGD+P/g4vELARtCMQRtDMQQKDL4YKCMQQKDMQQKDR4QKCTIYKCFYQ2bOoI2C4BgCGwWASAQ2BGQKJC8DNBBAIAB+DNBPYf4ZoKrDAgPwT4K7BAwRdBB4K3BVYIqCVYY6BAwKrB/0DVY3+v/hAwf8n4SBdIXwnxEBAwXgnBEBAwShBO4IbBSYSVCOYQAHA"),
|
||||
HI: image(57, 50, "AAMPwAGE//gAocf//wgFwgEH////kH/AZBAwP+gf+Bof/wP/gEDAwWAAIMBAwc/FgIGDj4sBv4GBE4P8HAIdBE4IqBAwYgBKAIGCKAYKBAwN/EYIGDn4jBAwZfBDAQfBLIPAAwZZBDgItENYN/CAIfBIAIGCLIRfDLIXwAwc/RQJmCHAPv/0PEoI4B+f/AwcH/P/w50D/l/wZ0CgP+j/BK4Q4Bg/gJoQ4BwIGBIwU/4EwAQI4CIYICCAYY/EJQMHHATcCbAQKEHARGBGgQqBCIc/D4IGDaITCDT4PAAQJfCQQRYDeQQGDSIIGEYYIGEE4IGEDgYFCcAQ+CGQZsCABAA="),
|
||||
HU: image(58, 50, "gEP/AGEgf//wHE/4ABAwc/AwIPDh4OC8AGBg4GCEwUBAwX8Dod/EgoHC4AsF+BJFjAGDg4iEFgRfF/+AAwk/IwQjDFIgjDvAjDMYJlCgRHB4ABBFIUf/ABBFIXH/0HCoUf+BcBLwQpBCogpBCYIVDv+ACohNBn/wCoRxBCohNCMoIVBOIQVBAIJNCCAIVCEYIQBCoOAb4QtDCAQtC/gjCdIIXCN4QwBC4SVBDQIXBEYUP/gXBI4QEBHwPD/8ODgR/CwZNCCYN/8P/5/4GQOf+DtBKgXv/jtBKgX5/0PAwJxB/0/DAL8CvkDJYP/IYMMgFgg//fot/VYQACgYGFAAoA=="),
|
||||
HE: image(67, 45, "AAXwA43/4AHFn/8A4sPCA0B//+CAt///gA4kfCA0H/4QGA4IyFn4IBGQg5BIYsD//nCAt//F/CAkf/wzBCAYFBwH//BaE8ArBwBzFCAgNBLoQQCHIPADYIQD/6dBCAk/OQIQEHIQQEHIQkCCARaBO4YUCSYQQDHIQQFHIQQERQgQCLQQQEHIKBDCAPAn5fDCAP8gbNECAaJDCAbVECAPgvj+Gg72GdoqYFCAgHFKIoQDDA0AKIjODDA0ARYQAEhwHGAAIA=="),
|
||||
HO: image(53, 49, "h4GFv4FEg/4kAGDn/D/4ACwP+j4FC/kf+IMD8H/w4GDEAM/AoQEB4IMD4f+g4FCEoPwGIXggH/wEAgP/IIP8KQX4B4PAKQXAgP+AoMDAYMPEAQkC/+DEIIkBEAJVD/8/8IFD/P/h4GD5/wv5IDv+DBgfz/gTEz/gCYf4KIIABGgRRBLIZVDNIJVDNIRVDNIRlBNIZlCKwIDC+EDGYJpCwClCNIQMCCYIwBBgX8GAIBBJwRIBPofwJAIeBLwKCBBwIiCx/4H4IVCv/BFYIFB/f+KYIMCx6RD94YBwLfDwYTBGYV8LgJICgI5CBgUCgaGBLYQACAwLVBgA"),
|
||||
|
||||
MA: image(50, 49, "AAMH/gFDgP/Bgl/4AFDj/wDBsH/4AD/oFE/9/AwoARJVXhAon4JQn+j4MEw4YLn4YEJTIfCAooYCAoX4DgQwCwBdEBgMDHoYMB//3Bgd/8AUC4A7BJQP//kHBwQGB4JYBFoX8KgMP/gGBz/+h//AIPjGAXA//wAoXwh/4DgX4gP8IgQnCF4QFBgOAEIKIEv6SCAAIA=="),
|
||||
MI: image(58, 49, "gP/AAOAA4V/AwPgAwUfAwP4AwUHAwP+DjAABgYcDDwYcDDwQcDDwQcFg/8gAXDDgMAn4XDv/Ah4XDj/wGgkPDgpQBDghPB+AcDMoXjDgQGCNwZsCNwYGEDgM/AwYcBPQQAC/kP/4IEw//MgIYC+f/wZHBCAP8//AGwMDEgKGBRAQVBz/4NYI2C44sBNYMP/PxFQI9BAQMY/+BFQKvCOoIsBEYKSCFQU/SQP8WYQCCGYIqCEwI0BFQQmBMgIDBJwOAfgXAAYItBRAJVCKIIVBAYN/FQIYBAYN/FoIrBTQSzCdgRfCAAg0BAAkfbwQACgY4BAAgGDA"),
|
||||
MU: image(55, 49, "gED/gGEg/4AwkP+EAhwGCj/ggF+AwU/4EB/wGCv+Ag4GD/4kBAwM//4AB84GBv4GC54GBAoX/x/+gIGDh/+gYFC/0P/kHAwX8AwMPAwX4j5cCGwJOBAwJIDj5jBv4QCAwIpBNoU/+AiBNoIGCJYJtBAwPhFwPANQXjAwOAgEEv+P/A2C/H+CoI2BTIIhBwY2Bh/xwH+UgUf+CwBUgSgBBYKkCn/gh/gToI1B4Ef4AvCBIM/4ZmCIAN/44oBSgKdCFAJ3CLAY0BUgQoBGgIGBEIUPAwSID+AGBQIZHBJQRECd4Q9DI4QvBJwQ2Cj4sBGATRBJwLcDFgTcDC4QGEEILqEAwIbDIARoCBgQ"),
|
||||
ME: image(55, 49, "AAUf+AGEn/gAwl/4AECBQP/wAYC4EB/4YDwED/wYDwEH/gGCCIMP/AFBgIRBGwcDCIN/GwUH/EP/4bCDAP/AAI2C+4GCHwMfAoX/JgM/AwYjBv4GI8YGCFoN/wIGBgYCBFwIiBHYJfBNAPAn/8IwIGBwAaBh/wAwOD//4R4IfBg//+B2BDoJKB+AoBg/+JQPjOwMP/n/z/nQIMf/IOB76BBn/3/gVBMgN/94nBOQX/7/gAwKbBOwSOCHoJMCEIMH/v/CAJxBh/7/hcCF4X4KYLEC5/wj5KBEIOfGwJRCL4PzF4V/JIQvBCYJJCH4JxB4AGB/xCCFQIJDDoIMBBIRNBAQJdCIwKUCeAb5CPgQACSgIFDSgIDC"),
|
||||
MO: image(50, 49, "AAN+Aokf8AFDh/4AocD/wSE/+AAod/4AeE+AFDg/8CAf/AAX8j4FD/8HAonBAonwDBY3OKwkBKxc/N5M/GwcHh42D3/DAofn/AFD/P+DAf+v/PBgeP+YFD8f+NAuAG4axBU4ZaCKAUBOAJQDOYIYE+AYEVYKFCDAaICDASICDAsPDAQxBgYYBj4rBAoOAYQPwPQPgE4JYDRQo6BAoglBPoQ0CAogMCAoYvBIwQA="),
|
||||
|
||||
|
||||
YA: image(53, 49, "AAVgAYUf4EPAoUB/8B/gGCg/4j/wAwU/4F/4ATDgf/BgUP/EPDQYRBn///wTBAQP//4OBCYMfAwP4CYPPAoP/8AnBAAeAh4FD/gMD/n/+ALD8H/z4EB/v/wf+CIUH/kP+4+CLoN/CYJhBCYmAgfwCYP7CYMeIwOcOoYiBBAOAPYXggZuCIwIrCTgQrCCYIMBFYP8gYZBC4Mf8B3CTQIPBQgYwBg4MDGAKYBGITABBgZnCL4QTCj5EFAAbUBAwgTBAoYTGYAITFcwQTPfQYTCTAITYMAQTDVgUAA="),
|
||||
YU: image(51, 49, "AAV/4AFDh/4AocB/4DBj/ggE/AQMD/0Ag/8DgWAgH/AQMP+ASB//AgISBAoIDC4Ef///+ASBh4FB/4SBgYFC+E/4IFC/8H/F///9//g/8f/3/x/+j/nAQPwv/j/H/wf+I4N/KAJlBv+P9/4MoMP/f9/xlBAIIqBwAUBn/vFwIdBg40BNIIOBIIR7B+BbC8B7BKoX4uAyCAwM+GQX5//f8IyCn/z/hHCK4N/4/8h/8/4EB/4lBF4P/z5wB8f+RYJjBPoPAFwO/BQP4IQX/wJkCTAUfVYf4gf4BgS4BbQRiCcgbSCAAILEcALkCAAM/DoYeCC4ZLBfoIeD/ASEDAhoBAoYlBDwcAg/AAoY"),
|
||||
YO: image(49, 49, "AAMP/AFDg/8AocD/wFDgP/DAn/wAFDv/AAoc/8AFDj/wGCH/AAIwDAAImCAoQmCv4FBEwU/AoImCj4FBEwUPAoJXCMO4wEM4IWDI4IwCKYQwCL4oFCDAQFDCQIXCNgQFDEoMP/iSC+EHEIJ5CAoSSCwYaBEwXhFoMf8Y4BEAJnBCYN/+Ef/AuBz41CLoPPUQd/4YFDj/AAocD4AuBPIXgDQJ/En6REA="),
|
||||
|
||||
RA: image(47, 49, "gEP4AFDn//Aod///wAoX///+AgMDAoP/DIMHAoX4AowjC//gh/4gIXCj4mBj4wBn/gEoP8GYI/CvAzBwAFBkAaBIgYTCAAUHGARcCJ4YrBFAJcD4AZDFAI/CFAMPJYQOBK4XwLgZdBJwIFDMIQFCQod/+AIBOIXzO4nnRIQRB55dDDYJdDHgQEBIgM/OgUD/0+Nof8jBtDOYk/OYgyDYgQhCPwLOCFoQ4DMwIcCPYSBCAATkECwKBDCwIVCFoQFCIgSNCHASNBGIQA=="),
|
||||
RI: image(39, 49, "ngEDv+AAgX/AYUD/wGB4EH/EH//wh/wn4EBj/h/4EBn/HAgV/z4EB+P+v4EB8YCB4F/8//E4N/54VBFgIWB4AEB346BgP/v/8AgP+//4IQP9//ggBABC4UPAgJRBj4qCgBKBC4IwBF4QrBDgQrB/5vBgYcDEwIcCEwI5BEwP3EIU/94hCv/fEIImBn4+BRYKWCg/8EwSLBTQU/CwScCUYSoDj4zCBoIzCHoIuDKARjBJYJUCQAR7DQAQbDEASABbgU/BATqE"),
|
||||
RU: image(51, 49, "gf/AAXAgF/AoX8gEPBgeAgIFD/EAn4MEg4FD8EACQoACn4lBAAUf/4FDDYOAAoQuBHwIACv/wDwgkEh/+DwoFDDw5ECDwRLDMwg5BLIZMBNgh/FGgIeB+AVB4AeBEYJmBBAJQBDgPBOocf/AoCVIU/Kwc/+5WDg/+Kwl/5/wh4mBh/4/A2CFgMOAoJDC8GBMgUHGAJQCCQKpCBgISBgf+SQMPCQN/4H/4YSBGIIwBCgMBDoTMCn/AEIROCLoKFEAIJvBTwZvCTAarFNIQFCXASyCYoYxBAoQ"),
|
||||
RE: image(55, 49, "gEf8AGEn4GFv/AAwn/wAFDgP/BgkD/wGEg/8DoIkCh/4gf/+A2C+EPAwV///gAQIGB///4ICB+AuB/+PAQPgg4DBn4GE/wSB//AEoIABwABBj4FB/hODA4PwJwYgB4BOCHwROCNoQDBJwJtCLoM/PwJdBPYN/AQMPEoQvDDQMBBIV/DwMDF4QhCg4QBEIIlBh4QBLIIlBWoRRBWol/F4eAIYIlBMwR7BEoQQBUIYvCNgIlBF4SBBEoLsBHgI2DSwP9GwaWB+ZmEj/HGwIvCj+PFgKWBjk+RgSWB/E4Lgn4sBcCIII+CGwTjDWoZFBSYYRBYYgDBYYa5CLgIGBAAI"),
|
||||
RO: image(50, 49, "AAf4gEB/4AC8EAv4FC/kAj4MDwEHAofwDAgSBDAoACn/+AocfAokP/4FDE4OAApED//AAohJBAAI5BAocAIQIFEHghFCD4QFCBoU/KIQMBNQZ9BOAhOCQYYFE/B8CE4QFBM4JGB4YuDj/7AocD/xIE/+fP4c/84FDh/8QoZyBj5mE4aFDn5yEDAIFDGIIFDIgIXDDwKREv4eEv4eBiAFCDwMH+A8BIQLnEEgLnDSooqBQYQFCDgQ2DAoolCJAQA="),
|
||||
WA: image(54, 49, "gEf+AGEv/AAocB/4MEg/8DUv///Aj//wEDAwIcBAwMP//8BgIGBn//+IFBAwICB54GCDQQAC/0HAgXAn45BD4IDBn45Bv4MBAYPgGYJKCFAIbB8EAgf+DQRbEv/4LYYaBOQU/4EPCwIhCCYJrCgf8CYkP+BlBCYQaBv6GDOwQaECYIaEKwIaD4JWDgP+CYIaCg/4NQYTB8Z+BFwef+4aCMgN/74aCn/z/zXCIAOH/IaCh5CB44aBJoU+a4QyBwFwDQLGBCAOBX4adBGIJMBRIQaBUYI4CDQJnDFYJ7EDQKzCDQYECgA="),
|
||||
WO: image(52, 49, "AAMf+AFDgP+Bgk/8AFDgYMM/gkD/4AC+EBAof/BkA5FhEAg45Cg/AgF/AQMBBIMP/4DB//gE4Xwn5dBn4GB74IBgY0Fv4FD8AfBAoYfB/gbBIAIiBg///A7B/+A/4rBCQIxBBAISB/ghBCQeBEoIMBCQI0BBgQSCDIYSB54MBgIlB+AMCj0H/0PBgIABHQQMBOgP4BgZBBBwTDCMYIMDKIIMRWQQmDAwUMYYqyBAoaxBN4IMEV4QMCcggMBWwbZCAweA"),
|
||||
N: image(54, 49, "AAMHAwsf8AGE/+AAocD/wTF+AGEv/ACZUP/ATKgP/CYv8Awk/IQgTBIQkHCYxCFCYxWTIQxWGFAhCBAwkPAwJCE/5KDCYQiBhhCBAwJlBn+Aj/+/49BDoP/8IDBgf8IQIDBKgUf/EPLAJUBv/gn/AFgKZCAIMHCIP4DQSXBAIIaC/+BCIIaBYwKZCLwIuBCYLRCFwIKBEYX/CYUfEYP4TIRACCYQ+BwZUBDwIYBOgITCRAQVCEIP//0BYISjB+CtDUYRNBAwQ5Bg7gDBQIA="),
|
||||
|
||||
};
|
||||
const keys = [
|
||||
"A","I","U","E","O",
|
||||
"HA","HI","HU","HE","HO",
|
||||
"KA","KI","KU","KE","KO",
|
||||
"MA","MI","MU","ME","MO",
|
||||
"NA","NI","NU","NE","NO",
|
||||
"RA","RI","RU","RE","RO",
|
||||
"SA","SI","SU","SE","SO",
|
||||
"TA","TI","TU","TE","TO",
|
||||
"WA","WO","YO","YU","N",
|
||||
];
|
||||
let kana = katakana.KA;
|
||||
let scroll = 0;
|
||||
|
||||
// const keys = Object.keys(katakana).sort();
|
||||
// console.log(keys);
|
||||
let hiramode = false;
|
||||
let curkana = 'KA';
|
||||
|
||||
console.log("StartupTime: "+startupTime.diff());
|
||||
|
||||
function next () {
|
||||
let found = false;
|
||||
for (const k of Object.keys(katakana).sort()) {
|
||||
if (found) {
|
||||
kana = hiramode ? hiragana[k] : katakana[k];
|
||||
curkana = k;
|
||||
return;
|
||||
const off = keys.indexOf(curkana);
|
||||
if (off !== -1 && off + 1 < keys.length) {
|
||||
return keys[off + 1];
|
||||
}
|
||||
if (curkana === k) {
|
||||
found = true;
|
||||
}
|
||||
}
|
||||
curkana = 'KA';
|
||||
updateWatch(ohhmm);
|
||||
return keys[0];
|
||||
}
|
||||
|
||||
function randKana() {
|
||||
try {
|
||||
const keys = Object.keys(katakana);
|
||||
const total = keys.length;
|
||||
let index = 0 | (Math.random() * total);
|
||||
let index = 0 | (Math.random() * keys.length);
|
||||
curkana = keys[index];
|
||||
} catch (e) {
|
||||
randKana();
|
||||
}
|
||||
}
|
||||
// const bench = benchStart();
|
||||
// console.log("-->" + bench.diff());
|
||||
|
||||
function prev () {
|
||||
let oldk = '';
|
||||
let count = 0;
|
||||
for (const k of Object.keys(katakana).sort()) {
|
||||
if (curkana === k) {
|
||||
if (count > 0) {
|
||||
curkana = oldk;
|
||||
return;
|
||||
const off = keys.indexOf(curkana);
|
||||
if (off > 0) {
|
||||
return keys[off - 1];
|
||||
}
|
||||
return keys[keys.length - 1];
|
||||
}
|
||||
let color = 0;
|
||||
const colors = [
|
||||
() => g.setColor(0,1,0),
|
||||
() => g.setColor(1,1,0),
|
||||
() => g.setColor(0,1,1),
|
||||
() => g.setColor(1,1,1),
|
||||
// too dark
|
||||
() => g.setColor(0,0,1),
|
||||
() => g.setColor(0,0,0),
|
||||
() => g.setColor(1,0,0),
|
||||
];
|
||||
|
||||
function nextColor() {
|
||||
if (color + 1 >= colors.length) {
|
||||
color = 0;
|
||||
} else {
|
||||
color++;
|
||||
}
|
||||
}
|
||||
oldk = k;
|
||||
count++;
|
||||
function prevColor() {
|
||||
if (color < 1) {
|
||||
color = colors.length - 1;
|
||||
} else {
|
||||
color--;
|
||||
}
|
||||
curkana = oldk;
|
||||
updateWatch(ohhmm);
|
||||
}
|
||||
|
||||
const kanacolors = {
|
||||
A: []
|
||||
};
|
||||
|
||||
|
||||
function updateWatch (hhmm) {
|
||||
function render(hhmm) {
|
||||
g.setFontAlign(-1, -1, 0);
|
||||
g.setBgColor(0, 0, 0);
|
||||
g.setColor(0, 0, 0);
|
||||
var whitecolor = false;
|
||||
if (curkana.indexOf('A') != -1) {
|
||||
g.setColor(1, 0, 0);
|
||||
whitecolor = true;
|
||||
} else if (curkana.indexOf('I') != -1) {
|
||||
g.setColor(0, 1, 0);
|
||||
} else if (curkana.indexOf('U') != -1) {
|
||||
g.setColor(0, 0, 1);
|
||||
whitecolor = true;
|
||||
} else if (curkana.indexOf('E') != -1) {
|
||||
g.setColor(1, 1, 0);
|
||||
} else {
|
||||
g.setColor(0, 1, 1);
|
||||
}
|
||||
g.fillRect(0, 0, w, h);
|
||||
const whitecolor = color > 3;
|
||||
colors[color]();
|
||||
g.fillRect(0, 30, w, h);
|
||||
|
||||
g.setFont('Vector', 50);
|
||||
if (whitecolor) {
|
||||
|
@ -196,9 +228,9 @@ function updateWatch (hhmm) {
|
|||
}
|
||||
g.drawString(hhmm, x, y - 1);
|
||||
|
||||
drawKana(4 + (g.getWidth() / 6), 60);
|
||||
drawKana();
|
||||
drawMonthDay();
|
||||
Bangle.drawWidgets();
|
||||
// Bangle.drawWidgets(); // :? always draw?
|
||||
}
|
||||
|
||||
function drawMonthDay() {
|
||||
|
@ -220,19 +252,45 @@ function getPhoneme(k) {
|
|||
return k;
|
||||
}
|
||||
|
||||
var ohhmm = '';
|
||||
var ypos = 0;
|
||||
var xpos = 0;
|
||||
var zpos = 1;
|
||||
function drawKana (x, y) {
|
||||
if (!x) {
|
||||
x = 4 + (g.getWidth() / 6);
|
||||
}
|
||||
if (!y) {
|
||||
y = 40;
|
||||
}
|
||||
x += xpos;
|
||||
y += ypos;
|
||||
g.setColor(0, 0, 0);
|
||||
g.fillRect(0, 0, g.getWidth(), 6 * (h / 8) + 1);
|
||||
g.fillRect(0, 30, g.getWidth(), 6 * (h / 8) + 1);
|
||||
g.setColor(1, 1, 1);
|
||||
x -= ((zpos) - 1)*50;
|
||||
y -= (zpos - 1)*50;
|
||||
kana = hiramode ? hiragana[curkana] : katakana[curkana];
|
||||
g.drawImage(kana, x + 20, 40, { scale: 1.6 });
|
||||
if (guard) {
|
||||
g.setColor(0.8,0.8,0.8);
|
||||
}
|
||||
g.drawImage(kana, x + 20, y, { scale: 1.6 * zpos });
|
||||
g.setColor(1, 1, 1);
|
||||
g.setFont('Vector', 24);
|
||||
g.drawString(getPhoneme(curkana), 4, 32);
|
||||
g.drawString(hiramode ? 'H' : 'K', w - 20, 32);
|
||||
if (hiramode) {
|
||||
g.setColor(0.2,0.2,0.2)
|
||||
g.drawString('K', w - 20, 32);
|
||||
g.setColor(1, 1, 1);
|
||||
g.drawString('H', w - 20, 32+24);
|
||||
} else {
|
||||
g.setColor(1, 1, 1);
|
||||
g.drawString('K', w - 20, 32);
|
||||
g.setColor(0.2,0.2,0.2)
|
||||
g.drawString('H', w - 20, 32+24);
|
||||
}
|
||||
// g.drawString(hiramode ? 'H' : 'K', w - 20, 32);
|
||||
}
|
||||
|
||||
var ohhmm = '';
|
||||
|
||||
function tickWatch () {
|
||||
const now = Date();
|
||||
|
@ -243,27 +301,127 @@ function tickWatch () {
|
|||
}
|
||||
const hhmm = zpad(now.getHours()) + ':' + zpad(now.getMinutes());
|
||||
if (hhmm !== ohhmm) {
|
||||
randKana();
|
||||
updateWatch(hhmm);
|
||||
ohhmm = hhmm;
|
||||
randKana();
|
||||
render(hhmm);
|
||||
}
|
||||
}
|
||||
let guard = false;
|
||||
function hiraPush(d,dx) {
|
||||
if (guard) {
|
||||
return;
|
||||
}
|
||||
xpos = 0;
|
||||
ypos = 0;
|
||||
zpos = 1;
|
||||
guard = true;
|
||||
var count = 2;
|
||||
function paint() {
|
||||
count--;
|
||||
if (count < 0) {
|
||||
guard = false;
|
||||
xpos = 0;
|
||||
ypos = 0;
|
||||
zpos = 1;
|
||||
render(ohhmm);
|
||||
return;
|
||||
}
|
||||
zpos -= 0.04;
|
||||
render(ohhmm);
|
||||
setTimeout(paint, 100);
|
||||
}
|
||||
setTimeout (paint, 5);
|
||||
}
|
||||
|
||||
function hiraSwipe(d,dx, dostuff) {
|
||||
if (guard) {
|
||||
return;
|
||||
}
|
||||
if (dx) {
|
||||
ypos = 0;
|
||||
} else {
|
||||
ypos = (d * 4);
|
||||
}
|
||||
xpos = 0;
|
||||
guard = true;
|
||||
var count = 2;
|
||||
function paint() {
|
||||
count--;
|
||||
if (count < 0) {
|
||||
if (dx) {
|
||||
curkana = d>0?prev():next();
|
||||
} else {
|
||||
if (dostuff) {
|
||||
hiramode = !hiramode;
|
||||
}
|
||||
}
|
||||
guard = false;
|
||||
xpos = 0;
|
||||
ypos = 0;
|
||||
render(ohhmm);
|
||||
return;
|
||||
}
|
||||
if (dx) {
|
||||
xpos += (8*d);
|
||||
} else {
|
||||
ypos -= (4*d);
|
||||
}
|
||||
render(ohhmm);
|
||||
setTimeout(paint, 5);
|
||||
}
|
||||
setTimeout (paint, 5);
|
||||
}
|
||||
|
||||
Bangle.on('touch', function (tap, top) {
|
||||
if (top.x < w / 4) {
|
||||
prev();
|
||||
} else if (top.x > (w - (w / 4))) {
|
||||
next();
|
||||
if (top.y < (h / 1.5)) {
|
||||
if (top.x > w /2) {
|
||||
//hiramode = !hiramode;
|
||||
if (hiramode) {
|
||||
hiraSwipe(1,0, hiramode);
|
||||
} else {
|
||||
hiramode = !hiramode;
|
||||
hiraSwipe(-1,0, !hiramode);
|
||||
}
|
||||
kana = hiramode ? hiragana[curkana] : katakana[curkana];
|
||||
updateWatch(ohhmm);
|
||||
} else {
|
||||
hiraSwipe(1,1,1);
|
||||
}
|
||||
} else if (top.x < w / 2) {
|
||||
nextColor();
|
||||
hiraPush();
|
||||
// curkana = prev();
|
||||
} else {
|
||||
prevColor();
|
||||
hiraPush();
|
||||
// curkana = next();
|
||||
}
|
||||
render(ohhmm);
|
||||
});
|
||||
Bangle.on('swipe', function (x,y) {
|
||||
if (x > 0) {
|
||||
// nextColor();
|
||||
hiraSwipe(1, 1);
|
||||
} else if (x < 0) {
|
||||
// prevColor();
|
||||
hiraSwipe(-1,1);
|
||||
} else if (y < 0) {
|
||||
hiraSwipe(1, 0, hiramode);
|
||||
} else if (y > 0) {
|
||||
hiraSwipe(-1, 0, !hiramode);
|
||||
}
|
||||
render(ohhmm);
|
||||
});
|
||||
|
||||
g.clear(true);
|
||||
// show launcher when button pressed
|
||||
Bangle.setUI('clock');
|
||||
Bangle.loadWidgets();
|
||||
Bangle.drawWidgets();
|
||||
|
||||
// redraw widgets every 10 minutes
|
||||
setInterval(function() {
|
||||
// maybe not always necessary
|
||||
Bangle.drawWidgets();
|
||||
}, 1000 * 60 * 10);
|
||||
tickWatch();
|
||||
setInterval(tickWatch, 1000 * 60);
|
||||
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
"id": "kanawatch",
|
||||
"name": "Kanawatch",
|
||||
"shortName": "Kanawatch",
|
||||
"version": "0.07",
|
||||
"version": "0.11",
|
||||
"type": "clock",
|
||||
"description": "Learn Hiragana and Katakana",
|
||||
"icon": "app.png",
|
||||
|
@ -26,6 +26,9 @@
|
|||
"screenshots": [
|
||||
{
|
||||
"url": "screenshot.png"
|
||||
},
|
||||
{
|
||||
"url": "screenshot2.png"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
After Width: | Height: | Size: 2.9 KiB |
Before Width: | Height: | Size: 2.9 KiB After Width: | Height: | Size: 2.6 KiB |
After Width: | Height: | Size: 2.6 KiB |
|
@ -0,0 +1,7 @@
|
|||
1.00: New keyboard
|
||||
1.01: Change swipe interface to taps, speed up responses (efficiency tweaks).
|
||||
1.02: Generalize drawing and letter scaling. Allow custom and auto-generated character sets. Improve documentation.
|
||||
1.03: Attempt to improve keyboard load time.
|
||||
1.04: Make code asynchronous and improve load time.
|
||||
1.05: Fix layout issue and rename library
|
||||
1.06: Touch up readme, prep for IPO, add screenshots
|
|
@ -0,0 +1,119 @@
|
|||
# Matryoshka Keyboard
|
||||
|
||||

|
||||
|
||||
 
|
||||
|
||||
 
|
||||
 
|
||||
|
||||
Nested key input utility.
|
||||
|
||||
## How to type
|
||||
|
||||
Press your finger down on the letter group that contains the character you would like to type, then tap the letter you
|
||||
want to enter. Once you are touching the letter you want, release your
|
||||
finger.
|
||||
|
||||

|
||||
|
||||
Press "shft" or "caps" to access alternative characters, including upper case letters, punctuation, and special
|
||||
characters.
|
||||
Pressing "shft" also reveals a cancel button if you would like to terminate input without saving.
|
||||
|
||||
Press "ok" to finish typing and send your text to whatever app called this keyboard.
|
||||
|
||||
Press "del" to delete the leftmost character.
|
||||
|
||||
## Themes and Colors
|
||||
|
||||
This keyboard will attempt to use whatever theme or colorscheme is being used by your Bangle device.
|
||||
|
||||
## How to use in a program
|
||||
|
||||
This was developed to match the interface implemented for kbtouch, kbswipe, etc.
|
||||
|
||||
In your app's metadata, add:
|
||||
|
||||
```json
|
||||
"dependencies": {"textinput": "type"}
|
||||
```
|
||||
|
||||
From inside your app, call:
|
||||
|
||||
```js
|
||||
const textInput = require("textinput");
|
||||
|
||||
textInput.input({text: ""})
|
||||
.then(result => {
|
||||
console.log("The user entered: ", result);
|
||||
});
|
||||
```
|
||||
|
||||
Alternatively, if you want to improve the load time of the keyboard, you can pre-generate the data the keyboard needs
|
||||
to function and render like so:
|
||||
|
||||
```js
|
||||
const textInput = require("textinput");
|
||||
|
||||
const defaultKeyboard = textInput.generateKeyboard(textInput.defaultCharSet);
|
||||
const defaultShiftKeyboard = textInput.generateKeyboard(textInput.defaultCharSetShift);
|
||||
// ...
|
||||
textInput.input({text: "", keyboardMain: defaultKeyboard, keyboardShift: defaultShiftKeyboard})
|
||||
.then(result => {
|
||||
console.log("The user entered: ", result);
|
||||
// And it was faster!
|
||||
});
|
||||
```
|
||||
|
||||
This isn't required, but if you are using a large character set, and the user is interacting with the keyboard a lot,
|
||||
it can really smooth the experience.
|
||||
|
||||
The default keyboard has a full set of alphanumeric characters as well as special characters and buttons in a
|
||||
pre-defined layout. If your application needs something different, or you want to have a custom layout, you can do so:
|
||||
|
||||
```js
|
||||
const textInput = require("textinput");
|
||||
|
||||
const customKeyboard = textInput.generateKeyboard([
|
||||
["1", "2", "3", "4"], ["5", "6", "7", "8"], ["9", "0", ".", "-"], "ok", "del", "cncl"
|
||||
]);
|
||||
// ...
|
||||
textInput.input({text: "", keyboardMain: customKeyboard})
|
||||
.then(result => {
|
||||
console.log("The user entered: ", result);
|
||||
// And they could only enter numbers, periods, and dashes!
|
||||
});
|
||||
```
|
||||
|
||||
This will give you a keyboard with six buttons. The first three buttons will open up a 2x2 keyboard. The second three
|
||||
buttons are special keys for submitting, deleting, and cancelling respectively.
|
||||
|
||||
Finally if you are like, super lazy, or have a dynamic set of keys you want to be using at any given time, you can
|
||||
generate keysets from strings like so:
|
||||
|
||||
```js
|
||||
const textInput = require("textinput");
|
||||
|
||||
const customKeyboard = textInput.generateKeyboard(createCharSet("ABCDEFGHIJKLMNOP", ["ok", "shft", "cncl"]));
|
||||
const customShiftKeyboard = textInput.generateKeyboard(createCharSet("abcdefghijklmnop", ["ok", "shft", "cncl"]));
|
||||
// ...
|
||||
textInput.input({text: "", keyboardMain: customKeyboard, keyboardShift: customShiftKeyboard})
|
||||
.then(result => {
|
||||
console.log("The user entered: ", result);
|
||||
// And the keyboard was automatically generated to include "ABCDEFGHIJKLMNOP" plus an OK button, a shift button, and a cancel button!
|
||||
});
|
||||
```
|
||||
|
||||
The promise resolves when the user hits "ok" on the input or if they cancel. If the user cancels, undefined is
|
||||
returned, although the user can hit "OK" with an empty string as well. If you define a custom character set and
|
||||
do not include the "ok" button your user will be soft-locked by the keyboard. Fair warning!
|
||||
|
||||
At some point I may add swipe-for-space and swipe-for-delete as well as swipe-for-submit and swipe-for-cancel
|
||||
however I want to have a good strategy for the touch screen
|
||||
[affordance](https://careerfoundry.com/en/blog/ux-design/affordances-ux-design/).
|
||||
|
||||
## Secret features
|
||||
|
||||
If you long press a key with characters on it, that will enable "Shift" mode.
|
||||
|
|
@ -0,0 +1 @@
|
|||
require("heatshrink").decompress(atob("mEwwcBkmSpICVz//ABARGCBIRByA/Dk+AAgUH8AECgP4kmRCwX4n+PAoXH8YEC+IRC4HguE4/+P/EfCIXwgARHn4RG+P/j4RDJwgRBGQIRIEYNxCIRECGpV/CIXAgY1P4/8v41JOgeOn4RDGo4jER5Y1FCJWQg4RDYpeSNIQAMkmTCBwRBz4IG9YRIyA8COgJHBhMgI4+QyVJAYJrC9Mkw5rHwFAkEQCImSCJvAhIRBpazFGo3HEYVJkIjGCIIUCAQu/CKGSGo4jPLIhHMNayPLYo6zBYozpH9MvdI+TfaGSv4KHCI+Qg4GDI4IABg5HGyIYENYIAB45rGyPACKIIDx/4gF/CIPx/8fCIY1F4H8CJPA8BtCa4I1DCJFxCIYXBCILXBGpXHGplwn5HPuE4NaH4n6PLyC6CgEnYpeSpICDdJYRFz4RQARQ"))
|
After Width: | Height: | Size: 5.4 KiB |
After Width: | Height: | Size: 852 B |
|
@ -0,0 +1,501 @@
|
|||
/**
|
||||
* Attempt to lay out a set of characters in a logical way to optimize the number of buttons with the number
|
||||
* of characters per button. Useful if you need to dynamically (or frequently) change your character set
|
||||
* and don't want to create a layout for ever possible combination.
|
||||
* @param text The text you want to parse into a character set.
|
||||
* @param specials Any special buttons you want to add to the keyboard (must match hardcoded special string values)
|
||||
* @returns {*[]}
|
||||
*/
|
||||
function createCharSet(text, specials) {
|
||||
specials = specials || [];
|
||||
const mandatoryExtraKeys = specials.length;
|
||||
const preferredNumChars = [1, 2, 4, 6, 9, 12];
|
||||
const preferredNumKeys = [4, 6, 9, 12].map(num => num - mandatoryExtraKeys);
|
||||
let keyIndex = 0, charIndex = 0;
|
||||
let keySpace = preferredNumChars[charIndex] * preferredNumKeys[keyIndex];
|
||||
while (keySpace < text.length) {
|
||||
const numKeys = preferredNumKeys[keyIndex];
|
||||
const numChars = preferredNumChars[charIndex];
|
||||
const nextNumKeys = preferredNumKeys[keyIndex];
|
||||
const nextNumChars = preferredNumChars[charIndex];
|
||||
if (numChars <= numKeys) {
|
||||
charIndex++;
|
||||
} else if ((text.length / nextNumChars) < nextNumKeys) {
|
||||
charIndex++;
|
||||
} else {
|
||||
keyIndex++;
|
||||
}
|
||||
keySpace = preferredNumChars[charIndex] * preferredNumKeys[keyIndex];
|
||||
}
|
||||
const charsPerKey = preferredNumChars[charIndex];
|
||||
let charSet = [];
|
||||
for (let i = 0; i < text.length; i += charsPerKey) {
|
||||
charSet.push(text.slice(i, i + charsPerKey)
|
||||
.split(""));
|
||||
}
|
||||
charSet = charSet.concat(specials);
|
||||
return charSet;
|
||||
}
|
||||
|
||||
/**
|
||||
* Given the width, height, padding (between chars) and number of characters that need to fit horizontally /
|
||||
* vertically, this function attempts to select the largest font it can that will still fit within the bounds when
|
||||
* drawing a grid of characters. Does not handle multi-letter entries well, assumes we are laying out a grid of
|
||||
* single characters.
|
||||
* @param width The total width available for letters (px)
|
||||
* @param height The total height available for letters (px)
|
||||
* @param padding The amount of space required between characters (px)
|
||||
* @param gridWidth The number of characters wide the rendering is going to be
|
||||
* @param gridHeight The number of characters high the rendering is going to be
|
||||
* @returns {{w: number, h: number, font: string}}
|
||||
*/
|
||||
function getBestFont(width, height, padding, gridWidth, gridHeight) {
|
||||
let font = "4x6";
|
||||
let w = 4;
|
||||
let h = 6;
|
||||
const charMaxWidth = width / gridWidth - padding * gridWidth;
|
||||
const charMaxHeight = height / gridHeight - padding * gridHeight;
|
||||
if (charMaxWidth >= 6 && charMaxHeight >= 8) {
|
||||
w = 6;
|
||||
h = 8;
|
||||
font = "6x8";
|
||||
}
|
||||
if (charMaxWidth >= 12 && charMaxHeight >= 16) {
|
||||
w = 12;
|
||||
h = 16;
|
||||
font = "6x8:2";
|
||||
}
|
||||
if (charMaxWidth >= 12 && charMaxHeight >= 20) {
|
||||
w = 12;
|
||||
h = 20;
|
||||
font = "12x20";
|
||||
}
|
||||
if (charMaxWidth >= 20 && charMaxHeight >= 20) {
|
||||
font = "Vector" + Math.floor(Math.min(charMaxWidth, charMaxHeight));
|
||||
const dims = g.setFont(font)
|
||||
.stringMetrics("W");
|
||||
w = dims.width
|
||||
h = dims.height;
|
||||
}
|
||||
return {w, h, font};
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Generate a set of key objects given an array of arrays of characters to make available for typing.
|
||||
* @param characterArrays
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
function getKeys(characterArrays) {
|
||||
if (Array.isArray(characterArrays)) {
|
||||
return Promise.all(characterArrays.map((chars, i) => generateKeyFromChars(characterArrays, i)));
|
||||
} else {
|
||||
return generateKeyFromChars(characterArrays, 0);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a set of characters, determine whether or not this needs to be a matryoshka key, a basic key, or a special key.
|
||||
* Then generate that key. If the key is a matryoshka key, we queue up the generation of its sub-keys for later to
|
||||
* improve load times.
|
||||
* @param chars
|
||||
* @param i
|
||||
* @returns {Promise<unknown>}
|
||||
*/
|
||||
function generateKeyFromChars(chars, i) {
|
||||
return new Promise((resolve, reject) => {
|
||||
let special;
|
||||
if (!Array.isArray(chars[i]) && chars[i].length > 1) {
|
||||
// If it's not an array we assume it's a string. Fingers crossed I guess, lol. Be nice to my functions!
|
||||
special = chars[i];
|
||||
}
|
||||
const key = getKeyByIndex(chars, i, special);
|
||||
if (!special) {
|
||||
key.chars = chars[i];
|
||||
}
|
||||
if (key.chars.length > 1) {
|
||||
key.pendingSubKeys = true;
|
||||
key.getSubKeys = () => getKeys(key.chars);
|
||||
resolve(key)
|
||||
} else {
|
||||
resolve(key);
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Given a set of characters (or sets of characters) get the position and dimensions of the i'th key in that set.
|
||||
* @param charSet An array where each element represents a key on the hypothetical keyboard.
|
||||
* @param i The index of the key in the set you want to get dimensions for.
|
||||
* @param special The special property of the key - for example "del" for a key used for deleting characters.
|
||||
* @returns {{special, bord: number, pad: number, w: number, x: number, h: number, y: number, chars: *[]}}
|
||||
*/
|
||||
function getKeyByIndex(charSet, i, special) {
|
||||
// Key dimensions
|
||||
const keyboardOffsetY = 40;
|
||||
const margin = 3;
|
||||
const padding = 4;
|
||||
const border = 2;
|
||||
const gridWidth = Math.ceil(Math.sqrt(charSet.length));
|
||||
const gridHeight = Math.ceil(charSet.length / gridWidth);
|
||||
const keyWidth = Math.floor((g.getWidth()) / gridWidth) - margin;
|
||||
const keyHeight = Math.floor((g.getHeight() - keyboardOffsetY) / gridHeight) - margin;
|
||||
const gridx = i % gridWidth;
|
||||
const gridy = Math.floor(i / gridWidth) % gridWidth;
|
||||
const x = gridx * (keyWidth + margin);
|
||||
const y = gridy * (keyHeight + margin) + keyboardOffsetY;
|
||||
const w = keyWidth;
|
||||
const h = keyHeight;
|
||||
// internal Character spacing
|
||||
const numChars = charSet[i].length;
|
||||
const subGridWidth = Math.ceil(Math.sqrt(numChars));
|
||||
const subGridHeight = Math.ceil(numChars / subGridWidth);
|
||||
const bestFont = getBestFont(w - padding, h - padding, 0, subGridWidth, subGridHeight);
|
||||
const letterWidth = bestFont.w;
|
||||
const letterHeight = bestFont.h;
|
||||
const totalWidth = (subGridWidth - 1) * (w / subGridWidth) + padding + letterWidth + 1;
|
||||
const totalHeight = (subGridHeight - 1) * (h / subGridHeight) + padding + letterHeight + 1;
|
||||
const extraPadH = (w - totalWidth) / 2;
|
||||
const extraPadV = (h - totalHeight) / 2;
|
||||
return {
|
||||
x,
|
||||
y,
|
||||
w,
|
||||
h,
|
||||
pad : padding,
|
||||
bord : border,
|
||||
chars: [],
|
||||
special,
|
||||
subGridWidth,
|
||||
subGridHeight,
|
||||
extraPadH,
|
||||
extraPadV,
|
||||
font : bestFont.font
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* This is probably the most intense part of this keyboard library. If you don't do it ahead of time, it will happen
|
||||
* when you call the keyboard, and it can take up to 0.5 seconds for a full alphanumeric keyboard. Depending on what
|
||||
* is an acceptable user experience for you, and how many keys you are actually generating, you may choose to do this
|
||||
* ahead of time and pass the result to the "input" function of this library. NOTE: This function would need to be
|
||||
* called once per key set - so if you have a keyboard with a "shift" key you'd need to run it once for your base
|
||||
* keyset and once for your shift keyset.
|
||||
* @param charSets
|
||||
* @returns {Promise<unknown>}
|
||||
*/
|
||||
function generateKeyboard(charSets) {
|
||||
if (!Array.isArray(charSets)) {
|
||||
// User passed a string. We will divvy it up into a real set of subdivided characters.
|
||||
charSets = createCharSet(charSets, ["ok", "del", "shft"]);
|
||||
}
|
||||
return getKeys(charSets);
|
||||
}
|
||||
|
||||
// Default layout
|
||||
const defaultCharSet = [
|
||||
["a", "b", "c", "d", "e", "f", "g", "h", "i"],
|
||||
["j", "k", "l", "m", "n", "o", "p", "q", "r"],
|
||||
["s", "t", "u", "v", "w", "x", "y", "z", "0"],
|
||||
["1", "2", "3", "4", "5", "6", "7", "8", "9"],
|
||||
[" ", "`", "-", "=", "[", "]", "\\", ";", "'"],
|
||||
[",", ".", "/"],
|
||||
"ok",
|
||||
"shft",
|
||||
"del"
|
||||
];
|
||||
|
||||
// Default layout with shift pressed
|
||||
const defaultCharSetShift = [
|
||||
["A", "B", "C", "D", "E", "F", "G", "H", "I"],
|
||||
["J", "K", "L", "M", "N", "O", "P", "Q", "R"],
|
||||
["S", "T", "U", "V", "W", "X", "Y", "Z", ")"],
|
||||
["!", "@", "#", "$", "%", "^", "&", "*", "("],
|
||||
["~", "_", "+", "{", "}", "|", ":", "\"", "<"],
|
||||
[">", "?"],
|
||||
"ok",
|
||||
"shft",
|
||||
"del"
|
||||
];
|
||||
|
||||
/**
|
||||
* Given initial options, allow the user to type a set of characters and return their entry in a promise. If you do not
|
||||
* submit your own character set, a default alphanumeric keyboard will display.
|
||||
* @param options The object containing initial options for the keyboard.
|
||||
* @param {string} options.text The initial text to display / edit in the keyboard
|
||||
* @param {array[]|string[]} [options.keyboardMain] The primary keyboard generated with generateKeyboard()
|
||||
* @param {array[]|string[]} [options.keyboardShift] Like keyboardMain, but displayed when shift / capslock is pressed.
|
||||
* @returns {Promise<unknown>}
|
||||
*/
|
||||
function input(options) {
|
||||
options = options || {};
|
||||
let typed = options.text || "";
|
||||
let resolveFunction = () => {};
|
||||
let shift = false;
|
||||
let caps = false;
|
||||
let activeKeySet;
|
||||
|
||||
const offsetX = 0;
|
||||
const offsetY = 40;
|
||||
|
||||
E.showMessage("Loading...");
|
||||
let keyboardPromise;
|
||||
if (options.keyboardMain) {
|
||||
keyboardPromise = Promise.all([options.keyboardMain, options.keyboardShift || Promise.resolve([])]);
|
||||
} else {
|
||||
keyboardPromise = Promise.all([generateKeyboard(defaultCharSet), generateKeyboard(defaultCharSetShift)])
|
||||
}
|
||||
|
||||
let mainKeys;
|
||||
let mainKeysShift;
|
||||
|
||||
/**
|
||||
* Draw an individual keyboard key - handles special formatting and the rectangle pad, followed by the character
|
||||
* rendering.
|
||||
* @param key
|
||||
*/
|
||||
function drawKey(key) {
|
||||
let bgColor = g.theme.bg;
|
||||
if (key.special) {
|
||||
if (key.special === "ok") bgColor = "#0F0";
|
||||
if (key.special === "cncl") bgColor = "#F00";
|
||||
if (key.special === "del") bgColor = g.theme.bg2;
|
||||
if (key.special === "spc") bgColor = g.theme.bg2;
|
||||
if (key.special === "shft") {
|
||||
bgColor = shift ? g.theme.bgH : g.theme.bg2;
|
||||
}
|
||||
if (key.special === "caps") {
|
||||
bgColor = caps ? g.theme.bgH : g.theme.bg2;
|
||||
}
|
||||
g.setColor(bgColor)
|
||||
.fillRect({x: key.x, y: key.y, w: key.w, h: key.h});
|
||||
}
|
||||
g.setColor(g.theme.fg)
|
||||
.drawRect({x: key.x, y: key.y, w: key.w, h: key.h});
|
||||
drawChars(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Draw the characters for a given key - this handles the layout of all characters needed for the key, whether the
|
||||
* key has 12 characters, 1, or if it represents a special key.
|
||||
* @param key
|
||||
*/
|
||||
function drawChars(key) {
|
||||
const numChars = key.chars.length;
|
||||
if (key.special) {
|
||||
g.setColor(g.theme.fg)
|
||||
.setFont("12x20")
|
||||
.setFontAlign(-1, -1)
|
||||
.drawString(key.special, key.x + key.w / 2 - g.stringWidth(key.special) / 2, key.y + key.h / 2 - 10, false);
|
||||
} else {
|
||||
g.setColor(g.theme.fg)
|
||||
.setFont(key.font)
|
||||
.setFontAlign(-1, -1);
|
||||
for (let i = 0; i < numChars; i++) {
|
||||
const gridX = i % key.subGridWidth;
|
||||
const gridY = Math.floor(i / key.subGridWidth) % key.subGridWidth;
|
||||
const charOffsetX = gridX * (key.w / key.subGridWidth);
|
||||
const charOffsetY = gridY * (key.h / key.subGridHeight);
|
||||
const posX = key.x + key.pad + charOffsetX + key.extraPadH;
|
||||
const posY = key.y + key.pad + charOffsetY + key.extraPadV;
|
||||
g.drawString(key.chars[i], posX, posY, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the key set corresponding to the indicated shift state. Allows easy switching between capital letters and
|
||||
* lower case by just switching the boolean passed here.
|
||||
* @param shift
|
||||
* @returns {*[]}
|
||||
*/
|
||||
function getMainKeySet(shift) {
|
||||
return shift ? mainKeysShift : mainKeys;
|
||||
}
|
||||
|
||||
/**
|
||||
* Draw all the given keys on the screen.
|
||||
* @param keys
|
||||
*/
|
||||
function drawKeys(keys) {
|
||||
keys.forEach(key => {
|
||||
drawKey(key);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Draw the text that the user has typed so far, includes a cursor and automatic truncation when the string is too
|
||||
* long.
|
||||
* @param text
|
||||
* @param cursorChar
|
||||
*/
|
||||
function drawTyped(text, cursorChar) {
|
||||
let visibleText = text;
|
||||
let ellipsis = false;
|
||||
const maxWidth = 176 - 40;
|
||||
while (g.setFont("12x20")
|
||||
.stringWidth(visibleText) > maxWidth) {
|
||||
ellipsis = true;
|
||||
visibleText = visibleText.slice(1);
|
||||
}
|
||||
if (ellipsis) {
|
||||
visibleText = "..." + visibleText;
|
||||
}
|
||||
g.setColor(g.theme.bg2)
|
||||
.fillRect(5, 5, 171, 30);
|
||||
g.setColor(g.theme.fg2)
|
||||
.setFont("12x20")
|
||||
.drawString(visibleText + cursorChar, 15, 10, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear the space on the screen that the keyboard occupies (not the text the user has written).
|
||||
*/
|
||||
function clearKeySpace() {
|
||||
g.setColor(g.theme.bg)
|
||||
.fillRect(offsetX, offsetY, 176, 176);
|
||||
}
|
||||
|
||||
/**
|
||||
* Based on a touch event, determine which key was pressed by the user.
|
||||
* @param touchEvent
|
||||
* @param keys
|
||||
* @returns {*}
|
||||
*/
|
||||
function getTouchedKey(touchEvent, keys) {
|
||||
return keys.find((key) => {
|
||||
let relX = touchEvent.x - key.x;
|
||||
let relY = touchEvent.y - key.y;
|
||||
return relX > 0 && relX < key.w && relY > 0 && relY < key.h;
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* On a touch event, determine whether a key is touched and take appropriate action if it is.
|
||||
* @param button
|
||||
* @param touchEvent
|
||||
*/
|
||||
function keyTouch(button, touchEvent) {
|
||||
const pressedKey = getTouchedKey(touchEvent, activeKeySet);
|
||||
if (pressedKey == null) {
|
||||
// User tapped empty space.
|
||||
swapKeySet(getMainKeySet(shift !== caps));
|
||||
return;
|
||||
}
|
||||
if (pressedKey.pendingSubKeys) {
|
||||
// We have to generate the subkeys for this key still, but we decided to wait until we needed it!
|
||||
pressedKey.pendingSubKeys = false;
|
||||
pressedKey.getSubKeys()
|
||||
.then(subkeys => {
|
||||
pressedKey.subKeys = subkeys;
|
||||
keyTouch(undefined, touchEvent);
|
||||
})
|
||||
return;
|
||||
}
|
||||
// Haptic feedback
|
||||
Bangle.buzz(25, 1);
|
||||
if (pressedKey.subKeys) {
|
||||
// Hold press for "shift!"
|
||||
if (touchEvent.type > 1) {
|
||||
shift = !shift;
|
||||
swapKeySet(getMainKeySet(shift !== caps));
|
||||
} else {
|
||||
swapKeySet(pressedKey.subKeys);
|
||||
}
|
||||
} else {
|
||||
if (pressedKey.special) {
|
||||
evaluateSpecialFunctions(pressedKey);
|
||||
} else {
|
||||
typed = typed + pressedKey.chars;
|
||||
shift = false;
|
||||
drawTyped(typed, "");
|
||||
swapKeySet(getMainKeySet(shift !== caps));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Manage setting, generating, and rendering new keys when a key set is changed.
|
||||
* @param newKeys
|
||||
*/
|
||||
function swapKeySet(newKeys) {
|
||||
activeKeySet = newKeys;
|
||||
clearKeySpace();
|
||||
drawKeys(activeKeySet);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the key contains any of the special strings that have their own special behaviour when pressed.
|
||||
* @param key
|
||||
*/
|
||||
function evaluateSpecialFunctions(key) {
|
||||
switch (key.special) {
|
||||
case "ok":
|
||||
setTimeout(() => resolveFunction(typed), 50);
|
||||
break;
|
||||
case "del":
|
||||
typed = typed.slice(0, -1);
|
||||
drawTyped(typed, "");
|
||||
break;
|
||||
case "shft":
|
||||
shift = !shift;
|
||||
swapKeySet(getMainKeySet(shift !== caps));
|
||||
break;
|
||||
case "caps":
|
||||
caps = !caps;
|
||||
swapKeySet(getMainKeySet(shift !== caps));
|
||||
break;
|
||||
case "cncl":
|
||||
setTimeout(() => resolveFunction(), 50);
|
||||
break;
|
||||
case "spc":
|
||||
typed = typed + " ";
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
let isCursorVisible = true;
|
||||
|
||||
const blinkInterval = setInterval(() => {
|
||||
if (!activeKeySet) return;
|
||||
isCursorVisible = !isCursorVisible;
|
||||
if (isCursorVisible) {
|
||||
drawTyped(typed, "_");
|
||||
} else {
|
||||
drawTyped(typed, "");
|
||||
}
|
||||
}, 200);
|
||||
|
||||
|
||||
/**
|
||||
* We return a promise but the resolve function is assigned to a variable in the higher function scope. That allows
|
||||
* us to return the promise and resolve it after we are done typing without having to return the entire scope of the
|
||||
* application within the promise.
|
||||
*/
|
||||
return new Promise((resolve, reject) => {
|
||||
g.clear(true);
|
||||
resolveFunction = resolve;
|
||||
keyboardPromise.then((result) => {
|
||||
mainKeys = result[0];
|
||||
mainKeysShift = result[1];
|
||||
swapKeySet(getMainKeySet(shift !== caps));
|
||||
Bangle.setUI({
|
||||
mode: "custom", touch: keyTouch
|
||||
});
|
||||
Bangle.setLocked(false);
|
||||
})
|
||||
}).then((result) => {
|
||||
g.clearRect(Bangle.appRect);
|
||||
clearInterval(blinkInterval);
|
||||
Bangle.setUI();
|
||||
return result;
|
||||
});
|
||||
}
|
||||
|
||||
exports.input = input;
|
||||
exports.generateKeyboard = generateKeyboard;
|
||||
exports.defaultCharSet = defaultCharSet;
|
||||
exports.defaultCharSetShift = defaultCharSetShift;
|
||||
exports.createCharSet = createCharSet;
|
|
@ -0,0 +1,14 @@
|
|||
{ "id": "kbmatry",
|
||||
"name": "Matryoshka Keyboard",
|
||||
"version":"1.06",
|
||||
"description": "A library for text input via onscreen keyboard. Easily enter characters with nested keyboards.",
|
||||
"icon": "icon.png",
|
||||
"type":"textinput",
|
||||
"tags": "keyboard",
|
||||
"supports" : ["BANGLEJS2"],
|
||||
"screenshots": [{"url":"screenshot.png"},{"url":"screenshot6.png"},{"url":"screenshot2.png"},{"url":"screenshot3.png"},{"url":"screenshot4.png"},{"url":"screenshot5.png"},{"url": "help.png"}],
|
||||
"readme": "README.md",
|
||||
"storage": [
|
||||
{"name":"textinput","url":"lib.js"}
|
||||
]
|
||||
}
|
After Width: | Height: | Size: 3.5 KiB |
After Width: | Height: | Size: 2.9 KiB |
After Width: | Height: | Size: 3.3 KiB |
After Width: | Height: | Size: 2.9 KiB |
After Width: | Height: | Size: 3.4 KiB |
After Width: | Height: | Size: 3.5 KiB |
|
@ -3,3 +3,4 @@
|
|||
0.03: Use default Bangle formatter for booleans
|
||||
0.04: Allow moving the cursor
|
||||
0.05: Switch swipe directions for Caps Lock and moving cursor.
|
||||
0.06: Add ability to auto-lowercase after a capital letter insertion.
|
||||
|
|
|
@ -12,6 +12,6 @@ Uses the multitap keypad logic originally from here: http://www.espruino.com/Mor
|
|||

|
||||

|
||||
|
||||
Written by: [Sir Indy](https://github.com/sir-indy) and [Thyttan](https://github.com/thyttan)
|
||||
Written by: [Sir Indy](https://github.com/sir-indy), [Thyttan](https://github.com/thyttan) and [bobrippling](https://github.com/bobrippling).
|
||||
|
||||
For support and discussion please post in the [Bangle JS Forum](http://forum.espruino.com/microcosms/1424/)
|
||||
|
|
|
@ -9,6 +9,7 @@ exports.input = function(options) {
|
|||
if (settings.firstLaunch===undefined) { settings.firstLaunch = true; }
|
||||
if (settings.charTimeout===undefined) { settings.charTimeout = 500; }
|
||||
if (settings.showHelpBtn===undefined) { settings.showHelpBtn = true; }
|
||||
if (settings.autoLowercase===undefined) { settings.autoLowercase = true; }
|
||||
|
||||
var fontSize = "6x15";
|
||||
var Layout = require("Layout");
|
||||
|
@ -89,19 +90,21 @@ exports.input = function(options) {
|
|||
}
|
||||
|
||||
function newCharacter(ch) {
|
||||
displayText();
|
||||
displayText(false);
|
||||
if (ch && textIndex < text.length) textIndex ++;
|
||||
charCurrent = ch;
|
||||
charIndex = 0;
|
||||
}
|
||||
|
||||
function onKeyPad(key) {
|
||||
var retire = 0;
|
||||
deactivateTimeout(charTimeout);
|
||||
// work out which char was pressed
|
||||
if (key==charCurrent) {
|
||||
charIndex = (charIndex+1) % letters[charCurrent].length;
|
||||
text = text.slice(0, -1);
|
||||
} else {
|
||||
retire = charCurrent !== undefined;
|
||||
newCharacter(key);
|
||||
}
|
||||
var newLetter = letters[charCurrent][charIndex];
|
||||
|
@ -110,12 +113,21 @@ exports.input = function(options) {
|
|||
|
||||
text = pre + (caps ? newLetter.toUpperCase() : newLetter.toLowerCase()) + post;
|
||||
|
||||
if(retire)
|
||||
retireCurrent();
|
||||
|
||||
// set a timeout
|
||||
charTimeout = setTimeout(function() {
|
||||
charTimeout = undefined;
|
||||
newCharacter();
|
||||
retireCurrent();
|
||||
}, settings.charTimeout);
|
||||
displayText(charTimeout);
|
||||
displayText(true);
|
||||
}
|
||||
|
||||
function retireCurrent(why) {
|
||||
if (caps && settings.autoLowercase)
|
||||
setCaps();
|
||||
}
|
||||
|
||||
var moveMode = false;
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{ "id": "kbmulti",
|
||||
"name": "Multitap keyboard",
|
||||
"version":"0.05",
|
||||
"version":"0.06",
|
||||
"description": "A library for text input via multitap/T9 style keypad",
|
||||
"icon": "app.png",
|
||||
"type":"textinput",
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
var settings = require('Storage').readJSON("kbmulti.settings.json", true) || {};
|
||||
if (settings.showHelpBtn===undefined) { settings.showHelpBtn = true; }
|
||||
if (settings.charTimeout===undefined) { settings.charTimeout = 500; }
|
||||
if (settings.autoLowercase===undefined) { settings.autoLowercase = true; }
|
||||
return settings;
|
||||
}
|
||||
|
||||
|
@ -21,6 +22,10 @@
|
|||
format: v => v,
|
||||
onchange: v => updateSetting("charTimeout", v),
|
||||
},
|
||||
/*LANG*/'Lowercase after first uppercase': {
|
||||
value: !!settings().autoLowercase,
|
||||
onchange: v => updateSetting("autoLowercase", v)
|
||||
},
|
||||
/*LANG*/'Show help button?': {
|
||||
value: !!settings().showHelpBtn,
|
||||
onchange: v => updateSetting("showHelpBtn", v)
|
||||
|
|
|
@ -5,3 +5,4 @@
|
|||
0.05: Keep drag-function in ram, hopefully improving performance and input reliability somewhat.
|
||||
0.06: Support input of numbers and uppercase characters.
|
||||
0.07: Support input of symbols.
|
||||
0.08: Redone patterns a,e,m,w,z.
|
||||
|
|
|
@ -10,11 +10,11 @@ on the left of the IDE, then do a stroke and copy out the Uint8Array line
|
|||
*/
|
||||
exports.getStrokes = function(mode, cb) {
|
||||
if (mode === exports.INPUT_MODE_ALPHA) {
|
||||
cb("a", new Uint8Array([58, 159, 58, 155, 62, 144, 69, 127, 77, 106, 86, 90, 94, 77, 101, 68, 108, 62, 114, 59, 121, 59, 133, 61, 146, 70, 158, 88, 169, 107, 176, 124, 180, 135, 183, 144, 185, 152]));
|
||||
cb("a", new Uint8Array([31, 157, 33, 149, 37, 131, 42, 112, 46, 97, 49, 83, 52, 72, 56, 64, 59, 59, 63, 53, 68, 48, 74, 47, 80, 47, 88, 50, 98, 63, 109, 94, 114, 115, 116, 130, 117, 141]));
|
||||
cb("b", new Uint8Array([51, 47, 51, 77, 56, 123, 60, 151, 65, 163, 68, 164, 68, 144, 67, 108, 67, 76, 72, 43, 104, 51, 121, 74, 110, 87, 109, 95, 131, 117, 131, 140, 109, 152, 88, 157]));
|
||||
cb("c", new Uint8Array([153, 62, 150, 62, 145, 62, 136, 62, 123, 62, 106, 65, 85, 70, 65, 75, 50, 82, 42, 93, 37, 106, 36, 119, 36, 130, 40, 140, 49, 147, 61, 153, 72, 156, 85, 157, 106, 158, 116, 158]));
|
||||
cb("d", new Uint8Array([57, 178, 57, 176, 55, 171, 52, 163, 50, 154, 49, 146, 47, 135, 45, 121, 44, 108, 44, 97, 44, 85, 44, 75, 44, 66, 44, 58, 44, 48, 44, 38, 46, 31, 48, 26, 58, 21, 75, 20, 99, 26, 120, 35, 136, 51, 144, 70, 144, 88, 137, 110, 124, 131, 106, 145, 88, 153]));
|
||||
cb("e", new Uint8Array([150, 72, 141, 69, 114, 68, 79, 69, 48, 77, 32, 81, 31, 85, 46, 91, 73, 95, 107, 100, 114, 103, 83, 117, 58, 134, 66, 143, 105, 148, 133, 148, 144, 148]));
|
||||
cb("e", new Uint8Array([107, 50, 101, 46, 94, 42, 85, 40, 75, 40, 65, 40, 58, 40, 51, 40, 47, 40, 44, 43, 45, 54, 52, 68, 63, 79, 70, 84, 70, 85, 59, 89, 52, 96, 45, 108, 39, 119, 37, 126, 37, 132, 37, 137, 41, 143, 48, 147, 60, 148, 69, 148, 78, 148, 84, 148, 89, 148]));
|
||||
cb("f", new Uint8Array([157, 52, 155, 52, 148, 52, 137, 52, 124, 52, 110, 52, 96, 52, 83, 52, 74, 52, 67, 52, 61, 52, 57, 52, 55, 52, 52, 52, 52, 54, 52, 58, 52, 64, 54, 75, 58, 97, 59, 117, 60, 130]));
|
||||
cb("g", new Uint8Array([160, 66, 153, 62, 129, 58, 90, 56, 58, 57, 38, 65, 31, 86, 43, 125, 69, 152, 116, 166, 145, 154, 146, 134, 112, 116, 85, 108, 97, 106, 140, 106, 164, 106]));
|
||||
cb("h", new Uint8Array([58, 50, 58, 55, 58, 64, 58, 80, 58, 102, 58, 122, 58, 139, 58, 153, 58, 164, 58, 171, 58, 177, 58, 179, 58, 181, 58, 180, 58, 173, 58, 163, 59, 154, 61, 138, 64, 114, 68, 95, 72, 84, 80, 79, 91, 79, 107, 82, 123, 93, 137, 111, 145, 130, 149, 147, 150, 154, 150, 159]));
|
||||
|
@ -22,7 +22,7 @@ exports.getStrokes = function(mode, cb) {
|
|||
cb("j", new Uint8Array([130, 57, 130, 61, 130, 73, 130, 91, 130, 113, 130, 133, 130, 147, 130, 156, 130, 161, 130, 164, 130, 166, 129, 168, 127, 168, 120, 168, 110, 168, 91, 167, 81, 167, 68, 167]));
|
||||
cb("k", new Uint8Array([149, 63, 147, 68, 143, 76, 136, 89, 126, 106, 114, 123, 100, 136, 86, 147, 72, 153, 57, 155, 45, 152, 36, 145, 29, 131, 26, 117, 26, 104, 27, 93, 30, 86, 35, 80, 45, 77, 62, 80, 88, 96, 113, 116, 130, 131, 140, 142, 145, 149, 148, 153]));
|
||||
cb("l", new Uint8Array([42, 55, 42, 59, 42, 69, 44, 87, 44, 107, 44, 128, 44, 143, 44, 156, 44, 163, 44, 167, 44, 169, 45, 170, 49, 170, 59, 169, 76, 167, 100, 164, 119, 162, 139, 160, 163, 159]));
|
||||
cb("m", new Uint8Array([49, 165, 48, 162, 46, 156, 44, 148, 42, 138, 42, 126, 42, 113, 43, 101, 45, 91, 47, 82, 49, 75, 51, 71, 54, 70, 57, 70, 61, 74, 69, 81, 75, 91, 84, 104, 94, 121, 101, 132, 103, 137, 106, 130, 110, 114, 116, 92, 125, 75, 134, 65, 139, 62, 144, 66, 148, 83, 151, 108, 155, 132, 157, 149]));
|
||||
cb("m", new Uint8Array([36, 139, 36, 120, 36, 99, 36, 79, 36, 61, 41, 45, 56, 43, 71, 46, 77, 66, 77, 93, 77, 97, 84, 69, 93, 51, 107, 47, 118, 53, 123, 79, 124, 115, 124, 140]));
|
||||
cb("n", new Uint8Array([50, 165, 50, 160, 50, 153, 50, 140, 50, 122, 50, 103, 50, 83, 50, 65, 50, 52, 50, 45, 50, 43, 52, 52, 57, 67, 66, 90, 78, 112, 93, 131, 104, 143, 116, 152, 127, 159, 135, 160, 141, 150, 148, 125, 154, 96, 158, 71, 161, 56, 162, 49]));
|
||||
cb("o", new Uint8Array([107, 58, 104, 58, 97, 61, 87, 68, 75, 77, 65, 88, 58, 103, 54, 116, 53, 126, 55, 135, 61, 143, 75, 149, 91, 150, 106, 148, 119, 141, 137, 125, 143, 115, 146, 104, 146, 89, 142, 78, 130, 70, 116, 65, 104, 62]));
|
||||
cb("p", new Uint8Array([29, 47, 29, 55, 29, 75, 29, 110, 29, 145, 29, 165, 29, 172, 29, 164, 30, 149, 37, 120, 50, 91, 61, 74, 72, 65, 85, 61, 103, 61, 118, 63, 126, 69, 129, 76, 130, 87, 126, 98, 112, 108, 97, 114, 87, 116]));
|
||||
|
@ -32,10 +32,10 @@ exports.getStrokes = function(mode, cb) {
|
|||
cb("t", new Uint8Array([45, 55, 48, 55, 55, 55, 72, 55, 96, 55, 120, 55, 136, 55, 147, 55, 152, 55, 155, 55, 157, 55, 158, 56, 158, 60, 156, 70, 154, 86, 151, 102, 150, 114, 148, 125, 148, 138, 148, 146]));
|
||||
cb("u", new Uint8Array([35, 52, 35, 59, 35, 73, 35, 90, 36, 114, 38, 133, 42, 146, 49, 153, 60, 157, 73, 158, 86, 156, 100, 152, 112, 144, 121, 131, 127, 114, 132, 97, 134, 85, 135, 73, 136, 61, 136, 56]));
|
||||
cb("v", new Uint8Array([36, 55, 37, 59, 40, 68, 45, 83, 51, 100, 58, 118, 64, 132, 69, 142, 71, 149, 73, 156, 76, 158, 77, 160, 77, 159, 80, 151, 82, 137, 84, 122, 86, 111, 90, 91, 91, 78, 91, 68, 91, 63, 92, 61, 97, 61, 111, 61, 132, 61, 150, 61, 162, 61]));
|
||||
cb("w", new Uint8Array([25, 46, 25, 82, 25, 119, 33, 143, 43, 153, 60, 147, 73, 118, 75, 91, 76, 88, 85, 109, 96, 134, 107, 143, 118, 137, 129, 112, 134, 81, 134, 64, 134, 55]));
|
||||
cb("w", new Uint8Array([35, 37, 35, 44, 35, 58, 35, 81, 35, 110, 35, 129, 39, 136, 45, 140, 51, 141, 60, 137, 70, 121, 76, 99, 78, 79, 78, 70, 78, 69, 83, 89, 89, 112, 93, 127, 97, 135, 102, 136, 108, 131, 115, 116, 119, 93, 122, 72, 123, 55, 123, 43]));
|
||||
cb("x", new Uint8Array([56, 63, 56, 67, 57, 74, 60, 89, 66, 109, 74, 129, 85, 145, 96, 158, 107, 164, 117, 167, 128, 164, 141, 155, 151, 140, 159, 122, 166, 105, 168, 89, 170, 81, 170, 73, 169, 66, 161, 63, 141, 68, 110, 83, 77, 110, 55, 134, 47, 145]));
|
||||
cb("y", new Uint8Array([30, 41, 30, 46, 30, 52, 30, 63, 30, 79, 33, 92, 38, 100, 47, 104, 54, 107, 66, 105, 79, 94, 88, 82, 92, 74, 94, 77, 96, 98, 96, 131, 94, 151, 91, 164, 85, 171, 75, 171, 71, 162, 74, 146, 84, 130, 95, 119, 106, 113]));
|
||||
cb("z", new Uint8Array([29, 62, 35, 62, 43, 62, 63, 62, 87, 62, 110, 62, 125, 62, 134, 62, 138, 62, 136, 63, 122, 68, 103, 77, 85, 91, 70, 107, 59, 120, 50, 132, 47, 138, 43, 143, 41, 148, 42, 151, 53, 155, 80, 157, 116, 158, 146, 158, 163, 158]));
|
||||
cb("z", new Uint8Array([39, 38, 45, 38, 53, 38, 62, 38, 72, 38, 82, 38, 89, 38, 96, 38, 99, 39, 95, 48, 82, 68, 70, 87, 60, 100, 50, 117, 42, 132, 42, 140, 45, 143, 53, 143, 67, 143, 81, 143]));
|
||||
cb("SHIFT", new Uint8Array([100, 160, 100, 50]));
|
||||
} else if (mode === exports.INPUT_MODE_NUM) {
|
||||
cb("0", new Uint8Array([82, 50, 76, 50, 67, 50, 59, 50, 50, 51, 43, 57, 38, 68, 34, 83, 33, 95, 33, 108, 34, 121, 42, 136, 57, 148, 72, 155, 85, 157, 98, 155, 110, 149, 120, 139, 128, 127, 134, 119, 137, 114, 138, 107, 138, 98, 138, 88, 138, 77, 137, 71, 134, 65, 128, 60, 123, 58]));
|
||||
|
@ -210,7 +210,7 @@ exports.input = function(options) {
|
|||
if (o.stroke!==undefined && o.xy.length >= 6 && isStrokeInside(R, o.xy)) {
|
||||
var ch = o.stroke;
|
||||
if (ch=="\b") text = text.slice(0,-1);
|
||||
else if (ch==="SHIFT") { shift=!shift; Bangle.drawWidgets(); }
|
||||
else if (ch==="SHIFT") { shift=!shift; WIDGETS.kbswipe.draw(); }
|
||||
else text += shift ? ch.toUpperCase() : ch;
|
||||
}
|
||||
lastDrag = undefined;
|
||||
|
@ -226,7 +226,7 @@ exports.input = function(options) {
|
|||
shift = false;
|
||||
setupStrokes();
|
||||
show();
|
||||
Bangle.drawWidgets();
|
||||
WIDGETS.kbswipe.draw();
|
||||
}
|
||||
|
||||
Bangle.on('stroke',strokeHandler);
|
||||
|
@ -239,7 +239,7 @@ exports.input = function(options) {
|
|||
area:"tl",
|
||||
width: 36, // 3 chars, 6*2 px/char
|
||||
draw: function() {
|
||||
g.reset();
|
||||
g.reset().clearRect(this.x, this.y, this.x + this.width-1, this.y + 23);
|
||||
g.setFont("6x8:2x3");
|
||||
g.setColor("#f00");
|
||||
if (input_mode === exports.INPUT_MODE_ALPHA) {
|
||||
|
@ -251,6 +251,7 @@ exports.input = function(options) {
|
|||
}
|
||||
}
|
||||
};
|
||||
Bangle.drawWidgets();
|
||||
|
||||
return new Promise((resolve,reject) => {
|
||||
Bangle.setUI({mode:"custom", drag:e=>{
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{ "id": "kbswipe",
|
||||
"name": "Swipe keyboard",
|
||||
"version":"0.07",
|
||||
"version":"0.08",
|
||||
"description": "A library for text input via PalmOS style swipe gestures (beta!)",
|
||||
"icon": "app.png",
|
||||
"type":"textinput",
|
||||
|
|
|
@ -4,3 +4,4 @@
|
|||
0.04: Add masking widget input to other apps (using espruino/Espruino#2151), add a oversize option to increase the touch area.
|
||||
0.05: Prevent drawing into app area.
|
||||
0.06: Fix issue where .draw was being called by reference (not allowing widgets to be hidden)
|
||||
0.07: Handle the swipe event that is generated when draging to change light intensity, so it doesn't trigger some other swipe handler.
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
"id": "lightswitch",
|
||||
"name": "Light Switch Widget",
|
||||
"shortName": "Light Switch",
|
||||
"version": "0.06",
|
||||
"version": "0.07",
|
||||
"description": "A fast way to switch LCD backlight on/off, change the brightness and show the lock status. All in one widget.",
|
||||
"icon": "images/app.png",
|
||||
"screenshots": [
|
||||
|
|
|
@ -165,13 +165,12 @@
|
|||
w.changeValue(value, event.b);
|
||||
|
||||
// masks this drag event by messing up the event handler
|
||||
// see https://github.com/espruino/Espruino/issues/2151
|
||||
Bangle.removeListener("drag", w.dragListener);
|
||||
Bangle["#ondrag"] = [w.dragListener].concat(Bangle["#ondrag"]);
|
||||
E.stopEventPropagation&&E.stopEventPropagation();
|
||||
|
||||
// on touch release remove drag listener and reset drag status to indicate stopped drag action
|
||||
if (!event.b) {
|
||||
Bangle.removeListener("drag", w.dragListener);
|
||||
Bangle.removeListener("swipe", w.swipeListener);
|
||||
w.dragStatus = "off";
|
||||
}
|
||||
|
||||
|
@ -181,6 +180,11 @@
|
|||
value = undefined;
|
||||
},
|
||||
|
||||
swipeListener: function(_,__) {
|
||||
// masks this swipe event by messing up the event handler
|
||||
E.stopEventPropagation&&E.stopEventPropagation();
|
||||
},
|
||||
|
||||
// listener function //
|
||||
// touch listener for light control
|
||||
touchListener: function(button, cursor) {
|
||||
|
@ -197,12 +201,14 @@
|
|||
Bangle.buzz(25);
|
||||
// check if drag is disabled
|
||||
if (w.dragDelay) {
|
||||
// add drag listener at first position
|
||||
// add drag and swipe listeners at respective first position
|
||||
Bangle["#ondrag"] = [w.dragListener].concat(Bangle["#ondrag"]);
|
||||
Bangle["#onswipe"] = [w.swipeListener].concat(Bangle["#onswipe"]);
|
||||
// set drag timeout
|
||||
w.dragStatus = setTimeout((w) => {
|
||||
// remove drag listener
|
||||
// remove drag and swipe listeners
|
||||
Bangle.removeListener("drag", w.dragListener);
|
||||
Bangle.removeListener("swipe", w.swipeListener);
|
||||
// clear drag timeout
|
||||
if (typeof w.dragStatus === "number") clearTimeout(w.dragStatus);
|
||||
// reset drag status to indicate stopped drag action
|
||||
|
|
|
@ -91,3 +91,8 @@
|
|||
0.66: Updated Navigation handling to work with new Gadgetbridge release
|
||||
0.67: Support for 'Ignore' for messages from Gadgetbridge
|
||||
Message view is now taller, and we use swipe left/right to dismiss messages rather than buttons
|
||||
0.68: More navigation icons (for roundabouts)
|
||||
0.69: More navigation icons (keep/uturn left/right)
|
||||
Nav messages with '/' now get split on newlines
|
||||
0.70: Handle nav messages from newer Gadgetbridge builds that output distance as a String
|
||||
If we receive a 'music' message and we're in the messages app (but not showing a message) show music (#2814)
|
|
@ -15,8 +15,9 @@
|
|||
// a message
|
||||
require("messages").pushMessage({"t":"add","id":1575479849,"src":"Skype","title":"My Friend","body":"Hey! How's everything going?",positive:1,negative:1})
|
||||
// maps
|
||||
GB({t:"nav",src:"maps",title:"Navigation",instr:"High St towards Tollgate Rd",distance:966,action:"continue",eta:"08:39"})
|
||||
GB({t:"nav",src:"maps",title:"Navigation",instr:"High St",distance:12345,action:"left_slight",eta:"08:39"})
|
||||
GB({t:"nav",src:"maps",title:"Navigation",instr:"High St towards Tollgate Rd",distance:"966yd",action:"continue",eta:"08:39"})
|
||||
GB({t:"nav",src:"maps",title:"Navigation",instr:"High St",distance:"12km",action:"left_slight",eta:"08:39"})
|
||||
GB({t:"nav",src:"maps",title:"Navigation",instr:"Main St / I-29 ALT / Centerpoint Dr",distance:12345,action:"left_slight",eta:"08:39"})
|
||||
// call
|
||||
require("messages").pushMessage({"t":"add","id":"call","src":"Phone","title":"Bob","body":"12421312",positive:true,negative:true})
|
||||
*/
|
||||
|
@ -27,7 +28,7 @@ var fontMedium = g.getFonts().includes("6x15")?"6x15":"6x8:2";
|
|||
var fontBig = g.getFonts().includes("12x20")?"12x20":"6x8:2";
|
||||
var fontLarge = g.getFonts().includes("6x15")?"6x15:2":"6x8:4";
|
||||
var fontVLarge = g.getFonts().includes("6x15")?"12x20:2":"6x8:5";
|
||||
var active; // active screen
|
||||
var active; // active screen (undefined/"list"/"music"/"map"/"message"/"scroller"/"settings")
|
||||
var openMusic = false; // go back to music screen after we handle something else?
|
||||
// hack for 2v10 firmware's lack of ':size' font handling
|
||||
try {
|
||||
|
@ -67,7 +68,7 @@ var onMessagesModified = function(type,msg) {
|
|||
}
|
||||
if (msg && msg.id=="music") {
|
||||
if (msg.state && msg.state!="play") openMusic = false; // no longer playing music to go back to
|
||||
if (active!="music") return; // don't open music over other screens
|
||||
if ((active!=undefined) && (active!="list") && (active!="music")) return; // don't open music over other screens (but do if we're in the main menu)
|
||||
}
|
||||
showMessage(msg&&msg.id);
|
||||
};
|
||||
|
@ -81,22 +82,36 @@ E.on("kill", saveMessages);
|
|||
function showMapMessage(msg) {
|
||||
active = "map";
|
||||
var m, distance, street, target, img;
|
||||
if (msg.distance!==undefined)
|
||||
if ("string"==typeof msg.distance) // new gadgetbridge
|
||||
distance = msg.distance;
|
||||
else if ("number"==typeof msg.distance) // 0.74 gadgetbridge
|
||||
distance = require("locale").distance(msg.distance);
|
||||
if (msg.instr) {
|
||||
if (msg.instr.includes("towards") || msg.instr.includes("toward")) {
|
||||
m = msg.instr.split(/towards|toward/);
|
||||
var instr = msg.instr.replace(/\s*\/\s*/g," \/\n"); // convert slashes to newlines
|
||||
if (instr.includes("towards") || instr.includes("toward")) {
|
||||
m = instr.split(/towards|toward/);
|
||||
target = m[0].trim();
|
||||
street = m[1].trim();
|
||||
}else
|
||||
target = msg.instr;
|
||||
target = instr;
|
||||
}
|
||||
if (msg.action=="continue") img = "EBgBAIABwAPgD/Af+D/8f/773/PPY8cDwAPAA8ADwAPAA8AAAAPAA8ADwAAAA8ADwAPA";
|
||||
else if (msg.action=="left") img = "GhcBAYAAAPAAAHwAAD4AAB8AAA+AAAf//8P///x///+PAAPx4AA8fAAHD4ABwfAAcDwAHAIABwAAAcAAAHAAABwAAAcAAAHAAABwAAAc";
|
||||
else if (msg.action=="right") img = "GhcBAABgAAA8AAAPgAAB8AAAPgAAB8D///j///9///+/AAPPAAHjgAD44AB8OAA+DgAPA4ABAOAAADgAAA4AAAOAAADgAAA4AAAOAAAA";
|
||||
else if (msg.action=="left_slight") img = "ERgB//B/+D/8H4AP4Af4A74Bz4Dj4HD4OD4cD4AD4ADwADwADgAHgAPAAOAAcAA4ABwADgAH";
|
||||
else if (msg.action=="right_slight") img = "ERgBB/+D/8H/4APwA/gD/APuA+cD44Phw+Dj4HPgAeAB4ADgAPAAeAA4ABwADgAHAAOAAcAA";
|
||||
else if (msg.action=="finish") img = "HhsBAcAAAD/AAAH/wAAPB4AAeA4AAcAcAAYIcAA4cMAA48MAA4cMAAYAcAAcAcAAcA4AAOA4AAOBxjwHBzjwHjj/4Dnn/4B3P/4B+Pj4A8fj8Acfj8AI//8AA//+AA/j+AB/j+AB/j/A";
|
||||
switch (msg.action) {
|
||||
case "continue": img = "EBgBAIABwAPgD/Af+D/8f/773/PPY8cDwAPAA8ADwAPAA8AAAAPAA8ADwAAAA8ADwAPA";break;
|
||||
case "left": img = "GhcBAYAAAPAAAHwAAD4AAB8AAA+AAAf//8P///x///+PAAPx4AA8fAAHD4ABwfAAcDwAHAIABwAAAcAAAHAAABwAAAcAAAHAAABwAAAc";break;
|
||||
case "right": img = "GhcBAABgAAA8AAAPgAAB8AAAPgAAB8D///j///9///+/AAPPAAHjgAD44AB8OAA+DgAPA4ABAOAAADgAAA4AAAOAAADgAAA4AAAOAAAA";break;
|
||||
case "left_slight": img = "ERgB//B/+D/8H4AP4Af4A74Bz4Dj4HD4OD4cD4AD4ADwADwADgAHgAPAAOAAcAA4ABwADgAH";break;
|
||||
case "right_slight": img = "ERgBB/+D/8H/4APwA/gD/APuA+cD44Phw+Dj4HPgAeAB4ADgAPAAeAA4ABwADgAHAAOAAcAA";break;
|
||||
case "keep_left": img = "ERmBAACAAOAB+AD+AP+B/+H3+PO+8c8w4wBwADgAHgAPAAfAAfAAfAAfAAeAAeAAcAA8AA4ABwADgA==";break;
|
||||
case "keep_right": img = "ERmBAACAAOAA/AD+AP+A//D/fPueeceY4YBwADgAPAAeAB8AHwAfAB8ADwAPAAcAB4ADgAHAAOAAAA==";break;
|
||||
case "uturn_left": img = "GRiBAAAH4AAP/AAP/wAPj8APAfAPAHgHgB4DgA8BwAOA4AHAcADsOMB/HPA7zvgd9/gOf/gHH/gDh/gBwfgA4DgAcBgAOAAAHAAADgAABw==";break;
|
||||
case "uturn_right": img = "GRiBAAPwAAf+AAf/gAfj4AfAeAPAHgPADwHgA4DgAcBwAOA4AHAcBjhuB5x/A+57gP99wD/84A/8cAP8OAD8HAA4DgAMBwAAA4AAAcAAAA==";break;
|
||||
case "finish": img = "HhsBAcAAAD/AAAH/wAAPB4AAeA4AAcAcAAYIcAA4cMAA48MAA4cMAAYAcAAcAcAAcA4AAOA4AAOBxjwHBzjwHjj/4Dnn/4B3P/4B+Pj4A8fj8Acfj8AI//8AA//+AA/j+AB/j+AB/j/A";break;
|
||||
case "roundabout_left": img = "HBaCAAADwAAAAAAAD/AAAVUAAD/wABVVUAD/wABVVVQD/wAAVABUD/wAAVAAFT/////wABX/////8AAF//////AABT/////wABUP/AAD/AAVA/8AA/8AVAD/wAD//VQAP/AAP/1QAA/wAA/9AAADwAAD/AAAAAAAA/wAAAAAAAP8AAAAAAAD/AAAAAAAA/wAAAAAAAP8AAAAAAAD/AA=";break;
|
||||
case "roundabout_right": img = "HRaCAAAAAAAA8AAAP/8AAP8AAD///AA/8AA////AA/8AP/A/8AA/8A/wAP8AA/8P8AA/////8/wAD///////AAD//////8AAP////8P8ABUAAP/A/8AVQAD/wA//1UAA/8AA//VAAP/AAA/9AAA/wAAAPwAAA8AAAA/AAAAAAAAD8AAAAAAAAPwAAAAAAAA/AAAAAAAAD8AAAAAAAAPwAAAAAAA=";break;
|
||||
case "roundabout_straight": img = "EBuCAAADwAAAD/AAAD/8AAD//wAD///AD///8D/P8/z/D/D//A/wPzAP8AwA//UAA//1QA//9VA/8AFUP8AAVD8AAFQ/AABUPwAAVD8AAFQ/wABUP/ABVA//9VAD//VAAP/1AAAP8AAAD/AAAA/wAA==";break;
|
||||
case "roundabout_uturn": img = "ICCBAAAAAAAAAAAAAAAAAAAP4AAAH/AAAD/4AAB4fAAA8DwAAPAcAADgHgAA4B4AAPAcAADwPAAAeHwAADz4AAAc8AAABPAAAADwAAAY8YAAPPPAAD73gAAf/4AAD/8AABf8AAAb+AAAHfAAABzwAAAcYAAAAAAAAAAAAAAAAAAAAAAA";break;
|
||||
}
|
||||
//FIXME: what about countries where we drive on the right? How will we know to flip the icons?
|
||||
|
||||
layout = new Layout({ type:"v", c: [
|
||||
{type:"txt", font:street?fontMedium:fontLarge, label:target, bgCol:g.theme.bg2, col: g.theme.fg2, fillx:1, pad:3 },
|
||||
|
@ -202,7 +217,7 @@ function showMessageScroller(msg) {
|
|||
var bodyFont = fontBig;
|
||||
g.setFont(bodyFont);
|
||||
var lines = [];
|
||||
if (msg.title) lines = g.wrapString(msg.title, g.getWidth()-10)
|
||||
if (msg.title) lines = g.wrapString(msg.title, g.getWidth()-10);
|
||||
var titleCnt = lines.length;
|
||||
if (titleCnt) lines.push(""); // add blank line after title
|
||||
lines = lines.concat(g.wrapString(msg.body, g.getWidth()-10),["",/*LANG*/"< Back"]);
|
||||
|
@ -390,6 +405,7 @@ function checkMessages(options) {
|
|||
options=options||{};
|
||||
// If no messages, just show 'no messages' and return
|
||||
if (!MESSAGES.length) {
|
||||
active=undefined; // no messages
|
||||
if (!options.clockIfNoMsg) return E.showPrompt(/*LANG*/"No Messages",{
|
||||
title:/*LANG*/"Messages",
|
||||
img:require("heatshrink").decompress(atob("kkk4UBrkc/4AC/tEqtACQkBqtUDg0VqAIGgoZFDYQIIM1sD1QAD4AIBhnqA4WrmAIBhc6BAWs8AIBhXOBAWz0AIC2YIC5wID1gkB1c6BAYFBEQPqBAYXBEQOqBAnDAIQaEnkAngaEEAPDFgo+IKA5iIOhCGIAFb7RqAIGgtUBA0VqobFgNVA")),
|
||||
|
@ -419,7 +435,7 @@ function checkMessages(options) {
|
|||
// no new messages - go to clock?
|
||||
if (options.clockIfAllRead && newMessages.length==0)
|
||||
return load();
|
||||
active = "main";
|
||||
active = "list";
|
||||
// Otherwise show a menu
|
||||
E.showScroller({
|
||||
h : 48,
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
"id": "messagegui",
|
||||
"name": "Message UI",
|
||||
"shortName": "Messages",
|
||||
"version": "0.67",
|
||||
"version": "0.70",
|
||||
"description": "Default app to display notifications from iOS and Gadgetbridge/Android",
|
||||
"icon": "app.png",
|
||||
"type": "app",
|
||||
|
|
|
@ -3,3 +3,4 @@
|
|||
0.57: Optimize saving empty message list
|
||||
0.58: show/hide "messages" widget directly, instead of through library stub
|
||||
0.59: fixes message timeout by using setinterval, as it was intended. So the buzz is triggered every x seconds until the timeout occours.
|
||||
0.60: Bump version to allow new buzz.js module to be loaded - fixes memory/performance hog when buzz called
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"id": "messages",
|
||||
"name": "Messages",
|
||||
"version": "0.59",
|
||||
"version": "0.60",
|
||||
"description": "Library to handle, load and store message events received from Android/iOS",
|
||||
"icon": "app.png",
|
||||
"type": "module",
|
||||
|
|
|
@ -9,3 +9,4 @@
|
|||
0.10: Improvements to help notifications work with themes
|
||||
0.11: Fix regression that caused no notifications and corrupted background
|
||||
0.12: Add Bangle.js 2 support with Bangle.setLCDOverlay
|
||||
0.13: Add a default title background for the dark theme
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
"id": "notify",
|
||||
"name": "Notifications (default)",
|
||||
"shortName": "Notifications",
|
||||
"version": "0.12",
|
||||
"version": "0.13",
|
||||
"description": "Provides the default `notify` module used by applications to display notifications on the screen. This module is installed by default by client applications such as the Gadgetbridge app. Installing `Fullscreen Notifications` replaces this module with a version that displays the notifications using the full screen",
|
||||
"icon": "notify.png",
|
||||
"type": "notify",
|
||||
|
|
|
@ -103,7 +103,7 @@ exports.show = function(options) {
|
|||
b -= 2;h -= 2;
|
||||
// title bar
|
||||
if (options.title || options.src) {
|
||||
g.setColor(options.titleBgColor||0x39C7).fillRect(x,y, r,y+20);
|
||||
g.setColor("titleBgColor" in options ? options.titleBgColor : g.theme.dark ? 0x1 : 0x39C7).fillRect(x,y, r,y+20);
|
||||
const title = options.title||options.src;
|
||||
g.setColor(g.theme.fg).setFontAlign(-1, -1, 0).setFont("6x8", 2);
|
||||
g.drawString(title.trim().substring(0, 13), x+25,y+3);
|
||||
|
|
|
@ -100,7 +100,7 @@ exports.show = function(options) {
|
|||
gg.clearRect(x,y, r,b);
|
||||
// title bar
|
||||
if (options.title || options.src) {
|
||||
gg.setColor(options.titleBgColor||0x39C7).fillRect(x,y, r,y+20);
|
||||
gg.setColor("titleBgColor" in options ? options.titleBgColor : g.theme.dark ? 0x1 : 0x39C7).fillRect(x,y, r,y+20);
|
||||
const title = options.title||options.src;
|
||||
gg.setColor(g.theme.fg).setFontAlign(-1, -1, 0).setFont("6x8", 2);
|
||||
gg.drawString(title.trim().substring(0, 13), x+25,y+3);
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
0.01: New App!
|
||||
0.02: New Results menu item to show the formula and values used.
|
||||
0.03: Adds haptics to Input screen and long press "C" to go back.
|
||||
0.04: Fix font size not resetting on subsequent values in results screen
|
||||
|
|
|
@ -291,13 +291,11 @@ function calculateValue(calculatedVariable, variableValues) {
|
|||
function drawResultScreen(result) {
|
||||
let drawPage = function() {
|
||||
clearScreen();
|
||||
let fontSize = 30; // Initial font size
|
||||
let lineSpacing = 15; // Space between lines
|
||||
|
||||
// Define the vertical positions of the titles
|
||||
let titlePositions = [10, 72, 132];
|
||||
|
||||
let lineSpacing = 15; // Space between lines
|
||||
for (let i = 0; i < result.result.length; i++) {
|
||||
let fontSize = 30; // Initial font size
|
||||
let currentResult = result.result[i];
|
||||
let resultTitle = currentResult[0];
|
||||
let resultValue = currentResult[1];
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
"id": "ohmcalc",
|
||||
"name": "Ohm's Law Calculator",
|
||||
"shortName": "Ohm's Law Calc",
|
||||
"version": "0.03",
|
||||
"version": "0.04",
|
||||
"description": "A smart and simple calculator for Ohm's Law calculations, designed specifically for Bangle.js 2 smartwatches. Handles voltage, current, resistance, and power calculations with smart logic to prevent invalid inputs.",
|
||||
"icon": "app.png",
|
||||
"type": "app",
|
||||
|
|
|
@ -16,6 +16,5 @@
|
|||
],
|
||||
"data": [
|
||||
{"name":"popcon.cache.json"}
|
||||
],
|
||||
"sortorder": -10
|
||||
]
|
||||
}
|
||||
|
|
|
@ -30,3 +30,6 @@
|
|||
Altitude graphing now uses barometer altitude if it exists
|
||||
plotTrack in widget allows track to be drawn in the background (doesn't block execution)
|
||||
0.24: Can now specify `setRecording(true, {force:...` to not show a menu
|
||||
0.25: Widget now has `isRecording()` for retrieving recording status.
|
||||
0.26: Now record filename based on date
|
||||
0.27: Fix first ever recorded filename being log0 (now all are dated)
|
|
@ -33,10 +33,8 @@ function updateSettings() {
|
|||
function getTrackNumber(filename) {
|
||||
var trackNum = 0;
|
||||
var matches = filename.match(/^recorder\.log(.*)\.csv$/);
|
||||
if (matches) {
|
||||
trackNum = parseInt(matches[1]||0);
|
||||
}
|
||||
return trackNum;
|
||||
if (matches) return matches[1];
|
||||
return 0;
|
||||
}
|
||||
|
||||
function showMainMenu() {
|
||||
|
@ -62,23 +60,13 @@ function showMainMenu() {
|
|||
WIDGETS["recorder"].setRecording(v).then(function() {
|
||||
//print("Record start Complete");
|
||||
loadSettings();
|
||||
print("Recording: "+settings.recording);
|
||||
//print("Recording: "+settings.recording);
|
||||
showMainMenu();
|
||||
});
|
||||
}, 1);
|
||||
}
|
||||
},
|
||||
/*LANG*/'File #': {
|
||||
value: getTrackNumber(settings.file),
|
||||
min: 0,
|
||||
max: 99,
|
||||
step: 1,
|
||||
onchange: v => {
|
||||
settings.recording = false; // stop recording if we change anything
|
||||
settings.file = "recorder.log"+v+".csv";
|
||||
updateSettings();
|
||||
}
|
||||
},
|
||||
/*LANG*/'File' : {value:getTrackNumber(settings.file)},
|
||||
/*LANG*/'View Tracks': ()=>{viewTracks();},
|
||||
/*LANG*/'Time Period': {
|
||||
value: settings.period||10,
|
||||
|
@ -110,7 +98,7 @@ function viewTracks() {
|
|||
var found = false;
|
||||
require("Storage").list(/^recorder\.log.*\.csv$/,{sf:true}).forEach(filename=>{
|
||||
found = true;
|
||||
menu[/*LANG*/"Track "+getTrackNumber(filename)] = ()=>viewTrack(filename,false);
|
||||
menu[/*LANG*/getTrackNumber(filename)] = ()=>viewTrack(filename,false);
|
||||
});
|
||||
if (!found)
|
||||
menu[/*LANG*/"No Tracks found"] = function(){};
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
"id": "recorder",
|
||||
"name": "Recorder",
|
||||
"shortName": "Recorder",
|
||||
"version": "0.24",
|
||||
"version": "0.27",
|
||||
"description": "Record GPS position, heart rate and more in the background, then download to your PC.",
|
||||
"icon": "app.png",
|
||||
"tags": "tool,outdoors,gps,widget",
|
||||
|
@ -15,5 +15,8 @@
|
|||
{"name":"recorder.wid.js","url":"widget.js"},
|
||||
{"name":"recorder.settings.js","url":"settings.js"}
|
||||
],
|
||||
"data": [{"name":"recorder.json","url":"app-settings.json"},{"wildcard":"recorder.log?.csv","storageFile":true}]
|
||||
"data": [
|
||||
{"name":"recorder.json","url":"app-settings.json"},
|
||||
{"wildcard":"recorder.log?.csv","storageFile":true}
|
||||
]
|
||||
}
|
||||
|
|
|
@ -231,6 +231,8 @@
|
|||
},getRecorders:getRecorders,reload:function() {
|
||||
reload();
|
||||
Bangle.drawWidgets(); // relayout all widgets
|
||||
},isRecording:function() {
|
||||
return !!writeInterval;
|
||||
},setRecording:function(isOn, options) {
|
||||
/* options = {
|
||||
force : [optional] "append"/"new"/"overwrite" - don't ask, just do what's requested
|
||||
|
@ -238,13 +240,14 @@
|
|||
var settings = loadSettings();
|
||||
options = options||{};
|
||||
if (isOn && !settings.recording) {
|
||||
var date=(new Date()).toISOString().substr(0,10).replace(/-/g,""), trackNo=10;
|
||||
if (!settings.file) { // if no filename set
|
||||
settings.file = "recorder.log0.csv";
|
||||
settings.file = "recorder.log" + date + trackNo.toString(36) + ".csv";
|
||||
} else if (require("Storage").list(settings.file).length){ // if file exists
|
||||
if (!options.force) { // if not forced, ask the question
|
||||
g.reset(); // work around bug in 2v17 and earlier where bg color wasn't reset
|
||||
return E.showPrompt(
|
||||
/*LANG*/"Overwrite\nLog " + settings.file.match(/\d+/)[0] + "?",
|
||||
/*LANG*/"Overwrite\nLog " + settings.file.match(/^recorder\.log(.*)\.csv$/)[1] + "?",
|
||||
{ title:/*LANG*/"Recorder",
|
||||
buttons:{/*LANG*/"Yes":"overwrite",/*LANG*/"No":"cancel",/*LANG*/"New":"new",/*LANG*/"Append":"append"}
|
||||
}).then(selection=>{
|
||||
|
@ -260,11 +263,12 @@
|
|||
// wipe the file
|
||||
require("Storage").open(settings.file,"r").erase();
|
||||
} else if (options.force=="new") {
|
||||
// new file - find the max log file number and add one
|
||||
var maxNumber=0;
|
||||
require("Storage").list(/recorder.log.*/).forEach( fn => maxNumber = Math.max(maxNumber, fn.match(/\d+/)[0]) );
|
||||
var newFileName = "recorder.log" + (maxNumber + 1) + ".csv";
|
||||
// FIXME: use date?
|
||||
// new file - use the current date
|
||||
var newFileName;
|
||||
do { // while a file exists, add one to the letter after the date
|
||||
newFileName = "recorder.log" + date + trackNo.toString(36) + ".csv";
|
||||
trackNo++;
|
||||
} while (require("Storage").list(newFileName).length);
|
||||
settings.file = newFileName;
|
||||
} else throw new Error("Unknown options.force, "+options.force);
|
||||
}
|
||||
|
|
|
@ -36,25 +36,35 @@ function colorBandsToResistance(colorBands) {
|
|||
}
|
||||
|
||||
function resistanceToColorBands(resistance, tolerance) {
|
||||
let resistanceStr = resistance.toString();
|
||||
let firstDigit, secondDigit, multiplier;
|
||||
if (resistance < 1) {
|
||||
// The resistance is less than 1, so we need to handle this case specially
|
||||
let count = 0;
|
||||
while (resistance < 1) {
|
||||
resistance *= 10;
|
||||
count++;
|
||||
}
|
||||
// Now, resistance is a whole number and count is how many times we had to multiply by 10
|
||||
let resistanceStr = resistance.toString();
|
||||
firstDigit = 0; // Set the first band color to be black
|
||||
secondDigit = Number(resistanceStr.charAt(0)); // Set the second band color to be the significant digit
|
||||
// Use count to determine the multiplier
|
||||
multiplier = count === 1 ? 0.1 : 0.01;
|
||||
} else {
|
||||
// Convert the resistance to a string so we can manipulate it easily
|
||||
let resistanceStr = resistance.toString();
|
||||
if (resistanceStr.length === 1) { // Check if resistance is a single digit
|
||||
firstDigit = 0;
|
||||
secondDigit = Number(resistanceStr.charAt(0));
|
||||
multiplier = 0;
|
||||
} else if (resistance >= 100) {
|
||||
multiplier = 1; // Set multiplier to 1 for single digit resistance values
|
||||
} else {
|
||||
// Extract the first two digits from the resistance value
|
||||
firstDigit = Number(resistanceStr.charAt(0));
|
||||
secondDigit = Number(resistanceStr.charAt(1));
|
||||
// Calculate the multiplier
|
||||
multiplier = resistanceStr.length - 2;
|
||||
} else {
|
||||
// For values between 10-99, shift the color to the first band
|
||||
firstDigit = Number(resistanceStr.charAt(0));
|
||||
secondDigit = Number(resistanceStr.charAt(1));
|
||||
multiplier = 0;
|
||||
// Calculate the multiplier by matching it directly with the length of digits
|
||||
multiplier = resistanceStr.length - 2 >= 0 ? Math.pow(10, resistanceStr.length - 2) : Math.pow(10, resistanceStr.length - 1);
|
||||
}
|
||||
}
|
||||
|
||||
let firstBandEntry = Object.entries(colorData).find(function (entry) {
|
||||
return entry[1].value === firstDigit;
|
||||
});
|
||||
|
@ -64,7 +74,7 @@ function resistanceToColorBands(resistance, tolerance) {
|
|||
});
|
||||
let secondBand = secondBandEntry ? secondBandEntry[1].hex : undefined;
|
||||
let multiplierBandEntry = Object.entries(colorData).find(function (entry) {
|
||||
return entry[1].multiplier === Math.pow(10, multiplier);
|
||||
return entry[1].multiplier === multiplier;
|
||||
});
|
||||
let multiplierBand = multiplierBandEntry ? multiplierBandEntry[1].hex : undefined;
|
||||
let toleranceBandEntry = Object.entries(colorData).find(function (entry) {
|
||||
|
|
|
@ -13,10 +13,12 @@
|
|||
0.12: Fix for recorder not stopping at end of run. Bug introduced in 0.11
|
||||
0.13: Revert #1578 (stop duplicate entries) as with 2v12 menus it causes other boxes to be wiped (fix #1643)
|
||||
0.14: Fix Bangle.js 1 issue where after the 'overwrite track' menu, the start/stop button stopped working
|
||||
0.15: Diverge from the standard "Run" app. Swipe to intensity interface a la Karvonen (curtesy of FTeacher at https://github.com/f-teacher)
|
||||
Keep run state between runs (allowing you to exit and restart the app)
|
||||
0.16: Don't clear zone 2b indicator segment when updating HRM reading.
|
||||
0.15: Keep run state between runs (allowing you to exit and restart the app)
|
||||
0.16: Added ability to resume a run that was stopped previously (fix #1907)
|
||||
0.17: Diverge from the standard "Run" app. Swipe to intensity interface a la Karvonen (curtesy of FTeacher at https://github.com/f-teacher)
|
||||
0.18: Don't clear zone 2b indicator segment when updating HRM reading.
|
||||
Write to correct settings file, fixing settings not working.
|
||||
0.17: Fix typo in variable name preventing starting a run.
|
||||
0.18: Tweak HRM min/max defaults. Extend min/max intervals in settings. Fix
|
||||
0.19: Fix typo in variable name preventing starting a run
|
||||
0.20: Tweak HRM min/max defaults. Extend min/max intervals in settings. Fix
|
||||
another typo.
|
||||
0.21: Rebase on "Run" app ver. 0.16.
|
||||
|
|
|
@ -61,35 +61,46 @@ function setStatus(running) {
|
|||
|
||||
// Called to start/stop running
|
||||
function onStartStop() {
|
||||
let running = !exs.state.active;
|
||||
let prepPromises = [];
|
||||
var running = !exs.state.active;
|
||||
var shouldResume = false;
|
||||
var promise = Promise.resolve();
|
||||
|
||||
if (running && exs.state.duration > 10000) { // if more than 10 seconds of duration, ask if we should resume?
|
||||
promise = promise.
|
||||
then(() => {
|
||||
isMenuDisplayed = true;
|
||||
return E.showPrompt("Resume run?",{title:"Run"});
|
||||
}).then(r => {
|
||||
isMenuDisplayed=false;shouldResume=r;
|
||||
});
|
||||
}
|
||||
|
||||
// start/stop recording
|
||||
// Do this first in case recorder needs to prompt for
|
||||
// an overwrite before we start tracking exstats
|
||||
if (settings.record && WIDGETS["recorder"]) {
|
||||
if (running) {
|
||||
isMenuDisplayed = true;
|
||||
prepPromises.push(
|
||||
WIDGETS["recorder"].setRecording(true).then(() => {
|
||||
promise = promise.
|
||||
then(() => WIDGETS["recorder"].setRecording(true, { force : shouldResume?"append":undefined })).
|
||||
then(() => {
|
||||
isMenuDisplayed = false;
|
||||
layout.setUI(); // grab our input handling again
|
||||
layout.forgetLazyState();
|
||||
layout.render();
|
||||
})
|
||||
);
|
||||
});
|
||||
} else {
|
||||
prepPromises.push(
|
||||
WIDGETS["recorder"].setRecording(false)
|
||||
promise = promise.then(
|
||||
() => WIDGETS["recorder"].setRecording(false)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (!prepPromises.length) // fix for Promise.all bug in 2v12
|
||||
prepPromises.push(Promise.resolve());
|
||||
|
||||
Promise.all(prepPromises)
|
||||
.then(() => {
|
||||
promise = promise.then(() => {
|
||||
if (running) {
|
||||
if (shouldResume)
|
||||
exs.resume()
|
||||
else
|
||||
exs.start();
|
||||
} else {
|
||||
exs.stop();
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"id": "runplus",
|
||||
"name": "Run+",
|
||||
"version": "0.18",
|
||||
"version": "0.21",
|
||||
"description": "Displays distance, time, steps, cadence, pace and more for runners. Based on the Run app, but extended with additional screen for heart rate interval training.",
|
||||
"icon": "app.png",
|
||||
"tags": "run,running,fitness,outdoors,gps,karvonen,karvonnen",
|
||||
|
|
|
@ -86,31 +86,74 @@ function eventToAlarm(event, offsetMs) {
|
|||
}
|
||||
|
||||
function upload() {
|
||||
// kick off all the (active) timers
|
||||
const now = new Date();
|
||||
const currentTime = now.getHours()*3600000
|
||||
+ now.getMinutes()*60000
|
||||
+ now.getSeconds()*1000;
|
||||
|
||||
for (const alarm of alarms)
|
||||
if (alarm.timer != undefined && alarm.on)
|
||||
alarm.t = currentTime + alarm.timer;
|
||||
|
||||
Util.showModal("Saving...");
|
||||
Util.writeStorage("sched.json", JSON.stringify(alarms), () => {
|
||||
Puck.write(`\x10require("sched").reload();\n`, () => {
|
||||
location.reload(); // reload so we see current data
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function renderAlarm(alarm, exists) {
|
||||
const localDate = dateFromAlarm(alarm);
|
||||
const localDate = alarm.date ? dateFromAlarm(alarm) : null;
|
||||
|
||||
const tr = document.createElement('tr');
|
||||
tr.classList.add('event-row');
|
||||
tr.dataset.uid = alarm.id;
|
||||
const tdTime = document.createElement('td');
|
||||
tr.appendChild(tdTime);
|
||||
const tdType = document.createElement('td');
|
||||
tdType.type = "text";
|
||||
tdType.classList.add('event-summary');
|
||||
tr.appendChild(tdType);
|
||||
const inputTime = document.createElement('input');
|
||||
if (localDate) {
|
||||
tdType.textContent = "Event";
|
||||
inputTime.type = "datetime-local";
|
||||
inputTime.classList.add('event-date');
|
||||
inputTime.classList.add('form-input');
|
||||
inputTime.dataset.uid = alarm.id;
|
||||
inputTime.value = localDate.toISOString().slice(0,16);
|
||||
inputTime.onchange = (e => {
|
||||
const date = new Date(inputTime.value);
|
||||
alarm.t = dateToMsSinceMidnight(date);
|
||||
alarm.date = formatDate(date);
|
||||
});
|
||||
} else {
|
||||
const [hours, mins, secs] = msToHMS(alarm.timer || alarm.t);
|
||||
|
||||
inputTime.type = "time";
|
||||
inputTime.step = 1; // display seconds
|
||||
inputTime.value = `${hours}:${mins}:${secs}`;
|
||||
|
||||
if (alarm.timer) {
|
||||
tdType.textContent = "Timer";
|
||||
inputTime.onchange = e => {
|
||||
alarm.timer = hmsToMs(inputTime.value);
|
||||
// alarm.t is set on upload
|
||||
};
|
||||
} else {
|
||||
tdType.textContent = "Alarm";
|
||||
inputTime.onchange = e => {
|
||||
alarm.t = hmsToMs(inputTime.value);
|
||||
};
|
||||
}
|
||||
}
|
||||
if (!exists) {
|
||||
const asterisk = document.createElement('sup');
|
||||
asterisk.textContent = '*';
|
||||
tdType.appendChild(asterisk);
|
||||
}
|
||||
inputTime.classList.add('event-date');
|
||||
inputTime.classList.add('form-input');
|
||||
inputTime.dataset.uid = alarm.id;
|
||||
const tdTime = document.createElement('td');
|
||||
tr.appendChild(tdTime);
|
||||
tdTime.appendChild(inputTime);
|
||||
|
||||
const tdSummary = document.createElement('td');
|
||||
|
@ -130,13 +173,31 @@ function renderAlarm(alarm, exists) {
|
|||
tdSummary.appendChild(inputSummary);
|
||||
inputSummary.onchange();
|
||||
|
||||
const tdOptions = document.createElement('td');
|
||||
tr.appendChild(tdOptions);
|
||||
|
||||
const onOffCheck = document.createElement('input');
|
||||
onOffCheck.type = 'checkbox';
|
||||
onOffCheck.checked = alarm.on;
|
||||
onOffCheck.onchange = e => {
|
||||
alarm.on = !alarm.on;
|
||||
if (alarm.on) delete alarm.last;
|
||||
};
|
||||
const onOffIcon = document.createElement('i');
|
||||
onOffIcon.classList.add('form-icon');
|
||||
const onOff = document.createElement('label');
|
||||
onOff.classList.add('form-switch');
|
||||
onOff.appendChild(onOffCheck);
|
||||
onOff.appendChild(onOffIcon);
|
||||
tdOptions.appendChild(onOff);
|
||||
|
||||
const tdInfo = document.createElement('td');
|
||||
tr.appendChild(tdInfo);
|
||||
|
||||
const buttonDelete = document.createElement('button');
|
||||
buttonDelete.classList.add('btn');
|
||||
buttonDelete.classList.add('btn-action');
|
||||
tdInfo.prepend(buttonDelete);
|
||||
tdInfo.appendChild(buttonDelete);
|
||||
const iconDelete = document.createElement('i');
|
||||
iconDelete.classList.add('icon');
|
||||
iconDelete.classList.add('icon-delete');
|
||||
|
@ -150,12 +211,53 @@ function renderAlarm(alarm, exists) {
|
|||
document.getElementById('upload').disabled = false;
|
||||
}
|
||||
|
||||
function msToHMS(ms) {
|
||||
let secs = Math.floor(ms / 1000) % 60;
|
||||
let mins = Math.floor(ms / 1000 / 60) % 60;
|
||||
let hours = Math.floor(ms / 1000 / 60 / 60);
|
||||
if (secs < 10) secs = "0" + secs;
|
||||
if (mins < 10) mins = "0" + mins;
|
||||
if (hours < 10) hours = "0" + hours;
|
||||
return [hours, mins, secs];
|
||||
}
|
||||
|
||||
function hmsToMs(hms) {
|
||||
let [hours, mins, secs] = hms.split(":");
|
||||
hours = Number(hours);
|
||||
mins = Number(mins);
|
||||
secs = Number(secs);
|
||||
return ((hours * 60 + mins) * 60 + secs) * 1000;
|
||||
}
|
||||
|
||||
function addEvent() {
|
||||
const event = getAlarmDefaults();
|
||||
renderAlarm(event);
|
||||
alarms.push(event);
|
||||
}
|
||||
|
||||
function addAlarm() {
|
||||
const alarm = getAlarmDefaults();
|
||||
delete alarm.date;
|
||||
renderAlarm(alarm);
|
||||
alarms.push(alarm);
|
||||
}
|
||||
|
||||
function addTimer() {
|
||||
const alarmDefaults = getAlarmDefaults();
|
||||
const timer = {
|
||||
timer: hmsToMs("00:00:30"),
|
||||
t: 0,
|
||||
on: alarmDefaults.on,
|
||||
dow: alarmDefaults.dow,
|
||||
last: alarmDefaults.last,
|
||||
rp: alarmDefaults.rp,
|
||||
vibrate: alarmDefaults.vibrate,
|
||||
as: alarmDefaults.as,
|
||||
};;
|
||||
renderAlarm(timer);
|
||||
alarms.push(timer);
|
||||
}
|
||||
|
||||
function getData() {
|
||||
Util.showModal("Loading...");
|
||||
Util.readStorage('sched.json',data=>{
|
||||
|
@ -164,10 +266,19 @@ function getData() {
|
|||
Util.readStorage('sched.settings.json',data=>{
|
||||
schedSettings = JSON.parse(data || "{}") || {};
|
||||
Util.hideModal();
|
||||
alarms.sort((a, b) => {
|
||||
let x;
|
||||
|
||||
x = !!b.date - !!a.date;
|
||||
if(x) return x;
|
||||
|
||||
x = !!a.timer - !!b.timer;
|
||||
if(x) return x;
|
||||
|
||||
return a.t - b.t;
|
||||
});
|
||||
alarms.forEach(alarm => {
|
||||
if (alarm.date) {
|
||||
renderAlarm(alarm, true);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -183,16 +294,27 @@ function onInit() {
|
|||
<h4>Manage dated events</h4>
|
||||
|
||||
<div class="float-right">
|
||||
<button class="btn" onclick="addEvent()">
|
||||
<i class="icon icon-plus"></i>
|
||||
Event
|
||||
</button>
|
||||
<button class="btn" onclick="addAlarm()">
|
||||
<i class="icon icon-plus"></i>
|
||||
Alarm
|
||||
</button>
|
||||
<button class="btn" onclick="addTimer()">
|
||||
<i class="icon icon-plus"></i>
|
||||
Timer
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Date</th>
|
||||
<th>Type</th>
|
||||
<th>Date/Time</th>
|
||||
<th>Summary</th>
|
||||
<th>On?</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
|
|
@ -2,3 +2,5 @@
|
|||
0.02: Less time used during boot if disabled
|
||||
0.03: Fixed some test data
|
||||
0.04: Correct type of time attribute in gps to Date
|
||||
0.05: Fix gps emulation interpolation
|
||||
Add setting for log output
|
||||
|
|
|
@ -5,40 +5,56 @@ This allows to simulate sensor behaviour for development purposes
|
|||
|
||||
## Per Sensor settings:
|
||||
|
||||
enabled:
|
||||
true or false
|
||||
mode:
|
||||
emulate: Completely craft events for this sensor
|
||||
modify: Take existing events from real sensor and modify their data
|
||||
name:
|
||||
name of the emulation or modification mode
|
||||
power:
|
||||
emulate: Simulate Bangle._PWR changes, but do not call real power function
|
||||
nop: Do nothing, ignore all power calls for this sensor but return true
|
||||
passthrough: Just pass all power calls unmodified
|
||||
on: Do not allow switching the sensor off, all calls are switching the real sensor on
|
||||
Enabled:
|
||||
* **true**
|
||||
* **false**
|
||||
|
||||
Mode:
|
||||
* **emulate**: Completely craft events for this sensor
|
||||
* **modify**: Take existing events from real sensor and modify their data
|
||||
|
||||
Name:
|
||||
* name of the emulation or modification mode
|
||||
|
||||
Power:
|
||||
* **emulate**: Simulate Bangle._PWR changes, but do not call real power function
|
||||
* **nop**: Do nothing, ignore all power calls for this sensor but return true
|
||||
* **passthrough**: Just pass all power calls unmodified
|
||||
* **on**: Do not allow switching the sensor off, all calls are switching the real sensor on
|
||||
|
||||
### HRM
|
||||
|
||||
Modes: modify, emulate
|
||||
Modes:
|
||||
* **modify**: Modify the original events from this sensor
|
||||
* **emulate**: Create events simulating sensor activity
|
||||
|
||||
Modification:
|
||||
bpmtrippled: Multiply the bpm value of the original HRM values with 3
|
||||
* **bpmtrippled**: Multiply the bpm value of the original HRM values with 3
|
||||
|
||||
Emulation:
|
||||
sin: Calculate bpm changes by using sin
|
||||
* **sin**: Calculate bpm changes by using sin
|
||||
|
||||
### GPS
|
||||
|
||||
Modes: emulate
|
||||
Modes:
|
||||
* **emulate**
|
||||
|
||||
Emulation:
|
||||
staticfix: static complete fix with all values
|
||||
route: A square route starting in the SW corner and moving SW->NW->NO->SW...
|
||||
routeFuzzy: Roughly the same square as route, but with 100m seqments with some variaton in course
|
||||
nofix: All values NaN but time,sattelites,fix and fix == 0
|
||||
changingfix: A fix with randomly changing values
|
||||
* **staticfix**: static complete fix with all values
|
||||
* **route**: A square route starting in the SW corner and moving SW->NW->NO->SW... [Download as gpx](square.gpx)
|
||||
* **routeFuzzy**: Roughly the same square as route, but with 100m seqments with some variaton in course [Download as gpx](squareFuzzy.gpx)
|
||||
* **nofix**: All values NaN but time,sattelites,fix and fix == 0
|
||||
* **changingfix**: A fix with randomly changing values
|
||||
|
||||
### Compass
|
||||
|
||||
Modes: emulate
|
||||
Modes:
|
||||
* **emulate**
|
||||
|
||||
Emulation:
|
||||
static: All values but heading are 1, heading == 0
|
||||
rotate: All values but heading are 1, heading rotates 360°
|
||||
* **static**: All values but heading are 1, heading == 0
|
||||
* **rotate**: All values but heading are 1, heading rotates 360°
|
||||
|
||||
# Creator
|
||||
|
||||
[halemmerich](https://github.com/halemmerich)
|
|
@ -1,5 +1,6 @@
|
|||
{
|
||||
"enabled": false,
|
||||
"log": false,
|
||||
"mag": {
|
||||
"enabled": false,
|
||||
"mode": "emulate",
|
||||
|
|
|
@ -5,6 +5,7 @@ exports.enable = () => {
|
|||
);
|
||||
|
||||
let log = function(text, param) {
|
||||
if (!settings.log) return;
|
||||
let logline = new Date().toISOString() + " - " + "Sensortools - " + text;
|
||||
if (param) logline += ": " + JSON.stringify(param);
|
||||
print(logline);
|
||||
|
@ -138,63 +139,63 @@ exports.enable = () => {
|
|||
|
||||
function interpolate(a,b,progress){
|
||||
return {
|
||||
lat: a.lat * progress + b.lat * (1-progress),
|
||||
lon: a.lon * progress + b.lon * (1-progress),
|
||||
ele: a.ele * progress + b.ele * (1-progress)
|
||||
lat: b.lat * progress + a.lat * (1-progress),
|
||||
lon: b.lon * progress + a.lon * (1-progress),
|
||||
alt: b.alt * progress + a.alt * (1-progress)
|
||||
}
|
||||
}
|
||||
|
||||
function getSquareRoute(){
|
||||
return [
|
||||
{lat:"47.2577411",lon:"11.9927442",ele:2273},
|
||||
{lat:"47.266761",lon:"11.9926673",ele:2166},
|
||||
{lat:"47.2667605",lon:"12.0059511",ele:2245},
|
||||
{lat:"47.2577516",lon:"12.0059925",ele:1994}
|
||||
{lat:47.2577411,lon:11.9927442,alt:2273},
|
||||
{lat:47.266761,lon:11.9926673,alt:2166},
|
||||
{lat:47.2667605,lon:12.0059511,alt:2245},
|
||||
{lat:47.2577516,lon:12.0059925,alt:1994}
|
||||
];
|
||||
}
|
||||
function getSquareRouteFuzzy(){
|
||||
return [
|
||||
{lat:"47.2578455",lon:"11.9929891",ele:2265},
|
||||
{lat:"47.258592",lon:"11.9923341",ele:2256},
|
||||
{lat:"47.2594506",lon:"11.9927412",ele:2230},
|
||||
{lat:"47.2603323",lon:"11.9924949",ele:2219},
|
||||
{lat:"47.2612056",lon:"11.9928175",ele:2199},
|
||||
{lat:"47.2621002",lon:"11.9929817",ele:2182},
|
||||
{lat:"47.2629025",lon:"11.9923915",ele:2189},
|
||||
{lat:"47.2637828",lon:"11.9926486",ele:2180},
|
||||
{lat:"47.2646733",lon:"11.9928167",ele:2191},
|
||||
{lat:"47.2655617",lon:"11.9930357",ele:2185},
|
||||
{lat:"47.2662862",lon:"11.992252",ele:2186},
|
||||
{lat:"47.2669305",lon:"11.993173",ele:2166},
|
||||
{lat:"47.266666",lon:"11.9944419",ele:2171},
|
||||
{lat:"47.2667579",lon:"11.99576",ele:2194},
|
||||
{lat:"47.2669409",lon:"11.9970579",ele:2207},
|
||||
{lat:"47.2666562",lon:"11.9983128",ele:2212},
|
||||
{lat:"47.2666027",lon:"11.9996335",ele:2262},
|
||||
{lat:"47.2667245",lon:"12.0009395",ele:2278},
|
||||
{lat:"47.2668457",lon:"12.002256",ele:2297},
|
||||
{lat:"47.2666126",lon:"12.0035373",ele:2303},
|
||||
{lat:"47.2664554",lon:"12.004841",ele:2251},
|
||||
{lat:"47.2669461",lon:"12.005948",ele:2245},
|
||||
{lat:"47.2660877",lon:"12.006323",ele:2195},
|
||||
{lat:"47.2652729",lon:"12.0057552",ele:2163},
|
||||
{lat:"47.2643926",lon:"12.0060123",ele:2131},
|
||||
{lat:"47.2634978",lon:"12.0058302",ele:2095},
|
||||
{lat:"47.2626129",lon:"12.0060759",ele:2066},
|
||||
{lat:"47.2617325",lon:"12.0058188",ele:2037},
|
||||
{lat:"47.2608668",lon:"12.0061784",ele:1993},
|
||||
{lat:"47.2600155",lon:"12.0057392",ele:1967},
|
||||
{lat:"47.2591203",lon:"12.0058233",ele:1949},
|
||||
{lat:"47.2582307",lon:"12.0059718",ele:1972},
|
||||
{lat:"47.2578014",lon:"12.004804",ele:2011},
|
||||
{lat:"47.2577232",lon:"12.0034834",ele:2044},
|
||||
{lat:"47.257745",lon:"12.0021656",ele:2061},
|
||||
{lat:"47.2578682",lon:"12.0008597",ele:2065},
|
||||
{lat:"47.2577082",lon:"11.9995526",ele:2071},
|
||||
{lat:"47.2575917",lon:"11.9982348",ele:2102},
|
||||
{lat:"47.2577401",lon:"11.996924",ele:2147},
|
||||
{lat:"47.257715",lon:"11.9956061",ele:2197},
|
||||
{lat:"47.2578996",lon:"11.9943081",ele:2228}
|
||||
{lat:47.2578455,lon:11.9929891,alt:2265},
|
||||
{lat:47.258592,lon:11.9923341,alt:2256},
|
||||
{lat:47.2594506,lon:11.9927412,alt:2230},
|
||||
{lat:47.2603323,lon:11.9924949,alt:2219},
|
||||
{lat:47.2612056,lon:11.9928175,alt:2199},
|
||||
{lat:47.2621002,lon:11.9929817,alt:2182},
|
||||
{lat:47.2629025,lon:11.9923915,alt:2189},
|
||||
{lat:47.2637828,lon:11.9926486,alt:2180},
|
||||
{lat:47.2646733,lon:11.9928167,alt:2191},
|
||||
{lat:47.2655617,lon:11.9930357,alt:2185},
|
||||
{lat:47.2662862,lon:11.992252,alt:2186},
|
||||
{lat:47.2669305,lon:11.993173,alt:2166},
|
||||
{lat:47.266666,lon:11.9944419,alt:2171},
|
||||
{lat:47.2667579,lon:11.99576,alt:2194},
|
||||
{lat:47.2669409,lon:11.9970579,alt:2207},
|
||||
{lat:47.2666562,lon:11.9983128,alt:2212},
|
||||
{lat:47.2666027,lon:11.9996335,alt:2262},
|
||||
{lat:47.2667245,lon:12.0009395,alt:2278},
|
||||
{lat:47.2668457,lon:12.002256,alt:2297},
|
||||
{lat:47.2666126,lon:12.0035373,alt:2303},
|
||||
{lat:47.2664554,lon:12.004841,alt:2251},
|
||||
{lat:47.2669461,lon:12.005948,alt:2245},
|
||||
{lat:47.2660877,lon:12.006323,alt:2195},
|
||||
{lat:47.2652729,lon:12.0057552,alt:2163},
|
||||
{lat:47.2643926,lon:12.0060123,alt:2131},
|
||||
{lat:47.2634978,lon:12.0058302,alt:2095},
|
||||
{lat:47.2626129,lon:12.0060759,alt:2066},
|
||||
{lat:47.2617325,lon:12.0058188,alt:2037},
|
||||
{lat:47.2608668,lon:12.0061784,alt:1993},
|
||||
{lat:47.2600155,lon:12.0057392,alt:1967},
|
||||
{lat:47.2591203,lon:12.0058233,alt:1949},
|
||||
{lat:47.2582307,lon:12.0059718,alt:1972},
|
||||
{lat:47.2578014,lon:12.004804,alt:2011},
|
||||
{lat:47.2577232,lon:12.0034834,alt:2044},
|
||||
{lat:47.257745,lon:12.0021656,alt:2061},
|
||||
{lat:47.2578682,lon:12.0008597,alt:2065},
|
||||
{lat:47.2577082,lon:11.9995526,alt:2071},
|
||||
{lat:47.2575917,lon:11.9982348,alt:2102},
|
||||
{lat:47.2577401,lon:11.996924,alt:2147},
|
||||
{lat:47.257715,lon:11.9956061,alt:2197},
|
||||
{lat:47.2578996,lon:11.9943081,alt:2228}
|
||||
];
|
||||
}
|
||||
|
||||
|
@ -215,19 +216,20 @@ exports.enable = () => {
|
|||
let interpSteps;
|
||||
if (settings.gps.name == "routeFuzzy"){
|
||||
route = getSquareRouteFuzzy();
|
||||
interpSteps = 5;
|
||||
interpSteps = 74;
|
||||
} else {
|
||||
route = getSquareRoute();
|
||||
interpSteps = 50;
|
||||
interpSteps = 740;
|
||||
}
|
||||
|
||||
let step = 0;
|
||||
let routeIndex = 0;
|
||||
modGps(() => {
|
||||
let newIndex = (routeIndex + 1)%route.length;
|
||||
let followingIndex = (routeIndex + 2)%route.length;
|
||||
|
||||
let result = {
|
||||
"speed": Math.random() * 3 + 2,
|
||||
"speed": Math.random()*1 + 4.5,
|
||||
"time": new Date(),
|
||||
"satellites": Math.floor(Math.random()*5)+3,
|
||||
"fix": 1,
|
||||
|
@ -235,31 +237,22 @@ exports.enable = () => {
|
|||
};
|
||||
|
||||
let oldPos = route[routeIndex];
|
||||
if (step != 0){
|
||||
oldPos = interpolate(route[routeIndex], route[newIndex], E.clip(0,1,step/interpSteps));
|
||||
}
|
||||
let newPos = route[newIndex];
|
||||
if (step < interpSteps - 1){
|
||||
newPos = interpolate(route[routeIndex], route[newIndex], E.clip(0,1,(step+1)%interpSteps/interpSteps));
|
||||
}
|
||||
|
||||
if (step == interpSteps - 1){
|
||||
let followingIndex = (routeIndex + 2)%route.length;
|
||||
newPos = interpolate(route[newIndex], route[followingIndex], E.clip(0,1,1/interpSteps));
|
||||
}
|
||||
|
||||
result.lat = oldPos.lat;
|
||||
result.lon = oldPos.lon;
|
||||
result.alt = oldPos.ele;
|
||||
let followingPos = route[followingIndex];
|
||||
let interpPos = interpolate(oldPos, newPos, E.clip(0,1,step/interpSteps));
|
||||
|
||||
if (step > 0.5* interpSteps) {
|
||||
result.course = bearing(interpPos, interpolate(newPos, followingPos, E.clip(0,1,(step-0.5*interpSteps)/interpSteps)));
|
||||
} else {
|
||||
result.course = bearing(oldPos, newPos);
|
||||
}
|
||||
|
||||
step++;
|
||||
if (step == interpSteps){
|
||||
routeIndex = (routeIndex + 1) % route.length;
|
||||
step = 0;
|
||||
}
|
||||
return result;
|
||||
return Object.assign(result, interpPos);
|
||||
});
|
||||
} else if (settings.gps.name == "nofix") {
|
||||
modGps(() => { return {
|
||||
|
@ -281,6 +274,7 @@ exports.enable = () => {
|
|||
let currentDir=1000;
|
||||
let currentAlt=500;
|
||||
let currentSats=5;
|
||||
|
||||
modGps(() => {
|
||||
currentLat += 0.01;
|
||||
if (currentLat > 50) currentLat = 20;
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
"id": "sensortools",
|
||||
"name": "Sensor tools",
|
||||
"shortName": "Sensor tools",
|
||||
"version": "0.04",
|
||||
"version": "0.05",
|
||||
"description": "Tools for testing and debugging apps that use sensor input",
|
||||
"icon": "icon.png",
|
||||
"type": "bootloader",
|
||||
|
|
|
@ -88,6 +88,12 @@
|
|||
writeSettings("enabled",v);
|
||||
},
|
||||
},
|
||||
'Log': {
|
||||
value: !!settings.log,
|
||||
onchange: v => {
|
||||
writeSettings("log",v);
|
||||
},
|
||||
},
|
||||
'GPS': ()=>{showSubMenu("GPS","gps",["nop", "staticfix", "nofix", "changingfix", "route", "routeFuzzy"],[]);},
|
||||
'Compass': ()=>{showSubMenu("Compass","mag",["nop", "static", "rotate"],[]);},
|
||||
'HRM': ()=>{showSubMenu("HRM","hrm",["nop", "static"],["bpmtrippled"],["sin"]);}
|
||||
|
|
|
@ -0,0 +1,33 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<gpx version="1.1" creator="OsmAndRouterV2" xmlns="http://www.topografix.com/GPX/1/1" xmlns:osmand="https://osmand.net" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.topografix.com/GPX/1/1 http://www.topografix.com/GPX/1/1/gpx.xsd">
|
||||
<metadata>
|
||||
<name>1kmsquare</name>
|
||||
<desc>Export from GpsPrune</desc>
|
||||
</metadata>
|
||||
<trk>
|
||||
<name>1kmsquare</name>
|
||||
<number>1</number>
|
||||
<trkseg>
|
||||
<trkpt lat="47.2577411" lon="11.9927442">
|
||||
<ele>2273</ele>
|
||||
<name>Lower left</name>
|
||||
</trkpt>
|
||||
<trkpt lat="47.266761" lon="11.9926673">
|
||||
<ele>2166</ele>
|
||||
<name>Top left</name>
|
||||
</trkpt>
|
||||
<trkpt lat="47.2667605" lon="12.0059511">
|
||||
<ele>2245</ele>
|
||||
<name>Top right</name>
|
||||
</trkpt>
|
||||
<trkpt lat="47.2577516" lon="12.0059925">
|
||||
<ele>1994</ele>
|
||||
<name>Lower right</name>
|
||||
</trkpt>
|
||||
<trkpt lat="47.2577412" lon="11.9927442">
|
||||
<ele>2273</ele>
|
||||
<name>Destination</name>
|
||||
</trkpt>
|
||||
</trkseg>
|
||||
</trk>
|
||||
</gpx>
|
|
@ -0,0 +1,144 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<gpx version="1.1" creator="OsmAndRouterV2" xmlns="http://www.topografix.com/GPX/1/1" xmlns:osmand="https://osmand.net" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.topografix.com/GPX/1/1 http://www.topografix.com/GPX/1/1/gpx.xsd">
|
||||
<metadata>
|
||||
<name>1kmsquare100</name>
|
||||
<desc>Export from GpsPrune</desc>
|
||||
</metadata>
|
||||
<trk>
|
||||
<name>1kmsquare100</name>
|
||||
<number>1</number>
|
||||
<trkseg>
|
||||
<trkpt lat="47.2578455" lon="11.9929891">
|
||||
<ele>2265</ele>
|
||||
<name>Lower left</name>
|
||||
</trkpt>
|
||||
<trkpt lat="47.258592" lon="11.9923341">
|
||||
<ele>2256</ele>
|
||||
</trkpt>
|
||||
<trkpt lat="47.2594506" lon="11.9927412">
|
||||
<ele>2230</ele>
|
||||
</trkpt>
|
||||
<trkpt lat="47.2603323" lon="11.9924949">
|
||||
<ele>2219</ele>
|
||||
</trkpt>
|
||||
<trkpt lat="47.2612056" lon="11.9928175">
|
||||
<ele>2199</ele>
|
||||
</trkpt>
|
||||
<trkpt lat="47.2621002" lon="11.9929817">
|
||||
<ele>2182</ele>
|
||||
</trkpt>
|
||||
<trkpt lat="47.2629025" lon="11.9923915">
|
||||
<ele>2189</ele>
|
||||
</trkpt>
|
||||
<trkpt lat="47.2637828" lon="11.9926486">
|
||||
<ele>2180</ele>
|
||||
</trkpt>
|
||||
<trkpt lat="47.2646733" lon="11.9928167">
|
||||
<ele>2191</ele>
|
||||
</trkpt>
|
||||
<trkpt lat="47.2655617" lon="11.9930357">
|
||||
<ele>2185</ele>
|
||||
</trkpt>
|
||||
<trkpt lat="47.2662862" lon="11.992252">
|
||||
<ele>2186</ele>
|
||||
</trkpt>
|
||||
<trkpt lat="47.2669305" lon="11.993173">
|
||||
<ele>2166</ele>
|
||||
<name>Top left</name>
|
||||
</trkpt>
|
||||
<trkpt lat="47.266666" lon="11.9944419">
|
||||
<ele>2171</ele>
|
||||
</trkpt>
|
||||
<trkpt lat="47.2667579" lon="11.99576">
|
||||
<ele>2194</ele>
|
||||
</trkpt>
|
||||
<trkpt lat="47.2669409" lon="11.9970579">
|
||||
<ele>2207</ele>
|
||||
</trkpt>
|
||||
<trkpt lat="47.2666562" lon="11.9983128">
|
||||
<ele>2212</ele>
|
||||
</trkpt>
|
||||
<trkpt lat="47.2666027" lon="11.9996335">
|
||||
<ele>2262</ele>
|
||||
</trkpt>
|
||||
<trkpt lat="47.2667245" lon="12.0009395">
|
||||
<ele>2278</ele>
|
||||
</trkpt>
|
||||
<trkpt lat="47.2668457" lon="12.002256">
|
||||
<ele>2297</ele>
|
||||
</trkpt>
|
||||
<trkpt lat="47.2666126" lon="12.0035373">
|
||||
<ele>2303</ele>
|
||||
</trkpt>
|
||||
<trkpt lat="47.2664554" lon="12.004841">
|
||||
<ele>2251</ele>
|
||||
</trkpt>
|
||||
<trkpt lat="47.2669461" lon="12.005948">
|
||||
<ele>2245</ele>
|
||||
<name>Top right</name>
|
||||
</trkpt>
|
||||
<trkpt lat="47.2660877" lon="12.006323">
|
||||
<ele>2195</ele>
|
||||
</trkpt>
|
||||
<trkpt lat="47.2652729" lon="12.0057552">
|
||||
<ele>2163</ele>
|
||||
</trkpt>
|
||||
<trkpt lat="47.2643926" lon="12.0060123">
|
||||
<ele>2131</ele>
|
||||
</trkpt>
|
||||
<trkpt lat="47.2634978" lon="12.0058302">
|
||||
<ele>2095</ele>
|
||||
</trkpt>
|
||||
<trkpt lat="47.2626129" lon="12.0060759">
|
||||
<ele>2066</ele>
|
||||
</trkpt>
|
||||
<trkpt lat="47.2617325" lon="12.0058188">
|
||||
<ele>2037</ele>
|
||||
</trkpt>
|
||||
<trkpt lat="47.2608668" lon="12.0061784">
|
||||
<ele>1993</ele>
|
||||
</trkpt>
|
||||
<trkpt lat="47.2600155" lon="12.0057392">
|
||||
<ele>1967</ele>
|
||||
</trkpt>
|
||||
<trkpt lat="47.2591203" lon="12.0058233">
|
||||
<ele>1949</ele>
|
||||
</trkpt>
|
||||
<trkpt lat="47.2582307" lon="12.0059718">
|
||||
<ele>1972</ele>
|
||||
</trkpt>
|
||||
<trkpt lat="47.2578014" lon="12.004804">
|
||||
<ele>2011</ele>
|
||||
<name>Lower right</name>
|
||||
</trkpt>
|
||||
<trkpt lat="47.2577232" lon="12.0034834">
|
||||
<ele>2044</ele>
|
||||
</trkpt>
|
||||
<trkpt lat="47.257745" lon="12.0021656">
|
||||
<ele>2061</ele>
|
||||
</trkpt>
|
||||
<trkpt lat="47.2578682" lon="12.0008597">
|
||||
<ele>2065</ele>
|
||||
</trkpt>
|
||||
<trkpt lat="47.2577082" lon="11.9995526">
|
||||
<ele>2071</ele>
|
||||
</trkpt>
|
||||
<trkpt lat="47.2575917" lon="11.9982348">
|
||||
<ele>2102</ele>
|
||||
</trkpt>
|
||||
<trkpt lat="47.2577401" lon="11.996924">
|
||||
<ele>2147</ele>
|
||||
</trkpt>
|
||||
<trkpt lat="47.257715" lon="11.9956061">
|
||||
<ele>2197</ele>
|
||||
</trkpt>
|
||||
<trkpt lat="47.2578996" lon="11.9943081">
|
||||
<ele>2228</ele>
|
||||
</trkpt>
|
||||
<trkpt lat="47.2578436" lon="11.992992">
|
||||
<ele>2265</ele>
|
||||
<name>Destination</name>
|
||||
</trkpt>
|
||||
</trkseg>
|
||||
</trk>
|
||||
</gpx>
|
|
@ -67,3 +67,4 @@ of 'Select Clock'
|
|||
0.59: Preserve BLE whitelist even when disabled
|
||||
0.60: Moved LCD calibration to top of menu, and use 12 taps (not 8)
|
||||
LCD calibration will now error if the calibration is obviously wrong
|
||||
0.61: Permit temporary bypass of the BLE whitelist
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"id": "setting",
|
||||
"name": "Settings",
|
||||
"version": "0.60",
|
||||
"version": "0.61",
|
||||
"description": "A menu for setting up Bangle.js",
|
||||
"icon": "settings.png",
|
||||
"tags": "tool,system",
|
||||
|
|
|
@ -658,6 +658,7 @@ function showUtilMenu() {
|
|||
function makeConnectable() {
|
||||
try { NRF.wake(); } catch (e) { }
|
||||
Bluetooth.setConsole(1);
|
||||
NRF.ignoreWhitelist = 1;
|
||||
var name = "Bangle.js " + NRF.getAddress().substr(-5).replace(":", "");
|
||||
E.showPrompt(name + /*LANG*/"\nStay Connectable?", { title: /*LANG*/"Connectable" }).then(r => {
|
||||
if (settings.ble != r) {
|
||||
|
@ -665,6 +666,7 @@ function makeConnectable() {
|
|||
updateSettings();
|
||||
}
|
||||
if (!r) try { NRF.sleep(); } catch (e) { }
|
||||
delete NRF.ignoreWhitelist;
|
||||
showMainMenu();
|
||||
});
|
||||
}
|
||||
|
|
|
@ -2,3 +2,4 @@
|
|||
0.02: Add "from Consec."-setting
|
||||
0.03: Correct how to ignore last triggered alarm
|
||||
0.04: Make "disable alarm" possible on next day; correct alarm filtering; improve settings
|
||||
0.05: Correct hide function + replace all `var` with `let`.
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
# Sleep Log Alarm
|
||||
|
||||
This widget searches for active alarms and raises an own alarm event up to the defined time earlier, if in light sleep or awake phase. Optional the earlier alarm will only be triggered if comming from or in consecutive sleep. The settings of the earlier alarm can be adjusted and it is possible to filter the targeting alarms by time and message. By default the time of the targeting alarm is displayed inside the widget which can be adjusted, too.
|
||||
This widget searches for active alarms and raises an own alarm event up to the defined time earlier, if in light sleep or awake phase. Optional the earlier alarm will only be triggered if comming from or in consecutive sleep. The settings of the earlier alarm can be adjusted and it is possible to filter the targeting alarms by time and message. The widget is only displayed if an active alarm is detected. The time of the targeting alarm is displayed inside the widget, too. The time or the complete widget can be hidden in the options.
|
||||
|
||||
_This widget does not detect sleep on its own and can not create alarms. It requires the [sleeplog](/apps/?id=sleeplog) app and any alarm app that uses [sched](/apps/?id=sched) to be installed._
|
||||
|
||||
|
@ -30,7 +30,7 @@ _This widget does not detect sleep on its own and can not create alarms. It requ
|
|||
- __msg includes__ | include only alarms including this string in msg
|
||||
__""__ / ...
|
||||
- __Widget__ submenu
|
||||
- __hide__ | completely hide the widget
|
||||
- __hide always__ | completely hide the widget
|
||||
_on_ / __off__
|
||||
- __show time__ | show the time of the targeting alarm
|
||||
__on__ / _off_
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
// load library
|
||||
var sched = require("sched");
|
||||
let sched = require("sched");
|
||||
|
||||
// find next active alarm in range
|
||||
function getNextAlarm(allAlarms, fo, withId) {
|
||||
|
@ -10,7 +10,7 @@ function getNextAlarm(allAlarms, fo, withId) {
|
|||
// return next active alarms in range, filter for
|
||||
// active && not timer && not own alarm &&
|
||||
// after from && before to && includes msg
|
||||
var ret = allAlarms.filter(
|
||||
let ret = allAlarms.filter(
|
||||
a => a.on && !a.timer && a.id !== "sleeplog" &&
|
||||
a.t >= fo.from && a.t < fo.to && (!fo.msg || a.msg.includes(fo.msg))
|
||||
).map(a => { // add time to alarm
|
||||
|
@ -21,7 +21,7 @@ function getNextAlarm(allAlarms, fo, withId) {
|
|||
).sort((a, b) => a.tTo - b.tTo);
|
||||
// prevent triggering for an already triggered alarm again if available
|
||||
if (fo.lastDate) {
|
||||
var toLast = fo.lastDate - new Date().valueOf() + 1000;
|
||||
let toLast = fo.lastDate - new Date().valueOf() + 1000;
|
||||
if (toLast > 0) ret = ret.filter(a => a.tTo > toLast);
|
||||
}
|
||||
// return first entry
|
||||
|
@ -59,7 +59,7 @@ exports = {
|
|||
if (typeof (global.sleeplog || {}).trigger !== "object") return;
|
||||
|
||||
// read settings to calculate alarm range
|
||||
var settings = exports.getSettings();
|
||||
let settings = exports.getSettings();
|
||||
|
||||
// set the alarm time
|
||||
this.time = getNextAlarm(sched.getAlarms(), settings.filter).t;
|
||||
|
@ -68,7 +68,7 @@ exports = {
|
|||
if (!this.time) return;
|
||||
|
||||
// set widget width if not hidden
|
||||
if (!this.hidden) this.width = 8;
|
||||
if (!settings.wid.hide) this.width = 8;
|
||||
|
||||
// insert sleeplogalarm conditions and function
|
||||
sleeplog.trigger.sleeplogalarm = {
|
||||
|
@ -87,22 +87,22 @@ exports = {
|
|||
// trigger function
|
||||
trigger: function() {
|
||||
// read settings
|
||||
var settings = exports.getSettings();
|
||||
let settings = exports.getSettings();
|
||||
|
||||
// read all alarms
|
||||
var allAlarms = sched.getAlarms();
|
||||
let allAlarms = sched.getAlarms();
|
||||
|
||||
// find first active alarm
|
||||
var alarm = getNextAlarm(sched.getAlarms(), settings.filter, settings.disableOnAlarm);
|
||||
let alarm = getNextAlarm(sched.getAlarms(), settings.filter, settings.disableOnAlarm);
|
||||
|
||||
// return if no alarm is found
|
||||
if (!alarm) return;
|
||||
|
||||
// get now
|
||||
var now = new Date();
|
||||
let now = new Date();
|
||||
|
||||
// get date of the alarm
|
||||
var aDate = new Date(now + alarm.tTo);
|
||||
let aDate = new Date(now + alarm.tTo);
|
||||
|
||||
// disable earlier triggered alarm if set
|
||||
if (settings.disableOnAlarm) {
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
"id":"sleeplogalarm",
|
||||
"name":"Sleep Log Alarm",
|
||||
"shortName": "SleepLogAlarm",
|
||||
"version": "0.04",
|
||||
"version": "0.05",
|
||||
"description": "Enhance your morning and let your alarms wake you up when you are in light sleep.",
|
||||
"icon": "app.png",
|
||||
"type": "widget",
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
(function(back) {
|
||||
// read settings
|
||||
var settings = require("sleeplogalarm").getSettings();
|
||||
let settings = require("sleeplogalarm").getSettings();
|
||||
|
||||
// write change to storage
|
||||
function writeSetting() {
|
||||
|
@ -23,7 +23,7 @@
|
|||
// show widget menu
|
||||
function showFilterMenu() {
|
||||
// set menu
|
||||
var filterMenu = {
|
||||
let filterMenu = {
|
||||
"": {
|
||||
title: "Filter Alarm"
|
||||
},
|
||||
|
@ -64,22 +64,22 @@
|
|||
})
|
||||
}
|
||||
};
|
||||
var menu = E.showMenu(filterMenu);
|
||||
let menu = E.showMenu(filterMenu);
|
||||
}
|
||||
|
||||
// show widget menu
|
||||
function showWidMenu() {
|
||||
// define color values and names
|
||||
var colName = ["red", "yellow", "green", "cyan", "blue", "magenta", "black", "white"];
|
||||
var colVal = [63488, 65504, 2016, 2047, 31, 63519, 0, 65535];
|
||||
let colName = ["red", "yellow", "green", "cyan", "blue", "magenta", "black", "white"];
|
||||
let colVal = [63488, 65504, 2016, 2047, 31, 63519, 0, 65535];
|
||||
|
||||
// set menu
|
||||
var widgetMenu = {
|
||||
let widgetMenu = {
|
||||
"": {
|
||||
title: "Widget Settings"
|
||||
},
|
||||
/*LANG*/"< Back": () => showMain(9),
|
||||
/*LANG*/"hide": {
|
||||
/*LANG*/"hide always": {
|
||||
value: settings.wid.hide,
|
||||
onchange: v => {
|
||||
settings.wid.hide = v;
|
||||
|
@ -105,13 +105,13 @@
|
|||
}
|
||||
}
|
||||
};
|
||||
var menu = E.showMenu(widgetMenu);
|
||||
let menu = E.showMenu(widgetMenu);
|
||||
}
|
||||
|
||||
// show main menu
|
||||
function showMain(selected) {
|
||||
// set menu
|
||||
var mainMenu = {
|
||||
let mainMenu = {
|
||||
"": {
|
||||
title: "Sleep Log Alarm",
|
||||
selected: selected
|
||||
|
@ -184,7 +184,7 @@
|
|||
}
|
||||
}
|
||||
};
|
||||
var menu = E.showMenu(mainMenu);
|
||||
let menu = E.showMenu(mainMenu);
|
||||
}
|
||||
|
||||
// draw main menu
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
// check if enabled in settings
|
||||
if ((require("Storage").readJSON("sleeplogalarm.settings.json", true) || {enabled: true}).enabled) {
|
||||
// read settings
|
||||
settings = require("sleeplogalarm").getSettings(); // is undefined if used with var
|
||||
let settings = require("sleeplogalarm").getSettings();
|
||||
|
||||
// insert neccessary settings into widget
|
||||
WIDGETS.sleeplogalarm = {
|
||||
|
@ -10,10 +10,13 @@ if ((require("Storage").readJSON("sleeplogalarm.settings.json", true) || {enable
|
|||
time: 0,
|
||||
earlier: settings.earlier,
|
||||
draw: function () {
|
||||
// draw if width is set
|
||||
if (this.width) {
|
||||
// draw zzz
|
||||
g.reset().setColor(settings.wid.color).drawImage(atob("BwoBD8SSSP4EEEDg"), this.x + 1, this.y);
|
||||
// call function to draw the time of alarm if a alarm is found
|
||||
if (this.time) this.drawTime(this.time + 1);
|
||||
}
|
||||
},
|
||||
drawTime: () => {},
|
||||
reload: require("sleeplogalarm").widReload
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
0.01: First release
|
||||
0.02: Faster sinus line and fix button to open menu
|
||||
0.03: Show day/month, add animations, fix !mylocation and text glitch
|
||||
0.04: Always show the widgets, swifter animations and lighter sea line
|
|
@ -0,0 +1,25 @@
|
|||
# sunrise watchface
|
||||
|
||||
This app mimics the Apple Watch watchface that shows the sunrise and sunset time.
|
||||
|
||||
This is a work-in-progress app, so you may expect missfeatures, bugs and heavy
|
||||
battery draining. There's still a lot of things to optimize and improve, so take
|
||||
this into account before complaining :-)
|
||||
|
||||
* Requires to configure the location in Settings -> Apps -> My Location
|
||||
* Shows sea level and make the sun/moon glow depending on the x position
|
||||
* The sinus is fixed, so the sea level is curved to match the sunrise/sunset positions)
|
||||
|
||||
## TODO
|
||||
|
||||
* Improved gradients and add support for banglejs1
|
||||
* Faster rendering, by reducing sinus stepsize, only refreshing whats needed, etc
|
||||
* Show red vertical lines or dots inside the sinus if there are alarms
|
||||
|
||||
## Author
|
||||
|
||||
Written by pancake in 2023
|
||||
|
||||
## Screenshots
|
||||
|
||||

|