mirror of https://github.com/espruino/BangleApps
249 lines
6.3 KiB
JavaScript
249 lines
6.3 KiB
JavaScript
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() {
|
|
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();
|
|
}
|
|
|
|
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};
|