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/model.js
David Crocker 8d99d640b8 Version 1.09x-beta3
Merged in chrishamm's changes to Network, PrintMonitor, and his support
for firmware updates from SD card
Fixed print monitor issue that threw out the layer count and time
estimates when there was an initial extruder priming move in the start
gcode
2016-03-11 18:03:41 +00:00

1868 lines
60 KiB
JavaScript

/* Interface logic for the Duet Web Control v1.10
*
* 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.10";
var sessionPassword = "reprap";
var translationWarning = false; // Set this to "true" if you want to look for missing translation entries
/* Settings */
var settings = {
autoConnect: true, // automatically connect once the page has loaded
updateInterval: 250, // in ms
haltedReconnectDelay: 5000, // in ms
updateReconnectDelay: 15000, // 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
showFanRPM: false, // show fan RPM in sensors
showATXControl: false, // show ATX control
confirmStop: false, // ask for confirmation when pressing Emergency STOP
useDarkTheme: false, // load dark theme by Fotomas
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"],
["M561", "Disable bed compensation"]
],
language: "en",
webcamURL: "",
webcamInterval: 5000 // in ms
};
var defaultSettings = jQuery.extend(true, {}, settings); // need to do this to get a valid copy
/* Variables */
var isConnected = false, justConnected, isUploading, updateTaskLive, stopUpdating;
var ajaxRequests = [], extendedStatusCounter, lastStatusResponse, configResponse, configFile;
var lastSendGCode, lastGCodeFromInput;
var fileInfo, currentLayerTime, maxLayerTime, lastLayerPrintDuration;
var geometry, probeSlowDownValue, probeTriggerValue;
var heatedBed, chamber, numHeads, numExtruderDrives, toolMapping;
var coldExtrudeTemp, coldRetractTemp;
var recordedBedTemperatures, recordedChamberTemperatures, recordedHeadTemperatures, layerData;
var isPrinting, isPaused, printHasFinished;
var currentPage = "control", refreshTempChart = false, refreshPrintChart = false, waitingForPrintStart;
var translationData, darkThemeInclude;
var currentGCodeDirectory, knownGCodeFiles, gcodeUpdateIndex, gcodeLastDirectory;
var currentMacroDirectory, nownMacroFiles, macroUpdateIndex, macroLastDirectory;
var fanSliderActive, speedSliderActive, extrSliderActive;
function resetGuiData() {
setPauseStatus(false);
setPrintStatus(false);
setGeometry("cartesian");
justConnected = isUploading = updateTaskLive = stopUpdating = false;
fileInfo = lastStatusResponse = configResponse = configFile = undefined;
lastSendGCode = "";
lastGCodeFromInput = false;
probeSlowDownValue = probeTriggerValue = undefined;
heatedBed = 1;
chamber = 0;
numHeads = 2; // 6 Heaters max, only 2 are visible on load
numExtruderDrives = 2; // 6 Extruder Drives max, only 2 are visible on load
coldExtrudeTemp = 160;
coldRetractTemp = 90;
recordedBedTemperatures = [];
recordedChamberTemperatures = [];
layerData = [];
recordedHeadTemperatures = [[], [], [], [], [], []];
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
startUpdates();
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 startUpdates() {
stopUpdating = false;
if (!updateTaskLive) {
updateStatus();
}
}
function stopUpdates() {
stopUpdating = updateTaskLive;
}
function updateStatus() {
if (stopUpdating) {
updateTaskLive = false;
return;
}
updateTaskLive = true;
var ajaxRequest = "rr_status";
if (extendedStatusCounter >= settings.extendedStatusInterval || (currentPage == "settings" && (!isPrinting || extendedStatusCounter != 0))) {
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;
}
// Endstops
if (status.hasOwnProperty("endstops")) {
var endstops = status.endstops;
for(i=0; i<9; i++) {
var displayText;
if (endstops & (1 << i)) {
displayText = T("Yes");
} else {
displayText = T("No");
}
$("#tr_drive_" + i + " > td:nth-child(2)").text(displayText);
}
}
// Printer Geometry
if (status.hasOwnProperty("geometry")) {
setGeometry(status.geometry);
}
// Machine Name
if (status.hasOwnProperty("name")) {
setTitle(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/ch firmware forks
var probeType;
switch (status.probe.type) {
case 0:
probeType = T("Switch (0)");
break;
case 1:
probeType = T("Unmodulated (1)");
break;
case 2:
probeType = T("Modulated (2)");
break;
case 3:
probeType = T("Alternative (3)");
break;
case 4:
probeType = T("Two Switches (4)");
break;
default:
probeType = T("Unknown ({0})", status.probe.type);
break;
}
$("#dd_probe_type").text(probeType);
$("#dd_probe_height").text(status.probe.height + " mm");
$("#dd_probe_value").text(probeTriggerValue);
}
// Tool Mapping
if (status.hasOwnProperty("tools")) {
setToolMapping(status.tools);
}
/*** Default status response ***/
// Status
var printing = false, paused = false;
switch (status.status) {
case 'F': // Flashing new firmware
setStatusLabel("Updating", "success");
break;
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
var newFanValue = (status.params.fanPercent.constructor === Array) ? status.params.fanPercent[0] : status.params.fanPercent;
if (!fanSliderActive && (lastStatusResponse == undefined || $("#slider_fan_print").slider("getValue") != newFanValue)) {
if ($("#override_fan").is(":checked") && settings.showFanControl) {
sendGCode("M106 S" + ($("#slider_fan_print").slider("getValue") / 100.0));
} else {
$("#slider_fan_control").slider("setValue", newFanValue);
$("#slider_fan_print").slider("setValue", newFanValue);
}
}
// 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.replace(/\n/g, "<br/>") + "</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")) {
if (!heatedBed) {
heatedBed = 1;
needGuiUpdate = true;
}
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")) {
if (!chamber)
{
chamber = 1;
needGuiUpdate = true;
}
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 (status.hasOwnProperty("fractionPrinted") && fileInfo != undefined) {
var printJustFinished = false;
if (!printHasFinished) {
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);
var remainingFilament = (totalFileFilament - totalRawFilament);
if (progress < 0) {
progress = 0;
} else if (progress > 100) {
progress = 100;
totalRawFilament = totalFileFilament;
remainingFilament = 0;
printJustFinished = printHasFinished = true;
}
progressText.push(T("Filament Usage: {0}mm of {1}mm", totalRawFilament, totalFileFilament));
// TODO: Make this optional
progressText[progressText.length - 1] += " " + T("({0}mm remaining)", remainingFilament.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 < 0) {
progress = 0;
} else if (progress > 100) {
progress = 100;
printJustFinished = printHasFinished = true;
}
}
// Use the file-based progress as a fallback option
else {
progress = status.fractionPrinted;
if (progress < 0) {
progress = 100;
printJustFinished = printHasFinished = true;
}
}
setProgress(progress, T("Printing {0}, {1}% Complete", fileInfo.fileName, progress),
(progressText.length > 0) ? progressText.reduce(function(a, b) { return a + ", " + b; }) : "");
}
// Print Chart
if (status.currentLayer > 1) {
var realPrintTime = (status.printDuration - status.warmUpDuration - status.firstLayerDuration);
if (layerData.length == 0) {
if (status.currentLayer > 2) { // add avg values on reconnect
addLayerData(status.firstLayerDuration, false);
realPrintTime -= status.currentLayerTime;
for(var layer=2; layer<status.currentLayer; layer++) {
addLayerData(realPrintTime / (status.currentLayer - 1), false);
}
drawPrintChart();
} else { // else only the first layer is complete
addLayerData(status.firstLayerDuration, true);
}
if (status.currentLayer == 2) {
lastLayerPrintDuration = 0;
} else {
lastLayerPrintDuration = realPrintTime;
}
} else if (printJustFinished || status.currentLayer - 1 > layerData.length) {
addLayerData(realPrintTime - lastLayerPrintDuration, true);
lastLayerPrintDuration = realPrintTime;
}
}
// Warm-Up Time
if (status.warmUpDuration > 0) {
if (status.warmUpDuration < 0.5) {
$("#td_warmup_time").html(T("none"));
} else {
$("#td_warmup_time").html(convertSeconds(status.warmUpDuration));
}
} else if (!printHasFinished) {
$("#td_warmup_time").html("n/a");
}
// Current Layer Time
if (!printHasFinished) {
if (status.currentLayerTime > 0 || status.currentLayer > 1) {
currentLayerTime = status.currentLayerTime;
$("#td_layertime").html(convertSeconds(status.currentLayerTime));
} else if (status.firstLayerDuration > 0) {
currentLayerTime = status.firstLayerDuration;
$("#td_layertime").html(convertSeconds(status.firstLayerDuration));
} else {
$("#td_layertime").html("n/a");
}
} else {
$("#td_layertime").html("n/a");
}
// Print Duration
if (status.printDuration > 0) {
$("#td_print_duration").html(convertSeconds(status.printDuration));
} else if (!printHasFinished) {
$("#td_print_duration").html("n/a");
}
// First Layer Height (maybe we need to update the layer height info)
if (status.firstLayerHeight > 0 && $("#dd_layer_height").html().indexOf("/") == -1)
{
$("#dd_layer_height").html(status.firstLayerHeight + " mm / " + $("#dd_layer_height").html());
}
// Print Estimations
if (printHasFinished) {
["filament", "layer", "file"].forEach(function(id) {
if ($("#tl_" + id).html() != "n/a") {
$("#tl_" + id).html("00s");
$("#et_" + id).html((new Date()).toLocaleTimeString());
}
});
} else {
if (fileInfo.filament.length > 0) {
setTimeLeft("filament", status.timesLeft.filament);
} else {
setTimeLeft("filament", undefined);
}
setTimeLeft("file", status.timesLeft.file);
if (fileInfo.height > 0) {
setTimeLeft("layer", status.timesLeft.layer);
} else {
setTimeLeft("layer", undefined);
}
}
}
// Update the GUI when we have processed the whole status response
if (needGuiUpdate) {
updateGui();
}
drawTemperatureChart();
// Set timer for next status update
if (status.status == 'F') {
isConnected = updateTaskLive = false;
log("info", "<strong>" + T("Updating Firmware...") + "</strong>");
setTimeout(function() {
connect(sessionPassword, false);
}, settings.updateReconnectDelay);
} else if (status.status == 'H') {
isConnected = updateTaskLive = false;
log("danger", "<strong>" + T("Emergency Stop!") + "</strong>");
setTimeout(function() {
connect(sessionPassword, false);
}, settings.haltedReconnectDelay);
} else {
setTimeout(updateStatus, settings.updateInterval);
}
// Save the last status response
lastStatusResponse = status;
}
});
}
function getConfigFile() {
// Lock the config file as long as the request is being processed
$("#text_config").prop("readonly", true);
// Request config file from the Duet
$.ajax("rr_configfile", {
dataType: "html",
global: false,
success: function(response) {
if (response != "") {
configFile = response;
$("#div_config > h1").addClass("hidden");
$("#text_config").val(response).prop("readonly", false).trigger("input");
$("#row_save_settings").removeClass("hidden");
}
}
});
}
function getConfigResponse() {
$.ajax("rr_config", {
dataType: "json",
success: function(response) {
configResponse = response;
$("#firmware_name").text(response.firmwareName);
$("#firmware_version").text(response.firmwareVersion + " (" + response.firmwareDate + ")");
if (configFile == undefined) {
if (response.hasOwnProperty("configFile")) {
$("#div_config > h1").addClass("hidden");
$("#text_config").removeClass("hidden").prop("readonly", true).val(response.configFile).trigger("input");
} else {
$("#div_config > h1").removeClass("hidden").text(T("loading"));
$("#text_config").addClass("hidden");
}
}
for(var drive = 0; drive < response.accelerations.length; drive++) {
if (drive < response.axisMins.length) {
$("#tr_drive_" + drive + " > td:nth-child(3)").text(response.axisMins[drive] + " mm");
}
if (drive < response.axisMaxes.length) {
$("#tr_drive_" + drive + " > td:nth-child(4)").text(response.axisMaxes[drive] + " mm");
}
$("#tr_drive_" + drive + " > td:nth-child(5)").text(response.minFeedrates[drive] + " mm/s");
$("#tr_drive_" + drive + " > td:nth-child(6)").text(response.maxFeedrates[drive] + " mm/s");
$("#tr_drive_" + drive + " > td:nth-child(7)").text(response.accelerations[drive] + " mm/s²");
if (response.hasOwnProperty("currents")) {
$("#tr_drive_" + drive + " > td:nth-child(8)").text(response.currents[drive] + " mA");
}
}
if (response.hasOwnProperty("idleCurrentFactor")) {
$("#dd_idle_current").text(response.idleCurrentFactor.toFixed(0) + "%");
}
if (response.hasOwnProperty("idleTimeout")) {
var idleTimeoutText = (response.idleTimeout == 0) ? T("never") : convertSeconds(response.idleTimeout);
$("#dd_idle_timeout").text(idleTimeoutText);
}
}
});
}
function updateGCodeFiles() {
if (!isConnected) {
gcodeUpdateIndex = -1;
$(".span-refresh-files").addClass("hidden");
$("#table_gcode_files").css("cursor", "");
clearGCodeDirectory();
clearGCodeFiles();
return;
}
if (gcodeUpdateIndex == -1) {
stopUpdates();
gcodeUpdateIndex = 0;
gcodeLastDirectory = undefined;
clearGCodeFiles();
$.ajax("rr_files?dir=" + encodeURIComponent(currentGCodeDirectory) + "&flagDirs=1", {
dataType: "json",
success: function(response) {
if (isConnected) {
knownGCodeFiles = response.files.sort(function (a, b) {
return a.toLowerCase().localeCompare(b.toLowerCase());
});
var i = 0;
while (i < knownGCodeFiles.length) {
if (knownGCodeFiles[i][0] == '*')
{
var dirName = knownGCodeFiles[i].substring(1);
setGCodeDirectoryItem(addGCodeFile(dirName));
knownGCodeFiles.splice(i, 1);
}
else
{
addGCodeFile(knownGCodeFiles[i]);
i++;
}
}
if (knownGCodeFiles.length == 0) {
if (currentPage == "files") {
$(".span-refresh-files").removeClass("hidden");
}
startUpdates();
} else {
$("#table_gcode_files").css("cursor", "wait");
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,
dir: currentGCodeDirectory,
success: function(response) {
if (!isConnected || this.dir != currentGCodeDirectory) {
return;
}
gcodeUpdateIndex++;
if (response.err == 0) { // File
setGCodeFileItem(this.row, response.size, response.height, response.firstLayerHeight, response.layerHeight, response.filament, response.generatedBy);
} else { // Directory
setGCodeDirectoryItem(this.row);
}
if (currentPage == "files") {
if (gcodeUpdateIndex >= knownGCodeFiles.length) {
$(".span-refresh-files").removeClass("hidden");
$("#table_gcode_files").css("cursor", "");
startUpdates();
} else {
updateGCodeFiles();
}
} else {
startUpdates();
}
}
});
}
}
function updateMacroFiles() {
if (!isConnected) {
macroUpdateIndex = -1;
$(".span-refresh-macros").addClass("hidden");
$("#table_macro_files").css("cursor", "");
clearMacroFiles();
return;
}
if (macroUpdateIndex == -1) {
stopUpdates();
macroUpdateIndex = 0;
macroLastDirectory = undefined;
clearMacroFiles();
$.ajax("rr_files?dir=" + encodeURIComponent(currentMacroDirectory) + "&flagDirs=1", {
dataType: "json",
success: function(response) {
if (isConnected) {
knownMacroFiles = response.files.sort(function (a, b) {
return a.toLowerCase().localeCompare(b.toLowerCase());
});
var i = 0;
while (i < knownMacroFiles.length) {
if (knownMacroFiles[i][0] == '*')
{
var dirName = knownMacroFiles[i].substring(1);
setMacroDirectoryItem(addMacroFile(dirName));
knownMacroFiles.splice(i, 1);
}
else
{
addMacroFile(knownMacroFiles[i]);
i++;
}
}
if (knownMacroFiles.length == 0) {
if (currentPage == "macros") {
$(".span-refresh-macros").removeClass("hidden");
}
startUpdates();
} else {
$("#table_macro_files").css("cursor", "wait");
updateMacroFiles();
}
}
}
});
} else if (macroUpdateIndex < knownMacroFiles.length) {
var row = $("#table_macro_files tr[data-item='" + knownMacroFiles[macroUpdateIndex] + "']");
$.ajax("rr_fileinfo?name=" + encodeURIComponent(currentMacroDirectory + "/" + knownMacroFiles[macroUpdateIndex]), {
dataType: "json",
row: row,
dir: currentMacroDirectory,
success: function(response) {
if (!isConnected || this.dir != currentMacroDirectory) {
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");
}
$("#table_macro_files").css("cursor", "");
startUpdates();
} else if (currentPage == "control" || currentPage == "macros") {
updateMacroFiles();
} else {
startUpdates();
}
}
});
}
}
// 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();
// Try to log the faulty response to console
if (jqxhr.responseText != undefined) {
console.log("Error! The following JSON response could not be parsed:");
console.log(jqxhr.responseText);
}
}
});
/* 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(usingCookie) {
var loadedSettings;
// Older versions of DWC used cookies to store the settings. This is disadvantageous for multiple reasons.
// That's why we try to migrate these settings to localStorage, which allows a smaller HTTP request footprint.
if (!usingCookie) {
if (localStorage.getItem("settings") == null) {
var cookieScript = document.createElement("script");
cookieScript.type = "text/javascript";
cookieScript.src = "js/jquery.cookie.min.js";
cookieScript.onload = function() { loadSettings(true) };
document.body.appendChild(cookieScript);
return;
} else {
loadedSettings = localStorage.getItem("settings");
}
} else {
loadedSettings = $.cookie("settings");
$.removeCookie("settings");
}
// Try to parse the loaded settings (if any)
if (loadedSettings != undefined && loadedSettings.length > 0) {
loadedSettings = JSON.parse(loadedSettings);
// Webcam URL
if (loadedSettings.hasOwnProperty("webcamURL")) {
settings.webcamURL = loadedSettings.webcamURL;
}
// UI Timing
if (loadedSettings.hasOwnProperty("updateInterval")) {
settings.updateInterval = loadedSettings.updateInterval;
}
if (loadedSettings.hasOwnProperty("haltedReconnectDelay")) {
settings.haltedReconnectDelay = loadedSettings.haltedReconnectDelay;
}
if (loadedSettings.hasOwnProperty("updatedReconnectDelay")) {
settings.updateReconnectDelay = loadedSettings.updateReconnectDelay;
}
if (loadedSettings.hasOwnProperty("extendedStatusInterval")) {
settings.extendedStatusInterval = loadedSettings.extendedStatusInterval;
}
if (loadedSettings.hasOwnProperty("maxRequestTime")) {
settings.maxRequestTime = loadedSettings.maxRequestTime;
}
if (loadedSettings.hasOwnProperty("webcamInterval")) {
settings.webcamInterval = loadedSettings.webcamInterval;
}
// 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("showFanRPM")) {
settings.showFanRPM = loadedSettings.showFanRPM;
}
if (loadedSettings.hasOwnProperty("showATXControl")) {
settings.showATXControl = loadedSettings.showATXControl;
}
if (loadedSettings.hasOwnProperty("confirmStop")) {
settings.confirmStop = loadedSettings.confirmStop;
}
if (loadedSettings.hasOwnProperty("useDarkTheme")) {
settings.useDarkTheme = loadedSettings.useDarkTheme;
}
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;
}
// Other (fallback in case language.xml couldn't be loaded)
if (loadedSettings.hasOwnProperty("language")) {
settings.language = loadedSettings.language;
}
}
// Final migration, so we don't use the cookie JS next time
if (usingCookie) {
localStorage.setItem("settings", JSON.stringify(settings));
}
settingsLoaded();
}
function saveSettings() {
// Webcam URL
settings.webcamURL = $("#webcam_url").val();
// 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.showFanRPM = $("#fan_rpm_display").is(":checked");
settings.showATXControl = $("#show_atx").is(":checked");
settings.confirmStop = $("#confirm_stop").is(":checked");
settings.useDarkTheme = $("#dark_theme").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_halt_delay").val(), defaultSettings.haltedReconnectDelay, 1000);
settings.updateReconnectDelay = checkBoundaries($("#reconnect_update_delay").val(), defaultSettings.updateReconnectDelay, 1000);
settings.maxRequestTime = checkBoundaries($("#ajax_timeout").val(), defaultSettings.maxRequestTime, 100);
settings.webcamInterval = checkBoundaries($("#webcam_interval").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; });
// Config file (on demand) - NB: We only update it while it's displayed, else users might mess up their configs...
if (configFile != undefined && configFile != $("#text_config").val() && $('a[href="#page_config"]').parent().hasClass("active"))
{
configFile = $("#text_config").val();
var uploadFile = new File([configFile], "config.g", { type: "application/octet-stream" });
startUpload("generic", [uploadFile], false);
}
// Save Settings
localStorage.setItem("settings", JSON.stringify(settings));
}
function applySettings() {
/* Behavior */
// Webcam
if (settings.webcamURL != "") {
$("#panel_webcam").removeClass("hidden");
updateWebcam(true);
} else {
$("#panel_webcam").addClass("hidden");
}
// 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 Fan RPM
if (settings.showFanRPM) {
$(".fan-rpm").removeClass("hidden");
} else {
$(".fan-rpm").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");
}
// Apply or revoke theme
if (settings.useDarkTheme) {
if (darkThemeInclude == undefined) {
darkThemeInclude = $('<link onload="applyThemeColors(true);" rel="stylesheet" href="css/slate.css" type="text/css"></link>');
darkThemeInclude.appendTo('head');
$("#theme_notice").removeClass("hidden");
}
} else {
if (darkThemeInclude != undefined) {
darkThemeInclude.remove();
darkThemeInclude = undefined;
applyThemeColors(false);
$("#theme_notice").addClass("hidden");
}
}
/* Set values on the Settings page */
// Webcam link
$("#webcam_url").val(settings.webcamURL);
// 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);
$("#fan_rpm_display").prop("checked", settings.showFanRPM);
$("#show_atx").prop("checked", settings.showATXControl);
$("#confirm_stop").prop("checked", settings.confirmStop);
$("#dark_theme").prop("checked", settings.useDarkTheme);
$("#move_feedrate").val(settings.moveFeedrate);
// UI Timing
$("#update_interval").val(settings.updateInterval);
$("#extended_status_interval").val(settings.extendedStatusInterval);
$("#reconnect_halt_delay").val(settings.haltedReconnectDelay);
$("#reconnect_update_delay").val(settings.updateReconnectDelay);
$("#ajax_timeout").val(settings.maxRequestTime);
$("#webcam_interval").val(settings.webcamInterval);
/* 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]);
});
}
/* File Uploads */
var uploadType, uploadFiles, uploadRows, uploadedFileCount;
var uploadTotalBytes, uploadedTotalBytes;
var uploadStartTime, uploadRequest, uploadFileSize, uploadFileName, uploadPosition;
var uploadedDWC, uploadIncludedConfig, uploadFirmwareFile;
function startUpload(type, files, fromCallback) {
// Initialize some values
stopUpdates();
isUploading = true;
uploadType = type;
uploadTotalBytes = uploadedTotalBytes = uploadedFileCount = 0;
uploadFiles = files;
$.each(files, function() {
uploadTotalBytes += this.size;
});
uploadRows = [];
if (!fromCallback) {
uploadedDWC = false;
}
uploadIncludedConfig = false;
uploadFirmwareFile = undefined;
// 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;
}
// Unzip files if necessary
if (type == "macro" || type == "generic") {
var containsZip = false;
$.each(files, function() {
if (this.name.toLowerCase().match("\\.zip$") != null) {
uploadedDWC |= this.name.toLowerCase().match("^duetwebcontrol.*\\.zip") != null;
var fileReader = new FileReader();
fileReader.onload = (function(theFile) {
return function(e) {
try {
var zip = new JSZip(e.target.result);
var zipFiles = [];
$.each(zip.files, function(index, zipEntry) {
if (!zipEntry.dir && zipEntry.name.match("\/\\.git") == null && zipEntry.name.match("README") == null) {
var zipName = zipEntry.name.split("/");
zipName = zipName[zipName.length - 1];
var unpackedFile = new File([zipEntry.asArrayBuffer()], zipName, { type: "application/octet-stream", lastModified: zipEntry.date });
zipFiles.push(unpackedFile);
}
});
if (zipFiles.length == 0) {
showMessage("exclamation-sign", T("Error"), T("The archive {0} does not contain any files!", theFile.name), "md");
} else {
startUpload(type, zipFiles, true);
}
} catch(e) {
showMessage("exclamation-sign", T("Error"), T("Could not read contents of file {0}!", theFile.name), "md");
}
}
})(this);
fileReader.readAsArrayBuffer(this);
containsZip = true;
return false;
}
});
if (containsZip) {
// We're relying on an async task which will trigger this method again when required
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() {
if (type == "generic") {
uploadIncludedConfig |= (this.name == "config.g");
if (this.name.toUpperCase().match("^REPRAPFIRMWARE.*\.BIN") != null) {
uploadFirmwareFile = this.name;
}
}
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":
case "xml":
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();
startUpdates();
}
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);
}
}
// Ask for page reload if DWC has been updated
if (uploadedDWC) {
$("#modal_upload").modal("hide");
showConfirmationDialog(T("Reload Page?"), T("You have just updated Duet Web Control. Would you like to reload the page now?"), function() {
location.reload();
});
}
// Ask for software reset if it's safe to do
else if (lastStatusResponse != undefined && lastStatusResponse.status == 'I') {
if (uploadIncludedConfig) {
$("#modal_upload").modal("hide");
showConfirmationDialog(T("Reboot Duet?"), T("You have just uploaded a config file. Would you like to perform a software reset now?"), function() {
sendGCode("M999");
});
}
if (uploadFirmwareFile != undefined)
{
$("#modal_upload").modal("hide");
showConfirmationDialog(T("Perform Firmware Update?"), T("You have just uploaded a firmware file. Would you like to update your Duet now?"), function() {
if (uploadFirmwareFile.toUpperCase() != "REPRAPFIRMWARE.BIN")
{
// Firmware update filename is hardcoded in the IAP binary, so try to rename this one first
$.ajax("rr_move?old=" + encodeURIComponent("/sys/" + uploadFirmwareFile) + "&new=" + encodeURIComponent("/sys/RepRapFirmware.bin"), {
dataType: "json",
row: draggingObject,
success: function(response) {
if (response.err == 0) {
// Flashing can be performed now
sendGCode("M997");
} else {
// Something went wrong
showMessage("exclamation-sign", T("Error"), T("Could not rename firmware file!"), "md");
}
}
});
}
else
{
// Filename is okay, start flashing immediately
sendGCode("M997");
}
});
}
}
// Start polling again
startUpdates();
}
/* Data helpers */
function requestFileInfo() {
$.ajax("rr_fileinfo", {
dataType: "json",
success: function(response) {
if (isConnected && response.err == 2) {
// The firmware is still busy parsing the file, so try again until it's ready
setTimeout(function() {
if (isConnected) {
requestFileInfo();
}
}, 250);
} else if (response.err == 0) {
// File info is valid, use it
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");
var layerHeight = (response.layerHeight > 0) ? (response.layerHeight + " mm") : "n/a";
if (response.firstLayerHeight > 0) {
$("#dd_layer_height").html(response.firstLayerHeight + " mm / " + layerHeight);
} else {
$("#dd_layer_height").html(layerHeight);
}
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);
$("#td_print_duration").html(convertSeconds(response.printDuration));
}
}
});
}
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, abbr, button, label, #chart_temp, input"), "title");
translateEntries(root, $("img"), "alt");
$("#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 + "'");
}
}
}
}
// vim: ts=4:sw=4