Initial commit for multi-timer

main
Devin Leamy 2023-10-28 13:29:38 -04:00
commit 1d96447e3d
10 changed files with 868 additions and 0 deletions

4
.gitignore vendored Normal file
View File

@ -0,0 +1,4 @@
dist
node_modules
app-config.user.yaml
src/app-config.ts

2
README.md Normal file
View File

@ -0,0 +1,2 @@
# espruino-ts-quickstart
Quickstart for Espruino using typescript and Visual Studio Code IDE

7
env-config.yaml Normal file
View File

@ -0,0 +1,7 @@
# Port where mcu device is located
port: COM4
# ESP32 after flash has 115200 speed.
port_speed: 115200
board: ESP32

18
metadata.json Normal file
View File

@ -0,0 +1,18 @@
{
"id": "stopwatch",
"name": "Stopwatch Touch",
"version": "0.05",
"description": "A touch based stop watch for Bangle JS 2",
"icon": "stopwatch.png",
"screenshots": [{"url":"screenshot1.png"},{"url":"screenshot2.png"},{"url":"screenshot3.png"}],
"tags": "tools,app",
"supports": ["BANGLEJS2"],
"readme": "README.md",
"storage": [
{"name":"stopwatch.app.js","url":"stopwatch.app.js"},
{"name":"stopwatch.img","url":"stopwatch.icon.js","evaluate":true}
],
"data": [
{"name":"stopwatch.json"}
]
}

19
package.json Normal file
View File

@ -0,0 +1,19 @@
{
"name": "espruino-ts-quickstart",
"version": "1.0.0",
"description": "Quickstart for Espruino using typescript and Visual Studio Code IDE",
"main": "app.js",
"repository": {
"url": "https://github.com/stasberkov/espruino-ts-quickstart"
},
"scripts": {
"build": "tsc ./src/app.ts"
},
"author": "Stanislav Berkov",
"license": "ISC",
"dependencies": {
"@types/espruino": "^1.94",
"espruino": "^0.1",
"typescript": "^4.2"
}
}

219
src/app.js Normal file
View File

@ -0,0 +1,219 @@
var STORAGE_FILE = "timer.json";
var PAUSE_IMG = atob("GBiBAf////////////////wYP/wYP/wYP/wYP/wYP/wYP/wYP/wYP/wYP/wYP/wYP/wYP/wYP/wYP/wYP/wYP////////////////w==");
var PLAY_IMG = atob("GBjBAP//AAAAAAAAAAAIAAAOAAAPgAAP4AAP+AAP/AAP/wAP/8AP//AP//gP//gP//AP/8AP/wAP/AAP+AAP4AAPgAAOAAAIAAAAAAAAAAA=");
var RESET_IMG = atob("GBiBAf////////////AAD+AAB+f/5+f/5+f/5+cA5+cA5+cA5+cA5+cA5+cA5+cA5+cA5+f/5+f/5+f/5+AAB/AAD////////////w==");
var BLUE_COLOR = "#0ff";
var YELLOW_COLOR = "#ff0";
var BLACK_COLOR = "#000";
var ICON_SCALE = g.getWidth() / 178;
var ICON_SIZE_IN_PIXELS = 24;
var UPDATE_DELAY_MS = 100;
function convertTimeToText(time) {
var hours = Math.floor(time / 3600000);
var minutes = Math.floor(time / 60000) % 60;
var seconds = Math.floor(time / 1000) % 60;
var tenthsOfASecond = Math.floor(time / 100) % 10;
if (hours == 0) {
return ("0" + minutes).substr(-2) + ":" + ("0" + seconds).substr(-2) + "." + tenthsOfASecond;
}
else {
return "0" + hours + ":" + ("0" + minutes).substr(-2) + ":" + ("0" + seconds).substr(-2);
}
}
var Button = /** @class */ (function () {
function Button(x, y, width, height, color, callback, image) {
this.x = x;
this.y = y;
this.width = width;
this.height = height;
this.color = color;
this.callback = callback;
this.image = image;
}
Button.prototype.setImage = function (image) {
this.image = image;
};
Button.prototype.center = function () {
return {
x: this.x + this.width / 2,
y: this.y + this.height / 2
};
};
Button.prototype.draw = function () {
g.setColor(this.color);
g.fillRect(this.x, this.y, this.x + this.width, this.y + this.height);
if (this.image != undefined) {
var center = this.center();
var imageSize = ICON_SCALE * ICON_SIZE_IN_PIXELS;
var iconX = center.x - imageSize / 2.0;
var iconY = center.y - imageSize / 2.0;
g.drawImage(this.image, iconX, iconY, { scale: ICON_SCALE });
}
g.drawRect(this.x, this.y, this.x + this.width, this.y + this.height);
};
Button.prototype.isOnButton = function (x, y) {
return this.x <= x && x <= this.x + this.width && this.y <= y && y <= this.y + this.height;
};
// Returns true if clicked.
Button.prototype.onClick = function (x, y) {
if (this.isOnButton(x, y)) {
this.callback();
return true;
}
return false;
};
return Button;
}());
var TimerApp = /** @class */ (function () {
function TimerApp() {
this.width = g.getWidth();
this.height = g.getWidth();
this.timers = [];
this.displayedTimerIndex = 0;
this.timeTextY = this.height * (2.0 / 5.0);
this.loadStateOrDefault();
this.initializeButtons();
this.initializeScreen();
this.initApp();
}
TimerApp.prototype.run = function () {
this.draw();
var self = this;
this.updateInterval = setInterval(function () {
self.draw();
}, UPDATE_DELAY_MS);
};
TimerApp.prototype.initApp = function () {
var self = this;
var originalTheme = g.theme;
Bangle.setUI({
mode: "custom",
btn: function () { return load(); },
touch: function (button, point) {
var x = point.x;
var y = point.y;
// adjust for outside the dimension of the screen
// http://forum.espruino.com/conversations/371867/#comment16406025
if (y > self.height)
y = self.height;
if (y < 0)
y = 0;
if (x > self.width)
x = self.width;
if (x < 0)
x = 0;
// not running, and reset
var timer = self.displayedTimer();
self.largeButton.onClick(x, y);
// if (!running && tCurrent == tTotal && bigPlayPauseBtn.check(x, y)) return;
// // paused and hit play
// if (!running && tCurrent != tTotal && smallPlayPauseBtn.check(x, y)) return;
// // paused and press reset
// if (!running && tCurrent != tTotal && resetBtn.check(x, y)) return;
// // must be running
// if (running && bigPlayPauseBtn.check(x, y)) return;
},
remove: function () {
self.pauseTimer();
// Bangle.removeListener("lcdPower", onLCDPower)
g.setTheme(originalTheme);
}
});
};
TimerApp.prototype.initializeScreen = function () {
Bangle.loadWidgets();
Bangle.drawWidgets();
g.setColor(BLACK_COLOR);
g.fillRect(0, 0, this.width, this.height);
};
TimerApp.prototype.initializeButtons = function () {
function largeButtonClick() {
console.log("BIG BUTTON");
}
function leftButtonClick() {
console.log("LEFT BUTTON");
}
function rightButtonClick() {
console.log("RIGHT BUTTON");
}
this.largeButton = new Button(0.0, (3.0 / 4.0) * this.height, this.width, this.height / 4.0, BLUE_COLOR, largeButtonClick, PLAY_IMG);
this.leftButton = new Button(0.0, (3.0 / 4.0) * this.height, this.width / 2.0, this.height / 4.0, BLUE_COLOR, leftButtonClick, PLAY_IMG);
this.rightButton = new Button(this.width / 2.0, (3.0 / 4.0) * this.height, this.width / 2.0, this.height / 4.0, BLUE_COLOR, rightButtonClick, PAUSE_IMG);
};
TimerApp.prototype.resumeTimer = function () {
var self = this;
this.updateInterval = setInterval(function () {
self.draw();
}, UPDATE_DELAY_MS);
};
TimerApp.prototype.displayedTimer = function () {
return this.timers[this.displayedTimerIndex];
};
TimerApp.prototype.save = function () {
require("Storage").writeJSON(STORAGE_FILE, {
displayedTimerIndex: this.displayedTimerIndex,
timers: this.timers
});
};
TimerApp.prototype.loadStateOrDefault = function () {
this.timers = [
{
startTime: 0.0,
currentTime: 3000.0,
totalTime: 5000.0,
running: true
},
];
};
TimerApp.prototype.drawButtons = function () {
console.log("DRAW BUTTONS", JSON.stringify(this.timers));
var timer = this.displayedTimer();
if (timer.running) {
this.largeButton.draw();
}
else {
this.leftButton.draw();
this.rightButton.draw();
}
};
TimerApp.prototype.drawTime = function () {
var timer = this.displayedTimer();
var totalTime = Date.now();
var timeText = convertTimeToText(totalTime);
g.setFont("Vector", 38);
g.setFontAlign(0, 0);
g.clearRect(0, this.timeTextY - 21, this.width, this.timeTextY + 21);
g.drawString(timeText, this.width / 2, this.timeTextY);
};
TimerApp.prototype.draw = function () {
g.setColor(g.theme.fg);
this.drawButtons();
this.drawTime();
};
TimerApp.prototype.pauseTimer = function () {
if (this.displayedTimer().running) {
clearInterval(this.updateInterval);
this.updateInterval = undefined;
}
};
return TimerApp;
}());
var Timer = /** @class */ (function () {
function Timer(state) {
this.totalTime = state.totalTime;
this.startTime = state.startTime;
this.currentTime = state.currentTime;
this.running = state.running;
}
Timer.prototype.state = function () {
return {
totalTime: this.totalTime,
startTime: this.startTime,
currentTime: this.currentTime,
running: this.running
};
};
return Timer;
}());
var app = new TimerApp();
app.run();

320
src/app.ts Normal file
View File

@ -0,0 +1,320 @@
const STORAGE_FILE = "timer.json"
const PAUSE_IMG = atob(
"GBiBAf////////////////wYP/wYP/wYP/wYP/wYP/wYP/wYP/wYP/wYP/wYP/wYP/wYP/wYP/wYP/wYP/wYP////////////////w=="
)
const PLAY_IMG = atob(
"GBjBAP//AAAAAAAAAAAIAAAOAAAPgAAP4AAP+AAP/AAP/wAP/8AP//AP//gP//gP//AP/8AP/wAP/AAP+AAP4AAPgAAOAAAIAAAAAAAAAAA="
)
const RESET_IMG = atob(
"GBiBAf////////////AAD+AAB+f/5+f/5+f/5+cA5+cA5+cA5+cA5+cA5+cA5+cA5+cA5+f/5+f/5+f/5+AAB/AAD////////////w=="
)
const BLUE_COLOR = "#0ff"
const YELLOW_COLOR = "#ff0"
const BLACK_COLOR = "#000"
const ICON_SCALE = g.getWidth() / 178
const ICON_SIZE_IN_PIXELS = 24
const UPDATE_DELAY_MS = 100
function convertTimeToText(time: number): string {
let hours = Math.floor(time / 3600000)
let minutes = Math.floor(time / 60000) % 60
let seconds = Math.floor(time / 1000) % 60
let tenthsOfASecond = Math.floor(time / 100) % 10
if (hours == 0) {
return ("0" + minutes).substr(-2) + ":" + ("0" + seconds).substr(-2) + "." + tenthsOfASecond
} else {
return "0" + hours + ":" + ("0" + minutes).substr(-2) + ":" + ("0" + seconds).substr(-2)
}
}
interface TimerState {
totalTime: number
startTime: number
currentTime: number
running: boolean
}
interface TimersState {
displayedTimerIndex: number
timers: TimerState[]
}
class Button {
x: number
y: number
width: number
height: number
color: string
callback: () => any
image: string
constructor(
x: number,
y: number,
width: number,
height: number,
color: string,
callback: () => any,
image: string
) {
this.x = x
this.y = y
this.width = width
this.height = height
this.color = color
this.callback = callback
this.image = image
}
setImage(image: string) {
this.image = image
}
center(): { x: number; y: number } {
return {
x: this.x + this.width / 2,
y: this.y + this.height / 2,
}
}
draw() {
g.setColor(this.color)
g.fillRect(this.x, this.y, this.x + this.width, this.y + this.height)
if (this.image != undefined) {
const center = this.center()
const imageSize = ICON_SCALE * ICON_SIZE_IN_PIXELS
const iconX = center.x - imageSize / 2.0
const iconY = center.y - imageSize / 2.0
g.drawImage(this.image, iconX, iconY, { scale: ICON_SCALE })
}
g.drawRect(this.x, this.y, this.x + this.width, this.y + this.height)
}
isOnButton(x: number, y: number): boolean {
return this.x <= x && x <= this.x + this.width && this.y <= y && y <= this.y + this.height
}
// Returns true if clicked.
onClick(x: number, y: number): boolean {
if (this.isOnButton(x, y)) {
this.callback()
return true
}
return false
}
}
class TimerApp {
width: number
height: number
timers: TimerState[]
displayedTimerIndex: number
timeTextY: number
largeButton!: Button
leftButton!: Button
rightButton!: Button
updateInterval: undefined | any
constructor() {
this.width = g.getWidth()
this.height = g.getWidth()
this.timers = []
this.displayedTimerIndex = 0
this.timeTextY = this.height * (2.0 / 5.0)
this.loadStateOrDefault()
this.initializeButtons()
this.initializeScreen()
this.initApp()
}
run() {
this.draw()
const self = this
this.updateInterval = setInterval(function () {
self.draw()
}, UPDATE_DELAY_MS)
}
initApp() {
const self = this
const originalTheme = g.theme
Bangle.setUI({
mode: "custom",
btn: () => load(),
touch: (button, point) => {
let x = point.x
let y = point.y
// adjust for outside the dimension of the screen
// http://forum.espruino.com/conversations/371867/#comment16406025
if (y > self.height) y = self.height
if (y < 0) y = 0
if (x > self.width) x = self.width
if (x < 0) x = 0
// not running, and reset
const timer = self.displayedTimer()
self.largeButton.onClick(x, y)
// if (!running && tCurrent == tTotal && bigPlayPauseBtn.check(x, y)) return;
// // paused and hit play
// if (!running && tCurrent != tTotal && smallPlayPauseBtn.check(x, y)) return;
// // paused and press reset
// if (!running && tCurrent != tTotal && resetBtn.check(x, y)) return;
// // must be running
// if (running && bigPlayPauseBtn.check(x, y)) return;
},
remove: () => {
self.pauseTimer()
// Bangle.removeListener("lcdPower", onLCDPower)
g.setTheme(originalTheme)
},
})
}
initializeScreen() {
Bangle.loadWidgets()
Bangle.drawWidgets()
g.setColor(BLACK_COLOR)
g.fillRect(0, 0, this.width, this.height)
}
initializeButtons() {
function largeButtonClick() {
console.log("BIG BUTTON")
}
function leftButtonClick() {
console.log("LEFT BUTTON")
}
function rightButtonClick() {
console.log("RIGHT BUTTON")
}
this.largeButton = new Button(
0.0,
(3.0 / 4.0) * this.height,
this.width,
this.height / 4.0,
BLUE_COLOR,
largeButtonClick,
PLAY_IMG
)
this.leftButton = new Button(
0.0,
(3.0 / 4.0) * this.height,
this.width / 2.0,
this.height / 4.0,
BLUE_COLOR,
leftButtonClick,
PLAY_IMG
)
this.rightButton = new Button(
this.width / 2.0,
(3.0 / 4.0) * this.height,
this.width / 2.0,
this.height / 4.0,
BLUE_COLOR,
rightButtonClick,
PAUSE_IMG
)
}
resumeTimer() {
const self = this
this.updateInterval = setInterval(function () {
self.draw()
}, UPDATE_DELAY_MS)
}
displayedTimer(): TimerState {
return this.timers[this.displayedTimerIndex]
}
save() {
require("Storage").writeJSON(STORAGE_FILE, {
displayedTimerIndex: this.displayedTimerIndex,
timers: this.timers,
})
}
loadStateOrDefault() {
this.timers = [
{
startTime: 0.0,
currentTime: 3000.0,
totalTime: 5000.0,
running: true,
},
]
}
drawButtons() {
console.log("DRAW BUTTONS", JSON.stringify(this.timers))
const timer = this.displayedTimer()
if (timer.running) {
this.largeButton.draw()
} else {
this.leftButton.draw()
this.rightButton.draw()
}
}
drawTime() {
const timer = this.displayedTimer()
const totalTime = Date.now()
const timeText = convertTimeToText(totalTime)
g.setFont("Vector", 38)
g.setFontAlign(0, 0)
g.clearRect(0, this.timeTextY - 21, this.width, this.timeTextY + 21)
g.drawString(timeText, this.width / 2, this.timeTextY)
}
draw() {
g.setColor(g.theme.fg)
this.drawButtons()
this.drawTime()
}
pauseTimer() {
if (this.displayedTimer().running) {
clearInterval(this.updateInterval)
this.updateInterval = undefined
}
}
}
class Timer {
totalTime: number
startTime: number
currentTime: number
running: boolean
constructor(state: TimerState) {
this.totalTime = state.totalTime
this.startTime = state.startTime
this.currentTime = state.currentTime
this.running = state.running
}
state(): TimerState {
return {
totalTime: this.totalTime,
startTime: this.startTime,
currentTime: this.currentTime,
running: this.running,
}
}
}
const app = new TimerApp()
app.run()

264
src/stopwatch.app.js Normal file
View File

@ -0,0 +1,264 @@
{
const CONFIGFILE = "stopwatch.json";
const now = Date.now();
const config = Object.assign({
state: {
total: now,
start: now,
current: now,
running: false,
}
}, require("Storage").readJSON(CONFIGFILE, 1) || {});
let w = g.getWidth();
let h = g.getHeight();
let tTotal = config.state.total;
let tStart = config.state.start;
let tCurrent = config.state.current;
let running = config.state.running;
let timeY = 2 * h / 5;
let displayInterval;
let redrawButtons = true;
const iconScale = g.getWidth() / 178; // scale up/down based on Bangle 2 size
const origTheme = g.theme;
// 24 pixel images, scale to watch
// 1 bit optimal, image string, no E.toArrayBuffer()
const pause_img = atob("GBiBAf////////////////wYP/wYP/wYP/wYP/wYP/wYP/wYP/wYP/wYP/wYP/wYP/wYP/wYP/wYP/wYP/wYP////////////////w==");
const play_img = atob("GBjBAP//AAAAAAAAAAAIAAAOAAAPgAAP4AAP+AAP/AAP/wAP/8AP//AP//gP//gP//AP/8AP/wAP/AAP+AAP4AAPgAAOAAAIAAAAAAAAAAA=");
const reset_img = atob("GBiBAf////////////AAD+AAB+f/5+f/5+f/5+cA5+cA5+cA5+cA5+cA5+cA5+cA5+cA5+f/5+f/5+f/5+AAB/AAD////////////w==");
const saveState = function () {
config.state.total = tTotal;
config.state.start = tStart;
config.state.current = tCurrent;
config.state.running = running;
require("Storage").writeJSON(CONFIGFILE, config);
};
const log_debug = function (o) {
//console.log(o);
};
const timeToText = function (t) {
let hrs = Math.floor(t / 3600000);
let mins = Math.floor(t / 60000) % 60;
let secs = Math.floor(t / 1000) % 60;
let tnth = Math.floor(t / 100) % 10;
let text;
if (hrs === 0)
text = ("0" + mins).substr(-2) + ":" + ("0" + secs).substr(-2) + "." + tnth;
else
text = ("0" + hrs) + ":" + ("0" + mins).substr(-2) + ":" + ("0" + secs).substr(-2);
//log_debug(text);
return text;
};
const drawButtons = function () {
log_debug("drawButtons()");
if (!running && tCurrent == tTotal) {
bigPlayPauseBtn.draw();
} else if (!running && tCurrent != tTotal) {
resetBtn.draw();
smallPlayPauseBtn.draw();
} else {
bigPlayPauseBtn.draw();
}
redrawButtons = false;
};
const drawTime = function () {
log_debug("drawTime()");
let Tt = tCurrent - tTotal;
let Ttxt = timeToText(Tt);
// total time
g.setFont("Vector", 38); // check
g.setFontAlign(0, 0);
g.clearRect(0, timeY - 21, w, timeY + 21);
g.setColor(g.theme.fg);
g.drawString(Ttxt, w / 2, timeY);
};
const draw = function () {
if (running) tCurrent = Date.now();
g.setColor(g.theme.fg);
if (redrawButtons) drawButtons();
drawTime();
};
const startTimer = function () {
log_debug("startTimer()");
draw();
displayInterval = setInterval(draw, 100);
};
const stopTimer = function () {
log_debug("stopTimer()");
if (displayInterval) {
clearInterval(displayInterval);
displayInterval = undefined;
}
};
// BTN stop start
const stopStart = function () {
log_debug("stopStart()");
if (running)
stopTimer();
running = !running;
Bangle.buzz();
if (running)
tStart = Date.now() + tStart - tCurrent;
tTotal = Date.now() + tTotal - tCurrent;
tCurrent = Date.now();
setButtonImages();
redrawButtons = true;
if (running) {
startTimer();
} else {
draw();
}
saveState();
};
const setButtonImages = function () {
if (running) {
bigPlayPauseBtn.setImage(pause_img);
smallPlayPauseBtn.setImage(pause_img);
resetBtn.setImage(reset_img);
} else {
bigPlayPauseBtn.setImage(play_img);
smallPlayPauseBtn.setImage(play_img);
resetBtn.setImage(reset_img);
}
};
// lap or reset
const lapReset = function () {
log_debug("lapReset()");
if (!running && tStart != tCurrent) {
redrawButtons = true;
Bangle.buzz();
tStart = tCurrent = tTotal = Date.now();
g.clearRect(0, 24, w, h);
draw();
}
saveState();
};
// simple on screen button class
const BUTTON = function (name, x, y, w, h, c, f, i) {
this.name = name;
this.x = x;
this.y = y;
this.w = w;
this.h = h;
this.color = c;
this.callback = f;
this.img = i;
};
BUTTON.prototype.setImage = function (i) {
this.img = i;
};
// if pressed the callback
BUTTON.prototype.check = function (x, y) {
//console.log(this.name + ":check() x=" + x + " y=" + y +"\n");
if (x >= this.x && x <= (this.x + this.w) && y >= this.y && y <= (this.y + this.h)) {
log_debug(this.name + ":callback\n");
this.callback();
return true;
}
return false;
};
BUTTON.prototype.draw = function () {
g.setColor(this.color);
g.fillRect(this.x, this.y, this.x + this.w, this.y + this.h);
g.setColor("#000"); // the icons and boxes are drawn black
if (this.img != undefined) {
let iw = iconScale * 24; // the images were loaded as 24 pixels, we will scale
let ix = this.x + ((this.w - iw) / 2);
let iy = this.y + ((this.h - iw) / 2);
log_debug("g.drawImage(" + ix + "," + iy + "{scale: " + iconScale + "})");
g.drawImage(this.img, ix, iy, { scale: iconScale });
}
g.drawRect(this.x, this.y, this.x + this.w, this.y + this.h);
};
const bigPlayPauseBtn = new BUTTON("big", 0, 3 * h / 4, w, h / 4, "#0ff", stopStart, play_img);
const smallPlayPauseBtn = new BUTTON("small", w / 2, 3 * h / 4, w / 2, h / 4, "#0ff", stopStart, play_img);
const resetBtn = new BUTTON("rst", 0, 3 * h / 4, w / 2, h / 4, "#ff0", lapReset, pause_img);
bigPlayPauseBtn.setImage(play_img);
smallPlayPauseBtn.setImage(play_img);
resetBtn.setImage(pause_img);
Bangle.setUI({
mode: "custom", btn: () => load(), touch: (button, xy) => {
let x = xy.x;
let y = xy.y;
// adjust for outside the dimension of the screen
// http://forum.espruino.com/conversations/371867/#comment16406025
if (y > h) y = h;
if (y < 0) y = 0;
if (x > w) x = w;
if (x < 0) x = 0;
// not running, and reset
if (!running && tCurrent == tTotal && bigPlayPauseBtn.check(x, y)) return;
// paused and hit play
if (!running && tCurrent != tTotal && smallPlayPauseBtn.check(x, y)) return;
// paused and press reset
if (!running && tCurrent != tTotal && resetBtn.check(x, y)) return;
// must be running
if (running && bigPlayPauseBtn.check(x, y)) return;
}, remove: () => {
if (displayInterval) {
clearInterval(displayInterval);
displayInterval = undefined;
}
Bangle.removeListener('lcdPower', onLCDPower);
g.setTheme(origTheme);
}
});
// Stop updates when LCD is off, restart when on
const onLCDPower = (on) => {
if (on) {
draw(); // draw immediately, queue redraw
}
};
Bangle.on('lcdPower', onLCDPower);
// Clear the screen once, at startup
g.setTheme({ bg: "#000", fg: "#fff", dark: true }).clear();
// above not working, hence using next 2 lines
g.setColor("#000");
g.fillRect(0, 0, w, h);
Bangle.loadWidgets();
Bangle.drawWidgets();
setButtonImages();
if (running) {
startTimer();
} else {
draw();
}
}

1
src/stopwatch.icon.js Normal file
View File

@ -0,0 +1 @@
require("heatshrink").decompress(atob("mEw4UA///vvvvEF/muH+cDHgPABf4AElWoKhILClALH1WqAQIWHBYIABwAKEgQKD1WgBYkK1X1r4XHlWtqtVvQLG1XVBYNXHYsC1YJBBoPqC4kKEQILCvQ7EhW1BYdeBYkqytVqwCCQwkqCgILCq4LFIoILCqoLEIwIsBGQJIBBZ+pA4Na0oDBtQLGvSFCBaYjIHYR3CI5AADBYhrCAAaDHAASDGQASGCBYizCAASzFZYQACZYrjCIwb7QHgIkCvQ6EGAWq+tf1QuEGAWqAAQuFEgQKBEQw9DHIwAuA="))

14
tsconfig.json Normal file
View File

@ -0,0 +1,14 @@
{
"compilerOptions": {
"target": "es5",
"module": "commonjs",
"lib": [
"es2015",
"dom" // setInterval, console
],
"strict": true
},
"include": [
"src/**/*.ts"
]
}