##// END OF EJS Templates
fix regular checkpoint updates in notebook...
Min RK -
Show More
@@ -1,260 +1,225 b''
1 // Copyright (c) IPython Development Team.
1 // Copyright (c) IPython Development Team.
2 // Distributed under the terms of the Modified BSD License.
2 // Distributed under the terms of the Modified BSD License.
3
3
4 define([
4 define([
5 'base/js/namespace',
5 'base/js/namespace',
6 'jquery',
6 'jquery',
7 'base/js/utils',
7 'base/js/utils',
8 'base/js/dialog',
8 'base/js/dialog',
9 'base/js/keyboard',
9 'base/js/keyboard',
10 'moment',
10 'moment',
11 ], function(IPython, $, utils, dialog, keyboard, moment) {
11 ], function(IPython, $, utils, dialog, keyboard, moment) {
12 "use strict";
12 "use strict";
13
13
14 var SaveWidget = function (selector, options) {
14 var SaveWidget = function (selector, options) {
15 /**
15 /**
16 * TODO: Remove circular ref.
16 * TODO: Remove circular ref.
17 */
17 */
18 this.notebook = undefined;
18 this.notebook = undefined;
19 this.selector = selector;
19 this.selector = selector;
20 this.events = options.events;
20 this.events = options.events;
21 this._checkpoint_date = undefined;
21 this._checkpoint_date = undefined;
22 this.keyboard_manager = options.keyboard_manager;
22 this.keyboard_manager = options.keyboard_manager;
23 if (this.selector !== undefined) {
23 if (this.selector !== undefined) {
24 this.element = $(selector);
24 this.element = $(selector);
25 this.bind_events();
25 this.bind_events();
26 }
26 }
27 };
27 };
28
28
29
29
30 SaveWidget.prototype.bind_events = function () {
30 SaveWidget.prototype.bind_events = function () {
31 var that = this;
31 var that = this;
32 this.element.find('span.filename').click(function () {
32 this.element.find('span.filename').click(function () {
33 that.rename_notebook({notebook: that.notebook});
33 that.rename_notebook({notebook: that.notebook});
34 });
34 });
35 this.events.on('notebook_loaded.Notebook', function () {
35 this.events.on('notebook_loaded.Notebook', function () {
36 that.update_notebook_name();
36 that.update_notebook_name();
37 that.update_document_title();
37 that.update_document_title();
38 });
38 });
39 this.events.on('notebook_saved.Notebook', function () {
39 this.events.on('notebook_saved.Notebook', function () {
40 that.update_notebook_name();
40 that.update_notebook_name();
41 that.update_document_title();
41 that.update_document_title();
42 });
42 });
43 this.events.on('notebook_renamed.Notebook', function () {
43 this.events.on('notebook_renamed.Notebook', function () {
44 that.update_notebook_name();
44 that.update_notebook_name();
45 that.update_document_title();
45 that.update_document_title();
46 that.update_address_bar();
46 that.update_address_bar();
47 });
47 });
48 this.events.on('notebook_save_failed.Notebook', function () {
48 this.events.on('notebook_save_failed.Notebook', function () {
49 that.set_save_status('Autosave Failed!');
49 that.set_save_status('Autosave Failed!');
50 });
50 });
51 this.events.on('notebook_read_only.Notebook', function () {
51 this.events.on('notebook_read_only.Notebook', function () {
52 that.set_save_status('(read only)');
52 that.set_save_status('(read only)');
53 // disable future set_save_status
53 // disable future set_save_status
54 that.set_save_status = function () {};
54 that.set_save_status = function () {};
55 });
55 });
56 this.events.on('checkpoints_listed.Notebook', function (event, data) {
56 this.events.on('checkpoints_listed.Notebook', function (event, data) {
57 that._set_last_checkpoint(data[0]);
57 that._set_last_checkpoint(data[0]);
58 });
58 });
59
59
60 this.events.on('checkpoint_created.Notebook', function (event, data) {
60 this.events.on('checkpoint_created.Notebook', function (event, data) {
61 that._set_last_checkpoint(data);
61 that._set_last_checkpoint(data);
62 });
62 });
63 this.events.on('set_dirty.Notebook', function (event, data) {
63 this.events.on('set_dirty.Notebook', function (event, data) {
64 that.set_autosaved(data.value);
64 that.set_autosaved(data.value);
65 });
65 });
66 };
66 };
67
67
68
68
69 SaveWidget.prototype.rename_notebook = function (options) {
69 SaveWidget.prototype.rename_notebook = function (options) {
70 options = options || {};
70 options = options || {};
71 var that = this;
71 var that = this;
72 var dialog_body = $('<div/>').append(
72 var dialog_body = $('<div/>').append(
73 $("<p/>").addClass("rename-message")
73 $("<p/>").addClass("rename-message")
74 .text('Enter a new notebook name:')
74 .text('Enter a new notebook name:')
75 ).append(
75 ).append(
76 $("<br/>")
76 $("<br/>")
77 ).append(
77 ).append(
78 $('<input/>').attr('type','text').attr('size','25').addClass('form-control')
78 $('<input/>').attr('type','text').attr('size','25').addClass('form-control')
79 .val(options.notebook.get_notebook_name())
79 .val(options.notebook.get_notebook_name())
80 );
80 );
81 var d = dialog.modal({
81 var d = dialog.modal({
82 title: "Rename Notebook",
82 title: "Rename Notebook",
83 body: dialog_body,
83 body: dialog_body,
84 notebook: options.notebook,
84 notebook: options.notebook,
85 keyboard_manager: this.keyboard_manager,
85 keyboard_manager: this.keyboard_manager,
86 buttons : {
86 buttons : {
87 "OK": {
87 "OK": {
88 class: "btn-primary",
88 class: "btn-primary",
89 click: function () {
89 click: function () {
90 var new_name = d.find('input').val();
90 var new_name = d.find('input').val();
91 if (!options.notebook.test_notebook_name(new_name)) {
91 if (!options.notebook.test_notebook_name(new_name)) {
92 d.find('.rename-message').text(
92 d.find('.rename-message').text(
93 "Invalid notebook name. Notebook names must "+
93 "Invalid notebook name. Notebook names must "+
94 "have 1 or more characters and can contain any characters " +
94 "have 1 or more characters and can contain any characters " +
95 "except :/\\. Please enter a new notebook name:"
95 "except :/\\. Please enter a new notebook name:"
96 );
96 );
97 return false;
97 return false;
98 } else {
98 } else {
99 d.find('.rename-message').text("Renaming...");
99 d.find('.rename-message').text("Renaming...");
100 d.find('input[type="text"]').prop('disabled', true);
100 d.find('input[type="text"]').prop('disabled', true);
101 that.notebook.rename(new_name).then(
101 that.notebook.rename(new_name).then(
102 function () {
102 function () {
103 d.modal('hide');
103 d.modal('hide');
104 }, function (error) {
104 }, function (error) {
105 d.find('.rename-message').text(error.message || 'Unknown error');
105 d.find('.rename-message').text(error.message || 'Unknown error');
106 d.find('input[type="text"]').prop('disabled', false).focus().select();
106 d.find('input[type="text"]').prop('disabled', false).focus().select();
107 }
107 }
108 );
108 );
109 return false;
109 return false;
110 }
110 }
111 }
111 }
112 },
112 },
113 "Cancel": {}
113 "Cancel": {}
114 },
114 },
115 open : function () {
115 open : function () {
116 /**
116 /**
117 * Upon ENTER, click the OK button.
117 * Upon ENTER, click the OK button.
118 */
118 */
119 d.find('input[type="text"]').keydown(function (event) {
119 d.find('input[type="text"]').keydown(function (event) {
120 if (event.which === keyboard.keycodes.enter) {
120 if (event.which === keyboard.keycodes.enter) {
121 d.find('.btn-primary').first().click();
121 d.find('.btn-primary').first().click();
122 return false;
122 return false;
123 }
123 }
124 });
124 });
125 d.find('input[type="text"]').focus().select();
125 d.find('input[type="text"]').focus().select();
126 }
126 }
127 });
127 });
128 };
128 };
129
129
130
130
131 SaveWidget.prototype.update_notebook_name = function () {
131 SaveWidget.prototype.update_notebook_name = function () {
132 var nbname = this.notebook.get_notebook_name();
132 var nbname = this.notebook.get_notebook_name();
133 this.element.find('span.filename').text(nbname);
133 this.element.find('span.filename').text(nbname);
134 };
134 };
135
135
136
136
137 SaveWidget.prototype.update_document_title = function () {
137 SaveWidget.prototype.update_document_title = function () {
138 var nbname = this.notebook.get_notebook_name();
138 var nbname = this.notebook.get_notebook_name();
139 document.title = nbname;
139 document.title = nbname;
140 };
140 };
141
141
142 SaveWidget.prototype.update_address_bar = function(){
142 SaveWidget.prototype.update_address_bar = function(){
143 var base_url = this.notebook.base_url;
143 var base_url = this.notebook.base_url;
144 var path = this.notebook.notebook_path;
144 var path = this.notebook.notebook_path;
145 var state = {path : path};
145 var state = {path : path};
146 window.history.replaceState(state, "", utils.url_join_encode(
146 window.history.replaceState(state, "", utils.url_join_encode(
147 base_url,
147 base_url,
148 "notebooks",
148 "notebooks",
149 path)
149 path)
150 );
150 );
151 };
151 };
152
152
153
153
154 SaveWidget.prototype.set_save_status = function (msg) {
154 SaveWidget.prototype.set_save_status = function (msg) {
155 this.element.find('span.autosave_status').text(msg);
155 this.element.find('span.autosave_status').text(msg);
156 };
156 };
157
157
158 SaveWidget.prototype._set_checkpoint_status = function (human_date, iso_date) {
158 SaveWidget.prototype._set_last_checkpoint = function (checkpoint) {
159 var el = this.element.find('span.checkpoint_status');
159 if (checkpoint) {
160 if(human_date){
160 this._checkpoint_date = new Date(checkpoint.last_modified);
161 el.text("Last Checkpoint: "+human_date).attr('title',iso_date);
162 } else {
161 } else {
163 el.text('').attr('title', 'no-checkpoint');
162 this._checkpoint_date = null;
164 }
165 };
166
167 // compute (roughly) the remaining time in millisecond until the next
168 // moment.js relative time update of the string, which by default
169 // happend at
170 // (a few seconds ago)
171 // - 45sec,
172 // (a minute ago)
173 // - 90sec,
174 // ( x minutes ago)
175 // - then every minutes until
176 // - 45 min,
177 // (an hour ago)
178 // - 1h45,
179 // (x hours ago )
180 // - then every hours
181 // - 22 hours ago
182 var _next_timeago_update = function(deltatime_ms){
183 var s = 1000;
184 var m = 60*s;
185 var h = 60*m;
186
187 var mtt = moment.relativeTimeThreshold;
188
189 if(deltatime_ms < mtt.s*s){
190 return mtt.s*s-deltatime_ms;
191 } else if (deltatime_ms < (mtt.s*s+m)) {
192 return (mtt.s*s+m)-deltatime_ms;
193 } else if (deltatime_ms < mtt.m*m){
194 return m;
195 } else if (deltatime_ms < (mtt.m*m+h)){
196 return (mtt.m*m+h)-deltatime_ms;
197 } else {
198 return h;
199 }
163 }
164 this._render_checkpoint();
200 };
165 };
201
166
202 SaveWidget.prototype._regularly_update_checkpoint_date = function(){
167 SaveWidget.prototype._render_checkpoint = function () {
203 if (!this._checkpoint_date) {
168 /** actually set the text in the element, from our _checkpoint value
204 this._set_checkpoint_status(null);
169
205 console.log('no checkpoint done');
170 called directly, and periodically in timeouts.
171 */
172 this._schedule_render_checkpoint();
173 var el = this.element.find('span.checkpoint_status');
174 if (!this._checkpoint_date) {
175 el.text('').attr('title', 'no checkpoint');
206 return;
176 return;
207 }
177 }
208 var chkd = moment(this._checkpoint_date);
178 var chkd = moment(this._checkpoint_date);
209 var longdate = chkd.format('llll');
179 var long_date = chkd.format('llll');
210
180 var human_date;
211 var that = this;
181 var tdelta = Math.ceil(new Date() - this._checkpoint_date);
212 var recall = function(t){
182 if (tdelta < utils.time.milliseconds.d){
213 /**
183 // less than 24 hours old, use relative date
214 * recall slightly later (1s) as long timeout in js might be imprecise,
184 human_date = chkd.fromNow();
215 * and you want to be call **after** the change of formatting should happend.
185 } else {
216 */
186 // otherwise show calendar
217 return setTimeout(
187 // <Today | yesterday|...> at hh,mm,ss
218 $.proxy(that._regularly_update_checkpoint_date, that),
188 human_date = chkd.calendar();
219 t + 1000
220 );
221 };
222 var tdelta = Math.ceil(new Date()-this._checkpoint_date);
223
224 // update regularly for the first 6hours and show
225 // <x time> ago
226 if(tdelta < 6*3600*1000){
227 recall(_next_timeago_update(tdelta));
228 this._set_checkpoint_status(chkd.fromNow(), longdate);
229 // otherwise update every hour and show
230 // <Today | yesterday|...> at hh,mm,ss
231 } else {
232 recall(1*3600*1000);
233 this._set_checkpoint_status(chkd.calendar(), longdate);
234 }
189 }
190 el.text('Last Checkpoint: ' + human_date).attr('title', long_date);
235 };
191 };
236
192
237 SaveWidget.prototype._set_last_checkpoint = function (checkpoint) {
193
238 if (checkpoint) {
194 SaveWidget.prototype._schedule_render_checkpoint = function () {
239 this._checkpoint_date = new Date(checkpoint.last_modified);
195 /** schedule the next update to relative date
240 } else {
196
241 this._checkpoint_date = null;
197 periodically updated, so short values like 'a few seconds ago' don't get stale.
198 */
199 if (!this._checkpoint_date) {
200 return;
242 }
201 }
243 this._regularly_update_checkpoint_date();
202 if ((this._checkpoint_timeout)) {
244
203 clearTimeout(this._checkpoint_timeout);
204 }
205 var dt = Math.ceil(new Date() - this._checkpoint_date);
206 this._checkpoint_timeout = setTimeout(
207 $.proxy(this._render_checkpoint, this),
208 utils.time.timeout_from_dt(dt)
209 );
245 };
210 };
246
211
247 SaveWidget.prototype.set_autosaved = function (dirty) {
212 SaveWidget.prototype.set_autosaved = function (dirty) {
248 if (dirty) {
213 if (dirty) {
249 this.set_save_status("(unsaved changes)");
214 this.set_save_status("(unsaved changes)");
250 } else {
215 } else {
251 this.set_save_status("(autosaved)");
216 this.set_save_status("(autosaved)");
252 }
217 }
253 };
218 };
254
219
255 // Backwards compatibility.
220 // Backwards compatibility.
256 IPython.SaveWidget = SaveWidget;
221 IPython.SaveWidget = SaveWidget;
257
222
258 return {'SaveWidget': SaveWidget};
223 return {'SaveWidget': SaveWidget};
259
224
260 });
225 });
General Comments 0
You need to be logged in to leave comments. Login now