1
0
mirror of https://github.com/Jermolene/TiddlyWiki5 synced 2025-08-11 16:24:31 +00:00

Expand the logging mechanism to support alerts overlaid on the main screen

Now we get decent visual indication of sync errors, for instance. Still
work to do to coalesce alerts so that the screen doesn’t fill up with
them after an interval. And probably we should add a button to clear
all alerts.
This commit is contained in:
Jermolene 2014-02-14 07:53:41 +00:00
parent 28212f08b2
commit 70a120d4a6
12 changed files with 118 additions and 70 deletions

View File

@ -219,6 +219,7 @@ exports.compileFilter = function(filterString) {
}); });
// Return a function that applies the operations to a source array/hashmap of tiddler titles // Return a function that applies the operations to a source array/hashmap of tiddler titles
return function(source,currTiddlerTitle) { return function(source,currTiddlerTitle) {
source = source || self.tiddlers;
var results = []; var results = [];
$tw.utils.each(operationFunctions,function(operationFunction) { $tw.utils.each(operationFunctions,function(operationFunction) {
operationFunction(results,source,currTiddlerTitle); operationFunction(results,source,currTiddlerTitle);

View File

@ -16,11 +16,8 @@ var widget = require("$:/core/modules/widgets/widget.js");
exports.startup = function() { exports.startup = function() {
var modules,n,m,f,commander; var modules,n,m,f,commander;
// Load utility modules and initialise the logger // Load modules
$tw.modules.applyMethods("utils",$tw.utils); $tw.modules.applyMethods("utils",$tw.utils);
$tw.logger = new $tw.utils.Logger();
$tw.log = $tw.logger.log;
// Load other modules
$tw.modules.applyMethods("global",$tw); $tw.modules.applyMethods("global",$tw);
$tw.modules.applyMethods("config",$tw.config); $tw.modules.applyMethods("config",$tw.config);
if($tw.browser) { if($tw.browser) {
@ -111,7 +108,7 @@ exports.startup = function() {
$tw.rootWidget.addEventListener("tw-scroll",function(event) { $tw.rootWidget.addEventListener("tw-scroll",function(event) {
$tw.pageScroller.handleEvent(event); $tw.pageScroller.handleEvent(event);
}); });
// Install the save action handler // Install the save action handlers
$tw.rootWidget.addEventListener("tw-save-wiki",function(event) { $tw.rootWidget.addEventListener("tw-save-wiki",function(event) {
$tw.syncer.saveWiki({ $tw.syncer.saveWiki({
template: event.param, template: event.param,
@ -132,6 +129,16 @@ exports.startup = function() {
downloadType: "text/plain" downloadType: "text/plain"
}); });
}); });
// Listen out for login/logout/refresh events in the browser
$tw.rootWidget.addEventListener("tw-login",function() {
$tw.syncer.handleLoginEvent();
});
$tw.rootWidget.addEventListener("tw-logout",function() {
$tw.syncer.handleLogoutEvent();
});
$tw.rootWidget.addEventListener("tw-server-refresh",function() {
$tw.syncer.handleRefreshEvent();
});
// Install the crypto event handlers // Install the crypto event handlers
$tw.rootWidget.addEventListener("tw-set-password",function(event) { $tw.rootWidget.addEventListener("tw-set-password",function(event) {
$tw.passwordPrompt.createPrompt({ $tw.passwordPrompt.createPrompt({

View File

@ -20,7 +20,7 @@ function Syncer(options) {
var self = this; var self = this;
this.wiki = options.wiki; this.wiki = options.wiki;
// Make a logger // Make a logger
this.log = $tw.logger.makeLog("syncer"); this.logger = new $tw.utils.Logger("syncer" + ($tw.browser ? "-browser" : "") + ($tw.node ? "-server" : ""));
// Find a working syncadaptor // Find a working syncadaptor
this.syncadaptor = undefined; this.syncadaptor = undefined;
$tw.modules.forEachModuleOfType("syncadaptor",function(title,module) { $tw.modules.forEachModuleOfType("syncadaptor",function(title,module) {
@ -51,18 +51,6 @@ function Syncer(options) {
self.handleLazyLoadEvent(title); self.handleLazyLoadEvent(title);
}); });
} }
// Listen out for login/logout/refresh events in the browser
if($tw.browser) {
document.addEventListener("tw-login",function(event) {
self.handleLoginEvent(event);
},false);
document.addEventListener("tw-logout",function(event) {
self.handleLogoutEvent(event);
},false);
document.addEventListener("tw-server-refresh",function(event) {
self.handleRefreshEvent(event);
},false);
}
// Get the login status // Get the login status
this.getStatus(function (err,isLoggedIn) { this.getStatus(function (err,isLoggedIn) {
// Do a sync from the server // Do a sync from the server
@ -70,13 +58,6 @@ function Syncer(options) {
}); });
} }
/*
Error handling
*/
Syncer.prototype.showError = function(error) {
this.log("Error: " + error);
};
/* /*
Constants Constants
*/ */
@ -96,8 +77,10 @@ Syncer.prototype.readTiddlerInfo = function() {
// Hashmap by title of {revision:,changeCount:,adaptorInfo:} // Hashmap by title of {revision:,changeCount:,adaptorInfo:}
this.tiddlerInfo = {}; this.tiddlerInfo = {};
// Record information for known tiddlers // Record information for known tiddlers
var self = this; var self = this,
this.wiki.forEachTiddler({includeSystem: true},function(title,tiddler) { tiddlers = this.filterFn.call(this.wiki);
$tw.utils.each(tiddlers,function(title) {
var tiddler = self.wiki.getTiddler(title);
self.tiddlerInfo[title] = { self.tiddlerInfo[title] = {
revision: tiddler.fields["revision"], revision: tiddler.fields["revision"],
adaptorInfo: self.syncadaptor && self.syncadaptor.getTiddlerInfo(tiddler), adaptorInfo: self.syncadaptor && self.syncadaptor.getTiddlerInfo(tiddler),
@ -165,7 +148,7 @@ Syncer.prototype.saveWiki = function(options) {
for(var t=this.savers.length-1; t>=0; t--) { for(var t=this.savers.length-1; t>=0; t--) {
var saver = this.savers[t]; var saver = this.savers[t];
if(saver.info.capabilities.indexOf(method) !== -1 && saver.save(text,method,callback)) { if(saver.info.capabilities.indexOf(method) !== -1 && saver.save(text,method,callback)) {
this.log("Saving wiki with method",method,"through saver",saver.info.name); this.logger.log("Saving wiki with method",method,"through saver",saver.info.name);
// Clear the task queue if we're saving (rather than downloading) // Clear the task queue if we're saving (rather than downloading)
if(method !== "download") { if(method !== "download") {
this.readTiddlerInfo(); this.readTiddlerInfo();
@ -208,7 +191,7 @@ Syncer.prototype.getStatus = function(callback) {
// Get login status // Get login status
this.syncadaptor.getStatus(function(err,isLoggedIn,username) { this.syncadaptor.getStatus(function(err,isLoggedIn,username) {
if(err) { if(err) {
self.showError(err); self.logger.alert(err);
return; return;
} }
// Set the various status tiddlers // Set the various status tiddlers
@ -233,7 +216,7 @@ Synchronise from the server by reading the skinny tiddler list and queuing up lo
*/ */
Syncer.prototype.syncFromServer = function() { Syncer.prototype.syncFromServer = function() {
if(this.syncadaptor && this.syncadaptor.getSkinnyTiddlers) { if(this.syncadaptor && this.syncadaptor.getSkinnyTiddlers) {
this.log("Retrieving skinny tiddler list"); this.logger.log("Retrieving skinny tiddler list");
var self = this; var self = this;
if(this.pollTimerId) { if(this.pollTimerId) {
clearTimeout(this.pollTimerId); clearTimeout(this.pollTimerId);
@ -247,7 +230,7 @@ Syncer.prototype.syncFromServer = function() {
},self.pollTimerInterval); },self.pollTimerInterval);
// Check for errors // Check for errors
if(err) { if(err) {
self.log("Error retrieving skinny tiddler list:",err); self.logger.alert("Error retrieving skinny tiddler list:",err);
return; return;
} }
// Process each incoming tiddler // Process each incoming tiddler
@ -285,8 +268,8 @@ Syncer.prototype.syncToServer = function(changes) {
now = new Date(), now = new Date(),
filteredChanges = this.filterFn.call(this.wiki,changes); filteredChanges = this.filterFn.call(this.wiki,changes);
$tw.utils.each(changes,function(change,title,object) { $tw.utils.each(changes,function(change,title,object) {
// Ignore the change if it is a shadow tiddler // Process the change if it is a deletion of a tiddler we're already syncing, or is on the filtered change list
if((change.deleted && $tw.utils.hop(self.tiddlerInfo,title)) || (!change.deleted && filteredChanges.indexOf(title) !== -1)) { if((change.deleted && $tw.utils.hop(self.tiddlerInfo,title)) || filteredChanges.indexOf(title) !== -1) {
// Queue a task to sync this tiddler // Queue a task to sync this tiddler
self.enqueueSyncTask({ self.enqueueSyncTask({
type: change.deleted ? "delete" : "save", type: change.deleted ? "delete" : "save",
@ -300,7 +283,6 @@ Syncer.prototype.syncToServer = function(changes) {
Lazily load a skinny tiddler if we can Lazily load a skinny tiddler if we can
*/ */
Syncer.prototype.handleLazyLoadEvent = function(title) { Syncer.prototype.handleLazyLoadEvent = function(title) {
console.log("Lazy loading",title)
// Queue up a sync task to load this tiddler // Queue up a sync task to load this tiddler
this.enqueueSyncTask({ this.enqueueSyncTask({
type: "load", type: "load",
@ -335,7 +317,7 @@ Attempt to login to TiddlyWeb.
callback: invoked with arguments (err,isLoggedIn) callback: invoked with arguments (err,isLoggedIn)
*/ */
Syncer.prototype.login = function(username,password,callback) { Syncer.prototype.login = function(username,password,callback) {
this.log("Attempting to login as",username); this.logger.log("Attempting to login as",username);
var self = this; var self = this;
if(this.syncadaptor.login) { if(this.syncadaptor.login) {
this.syncadaptor.login(username,password,function(err) { this.syncadaptor.login(username,password,function(err) {
@ -357,12 +339,12 @@ Syncer.prototype.login = function(username,password,callback) {
Attempt to log out of TiddlyWeb Attempt to log out of TiddlyWeb
*/ */
Syncer.prototype.handleLogoutEvent = function() { Syncer.prototype.handleLogoutEvent = function() {
this.log("Attempting to logout"); this.logger.log("Attempting to logout");
var self = this; var self = this;
if(this.syncadaptor.logout) { if(this.syncadaptor.logout) {
this.syncadaptor.logout(function(err) { this.syncadaptor.logout(function(err) {
if(err) { if(err) {
self.showError(err); self.logger.alert(err);
} else { } else {
self.getStatus(); self.getStatus();
} }
@ -400,7 +382,7 @@ Syncer.prototype.enqueueSyncTask = function(task) {
} }
// Check if this tiddler is already in the queue // Check if this tiddler is already in the queue
if($tw.utils.hop(this.taskQueue,task.title)) { if($tw.utils.hop(this.taskQueue,task.title)) {
// this.log("Re-queueing up sync task with type:",task.type,"title:",task.title); // this.logger.log("Re-queueing up sync task with type:",task.type,"title:",task.title);
var existingTask = this.taskQueue[task.title]; var existingTask = this.taskQueue[task.title];
// If so, just update the last modification time // If so, just update the last modification time
existingTask.lastModificationTime = task.lastModificationTime; existingTask.lastModificationTime = task.lastModificationTime;
@ -409,7 +391,7 @@ Syncer.prototype.enqueueSyncTask = function(task) {
existingTask.type = task.type; existingTask.type = task.type;
} }
} else { } else {
// this.log("Queuing up sync task with type:",task.type,"title:",task.title); // this.logger.log("Queuing up sync task with type:",task.type,"title:",task.title);
// If it is not in the queue, insert it // If it is not in the queue, insert it
this.taskQueue[task.title] = task; this.taskQueue[task.title] = task;
} }
@ -463,7 +445,7 @@ Syncer.prototype.processTaskQueue = function() {
// Dispatch the task // Dispatch the task
this.dispatchTask(task,function(err) { this.dispatchTask(task,function(err) {
if(err) { if(err) {
self.showError("Sync error while processing '" + task.title + "':\n" + err); self.logger.alert("Sync error while processing '" + task.title + "':\n" + err);
} }
// Mark that this task is no longer in progress // Mark that this task is no longer in progress
delete self.taskInProgress[task.title]; delete self.taskInProgress[task.title];
@ -515,7 +497,7 @@ Syncer.prototype.dispatchTask = function(task,callback) {
if(task.type === "save") { if(task.type === "save") {
var changeCount = this.wiki.getChangeCount(task.title), var changeCount = this.wiki.getChangeCount(task.title),
tiddler = this.wiki.getTiddler(task.title); tiddler = this.wiki.getTiddler(task.title);
this.log("Dispatching 'save' task:",task.title); this.logger.log("Dispatching 'save' task:",task.title);
if(tiddler) { if(tiddler) {
this.syncadaptor.saveTiddler(tiddler,function(err,adaptorInfo,revision) { this.syncadaptor.saveTiddler(tiddler,function(err,adaptorInfo,revision) {
if(err) { if(err) {
@ -533,7 +515,7 @@ Syncer.prototype.dispatchTask = function(task,callback) {
} }
} else if(task.type === "load") { } else if(task.type === "load") {
// Load the tiddler // Load the tiddler
this.log("Dispatching 'load' task:",task.title); this.logger.log("Dispatching 'load' task:",task.title);
this.syncadaptor.loadTiddler(task.title,function(err,tiddlerFields) { this.syncadaptor.loadTiddler(task.title,function(err,tiddlerFields) {
if(err) { if(err) {
return callback(err); return callback(err);
@ -547,7 +529,7 @@ Syncer.prototype.dispatchTask = function(task,callback) {
}); });
} else if(task.type === "delete") { } else if(task.type === "delete") {
// Delete the tiddler // Delete the tiddler
this.log("Dispatching 'delete' task:",task.title); this.logger.log("Dispatching 'delete' task:",task.title);
this.syncadaptor.deleteTiddler(task.title,function(err) { this.syncadaptor.deleteTiddler(task.title,function(err) {
if(err) { if(err) {
return callback(err); return callback(err);

View File

@ -45,7 +45,7 @@ exports.httpRequest = function(options) {
return; return;
} }
// Something went wrong // Something went wrong
options.callback(new Error("XMLHttpRequest error: " + this.status)); options.callback("XMLHttpRequest error code: " + this.status);
} }
}; };
// Make the request // Make the request

View File

@ -15,29 +15,36 @@ A basic logging implementation
/* /*
Make a new logger Make a new logger
*/ */
function Logger() { function Logger(componentName) {
this.componentName = componentName || "";
} }
/*
Make a log function for a particular component
*/
Logger.prototype.makeLog = function(componentName) {
var self = this;
return function(/* args */) {
self.log.apply(self.log,[componentName + ":"].concat(Array.prototype.slice.call(arguments,0)));
};
};
/* /*
Log a message Log a message
*/ */
Logger.prototype.log = function(/* args */) { Logger.prototype.log = function(/* args */) {
if(console !== undefined && console.log !== undefined) { if(console !== undefined && console.log !== undefined) {
return Function.apply.call(console.log, console, arguments); return Function.apply.call(console.log, console, [this.componentName + ":"].concat(Array.prototype.slice.call(arguments,0)));
} }
}; };
/*
Alert a message
*/
Logger.prototype.alert = function(/* args */) {
var text = Array.prototype.join.call(arguments," "),
fields = {
title: $tw.wiki.generateNewTitle("$:/temp/alerts/alert",{prefix: ""}),
text: text,
tags: ["$:/tags/Alert"],
component: this.componentName,
modified: new Date()
};
$tw.wiki.addTiddler(new $tw.Tiddler(fields));
// Log it too
this.log.apply(this,Array.prototype.slice.call(arguments,0));
};
exports.Logger = Logger; exports.Logger = Logger;
})(); })();

View File

@ -172,11 +172,14 @@ exports.tiddlerExists = function(title) {
/* /*
Generate an unused title from the specified base Generate an unused title from the specified base
*/ */
exports.generateNewTitle = function(baseTitle) { exports.generateNewTitle = function(baseTitle,options) {
options = options || {};
var c = 0, var c = 0,
title = baseTitle; title = baseTitle;
while(this.tiddlerExists(title)) { while(this.tiddlerExists(title)) {
title = baseTitle + " " + (++c); title = baseTitle +
(options.prefix || " ") +
(++c);
}; };
return title; return title;
}; };

View File

@ -0,0 +1,3 @@
title: $:/core/ui/AlertTemplate
<div class="tw-alert"><div class="tw-alert-toolbar"><$button message="tw-delete-tiddler" class="btn-invisible">{{$:/core/images/delete-button}}</$button></div><div class="tw-alert-subtitle"><$view field="component"/> - <$view field="modified" format="relativedate"/></div><div class="tw-alert-body"><$view field="text"/></div></div>

View File

@ -3,6 +3,13 @@ tags: $:/tags/PageTemplate
<section class="story-river"> <section class="story-river">
<!-- Alerts -->
<div class="tw-alerts">
<$list filter="[is[shadow]!has[draft.of]tag[$:/tags/Alert]] [!is[shadow]!has[draft.of]tag[$:/tags/Alert]] +[sort[modified]]" template="$:/core/ui/AlertTemplate" storyview="pop"/>
</div>
<!-- The main story --> <!-- The main story -->
<$list filter="[list[$:/StoryList]]" history="$:/HistoryList" template="$:/core/ui/ViewTemplate" editTemplate="$:/core/ui/EditTemplate" storyview={{$:/view}} /> <$list filter="[list[$:/StoryList]]" history="$:/HistoryList" template="$:/core/ui/ViewTemplate" editTemplate="$:/core/ui/EditTemplate" storyview={{$:/view}} />

View File

@ -0,0 +1,17 @@
created: 20140213224306412
modified: 20140213224622441
tags: mechanism
title: AlertMechanism
type: text/vnd.tiddlywiki
Alerts are displayed as yellow boxes overlaying the main TiddlyWiki window. Each one corresponds to a tiddler with the tag [[$:/tags/Alert]]. Clicking the delete icon on an alert deletes the corresponding tiddler.
Alert tiddlers should have the following fields:
|!Field |!Description |
|title |By default, alert titles have the prefix `$:/temp/alerts/` |
|text |The text of the alert message |
|modified |Date of the alert (used for ordering the alerts on screen) |
|component |Component name associated with the alert |
|tags |Must include [[$:/tags/Alert]] |

View File

@ -21,12 +21,12 @@ function FileSystemAdaptor(syncer) {
this.syncer = syncer; this.syncer = syncer;
this.watchers = {}; this.watchers = {};
this.pending = {}; this.pending = {};
this.log = $tw.logger.makeLog("FileSystem"); this.logger = new $tw.utils.Logger("FileSystem");
this.setwatcher = function(filename, title) { this.setwatcher = function(filename, title) {
return undefined; return undefined;
return this.watchers[filename] = this.watchers[filename] || return this.watchers[filename] = this.watchers[filename] ||
fs.watch(filename, {persistent: false}, function(e) { fs.watch(filename, {persistent: false}, function(e) {
self.log("Error:",e,filename); self.logger.log("Error:",e,filename);
if(e === "change") { if(e === "change") {
var tiddlers = $tw.loadTiddlersFromFile(filename).tiddlers; var tiddlers = $tw.loadTiddlersFromFile(filename).tiddlers;
for(var t in tiddlers) { for(var t in tiddlers) {
@ -157,7 +157,7 @@ FileSystemAdaptor.prototype.saveTiddler = function(tiddler,callback) {
if(err) { if(err) {
return callback(err); return callback(err);
} }
self.log("Saved file",fileInfo.filepath); self.logger.log("Saved file",fileInfo.filepath);
_finish(); _finish();
}); });
}); });
@ -169,7 +169,7 @@ FileSystemAdaptor.prototype.saveTiddler = function(tiddler,callback) {
if(err) { if(err) {
return callback(err); return callback(err);
} }
self.log("Saved file",fileInfo.filepath); self.logger.log("Saved file",fileInfo.filepath);
_finish(); _finish();
}); });
} }
@ -203,7 +203,7 @@ FileSystemAdaptor.prototype.deleteTiddler = function(title,callback) {
if(err) { if(err) {
return callback(err); return callback(err);
} }
self.log("Deleted file",fileInfo.filepath); self.logger.log("Deleted file",fileInfo.filepath);
// Delete the metafile if present // Delete the metafile if present
if(fileInfo.hasMetaFile) { if(fileInfo.hasMetaFile) {
fs.unlink(fileInfo.filepath + ".meta",function(err) { fs.unlink(fileInfo.filepath + ".meta",function(err) {

View File

@ -19,7 +19,7 @@ function TiddlyWebAdaptor(syncer) {
this.syncer = syncer; this.syncer = syncer;
this.host = this.getHost(); this.host = this.getHost();
this.recipe = undefined; this.recipe = undefined;
this.log = $tw.logger.makeLog("TiddlyWebAdaptor"); this.logger = new $tw.utils.Logger("TiddlyWebAdaptor");
} }
TiddlyWebAdaptor.prototype.getHost = function() { TiddlyWebAdaptor.prototype.getHost = function() {
@ -48,7 +48,7 @@ TiddlyWebAdaptor.prototype.getStatus = function(callback) {
// Get status // Get status
var self = this, var self = this,
wiki = self.syncer.wiki; wiki = self.syncer.wiki;
this.log("Getting status"); this.logger.log("Getting status");
$tw.utils.httpRequest({ $tw.utils.httpRequest({
url: this.host + "status", url: this.host + "status",
callback: function(err,data) { callback: function(err,data) {
@ -63,7 +63,7 @@ TiddlyWebAdaptor.prototype.getStatus = function(callback) {
} catch (e) { } catch (e) {
} }
if(json) { if(json) {
self.log("Status:",data); self.logger.log("Status:",data);
// Record the recipe // Record the recipe
if(json.space) { if(json.space) {
self.recipe = json.space.recipe; self.recipe = json.space.recipe;
@ -95,7 +95,7 @@ TiddlyWebAdaptor.prototype.login = function(username,password,callback) {
callback(err); callback(err);
} }
}; };
this.log("Logging in:",options); this.logger.log("Logging in:",options);
$tw.utils.httpRequest(options); $tw.utils.httpRequest(options);
}; };
@ -113,7 +113,7 @@ TiddlyWebAdaptor.prototype.logout = function(callback) {
callback(err); callback(err);
} }
}; };
this.log("Logging out:",options); this.logger.log("Logging out:",options);
$tw.utils.httpRequest(options); $tw.utils.httpRequest(options);
}; };

View File

@ -922,14 +922,35 @@ canvas.tw-edit-bitmapeditor {
** Alerts ** Alerts
*/ */
.tw-alerts {
position: fixed;
top: 0;
left: 0;
right: 0;
z-index: 20000;
}
.tw-alert { .tw-alert {
position: relative;
<<border-radius 6px>> <<border-radius 6px>>
margin: 7px 7px 7px 7px; margin: 28px;
padding: 14px 14px 14px 14px; padding: 14px 14px 14px 14px;
border: 3px solid #DBB727; border: 3px solid #DBB727;
background-color: rgb(250, 210, 50); background-color: rgb(250, 210, 50);
} }
.tw-alert-toolbar {
position: absolute;
top: 14px;
right: 14px;
}
.tw-alert-subtitle {
color: #DBB727;
font-weight: bold;
}
.tw-static-alert { .tw-static-alert {
position: relative; position: relative;
} }