Browse Source

Add a responsive file uploader

- Specify HTML5
- Allow drag and drop for loading configurations
Scott Lahteine 9 years ago
parent
commit
ac7a4358d6

+ 1
- 1
Marlin/configurator/css/configurator.css View File

@@ -15,7 +15,7 @@ label, input, select, textarea { display: block; float: left; margin: 1px 0; }
15 15
 label.newline, textarea { clear: both; }
16 16
 label { width: 130px; height: 1em; padding: 10px 480px 10px 1em; margin-right: -470px; text-align: right; }
17 17
 input[type="text"], select, .jstepper { margin: 0.75em 0 0; }
18
-input[type="checkbox"], input[type="radio"] { margin: 1em 0 0; }
18
+input[type="checkbox"], input[type="radio"], input[type="file"] { margin: 1em 0 0; }
19 19
 #config_form { display: block; background: #DDD; padding: 20px; color: #000; }
20 20
 /*#config_text, #config_adv_text { font-family: "Andale mono", monospace; clear: both; }*/
21 21
 #config_text, #config_adv_text { height: 25em; overflow: auto; background-color: #FFF; color: #888; padding: 10px; }

+ 8
- 5
Marlin/configurator/index.html View File

@@ -1,15 +1,18 @@
1
+<!DOCTYPE html>
1 2
 <html>
2 3
   <head>
3
-    <title>Marlin Configurator</title>
4 4
     <meta charset="UTF-8">
5
+    <title>Marlin Configurator</title>
5 6
     <script type="text/javascript" src="https://code.jquery.com/jquery-2.1.3.min.js"></script>
7
+    <script src="js/binarystring.js"></script>
8
+    <script src="js/binaryfileuploader.js"></script>
6 9
     <script src="js/configurator.js"></script>
7 10
     <script src="js/jcanvas.js"></script>
8 11
     <script src="js/jstepper.js"></script>
9 12
     <link rel="stylesheet" href="css/configurator.css" type="text/css" media="all" />
10 13
   </head>
11 14
   <body>
12
-    <div id="main">
15
+    <section id="main">
13 16
       <h1>Marlin Configurator 0.1a</h1>
14 17
       <p>Enter values in the form, get a Marlin configuration.<br/>Will include a drop-down of known configurations.</p>
15 18
       <ul id="help">
@@ -18,9 +21,9 @@
18 21
 
19 22
       <form id="config_form">
20 23
 
21
-        <!-- label>Serial Port:</label><input name="SERIAL_PORT" type="text" size="4" maxlength="2" value="99" / -->
24
+        <label>Drop Files Here:</label><input type="file" id="file-upload" />
22 25
 
23
-        <label>Serial Port:</label><select name="SERIAL_PORT"></select><div id="serial_stepper"></div>
26
+        <label class="newline">Serial Port:</label><select name="SERIAL_PORT"></select><div id="serial_stepper"></div>
24 27
 
25 28
         <label>Baud Rate:</label><select name="BAUDRATE"></select>
26 29
 
@@ -52,6 +55,6 @@
52 55
 
53 56
         <br class="clear" />
54 57
       </form>
55
-    </div>
58
+    <section>
56 59
   </body>
57 60
 </html>

+ 79
- 0
Marlin/configurator/js/binaryfileuploader.js View File

@@ -0,0 +1,79 @@
1
+function BinaryFileUploader(o) {
2
+	this.options = null;
3
+
4
+
5
+	this._defaultOptions = {
6
+		element: null, // HTML file element
7
+		onFileLoad: function(file) {
8
+			console.log(file.toString());
9
+		}
10
+	};
11
+
12
+
13
+	this._init = function(o) {
14
+		if (!this.hasFileUploaderSupport()) return;
15
+
16
+		this._verifyDependencies();
17
+
18
+		this.options = this._mergeObjects(this._defaultOptions, o);
19
+		this._verifyOptions();
20
+
21
+		this.addFileChangeListener();
22
+	}
23
+
24
+
25
+	this.hasFileUploaderSupport = function() {
26
+		return !!(window.File && window.FileReader && window.FileList && window.Blob);
27
+	}
28
+
29
+	this.addFileChangeListener = function() {
30
+		this.options.element.addEventListener(
31
+			'change',
32
+			this._bind(this, this.onFileChange)
33
+		);
34
+	}
35
+
36
+	this.onFileChange = function(e) {
37
+		// TODO accept multiple files
38
+		var file = e.target.files[0],
39
+		    reader = new FileReader();
40
+
41
+		reader.onload = this._bind(this, this.onFileLoad);
42
+		reader.readAsBinaryString(file);
43
+	}
44
+
45
+	this.onFileLoad = function(e) {
46
+		var content = e.target.result,
47
+		    string  = new BinaryString(content);
48
+		this.options.onFileLoad(string);
49
+	}
50
+
51
+
52
+	this._mergeObjects = function(starting, override) {
53
+		var merged = starting;
54
+		for (key in override) merged[key] = override[key];
55
+
56
+		return merged;
57
+	}
58
+
59
+	this._verifyOptions = function() {
60
+		if (!(this.options.element && this.options.element.type && this.options.element.type === 'file')) {
61
+			throw 'Invalid element param in options. Must be a file upload DOM element';
62
+		}
63
+
64
+		if (typeof this.options.onFileLoad !== 'function') {
65
+			throw 'Invalid onFileLoad param in options. Must be a function';
66
+		}
67
+	}
68
+
69
+	this._verifyDependencies = function() {
70
+		if (!window.BinaryString) throw 'BinaryString is missing. Check that you\'ve correctly included it';
71
+	}
72
+
73
+	// helper function for binding methods to objects
74
+	this._bind = function(object, method) {
75
+		return function() {return method.apply(object, arguments);};
76
+	}
77
+
78
+	this._init(o);
79
+}

+ 168
- 0
Marlin/configurator/js/binarystring.js View File

@@ -0,0 +1,168 @@
1
+function BinaryString(source) {
2
+	this._source = null;
3
+	this._bytes  = [];
4
+	this._pos    = 0;
5
+	this._length = 0;
6
+
7
+	this._init = function(source) {
8
+		this._source = source;
9
+		this._bytes  = this._stringToBytes(this._source);
10
+		this._length = this._bytes.length;
11
+	}
12
+
13
+	this.current = function() {return this._pos;}
14
+
15
+	this.rewind  = function() {return this.jump(0);}
16
+	this.end     = function() {return this.jump(this.length()  - 1);}
17
+	this.next    = function() {return this.jump(this.current() + 1);}
18
+	this.prev    = function() {return this.jump(this.current() - 1);}
19
+
20
+	this.jump = function(pos) {
21
+		if (pos < 0 || pos >= this.length()) return false;
22
+
23
+		this._pos = pos;
24
+		return true;
25
+	}
26
+
27
+	this.readByte = function(pos) {
28
+		pos = (typeof pos == 'number') ? pos : this.current();
29
+		return this.readBytes(1, pos)[0];
30
+	}
31
+
32
+	this.readBytes = function(length, pos) {
33
+		length = length || 1;
34
+		pos    = (typeof pos == 'number') ? pos : this.current();
35
+
36
+		if (pos          >  this.length() ||
37
+		    pos          <  0             ||
38
+		    length       <= 0             ||
39
+		    pos + length >  this.length() ||
40
+		    pos + length <  0
41
+		) {
42
+			return false;
43
+		}
44
+
45
+		var bytes = [];
46
+		
47
+		for (var i = pos; i < pos + length; i++) {
48
+			bytes.push(this._bytes[i]);
49
+		}
50
+
51
+		return bytes;
52
+	}
53
+
54
+	this.length = function() {return this._length;}
55
+
56
+	this.toString = function() {
57
+		var string = '',
58
+		    length = this.length();
59
+
60
+		for (var i = 0; i < length; i++) {
61
+			string += String.fromCharCode(this.readByte(i));
62
+		}
63
+
64
+		return string;
65
+	}
66
+
67
+	this.toUtf8 = function() {
68
+		var inc    = 0,
69
+		    string = '',
70
+		    length = this.length();
71
+
72
+		// determine if first 3 characters are the BOM
73
+		// then skip them in output if so
74
+		if (length >= 3               &&
75
+		    this.readByte(0) === 0xEF &&
76
+		    this.readByte(1) === 0xBB &&
77
+		    this.readByte(2) === 0xBF
78
+		) {
79
+			inc = 3;
80
+		}
81
+
82
+		for (; inc < length; inc++) {
83
+			var byte1 = this.readByte(inc),
84
+			    byte2 = 0,
85
+			    byte3 = 0,
86
+			    byte4 = 0,
87
+			    code1 = 0,
88
+			    code2 = 0,
89
+			    point = 0;
90
+
91
+			switch (true) {
92
+				// single byte character; same as ascii
93
+				case (byte1 < 0x80):
94
+					code1 = byte1;
95
+					break;
96
+
97
+				// 2 byte character
98
+				case (byte1 >= 0xC2 && byte1 < 0xE0):
99
+					byte2 = this.readByte(++inc);
100
+
101
+					code1 = ((byte1 & 0x1F) << 6) +
102
+					         (byte2 & 0x3F);
103
+					break;
104
+
105
+				// 3 byte character
106
+				case (byte1 >= 0xE0 && byte1 < 0xF0):
107
+					byte2 = this.readByte(++inc);
108
+					byte3 = this.readByte(++inc);
109
+
110
+					code1 = ((byte1 & 0xFF) << 12) +
111
+					        ((byte2 & 0x3F) <<  6) +
112
+					         (byte3 & 0x3F);
113
+					break;
114
+
115
+				// 4 byte character
116
+				case (byte1 >= 0xF0 && byte1 < 0xF5):
117
+					byte2 = this.readByte(++inc);
118
+					byte3 = this.readByte(++inc);
119
+					byte4 = this.readByte(++inc);
120
+
121
+					point = ((byte1 & 0x07) << 18) +
122
+					        ((byte2 & 0x3F) << 12) +
123
+					        ((byte3 & 0x3F) <<  6) +
124
+					         (byte4 & 0x3F)
125
+					point -= 0x10000;
126
+
127
+					code1 = (point >> 10)    + 0xD800;
128
+					code2 = (point &  0x3FF) + 0xDC00;
129
+					break;
130
+
131
+				default:
132
+					throw 'Invalid byte ' + this._byteToString(byte1) + ' whilst converting to UTF-8';
133
+					break;
134
+			}
135
+
136
+			string += (code2) ? String.fromCharCode(code1, code2)
137
+			                  : String.fromCharCode(code1);
138
+		}
139
+
140
+		return string;
141
+	}
142
+
143
+	this.toArray  = function() {return this.readBytes(this.length() - 1, 0);} 
144
+
145
+
146
+	this._stringToBytes = function(str) {
147
+		var bytes = [],
148
+		    chr   = 0;
149
+
150
+		for (var i = 0; i < str.length; i++) {
151
+			chr = str.charCodeAt(i);
152
+			bytes.push(chr & 0xFF);
153
+		}
154
+
155
+		return bytes;
156
+	}
157
+
158
+	this._byteToString = function(byte) {
159
+		var asString = byte.toString(16).toUpperCase();
160
+		while (asString.length < 2) {
161
+			asString = '0' + asString;
162
+		}
163
+
164
+		return '0x' + asString;
165
+	}
166
+
167
+	this._init(source);
168
+}

+ 103
- 25
Marlin/configurator/js/configurator.js View File

@@ -55,6 +55,9 @@ var configuratorApp = (function(){
55 55
   // private variables and functions go here
56 56
   var self,
57 57
       pi2 = Math.PI * 2,
58
+      boards_file = 'boards.h',
59
+      config_file = 'Configuration.h',
60
+      config_adv_file = 'Configuration_adv.h',
58 61
       $config = $('#config_text'),
59 62
       $config_adv = $('#config_adv_text'),
60 63
       boards_list = {},
@@ -67,37 +70,106 @@ var configuratorApp = (function(){
67 70
     init: function() {
68 71
       self = this; // a 'this' for use when 'this' is something else
69 72
 
73
+      // Make a droppable file uploader
74
+      var $uploader = $('#file-upload');
75
+      var fileUploader = new BinaryFileUploader({
76
+        element:    $uploader[0],
77
+        onFileLoad: function(file) { console.log(this); self.handleFileLoad(file, $uploader); }
78
+      });
79
+
80
+      if (!fileUploader.hasFileUploaderSupport()) alert('Your browser doesn\'t support the file reading API');
81
+
70 82
       // Read boards.h
71 83
       boards_list = {};
72
-      $.get(marlin_config + "/boards.h", function(txt) {
73
-        // Get all the boards and save them into an object
74
-        var r, findDef = new RegExp('[ \\t]*#define[ \\t]+(BOARD_[^ \\t]+)[ \\t]+(\\d+)[ \\t]*(//[ \\t]*)?(.+)?', 'gm');
75
-        while((r = findDef.exec(txt)) !== null) {
76
-          boards_list[r[1]] = r[2].prePad(3, '  ') + " — " + r[4].replace(/\).*/, ')');
77
-        }
84
+
85
+      var errFunc = function(jqXHR, textStatus, errorThrown) {
86
+                      alert('Failed to load '+this.url+'. Try the file field.');
87
+                    };
88
+
89
+      $.ajax({
90
+        url: marlin_config+'/'+boards_file,
91
+        type: 'GET',
92
+        async: false,
93
+        cache: false,
94
+        success: function(txt) {
95
+          // Get all the boards and save them into an object
96
+          self.initBoardsFromText(txt);
97
+        },
98
+        error: errFunc
78 99
       });
79 100
 
80 101
       // Read Configuration.h
81
-      $.get(marlin_config+"/Configuration.h", function(txt) {
82
-        // File contents into the textarea
83
-        $config.text(txt);
84
-        // Get all the thermistors and save them into an object
85
-        var r, s, findDef = new RegExp('(//.*\n)+\\s+(#define[ \\t]+TEMP_SENSOR_0)', 'g');
86
-        r = findDef.exec(txt);
87
-        findDef = new RegExp('^//[ \\t]*([-\\d]+)[ \\t]+is[ \\t]+(.*)[ \\t]*$', 'gm');
88
-        while((s = findDef.exec(r[0])) !== null) {
89
-          therms_list[s[1]] = s[1].prePad(4, '  ') + " — " + s[2];
90
-        }
102
+      $.ajax({
103
+        url: marlin_config+'/'+config_file,
104
+        type: 'GET',
105
+        async: false,
106
+        cache: false,
107
+        success: function(txt) {
108
+          // File contents into the textarea
109
+          $config.text(txt);
110
+          self.initThermistorsFromText(txt);
111
+        },
112
+        error: errFunc
91 113
       });
92 114
 
93 115
       // Read Configuration.h
94
-      $.get(marlin_config+"/Configuration_adv.h", function(txt) {
95
-        $config_adv.text(txt);
96
-        self.setupConfigForm();
116
+      $.ajax({
117
+        url: marlin_config+'/'+config_adv_file,
118
+        type: 'GET',
119
+        async: false,
120
+        cache: false,
121
+        success: function(txt) {
122
+          // File contents into the textarea
123
+          $config_adv.text(txt);
124
+          self.setupConfigForm();
125
+        },
126
+        error: errFunc
97 127
       });
98 128
 
99 129
     },
100 130
 
131
+    initBoardsFromText: function(txt) {
132
+      boards_list = {};
133
+      var r, findDef = new RegExp('[ \\t]*#define[ \\t]+(BOARD_[^ \\t]+)[ \\t]+(\\d+)[ \\t]*(//[ \\t]*)?(.+)?', 'gm');
134
+      while((r = findDef.exec(txt)) !== null) {
135
+        boards_list[r[1]] = r[2].prePad(3, '  ') + " — " + r[4].replace(/\).*/, ')');
136
+      }
137
+    },
138
+
139
+    initThermistorsFromText: function(txt) {
140
+      // Get all the thermistors and save them into an object
141
+      var r, s, findDef = new RegExp('(//.*\n)+\\s+(#define[ \\t]+TEMP_SENSOR_0)', 'g');
142
+      r = findDef.exec(txt);
143
+      findDef = new RegExp('^//[ \\t]*([-\\d]+)[ \\t]+is[ \\t]+(.*)[ \\t]*$', 'gm');
144
+      while((s = findDef.exec(r[0])) !== null) {
145
+        therms_list[s[1]] = s[1].prePad(4, '  ') + " — " + s[2];
146
+      }
147
+    },
148
+
149
+    handleFileLoad: function(file, $uploader) {
150
+      file += '';
151
+      var filename = $uploader.val().replace(/.*[\/\\](.*)$/, '$1');
152
+      switch(filename) {
153
+        case config_file:
154
+          $config.text(file);
155
+          this.initThermistorsFromText(file);
156
+          this.refreshConfigForm();
157
+          break;
158
+        case config_adv_file:
159
+          $config_adv.text(file);
160
+          this.refreshConfigForm();
161
+          break;
162
+        case boards_file:
163
+          this.initBoardsFromText(file);
164
+          $('#MOTHERBOARD').html('').addOptions(boards_list);
165
+          this.initField('MOTHERBOARD');
166
+          break;
167
+        default:
168
+          console.log("Can't parse "+filename);
169
+          break;
170
+      }
171
+    },
172
+
101 173
     setupConfigForm: function() {
102 174
       // Modify form fields and make the form responsive.
103 175
       // As values change on the form, we could update the
@@ -128,6 +200,17 @@ var configuratorApp = (function(){
128 200
         );
129 201
       });
130 202
 
203
+      $('#SERIAL_PORT').addOptions([0,1,2,3,4,5,6,7]);
204
+      $('#BAUDRATE').addOptions([2400,9600,19200,38400,57600,115200,250000]);
205
+      $('#MOTHERBOARD').addOptions(boards_list);
206
+      $('#EXTRUDERS').addOptions([1,2,3,4]);
207
+      $('#POWER_SUPPLY').addOptions({'1':'ATX','2':'Xbox 360'});
208
+
209
+      this.refreshConfigForm();
210
+    },
211
+
212
+    refreshConfigForm: function() {
213
+
131 214
       /**
132 215
        * For now I'm manually creating these references
133 216
        * but I should be able to parse Configuration.h
@@ -140,30 +223,25 @@ var configuratorApp = (function(){
140 223
        * Then we only need to specify exceptions to
141 224
        * standard behavior, (which is to add a text field)
142 225
        */
143
-      $('#SERIAL_PORT').addOptions([0,1,2,3,4,5,6,7]);
144 226
       this.initField('SERIAL_PORT');
145 227
 
146
-      $('#BAUDRATE').addOptions([2400,9600,19200,38400,57600,115200,250000]);
147 228
       this.initField('BAUDRATE');
148 229
 
149 230
       this.initField('BTENABLED');
150 231
 
151
-      $('#MOTHERBOARD').addOptions(boards_list);
152 232
       this.initField('MOTHERBOARD');
153 233
 
154 234
       this.initField('CUSTOM_MENDEL_NAME');
155 235
 
156 236
       this.initField('MACHINE_UUID');
157 237
 
158
-      $('#EXTRUDERS').addOptions([1,2,3,4]);
159 238
       this.initField('EXTRUDERS');
160 239
 
161
-      $('#POWER_SUPPLY').addOptions({'1':'ATX','2':'Xbox 360'});
162 240
       this.initField('POWER_SUPPLY');
163 241
 
164 242
       this.initField('PS_DEFAULT_OFF');
165 243
 
166
-      $('#TEMP_SENSOR_0, #TEMP_SENSOR_1, #TEMP_SENSOR_2, #TEMP_SENSOR_BED').addOptions(therms_list);
244
+      $('#TEMP_SENSOR_0, #TEMP_SENSOR_1, #TEMP_SENSOR_2, #TEMP_SENSOR_BED').html('').addOptions(therms_list);
167 245
       this.initField('TEMP_SENSOR_0');
168 246
       this.initField('TEMP_SENSOR_1');
169 247
       this.initField('TEMP_SENSOR_2');

Loading…
Cancel
Save