Add a responsive file uploader
- Specify HTML5 - Allow drag and drop for loading configurations
This commit is contained in:
parent
09a385d9b0
commit
ac7a4358d6
5 changed files with 359 additions and 31 deletions
|
@ -15,7 +15,7 @@ label, input, select, textarea { display: block; float: left; margin: 1px 0; }
|
|||
label.newline, textarea { clear: both; }
|
||||
label { width: 130px; height: 1em; padding: 10px 480px 10px 1em; margin-right: -470px; text-align: right; }
|
||||
input[type="text"], select, .jstepper { margin: 0.75em 0 0; }
|
||||
input[type="checkbox"], input[type="radio"] { margin: 1em 0 0; }
|
||||
input[type="checkbox"], input[type="radio"], input[type="file"] { margin: 1em 0 0; }
|
||||
#config_form { display: block; background: #DDD; padding: 20px; color: #000; }
|
||||
/*#config_text, #config_adv_text { font-family: "Andale mono", monospace; clear: both; }*/
|
||||
#config_text, #config_adv_text { height: 25em; overflow: auto; background-color: #FFF; color: #888; padding: 10px; }
|
||||
|
|
|
@ -1,15 +1,18 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Marlin Configurator</title>
|
||||
<meta charset="UTF-8">
|
||||
<title>Marlin Configurator</title>
|
||||
<script type="text/javascript" src="https://code.jquery.com/jquery-2.1.3.min.js"></script>
|
||||
<script src="js/binarystring.js"></script>
|
||||
<script src="js/binaryfileuploader.js"></script>
|
||||
<script src="js/configurator.js"></script>
|
||||
<script src="js/jcanvas.js"></script>
|
||||
<script src="js/jstepper.js"></script>
|
||||
<link rel="stylesheet" href="css/configurator.css" type="text/css" media="all" />
|
||||
</head>
|
||||
<body>
|
||||
<div id="main">
|
||||
<section id="main">
|
||||
<h1>Marlin Configurator 0.1a</h1>
|
||||
<p>Enter values in the form, get a Marlin configuration.<br/>Will include a drop-down of known configurations.</p>
|
||||
<ul id="help">
|
||||
|
@ -18,9 +21,9 @@
|
|||
|
||||
<form id="config_form">
|
||||
|
||||
<!-- label>Serial Port:</label><input name="SERIAL_PORT" type="text" size="4" maxlength="2" value="99" / -->
|
||||
<label>Drop Files Here:</label><input type="file" id="file-upload" />
|
||||
|
||||
<label>Serial Port:</label><select name="SERIAL_PORT"></select><div id="serial_stepper"></div>
|
||||
<label class="newline">Serial Port:</label><select name="SERIAL_PORT"></select><div id="serial_stepper"></div>
|
||||
|
||||
<label>Baud Rate:</label><select name="BAUDRATE"></select>
|
||||
|
||||
|
@ -52,6 +55,6 @@
|
|||
|
||||
<br class="clear" />
|
||||
</form>
|
||||
</div>
|
||||
<section>
|
||||
</body>
|
||||
</html>
|
||||
|
|
79
Marlin/configurator/js/binaryfileuploader.js
Normal file
79
Marlin/configurator/js/binaryfileuploader.js
Normal file
|
@ -0,0 +1,79 @@
|
|||
function BinaryFileUploader(o) {
|
||||
this.options = null;
|
||||
|
||||
|
||||
this._defaultOptions = {
|
||||
element: null, // HTML file element
|
||||
onFileLoad: function(file) {
|
||||
console.log(file.toString());
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
this._init = function(o) {
|
||||
if (!this.hasFileUploaderSupport()) return;
|
||||
|
||||
this._verifyDependencies();
|
||||
|
||||
this.options = this._mergeObjects(this._defaultOptions, o);
|
||||
this._verifyOptions();
|
||||
|
||||
this.addFileChangeListener();
|
||||
}
|
||||
|
||||
|
||||
this.hasFileUploaderSupport = function() {
|
||||
return !!(window.File && window.FileReader && window.FileList && window.Blob);
|
||||
}
|
||||
|
||||
this.addFileChangeListener = function() {
|
||||
this.options.element.addEventListener(
|
||||
'change',
|
||||
this._bind(this, this.onFileChange)
|
||||
);
|
||||
}
|
||||
|
||||
this.onFileChange = function(e) {
|
||||
// TODO accept multiple files
|
||||
var file = e.target.files[0],
|
||||
reader = new FileReader();
|
||||
|
||||
reader.onload = this._bind(this, this.onFileLoad);
|
||||
reader.readAsBinaryString(file);
|
||||
}
|
||||
|
||||
this.onFileLoad = function(e) {
|
||||
var content = e.target.result,
|
||||
string = new BinaryString(content);
|
||||
this.options.onFileLoad(string);
|
||||
}
|
||||
|
||||
|
||||
this._mergeObjects = function(starting, override) {
|
||||
var merged = starting;
|
||||
for (key in override) merged[key] = override[key];
|
||||
|
||||
return merged;
|
||||
}
|
||||
|
||||
this._verifyOptions = function() {
|
||||
if (!(this.options.element && this.options.element.type && this.options.element.type === 'file')) {
|
||||
throw 'Invalid element param in options. Must be a file upload DOM element';
|
||||
}
|
||||
|
||||
if (typeof this.options.onFileLoad !== 'function') {
|
||||
throw 'Invalid onFileLoad param in options. Must be a function';
|
||||
}
|
||||
}
|
||||
|
||||
this._verifyDependencies = function() {
|
||||
if (!window.BinaryString) throw 'BinaryString is missing. Check that you\'ve correctly included it';
|
||||
}
|
||||
|
||||
// helper function for binding methods to objects
|
||||
this._bind = function(object, method) {
|
||||
return function() {return method.apply(object, arguments);};
|
||||
}
|
||||
|
||||
this._init(o);
|
||||
}
|
168
Marlin/configurator/js/binarystring.js
Normal file
168
Marlin/configurator/js/binarystring.js
Normal file
|
@ -0,0 +1,168 @@
|
|||
function BinaryString(source) {
|
||||
this._source = null;
|
||||
this._bytes = [];
|
||||
this._pos = 0;
|
||||
this._length = 0;
|
||||
|
||||
this._init = function(source) {
|
||||
this._source = source;
|
||||
this._bytes = this._stringToBytes(this._source);
|
||||
this._length = this._bytes.length;
|
||||
}
|
||||
|
||||
this.current = function() {return this._pos;}
|
||||
|
||||
this.rewind = function() {return this.jump(0);}
|
||||
this.end = function() {return this.jump(this.length() - 1);}
|
||||
this.next = function() {return this.jump(this.current() + 1);}
|
||||
this.prev = function() {return this.jump(this.current() - 1);}
|
||||
|
||||
this.jump = function(pos) {
|
||||
if (pos < 0 || pos >= this.length()) return false;
|
||||
|
||||
this._pos = pos;
|
||||
return true;
|
||||
}
|
||||
|
||||
this.readByte = function(pos) {
|
||||
pos = (typeof pos == 'number') ? pos : this.current();
|
||||
return this.readBytes(1, pos)[0];
|
||||
}
|
||||
|
||||
this.readBytes = function(length, pos) {
|
||||
length = length || 1;
|
||||
pos = (typeof pos == 'number') ? pos : this.current();
|
||||
|
||||
if (pos > this.length() ||
|
||||
pos < 0 ||
|
||||
length <= 0 ||
|
||||
pos + length > this.length() ||
|
||||
pos + length < 0
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
var bytes = [];
|
||||
|
||||
for (var i = pos; i < pos + length; i++) {
|
||||
bytes.push(this._bytes[i]);
|
||||
}
|
||||
|
||||
return bytes;
|
||||
}
|
||||
|
||||
this.length = function() {return this._length;}
|
||||
|
||||
this.toString = function() {
|
||||
var string = '',
|
||||
length = this.length();
|
||||
|
||||
for (var i = 0; i < length; i++) {
|
||||
string += String.fromCharCode(this.readByte(i));
|
||||
}
|
||||
|
||||
return string;
|
||||
}
|
||||
|
||||
this.toUtf8 = function() {
|
||||
var inc = 0,
|
||||
string = '',
|
||||
length = this.length();
|
||||
|
||||
// determine if first 3 characters are the BOM
|
||||
// then skip them in output if so
|
||||
if (length >= 3 &&
|
||||
this.readByte(0) === 0xEF &&
|
||||
this.readByte(1) === 0xBB &&
|
||||
this.readByte(2) === 0xBF
|
||||
) {
|
||||
inc = 3;
|
||||
}
|
||||
|
||||
for (; inc < length; inc++) {
|
||||
var byte1 = this.readByte(inc),
|
||||
byte2 = 0,
|
||||
byte3 = 0,
|
||||
byte4 = 0,
|
||||
code1 = 0,
|
||||
code2 = 0,
|
||||
point = 0;
|
||||
|
||||
switch (true) {
|
||||
// single byte character; same as ascii
|
||||
case (byte1 < 0x80):
|
||||
code1 = byte1;
|
||||
break;
|
||||
|
||||
// 2 byte character
|
||||
case (byte1 >= 0xC2 && byte1 < 0xE0):
|
||||
byte2 = this.readByte(++inc);
|
||||
|
||||
code1 = ((byte1 & 0x1F) << 6) +
|
||||
(byte2 & 0x3F);
|
||||
break;
|
||||
|
||||
// 3 byte character
|
||||
case (byte1 >= 0xE0 && byte1 < 0xF0):
|
||||
byte2 = this.readByte(++inc);
|
||||
byte3 = this.readByte(++inc);
|
||||
|
||||
code1 = ((byte1 & 0xFF) << 12) +
|
||||
((byte2 & 0x3F) << 6) +
|
||||
(byte3 & 0x3F);
|
||||
break;
|
||||
|
||||
// 4 byte character
|
||||
case (byte1 >= 0xF0 && byte1 < 0xF5):
|
||||
byte2 = this.readByte(++inc);
|
||||
byte3 = this.readByte(++inc);
|
||||
byte4 = this.readByte(++inc);
|
||||
|
||||
point = ((byte1 & 0x07) << 18) +
|
||||
((byte2 & 0x3F) << 12) +
|
||||
((byte3 & 0x3F) << 6) +
|
||||
(byte4 & 0x3F)
|
||||
point -= 0x10000;
|
||||
|
||||
code1 = (point >> 10) + 0xD800;
|
||||
code2 = (point & 0x3FF) + 0xDC00;
|
||||
break;
|
||||
|
||||
default:
|
||||
throw 'Invalid byte ' + this._byteToString(byte1) + ' whilst converting to UTF-8';
|
||||
break;
|
||||
}
|
||||
|
||||
string += (code2) ? String.fromCharCode(code1, code2)
|
||||
: String.fromCharCode(code1);
|
||||
}
|
||||
|
||||
return string;
|
||||
}
|
||||
|
||||
this.toArray = function() {return this.readBytes(this.length() - 1, 0);}
|
||||
|
||||
|
||||
this._stringToBytes = function(str) {
|
||||
var bytes = [],
|
||||
chr = 0;
|
||||
|
||||
for (var i = 0; i < str.length; i++) {
|
||||
chr = str.charCodeAt(i);
|
||||
bytes.push(chr & 0xFF);
|
||||
}
|
||||
|
||||
return bytes;
|
||||
}
|
||||
|
||||
this._byteToString = function(byte) {
|
||||
var asString = byte.toString(16).toUpperCase();
|
||||
while (asString.length < 2) {
|
||||
asString = '0' + asString;
|
||||
}
|
||||
|
||||
return '0x' + asString;
|
||||
}
|
||||
|
||||
this._init(source);
|
||||
}
|
|
@ -55,6 +55,9 @@ var configuratorApp = (function(){
|
|||
// private variables and functions go here
|
||||
var self,
|
||||
pi2 = Math.PI * 2,
|
||||
boards_file = 'boards.h',
|
||||
config_file = 'Configuration.h',
|
||||
config_adv_file = 'Configuration_adv.h',
|
||||
$config = $('#config_text'),
|
||||
$config_adv = $('#config_adv_text'),
|
||||
boards_list = {},
|
||||
|
@ -67,37 +70,106 @@ var configuratorApp = (function(){
|
|||
init: function() {
|
||||
self = this; // a 'this' for use when 'this' is something else
|
||||
|
||||
// Make a droppable file uploader
|
||||
var $uploader = $('#file-upload');
|
||||
var fileUploader = new BinaryFileUploader({
|
||||
element: $uploader[0],
|
||||
onFileLoad: function(file) { console.log(this); self.handleFileLoad(file, $uploader); }
|
||||
});
|
||||
|
||||
if (!fileUploader.hasFileUploaderSupport()) alert('Your browser doesn\'t support the file reading API');
|
||||
|
||||
// Read boards.h
|
||||
boards_list = {};
|
||||
$.get(marlin_config + "/boards.h", function(txt) {
|
||||
// Get all the boards and save them into an object
|
||||
var r, findDef = new RegExp('[ \\t]*#define[ \\t]+(BOARD_[^ \\t]+)[ \\t]+(\\d+)[ \\t]*(//[ \\t]*)?(.+)?', 'gm');
|
||||
while((r = findDef.exec(txt)) !== null) {
|
||||
boards_list[r[1]] = r[2].prePad(3, ' ') + " — " + r[4].replace(/\).*/, ')');
|
||||
}
|
||||
|
||||
var errFunc = function(jqXHR, textStatus, errorThrown) {
|
||||
alert('Failed to load '+this.url+'. Try the file field.');
|
||||
};
|
||||
|
||||
$.ajax({
|
||||
url: marlin_config+'/'+boards_file,
|
||||
type: 'GET',
|
||||
async: false,
|
||||
cache: false,
|
||||
success: function(txt) {
|
||||
// Get all the boards and save them into an object
|
||||
self.initBoardsFromText(txt);
|
||||
},
|
||||
error: errFunc
|
||||
});
|
||||
|
||||
// Read Configuration.h
|
||||
$.get(marlin_config+"/Configuration.h", function(txt) {
|
||||
// File contents into the textarea
|
||||
$config.text(txt);
|
||||
// Get all the thermistors and save them into an object
|
||||
var r, s, findDef = new RegExp('(//.*\n)+\\s+(#define[ \\t]+TEMP_SENSOR_0)', 'g');
|
||||
r = findDef.exec(txt);
|
||||
findDef = new RegExp('^//[ \\t]*([-\\d]+)[ \\t]+is[ \\t]+(.*)[ \\t]*$', 'gm');
|
||||
while((s = findDef.exec(r[0])) !== null) {
|
||||
therms_list[s[1]] = s[1].prePad(4, ' ') + " — " + s[2];
|
||||
}
|
||||
$.ajax({
|
||||
url: marlin_config+'/'+config_file,
|
||||
type: 'GET',
|
||||
async: false,
|
||||
cache: false,
|
||||
success: function(txt) {
|
||||
// File contents into the textarea
|
||||
$config.text(txt);
|
||||
self.initThermistorsFromText(txt);
|
||||
},
|
||||
error: errFunc
|
||||
});
|
||||
|
||||
// Read Configuration.h
|
||||
$.get(marlin_config+"/Configuration_adv.h", function(txt) {
|
||||
$config_adv.text(txt);
|
||||
self.setupConfigForm();
|
||||
$.ajax({
|
||||
url: marlin_config+'/'+config_adv_file,
|
||||
type: 'GET',
|
||||
async: false,
|
||||
cache: false,
|
||||
success: function(txt) {
|
||||
// File contents into the textarea
|
||||
$config_adv.text(txt);
|
||||
self.setupConfigForm();
|
||||
},
|
||||
error: errFunc
|
||||
});
|
||||
|
||||
},
|
||||
|
||||
initBoardsFromText: function(txt) {
|
||||
boards_list = {};
|
||||
var r, findDef = new RegExp('[ \\t]*#define[ \\t]+(BOARD_[^ \\t]+)[ \\t]+(\\d+)[ \\t]*(//[ \\t]*)?(.+)?', 'gm');
|
||||
while((r = findDef.exec(txt)) !== null) {
|
||||
boards_list[r[1]] = r[2].prePad(3, ' ') + " — " + r[4].replace(/\).*/, ')');
|
||||
}
|
||||
},
|
||||
|
||||
initThermistorsFromText: function(txt) {
|
||||
// Get all the thermistors and save them into an object
|
||||
var r, s, findDef = new RegExp('(//.*\n)+\\s+(#define[ \\t]+TEMP_SENSOR_0)', 'g');
|
||||
r = findDef.exec(txt);
|
||||
findDef = new RegExp('^//[ \\t]*([-\\d]+)[ \\t]+is[ \\t]+(.*)[ \\t]*$', 'gm');
|
||||
while((s = findDef.exec(r[0])) !== null) {
|
||||
therms_list[s[1]] = s[1].prePad(4, ' ') + " — " + s[2];
|
||||
}
|
||||
},
|
||||
|
||||
handleFileLoad: function(file, $uploader) {
|
||||
file += '';
|
||||
var filename = $uploader.val().replace(/.*[\/\\](.*)$/, '$1');
|
||||
switch(filename) {
|
||||
case config_file:
|
||||
$config.text(file);
|
||||
this.initThermistorsFromText(file);
|
||||
this.refreshConfigForm();
|
||||
break;
|
||||
case config_adv_file:
|
||||
$config_adv.text(file);
|
||||
this.refreshConfigForm();
|
||||
break;
|
||||
case boards_file:
|
||||
this.initBoardsFromText(file);
|
||||
$('#MOTHERBOARD').html('').addOptions(boards_list);
|
||||
this.initField('MOTHERBOARD');
|
||||
break;
|
||||
default:
|
||||
console.log("Can't parse "+filename);
|
||||
break;
|
||||
}
|
||||
},
|
||||
|
||||
setupConfigForm: function() {
|
||||
// Modify form fields and make the form responsive.
|
||||
// As values change on the form, we could update the
|
||||
|
@ -128,6 +200,17 @@ var configuratorApp = (function(){
|
|||
);
|
||||
});
|
||||
|
||||
$('#SERIAL_PORT').addOptions([0,1,2,3,4,5,6,7]);
|
||||
$('#BAUDRATE').addOptions([2400,9600,19200,38400,57600,115200,250000]);
|
||||
$('#MOTHERBOARD').addOptions(boards_list);
|
||||
$('#EXTRUDERS').addOptions([1,2,3,4]);
|
||||
$('#POWER_SUPPLY').addOptions({'1':'ATX','2':'Xbox 360'});
|
||||
|
||||
this.refreshConfigForm();
|
||||
},
|
||||
|
||||
refreshConfigForm: function() {
|
||||
|
||||
/**
|
||||
* For now I'm manually creating these references
|
||||
* but I should be able to parse Configuration.h
|
||||
|
@ -140,30 +223,25 @@ var configuratorApp = (function(){
|
|||
* Then we only need to specify exceptions to
|
||||
* standard behavior, (which is to add a text field)
|
||||
*/
|
||||
$('#SERIAL_PORT').addOptions([0,1,2,3,4,5,6,7]);
|
||||
this.initField('SERIAL_PORT');
|
||||
|
||||
$('#BAUDRATE').addOptions([2400,9600,19200,38400,57600,115200,250000]);
|
||||
this.initField('BAUDRATE');
|
||||
|
||||
this.initField('BTENABLED');
|
||||
|
||||
$('#MOTHERBOARD').addOptions(boards_list);
|
||||
this.initField('MOTHERBOARD');
|
||||
|
||||
this.initField('CUSTOM_MENDEL_NAME');
|
||||
|
||||
this.initField('MACHINE_UUID');
|
||||
|
||||
$('#EXTRUDERS').addOptions([1,2,3,4]);
|
||||
this.initField('EXTRUDERS');
|
||||
|
||||
$('#POWER_SUPPLY').addOptions({'1':'ATX','2':'Xbox 360'});
|
||||
this.initField('POWER_SUPPLY');
|
||||
|
||||
this.initField('PS_DEFAULT_OFF');
|
||||
|
||||
$('#TEMP_SENSOR_0, #TEMP_SENSOR_1, #TEMP_SENSOR_2, #TEMP_SENSOR_BED').addOptions(therms_list);
|
||||
$('#TEMP_SENSOR_0, #TEMP_SENSOR_1, #TEMP_SENSOR_2, #TEMP_SENSOR_BED').html('').addOptions(therms_list);
|
||||
this.initField('TEMP_SENSOR_0');
|
||||
this.initField('TEMP_SENSOR_1');
|
||||
this.initField('TEMP_SENSOR_2');
|
||||
|
|
Reference in a new issue