Просмотр исходного кода

Highlight the edited line

- Add a span to the edited text line to provide a highlight
- Scroll and highlight for switch checkboxes also
- Clean up initialization
- More API documentation
- Smarter handling of asynchronous file loading during init
Scott Lahteine 10 лет назад
Родитель
Сommit
4bb72f9480

+ 19
- 3
Marlin/configurator/css/configurator.css Просмотреть файл

@@ -2,13 +2,19 @@
2 2
 /* Styles for Marlin Configurator */
3 3
 
4 4
 body { margin: 0; padding: 0; background: #56A; color: #FFC; font-family: sans-serif; }
5
+
5 6
 #main { max-width: 1000px; margin: 0 auto; }
6 7
 #main { padding: 0 4%; width: 92%; }
7 8
 #main { font-family: monospace; }
9
+
10
+#message { width: 80%; margin: 0 auto 0.25em; color: #FF0; text-align: center; }
11
+#message p { padding: 2px 0; }
12
+#message p.error, #message p.message { color: #F00; background: #FF4; font-weight: bold; border-radius: 0.8em; }
13
+#message p.message { color: #080; background: #CFC; }
14
+
8 15
 .info { color: #AAF; }
9 16
 .info span { color: #FFF; }
10 17
 .info span span { color: #000; font-weight: bold; }
11
-p { width: 80%; color: #FF0; }
12 18
 #help strong { color: #0DD; }
13 19
 img { display: none; }
14 20
 label, input, select, textarea { display: block; float: left; margin: 1px 0; }
@@ -24,8 +30,6 @@ input:disabled { color: #BBB; }
24 30
 .clear { clear: both; }
25 31
 h1, h2, h3, h4, h5, h6 { clear: both; }
26 32
 h2 { margin: 0; padding: 1em 0 0; }
27
-#serial_stepper { padding-top: 0.75em; display: block; float: left; }
28
-#SERIAL_PORT { display: none; }
29 33
 
30 34
 ul.tabs { display: inline; list-style: none; }
31 35
 ul.tabs li { display: inline; }
@@ -56,3 +60,15 @@ ul.tabs li a:active {
56 60
 
57 61
 fieldset { display: none; border: 1px solid #AAA; border-radius: 1em; }
58 62
 fieldset legend { display: none; }
63
+
64
+.hilightable span {
65
+	display: block;
66
+	float: left;
67
+	width: 100%;
68
+	height: 1.3em;
69
+	background: rgba(225,255,0,1);
70
+	margin: 0 -100% -1em 0;
71
+	}
72
+
73
+#serial_stepper { padding-top: 0.75em; display: block; float: left; }
74
+#SERIAL_PORT { display: none; }

+ 6
- 3
Marlin/configurator/index.html Просмотреть файл

@@ -14,7 +14,10 @@
14 14
   <body>
15 15
     <section id="main">
16 16
       <h1>Marlin Configurator 0.1a</h1>
17
-      <p>Enter values in the form, get a Marlin configuration. Will include a drop-down of known configurations.</p>
17
+
18
+      <div id="message">
19
+        <p class="info">Enter values in the form, get a Marlin configuration.<br/>Will include a drop-down of known configurations.</p>
20
+      </div>
18 21
 
19 22
       <div id="tabs"></div>
20 23
 
@@ -64,9 +67,9 @@
64 67
         </fieldset>
65 68
 
66 69
         <h2>Marlin/Configuration.h</h2>
67
-        <pre id="config_text" class="prettyprint linenums"></pre>
70
+        <pre id="config_text" class="hilightable"></pre>
68 71
         <h2>Marlin/Configuration_adv.h</h2>
69
-        <pre id="config_adv_text" class="prettyprint linenums"></pre>
72
+        <pre id="config_adv_text" class="hilightable"></pre>
70 73
 
71 74
         <br class="clear" />
72 75
       </form>

+ 154
- 108
Marlin/configurator/js/configurator.js Просмотреть файл

@@ -79,7 +79,7 @@ var configuratorApp = (function(){
79 79
       self = this; // a 'this' for use when 'this' is something else
80 80
 
81 81
       // Set up the form
82
-      this.setupConfigForm();
82
+      this.initConfigForm();
83 83
 
84 84
       // Make tabs for the fieldsets
85 85
       var $fset = $('#config_form fieldset');
@@ -105,78 +105,87 @@ var configuratorApp = (function(){
105 105
       $('<br>',{class:'clear'}).appendTo('#tabs');
106 106
       $tabs.find('a:first').trigger('click');
107 107
 
108
-      // Make a droppable file uploader
108
+      // Make a droppable file uploader, if possible
109 109
       var $uploader = $('#file-upload');
110 110
       var fileUploader = new BinaryFileUploader({
111 111
         element:    $uploader[0],
112 112
         onFileLoad: function(file) { self.handleFileLoad(file, $uploader); }
113 113
       });
114
-
115
-      if (!fileUploader.hasFileUploaderSupport()) alert('Your browser doesn\'t support the file reading API');
116
-
117
-      // Read boards.h
118
-      boards_list = {};
119
-
120
-      var errFunc = function(jqXHR, textStatus, errorThrown) {
121
-                      alert('Failed to load '+this.url+'. Try the file field.');
122
-                    };
123
-
124
-      $.ajax({
125
-        url: marlin_config+'/'+boards_file,
126
-        type: 'GET',
127
-        async: false,
128
-        cache: false,
129
-        success: function(txt) {
130
-          // Get all the boards and save them into an object
131
-          self.initBoardsFromText(txt);
132
-          has_boards = true;
133
-        },
134
-        error: errFunc
135
-      });
136
-
137
-      // Read Configuration.h
138
-      $.ajax({
139
-        url: marlin_config+'/'+config_file,
140
-        type: 'GET',
141
-        async: false,
142
-        cache: false,
143
-        success: function(txt) {
144
-          // File contents into the textarea
145
-          $config.text(txt);
146
-          // Get thermistor info too
147
-          self.initThermistorsFromText(txt);
148
-          has_config = true;
149
-        },
150
-        error: errFunc
151
-      });
152
-
153
-      // Read Configuration.h
154
-      $.ajax({
155
-        url: marlin_config+'/'+config_adv_file,
156
-        type: 'GET',
157
-        async: false,
158
-        cache: false,
159
-        success: function(txt) {
160
-          // File contents into the textarea
161
-          $config_adv.text(txt);
162
-          has_config_adv = true;
163
-          self.refreshConfigForm();
164
-        },
165
-        error: errFunc
114
+      if (!fileUploader.hasFileUploaderSupport())
115
+        this.setMessage("Your browser doesn't support the file reading API.", 'error');
116
+
117
+      // Read boards.h, Configuration.h, Configuration_adv.h
118
+      var ajax_count = 0, success_count = 0;
119
+      var loaded_items = {};
120
+      var config_files = [boards_file, config_file, config_adv_file];
121
+      $.each(config_files, function(i,fname){
122
+        self.log("Loading " + fname + "...", 3);
123
+        $.ajax({
124
+          url: marlin_config+'/'+fname,
125
+          type: 'GET',
126
+          async: true,
127
+          cache: false,
128
+          success: function(txt) {
129
+            self.log("Loaded " + fname + "...", 3);
130
+            loaded_items[fname] = function(){ self.fileLoaded(fname, txt); };
131
+            success_count++;
132
+          },
133
+          complete: function() {
134
+            ajax_count++;
135
+            if (ajax_count >= 3) {
136
+              $.each(config_files, function(i,fname){ if (typeof loaded_items[fname] != 'undefined') loaded_items[fname](); });
137
+              self.refreshConfigForm();
138
+              if (success_count < ajax_count)
139
+                self.setMessage('Unable to load configurations. Use the upload field instead.', 'error');
140
+            }
141
+          }
142
+        });
166 143
       });
144
+    },
167 145
 
146
+    setMessage: function(msg,type) {
147
+      if (msg) {
148
+        if (typeof type == 'undefined') type = 'message';
149
+        var $err = $('<p class="'+type+'">'+msg+'</p>'), err = $err[0];
150
+        $('#message').prepend($err);
151
+        var baseColor = $err.css('color').replace(/rgba?\(([^),]+,[^),]+,[^),]+).*/, 'rgba($1,');
152
+        var d = new Date();
153
+        err.startTime = d.getTime();
154
+        err.pulser = setInterval(function(){
155
+            d = new Date();
156
+            var pulse_time = (d.getTime() - err.startTime);
157
+            $err.css({color:baseColor+(0.5+Math.sin(pulse_time/200)*0.4)+')'});
158
+            if (pulse_time > 5000) {
159
+              clearInterval(err.pulser);
160
+              $err.remove();
161
+            }
162
+          }, 50);
163
+      }
164
+      else {
165
+        $('#message p.error, #message p.warning').each(function() {
166
+          if (typeof this.pulser != 'undefined' && this.pulser)
167
+            clearInterval(this.pulser);
168
+          $(this).remove();
169
+        });
170
+      }
168 171
     },
169 172
 
173
+    /**
174
+     * Init the boards array from a boards.h file
175
+     */
170 176
     initBoardsFromText: function(txt) {
171 177
       boards_list = {};
172 178
       var r, findDef = new RegExp('[ \\t]*#define[ \\t]+(BOARD_[\\w_]+)[ \\t]+(\\d+)[ \\t]*(//[ \\t]*)?(.+)?', 'gm');
173 179
       while((r = findDef.exec(txt)) !== null) {
174 180
         boards_list[r[1]] = r[2].prePad(3, '  ') + " — " + r[4].replace(/\).*/, ')');
175 181
       }
176
-      this.log("Loaded boards", 0);
177
-      this.log(boards_list, 0);
182
+      this.log("Loaded boards", 3); this.log(boards_list, 3);
183
+      has_boards = true;
178 184
     },
179 185
 
186
+    /**
187
+     * Init the thermistors array from the Configuration.h file
188
+     */
180 189
     initThermistorsFromText: function(txt) {
181 190
       // Get all the thermistors and save them into an object
182 191
       var r, s, findDef = new RegExp('(//.*\n)+\\s+(#define[ \\t]+TEMP_SENSOR_0)', 'g');
@@ -187,48 +196,67 @@ var configuratorApp = (function(){
187 196
       }
188 197
     },
189 198
 
190
-    handleFileLoad: function(file, $uploader) {
191
-      file += '';
199
+    /**
200
+     * Handle a file being dropped on the file field
201
+     */
202
+    handleFileLoad: function(txt, $uploader) {
203
+      txt += '';
192 204
       var filename = $uploader.val().replace(/.*[\/\\](.*)$/, '$1');
193 205
       switch(filename) {
194 206
         case boards_file:
195
-          this.initBoardsFromText(file);
196
-          has_boards = true;
207
+        case config_file:
208
+        case config_adv_file:
209
+          this.fileLoaded(filename, txt);
210
+          break;
211
+        default:
212
+          this.log("Can't parse "+filename, 1);
213
+          break;
214
+      }
215
+    },
216
+
217
+    fileLoaded: function(filename, txt) {
218
+      this.log("fileLoaded:"+filename,4);
219
+      switch(filename) {
220
+        case boards_file:
221
+          this.initBoardsFromText(txt);
197 222
           $('#MOTHERBOARD').html('').addOptions(boards_list);
198 223
           if (has_config) this.initField('MOTHERBOARD');
224
+          this.setMessage(boards_file+' loaded successfully.');
199 225
           break;
200 226
         case config_file:
201 227
           if (has_boards) {
202
-            $config.text(file);
203
-            has_config = true;
204
-            total_config_lines = file.replace(/[^\n]+/g, '').length;
205
-            this.initThermistorsFromText(file);
228
+            $config.text(txt);
229
+            total_config_lines = txt.split(/\r?\n|\r/).length;
230
+            this.initThermistorsFromText(txt);
206 231
             this.purgeDefineInfo(false);
207 232
             this.refreshConfigForm();
233
+            this.setMessage(config_file+' loaded successfully.');
234
+            has_config = true;
208 235
           }
209 236
           else {
210
-            alert("Upload a " + boards_file + " file first!");
237
+            this.setMessage("Upload a " + boards_file + " file first!", 'error');
211 238
           }
212 239
           break;
213 240
         case config_adv_file:
214 241
           if (has_config) {
215
-            $config_adv.text(file);
216
-            has_config_adv = true;
217
-            total_config_adv_lines = file.replace(/[^\n]+/g, '').length;
242
+            $config_adv.text(txt);
243
+            total_config_adv_lines = txt.split(/\r?\n|\r/).length;
218 244
             this.purgeDefineInfo(true);
219 245
             this.refreshConfigForm();
246
+            this.setMessage(config_adv_file+' loaded successfully.');
247
+            has_config_adv = true;
220 248
           }
221 249
           else {
222
-            alert("Upload a " + config_file + " file first!");
250
+            this.setMessage("Upload a " + config_file + " file first!", 'error');
223 251
           }
224 252
           break;
225
-        default:
226
-          this.log("Can't parse "+filename, 1);
227
-          break;
228 253
       }
229 254
     },
230 255
 
231
-    setupConfigForm: function() {
256
+    /**
257
+     * Add enhancements to the form
258
+     */
259
+    initConfigForm: function() {
232 260
       // Modify form fields and make the form responsive.
233 261
       // As values change on the form, we could update the
234 262
       // contents of text areas containing the configs, for
@@ -258,14 +286,16 @@ var configuratorApp = (function(){
258 286
         );
259 287
       });
260 288
 
289
+      // Add options to the popup menus
261 290
       $('#SERIAL_PORT').addOptions([0,1,2,3,4,5,6,7]);
262 291
       $('#BAUDRATE').addOptions([2400,9600,19200,38400,57600,115200,250000]);
263 292
       $('#EXTRUDERS').addOptions([1,2,3,4]);
264 293
       $('#POWER_SUPPLY').addOptions({'1':'ATX','2':'Xbox 360'});
265 294
 
295
+      // Replace the Serial popup menu with a stepper control
266 296
       $('#serial_stepper').jstepper({
267 297
         min: 0,
268
-        max: 7,
298
+        max: 3,
269 299
         val: $('#SERIAL_PORT').val(),
270 300
         arrowWidth: '18px',
271 301
         arrowHeight: '15px',
@@ -276,7 +306,6 @@ var configuratorApp = (function(){
276 306
         textStyle: {width:'1.5em',fontSize:'120%',textAlign:'center'},
277 307
         onChange: function(v) { $('#SERIAL_PORT').val(v).trigger('change'); }
278 308
       });
279
-
280 309
     },
281 310
 
282 311
     refreshConfigForm: function() {
@@ -322,30 +351,36 @@ var configuratorApp = (function(){
322 351
       this.initField('MAX_REDUNDANT_TEMP_SENSOR_DIFF');
323 352
 
324 353
       this.initField('TEMP_RESIDENCY_TIME');
354
+    },
325 355
 
326
-      // prettyPrint();
356
+    setTextAndHighlight: function($field, txt, name) {
357
+      var $elm = $('#'+name), elm = $elm[0], inf = elm.defineInfo;
358
+      if (inf == null) return;
327 359
     },
328 360
 
329 361
     /**
330
-     * initField - make a field responsive and get info
331
-     * about its configuration file define
362
+     * Make a field responsive and initialize its defineInfo
332 363
      */
333 364
     initField: function(name, adv) {
334
-      this.log("initField:"+name,3);
365
+      this.log("initField:"+name,4);
335 366
       var $elm = $('#'+name), elm = $elm[0];
336
-      if (elm.defineInfo === undefined) {
367
+      if (elm.defineInfo == null) {
337 368
         elm.defineInfo = this.getDefineInfo(name, adv);
338 369
         $elm.on($elm.attr('type') == 'text' ? 'input' : 'change', this.handleChange);
339 370
       }
340 371
       this.setFieldFromDefine(name);
341 372
     },
342 373
 
343
-    handleChange: function(e) {
344
-      self.updateDefineFromField(e.target.id);
345
-    },
374
+    /**
375
+     * Handle any value field being changed
376
+     */
377
+    handleChange: function() { self.updateDefineFromField(this.id); },
346 378
 
347
-    handleSwitch: function(e) {
348
-      var $elm = $(e.target), $prev = $elm.prev();
379
+    /**
380
+     * Handle a switch checkbox being changed
381
+     */
382
+    handleSwitch: function() {
383
+      var $elm = $(this), $prev = $elm.prev();
349 384
       var on = $elm.prop('checked') || false;
350 385
       $prev.attr('disabled', !on);
351 386
       self.setDefineEnabled($prev[0].id, on);
@@ -357,6 +392,7 @@ var configuratorApp = (function(){
357 392
     defineValue: function(name) {
358 393
       this.log('defineValue:'+name,4);
359 394
       var $elm = $('#'+name), elm = $elm[0], inf = elm.defineInfo;
395
+      if (inf == null) return 'n/a';
360 396
       var result = inf.regex.exec($(inf.field).text());
361 397
 
362 398
       this.log(result,2);
@@ -370,12 +406,13 @@ var configuratorApp = (function(){
370 406
     defineIsEnabled: function(name) {
371 407
       this.log('defineIsEnabled:'+name,4);
372 408
       var $elm = $('#'+name), elm = $elm[0], inf = elm.defineInfo;
409
+      if (inf == null) return false;
373 410
       var result = inf.regex.exec($(inf.field).text());
374 411
 
375 412
       this.log(result,2);
376 413
 
377 414
       var on = result !== null ? result[1].trim() != '//' : true;
378
-      this.log(name + ' = ' + on, 4);
415
+      this.log(name + ' = ' + on, 2);
379 416
 
380 417
       return on;
381 418
     },
@@ -385,23 +422,15 @@ var configuratorApp = (function(){
385 422
      */
386 423
     setDefineEnabled: function(name, val) {
387 424
       this.log('setDefineEnabled:'+name,4);
388
-
389
-      var $elm = $('#'+name), elm = $elm[0], inf = elm.defineInfo;
390
-      var $c = $(inf.field), txt = $c.text();
425
+      var $elm = $('#'+name), inf = $elm[0].defineInfo;
426
+      if (inf == null) return;
391 427
 
392 428
       var slash = val ? '' : '//';
393 429
       var newline = inf.line
394
-        .replace(/^([ \t]*)(\/\/)([ \t]*)/, '$1$3') // remove slashes
395
-        .replace(inf.pre+inf.define, inf.pre+slash+inf.define);     // add them back
396
-
397
-      txt = txt.replace(inf.line, newline);
398
-      inf.line = newline;
399
-      this.log(newline, 2);
400
-
401
-      $c.text(txt);
430
+        .replace(/^([ \t]*)(\/\/)([ \t]*)/, '$1$3')              // remove slashes
431
+        .replace(inf.pre+inf.define, inf.pre+slash+inf.define);  // add them back
402 432
 
403
-      // Scroll to reveal the define
404
-      this.scrollToDefine(name);
433
+      this.setDefineLine(name, newline);
405 434
     },
406 435
 
407 436
     /**
@@ -409,11 +438,8 @@ var configuratorApp = (function(){
409 438
      */
410 439
     updateDefineFromField: function(name) {
411 440
       this.log('updateDefineFromField:'+name,4);
412
-      var $elm = $('#'+name), elm = $elm[0], inf = elm.defineInfo;
413
-      var $c = $(inf.field), txt = $c.text();
414
-
415
-      // var result = inf.repl.exec(txt);
416
-      // this.log(result, 2);
441
+      var $elm = $('#'+name), inf = $elm[0].defineInfo;
442
+      if (inf == null) return;
417 443
 
418 444
       var isCheck = $elm.attr('type') == 'checkbox',
419 445
           val = isCheck ? $elm.prop('checked') : $elm.val();
@@ -446,18 +472,36 @@ var configuratorApp = (function(){
446 472
           break;
447 473
       }
448 474
 
449
-      txt = txt.replace(inf.line, newline);
475
+      this.setDefineLine(name, newline);
476
+    },
477
+
478
+    /**
479
+     * Set the define's line in the text to a new line,
480
+     *   then update, highlight, and scroll to the line
481
+     */
482
+    setDefineLine: function(name, newline) {
483
+      var $elm = $('#'+name), elm = $elm[0], inf = elm.defineInfo;
484
+      var $c = $(inf.field), txt = $c.text();
485
+
486
+      var hilite_token = '[HIGHLIGHTER-TOKEN]';
487
+
488
+      txt = txt.replace(inf.line, hilite_token + newline);
450 489
       inf.line = newline;
490
+
451 491
       this.log(newline, 2);
452 492
 
453
-      $c.text(txt);
493
+      // Convert txt into HTML before storing
494
+      var html = $('<div/>').text(txt).html().replace(hilite_token, '<span></span>');
495
+
496
+      // Set the final text including the highlighter
497
+      $c.html(html);
454 498
 
455 499
       // Scroll to reveal the define
456 500
       this.scrollToDefine(name);
457 501
     },
458 502
 
459 503
     /**
460
-     * Scroll the field to show a define
504
+     * Scroll a pre box to reveal a #define
461 505
      */
462 506
     scrollToDefine: function(name, always) {
463 507
       this.log('scrollToDefine:'+name,4);
@@ -474,7 +518,6 @@ var configuratorApp = (function(){
474 518
 
475 519
       if (always == true || Math.abs($c.prop('scrollTop') - textScrollY) > halfHeight)
476 520
         $c.animate({ scrollTop: textScrollY < 0 ? 0 : textScrollY });
477
-
478 521
     },
479 522
 
480 523
     /**
@@ -587,6 +630,9 @@ var configuratorApp = (function(){
587 630
       return null;
588 631
     },
589 632
 
633
+    /**
634
+     * Count the number of lines before a match, return -1 on fail
635
+     */
590 636
     getLineInText: function(line, txt) {
591 637
       var pos = txt.indexOf(line);
592 638
       return (pos < 0) ? pos : txt.substr(0, pos).replace(/[^\n]+/g, '').length;

Загрузка…
Отмена
Сохранить