From 1fc745687c72fc55e8da0c7f31e007e9e48c8047 Mon Sep 17 00:00:00 2001 From: Joseph Coffland Date: Fri, 16 Mar 2018 01:28:50 -0700 Subject: [PATCH] - Fixed disappearing GCode in Web. - More efficient GCode scrolling with very large files. --- CHANGELOG.md | 2 + src/jade/index.jade | 2 + src/jade/templates/control-view.jade | 8 +- src/jade/templates/gcode-viewer.jade | 33 ++++++ src/js/control-view.js | 126 +++------------------- src/js/gcode-viewer.js | 151 +++++++++++++++++++++++++++ src/py/bbctrl/Ctrl.py | 1 - src/resources/css/clusterize.css | 38 +++++++ src/resources/js/clusterize.min.js | 17 +++ src/stylus/style.styl | 21 ++-- 10 files changed, 272 insertions(+), 127 deletions(-) create mode 100644 src/jade/templates/gcode-viewer.jade create mode 100644 src/js/gcode-viewer.js create mode 100644 src/resources/css/clusterize.css create mode 100644 src/resources/js/clusterize.min.js diff --git a/CHANGELOG.md b/CHANGELOG.md index ee79e0b..34c3e8c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,8 @@ Buildbotics CNC Controller Firmware Change Log ## v0.3.20 - Eliminated drift caused by miscounting half microsteps. + - Fixed disappearing GCode in Web. + - More efficient GCode scrolling with very large files. ## v0.3.19 - Fixed stopping problems. #127 diff --git a/src/jade/index.jade b/src/jade/index.jade index 96d549d..c25fac1 100644 --- a/src/jade/index.jade +++ b/src/jade/index.jade @@ -45,6 +45,7 @@ html(lang="en") link(rel="stylesheet" href="css/font-awesome.min.css") link(href="css/Audiowide.css" rel="stylesheet" type="text/css") + link(href="css/clusterize.css" rel="stylesheet" type="text/css") link(rel="stylesheet" href="/css/style-" + css_hash + ".css") @@ -195,5 +196,6 @@ html(lang="en") script(src="js/jquery-1.11.3.min.js") script(src="js/vue.js") script(src="js/sockjs.min.js") + script(src="js/clusterize.min.js") script(src='/js/assets-' + js_hash + '.js') script(src="js/ui.js") diff --git a/src/jade/templates/control-view.jade b/src/jade/templates/control-view.jade index 504eb60..90880ee 100644 --- a/src/jade/templates/control-view.jade +++ b/src/jade/templates/control-view.jade @@ -229,13 +229,7 @@ script#control-view-template(type="text/x-template") :disabled="is_running || is_stopping") option(v-for="file in files", :value="file") {{file}} - .gcode(:class="{placeholder: !gcode}", @scroll="gcode_scroll") - span(v-if="!gcode.length") GCode displays here. - ul - li(v-for="item in gcode", id="gcode-line-{{$index}}", - track-by="$index") - span {{$index + 1 + gcode_offset}} - | {{item}} + gcode-viewer section#content2.tab-content .mdi.pure-form(title="Manual GCode entry.") diff --git a/src/jade/templates/gcode-viewer.jade b/src/jade/templates/gcode-viewer.jade new file mode 100644 index 0000000..c79197a --- /dev/null +++ b/src/jade/templates/gcode-viewer.jade @@ -0,0 +1,33 @@ +//-///////////////////////////////////////////////////////////////////////////// +//- // +//- This file is part of the Buildbotics firmware. // +//- // +//- Copyright (c) 2015 - 2018, Buildbotics LLC // +//- All rights reserved. // +//- // +//- This file ("the software") is free software: you can redistribute it // +//- and/or modify it under the terms of the GNU General Public License, // +//- version 2 as published by the Free Software Foundation. You should // +//- have received a copy of the GNU General Public License, version 2 // +//- along with the software. If not, see . // +//- // +//- The software is distributed in the hope that it will be useful, but // +//- WITHOUT ANY WARRANTY; without even the implied warranty of // +//- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU // +//- Lesser General Public License for more details. // +//- // +//- You should have received a copy of the GNU Lesser General Public // +//- License along with the software. If not, see // +//- . // +//- // +//- For information regarding this software email: // +//- "Joseph Coffland" // +//- // +//-///////////////////////////////////////////////////////////////////////////// + +script#gcode-viewer-template(type="text/x-template") + .gcode + .clusterize + .clusterize-scroll + ul.clusterize-content + li.clusterize-no-data.placeholder GCode displays here. diff --git a/src/js/control-view.js b/src/js/control-view.js index 2d8c00b..f4d0be7 100644 --- a/src/js/control-view.js +++ b/src/js/control-view.js @@ -29,9 +29,6 @@ var api = require('./api'); -var maxLines = 1000; -var pageSize = Math.round(maxLines / 10); - function _is_array(x) { return Object.prototype.toString.call(x) === '[object Array]'; @@ -52,12 +49,8 @@ module.exports = { data: function () { return { mdi: '', - last_file: '', files: [], axes: 'xyzabc', - all_gcode: [], - gcode: [], - gcode_offset: 0, history: [], speed_override: 1, feed_override: 1, @@ -72,12 +65,18 @@ module.exports = { components: { - 'axis-control': require('./axis-control') + 'axis-control': require('./axis-control'), + 'gcode-viewer': require('./gcode-viewer') }, watch: { - 'state.line': function () {this.update_gcode_line()}, + 'state.line': function () { + if (this.mach_state != 'HOMING') + this.$broadcast('gcode-line', this.state.line); + }, + + 'state.selected': function () {this.load()} }, @@ -122,9 +121,7 @@ module.exports = { api.put('jog', data); }, - connected: function () {this.update()}, - - update: function () {console.log(this.state.xx, this.state.cycle)} + connected: function () {this.update()} }, @@ -195,86 +192,6 @@ module.exports = { }, - gcode_move_up: function (count) { - var lines = 0; - - for (var i = 0; i < count; i++) { - if (!this.gcode_offset) break; - - this.gcode.unshift(this.all_gcode[this.gcode_offset - 1]) - this.gcode.pop(); - this.gcode_offset--; - lines++; - } - - return lines; - }, - - - gcode_move_down: function (count) { - var lines = 0; - - for (var i = 0; i < count; i++) { - if (this.all_gcode.length <= this.gcode_offset + this.gcode.length) - break; - - this.gcode.push(this.all_gcode[this.gcode_offset + this.gcode.length]) - this.gcode.shift(); - this.gcode_offset++; - lines++ - } - - return lines; - }, - - - gcode_scroll: function (e) { - if (this.gcode.length == this.all_gcode.length) return; - - var t = e.target; - var percentScroll = t.scrollTop / (t.scrollHeight - t.clientHeight); - - var lines = 0; - if (percentScroll < 0.2) lines = this.gcode_move_up(pageSize); - else if (0.8 < percentScroll) lines = -this.gcode_move_down(pageSize); - else return; - - if (lines) t.scrollTop += t.scrollHeight * lines / maxLines; - }, - - - update_gcode_line: function () { - if (this.mach_state == 'HOMING') return; - - if (typeof this.last_line != 'undefined') { - $('#gcode-line-' + this.last_line).removeClass('highlight'); - this.last_line = undefined; - } - - if (0 <= this.state.line) { - var line = this.state.line - 1; - - // Make sure the current GCode is loaded - if (line < this.gcode_offset || - this.gcode_offset + this.gcode.length <= line) { - this.gcode_offset = line - pageSize; - if (this.gcode_offset < 0) this.gcode_offset = 0; - - this.gcode = this.all_gcode.slice(this.gcode_offset, maxLines); - } - - Vue.nextTick(function () { - var e = $('#gcode-line-' + line); - if (e.length) - e.addClass('highlight')[0] - .scrollIntoView({behavior: 'smooth'}); - - this.last_line = line; - }.bind(this)); - } - }, - - update: function () { // Update file list api.get('file').done(function (files) { @@ -284,6 +201,13 @@ module.exports = { }, + load: function () { + var file = this.state.selected; + if (typeof file != 'undefined') this.$broadcast('gcode-load', file); + this.$broadcast('gcode-line', this.state.line); + }, + + submit_mdi: function () { this.send(this.mdi); if (!this.history.length || this.history[0] != this.mdi) @@ -321,28 +245,12 @@ module.exports = { api.upload('file', fd) .done(function () { - if (file.name == this.last_file) this.last_file = ''; + this.$broadcast('gcode-reload', file.name); this.update(); }.bind(this)); }, - load: function () { - var file = this.state.selected; - if (typeof file == 'undefined' || file == this.last_file) return; - - api.get('file/' + file) - .done(function (data) { - this.all_gcode = data.trimRight().split(/\r?\n/); - this.gcode = this.all_gcode.slice(0, maxLines); - this.gcode_offset = 0; - this.last_file = file; - - Vue.nextTick(this.update_gcode_line); - }.bind(this)); - }, - - deleteCurrent: function () { if (this.state.selected) api.delete('file/' + this.state.selected).done(this.update); diff --git a/src/js/gcode-viewer.js b/src/js/gcode-viewer.js new file mode 100644 index 0000000..ca92b7c --- /dev/null +++ b/src/js/gcode-viewer.js @@ -0,0 +1,151 @@ +/******************************************************************************\ + + This file is part of the Buildbotics firmware. + + Copyright (c) 2015 - 2018, Buildbotics LLC + All rights reserved. + + This file ("the software") is free software: you can redistribute it + and/or modify it under the terms of the GNU General Public License, + version 2 as published by the Free Software Foundation. You should + have received a copy of the GNU General Public License, version 2 + along with the software. If not, see . + + The software is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with the software. If not, see + . + + For information regarding this software email: + "Joseph Coffland" + +\******************************************************************************/ + +'use strict' + +var api = require('./api'); + +var max_lines = 1000; +var page_size = Math.round(max_lines / 10); + + +module.exports = { + template: '#gcode-viewer-template', + + + data: function () { + return { + empty: true, + file: '', + line: -1, + scrolling: false + } + }, + + + events: { + 'gcode-load': function (file) {this.load(file)}, + 'gcode-clear': function () {this.clear()}, + 'gcode-reload': function (file) {this.reload(file)}, + 'gcode-line': function (line) {this.update_line(line)} + }, + + + methods: { + load: function (file) { + if (file == this.file) return; + this.clear(); + this.file = file; + + api.get('file/' + file) + .done(function (data) { + var lines = data.trimRight().split(/\r?\n/); + + for (var i = 0; i < lines.length; i++) { + lines[i] = '
  • ' + + '' + (i + 1) + '' + + lines[i] + '
  • '; + } + + this.clusterize = new Clusterize({ + rows: lines, + scrollElem: $(this.$el).find('.clusterize-scroll')[0], + contentElem: $(this.$el).find('.clusterize-content')[0], + callbacks: {clusterChanged: this.highlight} + }); + + this.empty = false; + + Vue.nextTick(this.update_line); + }.bind(this)); + }, + + + clear: function () { + this.empty = true; + this.file = ''; + this.line = -1; + + if (typeof this.clusterize != 'undefined') + this.clusterize.destroy(); + }, + + + reload: function (file) { + if (file != this.file) return; + this.clear(); + this.load(file); + }, + + + highlight: function () { + var e = $(this.$el).find('.highlight'); + if (e.length) e.removeClass('highlight'); + + e = $(this.$el).find('.line-' + this.line); + if (e.length) e.addClass('highlight'); + }, + + + update_line: function(line) { + if (typeof line != 'undefined') { + if (this.line == line) return; + this.line = line; + + } else line = this.line; + + if (typeof this.clusterize == 'undefined') return; + + var totalLines = this.clusterize.getRowsAmount(); + + if (line <= 0) line = 1; + if (totalLines < line) line = totalLines; + + var e = $(this.$el).find('.clusterize-scroll'); + + var lineHeight = e[0].scrollHeight / totalLines; + var linesPerPage = Math.floor(e[0].clientHeight / lineHeight); + var current = e[0].scrollTop / lineHeight; + var target = line - 1 - Math.floor(linesPerPage / 2); + + // Update scroll position + if (!this.scrolling) { + if (target < current - 20 || current + 20 < target) + e[0].scrollTop = target * lineHeight; + + else { + this.scrolling = true; + e.animate({scrollTop: target * lineHeight}, { + complete: function () {this.scrolling = false}.bind(this) + }) + } + } + + Vue.nextTick(this.highlight); + } + } +} diff --git a/src/py/bbctrl/Ctrl.py b/src/py/bbctrl/Ctrl.py index c1366cf..2b0ef52 100644 --- a/src/py/bbctrl/Ctrl.py +++ b/src/py/bbctrl/Ctrl.py @@ -26,7 +26,6 @@ ################################################################################ import logging - import bbctrl diff --git a/src/resources/css/clusterize.css b/src/resources/css/clusterize.css new file mode 100644 index 0000000..dc7f878 --- /dev/null +++ b/src/resources/css/clusterize.css @@ -0,0 +1,38 @@ +/* max-height - the only parameter in this file that needs to be edited. + * Change it to suit your needs. The rest is recommended to leave as is. + */ +.clusterize-scroll{ + max-height: 200px; + overflow: auto; +} + +/** + * Avoid vertical margins for extra tags + * Necessary for correct calculations when rows have nonzero vertical margins + */ +.clusterize-extra-row{ + margin-top: 0 !important; + margin-bottom: 0 !important; +} + +/* By default extra tag .clusterize-keep-parity added to keep parity of rows. + * Useful when used :nth-child(even/odd) + */ +.clusterize-extra-row.clusterize-keep-parity{ + display: none; +} + +/* During initialization clusterize adds tabindex to force the browser to keep focus + * on the scrolling list, see issue #11 + * Outline removes default browser's borders for focused elements. + */ +.clusterize-content{ + outline: 0; + counter-reset: clusterize-counter; +} + +/* Centering message that appears when no data provided + */ +.clusterize-no-data td{ + text-align: center; +} \ No newline at end of file diff --git a/src/resources/js/clusterize.min.js b/src/resources/js/clusterize.min.js new file mode 100644 index 0000000..a97f921 --- /dev/null +++ b/src/resources/js/clusterize.min.js @@ -0,0 +1,17 @@ +/* Clusterize.js - v0.18.1 - 2018-01-02 + http://NeXTs.github.com/Clusterize.js/ + Copyright (c) 2015 Denis Lukov; Licensed GPLv3 */ + +;(function(q,n){"undefined"!=typeof module?module.exports=n():"function"==typeof define&&"object"==typeof define.amd?define(n):this[q]=n()})("Clusterize",function(){function q(b,a,c){return a.addEventListener?a.addEventListener(b,c,!1):a.attachEvent("on"+b,c)}function n(b,a,c){return a.removeEventListener?a.removeEventListener(b,c,!1):a.detachEvent("on"+b,c)}function r(b){return"[object Array]"===Object.prototype.toString.call(b)}function m(b,a){return window.getComputedStyle?window.getComputedStyle(a)[b]: +a.currentStyle[b]}var l=function(){for(var b=3,a=document.createElement("b"),c=a.all||[];a.innerHTML="\x3c!--[if gt IE "+ ++b+"]>=l&&!c.tag&&(c.tag=b[0].match(/<([^>\s/]*)/)[1].toLowerCase()),1>=this.content_elem.children.length&&(a.data=this.html(b[0]+b[0]+b[0])),c.tag||(c.tag=this.content_elem.children[0].tagName.toLowerCase()), +this.getRowsHeight(b))},getRowsHeight:function(b){var a=this.options,c=a.item_height;a.cluster_height=0;if(b.length&&(b=this.content_elem.children,b.length)){var d=b[Math.floor(b.length/2)];a.item_height=d.offsetHeight;"tr"==a.tag&&"collapse"!=m("borderCollapse",this.content_elem)&&(a.item_height+=parseInt(m("borderSpacing",this.content_elem),10)||0);"tr"!=a.tag&&(b=parseInt(m("marginTop",d),10)||0,d=parseInt(m("marginBottom",d),10)||0,a.item_height+=Math.max(b,d));a.block_height=a.item_height*a.rows_in_block; +a.rows_in_cluster=a.blocks_in_cluster*a.rows_in_block;a.cluster_height=a.blocks_in_cluster*a.block_height;return c!=a.item_height}},getClusterNum:function(){this.options.scroll_top=this.scroll_elem.scrollTop;return Math.floor(this.options.scroll_top/(this.options.cluster_height-this.options.block_height))||0},generateEmptyRow:function(){var b=this.options;if(!b.tag||!b.show_no_data_row)return[];var a=document.createElement(b.tag),c=document.createTextNode(b.no_data_text);a.className=b.no_data_class; +if("tr"==b.tag){var d=document.createElement("td");d.colSpan=100;d.appendChild(c)}a.appendChild(d||c);return[a.outerHTML]},generate:function(b,a){var c=this.options,d=b.length;if(de&&g++;f=l&&"tr"==this.options.tag){var c=document.createElement("div");for(c.innerHTML=""+b+"
    ";b=a.lastChild;)a.removeChild(b);for(c=this.getChildNodes(c.firstChild.firstChild);c.length;)a.appendChild(c.shift())}else a.innerHTML=b},getChildNodes:function(b){b=b.children;for(var a=[],c=0,d=b.length;c