WIP: Improved temperature controls

This commit is contained in:
Gina Häußge 2017-06-20 16:53:41 +02:00
parent dad956b5c9
commit fc53febb87
6 changed files with 308 additions and 62 deletions

File diff suppressed because one or more lines are too long

View file

@ -6,7 +6,7 @@ $(function() {
self.settingsViewModel = parameters[1];
self._createToolEntry = function() {
return {
var entry = {
name: ko.observable(),
key: ko.observable(),
actual: ko.observable(0),
@ -14,11 +14,55 @@ $(function() {
offset: ko.observable(0),
newTarget: ko.observable(),
newOffset: ko.observable()
}
};
entry.newTargetValid = ko.pureComputed(function() {
var value = entry.newTarget();
try {
value = parseInt(value);
} catch (exc) {
return false;
}
return (value >= 0 && value <= 999);
});
entry.newOffsetValid = ko.pureComputed(function() {
var value = entry.newOffset();
try {
value = parseInt(value);
} catch (exc) {
return false;
}
return (-50 <= value <= 50);
});
return entry;
};
self.changingOffset = {
offset: ko.observable(0),
newOffset: ko.observable(0),
name: ko.observable(""),
item: undefined,
title: ko.pureComputed(function() {
return _.sprintf(gettext("Changing Offset of %(name)s"), {name: self.changingOffset.name()});
}),
description: ko.pureComputed(function() {
return _.sprintf(gettext("Use the form below to specify a new offset to apply to all temperature commands sent from printed files for \"%(name)s\""),
{name: self.changingOffset.name()});
})
};
self.changeOffsetDialog = undefined;
self.tools = ko.observableArray([]);
self.hasBed = ko.observable(true);
self.bedTemp = self._createToolEntry();
self.bedTemp["name"](gettext("Bed"));
self.bedTemp["key"]("bed");
@ -324,66 +368,147 @@ $(function() {
return maxTemp;
};
self.incrementTarget = function(item) {
var value = item.newTarget();
if (value === undefined || (typeof(value) == "string" && value.trim() == "")) value = item.target();
try {
value = parseInt(value);
if (value > 999) return;
item.newTarget(value + 1);
} catch (ex) {
// do nothing
}
};
self.decrementTarget = function(item) {
var value = item.newTarget();
if (value === undefined || (typeof(value) == "string" && value.trim() == "")) value = item.target();
try {
value = parseInt(value);
if (value <= 0) return;
item.newTarget(value - 1);
} catch (ex) {
// do nothing
}
};
self.setTarget = function(item) {
var value = item.newTarget();
if (!value) return;
var onSuccess = function() {
item.newTarget("");
};
if (item.key() == "bed") {
self._setBedTemperature(value)
.done(onSuccess);
} else {
self._setToolTemperature(item.key(), value)
.done(onSuccess);
}
if (value === undefined || (typeof(value) == "string" && value.trim() == "")) return OctoPrintClient.createRejectedDeferred();
return self.setTargetToValue(item, value);
};
self.setTargetFromProfile = function(item, profile) {
if (!profile) return;
if (!profile) return OctoPrintClient.createRejectedDeferred();
return self.setTargetToValue(item, (item.key() == "bed" ? profile.bed : profile.extruder));
};
self.setTargetToZero = function(item) {
return self.setTargetToValue(item, 0);
};
self.setTargetToValue = function(item, value) {
try {
value = parseInt(value);
} catch (ex) {
return OctoPrintClient.createRejectedDeferred();
}
if (value < 0 || value > 999) return OctoPrintClient.createRejectedDeferred();
var onSuccess = function() {
item.target(value);
item.newTarget("");
};
if (item.key() == "bed") {
self._setBedTemperature(profile.bed)
return self._setBedTemperature(value)
.done(onSuccess);
} else {
self._setToolTemperature(item.key(), profile.extruder)
return self._setToolTemperature(item.key(), value)
.done(onSuccess);
}
};
self.setTargetToZero = function(item) {
var onSuccess = function() {
item.newTarget("");
};
self.changeOffset = function(item) {
// copy values
self.changingOffset.item = item;
self.changingOffset.name(item.name());
self.changingOffset.offset(item.offset());
self.changingOffset.newOffset(item.offset());
if (item.key() == "bed") {
self._setBedTemperature(0)
.done(onSuccess);
} else {
self._setToolTemperature(item.key(), 0)
.done(onSuccess);
self.changeOffsetDialog.modal("show");
};
self.incrementChangeOffset = function() {
var value = self.changingOffset.newOffset();
if (value === undefined || (typeof(value) == "string" && value.trim() == "")) value = self.changingOffset.offset();
try {
value = parseInt(value);
if (value >= 50) return;
self.changingOffset.newOffset(value + 1);
} catch (ex) {
// do nothing
}
};
self.decrementChangeOffset = function() {
var value = self.changingOffset.newOffset();
if (value === undefined || (typeof(value) == "string" && value.trim() == "")) value = self.changingOffset.offset();
try {
value = parseInt(value);
if (value <= -50) return;
self.changingOffset.newOffset(value - 1);
} catch (ex) {
// do nothing
}
};
self.confirmChangeOffset = function() {
var item = self.changingOffset.item;
item.newOffset(self.changingOffset.newOffset());
self.setOffset(item)
.done(function() {
self.changeOffsetDialog.modal("hide");
// reset
self.changingOffset.offset(0);
self.changingOffset.newOffset(0);
self.changingOffset.name("");
self.changingOffset.item = undefined;
})
};
self.setOffset = function(item) {
var value = item.newOffset();
if (!value) return;
if (value === undefined || (typeof(value) == "string" && value.trim() == "")) return OctoPrintClient.createRejectedDeferred();
return self.setOffsetToValue(item, value);
};
self.setOffsetToZero = function(item) {
return self.setOffsetToValue(item, 0);
};
self.setOffsetToValue = function(item, value) {
try {
value = parseInt(value);
} catch (ex) {
return OctoPrintClient.createRejectedDeferred();
}
if (value < -50 || value > 50) return OctoPrintClient.createRejectedDeferred();
var onSuccess = function() {
item.offset(value);
item.newOffset("");
};
if (item.key() == "bed") {
self._setBedOffset(value)
return self._setBedOffset(value)
.done(onSuccess);
} else {
self._setToolOffset(item.key(), value)
return self._setToolOffset(item.key(), value)
.done(onSuccess);
}
};
@ -411,13 +536,28 @@ $(function() {
self.handleEnter = function(event, type, item) {
if (event.keyCode == 13) {
if (type == "target") {
self.setTarget(item);
self.setTarget(item)
.done(function() {
event.target.blur();
});
} else if (type == "offset") {
self.setOffset(item);
self.confirmChangeOffset();
}
}
};
self.handleFocus = function(event, type, item) {
if (type == "target") {
var value = item.newTarget();
if (value === undefined || (typeof(value) == "string" && value.trim() == "")) {
item.newTarget(item.target());
}
event.target.select();
} else if (type == "offset") {
event.target.select();
}
};
self.onAfterTabChange = function(current, previous) {
if (current != "#temp") {
return;
@ -425,6 +565,10 @@ $(function() {
self.updatePlot();
};
self.onStartup = function() {
self.changeOffsetDialog = $("#change_offset_dialog");
};
self.onStartupComplete = function() {
self._printerProfileUpdated();
};
@ -434,6 +578,6 @@ $(function() {
OCTOPRINT_VIEWMODELS.push([
TemperatureViewModel,
["loginStateViewModel", "settingsViewModel"],
"#temp"
["#temp", "#change_offset_dialog"]
]);
});

View file

@ -401,6 +401,40 @@ table {
.actioncol;
}
// Temperature panel
&.temperature_tool,
&.temperature_actual,
&.temperature_target,
&.temperature_offset {
vertical-align: middle;
text-align: center;
form {
margin: 0;
}
.dropdown-menu {
text-align: left;
}
}
&.temperature_tool {
width: 18%;
text-align: left;
}
&.temperature_actual {
width: 12%;
}
&.temperature_target {
width: 40%;
overflow: visible;
}
&.temperature_offset {
width: 30%;
}
}
}
@ -417,10 +451,6 @@ table {
overflow: visible;
}
.tempInput {
width: 50px;
}
#temp_newTemp, #temp_newBedTemp, #speed_innerWall, #speed_outerWall, #speed_fill, #speed_support,
#webcam_timelapse_interval, #webcam_timelapse_postRoll, #webcam_timelapse_fps, #webcam_timelapse_retractionZHop {
text-align: right;
@ -1236,6 +1266,27 @@ _::-webkit-full-page-media, _:future, :root .full-sized-box {
width: inherit;
}
}
.btn-group:first-child {
.btn:first-child {
-webkit-border-radius: 4px 0 0 4px;
-moz-border-radius: 4px 0 0 4px;
border-radius: 4px 0 0 4px;
}
}
.btn-group {
.btn:first-child {
-webkit-border-radius: 0;
-moz-border-radius: 0;
border-radius: 0;
}
}
}
.btn-input-dec,
.btn-input-enc {
//font-size: 80%;
}
.control-group.error .input-prepend .fileinput-button,
@ -1247,6 +1298,15 @@ input[type=number] {
text-align: right;
}
input[type="number"].input-nospin::-webkit-outer-spin-button,
input[type="number"].input-nospin::-webkit-inner-spin-button {
-webkit-appearance: none;
margin: 0;
}
input[type="number"].input-nospin {
-moz-appearance: textfield;
}
// Progress bars with text
//
// .progress-text-front will also need to have the full width of the enclosing

View file

@ -0,0 +1,33 @@
<div id="change_offset_dialog" class="modal hide fade">
<div class="modal-header">
<a href="javascript:void(0)" class="close" data-dismiss="modal" aria-hidden="true">&times;</a>
<h3 data-bind="text: changingOffset.title"></h3>
</div>
<div class="modal-body">
<p data-bind="text: changingOffset.description"></p>
<form class="form-horizontal">
<div class="control-group">
<label class="control-label">{{ _('Current Offset') }}</label>
<div class="controls">
<span data-bind="html: changingOffset.offset() + ' &deg;C'"></span>
</div>
</div>
<div class="control-group">
<label class="control-label">{{ _('New Offset') }}</label>
<div class="controls">
<div class="input-prepend input-append">
<button class="btn btn-input-dec" data-bind="click: $root.decrementChangeOffset" title="{{ _('-1') }}"><i class="fa fa-minus"></i></button>
<input type="number" min="-50" max="50" class="input-mini input-nospin" style="width: 30px" data-bind="value: changingOffset.newOffset, event: { focus: function(d, e) {$root.handleFocus(e, 'target', changingOffset.item);}, keyup: function(d, e) {$root.handleEnter(e, 'offset', changingOffset.item);} }">
<span class="add-on">&deg;C</span>
<button class="btn btn-input-inc" data-bind="click: $root.incrementChangeOffset" title="{{ _('+1') }}"><i class="fa fa-plus"></i></button>
</div>
<span class="help-block">{{ _('Hint: Hitting <kbd>Enter</kbd> in the input field will also submit the form') }}</span>
</div>
</div>
</form>
</div>
<div class="modal-footer">
<a href="javascript:void(0)" class="btn" data-dismiss="modal" aria-hidden="true">{{ _('Cancel') }}</a>
<a href="javascript:void(0)" class="btn btn-primary" data-bind="click: confirmChangeOffset">{{ _('Submit') }}</a>
</div>
</div>

View file

@ -131,6 +131,7 @@
{% include 'dialogs/wizard.jinja2' %}
{% include 'dialogs/about.jinja2' %}
{% include 'dialogs/files.jinja2' %}
{% include 'dialogs/temperature.jinja2' %}
<!-- End of dialogs -->
<!-- Overlays -->

View file

@ -7,10 +7,10 @@
<table class="table table-bordered table-hover" style="table-layout: fixed; width: 100%; margin-top: 20px">
<tr>
<th style="width: 18%"></th>
<th style="width: 12%; text-align: right">{{ _('Actual') }}</th>
<th style="width: 35%">{{ _('Target') }}</th>
<th style="width: 35%">{{ _('Offset') }}</th>
<th class="temperature_tool"></th>
<th class="temperature_actual" title="{{ _('Current actual temperature as reported by your printer') }}">{{ _('Actual') }}</th>
<th class="temperature_target" title="{{ _('Current target temperature as reported by your printer') }}">{{ _('Target') }}</th>
<th class="temperature_offset" title="{{ _('Offset to apply to temperature commands sent from files') }}">{{ _('Offset') }}</th>
</tr>
<!-- ko foreach: tools -->
<tr data-bind="template: { name: 'temprow-template' }"></tr>
@ -19,37 +19,45 @@
</table>
<script type="text/html" id="temprow-template">
<th style="vertical-align: middle" data-bind="text: name"></th>
<td style="text-align: right; vertical-align: middle" data-bind="html: formatTemperature(actual())"></td>
<td style="vertical-align: middle; overflow: visible">
<div class="input-append">
<input type="number" class="input-mini text-right tempInput" data-bind="attr: {placeholder: cleanTemperature(target()) }, value: newTarget, enable: $root.isOperational() && $root.loginState.isUser(), event: { keyup: function(d, e) {$root.handleEnter(e, 'target', $data);} }">
<span class="add-on">&deg;C</span>
<th class="temperature_tool" data-bind="text: name"></th>
<td class="temperature_actual" data-bind="html: formatTemperature(actual())"></td>
<td class="temperature_target">
<form class="form-inline" style="margin:0">
<div class="input-prepend input-append">
<button class="btn btn-input-dec" data-bind="click: $root.decrementTarget, enable: $root.isOperational() && $root.loginState.isUser()" title="{{ _('-1') }}"><i class="fa fa-minus"></i></button>
<input type="number" min="0" max="999" class="input-mini input-nospin" style="width: 30px" data-bind="attr: {placeholder: cleanTemperature(target())}, value: newTarget, valueUpdate: 'input', enable: $root.isOperational() && $root.loginState.isUser(), event: { focus: function(d, e) {$root.handleFocus(e, 'target', $data);}, keyup: function(d, e) {$root.handleEnter(e, 'target', $data);} }">
<span class="add-on">&deg;C</span>
<button class="btn btn-input-inc" data-bind="click: $root.incrementTarget, enable: $root.isOperational() && $root.loginState.isUser()" title="{{ _('+1') }}"><i class="fa fa-plus"></i></button>
</div>
<div class="btn-group">
<button type="submit" data-bind="click: $parent.setTarget, enable: $root.isOperational() && $root.loginState.isUser()" class="btn">{{ _('Set') }}</button>
<button class="btn dropdown-toggle" data-toggle="dropdown" data-bind="enable: $root.isOperational() && $root.loginState.isUser()">
<button data-bind="click: $root.setTarget, enable: $root.isOperational() && $root.loginState.isUser() && $data.newTargetValid()" class="btn btn-primary" title="{{ _('Set') }}"><i class="fa fa-check"></i></button>
<button class="btn btn-primary dropdown-toggle" data-toggle="dropdown" data-bind="enable: $root.isOperational() && $root.loginState.isUser()">
<span class="caret"></span>
</button>
<ul class="dropdown-menu">
<li>
<a href="#" data-bind="click: $root.setTargetToZero">{{ _('Off') }}</a>
</li>
<li class="divider"></li>
<!-- ko foreach: $root.temperature_profiles -->
<li>
<a href="#" data-bind="click: function() {$root.setTargetFromProfile($parent, $data);}, text: 'Set ' + name + ' (' + ($parent.key() == 'bed' ? bed : extruder) + '&deg;C)'"></a>
</li>
<!-- /ko -->
<li class="divider"></li>
<li>
<a href="#" data-bind="click: $root.setTargetToZero">{{ _('Off') }}</a>
</li>
</ul>
</div>
</div>
</form>
</td>
<td style="vertical-align: middle">
<div class="input-append">
<input type="number" min="-50" max="50" class="input-mini text-right tempInput" data-bind="attr: {placeholder: offset}, value: newOffset, enable: $root.isOperational() && $root.loginState.isUser(), event: { keyup: function(d, e) {$root.handleEnter(e, 'offset', $data);} }">
<span class="add-on">&deg;C</span>
<button type="submit" data-bind="click: $root.setOffset, enable: $root.isOperational() && $root.loginState.isUser()" class="btn">{{ _('Set') }}</button>
</div>
<td class="temperature_offset">
<form class="form-inline" style="margin:0">
<div class="input-append">
<span class="input-mini uneditable-input text-right" style="width: 30px" data-bind="text: offset"></span>
<span class="add-on">&deg;C</span>
<button class="btn" title="{{ _('Change Offset') }}" data-bind="click: $root.changeOffset, enable: $root.isOperational() && $root.loginState.isUser()"><i class="fa fa-pencil"></i></button>
<button class="btn" title="{{ _('Delete Offset') }}" data-bind="click: $root.setOffsetToZero, enable: $root.isOperational() && $root.loginState.isUser()"><i class="fa fa-trash"></i></button>
</div>
</form>
</td>
</script>
</div>