Merge pull request #3831 from bobrippling/feat/tally-app

New app/clkinfo: tally
master
Rob Pilling 2025-04-30 22:33:31 +01:00 committed by GitHub
commit 46795ed074
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
14 changed files with 428 additions and 0 deletions

1
apps/tally/ChangeLog Normal file
View File

@ -0,0 +1 @@
0.01: New app!

9
apps/tally/README.md Normal file
View File

@ -0,0 +1,9 @@
# Tally
An app that lets you keep a tally - when events happen, etc.
Use the clock info to add to the tally. The app can be used to review/edit/delete tallies
## Usage
Navigate to the tally clock info, then swipe up/down to select one of the tallies to enter. Tap on it to add to the tally.

1
apps/tally/app-icon.js Normal file
View File

@ -0,0 +1 @@
require("heatshrink").decompress(atob("mEwwInkgf/AAXAg4FD8EPAofwAowcDAqkD4AFW4fAAqYEBAtp3XTZStFXIsAh/gD4cPBAIFDwAFEbAkOAokMAokIApQAKA=="))

100
apps/tally/app.js Normal file
View File

@ -0,0 +1,100 @@
var storage = require("Storage");
function readTallies() {
var tallies = [];
var f = storage.open("tallies.csv", "r");
var line;
while ((line = f.readLine()) !== undefined) {
var parts = line.replace("\n", "").split(",");
tallies.push({ date: new Date(parts[0]), name: parts[1] });
}
return tallies;
}
function saveTallies(tallies) {
var f = storage.open("tallies.csv", "w");
for (var _i = 0, tallies_1 = tallies; _i < tallies_1.length; _i++) {
var tally = tallies_1[_i];
f.write([tally.date.toISOString(), tally.name].join(",") + "\n");
}
}
var dayEq = function (a, b) {
return a.getFullYear() === b.getFullYear()
&& a.getMonth() === b.getMonth()
&& a.getDate() === b.getDate();
};
function showTallies(tallies) {
var menu = {
"": { title: "Tallies" },
"< Back": function () { return load(); },
};
var today = new Date;
var day;
tallies.forEach(function (tally, i) {
var td = tally.date;
if (!dayEq(day !== null && day !== void 0 ? day : today, td)) {
var s = "".concat(td.getFullYear(), "-").concat(pad2(td.getMonth() + 1), "-").concat(pad2(td.getDate()));
menu[s] = function () { };
day = td;
}
menu["".concat(tfmt(tally), ": ").concat(tally.name)] = function () { return editTally(tallies, i); };
});
E.showMenu(menu);
}
function editTally(tallies, i) {
var tally = tallies[i];
var onback = function () {
saveTallies(tallies);
E.removeListener("kill", onback);
};
E.on("kill", onback);
var menu = {
"": { title: "Edit ".concat(tally.name) },
"< Back": function () {
onback();
showTallies(tallies);
},
"Name": {
value: tally.name,
onchange: function () {
setTimeout(function () {
require("textinput")
.input({ text: tally.name })
.then(function (text) {
if (text) {
tally.name = text;
}
editTally(tallies, i);
});
}, 0);
},
},
"Time": {
value: tally.date.getTime(),
format: function (_tm) { return tfmt(tally); },
onchange: function (v) {
tally.date = new Date(v);
},
step: 60000,
},
"Delete": function () {
E.showPrompt("Delete \"".concat(tally.name, "\"?"), {
title: "Delete",
buttons: { Yes: true, No: false },
}).then(function (confirm) {
if (confirm) {
tallies.splice(i, 1);
saveTallies(tallies);
showTallies(tallies);
return;
}
editTally(tallies, i);
});
},
};
E.showMenu(menu);
}
function tfmt(tally) {
var d = tally.date;
return "".concat(d.getHours(), ":").concat(pad2(d.getMinutes()));
}
var pad2 = function (s) { return ("0" + s.toFixed(0)).slice(-2); };
showTallies(readTallies());

BIN
apps/tally/app.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 296 B

120
apps/tally/app.ts Normal file
View File

@ -0,0 +1,120 @@
const storage = require("Storage");
type Tally = {
date: Date,
name: string,
};
function readTallies() {
const tallies: Tally[] = [];
const f = storage.open("tallies.csv", "r");
let line;
while ((line = f.readLine()) !== undefined) {
const parts = line.replace("\n", "").split(",");
tallies.push({ date: new Date(parts[0]), name: parts[1] });
}
return tallies;
}
function saveTallies(tallies: Tally[]) {
const f = storage.open("tallies.csv", "w");
for(const tally of tallies){
f.write([tally.date.toISOString(), tally.name].join(",") + "\n");
}
}
const dayEq = (a: Date, b: Date) =>
a.getFullYear() === b.getFullYear()
&& a.getMonth() === b.getMonth()
&& a.getDate() === b.getDate();
function showTallies(tallies: Tally[]) {
const menu: Menu = {
"": { title: "Tallies" },
"< Back": () => load(),
};
const today = new Date;
let day: undefined | Date;
tallies.forEach((tally, i) => {
const td = tally.date;
if(!dayEq(day ?? today, td)){
const s = `${td.getFullYear()}-${pad2(td.getMonth() + 1)}-${pad2(td.getDate())}`;
menu[s] = () => {};
day = td;
}
menu[`${tfmt(tally)}: ${tally.name}`] = () => editTally(tallies, i);
});
E.showMenu(menu);
}
function editTally(tallies: Tally[], i: number) {
const tally = tallies[i]!;
const onback = () => {
saveTallies(tallies);
E.removeListener("kill", onback);
};
E.on("kill", onback);
const menu: Menu = {
"": { title: `Edit ${tally.name}` },
"< Back": () => {
onback();
showTallies(tallies)
},
"Name": {
value: tally.name,
onchange: () => {
setTimeout(() => {
require("textinput")
.input({ text: tally.name })
.then(text => {
if (text) {
tally.name = text;
}
editTally(tallies, i);
});
}, 0);
},
},
"Time": {
value: tally.date.getTime(),
format: (_tm: number) => tfmt(tally),
onchange: (v: number) => {
tally.date = new Date(v);
},
step: 60000, // 1 min
},
"Delete": () => {
E.showPrompt(`Delete "${tally.name}"?`, {
title: "Delete",
buttons: { Yes: true, No: false },
}).then(confirm => {
if (confirm) {
tallies.splice(i, 1);
saveTallies(tallies);
showTallies(tallies);
return;
}
// need to regrab ui, can't `m.draw()`
editTally(tallies, i);
});
},
};
E.showMenu(menu);
}
function tfmt(tally: Tally) {
const d = tally.date;
return `${d.getHours()}:${pad2(d.getMinutes())}`;
}
const pad2 = (s: number) => ("0" + s.toFixed(0)).slice(-2);
showTallies(readTallies());

25
apps/tally/clkinfo.js Normal file
View File

@ -0,0 +1,25 @@
(function () {
var storage = require("Storage");
var tallyEntries = storage.readJSON("tallycfg.json", 1) || [];
var img = atob("GBiBAAAAAAAAAAAAAB//+D///DAADDAADDAYDDAYDDAZjDAZjDGZjDGZjDGZjDGZjDAADDAADD///B//+APAAAMAAAIAAAAAAAAAAA==");
return {
name: "Tally",
img: img,
items: tallyEntries.map(function (ent) { return ({
name: ent.name,
img: img,
get: function () {
return { text: this.name, img: img };
},
run: function () {
var f = storage.open("tallies.csv", "a");
f.write([
new Date().toISOString(),
this.name,
].join(",") + "\n");
},
show: function () { },
hide: function () { },
}); }),
};
})

30
apps/tally/clkinfo.ts Normal file
View File

@ -0,0 +1,30 @@
(function() {
const storage = require("Storage");
const tallyEntries = storage.readJSON("tallycfg.json", 1) as TallySettings || [];
// transparent
const img = atob("GBiBAAAAAAAAAAAAAB//+D///DAADDAADDAYDDAYDDAZjDAZjDGZjDGZjDGZjDGZjDAADDAADD///B//+APAAAMAAAIAAAAAAAAAAA==")
// non-transparent
//const img = atob("GBgBAAAAAAAAAAAAH//4P//8MAAMMAAMMBgMMBgMMBmMMBmMMZmMMZmMMZmMMZmMMAAMMAAMP//8H//4A4AAAwAAAgAAAAAAAAAA")
return {
name: "Tally",
img,
items: tallyEntries.map(ent => ({
name: ent.name,
img,
get: function() {
return { text: this.name, img };
},
run: function() {
const f = storage.open("tallies.csv", "a");
f.write([
new Date().toISOString(),
this.name,
].join(",") + "\n");
},
show: function(){},
hide: function(){},
})),
};
}) satisfies ClockInfoFunc

BIN
apps/tally/icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 760 B

21
apps/tally/metadata.json Normal file
View File

@ -0,0 +1,21 @@
{
"id": "tally",
"name": "Tally Keeper",
"shortName": "Tally",
"version": "0.01",
"description": "Add to a tally from clock info",
"icon": "app.png",
"tags": "clkinfo",
"supports": ["BANGLEJS2"],
"readme": "README.md",
"storage": [
{ "name": "tally.settings.js", "url": "settings.js" },
{ "name": "tally.img", "url": "app-icon.js", "evaluate": true },
{ "name": "tally.clkinfo.js", "url": "clkinfo.js" },
{ "name": "tally.app.js", "url": "app.js" }
],
"data": [
{ "name": "tallycfg.json" },
{ "name": "tallies.csv" }
]
}

51
apps/tally/settings.js Normal file
View File

@ -0,0 +1,51 @@
(function (back) {
var storage = require("Storage");
var SETTINGS_FILE = "tallycfg.json";
var tallycfg = storage.readJSON(SETTINGS_FILE, 1) || [];
function saveSettings() {
storage.writeJSON(SETTINGS_FILE, tallycfg);
}
function showMainMenu() {
var menu = {
"": { "title": "Tally Configs" },
"< Back": back,
"Add New": function () { return showEditMenu(); },
};
tallycfg.forEach(function (tally, index) {
menu[tally.name] = function () { return showEditMenu(tally, index); };
});
E.showMenu(menu);
}
function showEditMenu(tally, index) {
var isNew = tally == null;
if (tally == null) {
tally = { name: "" };
index = tallycfg.length;
tallycfg.push(tally);
}
var menu = {
"": { "title": isNew ? "New Tally" : "Edit Tally" },
"< Back": function () {
saveSettings();
showMainMenu();
},
};
menu[tally.name || "<set name>"] = function () {
require("textinput")
.input({ text: tally.name })
.then(function (text) {
tally.name = text;
showEditMenu(tally, index);
});
};
if (!isNew) {
menu["Delete"] = function () {
tallycfg.splice(index, 1);
saveSettings();
showMainMenu();
};
}
E.showMenu(menu);
}
showMainMenu();
})

66
apps/tally/settings.ts Normal file
View File

@ -0,0 +1,66 @@
type TallySettings = TallySetting[];
type TallySetting = { name: string };
(function(back) {
const storage = require("Storage");
const SETTINGS_FILE = "tallycfg.json";
const tallycfg = storage.readJSON(SETTINGS_FILE, 1) as TallySettings || [];
function saveSettings() {
storage.writeJSON(SETTINGS_FILE, tallycfg);
}
function showMainMenu() {
const menu: Menu = {
"": { "title": "Tally Configs" },
"< Back": back,
"Add New": () => showEditMenu(),
};
tallycfg.forEach((tally, index) => {
menu[tally.name] = () => showEditMenu(tally, index);
});
E.showMenu(menu);
}
function showEditMenu(tally?: TallySetting, index?: number) {
const isNew = tally == null;
if (tally == null) {
tally = { name: "" };
index = tallycfg.length;
tallycfg.push(tally);
}
const menu: Menu = {
"": { "title": isNew ? "New Tally" : "Edit Tally" },
"< Back": () => {
saveSettings();
showMainMenu();
},
};
menu[tally.name || "<set name>"] = () => {
require("textinput")
.input({ text: tally.name })
.then(text => {
tally.name = text;
showEditMenu(tally, index);
});
};
if (!isNew) {
menu["Delete"] = () => {
tallycfg.splice(index!, 1);
saveSettings();
showMainMenu();
};
}
E.showMenu(menu);
}
showMainMenu();
}) satisfies SettingsFunc;

View File

@ -8,3 +8,4 @@ declare function require(moduleName: "Layout"): typeof Layout.Layout;
declare function require(moduleName: "power_usage"): PowerUsageModule;
declare function require(moduleName: "exstats"): typeof ExStats;
declare function require(moduleName: "time_utils"): typeof TimeUtils;
declare function require(moduleName: "textinput"): typeof TextInput;

3
typescript/types/textinput.d.ts vendored Normal file
View File

@ -0,0 +1,3 @@
declare module TextInput {
function input(options: { text: string }): Promise<string>;
}