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
2016-03-24 10:43:40 +00:00

1903 lines
61 KiB
JavaScript

/* Interface logic for the Duet Web Control v1.11
*
* 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.11";
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: 20000, // in ms
extendedStatusInterval: 10, // nth status request will include extended values
maxRequestTime: 8000, // maximum time to wait for a status response in ms
notificationTimeout: 5000, // 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 lastSentGCode;
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 = false;
stopUpdating = true;
fileInfo = lastStatusResponse = configResponse = configFile = undefined;
lastSentGCode = "";
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, regularConnect) {
// Close all notifications before we connect...
if (regularConnect) {
$.notifyClose();
}
$(".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("danger", T("Error"), T("Could not establish a connection to the Duet firmware! Please check your settings and try again."), 0);
$(".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("danger", T("Error"), T("Could not connect to Duet, because there are no more HTTP sessions available."), 0);
$(".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 (regularConnect)
{
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("danger", T("Error"), T("Invalid password!"), 0);
$(".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();
if ($("#page_config").is(".active")) {
getConfigFile();
}
}
$(".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);
}
// CPU temperature
if (status.hasOwnProperty("cputemp")) {
$(".cpu-temp").removeClass("hidden");
$("#td_cputemp").html(status.cputemp.toFixed(1) + " °C");
}
/*** 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("info", T("Message from Duet firmware"), status.output.message, 0);
}
}
// 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();
if ((response != "") || (lastSentGCode != "" && settings.logSuccess)) {
// What kind of reply are we dealing with?
var style = (response == "") ? "success" : "info", isError = false;
if (response.match("^Error: ") != null) {
style = "warning";
isError = true;
}
// Prepare line breaks for HTML
lastSentGCode = lastSentGCode.trim().replace(/\n/g, "<br/>");
response = response.replace(/\n/g, "<br/>");
// Log this message in the G-Code console
var prefix = (lastSentGCode != "") ? "<strong>" + lastSentGCode + "</strong><br/>" : "";
log(style, prefix + response.replace(/Error:/g, "<strong>Error:</strong>"));
// If the console isn't visible, show a notification too
if (currentPage != "console") {
if (lastSentGCode != "") {
if (isError) {
showMessage(style, T("{0} has returned an error:", lastSentGCode), response.substring(6).trim());
} else if (response != "") {
showMessage(style, lastSentGCode, response);
} else {
showMessage(style, "", "<strong>" + lastSentGCode + "</strong>");
}
} else {
showMessage(style, "", response.replace(/Error:/g, "<strong>Error:</strong>"));
}
}
}
// Reset info about last sent G-Code again
lastSentGCode = "";
}
});
}
// 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>");
showUpdateMessage();
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).removeClass("hidden").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 (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) {
lastSentGCode = gcode;
// 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) {
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 has been 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("danger", T("Communication Error"), msg, 0);
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;
}
if (loadedSettings.hasOwnProperty("notificationTimeout")) {
settings.notificationTimeout = loadedSettings.notificationTimeout;
}
// 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("success", T("Language has changed"), T("You have changed the current language. Please reload the web interface to apply this change."), 0);
}
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.webcamInterval, 100);
settings.notificationTimeout = checkBoundaries($("#notification_timeout").val(), defaultSettings.notificationTimeout, 0);
// 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);
$.ajaxSetup({ timeout: settings.maxRequestTime });
$("#webcam_interval").val(settings.webcamInterval);
$("#notification_timeout").val(settings.notificationTimeout);
/* 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("warning", T("Error"), T("You can only upload and print one file at once!"));
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("warning", T("Error"), T("The archive {0} does not contain any files!", theFile.name));
} else {
startUpload(type, zipFiles, true);
}
} catch(e) {
showMessage("danger", T("Error"), T("Could not read contents of file {0}!", theFile.name));
}
}
})(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":
case "map":
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,
timeout: 0,
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(true);
} else {
// In case an upload has been aborted, give the firmware some time to recover
setTimeout(function() { uploadHasFinished(false); }, 1000);
}
}
function uploadHasFinished(success) {
// 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();
}
}
// Start polling again
startUpdates();
// Deal with different upload types
if (success) {
// Check if a print is supposed to be started
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?"), startFirmwareUpdate);
}
}
}
}
function startFirmwareUpdate() {
if (uploadFirmwareFile.toUpperCase() != "REPRAPFIRMWARE.BIN")
{
// The firmware filename is hardcoded in the IAP binary, so try to rename the uploaded file first
$.ajax("rr_move?old=" + encodeURIComponent("/sys/" + uploadFirmwareFile) + "&new=" + encodeURIComponent("/sys/RepRapFirmware.bin"), {
dataType: "json",
success: function(response) {
if (response.err == 0) {
// Rename succeeded and flashing can be performed now
sendGCode("M997");
} else {
// Looks like /sys/RepRapFirmware.bin already exists, attempt to delete it and try again
$.ajax("rr_delete?name=" + encodeURIComponent("/sys/RepRapFirmware.bin"), {
dataType: "json",
success: function(response) {
if (response.err == 0) {
// File delete succeeded, attempt to start the firmware update once again
startFirmwareUpdate();
} else {
// Something went wrong
showMessage("danger", T("Error"), T("Could not rename firmware file!"));
}
}
});
}
}
});
}
else
{
// Filename is okay, start flashing immediately
sendGCode("M997");
}
}
/* 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, td"), "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