
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
1868 lines
60 KiB
JavaScript
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
|