diff --git a/apps/popconlaunch/ChangeLog b/apps/popconlaunch/ChangeLog new file mode 100644 index 000000000..5560f00bc --- /dev/null +++ b/apps/popconlaunch/ChangeLog @@ -0,0 +1 @@ +0.01: New App! diff --git a/apps/popconlaunch/README.md b/apps/popconlaunch/README.md new file mode 100644 index 000000000..2814082a7 --- /dev/null +++ b/apps/popconlaunch/README.md @@ -0,0 +1,8 @@ +Popcon +====== + +Display apps sorted by regular use. No config - install the app and all your launchers will sort apps by most popular, based off launch counts within the last month, and then sort them by the most recently launched app. + +:warning: Warning: this app overrides [`Storage.readJSON`], so may slow down your watch when using apps that perform I/O. + +[`Storage.readJSON`]: https://www.espruino.com/ReferenceBANGLEJS2#l_Storage_readJSON diff --git a/apps/popconlaunch/app.png b/apps/popconlaunch/app.png new file mode 100644 index 000000000..d7cd295e4 Binary files /dev/null and b/apps/popconlaunch/app.png differ diff --git a/apps/popconlaunch/boot.js b/apps/popconlaunch/boot.js new file mode 100644 index 000000000..eb3f18e1b --- /dev/null +++ b/apps/popconlaunch/boot.js @@ -0,0 +1,81 @@ +{ + var oldRead_1 = require("Storage").readJSON; + var monthAgo_1 = Date.now() - 1000 * 86400 * 28; + var cache_1; + var ensureCache_1 = function () { + if (!cache_1) { + cache_1 = oldRead_1("popcon.cache.json", true); + if (!cache_1) + cache_1 = {}; + } + return cache_1; + }; + var saveCache_1 = function (orderChanged) { + require("Storage").writeJSON("popcon.cache.json", cache_1); + if (orderChanged) { + var info = oldRead_1("popcon.info", true); + info.cacheBuster = !info.cacheBuster; + require("Storage").writeJSON("popcon.info", info); + } + }; + var sortCache_1 = function () { + var ents = Object.values(cache_1); + ents.sort(function (a, b) { + var n; + var am = (a.last > monthAgo_1); + var bm = (b.last > monthAgo_1); + n = bm - am; + if (n) + return n; + n = b.pop - a.pop; + if (n) + return n; + n = b.last - a.last; + if (n) + return n; + if (a.name < b.name) + return -1; + if (a.name > b.name) + return 1; + return 0; + }); + var i = 0; + var orderChanged = false; + for (var _i = 0, ents_1 = ents; _i < ents_1.length; _i++) { + var ent = ents_1[_i]; + if (ent.sortorder !== i) + orderChanged = true; + ent.sortorder = i++; + } + return orderChanged; + }; + require("Storage").readJSON = (function (fname, skipExceptions) { + var _a; + var j = oldRead_1(fname, skipExceptions); + if (/\.info$/.test(fname)) { + var cache_2 = ensureCache_1(); + var so = void 0; + if (j.src && (so = (_a = cache_2[j.src]) === null || _a === void 0 ? void 0 : _a.sortorder) != null) + j.sortorder = so; + else + j.sortorder = 99; + } + return j; + }); + var oldLoad_1 = load; + global.load = function (src) { + if (src) { + var cache_3 = ensureCache_1(); + var ent = cache_3[src] || (cache_3[src] = { + pop: 0, + last: 0, + sortorder: -10, + }); + ent.pop++; + ent.last = Date.now(); + var orderChanged = sortCache_1(); + saveCache_1(orderChanged); + } + return oldLoad_1(src); + }; +} diff --git a/apps/popconlaunch/boot.ts b/apps/popconlaunch/boot.ts new file mode 100644 index 000000000..a7ac73518 --- /dev/null +++ b/apps/popconlaunch/boot.ts @@ -0,0 +1,102 @@ +{ +type Timestamp = number; + +const oldRead = require("Storage").readJSON; +const monthAgo = Date.now() - 1000 * 86400 * 28; +let cache: undefined | { + [key: string]: { + sortorder: number, + pop: number, // amount of launches + last: Timestamp, + } +}; + +const ensureCache = (): NonNull => { + if(!cache){ + cache = oldRead("popcon.cache.json", true); + if(!cache) + cache = {}; + } + return cache; +}; + +const saveCache = (orderChanged: boolean) => { + require("Storage").writeJSON("popcon.cache.json", cache); + if(orderChanged){ + // ensure launchers reload their caches: + const info: AppInfo & { cacheBuster?: boolean } = oldRead("popcon.info", true); + info.cacheBuster = !info.cacheBuster; + require("Storage").writeJSON("popcon.info", info); + } +}; + +const sortCache = () => { + const ents = Object.values(cache); + + ents.sort((a, b) => { + // group the most recently launched apps in the last month, + // then sort by launch count + // then by name + let n; + + const am = (a.last > monthAgo) as unknown as number; + const bm = (b.last > monthAgo) as unknown as number; + n = bm - am; + if(n) return n; + + n = b.pop - a.pop; + if(n) return n; + + // pops are the same, sort by most recent + n = b.last - a.last; + if(n) return n; + + if(a.nameb.name) return 1; + return 0; + }); + + let i = 0; + let orderChanged = false; + for(const ent of ents){ + if(ent.sortorder !== i) orderChanged = true; + ent.sortorder = i++; + } + return orderChanged; +}; + +require("Storage").readJSON = ((fname, skipExceptions) => { + const j: AppInfo = oldRead(fname, skipExceptions); + // ^ technically only AppInfo if we're "*.info" + + if(/\.info$/.test(fname)){ + const cache = ensureCache(); + let so; + + if(j.src && (so = cache[j.src]?.sortorder) != null) + j.sortorder = so; + else + j.sortorder = 99; + } + + return j; +}) satisfies typeof oldRead; + +const oldLoad = load; +global.load = (src: string) => { + if(src){ + const cache = ensureCache(); + const ent = cache[src] ||= { + pop: 0, + last: 0, + sortorder: -10, + }; + ent.pop++; + ent.last = Date.now(); + const orderChanged = sortCache(); + saveCache(orderChanged); + } + + return oldLoad(src); +}; +} diff --git a/apps/popconlaunch/icon.js b/apps/popconlaunch/icon.js new file mode 100644 index 000000000..a64ed9747 --- /dev/null +++ b/apps/popconlaunch/icon.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("mEwxH+AH4A/AEvNAAItq6YAFGoYEBF9I1GQ7vTAIIAMS7YqIRAQADBYwunGAyTYFyAyJLzIlJGBYvYXJowcRRaaHCAYveAoYuIF4/NdzQATF6f+45KD6vV6gfD44ME6gNBBgguU3YAB4/U64AD6fHBYQMB6YME6gMDFyoAB6wiE6wLEBhgvWEInX6AvF6ANFF7fREInRF4oMLF6xSLNhgv/GA3JF6PJFywv/F8nCF6PCF8XBBYfBF/4vSQZabLF/4wI6IvP6IuYF4nQF5/QF/4vN5IvP5Iv/F6fIBQfIF8ZUKNRQvVGAYvUFywvz3YvPRzQvE6IvN6IvfACYv/AH4A/AH4Ar")) diff --git a/apps/popconlaunch/metadata.json b/apps/popconlaunch/metadata.json new file mode 100644 index 000000000..0ccc54d9e --- /dev/null +++ b/apps/popconlaunch/metadata.json @@ -0,0 +1,20 @@ +{ + "id": "popconlaunch", + "name": "Popcon Launcher", + "shortName": "Popcon", + "version": "0.01", + "description": "Launcher modification - your launchers will display your favourite (popular) apps first. Overrides `readJSON`, may slow down your watch", + "readme": "README.md", + "icon": "app.png", + "type": "bootloader", + "tags": "tool,system,launcher", + "supports": ["BANGLEJS2"], + "storage": [ + {"name":"popcon.boot.js","url":"boot.js"}, + {"name":"popcon.img","url":"icon.js","evaluate":true} + ], + "data": [ + {"name":"popcon.cache.json"} + ], + "sortorder": -10 +} diff --git a/typescript/types/info.d.ts b/typescript/types/info.d.ts new file mode 100644 index 000000000..41dec8578 --- /dev/null +++ b/typescript/types/info.d.ts @@ -0,0 +1,12 @@ +type AppInfo = { + src: string, + img: string, + icon: string, + name: string, + type: AppType, + sortorder?: number, +}; + +type AppType = "app" | "clock" | "widget" | "module" | "bootloader" | + "settings" | "clkinfo" | "RAM" | "launch" | "textinput" | "scheduler" | + "notify" | "locale";