From 4079123c93bed52b653a8d53f4520aafc33c16b9 Mon Sep 17 00:00:00 2001 From: Travis Evans Date: Sat, 19 Aug 2023 18:21:21 -0500 Subject: [PATCH 01/40] =?UTF-8?q?WIP:=20New=20app=20=E2=80=9Cstamplog?= =?UTF-8?q?=E2=80=9D,=20with=20partial=20implementation=20of=20main=20scre?= =?UTF-8?q?en?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/stamplog/app.js | 340 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 340 insertions(+) create mode 100644 apps/stamplog/app.js diff --git a/apps/stamplog/app.js b/apps/stamplog/app.js new file mode 100644 index 0000000000..df11c5f154 --- /dev/null +++ b/apps/stamplog/app.js @@ -0,0 +1,340 @@ +Layout = require('Layout'); +locale = require('locale'); +storage = require('Storage'); + +// Storage filename to store user's timestamp log +const LOG_FILENAME = 'stamplog.json'; + +// Min number of pixels of movement to recognize a touchscreen drag/swipe +const DRAG_THRESHOLD = 10; + +var settings = { + logItemFont: '12x20' +}; + + +// Fetch a stringified image +function getIcon(id) { + if (id == 'add') { +// Graphics.createImage(` +// XX X X X X +// XX X X X X +// XXXXXX X X X X +// XXXXXX X X X X +// XX X X X X +// XX X X X X +// X XX X X +// X X X X +// X XX X +// X X X +// X X +// X XX +// XXX XX +// XXXXX +// XXXX +// XX +// `); + return "\0\x17\x10\x81\x000\t\x12`$K\xF0\x91'\xE2D\x83\t\x12\x06$H\x00\xB1 \x01$\x80\x042\x00\b(\x00 \x00A\x80\x01\xCC\x00\x03\xE0\x00\x0F\x00\x00\x18\x00\x00"; + } else if (id == 'menu') { +// Graphics.createImage(` +// +// +// +// +// XXXXXXXXXXXXXXXX +// XXXXXXXXXXXXXXXX +// +// +// XXXXXXXXXXXXXXXX +// XXXXXXXXXXXXXXXX +// +// +// XXXXXXXXXXXXXXXX +// XXXXXXXXXXXXXXXX +// +// +// `); + return "\0\x10\x10\x81\0\0\0\0\0\0\0\0\0\xFF\xFF\xFF\xFF\0\0\0\0\xFF\xFF\xFF\xFF\0\0\0\0\xFF\xFF\xFF\xFF\0\0\0\0"; + } +} + + +//// Data models ////////////////////////////////// + +// High-level timestamp log object that provides an interface to the +// UI for managing log entries and automatically loading/saving +// changes to flash storage. +class StampLog { + constructor(filename) { + // Name of file to save log to + this.filename = filename; + + // `true` when we have changes that need to be saved + this.isDirty = false; + // Wait at most this many msec upon first data change before + // saving (this is to avoid excessive writes to flash if several + // changes happen quickly; we wait a little bit so they can be + // rolled into a single write) + this.saveTimeout = 30000; + // setTimeout ID for scheduled save job + this.saveId = null; + // Underlying raw log data object. Outside this class it's + // recommended to use only the class methods to change it rather + // than modifying the object directly to ensure that changes are + // recognized and saved to storage. + this.log = this.load(); + } + + // Return the version of the log data that is currently in storage + load() { + let log = storage.readJSON(this.filename, true); + if (!log) log = []; + // Convert stringified datetimes back into Date objects + for (let logEntry of log) { + logEntry.stamp = new Date(logEntry.stamp); + } + return log; + } + + // Write current log data to storage if anything needs to be saved + save() { + // Cancel any pending scheduled calls to save() + if (this.saveId) { + clearTimeout(this.saveId); + this.saveId = null; + } + + if (this.isDirty) { + if (storage.writeJSON(this.filename, this.log)) { + console.log('stamplog: save to storage completed'); + this.isDirty = false; + } else { + console.log('stamplog: save to storage FAILED'); + } + } else { + console.log('stamplog: skipping save to storage because no changes made'); + } + } + + // Mark log as needing to be (re)written to storage + setDirty() { + this.isDirty = true; + if (!this.saveId) { + this.saveId = setTimeout(this.save.bind(this), this.saveTimeout); + } + } + + // Add a timestamp for the current time to the end of the log + addEntry() { + this.log.push({ + stamp: new Date() + }); + this.setDirty(); + } + + // Delete the log objects given in the array `entries` from the log + deleteEntries(entries) { + this.log = this.log.filter(entry => !entries.includes(entry)); + this.setDirty(); + } +} + + +//// UI /////////////////////////////////////////// + +// UI layout render callback for log entries +function renderLogItem(elem) { + if (elem.item) { + g.setColor(g.theme.bg) + .fillRect(elem.x, elem.y, elem.x + elem.w - 1, elem.y + elem.h - 1) + .setFont('12x20') + .setFontAlign(-1, -1) + .setColor(g.theme.fg) + .drawLine(elem.x, elem.y, elem.x + elem.w - 1, elem.y) + .drawString(locale.date(elem.item.stamp, 1) + + '\n' + + locale.time(elem.item.stamp).trim(), + elem.x, elem.y); + } else { + g.setColor(g.blendColor(g.theme.bg, g.theme.fg, 0.25)) + .fillRect(elem.x, elem.y, elem.x + elem.w - 1, elem.y + elem.h - 1); + } +} + +// Main app screen interface, launched by calling start() +class MainScreen { + constructor(stampLog) { + this.stampLog = stampLog; + + // Values set up by start() + this.logItemsPerPage = null; + this.logScrollPos = null; + this.layout = null; + + // Handlers/listeners + this.buttonTimeoutId = null; + this.listeners = {}; + } + + // Launch this UI and make it live + start() { + this.layout = this.getLayout(); + mainScreen.scrollLog('b'); + mainScreen.render(); + + Object.assign(this.listeners, this.getTouchListeners()); + Bangle.on('drag', this.listeners.drag); + } + + // Stop this UI, shut down all timers/listeners, and otherwise clean up + stop() { + if (this.buttonTimeoutId) { + clearTimeout(this.buttonTimeoutId); + this.buttonTimeoutId = null; + } + + // Kill layout handlers + Bangle.removeListener('drag', this.listeners.drag); + Bangle.setUI(); + + // Probably not necessary, but provides feedback for debugging :-) + g.clear(); + } + + // Generate the layout structure for the main UI + getLayout() { + let layout = new Layout( + {type: 'v', + c: [ + // Placeholder to force bottom alignment when there is unused + // vertical screen space + {type: '', id: 'placeholder', fillx: 1, filly: 1}, + + {type: 'v', + id: 'logItems', + + // To be filled in with log item elements once we determine + // how many will fit on screen + c: [], + }, + + {type: 'h', + id: 'buttons', + c: [ + {type: 'btn', font: '6x8:2', fillx: 1, label: '+ XX:XX', id: 'addBtn', + cb: this.addTimestamp.bind(this)}, + {type: 'btn', font: '6x8:2', label: getIcon('menu'), id: 'menuBtn', + cb: L => console.log(L)}, + ], + }, + ], + } + ); + + // Calculate how many log items per page we have space to display + layout.update(); + let availableHeight = layout.placeholder.h; + g.setFont(settings.logItemFont); + let logItemHeight = g.getFontHeight() * 2; + this.logItemsPerPage = Math.floor(availableHeight / logItemHeight); + + // Populate log items in layout + for (i = 0; i < this.logItemsPerPage; i++) { + layout.logItems.c.push( + {type: 'custom', render: renderLogItem, item: undefined, fillx: 1, height: logItemHeight} + ); + } + layout.update(); + + return layout; + } + + // Redraw a particular display `item`, or everything if `item` is falsey + render(item) { + if (!item || item == 'log') { + let layLogItems = this.layout.logItems; + let logIdx = this.logScrollPos - this.logItemsPerPage; + for (let elem of layLogItems.c) { + logIdx++; + elem.item = this.stampLog.log[logIdx]; + } + this.layout.render(layLogItems); + } + + if (!item || item == 'buttons') { + this.layout.addBtn.label = getIcon('add') + ' ' + locale.time(new Date(), 1).trim(); + this.layout.render(this.layout.buttons); + + // Auto-update time of day indication on log-add button upon next minute + if (!this.buttonTimeoutId) { + this.buttonTimeoutId = setTimeout( + () => { + this.buttonTimeoutId = null; + this.render('buttons'); + }, + 60000 - (Date.now() % 60000) + ); + } + } + + } + + getTouchListeners() { + let distanceY = null; + + function dragHandler(ev) { + // Handle up/down swipes for scrolling + if (ev.b) { + if (distanceY === null) { + // Drag started + distanceY = ev.dy; + } else { + // Drag in progress + distanceY += ev.dy; + } + } else { + // Drag ended + if (Math.abs(distanceY) > DRAG_THRESHOLD) { + this.scrollLog(distanceY > 0 ? 'u' : 'd'); + this.render('log'); + } + distanceY = null; + } + } + + return { + 'drag': dragHandler.bind(this), + }; + } + + // Add current timestamp to log and update UI display + addTimestamp() { + this.stampLog.addEntry(); + this.scrollLog('b'); + this.render('log'); + } + + // Scroll display in given direction or to given position: + // 'u': up, 'd': down, 't': to top, 'b': to bottom + scrollLog(how) { + top = (this.stampLog.log.length - 1) % this.logItemsPerPage; + bottom = this.stampLog.log.length - 1; + + if (how == 'u') { + this.logScrollPos -= this.logItemsPerPage; + } else if (how == 'd') { + this.logScrollPos += this.logItemsPerPage; + } else if (how == 't') { + this.logScrollPos = top; + } else if (how == 'b') { + this.logScrollPos = bottom; + } + + this.logScrollPos = E.clip(this.logScrollPos, top, bottom); + } +} + + +stampLog = new StampLog(); +mainScreen = new MainScreen(stampLog); +mainScreen.start(); From a7024aa52b57c7659a81d2fb3fe4e615a1878e1b Mon Sep 17 00:00:00 2001 From: Travis Evans Date: Mon, 21 Aug 2023 15:01:42 -0500 Subject: [PATCH 02/40] Save log on quit and display some kind of alert if error occurs saving --- apps/stamplog/app.js | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/apps/stamplog/app.js b/apps/stamplog/app.js index df11c5f154..f147774487 100644 --- a/apps/stamplog/app.js +++ b/apps/stamplog/app.js @@ -110,6 +110,7 @@ class StampLog { this.isDirty = false; } else { console.log('stamplog: save to storage FAILED'); + this.emit('saveError'); } } else { console.log('stamplog: skipping save to storage because no changes made'); @@ -335,6 +336,15 @@ class MainScreen { } +Bangle.loadWidgets(); +Bangle.drawWidgets(); + stampLog = new StampLog(); +E.on('kill', stampLog.save.bind(stampLog)); +stampLog.on('saveError', () => { + E.showAlert('Trouble saving timestamp log: Data may be lost!', + "Can't save log"); +}); + mainScreen = new MainScreen(stampLog); mainScreen.start(); From 3b5fe9c4a8e1a55f80c45d06f7e661922aaa5214 Mon Sep 17 00:00:00 2001 From: Travis Evans Date: Thu, 24 Aug 2023 15:12:25 -0500 Subject: [PATCH 03/40] Fix accidental use of global var instead of this --- apps/stamplog/app.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/stamplog/app.js b/apps/stamplog/app.js index f147774487..6263fc85a9 100644 --- a/apps/stamplog/app.js +++ b/apps/stamplog/app.js @@ -180,8 +180,8 @@ class MainScreen { // Launch this UI and make it live start() { this.layout = this.getLayout(); - mainScreen.scrollLog('b'); - mainScreen.render(); + this.scrollLog('b'); + this.render(); Object.assign(this.listeners, this.getTouchListeners()); Bangle.on('drag', this.listeners.drag); From 705d6619a8d1c3b960ebeb1f49850d8f57f6f353 Mon Sep 17 00:00:00 2001 From: Travis Evans Date: Thu, 24 Aug 2023 15:14:34 -0500 Subject: [PATCH 04/40] Make save error fit screen better and try to avoid disrupting current UI --- apps/stamplog/app.js | 25 ++++++++++++++++--------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/apps/stamplog/app.js b/apps/stamplog/app.js index 6263fc85a9..d01102dd6b 100644 --- a/apps/stamplog/app.js +++ b/apps/stamplog/app.js @@ -197,9 +197,6 @@ class MainScreen { // Kill layout handlers Bangle.removeListener('drag', this.listeners.drag); Bangle.setUI(); - - // Probably not necessary, but provides feedback for debugging :-) - g.clear(); } // Generate the layout structure for the main UI @@ -336,15 +333,25 @@ class MainScreen { } +function saveErrorAlert() { + currentUI.stop(); + E.showPrompt( + 'Trouble saving timestamp log; data may be lost!', + {title: "Can't save log", + img: '', + buttons: {'Ok': true}, + } + ).then(currentUI.start.bind(currentUI)); +} + + Bangle.loadWidgets(); Bangle.drawWidgets(); stampLog = new StampLog(); E.on('kill', stampLog.save.bind(stampLog)); -stampLog.on('saveError', () => { - E.showAlert('Trouble saving timestamp log: Data may be lost!', - "Can't save log"); -}); +stampLog.on('saveError', saveErrorAlert); + +var currentUI = new MainScreen(stampLog); +currentUI.start(); -mainScreen = new MainScreen(stampLog); -mainScreen.start(); From 575c09436303e574785607ce0dd954009992bf44 Mon Sep 17 00:00:00 2001 From: Travis Evans Date: Fri, 25 Aug 2023 23:30:54 -0500 Subject: [PATCH 05/40] Minor refactoring --- apps/stamplog/app.js | 27 +++++++++++++-------------- 1 file changed, 13 insertions(+), 14 deletions(-) diff --git a/apps/stamplog/app.js b/apps/stamplog/app.js index d01102dd6b..02698cdaae 100644 --- a/apps/stamplog/app.js +++ b/apps/stamplog/app.js @@ -164,6 +164,7 @@ function renderLogItem(elem) { // Main app screen interface, launched by calling start() class MainScreen { + constructor(stampLog) { this.stampLog = stampLog; @@ -179,12 +180,11 @@ class MainScreen { // Launch this UI and make it live start() { - this.layout = this.getLayout(); + this._initLayout(); this.scrollLog('b'); this.render(); - Object.assign(this.listeners, this.getTouchListeners()); - Bangle.on('drag', this.listeners.drag); + this._initTouch(); } // Stop this UI, shut down all timers/listeners, and otherwise clean up @@ -199,8 +199,7 @@ class MainScreen { Bangle.setUI(); } - // Generate the layout structure for the main UI - getLayout() { + _initLayout() { let layout = new Layout( {type: 'v', c: [ @@ -244,7 +243,7 @@ class MainScreen { } layout.update(); - return layout; + this.layout = layout; } // Redraw a particular display `item`, or everything if `item` is falsey @@ -263,7 +262,8 @@ class MainScreen { this.layout.addBtn.label = getIcon('add') + ' ' + locale.time(new Date(), 1).trim(); this.layout.render(this.layout.buttons); - // Auto-update time of day indication on log-add button upon next minute + // Auto-update time of day indication on log-add button upon + // next minute if (!this.buttonTimeoutId) { this.buttonTimeoutId = setTimeout( () => { @@ -277,7 +277,7 @@ class MainScreen { } - getTouchListeners() { + _initTouch() { let distanceY = null; function dragHandler(ev) { @@ -300,9 +300,8 @@ class MainScreen { } } - return { - 'drag': dragHandler.bind(this), - }; + this.listeners.drag = dragHandler.bind(this); + Bangle.on('drag', this.listeners.drag); } // Add current timestamp to log and update UI display @@ -335,12 +334,12 @@ class MainScreen { function saveErrorAlert() { currentUI.stop(); + // Not `showAlert` because the icon plus message don't fit the + // screen well E.showPrompt( 'Trouble saving timestamp log; data may be lost!', {title: "Can't save log", - img: '', - buttons: {'Ok': true}, - } + buttons: {'Ok': true}} ).then(currentUI.start.bind(currentUI)); } From 1307d3b21c76bb23ac31ec8c8d3e991422867c75 Mon Sep 17 00:00:00 2001 From: Travis Evans Date: Sun, 27 Aug 2023 21:53:03 -0500 Subject: [PATCH 06/40] Implement scroll bar for log display --- apps/stamplog/app.js | 86 ++++++++++++++++++++++++++++++++++++-------- 1 file changed, 72 insertions(+), 14 deletions(-) diff --git a/apps/stamplog/app.js b/apps/stamplog/app.js index 02698cdaae..2e05dddc71 100644 --- a/apps/stamplog/app.js +++ b/apps/stamplog/app.js @@ -8,6 +8,9 @@ const LOG_FILENAME = 'stamplog.json'; // Min number of pixels of movement to recognize a touchscreen drag/swipe const DRAG_THRESHOLD = 10; +// Width of scroll indicators +const SCROLL_BAR_WIDTH = 12; + var settings = { logItemFont: '12x20' }; @@ -162,6 +165,42 @@ function renderLogItem(elem) { } } +// Render a scroll indicator +// `scroll` format: { +// pos: int, +// min: int, +// max: int, +// itemsPerPage: int, +// } +function renderScrollBar(elem, scroll) { + const border = 1; + const boxArea = elem.h - 2 * border; + const boxSize = E.clip( + Math.round( + scroll.itemsPerPage / (scroll.max - scroll.min + 1) * (elem.h - 2) + ), + 3, + boxArea + ); + const boxTop = (scroll.max - scroll.min) ? + Math.round( + (scroll.pos - scroll.min) / (scroll.max - scroll.min) + * (boxArea - boxSize) + elem.y + border + ) : elem.y + border; + + // Draw border + g.setColor(g.theme.fg) + .fillRect(elem.x, elem.y, elem.x + elem.w - 1, elem.y + elem.h - 1) + // Draw scroll box area + .setColor(g.theme.bg) + .fillRect(elem.x + border, elem.y + border, + elem.x + elem.w - border - 1, elem.y + elem.h - border - 1) + // Draw scroll box + .setColor(g.blendColor(g.theme.bg, g.theme.fg, 0.5)) + .fillRect(elem.x + border, boxTop, + elem.x + elem.w - border - 1, boxTop + boxSize - 1); +} + // Main app screen interface, launched by calling start() class MainScreen { @@ -207,14 +246,21 @@ class MainScreen { // vertical screen space {type: '', id: 'placeholder', fillx: 1, filly: 1}, - {type: 'v', - id: 'logItems', - - // To be filled in with log item elements once we determine - // how many will fit on screen - c: [], + {type: 'h', + c: [ + {type: 'v', + id: 'logItems', + + // To be filled in with log item elements once we + // determine how many will fit on screen + c: [], + }, + {type: 'custom', + id: 'logScroll', + render: elem => { renderScrollBar(elem, this.logScrollInfo()); } + }, + ], }, - {type: 'h', id: 'buttons', c: [ @@ -241,6 +287,8 @@ class MainScreen { {type: 'custom', render: renderLogItem, item: undefined, fillx: 1, height: logItemHeight} ); } + layout.logScroll.height = logItemHeight * this.logItemsPerPage; + layout.logScroll.width = SCROLL_BAR_WIDTH; layout.update(); this.layout = layout; @@ -256,6 +304,7 @@ class MainScreen { elem.item = this.stampLog.log[logIdx]; } this.layout.render(layLogItems); + this.layout.render(this.layout.logScroll); } if (!item || item == 'buttons') { @@ -311,23 +360,32 @@ class MainScreen { this.render('log'); } + // Get scroll information for log display + logScrollInfo() { + return { + pos: this.logScrollPos, + min: (this.stampLog.log.length - 1) % this.logItemsPerPage, + max: this.stampLog.log.length - 1, + itemsPerPage: this.logItemsPerPage + }; + } + // Scroll display in given direction or to given position: // 'u': up, 'd': down, 't': to top, 'b': to bottom scrollLog(how) { - top = (this.stampLog.log.length - 1) % this.logItemsPerPage; - bottom = this.stampLog.log.length - 1; + let scroll = this.logScrollInfo(); if (how == 'u') { - this.logScrollPos -= this.logItemsPerPage; + this.logScrollPos -= scroll.itemsPerPage; } else if (how == 'd') { - this.logScrollPos += this.logItemsPerPage; + this.logScrollPos += scroll.itemsPerPage; } else if (how == 't') { - this.logScrollPos = top; + this.logScrollPos = scroll.min; } else if (how == 'b') { - this.logScrollPos = bottom; + this.logScrollPos = scroll.max; } - this.logScrollPos = E.clip(this.logScrollPos, top, bottom); + this.logScrollPos = E.clip(this.logScrollPos, scroll.min, scroll.max); } } From b64806e96a2f3cbabb7b2ba5ffc5f6b7328471e4 Mon Sep 17 00:00:00 2001 From: Travis Evans Date: Sun, 27 Aug 2023 21:55:40 -0500 Subject: [PATCH 07/40] Shorten some names --- apps/stamplog/app.js | 42 +++++++++++++++++++++--------------------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/apps/stamplog/app.js b/apps/stamplog/app.js index 2e05dddc71..c979bf3490 100644 --- a/apps/stamplog/app.js +++ b/apps/stamplog/app.js @@ -208,8 +208,8 @@ class MainScreen { this.stampLog = stampLog; // Values set up by start() - this.logItemsPerPage = null; - this.logScrollPos = null; + this.itemsPerPage = null; + this.scrollPos = null; this.layout = null; // Handlers/listeners @@ -220,7 +220,7 @@ class MainScreen { // Launch this UI and make it live start() { this._initLayout(); - this.scrollLog('b'); + this.scroll('b'); this.render(); this._initTouch(); @@ -257,7 +257,7 @@ class MainScreen { }, {type: 'custom', id: 'logScroll', - render: elem => { renderScrollBar(elem, this.logScrollInfo()); } + render: elem => { renderScrollBar(elem, this.scrollInfo()); } }, ], }, @@ -279,15 +279,15 @@ class MainScreen { let availableHeight = layout.placeholder.h; g.setFont(settings.logItemFont); let logItemHeight = g.getFontHeight() * 2; - this.logItemsPerPage = Math.floor(availableHeight / logItemHeight); + this.itemsPerPage = Math.floor(availableHeight / logItemHeight); // Populate log items in layout - for (i = 0; i < this.logItemsPerPage; i++) { + for (i = 0; i < this.itemsPerPage; i++) { layout.logItems.c.push( {type: 'custom', render: renderLogItem, item: undefined, fillx: 1, height: logItemHeight} ); } - layout.logScroll.height = logItemHeight * this.logItemsPerPage; + layout.logScroll.height = logItemHeight * this.itemsPerPage; layout.logScroll.width = SCROLL_BAR_WIDTH; layout.update(); @@ -298,7 +298,7 @@ class MainScreen { render(item) { if (!item || item == 'log') { let layLogItems = this.layout.logItems; - let logIdx = this.logScrollPos - this.logItemsPerPage; + let logIdx = this.scrollPos - this.itemsPerPage; for (let elem of layLogItems.c) { logIdx++; elem.item = this.stampLog.log[logIdx]; @@ -342,7 +342,7 @@ class MainScreen { } else { // Drag ended if (Math.abs(distanceY) > DRAG_THRESHOLD) { - this.scrollLog(distanceY > 0 ? 'u' : 'd'); + this.scroll(distanceY > 0 ? 'u' : 'd'); this.render('log'); } distanceY = null; @@ -356,36 +356,36 @@ class MainScreen { // Add current timestamp to log and update UI display addTimestamp() { this.stampLog.addEntry(); - this.scrollLog('b'); + this.scroll('b'); this.render('log'); } // Get scroll information for log display - logScrollInfo() { + scrollInfo() { return { - pos: this.logScrollPos, - min: (this.stampLog.log.length - 1) % this.logItemsPerPage, + pos: this.scrollPos, + min: (this.stampLog.log.length - 1) % this.itemsPerPage, max: this.stampLog.log.length - 1, - itemsPerPage: this.logItemsPerPage + itemsPerPage: this.itemsPerPage }; } // Scroll display in given direction or to given position: // 'u': up, 'd': down, 't': to top, 'b': to bottom - scrollLog(how) { - let scroll = this.logScrollInfo(); + scroll(how) { + let scroll = this.scrollInfo(); if (how == 'u') { - this.logScrollPos -= scroll.itemsPerPage; + this.scrollPos -= scroll.itemsPerPage; } else if (how == 'd') { - this.logScrollPos += scroll.itemsPerPage; + this.scrollPos += scroll.itemsPerPage; } else if (how == 't') { - this.logScrollPos = scroll.min; + this.scrollPos = scroll.min; } else if (how == 'b') { - this.logScrollPos = scroll.max; + this.scrollPos = scroll.max; } - this.logScrollPos = E.clip(this.logScrollPos, scroll.min, scroll.max); + this.scrollPos = E.clip(this.scrollPos, scroll.min, scroll.max); } } From 12f3a46a7e61598f7fabab5c4b9a4957b3ef72b9 Mon Sep 17 00:00:00 2001 From: Travis Evans Date: Sun, 27 Aug 2023 21:58:10 -0500 Subject: [PATCH 08/40] Auto render log when scroll() called --- apps/stamplog/app.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/stamplog/app.js b/apps/stamplog/app.js index c979bf3490..0b74fc2e15 100644 --- a/apps/stamplog/app.js +++ b/apps/stamplog/app.js @@ -221,7 +221,7 @@ class MainScreen { start() { this._initLayout(); this.scroll('b'); - this.render(); + this.render('buttons'); this._initTouch(); } @@ -343,7 +343,6 @@ class MainScreen { // Drag ended if (Math.abs(distanceY) > DRAG_THRESHOLD) { this.scroll(distanceY > 0 ? 'u' : 'd'); - this.render('log'); } distanceY = null; } @@ -357,7 +356,6 @@ class MainScreen { addTimestamp() { this.stampLog.addEntry(); this.scroll('b'); - this.render('log'); } // Get scroll information for log display @@ -386,6 +384,8 @@ class MainScreen { } this.scrollPos = E.clip(this.scrollPos, scroll.min, scroll.max); + + this.render('log'); } } From e8fc765943316d89dbe449f4257d5ac3f8540764 Mon Sep 17 00:00:00 2001 From: Travis Evans Date: Sun, 27 Aug 2023 22:28:05 -0500 Subject: [PATCH 09/40] Improve scrolling: haptic feedback, with multiple steps per swipe available --- apps/stamplog/app.js | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/apps/stamplog/app.js b/apps/stamplog/app.js index 0b74fc2e15..4776e173f7 100644 --- a/apps/stamplog/app.js +++ b/apps/stamplog/app.js @@ -6,7 +6,7 @@ storage = require('Storage'); const LOG_FILENAME = 'stamplog.json'; // Min number of pixels of movement to recognize a touchscreen drag/swipe -const DRAG_THRESHOLD = 10; +const DRAG_THRESHOLD = 30; // Width of scroll indicators const SCROLL_BAR_WIDTH = 12; @@ -340,10 +340,13 @@ class MainScreen { distanceY += ev.dy; } } else { - // Drag ended - if (Math.abs(distanceY) > DRAG_THRESHOLD) { - this.scroll(distanceY > 0 ? 'u' : 'd'); - } + // Drag released + distanceY = null; + } + if (Math.abs(distanceY) > DRAG_THRESHOLD) { + // Scroll threshold reached + Bangle.buzz(50, .2); + this.scroll(distanceY > 0 ? 'u' : 'd'); distanceY = null; } } From 43727c1fdbeab688aaf020e1bdaa9cc75d6e5b48 Mon Sep 17 00:00:00 2001 From: Travis Evans Date: Sat, 2 Sep 2023 17:14:50 -0500 Subject: [PATCH 10/40] Implement fixed log size (new entries replace old) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Also fix saving log to Storage file named “undefined” instead of correct name --- apps/stamplog/app.js | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/apps/stamplog/app.js b/apps/stamplog/app.js index 4776e173f7..71269fb439 100644 --- a/apps/stamplog/app.js +++ b/apps/stamplog/app.js @@ -12,7 +12,8 @@ const DRAG_THRESHOLD = 30; const SCROLL_BAR_WIDTH = 12; var settings = { - logItemFont: '12x20' + logItemFont: '12x20', + maxLogLength: 6 }; @@ -68,9 +69,12 @@ function getIcon(id) { // UI for managing log entries and automatically loading/saving // changes to flash storage. class StampLog { - constructor(filename) { + constructor(filename, maxLength) { // Name of file to save log to this.filename = filename; + // Maximum entries for log before old entries are overwritten with + // newer ones + this.maxLength = maxLength; // `true` when we have changes that need to be saved this.isDirty = false; @@ -130,6 +134,13 @@ class StampLog { // Add a timestamp for the current time to the end of the log addEntry() { + // If log full, purge an old entry to make room for new one + if (this.maxLength) { + while (this.log.length + 1 > this.maxLength) { + this.log.shift(); + } + } + // Add new entry this.log.push({ stamp: new Date() }); @@ -408,7 +419,7 @@ function saveErrorAlert() { Bangle.loadWidgets(); Bangle.drawWidgets(); -stampLog = new StampLog(); +stampLog = new StampLog(LOG_FILENAME, settings.maxLogLength); E.on('kill', stampLog.save.bind(stampLog)); stampLog.on('saveError', saveErrorAlert); From a4a5e1eaf62d8571083584dd36ea5c3b15bddec5 Mon Sep 17 00:00:00 2001 From: Travis Evans Date: Sat, 2 Sep 2023 18:19:45 -0500 Subject: [PATCH 11/40] Fix scrollbar geometry Nitpick: scrollbar box was larger than the true percentage of screen pages displayed because we weren't taking into account that we only scroll by full screens at a time. --- apps/stamplog/app.js | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/apps/stamplog/app.js b/apps/stamplog/app.js index 71269fb439..7b5975f5fd 100644 --- a/apps/stamplog/app.js +++ b/apps/stamplog/app.js @@ -268,7 +268,7 @@ class MainScreen { }, {type: 'custom', id: 'logScroll', - render: elem => { renderScrollBar(elem, this.scrollInfo()); } + render: elem => { renderScrollBar(elem, this.scrollBarInfo()); } }, ], }, @@ -382,6 +382,25 @@ class MainScreen { }; } + // Like scrollInfo, but adjust the data so as to suggest scrollbar + // geometry that accurately reflects the nature of the scrolling + // (page by page rather than item by item) + scrollBarInfo() { + const info = this.scrollInfo(); + + function toPage(scrollPos) { + return Math.floor(scrollPos / info.itemsPerPage); + } + + return { + // Define 1 “screenfull” as the unit here + itemsPerPage: 1, + pos: toPage(info.pos), + min: toPage(info.min), + max: toPage(info.max), + }; + } + // Scroll display in given direction or to given position: // 'u': up, 'd': down, 't': to top, 'b': to bottom scroll(how) { From a741e86a4145e51f539f7f98bf51622ab6ae24d6 Mon Sep 17 00:00:00 2001 From: Travis Evans Date: Mon, 4 Sep 2023 22:39:06 -0500 Subject: [PATCH 12/40] Avoid UTF-8 for now since there are encoding problems with the IDE --- apps/stamplog/app.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/stamplog/app.js b/apps/stamplog/app.js index 7b5975f5fd..1d54d8e328 100644 --- a/apps/stamplog/app.js +++ b/apps/stamplog/app.js @@ -393,7 +393,7 @@ class MainScreen { } return { - // Define 1 “screenfull” as the unit here + // Define 1 "screenfull" as the unit here itemsPerPage: 1, pos: toPage(info.pos), min: toPage(info.min), From f4b3dd78d82eb7967f303bfb1e77cc6f9105b0a7 Mon Sep 17 00:00:00 2001 From: Travis Evans Date: Mon, 4 Sep 2023 23:11:32 -0500 Subject: [PATCH 13/40] Change some default configuration details --- apps/stamplog/app.js | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/apps/stamplog/app.js b/apps/stamplog/app.js index 1d54d8e328..344f183c81 100644 --- a/apps/stamplog/app.js +++ b/apps/stamplog/app.js @@ -3,7 +3,7 @@ locale = require('locale'); storage = require('Storage'); // Storage filename to store user's timestamp log -const LOG_FILENAME = 'stamplog.json'; +const LOG_FILENAME = 'timestamplog.json'; // Min number of pixels of movement to recognize a touchscreen drag/swipe const DRAG_THRESHOLD = 30; @@ -13,7 +13,7 @@ const SCROLL_BAR_WIDTH = 12; var settings = { logItemFont: '12x20', - maxLogLength: 6 + maxLogLength: 30 }; @@ -444,4 +444,3 @@ stampLog.on('saveError', saveErrorAlert); var currentUI = new MainScreen(stampLog); currentUI.start(); - From 4134e9a322587f15e80801a9b4b92918a73a235f Mon Sep 17 00:00:00 2001 From: Travis Evans Date: Sat, 9 Sep 2023 11:50:23 -0500 Subject: [PATCH 14/40] Implement basis for settings menu --- apps/stamplog/app.js | 47 ++++++++++++++++++++++++++++++++++++++------ 1 file changed, 41 insertions(+), 6 deletions(-) diff --git a/apps/stamplog/app.js b/apps/stamplog/app.js index 344f183c81..d6317c1cab 100644 --- a/apps/stamplog/app.js +++ b/apps/stamplog/app.js @@ -2,8 +2,9 @@ Layout = require('Layout'); locale = require('locale'); storage = require('Storage'); -// Storage filename to store user's timestamp log +// Storage filenames const LOG_FILENAME = 'timestamplog.json'; +const SETTINGS_FILENAME = 'timestamplog.settings.json'; // Min number of pixels of movement to recognize a touchscreen drag/swipe const DRAG_THRESHOLD = 30; @@ -11,10 +12,20 @@ const DRAG_THRESHOLD = 30; // Width of scroll indicators const SCROLL_BAR_WIDTH = 12; -var settings = { + +// Settings + +const SETTINGS = Object.assign({ logItemFont: '12x20', + logItemFontSize: 1, maxLogLength: 30 -}; +}, storage.readJSON(SETTINGS_FILENAME, true) || {}); + +function saveSettings() { + if (!storage.writeJSON(SETTINGS_FILENAME, SETTINGS)) { + E.showAlert('Trouble saving settings', "Can't save settings"); + } +} // Fetch a stringified image @@ -278,7 +289,7 @@ class MainScreen { {type: 'btn', font: '6x8:2', fillx: 1, label: '+ XX:XX', id: 'addBtn', cb: this.addTimestamp.bind(this)}, {type: 'btn', font: '6x8:2', label: getIcon('menu'), id: 'menuBtn', - cb: L => console.log(L)}, + cb: settingsMenu}, ], }, ], @@ -288,7 +299,7 @@ class MainScreen { // Calculate how many log items per page we have space to display layout.update(); let availableHeight = layout.placeholder.h; - g.setFont(settings.logItemFont); + g.setFont(SETTINGS.logItemFont); let logItemHeight = g.getFontHeight() * 2; this.itemsPerPage = Math.floor(availableHeight / logItemHeight); @@ -423,6 +434,30 @@ class MainScreen { } +function settingsMenu() { + function endMenu() { + saveSettings(); + currentUI.start(); + } + + currentUI.stop(); + E.showMenu({ + '': { + title: 'Timestamp Logger', + back: endMenu, + }, + 'Max log size': { + value: SETTINGS.maxLogLength, + min: 5, max: 100, step: 5, + onchange: v => { + SETTINGS.maxLogLength = v; + stampLog.maxLength = v; + } + }, + }); +} + + function saveErrorAlert() { currentUI.stop(); // Not `showAlert` because the icon plus message don't fit the @@ -438,7 +473,7 @@ function saveErrorAlert() { Bangle.loadWidgets(); Bangle.drawWidgets(); -stampLog = new StampLog(LOG_FILENAME, settings.maxLogLength); +stampLog = new StampLog(LOG_FILENAME, SETTINGS.maxLogLength); E.on('kill', stampLog.save.bind(stampLog)); stampLog.on('saveError', saveErrorAlert); From 5a5cb62ebc2db997a5ed13198af409fc2dbda643 Mon Sep 17 00:00:00 2001 From: Travis Evans Date: Sat, 9 Sep 2023 11:50:38 -0500 Subject: [PATCH 15/40] Make haptic scroll feedback a little stronger --- apps/stamplog/app.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/stamplog/app.js b/apps/stamplog/app.js index d6317c1cab..0663826c70 100644 --- a/apps/stamplog/app.js +++ b/apps/stamplog/app.js @@ -367,7 +367,7 @@ class MainScreen { } if (Math.abs(distanceY) > DRAG_THRESHOLD) { // Scroll threshold reached - Bangle.buzz(50, .2); + Bangle.buzz(50, .5); this.scroll(distanceY > 0 ? 'u' : 'd'); distanceY = null; } From 56d4dae3570a2d582c8380642205f74e4fada702 Mon Sep 17 00:00:00 2001 From: Travis Evans Date: Sat, 9 Sep 2023 21:08:01 -0500 Subject: [PATCH 16/40] Implement log font selection --- apps/stamplog/app.js | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/apps/stamplog/app.js b/apps/stamplog/app.js index 0663826c70..cd310f913f 100644 --- a/apps/stamplog/app.js +++ b/apps/stamplog/app.js @@ -16,8 +16,8 @@ const SCROLL_BAR_WIDTH = 12; // Settings const SETTINGS = Object.assign({ - logItemFont: '12x20', - logItemFontSize: 1, + logFont: '12x20', + logFontSize: 1, maxLogLength: 30 }, storage.readJSON(SETTINGS_FILENAME, true) || {}); @@ -173,7 +173,7 @@ function renderLogItem(elem) { if (elem.item) { g.setColor(g.theme.bg) .fillRect(elem.x, elem.y, elem.x + elem.w - 1, elem.y + elem.h - 1) - .setFont('12x20') + .setFont(SETTINGS.logFont) .setFontAlign(-1, -1) .setColor(g.theme.fg) .drawLine(elem.x, elem.y, elem.x + elem.w - 1, elem.y) @@ -299,7 +299,7 @@ class MainScreen { // Calculate how many log items per page we have space to display layout.update(); let availableHeight = layout.placeholder.h; - g.setFont(SETTINGS.logItemFont); + g.setFont(SETTINGS.logFont); let logItemHeight = g.getFontHeight() * 2; this.itemsPerPage = Math.floor(availableHeight / logItemHeight); @@ -439,6 +439,7 @@ function settingsMenu() { saveSettings(); currentUI.start(); } + const fonts = g.getFonts(); currentUI.stop(); E.showMenu({ @@ -446,6 +447,14 @@ function settingsMenu() { title: 'Timestamp Logger', back: endMenu, }, + 'Log font': { + value: fonts.indexOf(SETTINGS.logFont), + min: 0, max: fonts.length - 1, + format: v => fonts[v], + onchange: v => { + SETTINGS.logFont = fonts[v]; + }, + }, 'Max log size': { value: SETTINGS.maxLogLength, min: 5, max: 100, step: 5, From 51c20c9e49e3a8bcec50608a2fbf0e6f3abd1e06 Mon Sep 17 00:00:00 2001 From: Travis Evans Date: Sat, 9 Sep 2023 21:08:06 -0500 Subject: [PATCH 17/40] Fix log display cosmetics Clear app display region before restarting log UI so as to not leave old junk behind if new font leaves leftover space at top of screen Draw text starting one pixel below log separator lines so the line doesn't overstrike topmost pixels of text --- apps/stamplog/app.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/stamplog/app.js b/apps/stamplog/app.js index cd310f913f..38f8e1eb23 100644 --- a/apps/stamplog/app.js +++ b/apps/stamplog/app.js @@ -180,7 +180,7 @@ function renderLogItem(elem) { .drawString(locale.date(elem.item.stamp, 1) + '\n' + locale.time(elem.item.stamp).trim(), - elem.x, elem.y); + elem.x, elem.y + 1); } else { g.setColor(g.blendColor(g.theme.bg, g.theme.fg, 0.25)) .fillRect(elem.x, elem.y, elem.x + elem.w - 1, elem.y + elem.h - 1); @@ -242,6 +242,7 @@ class MainScreen { // Launch this UI and make it live start() { this._initLayout(); + this.layout.clear(); this.scroll('b'); this.render('buttons'); From 5a01ae0159c60c3b661d7bc14e1b3a7bf292b2b0 Mon Sep 17 00:00:00 2001 From: Travis Evans Date: Sat, 9 Sep 2023 21:51:08 -0500 Subject: [PATCH 18/40] Allow both horizontal and vertical font size selection Nifty! --- apps/stamplog/app.js | 42 ++++++++++++++++++++++++++++++++---------- 1 file changed, 32 insertions(+), 10 deletions(-) diff --git a/apps/stamplog/app.js b/apps/stamplog/app.js index 38f8e1eb23..cc4cfd7af4 100644 --- a/apps/stamplog/app.js +++ b/apps/stamplog/app.js @@ -17,7 +17,8 @@ const SCROLL_BAR_WIDTH = 12; const SETTINGS = Object.assign({ logFont: '12x20', - logFontSize: 1, + logFontHSize: 1, + logFontVSize: 1, maxLogLength: 30 }, storage.readJSON(SETTINGS_FILENAME, true) || {}); @@ -27,6 +28,10 @@ function saveSettings() { } } +function fontSpec(name, hsize, vsize) { + return name + ':' + hsize + 'x' + vsize; +} + // Fetch a stringified image function getIcon(id) { @@ -173,7 +178,8 @@ function renderLogItem(elem) { if (elem.item) { g.setColor(g.theme.bg) .fillRect(elem.x, elem.y, elem.x + elem.w - 1, elem.y + elem.h - 1) - .setFont(SETTINGS.logFont) + .setFont(fontSpec(SETTINGS.logFont, + SETTINGS.logFontHSize, SETTINGS.logFontVSize)) .setFontAlign(-1, -1) .setColor(g.theme.fg) .drawLine(elem.x, elem.y, elem.x + elem.w - 1, elem.y) @@ -300,9 +306,11 @@ class MainScreen { // Calculate how many log items per page we have space to display layout.update(); let availableHeight = layout.placeholder.h; - g.setFont(SETTINGS.logFont); + g.setFont(fontSpec(SETTINGS.logFont, + SETTINGS.logFontHSize, SETTINGS.logFontVSize)); let logItemHeight = g.getFontHeight() * 2; - this.itemsPerPage = Math.floor(availableHeight / logItemHeight); + this.itemsPerPage = Math.max(1, + Math.floor(availableHeight / logItemHeight)); // Populate log items in layout for (i = 0; i < this.itemsPerPage; i++) { @@ -448,6 +456,14 @@ function settingsMenu() { title: 'Timestamp Logger', back: endMenu, }, + 'Max log size': { + value: SETTINGS.maxLogLength, + min: 5, max: 100, step: 5, + onchange: v => { + SETTINGS.maxLogLength = v; + stampLog.maxLength = v; + } + }, 'Log font': { value: fonts.indexOf(SETTINGS.logFont), min: 0, max: fonts.length - 1, @@ -456,13 +472,19 @@ function settingsMenu() { SETTINGS.logFont = fonts[v]; }, }, - 'Max log size': { - value: SETTINGS.maxLogLength, - min: 5, max: 100, step: 5, + 'Log font H size': { + value: SETTINGS.logFontHSize, + min: 1, max: 50, onchange: v => { - SETTINGS.maxLogLength = v; - stampLog.maxLength = v; - } + SETTINGS.logFontHSize = v; + }, + }, + 'Log font V size': { + value: SETTINGS.logFontVSize, + min: 1, max: 50, + onchange: v => { + SETTINGS.logFontVSize = v; + }, }, }); } From fe8694c7095801eb8c00e93ffa35f2b1be662c9b Mon Sep 17 00:00:00 2001 From: Travis Evans Date: Mon, 11 Sep 2023 16:18:28 -0500 Subject: [PATCH 19/40] Remove redundant message title --- apps/stamplog/app.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/stamplog/app.js b/apps/stamplog/app.js index cc4cfd7af4..0d812eabea 100644 --- a/apps/stamplog/app.js +++ b/apps/stamplog/app.js @@ -24,7 +24,7 @@ const SETTINGS = Object.assign({ function saveSettings() { if (!storage.writeJSON(SETTINGS_FILENAME, SETTINGS)) { - E.showAlert('Trouble saving settings', "Can't save settings"); + E.showAlert('Trouble saving settings'); } } From 0127a9654b37b89b94b0470195f41951d6d708c9 Mon Sep 17 00:00:00 2001 From: Travis Evans Date: Thu, 2 Nov 2023 12:28:24 -0500 Subject: [PATCH 20/40] Implement log rotate option and UI support --- apps/stamplog/app.js | 39 ++++++++++++++++++++++++++++++++++----- 1 file changed, 34 insertions(+), 5 deletions(-) diff --git a/apps/stamplog/app.js b/apps/stamplog/app.js index 0d812eabea..b41c37c433 100644 --- a/apps/stamplog/app.js +++ b/apps/stamplog/app.js @@ -19,7 +19,8 @@ const SETTINGS = Object.assign({ logFont: '12x20', logFontHSize: 1, logFontVSize: 1, - maxLogLength: 30 + maxLogLength: 30, + rotateLog: false, }, storage.readJSON(SETTINGS_FILENAME, true) || {}); function saveSettings() { @@ -168,6 +169,11 @@ class StampLog { this.log = this.log.filter(entry => !entries.includes(entry)); this.setDirty(); } + + // Does the log currently contain the maximum possible number of entries? + isFull() { + return this.log.length >= this.maxLength; + } } @@ -339,7 +345,21 @@ class MainScreen { } if (!item || item == 'buttons') { - this.layout.addBtn.label = getIcon('add') + ' ' + locale.time(new Date(), 1).trim(); + let addBtn = this.layout.addBtn; + + if (!SETTINGS.rotateLog && this.stampLog.isFull()) { + // Dimmed appearance for unselectable button + addBtn.btnFaceCol = g.blendColor(g.theme.bg2, g.theme.bg, 0.5); + addBtn.btnBorderCol = g.blendColor(g.theme.fg2, g.theme.bg, 0.5); + + addBtn.label = 'Log full'; + } else { + addBtn.btnFaceCol = g.theme.bg2; + addBtn.btnBorderCol = g.theme.fg2; + + addBtn.label = getIcon('add') + ' ' + locale.time(new Date(), 1).trim(); + } + this.layout.render(this.layout.buttons); // Auto-update time of day indication on log-add button upon @@ -386,10 +406,13 @@ class MainScreen { Bangle.on('drag', this.listeners.drag); } - // Add current timestamp to log and update UI display + // Add current timestamp to log if possible and update UI display addTimestamp() { - this.stampLog.addEntry(); - this.scroll('b'); + if (SETTINGS.rotateLog || !this.stampLog.isFull()) { + this.stampLog.addEntry(); + this.scroll('b'); + this.render('buttons'); + } } // Get scroll information for log display @@ -464,6 +487,12 @@ function settingsMenu() { stampLog.maxLength = v; } }, + 'Rotate log entries': { + value: SETTINGS.rotateLog, + onchange: v => { + SETTINGS.rotateLog = !SETTINGS.rotateLog; + } + }, 'Log font': { value: fonts.indexOf(SETTINGS.logFont), min: 0, max: fonts.length - 1, From 2872ed87eccb057e36b06990812fff09aa45e130 Mon Sep 17 00:00:00 2001 From: Travis Evans Date: Thu, 2 Nov 2023 15:41:37 -0500 Subject: [PATCH 21/40] Break up settings menu into submenus Although there aren't very many items yet, I feel that minimizing scrolling on the Bangle.js 2 touchscreen makes menu navigation and use easier. --- apps/stamplog/app.js | 113 ++++++++++++++++++++++++++----------------- 1 file changed, 69 insertions(+), 44 deletions(-) diff --git a/apps/stamplog/app.js b/apps/stamplog/app.js index b41c37c433..b9184efafd 100644 --- a/apps/stamplog/app.js +++ b/apps/stamplog/app.js @@ -169,7 +169,7 @@ class StampLog { this.log = this.log.filter(entry => !entries.includes(entry)); this.setDirty(); } - + // Does the log currently contain the maximum possible number of entries? isFull() { return this.log.length >= this.maxLength; @@ -467,55 +467,80 @@ class MainScreen { function settingsMenu() { + const fonts = g.getFonts(); + + function topMenu() { + E.showMenu({ + '': { + title: 'Stamplog', + back: endMenu, + }, + 'Log': logMenu, + 'Appearance': appearanceMenu, + }); + } + + function logMenu() { + E.showMenu({ + '': { + title: 'Log', + back: topMenu, + }, + 'Max entries': { + value: SETTINGS.maxLogLength, + min: 5, max: 100, step: 5, + onchange: v => { + SETTINGS.maxLogLength = v; + stampLog.maxLength = v; + } + }, + 'Auto-delete oldest': { + value: SETTINGS.rotateLog, + onchange: v => { + SETTINGS.rotateLog = !SETTINGS.rotateLog; + } + }, + }); + } + + function appearanceMenu() { + E.showMenu({ + '': { + title: 'Appearance', + back: topMenu, + }, + 'Log font': { + value: fonts.indexOf(SETTINGS.logFont), + min: 0, max: fonts.length - 1, + format: v => fonts[v], + onchange: v => { + SETTINGS.logFont = fonts[v]; + }, + }, + 'Log font H size': { + value: SETTINGS.logFontHSize, + min: 1, max: 50, + onchange: v => { + SETTINGS.logFontHSize = v; + }, + }, + 'Log font V size': { + value: SETTINGS.logFontVSize, + min: 1, max: 50, + onchange: v => { + SETTINGS.logFontVSize = v; + }, + }, + }); + } + function endMenu() { saveSettings(); currentUI.start(); } - const fonts = g.getFonts(); currentUI.stop(); - E.showMenu({ - '': { - title: 'Timestamp Logger', - back: endMenu, - }, - 'Max log size': { - value: SETTINGS.maxLogLength, - min: 5, max: 100, step: 5, - onchange: v => { - SETTINGS.maxLogLength = v; - stampLog.maxLength = v; - } - }, - 'Rotate log entries': { - value: SETTINGS.rotateLog, - onchange: v => { - SETTINGS.rotateLog = !SETTINGS.rotateLog; - } - }, - 'Log font': { - value: fonts.indexOf(SETTINGS.logFont), - min: 0, max: fonts.length - 1, - format: v => fonts[v], - onchange: v => { - SETTINGS.logFont = fonts[v]; - }, - }, - 'Log font H size': { - value: SETTINGS.logFontHSize, - min: 1, max: 50, - onchange: v => { - SETTINGS.logFontHSize = v; - }, - }, - 'Log font V size': { - value: SETTINGS.logFontVSize, - min: 1, max: 50, - onchange: v => { - SETTINGS.logFontVSize = v; - }, - }, - }); + topMenu(); } From 30734047668adce7af677b273566bbde83eafbf0 Mon Sep 17 00:00:00 2001 From: Travis Evans Date: Fri, 10 May 2024 20:21:36 -0500 Subject: [PATCH 22/40] Rename app --- apps/{stamplog => timestamplog}/app.js | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename apps/{stamplog => timestamplog}/app.js (100%) diff --git a/apps/stamplog/app.js b/apps/timestamplog/app.js similarity index 100% rename from apps/stamplog/app.js rename to apps/timestamplog/app.js From 1dd88b29b29d3cf9036c9bb43f08adb02371ffb2 Mon Sep 17 00:00:00 2001 From: Travis Evans Date: Fri, 10 May 2024 20:26:13 -0500 Subject: [PATCH 23/40] Fix breakage after recent FW (Date object serializes differently) --- apps/timestamplog/app.js | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/apps/timestamplog/app.js b/apps/timestamplog/app.js index b9184efafd..521480776e 100644 --- a/apps/timestamplog/app.js +++ b/apps/timestamplog/app.js @@ -129,7 +129,15 @@ class StampLog { } if (this.isDirty) { - if (storage.writeJSON(this.filename, this.log)) { + let logToSave = []; + for (let logEntry of this.log) { + // Serialize each Date object into an ISO string before saving + let newEntry = Object.assign({}, logEntry); + newEntry.stamp = logEntry.stamp.toISOString(); + logToSave.push(newEntry); + } + + if (storage.writeJSON(this.filename, logToSave)) { console.log('stamplog: save to storage completed'); this.isDirty = false; } else { From ab045fbd2437eebadc640504471999e15e95d5a2 Mon Sep 17 00:00:00 2001 From: Travis Evans Date: Fri, 24 May 2024 18:04:13 -0500 Subject: [PATCH 24/40] Implement ability to delete log entries --- apps/timestamplog/app.js | 135 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 134 insertions(+), 1 deletion(-) diff --git a/apps/timestamplog/app.js b/apps/timestamplog/app.js index 521480776e..901ddc458d 100644 --- a/apps/timestamplog/app.js +++ b/apps/timestamplog/app.js @@ -278,6 +278,7 @@ class MainScreen { // Kill layout handlers Bangle.removeListener('drag', this.listeners.drag); + Bangle.removeListener('touch', this.listeners.touch); Bangle.setUI(); } @@ -329,7 +330,8 @@ class MainScreen { // Populate log items in layout for (i = 0; i < this.itemsPerPage; i++) { layout.logItems.c.push( - {type: 'custom', render: renderLogItem, item: undefined, fillx: 1, height: logItemHeight} + {type: 'custom', render: renderLogItem, item: undefined, itemIdx: undefined, + fillx: 1, height: logItemHeight} ); } layout.logScroll.height = logItemHeight * this.itemsPerPage; @@ -347,6 +349,7 @@ class MainScreen { for (let elem of layLogItems.c) { logIdx++; elem.item = this.stampLog.log[logIdx]; + elem.itemIdx = logIdx; } this.layout.render(layLogItems); this.layout.render(this.layout.logScroll); @@ -412,6 +415,23 @@ class MainScreen { this.listeners.drag = dragHandler.bind(this); Bangle.on('drag', this.listeners.drag); + + function touchHandler(button, xy) { + // Handle taps on log entries + let logUIItems = this.layout.logItems.c; + for (var logUIObj of logUIItems) { + if (!xy.type && + logUIObj.x <= xy.x && xy.x < logUIObj.x + logUIObj.w && + logUIObj.y <= xy.y && xy.y < logUIObj.y + logUIObj.h && + logUIObj.item) { + switchUI(new LogEntryScreen(this.stampLog, logUIObj.itemIdx)); + break; + } + } + } + + this.listeners.touch = touchHandler.bind(this); + Bangle.on('touch', this.listeners.touch); } // Add current timestamp to log if possible and update UI display @@ -474,6 +494,96 @@ class MainScreen { } +// Log entry screen interface, launched by calling start() +class LogEntryScreen { + + constructor(stampLog, logIdx) { + this.stampLog = stampLog; + this.logIdx = logIdx; + this.logItem = stampLog.log[logIdx]; + + this.defaultFont = fontSpec( + SETTINGS.logFont, SETTINGS.logFontHSize, SETTINGS.logFontVSize); + } + + start() { + this._initLayout(); + this.layout.clear(); + this.render(); + } + + stop() { + Bangle.setUI(); + } + + back() { + this.stop(); + switchUI(mainUI); + } + + _initLayout() { + let layout = new Layout( + {type: 'v', + c: [ + {type: 'txt', font: this.defaultFont, label: locale.date(this.logItem.stamp, 1)}, + {type: 'txt', font: this.defaultFont, label: locale.time(this.logItem.stamp).trim()}, + {type: '', id: 'placeholder', fillx: 1, filly: 1}, + {type: 'btn', font: '6x15', label: 'Delete', cb: this.delLogItem.bind(this), + cbl: this.delLogItem.bind(this)}, + ], + }, + { + back: this.back.bind(this), + btns: [ + {label: '<', cb: this.prevLogItem.bind(this)}, + {label: '>', cb: this.nextLogItem.bind(this)}, + ], + } + ); + + layout.update(); + this.layout = layout; + } + + render(item) { + this.layout.clear(); + this.layout.render(); + } + + refresh() { + this.logItem = this.stampLog.log[this.logIdx]; + this._initLayout(); + this.render(); + } + + prevLogItem() { + this.logIdx = this.logIdx ? this.logIdx-1 : this.stampLog.log.length-1; + this.refresh(); + } + + nextLogItem() { + this.logIdx = this.logIdx == this.stampLog.log.length-1 ? 0 : this.logIdx+1; + this.refresh(); + } + + delLogItem() { + this.stampLog.deleteEntries([this.logItem]); + if (!this.stampLog.log.length) { + this.back(); + return; + } else if (this.logIdx > this.stampLog.log.length - 1) { + this.logIdx = this.stampLog.log.length - 1; + } + + // Create a brief “blink” on the screen to provide user feedback + // that the deletion has been performed + this.layout.clear(); + setTimeout(this.refresh.bind(this), 250); + } + +} + + function settingsMenu() { const fonts = g.getFonts(); @@ -508,6 +618,7 @@ function settingsMenu() { SETTINGS.rotateLog = !SETTINGS.rotateLog; } }, + 'Clear log': clearLogPrompt, }); } @@ -547,6 +658,20 @@ function settingsMenu() { currentUI.start(); } + function clearLogPrompt() { + E.showPrompt('Erase ALL log entries?', { + title: 'Clear log', + buttons: {'Erase':1, "Don't":0} + }).then((yes) => { + if (yes) { + stampLog.deleteEntries(stampLog.log) + endMenu(); + } else { + logMenu(); + } + }); + } + currentUI.stop(); topMenu(); } @@ -564,6 +689,13 @@ function saveErrorAlert() { } +function switchUI(newUI) { + currentUI.stop(); + currentUI = newUI; + currentUI.start(); +} + + Bangle.loadWidgets(); Bangle.drawWidgets(); @@ -572,4 +704,5 @@ E.on('kill', stampLog.save.bind(stampLog)); stampLog.on('saveError', saveErrorAlert); var currentUI = new MainScreen(stampLog); +var mainUI = currentUI; currentUI.start(); From 872a7a51de675226c3482372895a0578e48aa857 Mon Sep 17 00:00:00 2001 From: Travis Evans Date: Sat, 25 May 2024 18:32:18 -0500 Subject: [PATCH 25/40] Add action setting for button presses --- apps/timestamplog/app.js | 44 ++++++++++++++++++++++++++++++++++++---- 1 file changed, 40 insertions(+), 4 deletions(-) diff --git a/apps/timestamplog/app.js b/apps/timestamplog/app.js index 901ddc458d..11d48ecd85 100644 --- a/apps/timestamplog/app.js +++ b/apps/timestamplog/app.js @@ -21,8 +21,16 @@ const SETTINGS = Object.assign({ logFontVSize: 1, maxLogLength: 30, rotateLog: false, + buttonAction: 'Log time', }, storage.readJSON(SETTINGS_FILENAME, true) || {}); +const SETTINGS_BUTTON_ACTION = [ + 'Log time', + 'Show menu', + 'Quit app', + 'Do nothing', +]; + function saveSettings() { if (!storage.writeJSON(SETTINGS_FILENAME, SETTINGS)) { E.showAlert('Trouble saving settings'); @@ -279,6 +287,7 @@ class MainScreen { // Kill layout handlers Bangle.removeListener('drag', this.listeners.drag); Bangle.removeListener('touch', this.listeners.touch); + clearWatch(this.listeners.btnWatch); Bangle.setUI(); } @@ -432,6 +441,23 @@ class MainScreen { this.listeners.touch = touchHandler.bind(this); Bangle.on('touch', this.listeners.touch); + + function buttonHandler() { + let act = SETTINGS.buttonAction; + if (act == 'Log time') { + if (currentUI != mainUI) { + switchUI(mainUI); + } + mainUI.addTimestamp(); + } else if (act == 'Show menu') { + settingsMenu(); + } else if (act == 'Quit app') { + Bangle.showClock(); + } + } + + this.listeners.btnWatch = setWatch(buttonHandler, BTN, + {edge: 'falling', debounce: 50, repeat: true}); } // Add current timestamp to log if possible and update UI display @@ -509,7 +535,7 @@ class LogEntryScreen { start() { this._initLayout(); this.layout.clear(); - this.render(); + this.refresh(); } stop() { @@ -525,8 +551,8 @@ class LogEntryScreen { let layout = new Layout( {type: 'v', c: [ - {type: 'txt', font: this.defaultFont, label: locale.date(this.logItem.stamp, 1)}, - {type: 'txt', font: this.defaultFont, label: locale.time(this.logItem.stamp).trim()}, + {type: 'txt', font: this.defaultFont, id: 'date', label: '?'}, + {type: 'txt', font: this.defaultFont, id: 'time', label: '?'}, {type: '', id: 'placeholder', fillx: 1, filly: 1}, {type: 'btn', font: '6x15', label: 'Delete', cb: this.delLogItem.bind(this), cbl: this.delLogItem.bind(this)}, @@ -552,7 +578,9 @@ class LogEntryScreen { refresh() { this.logItem = this.stampLog.log[this.logIdx]; - this._initLayout(); + this.layout.date.label = locale.date(this.logItem.stamp, 1); + this.layout.time.label = locale.time(this.logItem.stamp).trim(); + this.layout.update(); this.render(); } @@ -595,6 +623,14 @@ function settingsMenu() { }, 'Log': logMenu, 'Appearance': appearanceMenu, + 'Button': { + value: SETTINGS_BUTTON_ACTION.indexOf(SETTINGS.buttonAction), + min: 0, max: SETTINGS_BUTTON_ACTION.length - 1, + format: v => SETTINGS_BUTTON_ACTION[v], + onchange: v => { + SETTINGS.buttonAction = SETTINGS_BUTTON_ACTION[v]; + }, + }, }); } From a08d580a1eb74aecc27b908c71dfcdc792a2d503 Mon Sep 17 00:00:00 2001 From: Travis Evans Date: Sun, 26 May 2024 13:23:29 -0500 Subject: [PATCH 26/40] Rename function for clarity --- apps/timestamplog/app.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/timestamplog/app.js b/apps/timestamplog/app.js index 11d48ecd85..7505c6afb8 100644 --- a/apps/timestamplog/app.js +++ b/apps/timestamplog/app.js @@ -320,7 +320,7 @@ class MainScreen { {type: 'btn', font: '6x8:2', fillx: 1, label: '+ XX:XX', id: 'addBtn', cb: this.addTimestamp.bind(this)}, {type: 'btn', font: '6x8:2', label: getIcon('menu'), id: 'menuBtn', - cb: settingsMenu}, + cb: launchSettingsMenu}, ], }, ], @@ -450,7 +450,7 @@ class MainScreen { } mainUI.addTimestamp(); } else if (act == 'Show menu') { - settingsMenu(); + launchSettingsMenu(); } else if (act == 'Quit app') { Bangle.showClock(); } @@ -612,7 +612,7 @@ class LogEntryScreen { } -function settingsMenu() { +function launchSettingsMenu() { const fonts = g.getFonts(); function topMenu() { From 9e2c87cad84239b31d0faac1bb2e341f2884c00b Mon Sep 17 00:00:00 2001 From: Travis Evans Date: Sun, 26 May 2024 13:57:41 -0500 Subject: [PATCH 27/40] Remove short-press callback from log entry delete button That was just for testing; the emulator doesn't support long presses. --- apps/timestamplog/app.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/timestamplog/app.js b/apps/timestamplog/app.js index 7505c6afb8..9e4fa2273c 100644 --- a/apps/timestamplog/app.js +++ b/apps/timestamplog/app.js @@ -554,7 +554,7 @@ class LogEntryScreen { {type: 'txt', font: this.defaultFont, id: 'date', label: '?'}, {type: 'txt', font: this.defaultFont, id: 'time', label: '?'}, {type: '', id: 'placeholder', fillx: 1, filly: 1}, - {type: 'btn', font: '6x15', label: 'Delete', cb: this.delLogItem.bind(this), + {type: 'btn', font: '6x15', label: 'Hold to delete', cbl: this.delLogItem.bind(this)}, ], }, From e7477784e91626471930df76dbb772a187553ea2 Mon Sep 17 00:00:00 2001 From: Travis Evans Date: Mon, 27 May 2024 14:38:32 -0500 Subject: [PATCH 28/40] Add app metadata files --- apps/timestamplog/app-icon.js | 1 + apps/timestamplog/app.png | Bin 0 -> 851 bytes apps/timestamplog/changelog.txt | 1 + apps/timestamplog/metadata.json | 16 ++++++++++++++++ 4 files changed, 18 insertions(+) create mode 100644 apps/timestamplog/app-icon.js create mode 100644 apps/timestamplog/app.png create mode 100644 apps/timestamplog/changelog.txt create mode 100644 apps/timestamplog/metadata.json diff --git a/apps/timestamplog/app-icon.js b/apps/timestamplog/app-icon.js new file mode 100644 index 0000000000..b35f05e08b --- /dev/null +++ b/apps/timestamplog/app-icon.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("mEw4cB/4ACBQX48AFDAAUkyVJAQoDCBZACDymSoEAgVJkQRJkskGAuJCJEpBwYDCyIRHpVICI0SogRGqQyEAgdIBwIUEyAODAQkJiVJxBoDwARIgJuBiIUBCIKzKCIOCQYWkCJUkpNQoiMBkARKgmJxUIxMlOgIQIQwOJyNBiKeBCJeRyUokUoGZPYAQMRyVFgiwDAAsGCIlI0GIBYfAAgUB2wECCINJikRCIfbAgVt2DaDCIMiwR9DjdggEDtg5DgTECSQIJDtuAEwYRFSQOSBIUN2xWCgANBgVSAYKSBR4k2AgYRCxQDBSQTGKgVRCISSBoARKxUpCIKSFAA0SqNFCIKSFAA0RlUo0iSHCI0losUSRARFkmo0SSEwAPFeoORkmViiSEiARHxJXB0SSFAgQCByEAggRCqiSEilChEgwUIXgMkBgVKSQmiqFBgkQoMUoArESQdE6Y1FxIRESQco0WIEYkRiQRDSQWRmnTpojEwRhFSQOKEYOJEYkQogRESQNNEZEIPoQUCEYeKkIjEoLFBCIdTEYc0EYsiCIlKpQjCkojCNIYREpMpEYwCCEYoCB0gjBkmEEYImCgQRGyWTNYJECbQQjHJQIDBygjNpSHCEZ0QAYIjODoJHPEAgjDA==")) diff --git a/apps/timestamplog/app.png b/apps/timestamplog/app.png new file mode 100644 index 0000000000000000000000000000000000000000..ddb51fe40ba201d8b0a5fb66583b8c720e932898 GIT binary patch literal 851 zcmV-Z1FZasP)EX>4Tx04R}tkv&MmKpe$iQ%glE4rY+zkfAzRC@RuXt5Adrp;l}?mh0_0bHx5XjWeW&~)3( zrc*+`uquRK5keF^5=cslWz30U2EOC#9s#!A#aWjBxj)CCTC@}(AP~leu-ldB4a z91EyJgXH?b{@{1FR%vR|ONyj`(2L`Ii~-?Ypxtzw?_n$MpNqV!Z z#g2fXZQ$a%tI2!7A(Ki)<;agx}&FihRkJASrM_pxZfP+I| zyiD2aKJV`D?d{()o&J6R_g-?`vQ)&D0000FP)t-s00030|Nj600RR60{{R3IQ;Pcl z0004WQchCAP9xMY@lX<1YICzg5G8SnfgE}*!b@1 z51t=j#D?HRPvjnC?vEbvHUy5whXF+D?@up)yG)^UgsjtHwA)qjpRp8Q#Tjjl5Qrsmgl&!Rg)9*m z3xxVOyb-x5k5Jbb6GO^*iAbkZkyf&(oN3HwM{3nnO$T0(1`Q(rrkasj6*;dK3=x$< zc%*vbjVNJAt%mS%3?VB*)3e@O5k884o**a>CI4_47@zBE^OGhj3j#gESrQrlBufGt d*EO;!eF1)BW!G6Pv=smV002ovPDHLkV1lpxZW#ao literal 0 HcmV?d00001 diff --git a/apps/timestamplog/changelog.txt b/apps/timestamplog/changelog.txt new file mode 100644 index 0000000000..ec66c5568c --- /dev/null +++ b/apps/timestamplog/changelog.txt @@ -0,0 +1 @@ +0.01: Initial version diff --git a/apps/timestamplog/metadata.json b/apps/timestamplog/metadata.json new file mode 100644 index 0000000000..8114b159da --- /dev/null +++ b/apps/timestamplog/metadata.json @@ -0,0 +1,16 @@ +{ "id": "timestamplog", + "name": "Timestamp log", + "shortName":"Timestamp log", + "icon": "app.png", + "version": "0.01", + "description": "Conveniently record a series of date/time stamps", + "tags": "timestamp, log", + "supports": ["BANGLEJS2"], + "storage": [ + {"name": "timestamplog.app.js", "url": "app.js"}, + {"name": "timestamplog.img", "url": "app-icon.js", "evaluate":true} + ], + "data": [ + {"name": "timestamplog.settings", "url": "timestamplog.settings"}, + {"name": "timestamplog.json", "url": "timestamplog.json"} +} From 453a4a0697509b503e448aa743e3e81f8b96d1b2 Mon Sep 17 00:00:00 2001 From: Travis Evans Date: Mon, 27 May 2024 14:59:25 -0500 Subject: [PATCH 29/40] Fix sanity-check errors --- apps/timestamplog/changelog.txt | 1 - apps/timestamplog/metadata.json | 6 ++++-- 2 files changed, 4 insertions(+), 3 deletions(-) delete mode 100644 apps/timestamplog/changelog.txt diff --git a/apps/timestamplog/changelog.txt b/apps/timestamplog/changelog.txt deleted file mode 100644 index ec66c5568c..0000000000 --- a/apps/timestamplog/changelog.txt +++ /dev/null @@ -1 +0,0 @@ -0.01: Initial version diff --git a/apps/timestamplog/metadata.json b/apps/timestamplog/metadata.json index 8114b159da..98d57e4bc6 100644 --- a/apps/timestamplog/metadata.json +++ b/apps/timestamplog/metadata.json @@ -1,4 +1,5 @@ -{ "id": "timestamplog", +{ + "id": "timestamplog", "name": "Timestamp log", "shortName":"Timestamp log", "icon": "app.png", @@ -8,9 +9,10 @@ "supports": ["BANGLEJS2"], "storage": [ {"name": "timestamplog.app.js", "url": "app.js"}, - {"name": "timestamplog.img", "url": "app-icon.js", "evaluate":true} + {"name": "timestamplog.img", "url": "app-icon.js", "evaluate": true} ], "data": [ {"name": "timestamplog.settings", "url": "timestamplog.settings"}, {"name": "timestamplog.json", "url": "timestamplog.json"} + ] } From 73ac306b76a046197acb8d0c56183e93e2dd81e2 Mon Sep 17 00:00:00 2001 From: Travis Evans Date: Mon, 27 May 2024 19:48:47 -0500 Subject: [PATCH 30/40] Fix metadata.json error (should not try to download data files) --- apps/timestamplog/metadata.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/timestamplog/metadata.json b/apps/timestamplog/metadata.json index 98d57e4bc6..2963953c67 100644 --- a/apps/timestamplog/metadata.json +++ b/apps/timestamplog/metadata.json @@ -12,7 +12,7 @@ {"name": "timestamplog.img", "url": "app-icon.js", "evaluate": true} ], "data": [ - {"name": "timestamplog.settings", "url": "timestamplog.settings"}, - {"name": "timestamplog.json", "url": "timestamplog.json"} + {"name": "timestamplog.settings"}, + {"name": "timestamplog.json"} ] } From df84f229e4fcfaa929962b70e95327a23500d963 Mon Sep 17 00:00:00 2001 From: Travis Evans Date: Mon, 27 May 2024 19:49:08 -0500 Subject: [PATCH 31/40] Give up on push-to-delete; Layout's cbl callback just doesn't work --- apps/timestamplog/app.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/timestamplog/app.js b/apps/timestamplog/app.js index 9e4fa2273c..87f227f46c 100644 --- a/apps/timestamplog/app.js +++ b/apps/timestamplog/app.js @@ -554,8 +554,8 @@ class LogEntryScreen { {type: 'txt', font: this.defaultFont, id: 'date', label: '?'}, {type: 'txt', font: this.defaultFont, id: 'time', label: '?'}, {type: '', id: 'placeholder', fillx: 1, filly: 1}, - {type: 'btn', font: '6x15', label: 'Hold to delete', - cbl: this.delLogItem.bind(this)}, + {type: 'btn', font: '12x20', label: 'Delete', + cb: this.delLogItem.bind(this)}, ], }, { From e542bed0a094edc28d43078a5096e71f5340dc20 Mon Sep 17 00:00:00 2001 From: Travis Evans Date: Wed, 19 Jun 2024 15:29:57 -0500 Subject: [PATCH 32/40] Implement log viewing in app loader --- apps/timestamplog/interface.html | 31 +++++++++++++++++++++++++++++++ apps/timestamplog/metadata.json | 1 + 2 files changed, 32 insertions(+) create mode 100644 apps/timestamplog/interface.html diff --git a/apps/timestamplog/interface.html b/apps/timestamplog/interface.html new file mode 100644 index 0000000000..6febe78498 --- /dev/null +++ b/apps/timestamplog/interface.html @@ -0,0 +1,31 @@ + + + + + + +
Loading...
+ + + diff --git a/apps/timestamplog/metadata.json b/apps/timestamplog/metadata.json index 2963953c67..858fab2375 100644 --- a/apps/timestamplog/metadata.json +++ b/apps/timestamplog/metadata.json @@ -7,6 +7,7 @@ "description": "Conveniently record a series of date/time stamps", "tags": "timestamp, log", "supports": ["BANGLEJS2"], + "interface": "interface.html", "storage": [ {"name": "timestamplog.app.js", "url": "app.js"}, {"name": "timestamplog.img", "url": "app-icon.js", "evaluate": true} From 27af11a9ca6cd3fdd408d69e817e11e99996fb23 Mon Sep 17 00:00:00 2001 From: Travis Evans Date: Mon, 8 Jul 2024 18:03:12 -0500 Subject: [PATCH 33/40] Refactor entire app so that it can have a settings.js --- apps/timestamplog/ChangeLog | 1 + apps/timestamplog/app.js | 319 +++++--------------------------- apps/timestamplog/lib.js | 245 ++++++++++++++++++++++++ apps/timestamplog/metadata.json | 3 +- apps/timestamplog/settings.js | 7 + 5 files changed, 304 insertions(+), 271 deletions(-) create mode 100644 apps/timestamplog/ChangeLog create mode 100644 apps/timestamplog/lib.js create mode 100644 apps/timestamplog/settings.js diff --git a/apps/timestamplog/ChangeLog b/apps/timestamplog/ChangeLog new file mode 100644 index 0000000000..ec66c5568c --- /dev/null +++ b/apps/timestamplog/ChangeLog @@ -0,0 +1 @@ +0.01: Initial version diff --git a/apps/timestamplog/app.js b/apps/timestamplog/app.js index 87f227f46c..b4e5a0ee4b 100644 --- a/apps/timestamplog/app.js +++ b/apps/timestamplog/app.js @@ -1,10 +1,9 @@ -Layout = require('Layout'); -locale = require('locale'); -storage = require('Storage'); +const Layout = require('Layout'); +const locale = require('locale'); +const storage = require('Storage'); + +const tsl = require('timestamplog'); -// Storage filenames -const LOG_FILENAME = 'timestamplog.json'; -const SETTINGS_FILENAME = 'timestamplog.settings.json'; // Min number of pixels of movement to recognize a touchscreen drag/swipe const DRAG_THRESHOLD = 30; @@ -13,35 +12,6 @@ const DRAG_THRESHOLD = 30; const SCROLL_BAR_WIDTH = 12; -// Settings - -const SETTINGS = Object.assign({ - logFont: '12x20', - logFontHSize: 1, - logFontVSize: 1, - maxLogLength: 30, - rotateLog: false, - buttonAction: 'Log time', -}, storage.readJSON(SETTINGS_FILENAME, true) || {}); - -const SETTINGS_BUTTON_ACTION = [ - 'Log time', - 'Show menu', - 'Quit app', - 'Do nothing', -]; - -function saveSettings() { - if (!storage.writeJSON(SETTINGS_FILENAME, SETTINGS)) { - E.showAlert('Trouble saving settings'); - } -} - -function fontSpec(name, hsize, vsize) { - return name + ':' + hsize + 'x' + vsize; -} - - // Fetch a stringified image function getIcon(id) { if (id == 'add') { @@ -88,120 +58,13 @@ function getIcon(id) { } -//// Data models ////////////////////////////////// - -// High-level timestamp log object that provides an interface to the -// UI for managing log entries and automatically loading/saving -// changes to flash storage. -class StampLog { - constructor(filename, maxLength) { - // Name of file to save log to - this.filename = filename; - // Maximum entries for log before old entries are overwritten with - // newer ones - this.maxLength = maxLength; - - // `true` when we have changes that need to be saved - this.isDirty = false; - // Wait at most this many msec upon first data change before - // saving (this is to avoid excessive writes to flash if several - // changes happen quickly; we wait a little bit so they can be - // rolled into a single write) - this.saveTimeout = 30000; - // setTimeout ID for scheduled save job - this.saveId = null; - // Underlying raw log data object. Outside this class it's - // recommended to use only the class methods to change it rather - // than modifying the object directly to ensure that changes are - // recognized and saved to storage. - this.log = this.load(); - } - - // Return the version of the log data that is currently in storage - load() { - let log = storage.readJSON(this.filename, true); - if (!log) log = []; - // Convert stringified datetimes back into Date objects - for (let logEntry of log) { - logEntry.stamp = new Date(logEntry.stamp); - } - return log; - } - - // Write current log data to storage if anything needs to be saved - save() { - // Cancel any pending scheduled calls to save() - if (this.saveId) { - clearTimeout(this.saveId); - this.saveId = null; - } - - if (this.isDirty) { - let logToSave = []; - for (let logEntry of this.log) { - // Serialize each Date object into an ISO string before saving - let newEntry = Object.assign({}, logEntry); - newEntry.stamp = logEntry.stamp.toISOString(); - logToSave.push(newEntry); - } - - if (storage.writeJSON(this.filename, logToSave)) { - console.log('stamplog: save to storage completed'); - this.isDirty = false; - } else { - console.log('stamplog: save to storage FAILED'); - this.emit('saveError'); - } - } else { - console.log('stamplog: skipping save to storage because no changes made'); - } - } - - // Mark log as needing to be (re)written to storage - setDirty() { - this.isDirty = true; - if (!this.saveId) { - this.saveId = setTimeout(this.save.bind(this), this.saveTimeout); - } - } - - // Add a timestamp for the current time to the end of the log - addEntry() { - // If log full, purge an old entry to make room for new one - if (this.maxLength) { - while (this.log.length + 1 > this.maxLength) { - this.log.shift(); - } - } - // Add new entry - this.log.push({ - stamp: new Date() - }); - this.setDirty(); - } - - // Delete the log objects given in the array `entries` from the log - deleteEntries(entries) { - this.log = this.log.filter(entry => !entries.includes(entry)); - this.setDirty(); - } - - // Does the log currently contain the maximum possible number of entries? - isFull() { - return this.log.length >= this.maxLength; - } -} - - -//// UI /////////////////////////////////////////// - // UI layout render callback for log entries function renderLogItem(elem) { if (elem.item) { g.setColor(g.theme.bg) .fillRect(elem.x, elem.y, elem.x + elem.w - 1, elem.y + elem.h - 1) - .setFont(fontSpec(SETTINGS.logFont, - SETTINGS.logFontHSize, SETTINGS.logFontVSize)) + .setFont(tsl.fontSpec(tsl.SETTINGS.logFont, + tsl.SETTINGS.logFontHSize, tsl.SETTINGS.logFontVSize)) .setFontAlign(-1, -1) .setColor(g.theme.fg) .drawLine(elem.x, elem.y, elem.x + elem.w - 1, elem.y) @@ -254,9 +117,7 @@ function renderScrollBar(elem, scroll) { // Main app screen interface, launched by calling start() class MainScreen { - constructor(stampLog) { - this.stampLog = stampLog; - + constructor() { // Values set up by start() this.itemsPerPage = null; this.scrollPos = null; @@ -320,7 +181,7 @@ class MainScreen { {type: 'btn', font: '6x8:2', fillx: 1, label: '+ XX:XX', id: 'addBtn', cb: this.addTimestamp.bind(this)}, {type: 'btn', font: '6x8:2', label: getIcon('menu'), id: 'menuBtn', - cb: launchSettingsMenu}, + cb: () => launchSettingsMenu(returnFromSettings)}, ], }, ], @@ -330,8 +191,8 @@ class MainScreen { // Calculate how many log items per page we have space to display layout.update(); let availableHeight = layout.placeholder.h; - g.setFont(fontSpec(SETTINGS.logFont, - SETTINGS.logFontHSize, SETTINGS.logFontVSize)); + g.setFont(tsl.fontSpec(tsl.SETTINGS.logFont, + tsl.SETTINGS.logFontHSize, tsl.SETTINGS.logFontVSize)); let logItemHeight = g.getFontHeight() * 2; this.itemsPerPage = Math.max(1, Math.floor(availableHeight / logItemHeight)); @@ -357,7 +218,7 @@ class MainScreen { let logIdx = this.scrollPos - this.itemsPerPage; for (let elem of layLogItems.c) { logIdx++; - elem.item = this.stampLog.log[logIdx]; + elem.item = stampLog.log[logIdx]; elem.itemIdx = logIdx; } this.layout.render(layLogItems); @@ -367,7 +228,7 @@ class MainScreen { if (!item || item == 'buttons') { let addBtn = this.layout.addBtn; - if (!SETTINGS.rotateLog && this.stampLog.isFull()) { + if (!tsl.SETTINGS.rotateLog && stampLog.isFull()) { // Dimmed appearance for unselectable button addBtn.btnFaceCol = g.blendColor(g.theme.bg2, g.theme.bg, 0.5); addBtn.btnBorderCol = g.blendColor(g.theme.fg2, g.theme.bg, 0.5); @@ -433,7 +294,7 @@ class MainScreen { logUIObj.x <= xy.x && xy.x < logUIObj.x + logUIObj.w && logUIObj.y <= xy.y && xy.y < logUIObj.y + logUIObj.h && logUIObj.item) { - switchUI(new LogEntryScreen(this.stampLog, logUIObj.itemIdx)); + switchUI(new LogEntryScreen(stampLog, logUIObj.itemIdx)); break; } } @@ -443,14 +304,14 @@ class MainScreen { Bangle.on('touch', this.listeners.touch); function buttonHandler() { - let act = SETTINGS.buttonAction; + let act = tsl.SETTINGS.buttonAction; if (act == 'Log time') { if (currentUI != mainUI) { switchUI(mainUI); } mainUI.addTimestamp(); - } else if (act == 'Show menu') { - launchSettingsMenu(); + } else if (act == 'Open settings') { + launchSettingsMenu(returnFromSettings); } else if (act == 'Quit app') { Bangle.showClock(); } @@ -462,8 +323,8 @@ class MainScreen { // Add current timestamp to log if possible and update UI display addTimestamp() { - if (SETTINGS.rotateLog || !this.stampLog.isFull()) { - this.stampLog.addEntry(); + if (tsl.SETTINGS.rotateLog || !stampLog.isFull()) { + stampLog.addEntry(); this.scroll('b'); this.render('buttons'); } @@ -473,8 +334,8 @@ class MainScreen { scrollInfo() { return { pos: this.scrollPos, - min: (this.stampLog.log.length - 1) % this.itemsPerPage, - max: this.stampLog.log.length - 1, + min: (stampLog.log.length - 1) % this.itemsPerPage, + max: stampLog.log.length - 1, itemsPerPage: this.itemsPerPage }; } @@ -524,12 +385,12 @@ class MainScreen { class LogEntryScreen { constructor(stampLog, logIdx) { - this.stampLog = stampLog; + stampLog = stampLog; this.logIdx = logIdx; this.logItem = stampLog.log[logIdx]; - this.defaultFont = fontSpec( - SETTINGS.logFont, SETTINGS.logFontHSize, SETTINGS.logFontVSize); + this.defaultFont = tsl.fontSpec( + tsl.SETTINGS.logFont, tsl.SETTINGS.logFontHSize, tsl.SETTINGS.logFontVSize); } start() { @@ -577,7 +438,7 @@ class LogEntryScreen { } refresh() { - this.logItem = this.stampLog.log[this.logIdx]; + this.logItem = stampLog.log[this.logIdx]; this.layout.date.label = locale.date(this.logItem.stamp, 1); this.layout.time.label = locale.time(this.logItem.stamp).trim(); this.layout.update(); @@ -585,22 +446,22 @@ class LogEntryScreen { } prevLogItem() { - this.logIdx = this.logIdx ? this.logIdx-1 : this.stampLog.log.length-1; + this.logIdx = this.logIdx ? this.logIdx-1 : stampLog.log.length-1; this.refresh(); } nextLogItem() { - this.logIdx = this.logIdx == this.stampLog.log.length-1 ? 0 : this.logIdx+1; + this.logIdx = this.logIdx == stampLog.log.length-1 ? 0 : this.logIdx+1; this.refresh(); } delLogItem() { - this.stampLog.deleteEntries([this.logItem]); - if (!this.stampLog.log.length) { + stampLog.deleteEntries([this.logItem]); + if (!stampLog.log.length) { this.back(); return; - } else if (this.logIdx > this.stampLog.log.length - 1) { - this.logIdx = this.stampLog.log.length - 1; + } else if (this.logIdx > stampLog.log.length - 1) { + this.logIdx = stampLog.log.length - 1; } // Create a brief “blink” on the screen to provide user feedback @@ -612,104 +473,10 @@ class LogEntryScreen { } -function launchSettingsMenu() { - const fonts = g.getFonts(); - - function topMenu() { - E.showMenu({ - '': { - title: 'Stamplog', - back: endMenu, - }, - 'Log': logMenu, - 'Appearance': appearanceMenu, - 'Button': { - value: SETTINGS_BUTTON_ACTION.indexOf(SETTINGS.buttonAction), - min: 0, max: SETTINGS_BUTTON_ACTION.length - 1, - format: v => SETTINGS_BUTTON_ACTION[v], - onchange: v => { - SETTINGS.buttonAction = SETTINGS_BUTTON_ACTION[v]; - }, - }, - }); - } - - function logMenu() { - E.showMenu({ - '': { - title: 'Log', - back: topMenu, - }, - 'Max entries': { - value: SETTINGS.maxLogLength, - min: 5, max: 100, step: 5, - onchange: v => { - SETTINGS.maxLogLength = v; - stampLog.maxLength = v; - } - }, - 'Auto-delete oldest': { - value: SETTINGS.rotateLog, - onchange: v => { - SETTINGS.rotateLog = !SETTINGS.rotateLog; - } - }, - 'Clear log': clearLogPrompt, - }); - } - - function appearanceMenu() { - E.showMenu({ - '': { - title: 'Appearance', - back: topMenu, - }, - 'Log font': { - value: fonts.indexOf(SETTINGS.logFont), - min: 0, max: fonts.length - 1, - format: v => fonts[v], - onchange: v => { - SETTINGS.logFont = fonts[v]; - }, - }, - 'Log font H size': { - value: SETTINGS.logFontHSize, - min: 1, max: 50, - onchange: v => { - SETTINGS.logFontHSize = v; - }, - }, - 'Log font V size': { - value: SETTINGS.logFontVSize, - min: 1, max: 50, - onchange: v => { - SETTINGS.logFontVSize = v; - }, - }, - }); - } - - function endMenu() { - saveSettings(); - currentUI.start(); - } - - function clearLogPrompt() { - E.showPrompt('Erase ALL log entries?', { - title: 'Clear log', - buttons: {'Erase':1, "Don't":0} - }).then((yes) => { - if (yes) { - stampLog.deleteEntries(stampLog.log) - endMenu(); - } else { - logMenu(); - } - }); - } - +function switchUI(newUI) { currentUI.stop(); - topMenu(); + currentUI = newUI; + currentUI.start(); } @@ -725,17 +492,29 @@ function saveErrorAlert() { } -function switchUI(newUI) { +function launchSettingsMenu(backCb) { currentUI.stop(); - currentUI = newUI; + stampLog.save(); + tsl.launchSettingsMenu(backCb); +} + +function returnFromSettings() { + // Reload stampLog to pick up any changes made from settings UI + stampLog = loadStampLog(); currentUI.start(); } +function loadStampLog() { + // Create a StampLog object with its data loaded from storage + return new tsl.StampLog(tsl.LOG_FILENAME, tsl.SETTINGS.maxLogLength); +} + + Bangle.loadWidgets(); Bangle.drawWidgets(); -stampLog = new StampLog(LOG_FILENAME, SETTINGS.maxLogLength); +var stampLog = loadStampLog(); E.on('kill', stampLog.save.bind(stampLog)); stampLog.on('saveError', saveErrorAlert); diff --git a/apps/timestamplog/lib.js b/apps/timestamplog/lib.js new file mode 100644 index 0000000000..13cb291efc --- /dev/null +++ b/apps/timestamplog/lib.js @@ -0,0 +1,245 @@ +const storage = require('Storage'); + +// Storage filenames + +const LOG_FILENAME = 'timestamplog.json'; +const SETTINGS_FILENAME = 'timestamplog.settings.json'; + + +// Settings + +const SETTINGS = Object.assign({ + logFont: '12x20', + logFontHSize: 1, + logFontVSize: 1, + maxLogLength: 30, + rotateLog: false, + buttonAction: 'Log time', +}, storage.readJSON(SETTINGS_FILENAME, true) || {}); + +const SETTINGS_BUTTON_ACTION = [ + 'Log time', + 'Open settings', + 'Quit app', + 'Do nothing', +]; + + +function fontSpec(name, hsize, vsize) { + return name + ':' + hsize + 'x' + vsize; +} + + +//// Data models //// + +// High-level timestamp log object that provides an interface to the +// UI for managing log entries and automatically loading/saving +// changes to flash storage. +class StampLog { + constructor(filename, maxLength) { + // Name of file to save log to + this.filename = filename; + // Maximum entries for log before old entries are overwritten with + // newer ones + this.maxLength = maxLength; + + // `true` when we have changes that need to be saved + this.isDirty = false; + // Wait at most this many msec upon first data change before + // saving (this is to avoid excessive writes to flash if several + // changes happen quickly; we wait a little bit so they can be + // rolled into a single write) + this.saveTimeout = 30000; + // setTimeout ID for scheduled save job + this.saveId = null; + // Underlying raw log data object. Outside this class it's + // recommended to use only the class methods to change it rather + // than modifying the object directly to ensure that changes are + // recognized and saved to storage. + this.log = []; + + this.load(); + } + + // Read in the log data that is currently in storage + load() { + let log = storage.readJSON(this.filename, true); + if (!log) log = []; + // Convert stringified datetimes back into Date objects + for (let logEntry of log) { + logEntry.stamp = new Date(logEntry.stamp); + } + this.log = log; + } + + // Write current log data to storage if anything needs to be saved + save() { + // Cancel any pending scheduled calls to save() + if (this.saveId) { + clearTimeout(this.saveId); + this.saveId = null; + } + + if (this.isDirty) { + let logToSave = []; + for (let logEntry of this.log) { + // Serialize each Date object into an ISO string before saving + let newEntry = Object.assign({}, logEntry); + newEntry.stamp = logEntry.stamp.toISOString(); + logToSave.push(newEntry); + } + + if (storage.writeJSON(this.filename, logToSave)) { + console.log('stamplog: save to storage completed'); + this.isDirty = false; + } else { + console.log('stamplog: save to storage FAILED'); + this.emit('saveError'); + } + } else { + console.log('stamplog: skipping save to storage because no changes made'); + } + } + + // Mark log as needing to be (re)written to storage + setDirty() { + this.isDirty = true; + if (!this.saveId) { + this.saveId = setTimeout(this.save.bind(this), this.saveTimeout); + } + } + + // Add a timestamp for the current time to the end of the log + addEntry() { + // If log full, purge an old entry to make room for new one + if (this.maxLength) { + while (this.log.length + 1 > this.maxLength) { + this.log.shift(); + } + } + // Add new entry + this.log.push({ + stamp: new Date() + }); + this.setDirty(); + } + + // Delete the log objects given in the array `entries` from the log + deleteEntries(entries) { + this.log = this.log.filter(entry => !entries.includes(entry)); + this.setDirty(); + } + + // Does the log currently contain the maximum possible number of entries? + isFull() { + return this.log.length >= this.maxLength; + } +} + +function launchSettingsMenu(backCb) { + const fonts = g.getFonts(); + const stampLog = new StampLog(LOG_FILENAME, SETTINGS.maxLogLength); + + function saveSettings() { + stampLog.save(); + if (!storage.writeJSON(SETTINGS_FILENAME, SETTINGS)) { + E.showAlert('Trouble saving settings'); + } + } + + function endMenu() { + saveSettings(); + backCb(); + } + + function topMenu() { + E.showMenu({ + '': { + title: 'Stamplog', + back: endMenu, + }, + 'Log': logMenu, + 'Appearance': appearanceMenu, + 'Button': { + value: SETTINGS_BUTTON_ACTION.indexOf(SETTINGS.buttonAction), + min: 0, max: SETTINGS_BUTTON_ACTION.length - 1, + format: v => SETTINGS_BUTTON_ACTION[v], + onchange: v => { + SETTINGS.buttonAction = SETTINGS_BUTTON_ACTION[v]; + }, + }, + }); + } + + function logMenu() { + E.showMenu({ + '': { + title: 'Log', + back: topMenu, + }, + 'Max entries': { + value: SETTINGS.maxLogLength, + min: 5, max: 100, step: 5, + onchange: v => { + SETTINGS.maxLogLength = v; + stampLog.maxLength = v; + } + }, + 'Auto-delete oldest': { + value: SETTINGS.rotateLog, + onchange: v => { + SETTINGS.rotateLog = !SETTINGS.rotateLog; + } + }, + 'Clear log': doClearLog, + }); + } + + function appearanceMenu() { + E.showMenu({ + '': { + title: 'Appearance', + back: topMenu, + }, + 'Log font': { + value: fonts.indexOf(SETTINGS.logFont), + min: 0, max: fonts.length - 1, + format: v => fonts[v], + onchange: v => { + SETTINGS.logFont = fonts[v]; + }, + }, + 'Log font H size': { + value: SETTINGS.logFontHSize, + min: 1, max: 50, + onchange: v => { + SETTINGS.logFontHSize = v; + }, + }, + 'Log font V size': { + value: SETTINGS.logFontVSize, + min: 1, max: 50, + onchange: v => { + SETTINGS.logFontVSize = v; + }, + }, + }); + } + + function doClearLog() { + E.showPrompt('Erase ALL log entries?', { + title: 'Clear log', + buttons: {'Erase':1, "Don't":0} + }).then((yes) => { + if (yes) { + stampLog.deleteEntries(stampLog.log); + } + logMenu(); + }); + } + + topMenu(); +} + +exports = {LOG_FILENAME, SETTINGS_FILENAME, SETTINGS, SETTINGS_BUTTON_ACTION, fontSpec, StampLog, + launchSettingsMenu}; diff --git a/apps/timestamplog/metadata.json b/apps/timestamplog/metadata.json index 858fab2375..d5df5c54b6 100644 --- a/apps/timestamplog/metadata.json +++ b/apps/timestamplog/metadata.json @@ -10,7 +10,8 @@ "interface": "interface.html", "storage": [ {"name": "timestamplog.app.js", "url": "app.js"}, - {"name": "timestamplog.img", "url": "app-icon.js", "evaluate": true} + {"name": "timestamplog.img", "url": "app-icon.js", "evaluate": true}, + {"name": "lib.js", "url": "lib.js"} ], "data": [ {"name": "timestamplog.settings"}, diff --git a/apps/timestamplog/settings.js b/apps/timestamplog/settings.js new file mode 100644 index 0000000000..137ed31dbe --- /dev/null +++ b/apps/timestamplog/settings.js @@ -0,0 +1,7 @@ +const tsl = require('timestamplog'); + +( + function(backCb) { + tsl.launchSettingsMenu(backCb); + } +); From 7cfdf1b58ebf3ed46f41f7d5ce4bda71ad281467 Mon Sep 17 00:00:00 2001 From: Travis Evans Date: Tue, 9 Jul 2024 13:23:34 -0500 Subject: [PATCH 34/40] Add log position indicator to log entry screen --- apps/timestamplog/app.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/apps/timestamplog/app.js b/apps/timestamplog/app.js index b4e5a0ee4b..35845c5e0a 100644 --- a/apps/timestamplog/app.js +++ b/apps/timestamplog/app.js @@ -412,6 +412,7 @@ class LogEntryScreen { let layout = new Layout( {type: 'v', c: [ + {type: 'txt', font: this.defaultFont, id: 'entryno', label: 'Entry ?/?'}, {type: 'txt', font: this.defaultFont, id: 'date', label: '?'}, {type: 'txt', font: this.defaultFont, id: 'time', label: '?'}, {type: '', id: 'placeholder', fillx: 1, filly: 1}, @@ -439,6 +440,7 @@ class LogEntryScreen { refresh() { this.logItem = stampLog.log[this.logIdx]; + this.layout.entryno.label = 'Entry ' + (this.logIdx+1) + '/' + stampLog.log.length; this.layout.date.label = locale.date(this.logItem.stamp, 1); this.layout.time.label = locale.time(this.logItem.stamp).trim(); this.layout.update(); From 58e710d2da5c50c2c83ecf0f7417e69a2833767c Mon Sep 17 00:00:00 2001 From: Travis Evans Date: Tue, 9 Jul 2024 16:58:53 -0500 Subject: [PATCH 35/40] Fix the metadata file again --- apps/timestamplog/metadata.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/timestamplog/metadata.json b/apps/timestamplog/metadata.json index d5df5c54b6..e9b23e0fde 100644 --- a/apps/timestamplog/metadata.json +++ b/apps/timestamplog/metadata.json @@ -11,7 +11,8 @@ "storage": [ {"name": "timestamplog.app.js", "url": "app.js"}, {"name": "timestamplog.img", "url": "app-icon.js", "evaluate": true}, - {"name": "lib.js", "url": "lib.js"} + {"name": "timestamplog", "url": "lib.js"}, + {"name": "timestamplog.settings.js", "url": "settings.js"} ], "data": [ {"name": "timestamplog.settings"}, From 2ee7ba26ed261df049d1d1385ff25fbbe8fe9c79 Mon Sep 17 00:00:00 2001 From: Travis Evans Date: Tue, 9 Jul 2024 17:09:59 -0500 Subject: [PATCH 36/40] Cleanup --- apps/timestamplog/app.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/apps/timestamplog/app.js b/apps/timestamplog/app.js index 35845c5e0a..c44ab719bc 100644 --- a/apps/timestamplog/app.js +++ b/apps/timestamplog/app.js @@ -1,6 +1,5 @@ const Layout = require('Layout'); const locale = require('locale'); -const storage = require('Storage'); const tsl = require('timestamplog'); @@ -198,7 +197,7 @@ class MainScreen { Math.floor(availableHeight / logItemHeight)); // Populate log items in layout - for (i = 0; i < this.itemsPerPage; i++) { + for (let i = 0; i < this.itemsPerPage; i++) { layout.logItems.c.push( {type: 'custom', render: renderLogItem, item: undefined, itemIdx: undefined, fillx: 1, height: logItemHeight} @@ -385,7 +384,6 @@ class MainScreen { class LogEntryScreen { constructor(stampLog, logIdx) { - stampLog = stampLog; this.logIdx = logIdx; this.logItem = stampLog.log[logIdx]; From 60662811803843b4d86109e857a99a7d53403bab Mon Sep 17 00:00:00 2001 From: Travis Evans Date: Thu, 11 Jul 2024 19:30:51 -0500 Subject: [PATCH 37/40] Add screenshot and README --- apps/timestamplog/README.md | 55 +++++++++++++++++++++++++++++++ apps/timestamplog/metadata.json | 2 ++ apps/timestamplog/screenshot.png | Bin 0 -> 18773 bytes 3 files changed, 57 insertions(+) create mode 100644 apps/timestamplog/README.md create mode 100644 apps/timestamplog/screenshot.png diff --git a/apps/timestamplog/README.md b/apps/timestamplog/README.md new file mode 100644 index 0000000000..96565e2d7e --- /dev/null +++ b/apps/timestamplog/README.md @@ -0,0 +1,55 @@ +# Timestamp Log + +Timestamp Log provides a convenient way to record points in time for later reference. Each time a button is tapped a date/time-stamped marker is logged. By default up to 30 entries can be stored at once; this can be increased up to 100 in the settings menu. + +![Timestamp Log screenshot](screenshot.png) + +## Usage and controls + +When the app starts you will see the log display. Initially the log is empty. The large button on the bottom left displays the current time and will add a date- and time-stamp when tapped. Each tap of the button adds a new entry to the bottom of the log. The small button on the bottom right opens the app settings menu. + +If the log contains more entries than can be displayed at once, swiping up and down will move through the entries one screenfull at a time. + +To delete an individual entry, display it on the screen and then tap on it. The entry's position in the list will be shown along with a Delete button. Tap this button to remove the entry. The Up and Down arrows on the right side of the screen can be used to move between log entries. Further deletions can be made. Finally, click the Back button in the upper-left to finish and return to the main log screen. + +## Settings + +The settings menu provides the following settings: + +### Log + +**Max entries:** Select the maximum number of entries that the log can hold. + +**Auto-delete oldest:** If turned on, adding a log entry when the log is full will cause the oldest entry to automatically be deleted to make room. Otherwise, it is not possible to add another log entry until some entries are manually deleted or the “Max entries” setting is increased. + +**Clear log:** Remove all log entries, leaving the log empty. + +### Appearance + +**Log font:** Select the font used to display log entries. + +**Log font H size** and **Log font V size**: Select the horizontal and vertical sizes, respectively, of the font. Reasonable values for bitmapped fonts are 1 or 2 for either setting. For Vector, values around 15 to 25 work best. Setting both sizes the same will display the font with normal proportions; varying the values will change the relative height or width of the font. + +### Button + +You can choose the action that the physical button (Bangle.js v2) performs when the screen is unlocked. + +**Log time:** Add a date/time stamp to the log. Same as tapping the large button on the touch screen. + +**Open settings:** Open this app settings menu. + +**Quit app:** Return to the main clock app. + +**Do nothing:** Perform no action. + +## Web interface + +Currently the web interface displays the list of dates and times, which can be copied and pasted as desired. The log cannot currently be edited with this interface, only displayed. + +## Support + +Issues and questions may be posted at: https://github.com/espruino/BangleApps/issues + +## Creator + +Travis Evans diff --git a/apps/timestamplog/metadata.json b/apps/timestamplog/metadata.json index e9b23e0fde..e1aa0eb239 100644 --- a/apps/timestamplog/metadata.json +++ b/apps/timestamplog/metadata.json @@ -5,6 +5,8 @@ "icon": "app.png", "version": "0.01", "description": "Conveniently record a series of date/time stamps", + "screenshots": [ {"url": "screenshot.png" } ], + "readme": "README.md", "tags": "timestamp, log", "supports": ["BANGLEJS2"], "interface": "interface.html", diff --git a/apps/timestamplog/screenshot.png b/apps/timestamplog/screenshot.png new file mode 100644 index 0000000000000000000000000000000000000000..27f36ac3292b08ce060d95748df2ca52a930ac56 GIT binary patch literal 18773 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 QjsO4v07*qoM6N<$f- Date: Mon, 15 Jul 2024 16:19:30 -0500 Subject: [PATCH 38/40] Add source for app icon --- apps/timestamplog/app.xcf | Bin 0 -> 3379 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 apps/timestamplog/app.xcf diff --git a/apps/timestamplog/app.xcf b/apps/timestamplog/app.xcf new file mode 100644 index 0000000000000000000000000000000000000000..27406b1ed881c36aafbb85586e0e33907efb704f GIT binary patch literal 3379 zcmeHI&2Jk;6n`_j-d%5;IC1MnAvj*gcH*>g6hlgz6OnR29FTJ4nmUea;nQeb#7BU%6nHeLe-`*3B#xDIkbInWJ_*)I?Ob@16XQr4UO zlap3&1Ub%-+aI(JN3Emivd2T?6%^+EgIVYI!qvvYUn&9`>%+}^pf+j!&l?%n^aYiuCsy(@w?qWEF+ zgW>66v(sxw4G0fyLF4*|z0N4=^qTiuL-y|<9}iojXq(+FoWQ?3^E|B~zDk7DrwLDg zVHpiH`<8;jKWO!uoi3X^5bp+WT8Z)7kMYKifmqKwlhzoFRxSLxg+mKR7M`(i#=^4} z&H{5jGkgHf&GD-mgHeSselxDMY+>_9aU5&NFD=1LpMP5n`F+CUx3RJQsnI_Y5h<}n zmoj;6(zzrlZqb=wev_Wb7;KP`iK<3YqjhpLS|eAZDy1~4kf%|Ze2rEq(5Qs021lIT z2t~RO6KMtISYm<8`oCo4ndCc%Bvy2KPd*b4p0UD}Pvg3zC~6le|BPMOtw_IVE7#5s z7Clk1IeDP^eafo@jWH*@ge`euf*?#8XU~&~i>SbU7Tcv*gY5z~7srh+Hps(fx?&UB zJ&Jc-ZIMuQl4_G2bql}TO-d>FsT%oeodUH+X;r0ARcJ<)DWg_tR+VT@MU+)VnpZ2d zpbC`x7g~sYR^ZG2MSREKMu@?2N{GjCE6BmQP-mPUb)qJr_6TDM#2w2G?HwGRwgv#C z|L+&7uNP5Q0rb3mJ;xK94DYiE9TlgQ*2Ks9mTG#igX2 zcr5;a@04xqDW;}3j_~S>@fw*mdCAslNn}-k6T`;|&cANxGo~E#i87 zB;V&dvZg6MA@;ld<_FvaFVc-~ZvE#y8O&+N^TG%}Pk8(`v)4Z1H&@vBo1ct6i`!rs zH$e)NM|;hJa<~dBH4=_T=X`K1ku)$3?F__#EkdNpaePoZE>Jotn7Gh6aGVv+9I(LT0!rZ%7dVkI z5hv7g6}l?E=L__bz~Y=^cE%N0)lMol)${CmRy<>#rHATy*1U?-E5fTZy>j-dW~>#@ z!q@CX4tn05Jc{PsTE@<03Hzs(af{~B6FKxlmX=h8mQ_dv^h^;wQ$){{&@W~5OBwxA pMZZi4eIxeJJrfOErS^95rKh6=h!`Sr1{t!*ltacmGNTU2e*u~9U3dTh literal 0 HcmV?d00001 From f7badead2c42110c8ef818d60ff056c699bb19b8 Mon Sep 17 00:00:00 2001 From: Travis Evans Date: Tue, 16 Jul 2024 17:20:04 -0500 Subject: [PATCH 39/40] Don't crash if timestamplog.json doesn't exist yet --- apps/timestamplog/interface.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/timestamplog/interface.html b/apps/timestamplog/interface.html index 6febe78498..b54dfb010e 100644 --- a/apps/timestamplog/interface.html +++ b/apps/timestamplog/interface.html @@ -14,7 +14,7 @@ function HTMLStampLog(stampLog) { html = '' - if (stampLog.length) { + if (stampLog !== undefined && stampLog.length) { html += '
    '; for (const logItem of stampLog) { html += ('
  1. ' + new Date(logItem.stamp).toLocaleString() + '
  2. '); From 8f22d8aa35ccc05e4d29ad7419a866b2faeac94c Mon Sep 17 00:00:00 2001 From: Travis Evans Date: Wed, 17 Jul 2024 15:07:49 -0500 Subject: [PATCH 40/40] Ensure settings are saved upon Bangle2 button long-press --- apps/timestamplog/lib.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/apps/timestamplog/lib.js b/apps/timestamplog/lib.js index 13cb291efc..59590bfb76 100644 --- a/apps/timestamplog/lib.js +++ b/apps/timestamplog/lib.js @@ -141,14 +141,17 @@ function launchSettingsMenu(backCb) { const stampLog = new StampLog(LOG_FILENAME, SETTINGS.maxLogLength); function saveSettings() { + console.log('Saving timestamp log and settings'); stampLog.save(); if (!storage.writeJSON(SETTINGS_FILENAME, SETTINGS)) { E.showAlert('Trouble saving settings'); } } + E.on('kill', saveSettings); function endMenu() { saveSettings(); + E.removeListener('kill', saveSettings); backCb(); }