Major refactoring of saving, notification.

* Refactored the save widget so that the notebook doesn't depend
  on it.  Now the notebook emits events and the save widget
  observes those events.
* Created a new event system for all IPython events (events.js).
  We should start to use this to allow our classes to be loosely
  coupled.
* Created a new notification widget that should be used for all
  notifications. Uses new event system.
* Removed the kernel status widget.
* All kernel status message use new event/notification system.
* The print notebook view works again.
This commit is contained in:
Brian Granger 2012-01-31 21:42:01 -08:00
parent 241e65b5c1
commit e59f95376d
10 changed files with 267 additions and 151 deletions

View File

@ -55,6 +55,19 @@ span#notebook_name {
margin: 0.3em 0;
}
#menubar_container {
position: relative;
}
#notification {
position: absolute;
right: 3px;
top: 3px;
height: 25px;
padding: 3px 6px;
z-index: 10;
}
#toolbar {
/* Initially hidden to prevent FLOUC */
display: none;
@ -71,31 +84,6 @@ span#quick_help_area {
margin: 0px 0px 0px 0px;
}
span#kernel_status {
position: absolute;
padding: 8px 5px 5px 5px;
right: 10px;
font-weight: bold;
}
.status_idle {
color: gray;
visibility: hidden;
}
.status_busy {
color: red;
}
.status_restarting {
color: black;
}
#kernel_persist {
float: right;
}
.help_string {
float: right;
width: 170px;

View File

@ -0,0 +1,31 @@
//----------------------------------------------------------------------------
// Copyright (C) 2008-2011 The IPython Development Team
//
// Distributed under the terms of the BSD License. The full license is in
// the file COPYING, distributed as part of this software.
//----------------------------------------------------------------------------
//============================================================================
// Events
//============================================================================
// Give us an object to bind all events to. This object should be created
// before all other objects so it exists when others register event handlers.
// To trigger an event handler:
// $([IPython.events]).trigger('event.Namespace);
// To handle it:
// $([IPython.events]).on('event.Namespace',function () {});
var IPython = (function (IPython) {
var utils = IPython.utils;
var Events = function () {};
IPython.Events = Events;
IPython.events = new Events();
return IPython;
}(IPython));

View File

@ -62,7 +62,7 @@ var IPython = (function (IPython) {
Kernel.prototype.restart = function (callback) {
IPython.kernel_status_widget.status_restarting();
$([IPython.events]).trigger('status_restarting.Kernel');
var url = this.kernel_url + "/restart";
var that = this;
if (this.running) {
@ -84,20 +84,19 @@ var IPython = (function (IPython) {
this.kernel_url = this.base_url + "/" + this.kernel_id;
this.start_channels();
callback();
IPython.kernel_status_widget.status_idle();
};
Kernel.prototype._websocket_closed = function(ws_url, early){
var msg;
var parent_item = $('body');
if (early) {
msg = "Websocket connection to " + ws_url + " could not be established.<br/>" +
" You will NOT be able to run code.<br/>" +
msg = "Websocket connection to " + ws_url + " could not be established." +
" You will NOT be able to run code." +
" Your browser may not be compatible with the websocket version in the server," +
" or if the url does not look right, there could be an error in the" +
" server's configuration.";
} else {
msg = "Websocket connection closed unexpectedly.<br/>" +
msg = "Websocket connection closed unexpectedly." +
" The kernel will no longer be responsive.";
}
var dialog = $('<div/>');
@ -211,6 +210,7 @@ var IPython = (function (IPython) {
Kernel.prototype.interrupt = function () {
if (this.running) {
$([IPython.events]).trigger('status_interrupting.Kernel');
$.post(this.kernel_url + "/interrupt");
};
};

View File

@ -45,21 +45,21 @@ var IPython = (function (IPython) {
IPython.save_widget.rename_notebook();
});
this.element.find('#copy_notebook').click(function () {
var notebook_id = IPython.save_widget.get_notebook_id();
var notebook_id = IPython.notebook.get_notebook_id();
var url = $('body').data('baseProjectUrl') + notebook_id + '/copy';
window.open(url,'_newtab');
});
this.element.find('#save_notebook').click(function () {
IPython.save_widget.save_notebook();
IPython.notebook.save_notebook();
});
this.element.find('#download_ipynb').click(function () {
var notebook_id = IPython.save_widget.get_notebook_id();
var notebook_id = IPython.notebook.get_notebook_id();
var url = $('body').data('baseProjectUrl') + 'notebooks/' +
notebook_id + '?format=json';
window.open(url,'_newtab');
});
this.element.find('#download_py').click(function () {
var notebook_id = IPython.save_widget.get_notebook_id();
var notebook_id = IPython.notebook.get_notebook_id();
var url = $('body').data('baseProjectUrl') + 'notebooks/' +
notebook_id + '?format=py';
window.open(url,'_newtab');

View File

@ -26,6 +26,9 @@ var IPython = (function (IPython) {
this.msg_cell_map = {};
this.metadata = {};
this.control_key_active = false;
this.notebook_id = null;
this.notebook_name = null;
this.notebook_name_blacklist_re = /[\/\\]/;
this.style();
this.create_elements();
this.bind_events();
@ -66,7 +69,7 @@ var IPython = (function (IPython) {
// Save (CTRL+S) or (AppleKey+S)
//metaKey = applekey on mac
if ((event.ctrlKey || event.metaKey) && event.keyCode==83) {
IPython.save_widget.save_notebook();
that.save_notebook();
event.preventDefault();
return false;
} else if (event.which === 27) {
@ -177,7 +180,7 @@ var IPython = (function (IPython) {
return false;
} else if (event.which === 83 && that.control_key_active) {
// Save notebook = s
IPython.save_widget.save_notebook();
that.save_notebook();
that.control_key_active = false;
return false;
} else if (event.which === 74 && that.control_key_active) {
@ -207,12 +210,12 @@ var IPython = (function (IPython) {
return false;
} else if (event.which === 73 && that.control_key_active) {
// Interrupt kernel = i
IPython.notebook.kernel.interrupt();
that.kernel.interrupt();
that.control_key_active = false;
return false;
} else if (event.which === 190 && that.control_key_active) {
// Restart kernel = . # matches qt console
IPython.notebook.restart_kernel();
that.restart_kernel();
that.control_key_active = false;
return false;
} else if (event.which === 72 && that.control_key_active) {
@ -402,10 +405,14 @@ var IPython = (function (IPython) {
var cell = this.get_cell(index)
cell.select();
if (cell.cell_type === 'heading') {
IPython.toolbar.set_cell_type(cell.cell_type+cell.level);
$([IPython.events]).trigger('selected_cell_type_changed.Notebook',
{'cell_type':cell.cell_type,level:cell.level}
);
} else {
IPython.toolbar.set_cell_type(cell.cell_type)
}
$([IPython.events]).trigger('selected_cell_type_changed.Notebook',
{'cell_type':cell.cell_type}
);
};
};
return this;
};
@ -676,7 +683,9 @@ var IPython = (function (IPython) {
source_element.remove();
this.dirty = true;
};
IPython.toolbar.set_cell_type("heading"+level);
$([IPython.events]).trigger('selected_cell_type_changed.Notebook',
{'cell_type':'heading',level:level}
);
};
};
@ -880,15 +889,12 @@ var IPython = (function (IPython) {
Notebook.prototype.start_kernel = function () {
this.kernel = new IPython.Kernel();
var notebook_id = IPython.save_widget.get_notebook_id();
this.kernel.start(notebook_id, $.proxy(this.kernel_started, this));
this.kernel.start(this.notebook_id, $.proxy(this.kernel_started, this));
};
Notebook.prototype.restart_kernel = function () {
var that = this;
var notebook_id = IPython.save_widget.get_notebook_id();
var dialog = $('<div/>');
dialog.html('Do you want to restart the current kernel? You will lose all variables defined in it.');
$(document).append(dialog);
@ -976,8 +982,8 @@ var IPython = (function (IPython) {
var cell = this.cell_for_msg(reply.parent_header.msg_id);
if (msg_type !== 'status' && !cell){
// message not from this notebook, but should be attached to a cell
console.log("Received IOPub message not caused by one of my cells");
console.log(reply);
// console.log("Received IOPub message not caused by one of my cells");
// console.log(reply);
return;
}
var output_types = ['stream','display_data','pyout','pyerr'];
@ -985,9 +991,10 @@ var IPython = (function (IPython) {
this.handle_output(cell, msg_type, content);
} else if (msg_type === 'status') {
if (content.execution_state === 'busy') {
$([IPython.events]).trigger('status_busy.Kernel');
IPython.kernel_status_widget.status_busy();
} else if (content.execution_state === 'idle') {
IPython.kernel_status_widget.status_idle();
$([IPython.events]).trigger('status_idle.Kernel');
} else if (content.execution_state === 'dead') {
this.handle_status_dead();
};
@ -1142,8 +1149,33 @@ var IPython = (function (IPython) {
this.msg_cell_map[msg_id] = cell.cell_id;
};
// Persistance and loading
Notebook.prototype.get_notebook_id = function () {
return this.notebook_id;
};
Notebook.prototype.get_notebook_name = function () {
return this.notebook_name;
};
Notebook.prototype.set_notebook_name = function (name) {
this.notebook_name = name;
};
Notebook.prototype.test_notebook_name = function (nbname) {
nbname = nbname || '';
if (this.notebook_name_blacklist_re.test(nbname) == false && nbname.length>0) {
return true;
} else {
return false;
};
};
Notebook.prototype.fromJSON = function (data) {
var ncells = this.ncells();
@ -1152,8 +1184,9 @@ var IPython = (function (IPython) {
// Always delete cell 0 as they get renumbered as they are deleted.
this.delete_cell(0);
};
// Save the metadata
// Save the metadata and name.
this.metadata = data.metadata;
this.notebook_name = data.metadata.name;
// Only handle 1 worksheet for now.
var worksheet = data.worksheets[0];
if (worksheet !== undefined) {
@ -1186,11 +1219,9 @@ var IPython = (function (IPython) {
};
Notebook.prototype.save_notebook = function () {
var notebook_id = IPython.save_widget.get_notebook_id();
var nbname = IPython.save_widget.get_notebook_name();
// We may want to move the name/id/nbformat logic inside toJSON?
var data = this.toJSON();
data.metadata.name = nbname;
data.metadata.name = this.notebook_name;
data.nbformat = 3;
// We do the call with settings so we can set cache to false.
var settings = {
@ -1199,30 +1230,29 @@ var IPython = (function (IPython) {
type : "PUT",
data : JSON.stringify(data),
headers : {'Content-Type': 'application/json'},
success : $.proxy(this.notebook_saved,this),
success : $.proxy(this.notebook_save_success,this),
error : $.proxy(this.notebook_save_failed,this)
};
IPython.save_widget.status_saving();
var url = $('body').data('baseProjectUrl') + 'notebooks/' + notebook_id;
$([IPython.events]).trigger('notebook_saving.Notebook');
var url = $('body').data('baseProjectUrl') + 'notebooks/' + this.notebook_id;
$.ajax(url, settings);
};
Notebook.prototype.notebook_saved = function (data, status, xhr) {
Notebook.prototype.notebook_save_success = function (data, status, xhr) {
this.dirty = false;
IPython.save_widget.notebook_saved();
IPython.save_widget.status_last_saved();
$([IPython.events]).trigger('notebook_saved.Notebook');
};
Notebook.prototype.notebook_save_failed = function (xhr, status, error_msg) {
IPython.save_widget.status_save_failed();
$([IPython.events]).trigger('notebook_save_failed.Notebook');
};
Notebook.prototype.load_notebook = function () {
Notebook.prototype.load_notebook = function (notebook_id) {
var that = this;
var notebook_id = IPython.save_widget.get_notebook_id();
this.notebook_id = notebook_id;
// We do the call with settings so we can set cache to false.
var settings = {
processData : false,
@ -1230,30 +1260,27 @@ var IPython = (function (IPython) {
type : "GET",
dataType : "json",
success : function (data, status, xhr) {
that.notebook_loaded(data, status, xhr);
that.load_notebook_success(data, status, xhr);
}
};
IPython.save_widget.status_loading();
var url = $('body').data('baseProjectUrl') + 'notebooks/' + notebook_id;
$([IPython.events]).trigger('notebook_loading.Notebook');
var url = $('body').data('baseProjectUrl') + 'notebooks/' + this.notebook_id;
$.ajax(url, settings);
};
Notebook.prototype.notebook_loaded = function (data, status, xhr) {
Notebook.prototype.load_notebook_success = function (data, status, xhr) {
this.fromJSON(data);
if (this.ncells() === 0) {
this.insert_cell_below('code');
};
IPython.save_widget.status_last_saved();
IPython.save_widget.set_notebook_name(data.metadata.name);
this.dirty = false;
if (! this.read_only) {
this.start_kernel();
}
this.select(0);
this.scroll_to_top();
IPython.save_widget.update_url();
IPython.layout_manager.do_resize();
$([IPython.events]).trigger('notebook_loaded.Notebook');
};
IPython.Notebook = Notebook;

View File

@ -82,14 +82,13 @@ $(document).ready(function () {
IPython.layout_manager = new IPython.LayoutManager();
IPython.pager = new IPython.Pager('div#pager', 'div#pager_splitter');
IPython.save_widget = new IPython.SaveWidget('span#save_widget');
IPython.quick_help = new IPython.QuickHelp('span#quick_help_area');
IPython.login_widget = new IPython.LoginWidget('span#login_widget');
IPython.notebook = new IPython.Notebook('div#notebook');
IPython.kernel_status_widget = new IPython.KernelStatusWidget('#kernel_status');
IPython.save_widget = new IPython.SaveWidget('span#save_widget');
IPython.menubar = new IPython.MenuBar('#menubar')
IPython.toolbar = new IPython.ToolBar('#toolbar')
IPython.kernel_status_widget.status_idle();
IPython.notification_widget = new IPython.NotificationWidget('#notification')
IPython.layout_manager.do_resize();
@ -111,7 +110,11 @@ $(document).ready(function () {
$('div#main_app').css('display','block');
IPython.layout_manager.do_resize();
IPython.notebook.load_notebook();
$([IPython.events]).on('notebook_loaded.Notebook', function () {
IPython.layout_manager.do_resize();
IPython.save_widget.update_url();
})
IPython.notebook.load_notebook($('body').data('notebookId'));
});

View File

@ -0,0 +1,102 @@
//----------------------------------------------------------------------------
// Copyright (C) 2008-2011 The IPython Development Team
//
// Distributed under the terms of the BSD License. The full license is in
// the file COPYING, distributed as part of this software.
//----------------------------------------------------------------------------
//============================================================================
// Notification widget
//============================================================================
var IPython = (function (IPython) {
var utils = IPython.utils;
var NotificationWidget = function (selector) {
this.selector = selector;
this.timeout = null;
this.busy = false;
if (this.selector !== undefined) {
this.element = $(selector);
this.style();
this.bind_events();
}
};
NotificationWidget.prototype.style = function () {
this.element.addClass('ui-widget ui-widget-content ui-corner-all');
this.element.addClass('border-box-sizing');
};
NotificationWidget.prototype.bind_events = function () {
var that = this;
// Kernel events
$([IPython.events]).on('status_idle.Kernel',function () {
IPython.save_widget.update_document_title();
if (that.get_message() === 'Kernel busy') {
that.element.fadeOut(100, function () {
that.element.html('');
});
};
});
$([IPython.events]).on('status_busy.Kernel',function () {
window.document.title='(Busy) '+window.document.title;
that.set_message("Kernel busy");
});
$([IPython.events]).on('status_restarting.Kernel',function () {
that.set_message("Restarting kernel",500);
});
$([IPython.events]).on('status_interrupting.Kernel',function () {
that.set_message("Interrupting kernel",500);
});
// Notebook events
$([IPython.events]).on('notebook_loading.Notebook', function () {
that.set_message("Loading notebook",500);
});
$([IPython.events]).on('notebook_loaded.Notebook', function () {
that.set_message("Notebook loaded",500);
});
$([IPython.events]).on('notebook_saving.Notebook', function () {
that.set_message("Saving notebook",500);
});
$([IPython.events]).on('notebook_saved.Notebook', function () {
that.set_message("Notebook saved",500);
});
$([IPython.events]).on('notebook_save_failed.Notebook', function () {
that.set_message("Notebook save failed",500);
});
};
NotificationWidget.prototype.set_message = function (msg, timeout) {
var that = this;
this.element.html(msg);
this.element.fadeIn(100);
if (this.timeout !== null) {
clearTimeout(this.timeout);
this.timeout = null;
};
if (timeout !== undefined) {
this.timeout = setTimeout(function () {
that.element.fadeOut(100, function () {that.element.html('');});
that.timeout = null;
}, timeout)
};
};
NotificationWidget.prototype.get_message = function () {
return this.element.html();
};
IPython.NotificationWidget = NotificationWidget;
return IPython;
}(IPython));

View File

@ -15,8 +15,6 @@ var IPython = (function (IPython) {
var SaveWidget = function (selector) {
this.selector = selector;
this.notebook_name_blacklist_re = /[\/\\]/;
this.last_saved_name = '';
if (this.selector !== undefined) {
this.element = $(selector);
this.style();
@ -43,11 +41,19 @@ var IPython = (function (IPython) {
}, function () {
$(this).removeClass("ui-state-hover");
});
};
SaveWidget.prototype.save_notebook = function () {
IPython.notebook.save_notebook();
$([IPython.events]).on('notebook_loaded.Notebook', function () {
that.set_last_saved();
that.update_notebook_name();
that.update_document_title();
});
$([IPython.events]).on('notebook_saved.Notebook', function () {
that.set_last_saved();
that.update_notebook_name();
that.update_document_title();
});
$([IPython.events]).on('notebook_save_failed.Notebook', function () {
that.set_save_status('');
});
};
@ -61,7 +67,7 @@ var IPython = (function (IPython) {
dialog.append(
$('<input/>').attr('type','text').attr('size','25')
.addClass('ui-widget ui-widget-content')
.attr('value',that.get_notebook_name())
.attr('value',IPython.notebook.get_notebook_name())
);
// $(document).append(dialog);
dialog.dialog({
@ -73,15 +79,15 @@ var IPython = (function (IPython) {
buttons : {
"OK": function () {
var new_name = $(this).find('input').attr('value');
if (!that.test_notebook_name(new_name)) {
if (!IPython.notebook.test_notebook_name(new_name)) {
$(this).find('h3').html(
"Invalid notebook name. Notebook names must "+
"have 1 or more characters and can contain any characters " +
"except / and \\. Please enter a new notebook name:"
);
} else {
that.set_notebook_name(new_name);
that.save_notebook();
IPython.notebook.set_notebook_name(new_name);
IPython.notebook.save_notebook();
$(this).dialog('close');
}
},
@ -92,82 +98,36 @@ var IPython = (function (IPython) {
});
}
SaveWidget.prototype.notebook_saved = function () {
this.set_document_title();
this.last_saved_name = this.get_notebook_name();
};
SaveWidget.prototype.get_notebook_name = function () {
return this.element.find('span#notebook_name').html();
};
SaveWidget.prototype.set_notebook_name = function (nbname) {
SaveWidget.prototype.update_notebook_name = function () {
var nbname = IPython.notebook.get_notebook_name();
this.element.find('span#notebook_name').html(nbname);
this.set_document_title();
this.last_saved_name = nbname;
};
SaveWidget.prototype.set_document_title = function () {
nbname = this.get_notebook_name();
SaveWidget.prototype.update_document_title = function () {
var nbname = IPython.notebook.get_notebook_name();
document.title = nbname;
};
SaveWidget.prototype.get_notebook_id = function () {
return $('body').data('notebookId');
};
SaveWidget.prototype.update_url = function () {
var notebook_id = this.get_notebook_id();
if (notebook_id !== '') {
var notebook_id = IPython.notebook.get_notebook_id();
if (notebook_id !== null) {
var new_url = $('body').data('baseProjectUrl') + notebook_id;
window.history.replaceState({}, '', new_url);
};
};
SaveWidget.prototype.test_notebook_name = function (nbname) {
nbname = nbname || '';
if (this.notebook_name_blacklist_re.test(nbname) == false && nbname.length>0) {
return true;
} else {
return false;
};
};
SaveWidget.prototype.set_save_status = function (msg) {
this.element.find('span#save_status').html(msg);
}
SaveWidget.prototype.set_last_saved = function () {
var d = new Date();
$('#save_status').html('Last saved: '+d.format('mmm dd h:MM TT'));
};
SaveWidget.prototype.reset_status = function () {
this.element.find('span#save_status').html('');
};
SaveWidget.prototype.status_last_saved = function () {
this.set_last_saved();
};
SaveWidget.prototype.status_saving = function () {
this.element.find('span#save_status').html('Saving...');
};
SaveWidget.prototype.status_save_failed = function () {
this.element.find('span#save_status').html('Save failed');
};
SaveWidget.prototype.status_loading = function () {
this.element.find('span#save_status').html('Loading...');
this.set_save_status('Last saved: '+d.format('mmm dd h:MM TT'));
};

View File

@ -72,8 +72,9 @@ var IPython = (function (IPython) {
ToolBar.prototype.bind_events = function () {
var that = this;
this.element.find('#save_b').click(function () {
IPython.save_widget.save_notebook();
IPython.notebook.save_notebook();
});
this.element.find('#cut_b').click(function () {
IPython.notebook.cut_cell();
@ -124,12 +125,13 @@ var IPython = (function (IPython) {
IPython.notebook.to_heading(undefined, 6);
};
});
};
ToolBar.prototype.set_cell_type = function (cell_type) {
this.element.find('#cell_type').val(cell_type);
$([IPython.events]).on('selected_cell_type_changed', function (event, data) {
if (data.cell_type === 'heading') {
that.element.find('#cell_type').val(data.cell_type+data.level);
} else {
that.element.find('#cell_type').val(data.cell_type);
}
});
};

View File

@ -54,10 +54,9 @@
<button id="login">Login</button>
{% end %}
</span>
<span id="kernel_status">Idle</span>
</div>
<div id="menubar_container">
<div id="menubar">
<ul id="menus">
<li><a href="#">File</a>
@ -153,6 +152,9 @@
</ul>
</div>
<div id="notification"></div>
</div>
<div id="toolbar">
@ -220,12 +222,12 @@
<script src="{{ static_url("dateformat/date.format.js") }}" charset="utf-8"></script>
<script src="{{ static_url("js/namespace.js") }}" type="text/javascript" charset="utf-8"></script>
<script src="{{ static_url("js/events.js") }}" type="text/javascript" charset="utf-8"></script>
<script src="{{ static_url("js/utils.js") }}" type="text/javascript" charset="utf-8"></script>
<script src="{{ static_url("js/cell.js") }}" type="text/javascript" charset="utf-8"></script>
<script src="{{ static_url("js/codecell.js") }}" type="text/javascript" charset="utf-8"></script>
<script src="{{ static_url("js/textcell.js") }}" type="text/javascript" charset="utf-8"></script>
<script src="{{ static_url("js/kernel.js") }}" type="text/javascript" charset="utf-8"></script>
<script src="{{ static_url("js/kernelstatus.js") }}" type="text/javascript" charset="utf-8"></script>
<script src="{{ static_url("js/layout.js") }}" type="text/javascript" charset="utf-8"></script>
<script src="{{ static_url("js/savewidget.js") }}" type="text/javascript" charset="utf-8"></script>
<script src="{{ static_url("js/quickhelp.js") }}" type="text/javascript" charset="utf-8"></script>
@ -234,6 +236,7 @@
<script src="{{ static_url("js/menubar.js") }}" type="text/javascript" charset="utf-8"></script>
<script src="{{ static_url("js/toolbar.js") }}" type="text/javascript" charset="utf-8"></script>
<script src="{{ static_url("js/notebook.js") }}" type="text/javascript" charset="utf-8"></script>
<script src="{{ static_url("js/notificationwidget.js") }}" type="text/javascript" charset="utf-8"></script>
<script src="{{ static_url("js/notebookmain.js") }}" type="text/javascript" charset="utf-8"></script>
</body>