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