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 000000000..d339ecc19 Binary files /dev/null and b/apps/tally/app.png differ 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 000000000..d075fa9bd Binary files /dev/null and b/apps/tally/icon.png differ diff --git a/apps/tally/metadata.json b/apps/tally/metadata.json new file mode 100644 index 000000000..195f7e889 --- /dev/null +++ b/apps/tally/metadata.json @@ -0,0 +1,22 @@ +{ + "id": "tally", + "name": "Tally Keeper", + "shortName": "Tally", + "version": "0.01", + "description": "Add to a tally from clock info", + "icon": "app.png", + "tags": "clkinfo", + "type": "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" } + ] +} diff --git a/apps/tally/settings.js b/apps/tally/settings.js new file mode 100644 index 000000000..f68e609aa --- /dev/null +++ b/apps/tally/settings.js @@ -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 || ""] = 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;