This repository has been archived on 2025-02-01. You can view files and clone it, but cannot push or open issues or pull requests.
reprapfirmware-dc42/SD-image/www/js/interface.js
David Crocker bb8a2c3ef7 Version 1.09b
Z axis min and max limits are now enforced if the axes have been homed
M665 command now shows the re-calculated delta radius and tower offsets
after auto-calibration
Initial and final RMS errors are now shown after delta auto-calibration
Pause/resume now mostly works when absolute extruder coordinates are
used
Doubled the maximum length of the SD card file listing that can be sent
to PanelDue
Implemented zpl's Network and PrintMonitor changes including M404
(thanks, zpl)
Increased default maximum instantaneous speed changes
Bug fix:: if M226 was used to pause the print, the resume coordinates
were not set up correctly
Bug fix: when changing the travel direction, the head was sometimes
slowed down more than necessary
Bug fix: warm-up time was not shown correctly in DuetWebControl
Bug fix: extruder drive positions were always shown as 0.0 in
DuetWebControl
Bug fix: incorrect PID parameters were shown in response to M301
Replaced old web interface files on SD card by DuetWebControl
Modified all resume.g files to set the speed when moving to the paused
head coordinates
2015-06-14 20:15:36 +01:00

3692 lines
116 KiB
JavaScript

/* Interface logic for the Duet Web Control v1.06
*
* written by Christian Hammacher
*
* licensed under the terms of the GPL v2
* see http://www.gnu.org/licenses/gpl-2.0.html
*/
var jsVersion = 1.06;
var sessionPassword = "reprap";
var translationWarning = false; // Set this to "true" if you want to look for missing translation entries
/* Constants */
var maxTemperatureSamples = 1000;
var probeSlowDownColor = "#FFFFE0", probeTriggerColor = "#FFF0F0";
var tempChart;
var tempChartOptions = {
colors: ["#0000FF", "#FF0000", "#00DD00", "#FFA000", "#FF00FF", "#337AB7", "#000000"],
grid: {
borderWidth: 0
},
xaxis: {
show: false
},
yaxis: {
min: 0,
max: 280
}
};
var tempChartPadding = 15;
var printChart;
var printChartOptions = {
grid: {
borderWidth: 0
},
pan: {
interactive: true
},
xaxis: {
min: 0,
tickDecimals: 0,
},
yaxis: {
min: 0,
max: 60,
ticks: 5,
tickDecimals: 0,
tickFormatter: function(val) { if (!val) { return ""; } else { return val + "s"; } }
},
zoom: {
interactive: true
}
};
/* Settings */
var settings = {
autoConnect: true, // automatically connect once the page has loaded
updateInterval: 250, // in ms
haltedReconnectDelay: 5000, // in ms
extendedStatusInterval: 10, // nth status request will include extended values
maxRequestTime: 8000, // time to wait for a status response in ms
halfZMovements: false, // use half Z movements
logSuccess: false, // log all sucessful G-Codes in the console
uppercaseGCode: true, // convert G-Codes to upper-case before sending them
useKiB: true, // display file sizes in KiB instead of KB
showFanControl: false, // show fan controls
showATXControl: false, // show ATX control
confirmStop: false, // ask for confirmation when pressing Emergency STOP
moveFeedrate: 6000, // in mm/min
defaultActiveTemps: [0, 170, 195, 205, 210, 235, 245, 250],
defaultStandbyTemps: [0, 95, 120, 155, 170],
defaultBedTemps: [0, 55, 65, 90, 110, 120],
defaultGCodes: [
["M0", "Stop"],
["M1", "Sleep"],
["M84", "Motors Off"],
["M119", "Get Endstop Status"],
["M122", "Diagnostics"],
["M561", "Disable bed compensation"]
],
language: "en",
bowdenLength: 300 // in mm
};
var defaultSettings = jQuery.extend(true, {}, settings); // need to do this to get a valid copy
/* Variables */
var isConnected = false, justConnected, isPrinting, isPaused, isUploading;
var ajaxRequests = [], extendedStatusCounter, lastStatusResponse, configResponse;
var lastSendGCode, lastGCodeFromInput;
var haveFileInfo, fileInfo;
var maxLayerTime, lastLayerPrintDuration;
var geometry, probeSlowDownValue, probeTriggerValue;
var heatedBed, chamber, numHeads, numExtruderDrives, toolMapping;
var coldExtrudeTemp, coldRetractTemp;
var recordedBedTemperatures, recordedChamberTemperatures, recordedHeadTemperatures, layerData;
var currentPage = "control", refreshTempChart = false, refreshPrintChart = false, waitingForPrintStart;
var translationData;
var currentGCodeDirectory, knownGCodeFiles, gcodeUpdateIndex, gcodeLastDirectory;
var currentMacroDirectory, nownMacroFiles, macroUpdateIndex, macroLastDirectory;
var fanSliderActive, speedSliderActive, extrSliderActive;
function resetGuiData() {
setPauseStatus(false);
setPrintStatus(false);
setGeometry("cartesian");
justConnected = haveFileInfo = isUploading = false;
fileInfo = lastStatusResponse = configResponse = undefined;
lastSendGCode = "";
lastGCodeFromInput = false;
probeSlowDownValue = probeTriggerValue = undefined;
heatedBed = 1;
chamber = 0;
numHeads = 2; // 5 Heaters max, only 2 are visible on load
numExtruderDrives = 2; // 5 Extruder Drives max, only 2 are visible on load
coldExtrudeTemp = 160;
coldRetractTemp = 90;
recordedBedTemperatures = [];
recordedChamberTemperatures = [];
layerData = [];
recordedHeadTemperatures = [[], [], [], [], []];
layerData = [];
maxLayerTime = lastLayerPrintDuration = 0;
setToolMapping(undefined);
knownGCodeFiles = knownMacroFiles = [];
gcodeUpdateIndex = macroUpdateIndex = -1;
currentGCodeDirectory = "/gcodes";
currentMacroDirectory = "/macros";
waitingForPrintStart = fanSliderActive = speedSliderActive = extrSliderActive = false;
}
/* Connect / Disconnect */
function connect(password, firstConnect) {
$(".btn-connect").removeClass("btn-info").addClass("btn-warning disabled").find("span:not(.glyphicon)").text(T("Connecting..."));
$(".btn-connect span.glyphicon").removeClass("glyphicon-log-in").addClass("glyphicon-transfer");
$.ajax("rr_connect?password=" + password, {
dataType: "json",
error: function() {
showMessage("exclamation-sign", T("Error"), T("Could not establish connection to the Duet firmware!<br/><br/>Please check your settings and try again."), "md");
$("#modal_message").one("hide.bs.modal", function() {
$(".btn-connect").removeClass("btn-warning disabled").addClass("btn-info").find("span:not(.glyphicon)").text(T("Connect"));
$(".btn-connect span.glyphicon").removeClass("glyphicon-transfer").addClass("glyphicon-log-in");
});
},
success: function(data) {
if (data.err == 2) { // Looks like the firmware ran out of HTTP sessions
showMessage("exclamation-sign", T("Error"), T("Could not connect to Duet, because there are no more HTTP sessions available."), "md");
$("#modal_message").one("hide.bs.modal", function() {
$(".btn-connect").removeClass("btn-warning disabled").addClass("btn-info").find("span:not(.glyphicon)").text(T("Connect"));
$(".btn-connect span.glyphicon").removeClass("glyphicon-transfer").addClass("glyphicon-log-in");
});
}
else if (firstConnect)
{
if (data.err == 0) { // No password authentication required
sessionPassword = password;
postConnect();
}
else { // We can connect, but we need a password first
showPasswordPrompt();
}
}
else {
if (data.err == 0) { // Connect successful
sessionPassword = password;
postConnect();
} else {
showMessage("exclamation-sign", T("Error"), T("Invalid password!"), "sm");
$("#modal_message").one("hide.bs.modal", function() {
$(".btn-connect").removeClass("btn-warning disabled").addClass("btn-info").find("span:not(.glyphicon)").text(T("Connect"));
$(".btn-connect span.glyphicon").removeClass("glyphicon-transfer").addClass("glyphicon-log-in");
});
}
}
}
});
}
function postConnect() {
log("success", "<strong>" + T("Connection established!") + "</strong>");
isConnected = justConnected = true;
extendedStatusCounter = settings.extendedStatusInterval; // ask for extended status response on first poll
updateStatus();
if (currentPage == "files") {
updateGCodeFiles();
} else if (currentPage == "control" || currentPage == "macros") {
updateMacroFiles();
} else if (currentPage == "settings") {
getConfigResponse();
}
$(".btn-connect").removeClass("btn-warning disabled").addClass("btn-success").find("span:not(.glyphicon)").text(T("Disconnect"));
$(".btn-connect span.glyphicon").removeClass("glyphicon-transfer").addClass("glyphicon-log-out");
enableControls();
validateAddTool();
}
function disconnect() {
if (isConnected) {
log("danger", "<strong>" + T("Disconnected.") + "</strong>");
}
isConnected = false;
$(".btn-connect").removeClass("btn-success").addClass("btn-info").find("span:not(.glyphicon)").text(T("Connect"));
$(".btn-connect span.glyphicon").removeClass("glyphicon-log-out").addClass("glyphicon-log-in");
$.ajax("rr_disconnect", { dataType: "json", global: false });
ajaxRequests.forEach(function(request) {
request.abort();
});
ajaxRequests = [];
resetGuiData();
resetGui();
updateGui();
disableControls();
validateAddTool();
setToolMapping(undefined);
}
/* AJAX */
function updateStatus() {
if (isUploading) {
// Don't poll for status updates while uploading data
return;
}
var ajaxRequest = "rr_status";
if (extendedStatusCounter >= settings.extendedStatusInterval) {
extendedStatusCounter = 0;
ajaxRequest += "?type=2";
} else if (isPrinting) {
ajaxRequest += "?type=3";
} else {
ajaxRequest += "?type=1";
}
extendedStatusCounter++;
$.ajax(ajaxRequest, {
dataType: "json",
success: function(status) {
// Don't process this one if we're no longer connected
if (!isConnected) {
return;
}
var needGuiUpdate = false;
/*** Extended status response ***/
// Cold Extrusion + Retraction Temperatures
if (status.hasOwnProperty("coldExtrudeTemp")) {
coldExtrudeTemp = status.coldExtrudeTemp;
}
if (status.hasOwnProperty("coldRetractTemp")) {
coldRetractTemp = status.coldRetractTemp;
}
// Printer Geometry
if (status.hasOwnProperty("geometry")) {
setGeometry(status.geometry);
}
// Machine Name
if (status.hasOwnProperty("name")) {
$(".machine-name").html(status.name);
}
// Probe Parameters (maybe hide probe info for type 0 someday?)
if (status.hasOwnProperty("probe")) {
probeTriggerValue = status.probe.threshold;
probeSlowDownValue = probeTriggerValue * 0.9; // see Platform::Stopped in dc42/zpl firmware forks
}
// Tool Mapping
if (status.hasOwnProperty("tools")) {
setToolMapping(status.tools);
}
/*** Default status response ***/
// Status
var printing = false, paused = false;
switch (status.status) {
case 'H': // Halted
setStatusLabel("Halted", "danger");
break;
case 'D': // Pausing / Decelerating
setStatusLabel("Pausing", "warning");
printing = true;
paused = true;
break;
case 'S': // Paused / Stopped
setStatusLabel("Paused", "info");
printing = true;
paused = true;
break;
case 'R': // Resuming
setStatusLabel("Resuming", "warning");
printing = true;
paused = true;
break;
case 'P': // Printing
setStatusLabel("Printing", "success");
printing = true;
break;
case 'B': // Busy
setStatusLabel("Busy", "warning");
break;
case 'I': // Idle
setStatusLabel("Idle", "default");
break;
}
setPrintStatus(printing);
setPauseStatus(paused);
justConnected = false;
// Set homed axes
setAxesHomed(status.coords.axesHomed);
// Update extruder drives
if (status.coords.extr.length != numExtruderDrives) {
numExtruderDrives = status.coords.extr.length;
needGuiUpdate = true;
}
for(var i=1; i<=numExtruderDrives; i++) {
$("#td_extr_" + i).html(status.coords.extr[i - 1].toFixed(1));
}
if (numExtruderDrives > 0) {
$("#td_extr_total").html(status.coords.extr.reduce(function(a, b) { return a + b; }));
} else {
$("#td_extr_total").html("n/a");
}
// XYZ coordinates
if (geometry == "delta" && !status.coords.axesHomed[0]) {
$("#td_x, #td_y, #td_z").html("n/a");
} else {
$("#td_x").html(status.coords.xyz[0].toFixed(2));
$("#td_y").html(status.coords.xyz[1].toFixed(2));
$("#td_z").html(status.coords.xyz[2].toFixed(2));
}
// Current Tool
if (lastStatusResponse != undefined && lastStatusResponse.currentTool != status.currentTool) {
var btn = $("div.panel-body[data-tool='" + lastStatusResponse.currentTool + "'] > button.btn-select-tool");
btn.attr("title", T("Select this tool"));
btn.find("span:last-child").text(T("Select"));
btn.find("span.glyphicon").removeClass("glyphicon-remove").addClass("glyphicon-pencil");
btn = $("div.panel-body[data-tool='" + status.currentTool + "'] > button.btn-select-tool");
btn.attr("title", T("Select this tool"));
btn.find("span.glyphicon").removeClass("glyphicon-remove").addClass("glyphicon-pencil");
btn.find("span:last-child").text(T("Deselect"));
}
// Output
if (status.hasOwnProperty("output")) {
if (status.output.hasOwnProperty("beepDuration") && status.output.hasOwnProperty("beepFrequency")) {
beep(status.output.beepFrequency, status.output.beepDuration);
}
if (status.output.hasOwnProperty("message")) {
showMessage("envelope", T("Message from Duet firmware"), status.output.message, "md");
}
}
// ATX Power
setATXPower(status.params.atxPower);
// Fan Control
if (!fanSliderActive && (lastStatusResponse == undefined || $("#slider_fan_print").slider("getValue") != status.params.fanPercent)) {
if ($("#override_fan").is(":checked") && settings.showFanControl) {
sendGCode("M106 S" + ($("#slider_fan_print").slider("getValue") / 100.0));
} else {
$("#slider_fan_control").slider("setValue", status.params.fanPercent);
$("#slider_fan_print").slider("setValue", status.params.fanPercent);
}
}
// Speed Factor
if (!speedSliderActive && (lastStatusResponse == undefined || $("#slider_speed").slider("getValue") != status.params.speedFactor)) {
$("#slider_speed").slider("setValue", status.params.speedFactor);
}
if (!extrSliderActive) {
for(var i=0; i<status.params.extrFactors.length; i++) {
var extrSlider = $("#slider_extr_" + (i + 1));
if (lastStatusResponse == undefined || extrSlider.slider("getValue") != status.params.extrFactors) {
extrSlider.slider("setValue", status.params.extrFactors[i]);
}
}
}
// Fetch the latest G-Code response from the server
if (lastStatusResponse == undefined || lastStatusResponse.seq != status.seq) {
$.ajax("rr_reply", {
dataType: "html",
success: function(response) {
response = response.trim();
// Log message (if necessary)
var logThis = (response != "");
logThis |= (lastSendGCode != "" && (lastGCodeFromInput || settings.logSuccess));
if (logThis) {
// Log this message in the console
var style = (response == "") ? "success" : "info";
var content = (lastSendGCode != "") ? "<strong>" + lastSendGCode + "</strong><br/>" : "";
if (response.match("^Error: ") != null) {
style = "warning";
response = response.replace(/Error:/g, "<strong>Error:</strong>");
}
content += response.replace(/\n/g, "<br/>")
log(style, content);
// If it the console isn't visible and if it was caused by an ordinary input, show message dialog
if (lastGCodeFromInput && lastSendGCode != "" && response != "" && currentPage != "console") {
var icon = (style == "warning" ? "exclamation-sign" : "info-sign");
if (response.match("^Error: ") != null) {
showMessage("warning-sign", T("{0} returned an error", lastSendGCode), response.substring(6).replace(/\n/g, "<br/>"), "md");
} else {
showMessage("info-sign", lastSendGCode, response.replace(/\n/g, "<br/>"), "md");
}
}
}
// Reset info about last sent G-Code
lastSendGCode = "";
lastGCodeFromInput = false;
}
});
}
// Sensors
setProbeValue(status.sensors.probeValue, status.sensors.probeSecondary);
$("#td_fanrpm").html(status.sensors.fanRPM);
// Heated bed
var bedTemp = undefined;
if (status.temps.hasOwnProperty("bed")) {
bedTemp = status.temps.bed.current;
setCurrentTemperature("bed", status.temps.bed.current);
setTemperatureInput("bed", status.temps.bed.active, 1);
setHeaterState("bed", status.temps.bed.state, status.currentTool);
} else if (heatedBed) {
heatedBed = 0;
needGuiUpdate = true;
}
// Chamber
var chamberTemp = undefined;
if (status.temps.hasOwnProperty("chamber")) {
needGuiUpdate |= (chamber == 0);
chamber = 1;
chamberTemp = status.temps.chamber.current;
setCurrentTemperature("chamber", chamberTemp);
setTemperatureInput("chamber", status.temps.chamber.active, 1);
setHeaterState("chamber", status.temps.chamber.state, status.currentTool);
} else if (chamber) {
chamber = 0;
needGuiUpdate = true;
}
// Heads
if (status.temps.heads.current.length != numHeads) {
numHeads = status.temps.heads.current.length;
needGuiUpdate = true;
}
for(var i=0; i<status.temps.heads.current.length; i++) {
setCurrentTemperature(i + 1, status.temps.heads.current[i]);
setTemperatureInput(i + 1, status.temps.heads.active[i], 1);
setTemperatureInput(i + 1, status.temps.heads.standby[i], 0);
setHeaterState(i + 1, status.temps.heads.state[i], status.currentTool);
}
recordHeaterTemperatures(bedTemp, chamberTemp, status.temps.heads.current);
/*** Print status response ***/
if (isPrinting && !isPaused && status.hasOwnProperty("fractionPrinted")) {
if (haveFileInfo) {
var progress = 100, progressText = [];
// Get the current layer progress text
if (fileInfo.height > 0 && fileInfo.layerHeight > 0) {
var numLayers;
if (status.firstLayerHeight > 0) {
numLayers = ((fileInfo.height - status.firstLayerHeight) / fileInfo.layerHeight) + 1;
} else {
numLayers = (fileInfo.height / fileInfo.layerHeight);
}
numLayers = numLayers.toFixed();
progressText.push(T("Layer: {0} of {1}", status.currentLayer, numLayers));
}
// Try to calculate the progress by checking the filament usage
if (fileInfo.filament.length > 0) {
var totalFileFilament = (fileInfo.filament.reduce(function(a, b) { return a + b; })).toFixed(1);
var totalRawFilament = (status.extrRaw.reduce(function(a, b) { return a + b; })).toFixed(1);
progress = ((totalRawFilament / totalFileFilament) * 100.0).toFixed(1);
progressText.push(T("Filament Usage: {0}mm of {1}mm", totalRawFilament, totalFileFilament));
if (true) { // TODO: make this optional
progressText[progressText.length - 1] += " " + T("({0}mm remaining)", (totalFileFilament - totalRawFilament).toFixed(1));
}
}
// Otherwise by comparing the current Z position to the total height
else if (fileInfo.height > 0) {
progress = ((status.coords.xyz[2] / fileInfo.height) * 100.0).toFixed(1);
if (progress > 100) {
progress = 100;
}
}
// Use the file-based progress as a fallback option
else {
progress = status.fractionPrinted;
if (progress < 0) {
progress = 100;
}
}
// Ensure we stay within reasonable boundaries
if (progress < 0) {
progress = 0;
} else if (progress > 100) {
progress = 100;
}
// Update info texts
setProgress(progress, T("Printing {0}, {1}% Complete", fileInfo.fileName, progress),
(progressText.length > 0) ? progressText.reduce(function(a, b) { return a + ", " + b; }) : "");
} else {
if (status.fractionPrinted > 0) {
setProgress(status.fractionPrinted, T("{0}% Complete", status.fractionPrinted), "");
} else {
setProgress(100, T("{0}% Complete", 100), "");
}
}
// Print Chart
if (status.currentLayer > 1) {
var realPrintTime = (status.printDuration - status.warmUpDuration - status.firstLayerDuration);
if (layerData.length == 0) {
addLayerData(status.firstLayerDuration);
if (status.currentLayer > 2) { // add avg values on reconnect
realPrintTime -= status.currentLayerTime;
for(var layer=2; layer<status.currentLayer; layer++) {
addLayerData(realPrintTime / status.currentLayer);
}
}
if (status.currentLayer == 2) {
lastLayerPrintDuration = 0;
} else {
lastLayerPrintDuration = realPrintTime;
}
} else if (status.currentLayer > layerData.length) { // there are two entries for the first layer
addLayerData(realPrintTime - lastLayerPrintDuration);
lastLayerPrintDuration = realPrintTime;
}
} else if (layerData.length > 0) {
layerData = [];
maxLayerTime = lastLayerPrintDuration = 0;
drawPrintChart();
}
// Print Estimations
if (haveFileInfo && fileInfo.filament.length > 0) {
setTimeLeft("filament", status.timesLeft.filament);
} else {
setTimeLeft("filament", undefined);
}
setTimeLeft("file", status.timesLeft.file);
if (haveFileInfo && fileInfo.height > 0) {
setTimeLeft("layer", status.timesLeft.layer);
} else {
setTimeLeft("layer", undefined);
}
// First Layer Duration
if (status.firstLayerDuration > 0) {
$("#td_fl_time").html(convertSeconds(status.firstLayerDuration));
} else if (status.warmUpDuration > 0) {
$("#td_fl_time").html(convertSeconds(status.printDuration - status.warmUpDuration));
} else {
$("#td_fl_time").html("n/a");
}
// First Layer Height
if (status.firstLayerHeight > 0) {
$("#td_fl_height").html(status.firstLayerHeight + " mm");
} else {
$("#td_fl_height").html("n/a");
}
// Current Layer Time
if (status.currentLayer > 1) {
if (status.currentLayerTime > 0) {
$("#td_curr_layer").html(convertSeconds(status.currentLayerTime));
} else {
$("#td_curr_layer").html("n/a");
}
$(".col-layertime").removeClass("hidden");
}
// Print Duration
if (status.printDuration > 0) {
$("#td_print_duration").html(convertSeconds(status.printDuration));
} else {
$("#td_print_duration").html("n/a");
}
// Warm-Up Time
if (status.warmUpDuration > 0 || status.firstLayerHeight > 0) {
if (status.warmUpDuration == 0) {
$("#td_warmup_time").html(T("none"));
} else {
$("#td_warmup_time").html(convertSeconds(status.warmUpDuration));
}
} else if (status.printDuration > 0 && haveFileInfo && fileInfo.filament.length) {
$("#td_warmup_time").html(convertSeconds(status.printDuration));
} else {
$("#td_warmup_time").html("n/a");
}
}
// Update the GUI when we have processed the whole status response
if (needGuiUpdate) {
updateGui();
}
drawTemperatureChart();
// Set timer for next status update
if (status.status != 'H') {
setTimeout(updateStatus, settings.updateInterval);
} else {
log("danger", "<strong>" + T("Emergency Stop!") + "</strong>");
setTimeout(function() {
connect(sessionPassword, false);
}, settings.haltedReconnectDelay);
}
// Save the last status response
lastStatusResponse = status;
}
});
}
function getConfigResponse() {
$.ajax("rr_config", {
dataType: "json",
success: function(response) {
configResponse = response;
$("#firmware_name").text(response.firmwareName);
$("#firmware_version").text(response.firmwareVersion + " (" + response.firmwareDate + ")");
$("#div_config").html(response.configFile.replace(/\n/g, "<br/>"));
}
});
}
function updateGCodeFiles() {
if (!isConnected) {
gcodeUpdateIndex = -1;
$(".span-refresh-files").addClass("hidden");
clearGCodeDirectory();
clearGCodeFiles();
return;
}
if (gcodeUpdateIndex == -1) {
gcodeUpdateIndex = 0;
gcodeLastDirectory = undefined;
clearGCodeFiles();
$.ajax("rr_files?dir=" + encodeURIComponent(currentGCodeDirectory), {
dataType: "json",
success: function(response) {
if (isConnected) {
knownGCodeFiles = response.files.sort(function (a, b) {
return a.toLowerCase().localeCompare(b.toLowerCase());
});
for(var i=0; i<knownGCodeFiles.length; i++) {
addGCodeFile(knownGCodeFiles[i]);
}
if (knownGCodeFiles.length == 0) {
if (currentPage == "files") {
$(".span-refresh-files").removeClass("hidden");
}
} else {
updateGCodeFiles();
}
}
}
});
} else if (gcodeUpdateIndex < knownGCodeFiles.length) {
var row = $("#table_gcode_files tr[data-item='" + knownGCodeFiles[gcodeUpdateIndex] + "']");
$.ajax("rr_fileinfo?name=" + encodeURIComponent(currentGCodeDirectory + "/" + knownGCodeFiles[gcodeUpdateIndex]), {
dataType: "json",
row: row,
success: function(response) {
if (!isConnected || this.row == undefined) {
return;
}
gcodeUpdateIndex++;
if (response.err == 0) { // File
setGCodeFileItem(this.row, response.size, response.height, response.layerHeight, response.filament, response.generatedBy);
} else { // Directory
setGCodeDirectoryItem(this.row);
}
if (currentPage == "files") {
if (gcodeUpdateIndex >= knownGCodeFiles.length) {
$(".span-refresh-files").removeClass("hidden");
} else {
updateGCodeFiles();
}
}
}
});
}
}
function updateMacroFiles() {
if (!isConnected) {
macroUpdateIndex = -1;
$(".span-refresh-macros").addClass("hidden");
clearMacroDirectory();
clearMacroFiles();
return;
}
if (macroUpdateIndex == -1) {
macroUpdateIndex = 0;
macroLastDirectory = undefined;
clearMacroFiles();
$.ajax("rr_files?dir=" + encodeURIComponent(currentMacroDirectory), {
dataType: "json",
success: function(response) {
if (isConnected) {
knownMacroFiles = response.files.sort(function (a, b) {
return a.toLowerCase().localeCompare(b.toLowerCase());
});
for(var i=0; i<knownMacroFiles.length; i++) {
addMacroFile(knownMacroFiles[i]);
}
if (knownMacroFiles.length == 0) {
if (currentPage == "macros") {
$(".span-refresh-macros").removeClass("hidden");
}
} else {
updateMacroFiles();
}
}
}
});
} else if (macroUpdateIndex < knownMacroFiles.length) {
var row = $("#table_macro_files tr[data-macro='" + knownMacroFiles[macroUpdateIndex] + "']");
$.ajax("rr_fileinfo?name=" + encodeURIComponent(currentMacroDirectory + "/" + knownMacroFiles[macroUpdateIndex]), {
dataType: "json",
row: row,
success: function(response) {
if (!isConnected || this.row == undefined) {
return;
}
macroUpdateIndex++;
if (response.err == 0) { // File
setMacroFileItem(this.row, response.size);
} else { // Directory
setMacroDirectoryItem(this.row);
}
if (macroUpdateIndex >= knownMacroFiles.length) {
if (currentPage == "macros") {
$(".span-refresh-macros").removeClass("hidden");
}
} else if (currentPage == "control" || currentPage == "macros") {
updateMacroFiles();
}
}
});
}
}
// Send G-Code directly to the firmware
function sendGCode(gcode, fromInput) {
lastSendGCode = gcode;
lastGCodeFromInput = (fromInput != undefined && fromInput);
// Although rr_gcode gives us a JSON response, it doesn't provide any results.
// We only need to worry about an AJAX error event.
$.ajax("rr_gcode?gcode=" + encodeURIComponent(gcode), {
dataType: "json"
});
}
/* AJAX Events */
$(document).ajaxSend(function(event, jqxhr, settings) {
settings.timeout = settings.maxRequestTime;
ajaxRequests.push(jqxhr);
});
$(document).ajaxComplete(function(event, jqxhr, settings) {
ajaxRequests = $.grep(ajaxRequests, function(item) { item != jqxhr; });
});
$(document).ajaxError(function(event, jqxhr, settings, thrownError) {
if (thrownError == "abort") {
// Ignore this error if this request was cancelled intentionally
return;
}
if (isConnected) {
var msg = T("An AJAX error was reported, so the current session has been terminated.<br/><br/>Please check if your printer is still on and try to connect again.");
if (thrownError != "") {
msg += "<br/></br>" + T("Error reason: {0}", thrownError);
}
showMessage("warning-sign", T("Communication Error"), msg, "md");
disconnect();
}
});
/* Settings */
function checkBoundaries(value, defaultValue, minValue, maxValue) {
if (isNaN(value)) {
return defaultValue;
}
if (value < minValue) {
return minValue;
}
if (value > maxValue) {
return maxValue;
}
return value;
}
function loadSettings() {
$.cookie.JSON = true;
var loadedSettings = $.cookie("settings");
if (loadedSettings != undefined && loadedSettings.length > 0) {
loadedSettings = JSON.parse(loadedSettings);
// UI Timing
if (loadedSettings.hasOwnProperty("updateInterval")) {
settings.updateInterval = loadedSettings.updateInterval;
}
if (loadedSettings.hasOwnProperty("haltedReconnectDelay")) {
settings.haltedReconnectDelay = loadedSettings.haltedReconnectDelay;
}
if (loadedSettings.hasOwnProperty("extendedStatusInterval")) {
settings.extendedStatusInterval = loadedSettings.extendedStatusInterval;
}
if (loadedSettings.hasOwnProperty("maxRequestTime")) {
settings.maxRequestTime = loadedSettings.maxRequestTime;
}
// Behavior
if (loadedSettings.hasOwnProperty("autoConnect")) {
settings.autoConnect = loadedSettings.autoConnect;
}
if (loadedSettings.hasOwnProperty("halfZMovements")) {
settings.halfZMovements = loadedSettings.halfZMovements;
}
if (loadedSettings.hasOwnProperty("logSuccess")) {
settings.logSuccess = loadedSettings.logSuccess;
}
if (loadedSettings.hasOwnProperty("uppercaseGCode")) {
settings.uppercaseGCode = loadedSettings.uppercaseGCode;
}
if (loadedSettings.hasOwnProperty("useKiB")) {
settings.useKiB = loadedSettings.useKiB;
}
if (loadedSettings.hasOwnProperty("showFanControl")) {
settings.showFanControl = loadedSettings.showFanControl;
}
if (loadedSettings.hasOwnProperty("showATXControl")) {
settings.showATXControl = loadedSettings.showATXControl;
}
if (loadedSettings.hasOwnProperty("confirmStop")) {
settings.confirmStop = loadedSettings.confirmStop;
}
if (loadedSettings.hasOwnProperty("moveFeedrate")) {
settings.moveFeedrate = loadedSettings.moveFeedrate;
}
// Default list items
if (loadedSettings.hasOwnProperty("defaultActiveTemps")) {
settings.defaultActiveTemps = loadedSettings.defaultActiveTemps;
}
if (loadedSettings.hasOwnProperty("defaultStandbyTemps")) {
settings.defaultStandbyTemps = loadedSettings.defaultStandbyTemps;
}
if (loadedSettings.hasOwnProperty("defaultBedTemps")) {
settings.defaultBedTemps = loadedSettings.defaultBedTemps;
}
if (loadedSettings.hasOwnProperty("defaultGCodes")) {
settings.defaultGCodes = loadedSettings.defaultGCodes;
}
// Spools
if (loadedSettings.hasOwnProperty("bowdenLength")) {
settings.bowdenLength = loadedSettings.bowdenLength;
}
// Other (fallback in case language.xml couldn't be loaded)
if (loadedSettings.hasOwnProperty("language")) {
settings.language = loadedSettings.language;
}
// Apply new settings
applySettings();
}
}
function saveSettings() {
// Appearance and Behavior
settings.autoConnect = $("#auto_connect").is(":checked");
settings.halfZMovements = $("#half_z").is(":checked");
settings.logSuccess = $("#log_success").is(":checked");
settings.uppercaseGCode = $("#uppercase_gcode").is(":checked");
settings.useKiB = $("#use_kib").is(":checked");
settings.showFanControl = $("#fan_sliders").is(":checked");
settings.showATXControl = $("#show_atx").is(":checked");
settings.confirmStop = $("#confirm_stop").is(":checked");
settings.moveFeedrate = checkBoundaries($("#move_feedrate").val(), defaultSettings.moveFeedrate, 0);
if (settings.language != $("#btn_language").data("language")) {
showMessage("info-sign", T("Language has changed"), T("You have changed the current language.<br/><br/>Please reload the web interface to apply this change."), "md");
}
settings.language = $("#btn_language").data("language");
// UI Timing
settings.updateInterval = checkBoundaries($("#update_interval").val(), defaultSettings.updateInterval, 50);
settings.extendedStatusInterval = checkBoundaries($("#extended_status_interval").val(), defaultSettings.extendedStatusInterval, 1, 99999);
settings.haltedReconnectDelay = checkBoundaries($("#reconnect_delay").val(), defaultSettings.haltedReconnectDelay, 1000);
settings.maxRequestTime = checkBoundaries($("#ajax_timeout").val(), defaultSettings.maxRequestTime, 100);
// Default G-Codes
settings.defaultGCodes = [];
$("#table_gcodes > tbody > tr").each(function() {
settings.defaultGCodes.push([$(this).find("label").text(), $(this).find("td:eq(1)").text()]);
});
settings.defaultGCodes = settings.defaultGCodes.sort(function(a, b) {
if (a[0][0] != b[0][0]) {
return a[0].charCodeAt(0) - b[0].charCodeAt(0);
}
var x = a[0].match(/(\d+)/g)[0];
var y = b[0].match(/(\d+)/g)[0];
if (x == undefined || y == undefined) {
return parseInt(a[0]) - parseInt(b[0]);
}
return x - y;
});
// Default Heater Temperatures
settings.defaultActiveTemps = [];
$("#ul_active_temps > li").each(function() {
settings.defaultActiveTemps.push($(this).data("temperature"));
});
settings.defaultActiveTemps = settings.defaultActiveTemps.sort(function(a, b) { return a - b; });
settings.defaultStandbyTemps = [];
$("#ul_standby_temps > li").each(function() {
settings.defaultStandbyTemps.push($(this).data("temperature"));
});
settings.defaultStandbyTemps = settings.defaultStandbyTemps.sort(function(a, b) { return a - b; });
// Default Bed Temperatures
settings.defaultBedTemps = [];
$("#ul_bed_temps > li").each(function() {
settings.defaultBedTemps.push($(this).data("temperature"));
});
settings.defaultBedTemps = settings.defaultBedTemps.sort(function(a, b) { return a - b; });
// Save Settings
$.cookie("settings", JSON.stringify(settings), { expires: 999999 });
}
function applySettings() {
/* Behavior */
// Half Z Movements
var decreaseChildren = $("#td_decrease_z a");
var decreaseVal = (settings.halfZMovements) ? 50 : 100;
decreaseChildren.each(function(index) {
decreaseChildren.eq(index).data("z", decreaseVal * (-1)).contents().last().replaceWith(" Z-" + decreaseVal);
decreaseVal /= 10;
});
var increaseChildren = $("#td_increase_z a");
var increaseVal = (settings.halfZMovements) ? 0.05 : 0.1;
increaseChildren.each(function(index) {
increaseChildren.eq(index).data("z", increaseVal).contents().first().replaceWith("Z+" + increaseVal + " ");
increaseVal *= 10;
});
// Show/Hide Fan Control
if (settings.showFanControl) {
$(".fan-control").removeClass("hidden");
} else {
$(".fan-control").addClass("hidden");
}
// Show/Hide ATX Power
if (settings.showATXControl) {
$(".atx-control").removeClass("hidden");
} else {
$(".atx-control").addClass("hidden");
}
// Possibly hide entire misc control panel
if (!settings.showFanControl && !settings.showATXControl) {
$("#panel_control_misc").addClass("hidden");
} else {
$("#panel_control_misc").removeClass("hidden");
}
/* Set values on the Settings page */
// Appearance and Behavior
$("#auto_connect").prop("checked", settings.autoConnect);
$("#half_z").prop("checked", settings.halfZMovements);
$("#log_success").prop("checked", settings.logSuccess);
$("#uppercase_gcode").prop("checked", settings.uppercaseGCode);
$("#use_kib").prop("checked", settings.useKiB);
$("#fan_sliders").prop("checked", settings.showFanControl);
$("#show_atx").prop("checked", settings.showATXControl);
$("#confirm_stop").prop("checked", settings.confirmStop);
$("#move_feedrate").val(settings.moveFeedrate);
// UI Timing
$("#update_interval").val(settings.updateInterval);
$("#extended_status_interval").val(settings.extendedStatusInterval);
$("#reconnect_delay").val(settings.haltedReconnectDelay);
$("#ajax_timeout").val(settings.maxRequestTime);
/* Default list items */
// Default head temperatures
clearHeadTemperatures();
settings.defaultActiveTemps.forEach(function(temp) {
addHeadTemperature(temp, "active");
});
settings.defaultStandbyTemps.forEach(function(temp) {
addHeadTemperature(temp, "standby");
});
// Default bed temperatures
clearBedTemperatures();
settings.defaultBedTemps.forEach(function(temp) {
addBedTemperature(temp);
});
// Default G-Codes
clearDefaultGCodes();
settings.defaultGCodes.forEach(function(entry) {
addDefaultGCode(entry[1], entry[0]);
});
/* Spools */
$("#input_bowden_length").val(settings.bowdenLength);
}
/* GUI */
$(document).ready(function() {
disableControls();
resetGuiData();
updateGui();
$("#web_version").append(", JS: " + jsVersion.toFixed(2));
$.ajax("language.xml", {
type: "GET",
dataType: "xml",
global: false,
error: function() {
pageLoadComplete();
},
success: function(response) {
translationData = response;
// We need to load the language here, because we don't want to translate dynamic entries
var loadedSettings = $.cookie("settings");
if (loadedSettings != undefined && loadedSettings.length > 0) {
loadedSettings = JSON.parse(loadedSettings);
if (loadedSettings.hasOwnProperty("language")) {
settings.language = loadedSettings.language;
}
}
$("#dropdown_language ul > li:not(:first-child)").remove();
for(var i=0; i<translationData.children[0].children.length; i++) {
var id = translationData.children[0].children[i].tagName;
var name = translationData.children[0].children[i].attributes["name"].value;
$("#dropdown_language ul").append( '<li><a data-language="' + id + '" href="#">' + name + '</a></li>');
if (settings.language == id) {
$("#btn_language > span:first-child").text(name);
}
}
translatePage();
pageLoadComplete();
}
});
});
function pageLoadComplete() {
loadSettings();
applySettings();
log("info", "<strong>" + T("Page Load complete!") + "</strong>");
if (settings.autoConnect) {
// Users might want to connect automatically once the page has loaded
connect(sessionPassword, true);
}
}
function enableControls() {
$("nav input, #div_heaters input, #main_content input").prop("disabled", false); // Generic inputs
$("#page_tools label").removeClass("disabled"); // and on Settings page
$(".btn-emergency-stop, .gcode-input button[type=submit], .gcode").removeClass("disabled"); // Navbar
$(".bed-temp, .gcode, .heater-temp, .btn-upload").removeClass("disabled"); // List items and Upload buttons
$("#mobile_home_buttons button, #btn_homeall, #table_move_head a").removeClass("disabled"); // Move buttons
$("#panel_extrude label.btn, #panel_extrude button").removeClass("disabled"); // Extruder Control
$("#panel_control_misc label.btn").removeClass("disabled"); // ATX Power
$("#slider_fan_control").slider("enable"); // Fan Control
$("#page_print .checkbox").removeClass("disabled"); // Print Control
$("#slider_fan_print").slider("enable"); // Fan Control
$("#slider_speed").slider("enable"); // Speed Factor
for(var extr=1; extr<=5; extr++) {
$("#slider_extr_" + extr).slider("enable"); // Extrusion Factors
}
$(".online-control").removeClass("hidden"); // G-Code/Macro Files
}
function disableControls() {
$("nav input, #div_heaters input, #main_content input").prop("disabled", true); // Generic inputs
$("#page_general input, #page_listitems input").prop("disabled", false); // ... except ...
$("#page_tools label").addClass("disabled"); // ... for Settings
$(".btn-emergency-stop, .gcode-input button[type=submit], .gcode").addClass("disabled"); // Navbar
$("#extruder_drives").addClass("disabled"); // Info Panels
$(".bed-temp, .gcode, .heater-temp, .btn-upload").addClass("disabled"); // List items and Upload buttons
$("#mobile_home_buttons button, #btn_homeall, #table_move_head a").addClass("disabled"); // Move buttons
$("#panel_extrude label.btn, #panel_extrude button").addClass("disabled"); // Extruder Control
$("#panel_control_misc label.btn").addClass("disabled"); // ATX Power
$("#slider_fan_control").slider("disable"); // Fan Control
$("#btn_pause, #page_print .checkbox").addClass("disabled"); // Print Control
$("#slider_fan_print").slider("disable"); // Fan Control
$("#slider_speed").slider("disable"); // Speed Factor
for(var extr=1; extr<=5; extr++) {
$("#slider_extr_" + extr).slider("disable"); // Extrusion Factors
}
$(".online-control").addClass("hidden"); // G-Code/Macro Files
}
function updateGui() {
// Visibility for Heater Temperatures
if (heatedBed) { // Heated Bed
$("#tr_bed").removeClass("hidden");
} else {
$("#tr_bed").addClass("hidden");
}
if (chamber) { // Chamber
$("#tr_chamber").removeClass("hidden");
} else {
$("#tr_chamber").addClass("hidden");
}
for(var i=1; i<=5; i++) { // Heads (Heaters)
if (i <= numHeads) {
$("#tr_head_" + i).removeClass("hidden");
} else {
$("#tr_head_" + i).addClass("hidden");
}
}
// Visibility for Extruder Drive columns
for(var i=1; i<=5; i++) {
if (i <= numExtruderDrives) {
$(".extr-" + i).removeClass("hidden");
$("#slider_extr_" + i).slider("relayout");
} else {
$(".extr-" + i).addClass("hidden");
}
}
if (numExtruderDrives > 0) {
$("#row_status_2").removeClass("hidden");
} else {
$("#row_status_2").addClass("hidden");
}
// Add Zero Extruder Drive buttons
if (isConnected) {
$("#extruder_drives").removeClass("disabled");
$("#ul_extruder_dropdown").html('<li><a href="#" class="zero-extruder" data-target="all">' + T("Zero All Drives") + '</li><li class="divider"></li>');
for(var i=1; i<= numExtruderDrives; i++) {
$("#ul_extruder_dropdown").append('<li><a href="#" class="zero-extruder" data-target="' + i + '">' + T("Zero Extruder Drive {0}", i) + '</a></li>');
}
$(".zero-extruder").off("click").click(function(e) {
if (lastStatusResponse != undefined) {
var gcode = "G92 E", target = $(this).data("target");
for(var i=1; i<=numExtruderDrives; i++) {
if (target != "all" && target != i) {
gcode += lastStatusResponse.coords.extr[i - 1];
} else {
gcode += "0";
}
if (i < numExtruderDrives) {
gcode += ":";
}
}
sendGCode(gcode);
}
e.preventDefault();
});
}
// Do some rearrangement for the panels if we have less than or exactly three extruder drives
if (numExtruderDrives <= 3) {
$(".div-head-temp").removeClass("hidden-sm");
$("#col_extr_totals, #td_extr_total").addClass("hidden");
for(var i=1; i<=3; i++) {
$("th.extr-" + i).html(T("Drive " + i));
$("#row_status_2 .extr-" + i).removeClass("hidden-md");
}
$("#div_heaters").removeClass("col-sm-5").addClass("col-sm-6");
$("#div_temp_chart").removeClass("col-lg-3").addClass("col-lg-5");
$("#div_status").removeClass("col-sm-7 col-lg-5").addClass("col-sm-6 col-lg-3");
} else {
$(".div-head-temp").addClass("hidden-sm");
$("#col_extr_totals, #td_extr_total").removeClass("hidden");
for(var i=1; i<=3; i++) {
$("th.extr-" + i).html(T("D" + i));
$("#row_status_2 .extr-" + i).addClass("hidden-md");
}
$("#div_heaters").removeClass("col-sm-6").addClass("col-sm-5");
$("#div_temp_chart").removeClass("col-lg-5").addClass("col-lg-3");
$("#div_status").removeClass("col-sm-6 col-lg-3").addClass("col-sm-7 col-lg-5");
}
// Charts
resizeCharts();
drawTemperatureChart();
drawPrintChart();
}
function resetGui() {
// Charts
drawTemperatureChart();
drawPrintChart();
// Navbar
$(".machine-name").html("Duet Web Control");
setStatusLabel("Disconnected", "default");
// Heater Temperatures
$("#table_heaters tr > th:first-child > span").text("");
setCurrentTemperature("bed", undefined);
setTemperatureInput("bed", 0, 1);
setCurrentTemperature("chamber", undefined);
setTemperatureInput("chamber", 0, 1);
for(var i=1; i<=5; i++) {
setCurrentTemperature(i, undefined);
setTemperatureInput(i, 0, 1);
setTemperatureInput(i, 0, 0);
}
// Status fields
$("#td_x, #td_y, #td_z").text("n/a");
for(var i=1; i<=numExtruderDrives; i++) {
$("#td_extr_" + i).text("n/a");
}
$("#td_extr_total").text("n/a");
setProbeValue(-1, undefined);
$("#td_fanrpm").text("n/a");
// Control page
setAxesHomed([1,1,1]);
setATXPower(false);
$('#slider_fan_control').slider("setValue", 35);
// Print Status
$(".row-progress, .col-layertime").addClass("hidden");
setProgress(100, "", "");
$("#override_fan, #auto_sleep").prop("checked", false);
$('#slider_fan_print').slider("setValue", 35);
$("#page_print dd, #panel_print_info table td, #table_estimations td").html("n/a");
$('#slider_speed').slider("setValue", 100);
for(var extr=1; extr<=5; extr++) {
$("#slider_extr_" + extr).slider("setValue", 100);
}
// G-Code Console is not cleared automatically
// G-Code Files
updateGCodeFiles();
// Macro Files
updateMacroFiles();
// Settings
$("#firmware_name, #firmware_version").html("n/a");
$("#div_config").html('<h1 class="text-center text-muted">' + T("Connect to your duet to display the configuration file") + '</h1>');
// Modal dialogs
$("#modal_upload").modal("hide");
}
/* Dynamic GUI Events */
$("body").on("click", ".bed-temp", function(e) {
if ($(this).parents("#tr_bed").length > 0) {
sendGCode("M140 S" + $(this).data("temp"));
} else {
sendGCode("M141 S" + $(this).data("temp"));
}
e.preventDefault();
});
$("body").on("click", ".btn-macro", function(e) {
var directory = $(this).data("directory");
if (directory != undefined) {
var dropdown = $(this).parent().children("ul");
dropdown.css("width", $(this).outerWidth());
loadMacroDropdown(directory, dropdown);
dropdown.dropdown();
} else {
sendGCode("M98 P" + $(this).data("macro"));
}
e.preventDefault();
});
$("body").on("click", ".btn-print-file, .gcode-file", function(e) {
var file = $(this).parents("tr").data("file");
showConfirmationDialog(T("Start Print"), T("Do you want to print <strong>{0}</strong>?", file), function() {
waitingForPrintStart = true;
if (currentGCodeDirectory == "/gcodes") {
sendGCode("M32 " + file);
} else {
sendGCode("M32 " + currentGCodeDirectory.substring(8) + "/" + file);
}
});
e.preventDefault();
});
$("body").on("click", ".btn-delete-file", function(e) {
var row = $(this).parents("tr");
var file = row.data("file");
showConfirmationDialog(T("Delete File"), T("Are you sure you want to delete <strong>{0}</strong>?", file), function() {
$.ajax("rr_delete?name=" + encodeURIComponent(currentGCodeDirectory + "/" + file), {
dataType: "json",
row: row,
file: file,
success: function(response) {
if (response.err == 0) {
this.row.remove();
if ($("#table_gcode_files tbody").children().length == 0) {
gcodeUpdateIndex = -1;
updateGCodeFiles();
}
} else {
showMessage("warning-sign", T("Deletion failed"), T("<strong>Warning:</strong> Could not delete file <strong>{0}</strong>!", this.file), "md");
}
}
});
});
e.preventDefault();
});
$("body").on("click", ".btn-delete-gcode-directory", function(e) {
var row = $(this).parents("tr");
var directory = row.data("directory");
$.ajax("rr_delete?name=" + encodeURIComponent(currentGCodeDirectory + "/" + directory), {
dataType: "json",
row: row,
directory: directory,
success: function(response) {
if (response.err == 0) {
this.row.remove();
if ($("#table_gcode_files tbody").children().length == 0) {
gcodeUpdateIndex = -1;
updateGCodeFiles();
}
} else {
showMessage("warning-sign", T("Deletion failed"), T("<strong>Warning:</strong> Could not delete directory <strong>{0}</strong>!<br/><br/>Perhaps it isn't empty?", this.directory), "md");
}
}
});
e.preventDefault();
});
$("body").on("click", ".btn-delete-parent", function(e) {
$(this).parents("tr, li").remove();
e.preventDefault();
});
$("body").on("click", ".btn-delete-macro-directory", function(e) {
var row = $(this).parents("tr");
var directory = row.data("directory");
$.ajax("rr_delete?name=" + encodeURIComponent(currentMacroDirectory + "/" + directory), {
dataType: "json",
row: row,
directory: directory,
success: function(response) {
if (response.err == 0) {
this.row.remove();
if ($("#table_macro_files tbody").children().length == 0) {
macroUpdateIndex = -1;
updateMacroFiles();
}
} else {
showMessage("warning-sign", T("Deletion failed"), T("<strong>Warning:</strong> Could not delete directory <strong>{0}</strong>!<br/><br/>Perhaps it isn't empty?", this.directory), "md");
}
}
});
e.preventDefault();
});
$("body").on("click", ".btn-delete-macro", function(e) {
var macroFile = $(this).parents("tr").data("file");
showConfirmationDialog("Delete File", "Are you sure you want to delete <strong>" + macroFile + "</strong>?", function() {
$.ajax("rr_delete?name=" + encodeURIComponent(currentMacroDirectory + "/" + macroFile), {
dataType: "json",
macro: macroFile,
success: function(response) {
if (response.err == 0) {
$("#table_macro_files tr[data-macro='" + macroFile + "'], #panel_macro_buttons button[data-macro='" + currentMacroDirectory + "/" + macroFile + "']").remove();
if ($("#table_macro_files tbody").children().length == 0) {
macroUpdateIndex = -1;
updateMacroFiles();
}
} else {
showMessage("warning-sign", T("Deletion failed"), T("<strong>Warning:</strong> Could not delete macro <strong>{0}</strong>!", this.macro), "md");
}
}
});
});
e.preventDefault();
});
$("body").on("click", ".btn-remove-tool", function(e) {
var tool = $(this).parents("div.panel-body").data("tool");
showConfirmationDialog(T("Delete Tool"), T("Are you sure you wish to remove tool {0}?", tool), function() {
sendGCode("M563 P" + tool + " D-1 H-1");
extendedStatusCounter = settings.extendedStatusInterval;
});
e.preventDefault();
});
$("body").on("click", ".btn-run-macro", function(e) {
sendGCode("M98 P" + currentMacroDirectory + "/" + $(this).parents("tr").data("file"));
e.preventDefault();
});
$("body").on("click", ".btn-select-tool", function(e) {
var tool = $(this).parents("div.panel-body").data("tool");
if (lastStatusResponse != undefined && lastStatusResponse.currentTool == tool) {
sendGCode("T-1");
} else {
sendGCode("T" + tool);
}
e.preventDefault();
});
$("body").on("click", "#dropdown_language a", function(e) {
$("#btn_language > span:first-child").text($(this).text());
$("#btn_language").data("language", $(this).data("language"));
e.preventDefault();
});
$("body").on("click", ".gcode", function(e) {
if (isConnected) {
// If this G-Code isn't performed by a button, treat it as a manual input
sendGCode($(this).data("gcode"), !($(this).is(".btn")));
}
e.preventDefault();
});
$("body").on("click", ".gcode-directory", function(e) {
setGCodeDirectory(currentGCodeDirectory + "/" + $(this).parents("tr").data("directory"));
gcodeUpdateIndex = -1;
updateGCodeFiles();
e.preventDefault();
});
$("body").on("click", ".heater-temp", function(e) {
var inputElement = $(this).parents("div.input-group").find("input");
var activeOrStandby = (inputElement.prop("id").match("active$")) ? "S" : "R";
var temperature = $(this).data("temp");
if (inputElement.prop("id").indexOf("all") == -1) {
var heater = inputElement.prop("id").match("_h(.)_")[1];
getToolsByHeater(heater).forEach(function(tool) {
sendGCode("G10 P" + tool + " " + activeOrStandby + temperature);
});
} else {
if (toolMapping != undefined) {
for(var i=0; i<toolMapping.length; i++) {
var number = (toolMapping[i].hasOwnProperty("number") ? toolMapping[i].number : i + 1);
if ($.inArray(0, toolMapping[i].heaters) == -1) {
// Make sure we don't set temperatures for the heated bed
sendGCode("G10 P" + number + " " + activeOrStandby + $(this).val());
}
}
}
}
e.preventDefault();
});
$("body").on("click", ".macro-directory", function(e) {
setMacroDirectory(currentMacroDirectory + "/" + $(this).parents("tr").data("directory"));
macroUpdateIndex = -1;
updateMacroFiles();
e.preventDefault();
});
$("body").on("click", "#ol_gcode_directory a", function(e) {
setGCodeDirectory($(this).data("directory"));
gcodeUpdateIndex = -1;
updateGCodeFiles();
e.preventDefault();
});
$("body").on("click", "#ol_macro_directory a", function(e) {
setMacroDirectory($(this).data("directory"));
macroUpdateIndex = -1;
updateMacroFiles();
e.preventDefault();
});
$("body").on("click", ".tool", function(e) {
sendGCode("T" + $(this).data("tool"));
e.preventDefault();
});
$("body").on("hidden.bs.popover", function() {
$(this).popover("destroy");
});
/* Static GUI Events */
$("#a_heaters_off").click(function(e) {
if (isConnected) {
// Turn off nozzle heaters
if (toolMapping != undefined) {
for(var i=0; i<toolMapping.length; i++) {
var number = (toolMapping[i].hasOwnProperty("number") ? toolMapping[i].number : i + 1);
var temps = [], tempString = "";
toolMapping[i].heaters.forEach(function() { temps.push("-273.15"); });
tempString = temps.reduce(function(a, b) { return a + ":" + b; });
sendGCode("G10 P" + number + " R" + tempString + " S" + tempString);
}
}
// Turn off bed
if (heatedBed) {
sendGCode("M140 S-273.15");
}
// Turn off chamber
if (chamber) {
sendGCode("M141 S-273.15");
}
}
e.preventDefault();
});
$("#btn_add_gcode").click(function(e) {
var item = '<tr><td><label class="label label-primary">' + $("#input_gcode").val().trim() + '</label></td><td>' + $("#input_gcode_description").val().trim() + '</td><td>';
item += '<button class="btn btn-sm btn-danger btn-delete-parent" title="' + T("Delete this G-Code item") + '">';
item += '<span class="glyphicon glyphicon-trash"></span></button></td></tr>';
$("#table_gcodes").append(item);
e.preventDefault();
});
$("#btn_add_head_temp").click(function(e) {
var temperature = checkBoundaries($("#input_add_head_temp").val(), 0, -273.15, 300);
var type = $('input[name="temp_selection"]:checked').val();
var item = '<li class="list-group-item col-xs-6 col-lg-3" data-temperature="' + temperature + '">' + temperature + ' °C';
item += '<button class="btn btn-danger btn-sm btn-delete-parent pull-right" title="' + T("Delete this temperature item") + '">';
item += '<span class="glyphicon glyphicon-trash"></span></button></li>';
$("#ul_" + type + "_temps").append(item);
e.preventDefault();
});
$("#btn_add_bed_temp").click(function(e) {
var temperature = checkBoundaries($("#input_add_bed_temp").val(), 0, -273.15, 180);
var item = '<li class="list-group-item col-md-6" data-temperature="' + temperature + '">' + temperature + ' °C';
item += '<button class="btn btn-danger btn-sm btn-delete-parent pull-right" title="' + T("Delete this temperature item") + '">';
item += '<span class="glyphicon glyphicon-trash"></span></button></li>';
$("#ul_bed_temps").append(item);
e.preventDefault();
});
$("#btn_add_tool").click(function(e) {
var gcode = "M563 P" + $("#input_tool_number").val();
var drives = $("input[name='tool_drives']:checked");
if (drives != undefined) {
var driveList = [];
drives.each(function() { driveList.push($(this).val()); });
gcode += " D" + driveList.reduce(function(a, b) { return a + ":" + b; });
}
var heaters = $("input[name='tool_heaters']:checked");
if (heaters != undefined) {
var heaterList = [];
heaters.each(function() { heaterList.push($(this).val()); });
gcode += " H" + heaterList.reduce(function(a, b) { return a + ":" + b; });
}
sendGCode(gcode);
extendedStatusCounter = settings.extendedStatusInterval;
e.preventDefault();
});
$("#btn_cancel").click(function() {
sendGCode("M0"); // Stop / Cancel Print
$(this).addClass("disabled");
});
$("#btn_cancel_upload").click(function() {
cancelUpload();
});
$(".btn-connect").click(function() {
if (!isConnected) {
// Attempt to connect with the last-known password first
connect(sessionPassword, true);
} else {
disconnect();
}
});
$(".btn-emergency-stop").click(function() {
if (settings.confirmStop) {
showConfirmationDialog(T("Emergency STOP"), T("This will turn off everything and perform a software reset.<br/><br/>Are you REALLY sure you want to do this?"), function() {
sendGCode("M112\nM999");
});
} else {
sendGCode("M112\nM999");
}
});
$("#btn_clear_log").click(function(e) {
$("#console_log").html("");
log("info", "<strong>" + T("Message Log cleared!") + "</strong>");
e.preventDefault();
});
// TODO: deal with mixing drives
$("#btn_extrude").click(function(e) {
var feedrate = $("#panel_extrude input[name=feedrate]:checked").val() * 60;
var amount = $("#panel_extrude input[name=feed]:checked").val();
sendGCode("M120\nM83\nG1 E" + amount + " F" + feedrate + "\nM121");
});
$("#btn_retract").click(function(e) {
var feedrate = $("#panel_extrude input[name=feedrate]:checked").val() * 60;
var amount = $("#panel_extrude input[name=feed]:checked").val();
sendGCode("M120\nM83\nG1 E-" + amount + " F" + feedrate + "\nM121");
});
$(".btn-hide-info").click(function() {
if ($(this).hasClass("active")) {
$("#row_info").addClass("hidden-xs hidden-sm");
setTimeout(function() {
$(".btn-hide-info").removeClass("active");
}, 100);
} else {
$("#row_info").removeClass("hidden-xs hidden-sm");
setTimeout(function() {
$(".btn-hide-info").addClass("active");
}, 100);
}
$(this).blur();
});
$(".btn-home-x").resize(function() {
if (!$(this).hasClass("hidden")) {
var width = $(this).parent().width();
if (width > 0) {
$("#btn_homeall").css("width", width);
}
}
}).resize();
$("#mobile_home_buttons button, #btn_homeall, #table_move_head a").click(function(e) {
$this = $(this);
if ($this.data("home") != undefined) {
if ($this.data("home") == "all") {
sendGCode("G28");
} else {
sendGCode("G28 " + $this.data("home"));
}
} else {
var moveString = "M120\nG91\nG1";
if ($this.data("x") != undefined) {
moveString += " X" + $this.data("x");
}
if ($this.data("y") != undefined) {
moveString += " Y" + $this.data("y");
}
if ($this.data("z") != undefined) {
moveString += " Z" + $this.data("z");
}
moveString += " F" + settings.moveFeedrate + "\nM121";
sendGCode(moveString);
}
e.preventDefault();
});
$("#btn_load_filament").click(function() {
// Load first 85% of filament at high speed
sendGCode("G1 E" + (settings.bowdenLength * 0.85) + " F6000");
// Then feed last 15% at lower speed
sendGCode("G1 E" + (settings.bowdenLength * 0.15) + " F450");
});
$("#btn_unload_filament").click(function() {
// Start slowly for the first 15% at low speed
sendGCode("G1 E-" + (settings.bowdenLength * 0.15) + " F450");
// Eject last 85% of filament at higher speed
sendGCode("G1 E-" + (settings.bowdenLength * 0.85) + " F6000");
});
$("#btn_new_gcode_directory").click(function() {
showTextInput(T("New directory"), T("Please enter a name:"), function(value) {
$.ajax("rr_mkdir?dir=" + currentGCodeDirectory + "/" + value, {
dataType: "json",
success: function(response) {
if (response.err == 0) {
gcodeUpdateIndex = -1;
updateGCodeFiles();
} else {
showMessage("warning-sign", T("Error"), T("Could not create this directory!"), "sm");
}
}
});
});
});
$("#btn_new_macro_directory").click(function() {
showTextInput(T("New directory"), T("Please enter a name:"), function(value) {
$.ajax("rr_mkdir?dir=" + currentMacroDirectory + "/" + value, {
dataType: "json",
success: function(response) {
if (response.err == 0) {
macroUpdateIndex = -1;
updateMacroFiles();
} else {
showMessage("warning-sign", T("Error"), T("Could not create this directory!"), "sm");
}
}
});
});
});
$("#btn_pause").click(function() {
if (isPaused) {
sendGCode("M24"); // Resume
} else if (isPrinting) {
sendGCode("M25"); // Pause
}
$(this).addClass("disabled");
});
$(".btn-upload").click(function(e) {
$("#input_file_upload").data("type", $(this).data("type")).click();
e.preventDefault();
});
$("#btn_reset_settings").click(function(e) {
showConfirmationDialog(T("Reset Settings"), T("Are you sure you want to revert to Factory Settings?"), function() {
if (defaultSettings.language != settings.language) {
showMessage("info-sign", T("Language has changed"), T("You have changed the current language.<br/><br/>Please reload the web interface to apply this change."), "md");
}
settings = jQuery.extend(true, {}, defaultSettings);
$("#btn_language").data("language", "en").children("span:first-child").text("English");
applySettings();
saveSettings();
});
e.preventDefault();
});
["print", "gcode", "macro", "generic"].forEach(function(type) {
var child = $(".btn-upload[data-type='" + type + "']");
// Drag Enter
child.on("dragover", function(e) {
$(this).removeClass($(this).data("style")).addClass("btn-success");
e.preventDefault();
e.stopPropagation();
});
// Drag Leave
child.on("dragleave", function(e) {
$(this).removeClass("btn-success").addClass($(this).data("style"));
e.preventDefault();
e.stopPropagation();
});
// Drop
child.on("drop", function(e) {
$(this).removeClass("btn-success").addClass($(this).data("style"));
e.preventDefault();
e.stopPropagation();
var files = e.originalEvent.dataTransfer.files;
if (files != null && files.length > 0) {
// Start new file upload
startUpload($(this).data("type"), files);
}
});
});
$(".gcode-input").submit(function(e) {
if (isConnected) {
var gcode = $(this).find("input").val();
if (settings.uppercaseGCode) {
gcode = gcode.toUpperCase();
}
sendGCode(gcode, true);
$(this).find("input").select();
}
e.preventDefault();
});
// Make the auto-complete dropdown items look proper.
// This should be replaced by proper CSS someday, but
// for now we only check which elements may float around.
$(".div-gcodes").bind("shown.bs.dropdown", function() {
var maxWidth = 0;
$(this).find("ul > li > a").each(function() {
var rowWidth = 0;
$(this).find("span").each(function() {
rowWidth += $(this).width();
});
if (rowWidth > maxWidth) {
maxWidth = rowWidth;
}
});
if (maxWidth > 0) {
$(this).find("ul > li > a").each(function() {
var rowWidth = 0;
$(this).find("span").each(function() {
rowWidth += $(this).width();
});
if (rowWidth < maxWidth) {
$(this).addClass("gcode-float");
}
});
}
});
$("#frm_settings").submit(function(e) {
saveSettings();
applySettings();
e.preventDefault();
});
$("input[type='number']").focus(function() {
var input = $(this);
setTimeout(function() {
input.select();
}, 10);
});
$("input[name='temp_selection']:radio").change(function() {
if ($(this).val() == "active") {
$("#ul_active_temps").removeClass("hidden");
$("#ul_standby_temps").addClass("hidden");
} else {
$("#ul_standby_temps").removeClass("hidden");
$("#ul_active_temps").addClass("hidden");
}
});
$("#input_bowden_length").blur(function() {
// NOTE: This is a temporary solution
settings.bowdenLength = checkBoundaries($(this).val(), 300, 0);
$.cookie("settings", JSON.stringify(settings), { expires: 999999 });
});
$("#input_file_upload").change(function(e) {
if (this.files.length > 0) {
// For POST uploads, we need file blobs
startUpload($(this).data("type"), this.files);
}
});
$("#input_temp_bed").keydown(function(e) {
var enterKeyPressed = (e.which == 13);
enterKeyPressed |= (e.which == 9 && window.matchMedia('(max-width: 991px)').matches); // need this for Android
if (isConnected && enterKeyPressed) {
sendGCode("M140 S" + $(this).val());
$(this).select();
e.preventDefault();
}
});
$("#input_temp_chamber").keydown(function(e) {
var enterKeyPressed = (e.which == 13);
enterKeyPressed |= (e.which == 9 && window.matchMedia('(max-width: 991px)').matches); // need this for Android
if (isConnected && enterKeyPressed) {
sendGCode("M141 S" + $(this).val());
$(this).select();
e.preventDefault();
}
});
$("input[id^='input_temp_h']").keydown(function(e) {
var enterKeyPressed = (e.which == 13);
enterKeyPressed |= (e.which == 9 && window.matchMedia('(max-width: 991px)').matches); // need this for Android
if (isConnected && enterKeyPressed) {
var activeOrStandby = ($(this).prop("id").match("active$")) ? "S" : "R";
var heater = $(this).prop("id").match("_h(.)_")[1];
var temperature = $(this).val();
getToolsByHeater(heater).forEach(function(toolNumber) {
sendGCode("G10 P" + toolNumber + " " + activeOrStandby + temperature);
});
$(this).select();
e.preventDefault();
}
});
$("#input_temp_all_active, #input_temp_all_standby").keydown(function(e) {
if (isConnected && e.which == 13) {
if (toolMapping != undefined) {
var activeOrStandby = ($(this).prop("id").match("active$")) ? "S" : "R";
var temperature = $(this).val();
for(var i=0; i<toolMapping.length; i++) {
var number = (toolMapping[i].hasOwnProperty("number") ? toolMapping[i].number : i + 1);
if ($.inArray(0, toolMapping[i].heaters) == -1) {
// Make sure we don't set temperatures for the heated bed
sendGCode("G10 P" + number + " " + activeOrStandby + $(this).val());
}
}
}
e.preventDefault();
}
});
$(".navbar-brand").click(function(e) { e.preventDefault(); });
$(".navlink").click(function(e) {
$(this).blur();
showPage($(this).data("target"));
e.preventDefault();
});
$("#page_listitems input").on("input", function() {
// Validate form controls
$("#btn_add_gcode").toggleClass("disabled", $("#input_gcode").val().trim() == "" || $("#input_gcode_description").val().trim() == "");
$("#btn_add_head_temp").toggleClass("disabled", isNaN(parseFloat($("#input_add_head_temp").val())));
$("#btn_add_bed_temp").toggleClass("disabled", isNaN(parseFloat($("#input_add_bed_temp").val())));
});
$("#page_listitems input").keydown(function(e) {
if (e.which == 13) {
var button = $(this).parents("div:not(.input-group):eq(0)").find("button");
if (!button.hasClass("disabled")) {
button.click();
}
e.preventDefault();
}
});
$("#panel_control_misc label.btn").click(function() {
if ($(this).find("input").val() == 1) { // ATX on
sendGCode("M80");
} else { // ATX off
showConfirmationDialog(T("ATX Power"), T("Do you really want to turn off ATX power?<br/><br/>This will turn off all drives, heaters and fans."), function() {
sendGCode("M81");
});
}
});
$("#panel_extrude label.btn").click(function() {
$(this).parent().find("label.btn").removeClass("btn-primary").addClass("btn-default");
$(this).removeClass("btn-default").addClass("btn-primary");
});
$(".span-refresh-files").click(function() {
gcodeUpdateIndex = -1;
updateGCodeFiles();
$(".span-refresh-files").addClass("hidden");
});
$(".span-refresh-macros").click(function() {
macroUpdateIndex = -1;
updateMacroFiles();
$(".span-refresh-macros").addClass("hidden");
});
$(".panel-chart").resize(function() {
resizeCharts();
});
$("#table_heaters a").click(function(e) {
if (isConnected && lastStatusResponse != undefined) {
if ($(this).parents("#tr_bed").length > 0) {
var bedState = lastStatusResponse.temps.bed.state;
if (bedState == 3) {
showMessage("exclamation-sign", T("Heater Fault"), T("<strong>Error:</strong> A heater fault has occured on this particular heater.<br/><br/>Please turn off your machine and check your wiring for loose connections."), "md");
} else if (bedState == 2) {
// Put bed into standby mode
sendGCode("M144");
} else {
// Bed is either off or in standby mode, send M140 to turn it back on
sendGCode("M140 S" + $("#input_temp_bed").val());
}
} else {
var heater = $(this).parents("tr").index();
var heaterState = lastStatusResponse.temps.heads.state[heater - 1];
if (heaterState == 3) {
showMessage("exclamation-sign", T("Heater Fault"), T("<strong>Error:</strong> A heater fault has occured on this particular heater.<br/><br/>Please turn off your machine and check your wiring for loose connections."), "md");
} else {
var tools = getToolsByHeater(heater), hasToolSelected = false;
tools.forEach(function(tool) {
if (tool == lastStatusResponse.currentTool) {
hasToolSelected = true;
}
});
if (hasToolSelected) {
sendGCode("T-1");
$(this).blur();
} else if (tools.length == 1) {
sendGCode("T" + tools[0]);
$(this).blur();
} else if (tools.length > 0) {
var popover = $(this).parent().children("div.popover");
if (popover.length) {
$(this).popover("hide");
$(this).blur();
} else {
var content = '<div class="btn-group-vertical btn-group-vertical-justified">';
tools.forEach(function(toolNumber) {
content += '<div class="btn-group"><a href="#" class="btn btn-default btn-sm tool" data-tool="' + toolNumber + '">T' + toolNumber + '</a></div>';
});
content += '</div>';
$(this).popover({
content: content,
html: true,
title: T("Select Tool"),
trigger: "manual",
placement: "bottom",
}).popover("show");
}
}
}
}
}
e.preventDefault();
});
$("#table_define_tool input[type='checkbox']").change(function() {
var isChecked = $(this).is(":checked");
$(this).parents("label").toggleClass("btn-primary", isChecked).toggleClass("btn-default", !isChecked);
validateAddTool();
});
$("#table_define_tool input[type='number']").on("input", function() {
validateAddTool();
});
function validateAddTool() {
var toolNumber = parseInt($("#input_tool_number").val());
var disableButton = (!isConnected) ||
(isNaN(toolNumber) || toolNumber < 0 || toolNumber > 255) ||
(toolMapping != undefined && getTool(toolNumber) != undefined) ||
($("input[name='tool_heaters']:checked").length + $("input[name='tool_drives']:checked").length == 0);
$("#btn_add_tool").toggleClass("disabled", disableButton);
}
$("#table_define_tool input[type='number']").keydown(function(e) {
if (e.which == 13) {
if (!$("#btn_add_tool").hasClass("disabled")) {
$("#btn_add_tool").click();
}
e.preventDefault();
}
});
$("#table_heaters tr > th:first-child > a").blur(function() {
$(this).popover("hide");
});
$("#ul_control_dropdown").click(function(e) {
$(this).find(".dropdown").removeClass("open");
if ($(e.target).is("a")) {
$(this).parents(".dropdown").removeClass("open");
} else {
e.stopPropagation();
}
});
$("#ul_control_dropdown .btn-active-temp, #ul_control_dropdown .btn-standby-temp").click(function(e) {
$(this).parent().toggleClass("open");
e.stopPropagation();
});
/* Temperature charts */
function addLayerData(lastLayerTime) {
if (layerData.length == 0) {
layerData.push([0, lastLayerTime]);
layerData.push([1, lastLayerTime]);
} else {
layerData.push([layerData.length, lastLayerTime]);
}
if (lastLayerTime > maxLayerTime) {
maxLayerTime = lastLayerTime;
}
drawPrintChart();
}
function drawTemperatureChart()
{
// Only draw the chart if it's possible
if ($("#chart_temp").width() === 0) {
refreshTempChart = true;
return;
}
// Prepare the data
var preparedBedTemps = [];
for(var i=0; i<recordedBedTemperatures.length; i++) {
preparedBedTemps.push([i, recordedBedTemperatures[i]]);
}
var preparedChamberTemps = [];
for(var i=0; i<recordedChamberTemperatures.length; i++) {
preparedChamberTemps.push([i, recordedChamberTemperatures[i]]);
}
var preparedHeadTemps = [[], [], [], [], []], heaterSamples;
for(var head=0; head<recordedHeadTemperatures.length; head++) {
heaterSamples = recordedHeadTemperatures[head];
for(var k=0; k<heaterSamples.length; k++) {
preparedHeadTemps[head].push([k, heaterSamples[k]]);
}
}
// Draw it
if (tempChart == undefined) {
tempChart = $.plot("#chart_temp", [preparedBedTemps, preparedHeadTemps[0], preparedHeadTemps[1], preparedHeadTemps[2], preparedHeadTemps[3], preparedHeadTemps[4], preparedChamberTemps], tempChartOptions);
} else {
tempChart.setData([preparedBedTemps, preparedHeadTemps[0], preparedHeadTemps[1], preparedHeadTemps[2], preparedHeadTemps[3], preparedHeadTemps[4], preparedChamberTemps]);
tempChart.setupGrid();
tempChart.draw();
}
refreshTempChart = false;
}
function drawPrintChart() {
// Only draw the chart if it's possible
if ($("#chart_print").width() === 0) {
refreshPrintChart = true;
return;
}
// Find absolute maximum values for the X axis
var maxX = 100, maxPanX = 100;
if (layerData.length < 21) {
maxX = maxPanX = 20;
} else if (layerData.length < 100) {
maxX = maxPanX = layerData.length - 1;
} else {
maxPanX = layerData.length - 1;
}
printChartOptions.xaxis.max = maxX;
printChartOptions.xaxis.panRange = [0, maxPanX];
printChartOptions.xaxis.zoomRange = [50, maxPanX];
printChartOptions.yaxis.panRange = [0, (maxLayerTime < 60) ? 60 : maxLayerTime];
printChartOptions.yaxis.zoomRange = [60, (maxLayerTime < 60) ? 60 : maxLayerTime];
// Find max visible value for Y axis
var maxVisibleValue = 30;
if (layerData.length > 3) {
for(var i=(layerData.length > 102) ? layerData.length - 100 : 2; i<layerData.length; i++) {
if (maxVisibleValue < layerData[i][1]) {
maxVisibleValue = layerData[i][1];
}
}
} else if (layerData.length > 0) {
maxVisibleValue = layerData[0][1];
} else {
maxVisibleValue = 60;
}
printChartOptions.yaxis.max = (layerData.length > 3 && layerData.length < 101) ? maxVisibleValue * 1.1 : maxVisibleValue;
// Update chart and pan to the right
printChart = $.plot("#chart_print", [layerData], printChartOptions);
printChart.pan({ left: 99999 });
refreshPrintChart = false;
}
function resizeCharts() {
var headsHeight = $("#table_heaters").height();
var statusHeight = 0;
$("#div_status table").each(function() {
statusHeight += $(this).outerHeight();
});
var max = (headsHeight > statusHeight) ? headsHeight : statusHeight;
max -= tempChartPadding;
if (max > 0) {
$("#chart_temp").css("height", max);
}
if (refreshTempChart) {
drawTemperatureChart();
}
if (refreshPrintChart) {
drawPrintChart();
}
}
/* Sliders */
$('#slider_fan_control').slider({
enabled: false,
id: "fan_control",
min: 0,
max: 100,
step: 1,
value: 35,
tooltip: "always",
formatter: function(value) {
return value + " %";
}
}).on("slideStart", function() {
fanSliderActive = true;
}).on("slideStop", function(slideEvt) {
if (isConnected && !isNaN(slideEvt.value)) {
sendGCode("M106 S" + (slideEvt.value / 100.0));
$("#slider_fan_print").slider("setValue", slideEvt.value);
}
fanSliderActive = false;
});
$('#slider_fan_print').slider({
enabled: false,
id: "fan_print",
min: 0,
max: 100,
step: 1,
value: 35,
tooltip: "always",
formatter: function(value) {
return value + " %";
}
}).on("slideStart", function() {
fanSliderActive = true;
}).on("slideStop", function(slideEvt) {
if (isConnected && !isNaN(slideEvt.value)) {
sendGCode("M106 S" + (slideEvt.value / 100.0));
$("#slider_fan_control").slider("setValue", slideEvt.value);
}
fanSliderActive = false;
});
for(var extr=1; extr<=5; extr++) {
$('#slider_extr_' + extr).slider({
enabled: false,
id: "extr-" + extr,
min: 50,
max: 150,
step: 1,
value: 100,
tooltip: "always",
formatter: function(value) {
return value + " %";
}
}).on("slideStart", function() {
extrSliderActive = true;
}).on("slideStop", function(slideEvt) {
if (isConnected && !isNaN(slideEvt.value)) {
sendGCode("M221 D" + $(this).data("drive") + " S" + slideEvt.value);
}
extrSliderActive = false;
});
}
$('#slider_speed').slider({
enabled: false,
id: "speed",
min: 50,
max: 250,
step: 1,
value: 100,
tooltip: "always",
formatter: function(value) {
return value + " %";
}
}).on("slideStart", function() {
speedSliderActive = true;
}).on("slideStop", function(slideEvt) {
if (isConnected && !isNaN(slideEvt.value)) {
sendGCode("M220 S" + slideEvt.value);
}
speedSliderActive = false;
});
/* Modals */
function showConfirmationDialog(title, message, action) {
$("#modal_confirmation h4").html('<span class="glyphicon glyphicon-question-sign"></span> ' + title);
$("#modal_confirmation p").html(message);
$("#modal_confirmation button:first-child").off().one("click", action);
$("#modal_confirmation").modal("show");
$("#modal_confirmation .btn-success").focus();
}
function showTextInput(title, message, action) {
$("#modal_textinput h4").html(title);
$("#modal_textinput p").html(message);
$("#modal_textinput input").val("");
$("#modal_textinput form").off().submit(function(e) {
$("#modal_textinput").modal("hide");
var value = $("#modal_textinput input").val();
if (value.trim() != "") {
action(value);
}
e.preventDefault();
});
$("#modal_textinput").modal("show");
}
function showMessage(icon, title, message, size) {
$("#modal_message h4").html((icon == "") ? "" : '<span class="glyphicon glyphicon-' + icon + '"></span> ');
$("#modal_message h4").append(title);
$("#modal_message p").html(message);
$("#modal_message .modal-dialog").removeClass("modal-sm modal-lg");
if (size != "md") {
$("#modal_message .modal-dialog").addClass("modal-" + size);
}
$("#modal_message").modal("show");
}
function showPasswordPrompt() {
$('#input_password').val("");
$("#modal_pass_input").modal("show");
$("#modal_pass_input").one("hide.bs.modal", function() {
$(".btn-connect").removeClass("btn-warning disabled").addClass("btn-info").html(T("Connect"));
});
}
$("#modal_pass_input, #modal_textinput").on('shown.bs.modal', function() {
$(this).find("input").focus()
});
var elementToFocus;
$("#modal_message").on('shown.bs.modal', function() {
elementToFocus = $("body :focus");
$('#modal_message button').focus()
});
$("#modal_message").on('hidden.bs.modal', function() {
if (elementToFocus != undefined) {
elementToFocus.focus();
elementToFocus = undefined;
}
});
$("#form_password").submit(function(e) {
$("#modal_pass_input").off("hide.bs.modal").modal("hide");
connect($("#input_password").val(), false);
e.preventDefault();
});
/* GUI Helpers */
function addBedTemperature(temperature) {
// Drop-Down item
$(".ul-bed-temp").append('<li><a href="#" class="bed-temp" data-temp="' + temperature + '">' + temperature + ' °C</a></li>');
$(".btn-bed-temp").removeClass("disabled");
// Entry on Settings page
var item = '<li class="list-group-item col-md-6" data-temperature="' + temperature + '">' + temperature + ' °C';
item += '<button class="btn btn-danger btn-sm btn-delete-parent pull-right" title="' + T("Delete this temperature item") + '">';
item += '<span class="glyphicon glyphicon-trash"></span></button></li>';
$("#ul_bed_temps").append(item);
}
function addDefaultGCode(label, gcode) {
// Drop-Down item
var item = '<li><a href="#" class="gcode" data-gcode="' + gcode + '">';
item += '<span>' + label + '</span>';
item += '<span class="label label-primary">' + gcode + '</span>';
item += '</a></li>';
$(".ul-gcodes").append(item);
$(".btn-gcodes").removeClass("disabled");
// Entry on Settings page
item = '<tr><td><label class="label label-primary">' + gcode + '</label></td><td>' + label + '</td><td>';
item += '<button class="btn btn-sm btn-danger btn-delete-parent" title="' + T("Delete this G-Code item") + '">';
item += '<span class="glyphicon glyphicon-trash"></span></button></td></tr>';
$("#table_gcodes").append(item);
}
function addGCodeFile(filename) {
$("#page_files h1").addClass("hidden");
var row = '<tr data-item="' + filename + '"><td class="col-actions"></td>';
row += '<td><span class="glyphicon glyphicon-asterisk"></span>' + filename + '</td>';
row += '<td class="hidden-xs">' + T("loading") + '</td>';
row += '<td>loading</td>';
row += '<td>loading</td>';
row += '<td>loading</td>';
row += '<td class="hidden-xs hidden-sm">loading</td></tr>';
$("#table_gcode_files").append(row).removeClass("hidden");
}
function addMacroFile(filename) {
// Control Page
if (currentMacroDirectory == "/macros") {
var label = stripMacroFilename(filename);
var macroButton = '<div class="btn-group">';
macroButton += '<button class="btn btn-default btn-macro btn-sm" data-macro="/macros/' + filename + '">' + label + '</button>';
macroButton += '</div>';
$("#panel_macro_buttons h4").addClass("hidden");
$("#panel_macro_buttons .btn-group-vertical").append(macroButton);
}
// Macro Page
var row = '<tr data-macro="' + filename + '"><td class="col-actions"></td>';
row += '<td><span class="glyphicon glyphicon-asterisk"></span>' + filename + '</td>';
row += '<td>loading</td></tr>';
$("#page_macros h1").addClass("hidden");
$("#table_macro_files").append(row).removeClass("hidden");
}
function beep(frequency, duration) {
var context = new webkitAudioContext();
oscillator = context.createOscillator();
oscillator.type = 0; // sine wave: TODO add more possibilities to the Settings page
oscillator.frequency.value = frequency;
oscillator.connect(context.destination);
oscillator.noteOn && oscillator.noteOn(0);
setTimeout(function() {
oscillator.disconnect();
}, duration);
}
function addHeadTemperature(temperature, type) {
// Drop-Down item
$(".ul-" + type + "-temp").append('<li><a href="#" class="heater-temp" data-temp="' + temperature + '">' + temperature + ' °C</a></li>');
$(".btn-" + type + "-temp").removeClass("disabled");
// Entry on Settings page
var item = '<li class="list-group-item col-xs-6 col-lg-3" data-temperature="' + temperature + '">' + temperature + ' °C';
item += '<button class="btn btn-danger btn-sm btn-delete-parent pull-right" title="' + T("Delete this temperature item") + '">';
item += '<span class="glyphicon glyphicon-trash"></span></button></li>';
$("#ul_" + type + "_temps").append(item);
}
function clearBedTemperatures() {
$(".ul-bed-temp, #ul_bed_temps").html("");
$(".btn-bed-temp").addClass("disabled");
}
function clearGCodeDirectory() {
$("#ol_gcode_directory").children(":not(:last-child)").remove();
$("#ol_gcode_directory").prepend('<li class="active"><span class="glyphicon glyphicon-folder-open"></span> ' + T("G-Codes Directory") + '</li>');
}
function clearGCodeFiles() {
$("#table_gcode_files > tbody").remove();
$("#table_gcode_files").addClass("hidden");
$("#page_files h1").removeClass("hidden");
if (isConnected) {
$("#page_files h1").text(T("No Files or Directories found"));
} else {
$("#page_files h1").text(T("Connect to your Duet to display G-Code files"));
}
}
function clearDefaultGCodes() {
$(".ul-gcodes").html("");
$("#table_gcodes > tbody").remove();
$(".btn-gcodes").addClass("disabled");
}
function clearHeadTemperatures() {
$(".ul-active-temp, .ul-standby-temp, #ul_active_temps, #ul_standby_temps").html("");
$(".btn-active-temp, .btn-standby-temp").addClass("disabled");
}
function clearMacroDirectory() {
$("#ol_macro_directory").children(":not(:last-child)").remove();
$("#ol_macro_directory").prepend('<li class="active"><span class="glyphicon glyphicon-folder-open"></span> ' + T("Macros Directory") + '</li>');
}
function clearMacroFiles() {
// Control Page
if (currentMacroDirectory == "/macros") {
$("#panel_macro_buttons .btn-group-vertical").html("");
$("#panel_macro_buttons h4").removeClass("hidden");
}
// Macros Page
$("#table_macro_files > tbody").remove();
$("#table_macro_files").addClass("hidden");
$("#page_macros h1").removeClass("hidden");
if (isConnected) {
$("#page_macros h1").text(T("No Macro Files found"));
} else {
$("#page_macros h1").text(T("Connect to your Duet to display Macro files"));
}
}
function convertSeconds(value) {
value = Math.round(value);
if (value < 0) {
value = 0;
}
var timeLeft = [], temp;
if (value >= 3600) {
temp = Math.floor(value / 3600);
if (temp > 0) {
timeLeft.push(temp + "h");
value = value % 3600;
}
}
if (value >= 60) {
temp = Math.floor(value / 60);
if (temp > 0) {
timeLeft.push((temp > 9 ? temp : "0" + temp) + "m");
value = value % 60;
}
}
value = value.toFixed(0);
timeLeft.push((value > 9 ? value : "0" + value) + "s");
return timeLeft.reduce(function(a, b) { return a + " " + b; });
}
function formatSize(bytes) {
if (settings.useKiB) {
if (bytes > 1073741824) { // GiB
return (bytes / 1073741824).toFixed(1) + " GiB";
}
if (bytes > 1048576) { // MiB
return (bytes / 1048576).toFixed(1) + " MiB";
}
if (bytes > 1024) { // KiB
return (bytes / 1024).toFixed(1) + " KiB";
}
} else {
if (bytes > 1000000000) { // GB
return (bytes / 1000000000).toFixed(1) + " GB";
}
if (bytes > 1000000) { // MB
return (bytes / 1000000).toFixed(1) + " MB";
}
if (bytes > 1000) { // KB
return (bytes / 1000).toFixed(1) + " KB";
}
}
return bytes + " B";
}
function loadMacroDropdown(directory, dropdown) {
$.ajax("rr_files?dir=" + encodeURIComponent(directory), {
dataType: "json",
directory: directory,
dropdown: dropdown,
success: function(response) {
if (response.files.length == 0) {
dropdown.html('<li><a href="#" class="disabled" style="color:#777;">' + T("No files found!") + '</a></li>');
} else {
dropdown.html('<li><a href="#" class="disabled" style="color:#777;">' + directory + '</a></li><li class="divider"></li>');
var files = response.files.sort(function (a, b) {
return a.toLowerCase().localeCompare(b.toLowerCase());
});
files.forEach(function(filename) {
$.ajax("rr_fileinfo?name=" + encodeURIComponent(directory + "/" + filename), {
dataType: "json",
directory: directory,
dropdown: dropdown,
filename: filename,
success: function(fileinfo) {
var label = stripMacroFilename(filename);
if (fileinfo.err == 0) {
dropdown.append('<li><a href="#" class="macro-file-entry" data-macro="' + directory + '/' + filename + '">' + label + '</a></li>');
} else {
dropdown.append('<li><a href="#" class="macro-dir-entry" data-directory="' + directory + '/' + filename + '">' + label + ' <span class="caret"></span></a></li>');
}
$(".macro-file-entry").off("click").click(function(e) {
sendGCode("M98 P" + $(this).data("macro"));
e.preventDefault();
});
$(".macro-dir-entry").off("click").click(function(e) {
var dropdown = $(this).parents("ul");
loadMacroDropdown($(this).data("directory"), dropdown);
e.stopPropagation();
e.preventDefault();
});
}
});
});
}
}
});
}
function log(style, message) {
var entry = '<div class="row alert-' + style + '">';
entry += '<div class="col-xs-2 col-sm-2 col-md-2 col-lg-1 text-center"><strong>' + (new Date()).toLocaleTimeString() + '</strong></div>';
entry += '<div class="col-xs-10 col-sm-10 col-md-10 col-lg-11">' + message + '</div></div>';
$("#console_log").prepend(entry);
}
function recordHeaterTemperatures(bedTemp, chamberTemp, headTemps) {
// Add bed temperature
if (heatedBed) {
recordedBedTemperatures.push(bedTemp);
} else {
recordedBedTemperatures = [];
}
if (recordedBedTemperatures.length > maxTemperatureSamples) {
recordedBedTemperatures.shift();
}
// Add chamber temperature
if (chamber) {
recordedChamberTemperatures.push(chamberTemp);
} else {
recordedChamberTemperatures = [];
}
if (recordedChamberTemperatures.length > maxTemperatureSamples) {
recordedChamberTemperatures.shift();
}
// Add heater temperatures
for(var i=0; i<headTemps.length; i++) {
recordedHeadTemperatures[i].push(headTemps[i]);
if (recordedHeadTemperatures[i].length > maxTemperatureSamples) {
recordedHeadTemperatures[i].shift();
}
}
// Remove invalid data (in case the number of heads has changed)
for(var i=headTemps.length; i<5; i++) {
recordedHeadTemperatures[i] = [];
}
}
function setAxesHomed(axes) {
// Set button colors
var unhomedAxes = "";
if (axes[0]) {
$(".btn-home-x").removeClass("btn-warning").addClass("btn-primary");
} else {
unhomedAxes = (geometry == "delta") ? ", A" : ", X";
$(".btn-home-x").removeClass("btn-primary").addClass("btn-warning");
}
if (axes[1]) {
$(".btn-home-y").removeClass("btn-warning").addClass("btn-primary");
} else {
unhomedAxes += (geometry == "delta") ? ", B" : ", Y";
$(".btn-home-y").removeClass("btn-primary").addClass("btn-warning");
}
if (axes[2]) {
$(".btn-home-z").removeClass("btn-warning").addClass("btn-primary");
} else {
unhomedAxes += (geometry == "delta") ? ", C" : ", Z";
$(".btn-home-z").removeClass("btn-primary").addClass("btn-warning");
}
// Set home alert visibility
if (unhomedAxes == "") {
$("#home_warning").addClass("hidden");
} else {
if (unhomedAxes.length > 3) {
$("#unhomed_warning").html(T("The following axes are not homed: <strong>{0}</strong>", unhomedAxes.substring(2)));
} else {
$("#unhomed_warning").html(T("The following axis is not homed: <strong>{0}</strong>", unhomedAxes.substring(2)));
}
$("#home_warning").removeClass("hidden");
}
}
function setATXPower(value) {
$("input[name='atxPower']").prop("checked", false).parent().removeClass("active");
$("input[name='atxPower'][value='" + (value ? "1" : "0") + "']").prop("checked", true).parent().addClass("active");
}
function setCurrentTemperature(heater, temperature) {
var field = "#tr_head_" + heater + " td";
if (heater == "bed") {
field = "#tr_bed td";
} else if (heater == "chamber") {
field = "#tr_chamber td";
}
if (temperature == undefined) {
$(field).first().html("n/a");
} else if (temperature < -200) {
$(field).first().html("error");
} else {
$(field).first().html(temperature.toFixed(1) + " °C");
}
if (heater != "bed" && heater != "chamber" && lastStatusResponse != undefined) {
if (lastStatusResponse.currentTool != -1) {
var isActiveHead = false;
getToolsByHeater(heater).forEach(function(tool) { if (tool == lastStatusResponse.currentTool) { isActiveHead = true; } });
if (isActiveHead) {
if (temperature >= coldRetractTemp) {
$(".btn-retract").removeClass("disabled");
} else {
$(".btn-retract").addClass("disabled");
}
if (temperature >= coldExtrudeTemp) {
$(".btn-extrude").removeClass("disabled");
} else {
$(".btn-extrude").addClass("disabled");
}
}
} else {
$(".btn-retract, .btn-extrude").addClass("disabled");
}
}
}
function setGeometry(g) {
// The following may be extended in future versions
if (g == "delta") {
$("#btn_bed_compensation > span").text(T("Auto Delta Calibration"));
$(".home-buttons div, div.home-buttons").addClass("hidden");
$("td.home-buttons").css("padding-right", "0px");
$("#btn_homeall").css("width", "");
} else {
$("#btn_bed_compensation > span").text(T("Auto Bed Compensation"));
$(".home-buttons div, div.home-buttons").removeClass("hidden");
$("td.home-buttons").css("padding-right", "");
$("#btn_homeall").resize();
}
geometry = g;
}
function setGCodeFileItem(row, size, height, layerHeight, filamentUsage, generatedBy) {
row.data("file", row.data("item"));
row.removeData("item");
var buttons = '<button class="btn btn-success btn-print-file btn-sm" title="' + T("Print this file (M32)") + '"><span class="glyphicon glyphicon-print"></span></button>';
buttons += '<button class="btn btn-danger btn-delete-file btn-sm" title="' + T("Delete this file") + '"><span class="glyphicon glyphicon-trash"></span></button>';
row.children().eq(0).html(buttons);
var linkCell = row.children().eq(1);
linkCell.find("span").removeClass("glyphicon-asterisk").addClass("glyphicon-file");
linkCell.html('<a href="#" class="gcode-file">' + linkCell.html() + '</a>');
row.children().eq(2).html(formatSize(size));
if (height > 0) {
row.children().eq(3).html(height + " mm");
} else {
row.children().eq(3).html("n/a");
}
if (layerHeight > 0) {
row.children().eq(4).html(layerHeight + " mm");
} else {
row.children().eq(4).html("n/a");
}
if (filamentUsage.length > 0) {
row.children().eq(5).html(filamentUsage.reduce(function(a, b) { return a + b; }).toFixed(1) + " mm");
} else {
row.children().eq(5).html("n/a");
}
if (generatedBy != "") {
row.children().eq(6).html(generatedBy);
} else {
row.children().eq(6).html("n/a");
}
}
function setGCodeDirectoryItem(row) {
row.data("directory", row.data("item"));
row.removeData("item");
row.children().eq(0).html('<button class="btn btn-danger btn-delete-gcode-directory btn-sm pull-right" title="' + T("Delete this directory") + '"><span class="glyphicon glyphicon-trash"></span></button>');
var linkCell = row.children().eq(1);
linkCell.find("span").removeClass("glyphicon-asterisk").addClass("glyphicon-folder-open");
linkCell.html('<a href="#" class="gcode-directory">' + linkCell.html() + '</a>').prop("colspan", 6);
for(var i=6; i>=2; i--) {
row.children().eq(i).remove();
}
if (gcodeLastDirectory == undefined) {
var firstRow = $("#table_gcode_files tbody > tr:first-child");
if (firstRow != row) {
row.insertBefore(firstRow);
}
} else {
row.insertAfter(gcodeLastDirectory);
}
gcodeLastDirectory = row;
}
function setGCodeDirectory(directory) {
currentGCodeDirectory = directory;
$("#ol_gcode_directory").children(":not(:last-child)").remove();
if (directory == "/gcodes") {
$("#ol_gcode_directory").prepend('<li class="active"><span class="glyphicon glyphicon-folder-open"></span> ' + T("G-Codes Directory") + '</li>');
$("#ol_gcode_directory li:last-child").html('<span class="glyphicon glyphicon-level-up"></span> ' + T("Go Up"));
} else {
var listContent = '<li><a href="#" data-directory="/gcodes"><span class="glyphicon glyphicon-folder-open"></span> ' + T("G-Codes Directory") + '</a></li>';
var directoryItems = directory.split("/"), directoryPath = "/gcodes";
for(var i=2; i<directoryItems.length -1; i++) {
directoryPath += "/" + directoryItems[i];
listContent += '<li class="active"><a href="#" data-directory="' + directoryPath + '">' + directoryItems[i] + '</a></li>';
}
listContent += '<li class="active">' + directoryItems[directoryItems.length -1] + '</li>';
$("#ol_gcode_directory").prepend(listContent);
$("#ol_gcode_directory li:last-child").html('<a href="#" data-directory="' + directoryPath + '"><span class="glyphicon glyphicon-level-up"></span> ' + T("Go Up") + '</a>');
}
}
function setHeaterState(heater, status, currentTool) {
var statusText = "n/a";
switch (status) {
case 0:
statusText = T("off");
break;
case 1:
statusText = T("standby");
break;
case 2:
statusText = T("active");
break;
case 3:
statusText = T("fault");
break;
}
if (heater == "bed") {
$("#tr_bed > th:first-child > span").text(statusText);
} else if (heater == "chamber") {
$("#tr_chamber > th:first-child > span").text(statusText);
} else {
var tools = getToolsByHeater(heater);
if (tools.length != 0) {
statusText += " (T" + tools.reduce(function(a, b) { return a + ", T" + b; }) + ")";
statusText = statusText.replace("T" + currentTool, "<u>T" + currentTool + "</u>");
if (tools.length > 1) {
$("#table_heaters tr > th:first-child").eq(heater).find(".caret").removeClass("hidden");
} else {
$("#table_heaters tr > th:first-child").eq(heater).find(".caret").addClass("hidden");
}
} else {
$("#table_heaters tr > th:first-child").eq(heater).find(".caret").addClass("hidden");
}
$("#table_heaters tr > th:first-child").eq(heater).children("span").html(statusText);
}
}
function setMacroFileItem(row, size) {
row.data("file", row.data("macro"));
row.removeData("macro");
var buttons = '<button class="btn btn-success btn-run-macro btn-sm" title="' + T("Run this macro file (M98)") + '"><span class="glyphicon glyphicon-play"></span></button>';
buttons += '<button class="btn btn-danger btn-delete-macro btn-sm" title="' + T("Delete this macro") + '"><span class="glyphicon glyphicon-trash"></span></button>';
row.children().eq(0).html(buttons);
var linkCell = row.children().eq(1);
linkCell.find("span").removeClass("glyphicon-asterisk").addClass("glyphicon-file");
row.children().eq(2).html(formatSize(size));
}
function setMacroDirectoryItem(row) {
if (currentMacroDirectory == "/macros") {
var button = $("#panel_macro_buttons button[data-macro='" + currentMacroDirectory + "/" + row.data("macro") + "']");
button.html(button.html() + ' <span class="caret"></span>');
button.data("directory", button.data("macro")).attr("data-toggle", "dropdown");
button.removeData("macro");
button.parent().append('<ul class="dropdown-menu"></ul>');
}
row.data("directory", row.data("macro"));
row.removeData("macro");
row.children().eq(0).html('<button class="btn btn-danger btn-delete-macro-directory btn-sm pull-right" title="' + T("Delete this directory") + '"><span class="glyphicon glyphicon-trash"></span></button>');
var linkCell = row.children().eq(1);
linkCell.find("span").removeClass("glyphicon-asterisk").addClass("glyphicon-folder-open");
linkCell.html('<a href="#" class="macro-directory">' + linkCell.html() + '</a>').prop("colspan", 2);
row.children().eq(2).remove();
if (macroLastDirectory == undefined) {
var firstRow = $("#table_macro_files tbody > tr:first-child");
if (firstRow != row) {
row.insertBefore(firstRow);
}
} else {
row.insertAfter(macroLastDirectory);
}
macroLastDirectory = row;
}
function setMacroDirectory(directory) {
currentMacroDirectory = directory;
$("#ol_macro_directory").children(":not(:last-child)").remove();
if (directory == "/macros") {
$("#ol_macro_directory").prepend('<li class="active"><span class="glyphicon glyphicon-folder-open"></span> ' + T("Macros Directory") + '</li>');
$("#ol_macro_directory li:last-child").html('<span class="glyphicon glyphicon-level-up"></span> ' + T("Go Up"));
} else {
var listContent = '<li><a href="#" data-directory="/macros"><span class="glyphicon glyphicon-folder-open"></span> ' + T("Macros Directory") + '</a></li>';
var directoryItems = directory.split("/"), directoryPath = "/macros";
for(var i=2; i<directoryItems.length -1; i++) {
directoryPath += "/" + directoryItems[i];
listContent += '<li class="active"><a href="#" data-directory="' + directoryPath + '">' + directoryItems[i] + '</a></li>';
}
listContent += '<li class="active">' + directoryItems[directoryItems.length -1] + '</li>';
$("#ol_macro_directory").prepend(listContent);
$("#ol_macro_directory li:last-child").html('<a href="#" data-directory="' + directoryPath + '"><span class="glyphicon glyphicon-level-up"></span> ' + T("Go Up") + '</a>');
}
}
function setGCodeDirectoryItem(row) {
row.data("directory", row.data("item"));
row.removeData("item");
row.children().eq(0).html('<button class="btn btn-danger btn-delete-gcode-directory btn-sm pull-right" title="' + T("Delete this directory") + '"><span class="glyphicon glyphicon-trash"></span></button>');
var linkCell = row.children().eq(1);
linkCell.find("span").removeClass("glyphicon-asterisk").addClass("glyphicon-folder-open");
linkCell.html('<a href="#" class="gcode-directory">' + linkCell.html() + '</a>').prop("colspan", 6);
for(var i=6; i>=2; i--) {
row.children().eq(i).remove();
}
if (gcodeLastDirectory == undefined) {
var firstRow = $("#table_gcode_files tbody > tr:first-child");
if (firstRow != row) {
row.insertBefore(firstRow);
}
} else {
row.insertAfter(gcodeLastDirectory);
}
gcodeLastDirectory = row;
}
function setPauseStatus(paused) {
if (paused == isPaused) {
return;
}
if (paused) {
$("#btn_cancel").removeClass("disabled").parents("div.btn-group").removeClass("hidden");
$("#btn_pause").removeClass("btn-warning disabled").addClass("btn-success").text(T("Resume")).attr("title", T("Resume paused print (M24)"));
} else {
$("#btn_cancel").parents("div.btn-group").addClass("hidden");
$("#btn_pause").removeClass("btn-success").addClass("btn-warning").text(T("Pause Print")).attr("title", T("Pause current print (M25)"));
if (isPrinting) {
$("#btn_pause").removeClass("disabled");
}
}
isPaused = paused;
}
function setPrintStatus(printing) {
if (printing == isPrinting) {
return;
}
if (printing) {
if (justConnected) {
showPage("print");
}
$("#btn_pause").removeClass("disabled");
$(".row-progress").removeClass("hidden");
$(".col-layertime").addClass("hidden");
$("th.col-layertime").text(T("Current Layer Time"));
$.ajax("rr_fileinfo", {
dataType: "json",
success: function(response) {
if (response.err == 0) {
haveFileInfo = true;
fileInfo = response;
$("span_progress_left").html(T("Printing {0}", response.fileName));
$("#dd_size").html(formatSize(response.size));
$("#dd_height").html((response.height > 0) ? (response.height + " mm") : "n/a");
$("#dd_layer_height").html((response.layerHeight > 0) ? (response.layerHeight + " mm") : "n/a");
if (response.filament.length == 0) {
$("#dd_filament").html("n/a");
} else {
var filament = response.filament.reduce(function(a, b) { return a + " mm, " + b; }) + " mm";
$("#dd_filament").html(filament);
}
$("#dd_generatedby").html((response.generatedBy == "") ? "n/a" : response.generatedBy);
}
}
});
} else {
$("#btn_pause").addClass("disabled");
if (!isConnected || !haveFileInfo) {
$(".row-progress").addClass("hidden");
}
if (isConnected) {
if ($("#auto_sleep").is(":checked")) {
sendGCode("M1");
$("#auto_sleep").prop("checked", false);
}
if (haveFileInfo) {
setProgress(100, T("Printed {0}, 100% Complete", fileInfo.fileName), undefined);
} else {
setProgress(100, T("Print Complete!"), undefined);
}
["filament", "layer", "file"].forEach(function(id) {
if ($("#tl_" + id).html() != "n/a") {
$("#tl_" + id).html("00s");
$("#et_" + id).html((new Date()).toLocaleTimeString());
}
});
$("th.col-layertime").text(T("Last Layer Time"));
}
haveFileInfo = false;
fileInfo = undefined;
}
isPrinting = printing;
if (waitingForPrintStart && printing) {
$("#modal_upload").modal("hide");
showPage("print");
waitingForPrintStart = false;
}
}
function setProbeValue(value, secondaryValue) {
if (value < 0) {
$("#td_probe").html("n/a");
} else if (secondaryValue == undefined) {
$("#td_probe").html(value);
} else {
$("#td_probe").html(value + " (" + secondaryValue.reduce(function(a, b) { return a + b; }) + ")");
}
if (probeTriggerValue != undefined && value > probeTriggerValue && !isPrinting) {
$("#td_probe").css("background-color", probeTriggerColor);
} else if (probeSlowDownValue != undefined && value > probeSlowDownValue && !isPrinting) {
$("#td_probe").css("background-color", probeSlowDownColor);
} else {
$("#td_probe").css("background-color", "");
}
}
function setProgress(progress, labelLeft, labelRight) {
if (labelLeft != undefined) {
$("#span_progress_left").text(labelLeft);
}
if (labelRight != undefined) {
$("#span_progress_right").text(labelRight);
}
$("#progress").css("width", progress + "%");
}
function setStatusLabel(text, style) {
text = T(text);
$(".label-status").removeClass("label-default label-danger label-info label-warning label-success").addClass("label-" + style).text(text);
}
function setTemperatureInput(head, value, active) {
var tempInputId;
if (head == "bed") {
tempInputId = "#input_temp_bed";
} else if (head == "chamber") {
tempInputId = "#input_temp_chamber";
} else {
tempInputId = "#input_temp_h" + head + "_";
tempInputId += (active) ? "active": "standby";
}
if (!$(tempInputId).is(":focus")) {
$(tempInputId).val((value < 0) ? 0 : value);
}
}
function setTimeLeft(field, value) {
if (value == undefined || (value == 0 && isPrinting)) {
$("#et_" + field + ", #tl_" + field).html("n/a");
} else {
// Estimate end time
var now = new Date();
var estEndTime = new Date(now.getTime() + value * 1000); // Date uses ms
$("#et_" + field).html(estEndTime.toLocaleTimeString());
// Estimate time left
$("#tl_" + field).html(convertSeconds(value));
}
}
function showPage(name) {
$(".navitem, .page").removeClass("active");
$(".navitem-" + name + ", #page_" + name).addClass("active");
if (name != currentPage) {
if (name == "control") {
$("#slider_fan_control").slider("relayout")
}
if (name == "print") {
$("#slider_speed").slider("relayout");
$("#slider_fan_print").slider("relayout");
for(var extr=1; extr<=5; extr++) {
$("#slider_extr_" + extr).slider("relayout");
}
if (refreshPrintChart) {
drawPrintChart();
}
waitingForPrintStart = false;
}
if (name == "console") {
currentPage = "console";
if (window.matchMedia('(min-width: 992px)').matches) {
$("#page_console input").focus();
}
}
if (name == "files") {
if (gcodeUpdateIndex == knownGCodeFiles.length) {
$(".span-refresh-files").removeClass("hidden");
} else {
updateGCodeFiles();
}
} else {
$(".span-refresh-files").addClass("hidden");
}
if (name == "macros") {
if (macroUpdateIndex == knownMacroFiles.length) {
$(".span-refresh-macros").removeClass("hidden");
} else {
updateMacroFiles();
}
} else {
$(".span-refresh-macros").addClass("hidden");
}
if (name == "settings" && isConnected) {
getConfigResponse();
}
}
currentPage = name;
}
function stripMacroFilename(filename) {
var match = filename.match(/(.*)\.\w+/);
if (match == null) {
label = filename;
} else {
label = match[1];
}
match = label.match(/\d+_(.*)/);
if (match != null) {
label = match[1];
}
return label;
}
/* Data helpers */
function setToolMapping(mapping) {
if (toolMapping != mapping) {
toolMapping = mapping;
// Clean up current tools
$("#page_tools").children(":not(:first-child)").remove();
// Create new panels for each tool
if (toolMapping != undefined) {
for(var i=0; i<toolMapping.length; i++) {
var number = toolMapping[i].hasOwnProperty("number") ? toolMapping[i].number : (i + 1);
var heaters;
if (toolMapping[i].heaters.length == 0) {
heaters = T("none");
} else {
heaters = toolMapping[i].heaters.reduce(function(a, b) { return a + ", " + b; });
}
var drives;
if (toolMapping[i].drives.length == 0) {
drives = T("none");
} else {
drives = toolMapping[i].drives.reduce(function(a, b) { return a + ", " + b; });
}
var div = '<div class="col-xs-6 col-sm-6 col-md-3 col-lg-3"><div class="panel panel-default">';
div += '<div class="panel-heading"><span>' + T("Tool {0}", number) + '</span></div>';
div += '<div data-tool="' + number + '" class="panel-body">';
div += '<dl><dt>' + T("Heaters:") + '</dt><dd>' + heaters + '</dd>';
div += '<dt>' + T("Drives:") + '</dt><dd>' + drives + '</dd>';
div += '</dl><div class="row"><div class="col-md-12 text-center">';
if (lastStatusResponse != undefined && lastStatusResponse.currentTool == number) {
div += '<button class="btn btn-success btn-select-tool" title="' + T("Deselect this tool") + '">';
div += '<span class="glyphicon glyphicon-remove"></span> <span>' + T("Deselect") + '</span></button>';
} else {
div += '<button class="btn btn-success btn-select-tool" title="' + T("Select this tool") + '">';
div += '<span class="glyphicon glyphicon-pencil"></span> <span>' + T("Select") + '</span></button>';
}
div += ' <button class="btn btn-danger btn-remove-tool" title="' + T("Remove this tool") + '">';
div += '<span class="glyphicon glyphicon-trash"></span> ' + T("Remove") + '</button>';
div += '</div></div></div></div></div>';
$("#page_tools").append(div);
}
}
// Keep the GUI updated
validateAddTool();
}
}
function getTool(number) {
if (toolMapping == undefined) {
return undefined;
}
for(var i=0; i<toolMapping.length; i++) {
if (toolMapping[i].hasOwnProperty("number")) {
if (toolMapping[i].number == number) {
return toolMapping[i];
}
} else if (i + 1 == number) {
return toolMapping[i];
}
}
return undefined;
}
function getToolsByHeater(heater) {
if (toolMapping == undefined) {
return [];
}
var result = [];
for(var i=0; i<toolMapping.length; i++) {
for(var k=0; k<toolMapping[i].heaters.length; k++) {
if (toolMapping[i].heaters[k] == heater) {
if (toolMapping[i].hasOwnProperty("number")) {
result.push(toolMapping[i].number);
} else {
result.push(i + 1);
}
}
}
}
return result;
}
function T(text) {
var entry = text;
if (translationData != undefined) {
// Generate a regex to check with
text = text.replace(/{(\d+)}/g, "{\\d+}").replace("(", "\\(").replace(")", "\\)");
text = text.replace("?", "[?]").replace(".", "[.]");
var regex = new RegExp("^" + text + "$");
// Get the translation node and see if we can find an entry
var root = translationData.getElementsByTagName(settings.language).item(settings.language);
if (root != null) {
for(var i=0; i<root.children.length; i++) {
if (regex.test(root.children[i].attributes["t"].value)) {
entry = root.children[i].textContent;
break;
}
}
// Log translation text if we couldn't find a suitable text
if (translationWarning && entry == text) {
console.log("WARNING: Could not translate '" + entry + "'");
}
}
}
// Format it with the given arguments
var args = arguments;
return entry.replace(/{(\d+)}/g, function(match, number) {
number = parseInt(number) + 1;
return typeof args[number] != 'undefined' ? args[number] : match;
});
}
// May be called only once on page load to translate the page
function translatePage() {
if (translationData != undefined) {
var root = translationData.getElementsByTagName(settings.language).item(settings.language);
if (root != null) {
translateEntries(root, $("span, th, td, strong, dt, button"), "textContent");
translateEntries(root, $("h1, h4, label, a"), "textContent");
translateEntries(root, $("input[type='text']"), "placeholder");
translateEntries(root, $("a, button, label, #chart_temp, input"), "title");
$("#btn_language").data("language", settings.language).children("span:first-child").text(root.attributes["name"].value);
$("html").attr("lang", settings.language);
}
}
}
function translateEntries(root, entries, key) {
var doNodeCheck = (key == "textContent");
$.each(entries, function() {
// If this node has no children, we can safely use it
if (!doNodeCheck || this.childNodes.length < 2) {
translateEntry(root, this, key);
// Otherwise we need to check for non-empty text nodes
} else {
for(var i=0; i<this.childNodes.length; i++) {
var val = this.childNodes[i][key];
if (this.childNodes[i].nodeType == 3 && val != undefined && this.childNodes[i].childNodes.length == 0 && val.trim().length > 0) {
translateEntry(root, this.childNodes[i], key);
}
}
}
});
}
function translateEntry(root, item, key) {
if (item != undefined) {
var originalText = item[key];
if (originalText != undefined && originalText.trim() != "") {
var text = originalText.trim();
for(var i=0; i<root.children.length; i++) {
var entry = root.children[i].attributes["t"].value;
if (entry.indexOf("{") == -1 && entry == text) {
item[key] = item[key].replace(text, root.children[i].textContent);
return;
}
}
// Log translation text if we couldn't find a suitable text
if (translationWarning) {
console.log("WARNING: Could not translate static '" + text + "'");
}
}
}
}
/* File Uploads */
var uploadType, uploadFiles, uploadRows, uploadedFileCount;
var uploadTotalBytes, uploadedTotalBytes;
var uploadStartTime, uploadRequest, uploadFileSize, uploadFileName, uploadPosition;
function startUpload(type, files) {
// Initialize some values
isUploading = true;
uploadType = type;
uploadTotalBytes = uploadedTotalBytes = uploadedFileCount = 0;
uploadFiles = files;
$.each(files, function() {
uploadTotalBytes += this.size;
});
uploadRows = [];
// Safety check for Upload and Print
if (type == "print" && files.length > 1) {
showMessage("exclamation-sign", T("Error"), T("You can only upload and print one file at once!"), "md");
return;
}
// Reset modal dialog
$("#modal_upload").data("backdrop", "static")
$("#modal_upload .close, #modal_upload button[data-dismiss='modal']").addClass("hidden");
$("#btn_cancel_upload, #modal_upload p").removeClass("hidden");
$("#modal_upload h4").text(T("Uploading File(s), {0}% Complete", 0));
// Add files to the table
$("#table_upload_files > tbody").remove();
$.each(files, function() {
var row = '<tr><td><span class="glyphicon glyphicon-asterisk"></span> ' + this.name + '</td>';
row += '<td>' + formatSize(this.size) + '</td>';
row += '<td><div class="progress"><div class="progress-bar progress-bar-info progress-bar-striped" role="progressbar"><span></span></div></div></td></tr>';
$("#table_upload_files").append(row);
uploadRows.push($("#table_upload_files > tbody > tr:last-child"));
});
$("#modal_upload").modal("show");
// Start file upload
uploadNextFile();
}
function uploadNextFile() {
// Prepare some upload values
var file = uploadFiles[uploadedFileCount];
uploadFileName = file.name;
uploadFileSize = file.size;
uploadStartTime = new Date();
uploadPosition = 0;
// Determine the right path
var targetPath = "";
switch (uploadType) {
case "gcode": // Upload G-Code
case "print": // Upload & Print G-Code
targetPath = currentGCodeDirectory + "/" + uploadFileName;
break;
case "macro": // Upload Macro
targetPath = currentMacroDirectory + "/" + uploadFileName;
break;
default: // Generic Upload (on the Settings page)
var fileExt = uploadFileName.split('.').pop().toLowerCase();
switch (fileExt) {
case "ico":
case "html":
case "htm":
targetPath = "/www/" + uploadFileName;
break;
case "css":
targetPath = "/www/css/" + uploadFileName;
break;
case "eot":
case "svg":
case "ttf":
case "woff":
case "woff2":
targetPath = "/www/fonts/" + uploadFileName;
break;
case "jpeg":
case "jpg":
case "png":
targetPath = "/www/img/" + uploadFileName;
break;
case "js":
targetPath = "/www/js/" + uploadFileName;
break;
default:
targetPath = "/sys/" + uploadFileName;
}
}
// Update the GUI
uploadRows[0].find(".progress-bar > span").text(T("Starting"));
uploadRows[0].find(".glyphicon").removeClass("glyphicon-asterisk").addClass("glyphicon-cloud-upload");
// Begin another POST file upload
uploadRequest = $.ajax("rr_upload?name=" + encodeURIComponent(targetPath), {
data: file,
dataType: "json",
processData: false,
contentType: false,
type: "POST",
success: function(data) {
if (isUploading) {
finishCurrentUpload(data.err == 0);
}
},
xhr: function() {
var xhr = new window.XMLHttpRequest();
xhr.upload.addEventListener("progress", function(event) {
if (isUploading && event.lengthComputable) {
// Calculate current upload speed (Date is based on milliseconds)
uploadSpeed = event.loaded / (((new Date()) - uploadStartTime) / 1000);
// Update global progress
uploadedTotalBytes += (event.loaded - uploadPosition);
uploadPosition = event.loaded;
var uploadTitle = T("Uploading File(s), {0}% Complete", ((uploadedTotalBytes / uploadTotalBytes) * 100).toFixed(0));
if (uploadSpeed > 0) {
uploadTitle += " (" + formatSize(uploadSpeed) + "/s)";
}
$("#modal_upload h4").text(uploadTitle);
// Update progress bar
var progress = ((event.loaded / event.total) * 100).toFixed(0);
uploadRows[0].find(".progress-bar").css("width", progress + "%");
uploadRows[0].find(".progress-bar > span").text(progress + " %");
}
}, false);
return xhr;
}
});
}
function finishCurrentUpload(success) {
// Keep the progress updated
if (!success) {
uploadedTotalBytes += (uploadFileSize - uploadPosition);
}
// Update glyphicon and progress bar
uploadRows[0].find(".glyphicon").removeClass("glyphicon-cloud-upload").addClass(success ? "glyphicon-ok" : "glyphicon-alert");
uploadRows[0].find(".progress-bar").removeClass("progress-bar-info").addClass(success ? "progress-bar-success" : "progress-bar-danger").css("width", "100%");
uploadRows[0].find(".progress-bar > span").text(success ? "100 %" : T("ERROR"));
// Go on with upload logic if we're still busy
if (isUploading) {
uploadedFileCount++;
if (uploadFiles.length > uploadedFileCount) {
// Purge last-uploaded file row
uploadRows.shift();
// Upload the next one
uploadNextFile();
} else {
// We're done
finishUpload(true);
}
}
}
function cancelUpload() {
isUploading = false;
finishCurrentUpload(false);
finishUpload(false);
$("#modal_upload h4").text(T("Upload Cancelled!"));
uploadRequest.abort();
}
function finishUpload(success) {
// Reset upload variables
isUploading = false;
uploadFiles = uploadRows = [];
$("#input_file_upload").val("");
// Set some values in the modal dialog
$("#modal_upload h4").text(T("Upload Complete!"));
$("#btn_cancel_upload, #modal_upload p").addClass("hidden");
$("#modal_upload .close, #modal_upload button[data-dismiss='modal']").removeClass("hidden");
if (success) {
// If everything went well, update the GUI immediately
uploadHasFinished();
} else {
// In case an upload has been aborted, give the firmware some time to recover
setTimeout(uploadHasFinished, 1000);
}
}
function uploadHasFinished() {
// Make sure the G-Codes and Macro pages are updated
if (uploadType == "gcode" || uploadType == "print") {
gcodeUpdateIndex = -1;
if (currentPage == "files") {
updateGCodeFiles();
}
} else if (uploadType == "macro") {
macroUpdateIndex = -1;
if (currentPage == "control" || currentPage == "macros") {
updateMacroFiles();
}
}
// Deal with different upload types
if (uploadType == "print") {
waitingForPrintStart = true;
if (currentGCodeDirectory == "/gcodes") {
sendGCode("M32 " + uploadFileName);
} else {
sendGCode("M32 " + currentGCodeDirectory.substring(8) + "/" + uploadFileName);
}
}
// Start polling again
updateStatus();
}