No need to have templates here, we do it directly from the helper methods. That also leaves us more options regarding callbacks, classes etc. Helper methods now take an options dictionary (but still can fallback to the old signature) containing messages, titles, callbacks etc instead of using positional arguments for that. Switched code over to utilize that new calling approach.
574 lines
20 KiB
JavaScript
574 lines
20 KiB
JavaScript
$(function() {
|
|
function ControlViewModel(parameters) {
|
|
var self = this;
|
|
|
|
self.loginState = parameters[0];
|
|
self.settings = parameters[1];
|
|
|
|
self._createToolEntry = function () {
|
|
return {
|
|
name: ko.observable(),
|
|
key: ko.observable()
|
|
}
|
|
};
|
|
|
|
self.isErrorOrClosed = ko.observable(undefined);
|
|
self.isOperational = ko.observable(undefined);
|
|
self.isPrinting = ko.observable(undefined);
|
|
self.isPaused = ko.observable(undefined);
|
|
self.isError = ko.observable(undefined);
|
|
self.isReady = ko.observable(undefined);
|
|
self.isLoading = ko.observable(undefined);
|
|
|
|
self.extrusionAmount = ko.observable(undefined);
|
|
self.controls = ko.observableArray([]);
|
|
|
|
self.tools = ko.observableArray([]);
|
|
|
|
self.feedRate = ko.observable(100);
|
|
self.flowRate = ko.observable(100);
|
|
|
|
self.feedbackControlLookup = {};
|
|
|
|
self.controlsFromServer = [];
|
|
self.additionalControls = [];
|
|
|
|
self.webcamDisableTimeout = undefined;
|
|
|
|
self.keycontrolActive = ko.observable(false);
|
|
self.keycontrolHelpActive = ko.observable(false);
|
|
self.keycontrolPossible = ko.computed(function () {
|
|
return self.isOperational() && !self.isPrinting() && self.loginState.isUser() && !$.browser.mobile;
|
|
});
|
|
self.showKeycontrols = ko.computed(function () {
|
|
return self.keycontrolActive() && self.keycontrolPossible();
|
|
});
|
|
|
|
self.settings.printerProfiles.currentProfileData.subscribe(function () {
|
|
self._updateExtruderCount();
|
|
self.settings.printerProfiles.currentProfileData().extruder.count.subscribe(self._updateExtruderCount);
|
|
});
|
|
self._updateExtruderCount = function () {
|
|
var tools = [];
|
|
|
|
var numExtruders = self.settings.printerProfiles.currentProfileData().extruder.count();
|
|
if (numExtruders > 1) {
|
|
// multiple extruders
|
|
for (var extruder = 0; extruder < numExtruders; extruder++) {
|
|
tools[extruder] = self._createToolEntry();
|
|
tools[extruder]["name"](gettext("Tool") + " " + extruder);
|
|
tools[extruder]["key"]("tool" + extruder);
|
|
}
|
|
} else {
|
|
// only one extruder, no need to add numbers
|
|
tools[0] = self._createToolEntry();
|
|
tools[0]["name"](gettext("Hotend"));
|
|
tools[0]["key"]("tool0");
|
|
}
|
|
|
|
self.tools(tools);
|
|
};
|
|
|
|
self.fromCurrentData = function (data) {
|
|
self._processStateData(data.state);
|
|
};
|
|
|
|
self.fromHistoryData = function (data) {
|
|
self._processStateData(data.state);
|
|
};
|
|
|
|
self._processStateData = function (data) {
|
|
self.isErrorOrClosed(data.flags.closedOrError);
|
|
self.isOperational(data.flags.operational);
|
|
self.isPaused(data.flags.paused);
|
|
self.isPrinting(data.flags.printing);
|
|
self.isError(data.flags.error);
|
|
self.isReady(data.flags.ready);
|
|
self.isLoading(data.flags.loading);
|
|
};
|
|
|
|
self.onEventSettingsUpdated = function (payload) {
|
|
self.requestData();
|
|
};
|
|
|
|
self.onEventRegisteredMessageReceived = function(payload) {
|
|
if (payload.key in self.feedbackControlLookup) {
|
|
var outputs = self.feedbackControlLookup[payload.key];
|
|
_.each(payload.outputs, function(value, key) {
|
|
if (outputs.hasOwnProperty(key)) {
|
|
outputs[key](value);
|
|
}
|
|
});
|
|
}
|
|
};
|
|
|
|
self.rerenderControls = function () {
|
|
var allControls = self.controlsFromServer.concat(self.additionalControls);
|
|
self.controls(self._processControls(allControls))
|
|
};
|
|
|
|
self.requestData = function () {
|
|
$.ajax({
|
|
url: API_BASEURL + "printer/command/custom",
|
|
method: "GET",
|
|
dataType: "json",
|
|
success: function (response) {
|
|
self._fromResponse(response);
|
|
}
|
|
});
|
|
};
|
|
|
|
self._fromResponse = function (response) {
|
|
self.controlsFromServer = response.controls;
|
|
self.rerenderControls();
|
|
};
|
|
|
|
self._processControls = function (controls) {
|
|
for (var i = 0; i < controls.length; i++) {
|
|
controls[i] = self._processControl(controls[i]);
|
|
}
|
|
return controls;
|
|
};
|
|
|
|
self._processControl = function (control) {
|
|
if (control.hasOwnProperty("processed") && control.processed) {
|
|
return control;
|
|
}
|
|
|
|
if (control.hasOwnProperty("template") && control.hasOwnProperty("key") && control.hasOwnProperty("template_key") && !control.hasOwnProperty("output")) {
|
|
control.output = ko.observable(control.default || "");
|
|
if (!self.feedbackControlLookup.hasOwnProperty(control.key)) {
|
|
self.feedbackControlLookup[control.key] = {};
|
|
}
|
|
self.feedbackControlLookup[control.key][control.template_key] = control.output;
|
|
}
|
|
|
|
if (control.hasOwnProperty("children")) {
|
|
control.children = ko.observableArray(self._processControls(control.children));
|
|
if (!control.hasOwnProperty("layout") || !(control.layout == "vertical" || control.layout == "horizontal" || control.layout == "horizontal_grid")) {
|
|
control.layout = "vertical";
|
|
}
|
|
|
|
if (!control.hasOwnProperty("collapsed")) {
|
|
control.collapsed = false;
|
|
}
|
|
}
|
|
|
|
if (control.hasOwnProperty("input")) {
|
|
var attributeToInt = function(obj, key, def) {
|
|
if (obj.hasOwnProperty(key)) {
|
|
var val = obj[key];
|
|
if (_.isNumber(val)) {
|
|
return val;
|
|
}
|
|
|
|
var parsedVal = parseInt(val);
|
|
if (!isNaN(parsedVal)) {
|
|
return parsedVal;
|
|
}
|
|
}
|
|
return def;
|
|
};
|
|
|
|
_.each(control.input, function (element) {
|
|
if (element.hasOwnProperty("slider") && _.isObject(element.slider)) {
|
|
element.slider["min"] = attributeToInt(element.slider, "min", 0);
|
|
element.slider["max"] = attributeToInt(element.slider, "max", 255);
|
|
|
|
// try defaultValue, default to min
|
|
var defaultValue = attributeToInt(element, "default", element.slider.min);
|
|
|
|
// if default value is not within range of min and max, correct that
|
|
if (!_.inRange(defaultValue, element.slider.min, element.slider.max)) {
|
|
// use bound closer to configured default value
|
|
defaultValue = defaultValue < element.slider.min ? element.slider.min : element.slider.max;
|
|
}
|
|
|
|
element.value = ko.observable(defaultValue);
|
|
} else {
|
|
element.slider = false;
|
|
element.value = ko.observable((element.hasOwnProperty("default")) ? element["default"] : undefined);
|
|
}
|
|
});
|
|
}
|
|
|
|
var js;
|
|
if (control.hasOwnProperty("javascript")) {
|
|
js = control.javascript;
|
|
|
|
// if js is a function everything's fine already, but if it's a string we need to eval that first
|
|
if (!_.isFunction(js)) {
|
|
control.javascript = function (data) {
|
|
eval(js);
|
|
};
|
|
}
|
|
}
|
|
|
|
if (control.hasOwnProperty("enabled")) {
|
|
js = control.enabled;
|
|
|
|
// if js is a function everything's fine already, but if it's a string we need to eval that first
|
|
if (!_.isFunction(js)) {
|
|
control.enabled = function (data) {
|
|
return eval(js);
|
|
}
|
|
}
|
|
}
|
|
|
|
control.processed = true;
|
|
return control;
|
|
};
|
|
|
|
self.isCustomEnabled = function (data) {
|
|
if (data.hasOwnProperty("enabled")) {
|
|
return data.enabled(data);
|
|
} else {
|
|
return self.isOperational() && self.loginState.isUser();
|
|
}
|
|
};
|
|
|
|
self.clickCustom = function (data) {
|
|
var callback;
|
|
if (data.hasOwnProperty("javascript")) {
|
|
callback = data.javascript;
|
|
} else {
|
|
callback = self.sendCustomCommand;
|
|
}
|
|
|
|
if (data.confirm) {
|
|
showConfirmationDialog({
|
|
message: data.confirm,
|
|
onproceed: function (e) {
|
|
callback(data);
|
|
}
|
|
});
|
|
} else {
|
|
callback(data);
|
|
}
|
|
};
|
|
|
|
self.sendJogCommand = function (axis, multiplier, distance) {
|
|
if (typeof distance === "undefined")
|
|
distance = $('#jog_distance button.active').data('distance');
|
|
if (self.settings.printerProfiles.currentProfileData() && self.settings.printerProfiles.currentProfileData()["axes"] && self.settings.printerProfiles.currentProfileData()["axes"][axis] && self.settings.printerProfiles.currentProfileData()["axes"][axis]["inverted"]()) {
|
|
multiplier *= -1;
|
|
}
|
|
|
|
var data = {
|
|
"command": "jog"
|
|
};
|
|
data[axis] = distance * multiplier;
|
|
|
|
self.sendPrintHeadCommand(data);
|
|
};
|
|
|
|
self.sendHomeCommand = function (axis) {
|
|
self.sendPrintHeadCommand({
|
|
"command": "home",
|
|
"axes": axis
|
|
});
|
|
};
|
|
|
|
self.sendFeedRateCommand = function () {
|
|
self.sendPrintHeadCommand({
|
|
"command": "feedrate",
|
|
"factor": self.feedRate()
|
|
});
|
|
};
|
|
|
|
self.sendExtrudeCommand = function () {
|
|
self._sendECommand(1);
|
|
};
|
|
|
|
self.sendRetractCommand = function () {
|
|
self._sendECommand(-1);
|
|
};
|
|
|
|
self.sendFlowRateCommand = function () {
|
|
self.sendToolCommand({
|
|
"command": "flowrate",
|
|
"factor": self.flowRate()
|
|
});
|
|
};
|
|
|
|
self._sendECommand = function (dir) {
|
|
var length = self.extrusionAmount();
|
|
if (!length) length = self.settings.printer_defaultExtrusionLength();
|
|
|
|
self.sendToolCommand({
|
|
command: "extrude",
|
|
amount: length * dir
|
|
});
|
|
};
|
|
|
|
self.sendSelectToolCommand = function (data) {
|
|
if (!data || !data.key()) return;
|
|
|
|
self.sendToolCommand({
|
|
command: "select",
|
|
tool: data.key()
|
|
});
|
|
};
|
|
|
|
self.sendPrintHeadCommand = function (data) {
|
|
$.ajax({
|
|
url: API_BASEURL + "printer/printhead",
|
|
type: "POST",
|
|
dataType: "json",
|
|
contentType: "application/json; charset=UTF-8",
|
|
data: JSON.stringify(data)
|
|
});
|
|
};
|
|
|
|
self.sendToolCommand = function (data) {
|
|
$.ajax({
|
|
url: API_BASEURL + "printer/tool",
|
|
type: "POST",
|
|
dataType: "json",
|
|
contentType: "application/json; charset=UTF-8",
|
|
data: JSON.stringify(data)
|
|
});
|
|
};
|
|
|
|
self.sendCustomCommand = function (command) {
|
|
if (!command)
|
|
return;
|
|
|
|
var data = undefined;
|
|
if (command.hasOwnProperty("command")) {
|
|
// single command
|
|
data = {"command": command.command};
|
|
} else if (command.hasOwnProperty("commands")) {
|
|
// multi command
|
|
data = {"commands": command.commands};
|
|
} else if (command.hasOwnProperty("script")) {
|
|
data = {"script": command.script};
|
|
if (command.hasOwnProperty("context")) {
|
|
data["context"] = command.context;
|
|
}
|
|
} else {
|
|
return;
|
|
}
|
|
|
|
if (command.hasOwnProperty("input")) {
|
|
// parametric command(s)
|
|
data["parameters"] = {};
|
|
_.each(command.input, function(input) {
|
|
if (!input.hasOwnProperty("parameter") || !input.hasOwnProperty("value")) {
|
|
return;
|
|
}
|
|
|
|
data["parameters"][input.parameter] = input.value();
|
|
});
|
|
}
|
|
|
|
$.ajax({
|
|
url: API_BASEURL + "printer/command",
|
|
type: "POST",
|
|
dataType: "json",
|
|
contentType: "application/json; charset=UTF-8",
|
|
data: JSON.stringify(data)
|
|
})
|
|
};
|
|
|
|
self.displayMode = function (customControl) {
|
|
if (customControl.hasOwnProperty("children")) {
|
|
if (customControl.name) {
|
|
return "customControls_containerTemplate_collapsable";
|
|
} else {
|
|
return "customControls_containerTemplate_nameless";
|
|
}
|
|
} else {
|
|
return "customControls_controlTemplate";
|
|
}
|
|
};
|
|
|
|
self.rowCss = function (customControl) {
|
|
var span = "span2";
|
|
var offset = "";
|
|
if (customControl.hasOwnProperty("width")) {
|
|
span = "span" + customControl.width;
|
|
}
|
|
if (customControl.hasOwnProperty("offset")) {
|
|
offset = "offset" + customControl.offset;
|
|
}
|
|
return span + " " + offset;
|
|
};
|
|
|
|
self.onStartup = function () {
|
|
self.requestData();
|
|
};
|
|
|
|
self.updateRotatorWidth = function() {
|
|
var webcamImage = $("#webcam_image");
|
|
if (self.settings.webcam_rotate90()) {
|
|
if (webcamImage.width() > 0) {
|
|
$("#webcam_rotator").css("height", webcamImage.width());
|
|
} else {
|
|
webcamImage.off("load.rotator");
|
|
webcamImage.on("load.rotator", function() {
|
|
$("#webcam_rotator").css("height", webcamImage.width());
|
|
webcamImage.off("load.rotator");
|
|
});
|
|
}
|
|
} else {
|
|
$("#webcam_rotator").css("height", "");
|
|
}
|
|
}
|
|
|
|
self.onSettingsBeforeSave = self.updateRotatorWidth;
|
|
|
|
self.onTabChange = function (current, previous) {
|
|
if (current == "#control") {
|
|
if (self.webcamDisableTimeout != undefined) {
|
|
clearTimeout(self.webcamDisableTimeout);
|
|
}
|
|
var webcamImage = $("#webcam_image");
|
|
var currentSrc = webcamImage.attr("src");
|
|
if (currentSrc === undefined || currentSrc.trim() == "") {
|
|
var newSrc = CONFIG_WEBCAM_STREAM;
|
|
if (CONFIG_WEBCAM_STREAM.lastIndexOf("?") > -1) {
|
|
newSrc += "&";
|
|
} else {
|
|
newSrc += "?";
|
|
}
|
|
newSrc += new Date().getTime();
|
|
|
|
self.updateRotatorWidth();
|
|
webcamImage.attr("src", newSrc);
|
|
}
|
|
} else if (previous == "#control") {
|
|
// only disable webcam stream if tab is out of focus for more than 5s, otherwise we might cause
|
|
// more load by the constant connection creation than by the actual webcam stream
|
|
self.webcamDisableTimeout = setTimeout(function () {
|
|
$("#webcam_image").attr("src", "");
|
|
}, 5000);
|
|
}
|
|
};
|
|
|
|
self.onAllBound = function (allViewModels) {
|
|
var additionalControls = [];
|
|
_.each(allViewModels, function (viewModel) {
|
|
if (viewModel.hasOwnProperty("getAdditionalControls")) {
|
|
additionalControls = additionalControls.concat(viewModel.getAdditionalControls());
|
|
}
|
|
});
|
|
if (additionalControls.length > 0) {
|
|
self.additionalControls = additionalControls;
|
|
self.rerenderControls();
|
|
}
|
|
};
|
|
|
|
self.onFocus = function (data, event) {
|
|
if (!self.settings.feature_keyboardControl()) return;
|
|
self.keycontrolActive(true);
|
|
};
|
|
|
|
self.onMouseOver = function (data, event) {
|
|
if (!self.settings.feature_keyboardControl()) return;
|
|
$("#webcam_container").focus();
|
|
self.keycontrolActive(true);
|
|
};
|
|
|
|
self.onMouseOut = function (data, event) {
|
|
if (!self.settings.feature_keyboardControl()) return;
|
|
$("#webcam_container").blur();
|
|
self.keycontrolActive(false);
|
|
};
|
|
|
|
self.toggleKeycontrolHelp = function () {
|
|
self.keycontrolHelpActive(!self.keycontrolHelpActive());
|
|
};
|
|
|
|
self.onKeyDown = function (data, event) {
|
|
if (!self.settings.feature_keyboardControl()) return;
|
|
|
|
var button = undefined;
|
|
var visualizeClick = true;
|
|
|
|
switch (event.which) {
|
|
case 37: // left arrow key
|
|
// X-
|
|
button = $("#control-xdec");
|
|
break;
|
|
case 38: // up arrow key
|
|
// Y+
|
|
button = $("#control-yinc");
|
|
break;
|
|
case 39: // right arrow key
|
|
// X+
|
|
button = $("#control-xinc");
|
|
break;
|
|
case 40: // down arrow key
|
|
// Y-
|
|
button = $("#control-ydec");
|
|
break;
|
|
case 49: // number 1
|
|
case 97: // numpad 1
|
|
// Distance 0.1
|
|
button = $("#control-distance01");
|
|
visualizeClick = false;
|
|
break;
|
|
case 50: // number 2
|
|
case 98: // numpad 2
|
|
// Distance 1
|
|
button = $("#control-distance1");
|
|
visualizeClick = false;
|
|
break;
|
|
case 51: // number 3
|
|
case 99: // numpad 3
|
|
// Distance 10
|
|
button = $("#control-distance10");
|
|
visualizeClick = false;
|
|
break;
|
|
case 52: // number 4
|
|
case 100: // numpad 4
|
|
// Distance 100
|
|
button = $("#control-distance100");
|
|
visualizeClick = false;
|
|
break;
|
|
case 33: // page up key
|
|
case 87: // w key
|
|
// z lift up
|
|
button = $("#control-zinc");
|
|
break;
|
|
case 34: // page down key
|
|
case 83: // s key
|
|
// z lift down
|
|
button = $("#control-zdec");
|
|
break;
|
|
case 36: // home key
|
|
// xy home
|
|
button = $("#control-xyhome");
|
|
break;
|
|
case 35: // end key
|
|
// z home
|
|
button = $("#control-zhome");
|
|
break;
|
|
default:
|
|
event.preventDefault();
|
|
return false;
|
|
}
|
|
|
|
if (button === undefined) {
|
|
return false;
|
|
} else {
|
|
event.preventDefault();
|
|
if (visualizeClick) {
|
|
button.addClass("active");
|
|
setTimeout(function () {
|
|
button.removeClass("active");
|
|
}, 150);
|
|
}
|
|
button.click();
|
|
}
|
|
};
|
|
|
|
}
|
|
|
|
OCTOPRINT_VIEWMODELS.push([
|
|
ControlViewModel,
|
|
["loginStateViewModel", "settingsViewModel"],
|
|
"#control"
|
|
]);
|
|
});
|