+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/resources/public/js/main.js b/resources/public/js/main.js
index cd9632283..66f9c6305 100644
--- a/resources/public/js/main.js
+++ b/resources/public/js/main.js
@@ -9,6 +9,10 @@
var app = function () {
var self = {};
+ self.conf = {};
+
+ self.conf.strings = {};
+
// Most importantly, the application has a worksheet! This is exposed so that the UI can bind to it, but note that
// you should never change the worksheet directly, as this will leave the event handlers in an inconsistent state.
// Rather you should use the `setWorksheet` function below.
@@ -19,7 +23,7 @@ var app = function () {
// whenever we change the filename, we update the URL to match
self.filename.subscribe(function (filename) {
if (filename !== "") history.pushState(null, null, "?filename=" + filename);
- else history.pushState(null, null, "/worksheet.html");
+ else history.pushState(null, null, self.conf.worksheetName || "/worksheet.html");
});
// shows the name of the Leiningen project that gorilla was launched from, makes it easier to manage multiple
// tabs with multiple gorilla sessions.
@@ -39,11 +43,11 @@ var app = function () {
// UI and, if appropriate, load an initial worksheet.
self.start = function (initialFilename) {
// get hold of configuration information from the backend
- $.get("/config")
+ $.get(/^.*\//.exec(window.location.pathname)[0]+"config")
.done(function (data) {
- self.config = data;
+ self.conf = data;
// If we've got the configuration, then start the app
- self.project(self.config.project);
+ self.project(self.conf.project);
// Prepare an empty worksheet so the UI has something to bind to. We do this as if we are loading a
// worksheet on startup, we do it asynchronously, so need to have something in place before starting
// the UI. This is easier than having two paths that both have UI startup code on them. (Although, the
@@ -52,9 +56,14 @@ var app = function () {
self.setWorksheet(ws, "");
// start the UI
- commandProcessor.installCommands(self.config.keymap);
+ commandProcessor.installCommands(self.conf.keymap);
ko.applyBindings(self, document.getElementById("document"));
+ // allow the filename to be passed as a parameter
+ if (!initialFilename && data.filename) {
+ initialFilename = data.filename;
+ }
+
if (initialFilename) loadFromFile(initialFilename);
else setBlankWorksheet();
})
@@ -67,15 +76,20 @@ var app = function () {
// A helper function to create a new, blank worksheet with some introductory messages in.
var setBlankWorksheet = function () {
var ws = worksheet();
+ var meinConf = self.conf || {};
+ var myStrings = self.conf.strings || {};
ws.segments().push(
// Note that the variable ck here is defined in commandProcessor.js, and gives the appropriate
// shortcut key (ctrl or alt) for the platform.
- freeSegment("# Gorilla REPL\n\nWelcome to gorilla :-)\n\nShift + enter evaluates code. " +
- "Hit " + ck + "+g twice in quick succession or click the menu icon (upper-right corner) " +
- "for more commands ...\n\nIt's a good habit to run each worksheet in its own namespace: feel " +
- "free to use the declaration we've provided below if you'd like.")
+ freeSegment(myStrings.introMessage ||
+ ("# Gorilla REPL\n\nWelcome to gorilla :-)\n\nShift + enter evaluates code. " +
+ "Hit " + ck + "+g twice in quick succession or click the menu icon (upper-right corner) " +
+ "for more commands ...\n\nIt's a good habit to run each worksheet in its own namespace: feel " +
+ "free to use the declaration we've provided below if you'd like."))
);
- ws.segments().push(codeSegment("(ns " + makeHipNSName() + "\n (:require [gorilla-plot.core :as plot]))"));
+ ws.segments().push(codeSegment(myStrings.nsSegment ||
+ ("(ns " + (meinConf.nameSpace || makeHipNSName()) +
+ "\n (:require [gorilla-plot.core :as plot]))")));
self.setWorksheet(ws, "");
// make it easier for the user to get started by highlighting the empty code segment
eventBus.trigger("worksheet:segment-clicked", {id: self.worksheet().segments()[1].id});
@@ -84,7 +98,7 @@ var app = function () {
// bound to the window's title
self.title = ko.computed(function () {
- if (self.filename() === "") return "Gorilla REPL - " + self.project();
+ if (self.filename() === "") return (self.conf.strings.title || "Gorilla REPL - ") + self.project();
else return self.project() + " : " + self.filename();
});
@@ -124,11 +138,11 @@ var app = function () {
// Helpers for loading and saving the worksheet - called by the various command handlers
var saveToFile = function (filename, successCallback) {
- $.post("/save", {
+ $.post(/^.*\//.exec(window.location.pathname)[0]+"save", {
"worksheet-filename": filename,
"worksheet-data": self.worksheet().toClojure()
}).done(function () {
- self.flashStatusMessage("Saved: " + filename);
+ if (!self.conf.hideSavedMsg) self.flashStatusMessage("Saved: " + filename);
if (successCallback) successCallback();
}).fail(function () {
self.flashStatusMessage("Failed to save worksheet: " + filename, 2000);
@@ -137,21 +151,23 @@ var app = function () {
var loadFromFile = function (filename) {
// ask the backend to load the data from disk
- $.get("/load", {"worksheet-filename": filename})
- .done(function (data) {
- if (data['worksheet-data']) {
- // parse and construct the new worksheet
- var segments = worksheetParser.parse(data["worksheet-data"]);
- var ws = worksheet();
- ws.segments = ko.observableArray(segments);
- // show it in the editor
- self.setWorksheet(ws, filename);
- // highlight the first code segment if it exists
- var codeSegments = _.filter(self.worksheet().segments(), function(s) {return s.type === 'code'});
- if (codeSegments.length > 0)
- eventBus.trigger("worksheet:segment-clicked", {id: codeSegments[0].id});
- }
- })
+ $.get(/^.*\//.exec(window.location.pathname)[0]+"load", {"worksheet-filename": filename})
+ .done(function (data) {
+ if (data['worksheet-data']) {
+ // parse and construct the new worksheet
+ var segments = worksheetParser.parse(data["worksheet-data"]);
+ var ws = worksheet();
+ ws.segments = ko.observableArray(segments);
+ // show it in the editor
+ self.setWorksheet(ws, filename);
+ // highlight the first code segment if it exists
+ var codeSegments = _.filter(self.worksheet().segments(), function(s) {return s.type === 'code'});
+ if (codeSegments.length > 0) {
+ eventBus.trigger("worksheet:segment-clicked", {id: codeSegments[0].id});
+ if (self.conf.recalcAllOnLoad) eventBus.trigger("worksheet:evaluate-all");
+ }
+ }
+ })
.fail(function () {
self.flashStatusMessage("Failed to load worksheet: " + filename, 2000);
});
@@ -178,7 +194,7 @@ var app = function () {
self.palette.show("Scanning for files ...", []);
$.ajax({
type: "GET",
- url: "/gorilla-files",
+ url: /^.*\//.exec(window.location.pathname)[0]+"gorilla-files",
success: function (data) {
var paletteFiles = data.files.map(function (c) {
return {
@@ -204,6 +220,16 @@ var app = function () {
} else self.saveDialog.show();
});
+ // Save only if the worksheet is named. Used for autosave.
+ eventBus.on("app:save-named", function () {
+ var fname = self.filename();
+ // if we already have a filename, save to it. Else, prompt for a name.
+ if (fname !== "" && self.conf.autosave) {
+ saveToFile(fname);
+ }
+ });
+
+
eventBus.on("app:saveas", function () {
var fname = self.filename();
self.saveDialog.show(fname);
diff --git a/resources/public/js/repl-ws.js b/resources/public/js/repl-ws.js
index b68667e1a..84820ec96 100644
--- a/resources/public/js/repl-ws.js
+++ b/resources/public/js/repl-ws.js
@@ -17,12 +17,18 @@ var repl = (function () {
self.ws.send(JSON.stringify(message));
};
+ // the function that deals with pinging the server which serves as
+ // (1) keep-alive and (2) a way to piggyback pushes from the server on the ping
+ self.pinger = function () {};
+
// Connect to the websocket nREPL bridge.
// TODO: handle errors.
self.connect = function (successCallback, failureCallback) {
// hard to believe we have to do this
var loc = window.location;
- var url = "ws://" + loc.hostname + ":" + loc.port + "/repl";
+ var protocol = window.location.protocol == "https:" ? "wss:" : "ws:";
+ var url = protocol + "//" + loc.hostname + ":" + loc.port +
+ /^.*\//.exec(loc.pathname)[0] +"repl";
self.ws = new WebSocket(url);
// we first install a handler that will capture the session id from the clone message. Once it's done its work
@@ -38,11 +44,24 @@ var repl = (function () {
// The first thing we do is send a clone op, to get a new session.
self.ws.onopen = function () {
+ // we're got an open web socket... proceed with pinging
+ self.pinger = function () {
+ if (self.sessionID) {
+ var id = UUID.generate();
+ self.ws.send(JSON.stringify({"op": "ping",
+ id: id,
+ "session": self.sessionID}));
+ }
+
+ setTimeout(function() {self.pinger();}, 1000);
+ };
+ self.pinger();
self.ws.send(JSON.stringify({"op": "clone"}));
};
// If the websocket connection dies we're done for, message the app to tell it so.
self.ws.onclose = function () {
+ self.pinger = function () {};
eventBus.trigger("app:connection-lost");
};
};
@@ -60,7 +79,11 @@ var repl = (function () {
var id = UUID.generate();
// store the evaluation ID and the segment ID in the evaluationMap
evaluationMap[id] = d.segmentID;
- var message = {'op': 'eval', 'code': d.code, id: id, session: self.sessionID};
+
+ // include the segmentID in the message so data can be pushed to
+ // the segment in the future
+ var message = {'op': 'eval', 'segmentID': d.segmentID,
+ 'code': d.code, id: id, session: self.sessionID};
self.sendREPLCommand(message);
};
@@ -108,13 +131,13 @@ var repl = (function () {
var d = JSON.parse(message.data);
// Is this a message relating to an evaluation triggered by the user?
- var segID = evaluationMap[d.id];
+ var segID = d.segmentID || evaluationMap[d.id];
if (segID != null) {
// - evaluation result (Hopefully no other responses have an ns component!)
if (d.ns) {
self.currentNamespace = d.ns;
- eventBus.trigger("evaluator:value-response", {ns: d.ns, value: d.value, segmentID: segID});
+ eventBus.trigger("evaluator:value-response", {clear: d.clear, ns: d.ns, value: d.value, segmentID: segID});
return;
}
@@ -166,9 +189,12 @@ var repl = (function () {
// If we get here, then we don't know what the message was for - just log it
- console.log("Unknown response: " + JSON.stringify(d));
+ // unless the message is explicitly set as ignore
+ if (!d.ignore && (!d.status || d.status.indexOf("done") < 0)) {
+ console.log("Unknown response: " + JSON.stringify(d));
+ }
};
return self;
-})();
\ No newline at end of file
+})();
diff --git a/resources/public/js/worksheet.js b/resources/public/js/worksheet.js
index 2d012a4ce..b214d777a 100644
--- a/resources/public/js/worksheet.js
+++ b/resources/public/js/worksheet.js
@@ -222,6 +222,10 @@ var worksheet = function () {
var seg = self.getActiveSegment();
if (seg == null) return;
evaluateSegment(seg);
+
+ // save the sheet on evaluate
+ eventBus.trigger("app:save-named");
+
// if this isn't the last segment, move to the next
if (self.activeSegmentIndex != self.segments().length - 1) eventBus.trigger("command:worksheet:leaveForward");
// if it is the last, create a new one at the end
@@ -237,6 +241,12 @@ var worksheet = function () {
addEventHandler("evaluator:value-response", function (e, d) {
var segID = d.segmentID;
var seg = self.getSegmentForID(segID);
+
+ // if the clear flag is set, clear the segment
+ if (d.clear) {
+ seg.clearOutput();
+ }
+
try {
// If you're paying attention, you'll notice that the value gets JSON.parse'd twice: once here, and again
// in the output viewer. This is a workaround for a problem in the rendering nREPL middleware that results
diff --git a/resources/public/worksheet.html b/resources/public/worksheet.html
index a77015139..418a4b50a 100644
--- a/resources/public/worksheet.html
+++ b/resources/public/worksheet.html
@@ -7,10 +7,11 @@
+
-
+
@@ -18,7 +19,7 @@
+ src="//cdn.mathjax.org/mathjax/latest/MathJax.js?config=TeX-AMS-MML_SVG-full.js&delayStartupUntil=configured">
@@ -175,4 +176,4 @@
-
\ No newline at end of file
+