diff --git a/Marlin/configurator/config/Configuration.h b/Marlin/configurator/config/Configuration.h index 770c86eb1..13d5545e4 100644 --- a/Marlin/configurator/config/Configuration.h +++ b/Marlin/configurator/config/Configuration.h @@ -51,6 +51,7 @@ Here are some standard links for getting your machine calibrated: #define SERIAL_PORT 0 // This determines the communication speed of the printer +// :[2400,9600,19200,38400,57600,115200,250000] #define BAUDRATE 250000 // This enables the serial port associated to the Bluetooth interface diff --git a/Marlin/configurator/config/_htaccess b/Marlin/configurator/config/_htaccess new file mode 100644 index 000000000..f28955094 --- /dev/null +++ b/Marlin/configurator/config/_htaccess @@ -0,0 +1 @@ +Header set Access-Control-Allow-Origin "*" diff --git a/Marlin/configurator/css/configurator.css b/Marlin/configurator/css/configurator.css index 620311475..e9ef345d9 100644 --- a/Marlin/configurator/css/configurator.css +++ b/Marlin/configurator/css/configurator.css @@ -1,22 +1,82 @@ /* configurator.css */ /* Styles for Marlin Configurator */ -body { margin: 0; padding: 0; background: #56A; color: #FFC; font-family: sans-serif; } -fieldset { height: 16.1em; overflow: auto; margin-top: 10px; } -#main { max-width: 1000px; margin: 0 auto; } -#main { padding: 0 4%; width: 92%; } -#main { font-family: monospace; } -h1, #message { text-align: center; } +.clear { clear: both; } + +/* Prevent selection except PRE tags */ +* { + -webkit-touch-callout: none; + -webkit-user-select: none; + -khtml-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; + } +pre { + -webkit-touch-callout: text; + -webkit-user-select: text; + -khtml-user-select: text; + -moz-user-select: text; + -ms-user-select: text; + user-select: text; + } + +body { margin: 0; padding: 0; background: #56A; color: #000; font-family: monospace; } +#main { + max-width: 1000px; + margin: 0 auto; + padding: 0 4%; width: 92%; + } + +h1, h2, h3, h4, h5, h6 { clear: both; } + +h1, p.info { font-family: sans-serif; } +h1 { + background: transparent url(logo.png) right top no-repeat; + height: 38px; + margin-bottom: -30px; + } +p.info { padding: 0; color: #000; } +p.info span { color: #800; } + +#message { text-align: center; } #message { width: 80%; margin: 0 auto 0.25em; color: #FF0; } #message p { padding: 2px 0; } #message p.error, #message p.message { color: #F00; background: #FF4; font-weight: bold; border-radius: 0.8em; } #message p.message { color: #080; background: #CFC; } +#message p.message span { + color: #A00; + background: rgba(255, 255, 255, 1); + border: 1px solid rgba(0,0,0,0.5); + border-radius: 1em; + float: right; + margin-right: 0.5em; + padding: 0 3px; + font-family: sans-serif; + font-size: small; + position: relative; + top: -1px; + } -.info { color: #AAF; } -.info span { color: #FFF; } -.info span span { color: #000; font-weight: bold; } #help strong { color: #0DD; } img { display: none; } + +/* Forms */ + +#config_form { + display: block; + background: #EEE; + padding: 6px 20px 20px; + color: #000; + position: relative; + border-top-right-radius: 1.5em; + } +fieldset { + height: 16.1em; + overflow-y: scroll; + overflow-x: hidden; + margin-top: 10px; + } label, input, select, textarea { display: block; float: left; margin: 1px 0; } label.newline, textarea, fieldset { clear: both; } label { @@ -28,31 +88,9 @@ label { } input[type="text"], select { margin: 0.75em 0 0; } input[type="checkbox"], input[type="radio"], input[type="file"] { margin: 1em 0 0; } -#config_form { - display: block; - background: #EEE; - padding: 6px 20px 20px; - color: #000; - position: relative; - } - -/*#config_text, #config_adv_text { font-family: "Andale mono", monospace; clear: both; }*/ -#config_text, #config_adv_text { - height: 25em; - padding: 10px; - border: 2px solid #888; - border-radius: 5px; - overflow: auto; - background-color: #FFF; - color: #000; - font-family: "Fira Mono"; - font-size: small; - } input[type="checkbox"], input[type="radio"].enabler { margin-left: 1em; } + input:disabled { color: #BBB; } -.clear { clear: both; } -h1, h2, h3, h4, h5, h6 { clear: both; } -h2 { margin: 0; padding: 1em 0 0; } ul.tabs { padding: 0; list-style: none; } ul.tabs li { display: inline; } @@ -100,6 +138,8 @@ fieldset legend { display: none; } #serial_stepper { padding-top: 0.75em; display: block; float: left; } #SERIAL_PORT { display: none; } +/* Tooltips */ + #tooltip { display: none; max-width: 30em; @@ -138,18 +178,61 @@ fieldset legend { display: none; } } #tooltip>strong { color: #00B; } -span.disclose { +/* Tooltips Checkbox */ + +#tipson { float: right; font-weight: bold; font-size: 100%; font-family: helvetica; } +#tipson input { float: none; display: inline; } + +/* Config Text */ + +pre.config { + height: 25em; + padding: 10px; + border: 2px solid #888; + border-radius: 5px; + overflow: auto; + clear: both; + background-color: #FFF; + color: #000; + font-family: "Fira Mono"; + font-size: small; + } + +/* Pre Headers */ + +h2 { + width: 100%; + margin: 12px -300px 4px 0; + padding: 0; + float: left; + } + +/* Disclosure Widget */ + +span.disclose, a.download {︎ + display: block; float: right; - margin-top: -10px; + margin-top: 12px; + } + +span.disclose { + margin-right: -10px; /* total width */ + margin-left: 14px; width: 0; height: 0; + position: relative; + left: 3px; + top: 3px; cursor: pointer; border-left: 8px solid transparent; border-right: 8px solid transparent; border-top: 10px solid #000; } span.disclose.closed { - margin: -14px 4px 0 0; + margin-right: -8px; /* total width */ + margin-left: 10px; + left: 0; + top: 0; border-top: 8px solid transparent; border-bottom: 8px solid transparent; border-right: 10px solid #000; @@ -160,9 +243,26 @@ span.disclose.almost { transform: rotate(45deg); } span.disclose.closed.almost { + left: 1px; + top: 3px; -ms-transform: rotate(315deg); /* IE 9 */ -webkit-transform: rotate(315deg); /* Chrome, Safari, Opera */ transform: rotate(315deg); } -#tipson { float: right; font-weight: bold; font-size: 100%; font-family: helvetica; } -#tipson input { float: none; display: inline; } + +/* Download Button */ + +a.download { + visibility: hidden; + padding: 2px; + border: 1px solid #494; + border-radius: 4px; + margin: 12px 0 0; + background: #FFF; + color: #494; + font-family: sans-serif; + font-size: small; + font-weight: bold; + text-decoration: none; + } + diff --git a/Marlin/configurator/css/logo.png b/Marlin/configurator/css/logo.png new file mode 100644 index 000000000..0618dc17a Binary files /dev/null and b/Marlin/configurator/css/logo.png differ diff --git a/Marlin/configurator/index.html b/Marlin/configurator/index.html index 46f862bf1..ffd4a36f5 100644 --- a/Marlin/configurator/index.html +++ b/Marlin/configurator/index.html @@ -2,7 +2,7 @@ <html> <head> <meta charset="UTF-8"> - <title>Marlin Configurator</title> + <title>Marlin Firmware Configurator</title> <link href='http://fonts.googleapis.com/css?family=Fira+Mono&subset=latin,latin-ext' rel='stylesheet' type='text/css' /> <script src="js/jquery-2.1.3.min.js"></script> <script src="js/binarystring.js"></script> @@ -15,11 +15,9 @@ <body> <section id="main"> <h1>Marlin Configurator</h1> + <p class="info">Select presets (coming soon), modify, and download.</p> - <div id="message"> - <p class="info">Enter values in the form, get a Marlin configuration.<br/>Will include a drop-down of known configurations.</p> - </div> - + <div id="message"></div> <div id="tabs"></div> <form id="config_form"> @@ -83,10 +81,19 @@ <legend>More…</legend> </fieldset> - <h2>Marlin/Configuration.h</h2><span class="disclose"></span> - <pre id="config_text" class="hilightable"></pre> - <h2>Marlin/Configuration_adv.h</h2><span class="disclose"></span> - <pre id="config_adv_text" class="hilightable"></pre> + <section id="config_text"> + <h2>Configuration.h</h2> + <span class="disclose"></span> + <a href="" class="download">Download</a> + <pre class="hilightable config"></pre> + </section> + + <section id="config_adv_text"> + <h2>Configuration_adv.h</h2> + <span class="disclose"></span> + <a href="" class="download">Download</a> + <pre class="hilightable config"></pre> + </section> <br class="clear" /> </form> diff --git a/Marlin/configurator/js/configurator.js b/Marlin/configurator/js/configurator.js index 7ebda3543..88af7d652 100644 --- a/Marlin/configurator/js/configurator.js +++ b/Marlin/configurator/js/configurator.js @@ -16,7 +16,62 @@ $(function(){ -var marlin_config = 'https://api.github.com/repos/MarlinFirmware/Marlin/contents/Marlin'; +/** + * Github API useful GET paths. (Start with "https://api.github.com/repos/:owner/:repo/") + * + * contributors Get a list of contributors + * tags Get a list of tags + * contents/[path]?ref=branch/tag/commit Get the contents of a file + */ + + // GitHub + // Warning! Limited to 60 requests per hour! +var config = { + type: 'github', + host: 'https://api.github.com', + owner: 'thinkyhead', + repo: 'Marlin', + ref: 'marlin_configurator', + path: 'Marlin/configurator/config' +}; +/**/ + +/* // Remote +var config = { + type: 'remote', + host: 'http://www.thinkyhead.com', + path: '_marlin/config' +}; +/**/ + +/* // Local +var config = { + type: 'local', + path: 'config' +}; +/**/ + +function github_command(conf, command, path) { + var req = conf.host+'/repos/'+conf.owner+'/'+conf.repo+'/'+command; + if (path) req += '/' + path; + return req; +} +function config_path(item) { + var path = '', ref = ''; + switch(config.type) { + case 'github': + path = github_command(config, 'contents', config.path); + if (config.ref !== undefined) ref = '?ref=' + config.ref; + break; + case 'remote': + path = config.host + '/' + config.path + '/'; + break; + case 'local': + path = config.path + '/'; + break; + } + return path + '/' + item + ref; +} // Extend builtins String.prototype.lpad = function(len, chr) { @@ -25,6 +80,7 @@ String.prototype.lpad = function(len, chr) { if (need > 0) { s = new Array(need+1).join(chr) + s; } return s; }; + String.prototype.prePad = function(len, chr) { return len ? this.lpad(len, chr) : this; }; String.prototype.zeroPad = function(len) { return this.prePad(len, '0'); }; String.prototype.toHTML = function() { return jQuery('<div>').text(this).html(); }; @@ -36,6 +92,19 @@ Number.prototype.limit = function(m1, m2) { if (m2 == null) return this > m1 ? m1 : this; return this < m1 ? m1 : this > m2 ? m2 : this; }; +Date.prototype.fileStamp = function(filename) { + var fs = this.getFullYear() + + ((this.getMonth()+1)+'').zeroPad(2) + + (this.getDate()+'').zeroPad(2) + + (this.getHours()+'').zeroPad(2) + + (this.getMinutes()+'').zeroPad(2) + + (this.getSeconds()+'').zeroPad(2); + + if (filename !== undefined) + return filename.replace(/^(.+)(\.\w+)$/g, '$1-['+fs+']$2'); + + return fs; +} /** * selectField.addOptions takes an array or keyed object @@ -49,6 +118,12 @@ $.fn.extend({ sel.append( $('<option>',{value:isArr?v:k}).text(v) ); }); }); + }, + noSelect: function() { + return this + .attr('unselectable', 'on') + .css('user-select', 'none') + .on('selectstart', false); } }); @@ -62,10 +137,11 @@ var configuratorApp = (function(){ boards_file = 'boards.h', config_file = 'Configuration.h', config_adv_file = 'Configuration_adv.h', + $msgbox = $('#message'), $form = $('#config_form'), $tooltip = $('#tooltip'), - $config = $('#config_text'), - $config_adv = $('#config_adv_text'), + $config = $('#config_text pre'), + $config_adv = $('#config_adv_text pre'), define_list = [[],[]], boards_list = {}, therms_list = {}, @@ -88,6 +164,9 @@ var configuratorApp = (function(){ // Make tabs for all the fieldsets this.makeTabsForFieldsets(); + // No selection on errors + $msgbox.noSelect(); + // Make a droppable file uploader, if possible var $uploader = $('#file-upload'); var fileUploader = new BinaryFileUploader({ @@ -99,41 +178,98 @@ var configuratorApp = (function(){ // Make the disclosure items work $('.disclose').click(function(){ - var $dis = $(this), $pre = $dis.next('pre'); + var $dis = $(this), $pre = $dis.nextAll('pre:first'); var didAnim = function() {$dis.toggleClass('closed almost');}; $dis.addClass('almost').hasClass('closed') - ? $pre.slideDown(500, didAnim) - : $pre.slideUp(500, didAnim); + ? $pre.slideDown(200, didAnim) + : $pre.slideUp(200, didAnim); }); // Read boards.h, Configuration.h, Configuration_adv.h var ajax_count = 0, success_count = 0; var loaded_items = {}; var config_files = [boards_file, config_file, config_adv_file]; - var isGithub = marlin_config.match('api.github'); + var isGithub = config.type == 'github'; + var rateLimit = 0; $.each(config_files, function(i,fname){ + var url = config_path(fname); $.ajax({ - url: marlin_config+'/'+fname, + url: url, type: 'GET', - dataType: isGithub ? 'jsonp' : 'script', + dataType: isGithub ? 'jsonp' : undefined, async: true, cache: false, + error: function(req, stat, err) { + self.log(req, 1); + if (req.status == 200) { + if (typeof req.responseText === 'string') { + var txt = req.responseText; + loaded_items[fname] = function(){ self.fileLoaded(fname, txt); }; + success_count++; + // self.setMessage('The request for "'+fname+'" may be malformed.', 'error'); + } + } + else { + self.setRequestError(req.status ? req.status : '(Access-Control-Allow-Origin?)', url); + } + }, success: function(txt) { - loaded_items[fname] = function(){ self.fileLoaded(fname, isGithub ? atob(txt.data.content) : txt); }; - success_count++; + if (isGithub && typeof txt.meta.status !== undefined && txt.meta.status != 200) { + self.setRequestError(txt.meta.status, url); + } + else { + // self.log(txt, 1); + if (isGithub) { + rateLimit = { + quota: 1 * txt.meta['X-RateLimit-Remaining'], + timeLeft: Math.floor(txt.meta['X-RateLimit-Reset'] - Date.now()/1000), + }; + } + loaded_items[fname] = function(){ self.fileLoaded(fname, isGithub ? atob(txt.data.content) : txt); }; + success_count++; + } }, complete: function() { ajax_count++; if (ajax_count >= 3) { $.each(config_files, function(){ if (loaded_items[this]) loaded_items[this](); }); if (success_count < ajax_count) - self.setMessage('Unable to load configurations. Use the upload field instead.', 'error'); + self.setMessage('Unable to load configurations. Try the upload field.', 'error'); + var r; + if (r = rateLimit) { + if (r.quota < 20) { + self.setMessage( + 'Approaching request limit (' + + r.quota + ' remaining.' + + ' Reset in ' + Math.floor(r.timeLeft/60) + ':' + (r.timeLeft%60+'').zeroPad(2) + ')' + ); + } + } } } }); }); }, + createDownloadLink: function(adv) { + var $c = adv ? $config_adv : $config, txt = $c.text(); + var filename = (adv ? config_adv_file : config_file); + $c.prevAll('.download:first') + .mouseover(function() { + var d = new Date(), fn = d.fileStamp(filename); + $(this).attr({ download:fn, href:'download:'+fn, title:'download:'+fn }); + }) + .click(function(){ + var $button = $(this); + $(this).attr({ href:'data:text/plain;charset=utf-8,' + encodeURIComponent($c.text()) }); + setTimeout(function(){ + $button.attr({ href:$button.attr('title') }); + }, 100); + return true; + }) + .css({visibility:'visible'}); + }, + /** * Init the boards array from a boards.h file */ @@ -239,6 +375,7 @@ var configuratorApp = (function(){ this.log(define_list[0], 2); this.createFieldsForDefines(0); this.refreshConfigForm(); + this.createDownloadLink(false); has_config = true; } else { @@ -253,6 +390,7 @@ var configuratorApp = (function(){ define_list[1] = this.getDefinesFromText(txt); this.log(define_list[1], 2); this.refreshConfigForm(); + this.createDownloadLink(true); has_config_adv = true; } else { @@ -289,7 +427,7 @@ var configuratorApp = (function(){ }); // Get all 'switchable' class items and add a checkbox - $('#config_form .switchable').each(function(){ + $form.find('.switchable').each(function(){ $(this).after( $('<input>',{type:'checkbox',value:'1',class:'enabler'}).prop('checked',true) .attr('id',this.id + '-switch') @@ -651,6 +789,7 @@ var configuratorApp = (function(){ } } + // Success? if (info.type) { // Get the end-of-line comment, if there is one var tooltip = ''; @@ -668,11 +807,11 @@ var configuratorApp = (function(){ tooltip = ''; break; } - tooltip += ' ' + s[1] + "\n"; + tooltip += ' ' + s[1] + '\n'; } } - findDef = new RegExp('^[ \\t]*'+name+'[ \\t]*', 'm'); + findDef = new RegExp('^[ \\t]*'+name); // To strip the name from the start $.extend(info, { tooltip: '<strong>'+name+'</strong> '+tooltip.replace(findDef,'').trim().toHTML(), lineNum: this.getLineNumberOfText(info.line, txt) @@ -700,24 +839,22 @@ var configuratorApp = (function(){ setMessage: function(msg,type) { if (msg) { if (type === undefined) type = 'message'; - var $err = $('<p class="'+type+'">'+msg+'</p>'), err = $err[0]; - $('#message').prepend($err); + var $err = $('<p class="'+type+'">'+msg+'<span>x</span></p>').appendTo($msgbox), err = $err[0]; var baseColor = $err.css('color').replace(/rgba?\(([^),]+,[^),]+,[^),]+).*/, 'rgba($1,'); - var d = new Date(); err.pulse_offset = (pulse_offset += 200); - err.startTime = d.getTime() + pulse_offset; + err.startTime = Date.now() + pulse_offset; err.pulser = setInterval(function(){ - d = new Date(); - var pulse_time = d.getTime() + err.pulse_offset; - $err.css({color:baseColor+(0.5+Math.sin(pulse_time/200)*0.4)+')'}); - if (pulse_time - err.startTime > 5000) { + var pulse_time = Date.now() + err.pulse_offset; + var opac = 0.5+Math.sin(pulse_time/200)*0.4; + $err.css({color:baseColor+(opac)+')'}); + if (pulse_time - err.startTime > 2500 && opac > 0.899) { clearInterval(err.pulser); - $err.remove(); } }, 50); + $err.click(function(e) { $(this).remove(); return false; }).css({cursor:'pointer'}); } else { - $('#message p.error, #message p.warning').each(function() { + $msgbox.find('p.error, p.warning').each(function() { if (this.pulser !== undefined && this.pulser) clearInterval(this.pulser); $(this).remove(); @@ -725,6 +862,10 @@ var configuratorApp = (function(){ } }, + setRequestError: function(stat, path) { + self.setMessage('Error '+stat+' – ' + path.replace(/^(https:\/\/[^\/]+\/)?.+(\/[^\/]+)$/, '$1...$2'), 'error'); + }, + log: function(o,l) { if (l === undefined) l = 0; if (this.logging>=l*1) console.log(o);