From 91e896bf9e57c0f25b599852197d3378db5bab27 Mon Sep 17 00:00:00 2001 From: Rob Pilling Date: Mon, 28 Apr 2025 12:22:42 +0100 Subject: [PATCH] tally: new app --- apps/tally/ChangeLog | 1 + apps/tally/README.md | 9 +++ apps/tally/app-icon.js | 1 + apps/tally/app.js | 100 ++++++++++++++++++++++++++++++++ apps/tally/app.png | Bin 0 -> 296 bytes apps/tally/app.ts | 120 +++++++++++++++++++++++++++++++++++++++ apps/tally/clkinfo.js | 25 ++++++++ apps/tally/clkinfo.ts | 30 ++++++++++ apps/tally/icon.png | Bin 0 -> 760 bytes apps/tally/metadata.json | 22 +++++++ apps/tally/settings.js | 51 +++++++++++++++++ apps/tally/settings.ts | 66 +++++++++++++++++++++ 12 files changed, 425 insertions(+) create mode 100644 apps/tally/ChangeLog create mode 100644 apps/tally/README.md create mode 100644 apps/tally/app-icon.js create mode 100644 apps/tally/app.js create mode 100644 apps/tally/app.png create mode 100644 apps/tally/app.ts create mode 100644 apps/tally/clkinfo.js create mode 100644 apps/tally/clkinfo.ts create mode 100644 apps/tally/icon.png create mode 100644 apps/tally/metadata.json create mode 100644 apps/tally/settings.js create mode 100644 apps/tally/settings.ts diff --git a/apps/tally/ChangeLog b/apps/tally/ChangeLog new file mode 100644 index 000000000..1a3bc1757 --- /dev/null +++ b/apps/tally/ChangeLog @@ -0,0 +1 @@ +0.01: New app! diff --git a/apps/tally/README.md b/apps/tally/README.md new file mode 100644 index 000000000..ba840a873 --- /dev/null +++ b/apps/tally/README.md @@ -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. diff --git a/apps/tally/app-icon.js b/apps/tally/app-icon.js new file mode 100644 index 000000000..151db87e8 --- /dev/null +++ b/apps/tally/app-icon.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("mEwwMB/4Ai/gsDn/4Aocf+AFDh4FGDgYFU/k/Aq08n4FTAgIFtO66bKVoq5F//wj4fD+AIBAod/AojYE+IFE+YFE+4FKABQ=")) diff --git a/apps/tally/app.js b/apps/tally/app.js new file mode 100644 index 000000000..b56989628 --- /dev/null +++ b/apps/tally/app.js @@ -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()); diff --git a/apps/tally/app.png b/apps/tally/app.png new file mode 100644 index 0000000000000000000000000000000000000000..d339ecc190a0002e99534ad3b5b6c143498b2dc8 GIT binary patch literal 296 zcmeAS@N?(olHy`uVBq!ia0vp^1|ZDA1|-9oezpTC&H|6fVg?3oVGw3ym^DWND0s`$ z#WAE}&f6J|d`$)duJ>(AR>lSACtrDRQiXAwRe%K832gU!x;9$>HcN_b!0s6gl}wAoRw$_**tt0)>rJRUtAxa5 zmSeYeF8o(Kb5$V&YsK{W^BY{h1u~Qi@A&7<@H-%Y(S)(2<;N-2b$jC|8qGW<_%7m_qKA9Svb&_44$rjF6*2UngC7Aa-{$O literal 0 HcmV?d00001 diff --git a/apps/tally/app.ts b/apps/tally/app.ts new file mode 100644 index 000000000..5a26dacf7 --- /dev/null +++ b/apps/tally/app.ts @@ -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()); diff --git a/apps/tally/clkinfo.js b/apps/tally/clkinfo.js new file mode 100644 index 000000000..6c4548cb1 --- /dev/null +++ b/apps/tally/clkinfo.js @@ -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 () { }, + }); }), + }; +}) diff --git a/apps/tally/clkinfo.ts b/apps/tally/clkinfo.ts new file mode 100644 index 000000000..c9759e516 --- /dev/null +++ b/apps/tally/clkinfo.ts @@ -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 diff --git a/apps/tally/icon.png b/apps/tally/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..d075fa9bd161d14160644b3cf06d368f40ca284b GIT binary patch literal 760 zcmVEX>4Tx04R}tkv&MmKpe$iKeQqh1v{uXWT;MdQ4!s06^c+H)C#RSm|XfHG-*gu zTpR`0f`cE6RRrd-(Wz7vovp=l&c$O5S9EPauvn-LQz)iKjL# zo%23%gq0+P_?&pmpbHW|a$R=$jdRgqKhKO9ne-fSgjguHvE0V2WT?cG#8E}nDBquT zS>e3JSuIyt^Pc>L;k>rI#C4j(NMZqt5FtQD6&onSLX1|86ccIMPk8u;9KT2|nOqxS zs!%!3j@L$uYrP0wrCqZy@@`Lzg#8JPZ;AV%?$<0NB7D`=iN*Yr0uN^$_NFFaa z=iQT&`}pq|RqjtP9>}@E57t$od(7}TaLj^hRIXrx7u+DPs&{l`iZ|TiJB@_|7eRg% z*FKnHj?2R8w4%Na@~=M#G;Cn2`lQG1<|UyV>qU=be%4Xd8P&+K}hGlI^B>CxN`=eO~z%yXK7oqaBgUTF$4Xj<6-L!aJ@b zIYXP>EI2p4e}x6^a2gM0IF2zcUTA>@9`RbIJ_+LUXn1QZ@r<)rYZ{X_iOk~gSmFss q`;Kphk3*3cC)~rK$oJ7{H~0m8n@GsC5NyZ*0000"] = 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(); +}) diff --git a/apps/tally/settings.ts b/apps/tally/settings.ts new file mode 100644 index 000000000..87aebb717 --- /dev/null +++ b/apps/tally/settings.ts @@ -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 || ""] = () => { + 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;