Modularize printer profile manager UI components

This commit is contained in:
Gina Häußge 2016-08-19 11:41:29 +02:00
parent dedffe3514
commit d2f8b957bc
6 changed files with 486 additions and 453 deletions

View file

@ -12,7 +12,7 @@
};
OctoPrint.printerprofiles = {
get: function (opts) {
list: function (opts) {
return OctoPrint.get(url, opts);
},
@ -26,6 +26,10 @@
return OctoPrint.postJson(url, data, opts);
},
get: function (id, opts) {
return OctoPrint.get(profileUrl(id), opts);
},
update: function (id, profile, additional, opts) {
profile = profile || {};
additional = additional || {};

View file

@ -1,37 +1,285 @@
$(function() {
function PrinterProfilesViewModel() {
var cleanProfile = function() {
return {
id: "",
name: "",
model: "",
color: "default",
volume: {
formFactor: "rectangular",
width: 200,
depth: 200,
height: 200,
origin: "lowerleft"
},
heatedBed: true,
axes: {
x: {speed: 6000, inverted: false},
y: {speed: 6000, inverted: false},
z: {speed: 200, inverted: false},
e: {speed: 300, inverted: false}
},
extruder: {
count: 1,
offsets: [
[0,0]
],
nozzleDiameter: 0.4
}
}
};
function EditedProfileViewModel(profiles) {
var self = this;
self._cleanProfile = function() {
return {
id: "",
name: "",
model: "",
color: "default",
self.profiles = profiles;
self.isNew = ko.observable(false);
self.name = ko.observable();
self.color = ko.observable();
self.identifier = ko.observable();
self.identifierPlaceholder = ko.observable();
self.model = ko.observable();
self.volumeWidth = ko.observable();
self.volumeHeight = ko.observable();
self.volumeDepth = ko.observable();
self.volumeFormFactor = ko.observable();
self.volumeOrigin = ko.observable();
self.volumeFormFactor.subscribe(function(value) {
if (value == "circular") {
self.volumeOrigin("center");
}
});
self.heatedBed = ko.observable();
self.nozzleDiameter = ko.observable();
self.extruders = ko.observable();
self.extruderOffsets = ko.observableArray();
self.axisXSpeed = ko.observable();
self.axisYSpeed = ko.observable();
self.axisZSpeed = ko.observable();
self.axisESpeed = ko.observable();
self.axisXInverted = ko.observable(false);
self.axisYInverted = ko.observable(false);
self.axisZInverted = ko.observable(false);
self.axisEInverted = ko.observable(false);
self.koExtruderOffsets = ko.pureComputed(function() {
var extruderOffsets = self.extruderOffsets();
var numExtruders = self.extruders();
if (!numExtruders) {
numExtruders = 1;
}
if (numExtruders - 1 > extruderOffsets.length) {
for (var i = extruderOffsets.length; i < numExtruders; i++) {
extruderOffsets[i] = {
idx: i + 1,
x: ko.observable(0),
y: ko.observable(0)
}
}
self.extruderOffsets(extruderOffsets);
}
return extruderOffsets.slice(0, numExtruders - 1);
});
self.nameInvalid = ko.pureComputed(function() {
return !self.name();
});
self.identifierInvalid = ko.pureComputed(function() {
var identifier = self.identifier();
var placeholder = self.identifierPlaceholder();
var data = identifier;
if (!identifier) {
data = placeholder;
}
var validCharacters = (data && (data == self._sanitize(data)));
var existingProfile = self.profiles.getItem(function(item) {return item.id == data});
return !data || !validCharacters || (self.isNew() && existingProfile != undefined);
});
self.identifierInvalidText = ko.pureComputed(function() {
if (!self.identifierInvalid()) {
return "";
}
if (!self.identifier() && !self.identifierPlaceholder()) {
return gettext("Identifier must be set");
} else if (self.identifier() != self._sanitize(self.identifier())) {
return gettext("Invalid characters, only a-z, A-Z, 0-9, -, ., _, ( and ) are allowed")
} else {
return gettext("A profile with such an identifier already exists");
}
});
self.name.subscribe(function() {
self.identifierPlaceholder(self._sanitize(self.name()).toLowerCase());
});
self.valid = function() {
return !self.nameInvalid() && !self.identifierInvalid();
};
self.availableColors = ko.observable([
{key: "default", name: gettext("default")},
{key: "red", name: gettext("red")},
{key: "orange", name: gettext("orange")},
{key: "yellow", name: gettext("yellow")},
{key: "green", name: gettext("green")},
{key: "blue", name: gettext("blue")},
{key: "black", name: gettext("black")}
]);
self.availableOrigins = ko.pureComputed(function() {
var formFactor = self.volumeFormFactor();
var possibleOrigins = {
"lowerleft": gettext("Lower Left"),
"center": gettext("Center")
};
var keys = [];
if (formFactor == "rectangular") {
keys = ["lowerleft", "center"];
} else if (formFactor == "circular") {
keys = ["center"];
}
var result = [];
_.each(keys, function(key) {
result.push({key: key, name: possibleOrigins[key]});
});
return result;
});
self.fromProfileData = function(data) {
self.isNew(data === undefined);
if (data === undefined) {
data = cleanProfile();
}
self.identifier(data.id);
self.name(data.name);
self.color(data.color);
self.model(data.model);
self.volumeWidth(data.volume.width);
self.volumeHeight(data.volume.depth);
self.volumeDepth(data.volume.height);
self.volumeFormFactor(data.volume.formFactor);
self.volumeOrigin(data.volume.origin);
self.heatedBed(data.heatedBed);
self.nozzleDiameter(data.extruder.nozzleDiameter);
self.extruders(data.extruder.count);
var offsets = [];
if (data.extruder.count > 1) {
_.each(_.slice(data.extruder.offsets, 1), function(offset, index) {
offsets.push({
idx: index + 1,
x: ko.observable(offset[0]),
y: ko.observable(offset[1])
});
});
}
self.extruderOffsets(offsets);
self.axisXSpeed(data.axes.x.speed);
self.axisXInverted(data.axes.x.inverted);
self.axisYSpeed(data.axes.y.speed);
self.axisYInverted(data.axes.y.inverted);
self.axisZSpeed(data.axes.z.speed);
self.axisZInverted(data.axes.z.inverted);
self.axisESpeed(data.axes.e.speed);
self.axisEInverted(data.axes.e.inverted);
};
self.toProfileData = function() {
var identifier = self.identifier();
if (!identifier) {
identifier = self.identifierPlaceholder();
}
var profile = {
id: identifier,
name: self.name(),
color: self.color(),
model: self.model(),
volume: {
formFactor: "rectangular",
width: 200,
depth: 200,
height: 200,
origin: "lowerleft"
},
heatedBed: true,
axes: {
x: {speed: 6000, inverted: false},
y: {speed: 6000, inverted: false},
z: {speed: 200, inverted: false},
e: {speed: 300, inverted: false}
width: parseFloat(self.volumeWidth()),
depth: parseFloat(self.volumeHeight()),
height: parseFloat(self.volumeHeight()),
formFactor: self.volumeFormFactor(),
origin: self.volumeOrigin()
},
heatedBed: self.heatedBed(),
extruder: {
count: 1,
count: parseInt(self.extruders()),
offsets: [
[0,0]
[0.0, 0.0]
],
nozzleDiameter: 0.4
nozzleDiameter: parseFloat(self.nozzleDiameter())
},
axes: {
x: {
speed: parseInt(self.axisXSpeed()),
inverted: self.axisXInverted()
},
y: {
speed: parseInt(self.axisYSpeed()),
inverted: self.axisYInverted()
},
z: {
speed: parseInt(self.axisZSpeed()),
inverted: self.axisZInverted()
},
e: {
speed: parseInt(self.axisESpeed()),
inverted: self.axisEInverted()
}
}
};
if (self.extruders() > 1) {
for (var i = 0; i < self.extruders() - 1; i++) {
var offset = [0.0, 0.0];
if (i < self.extruderOffsets().length) {
try {
offset = [parseFloat(self.extruderOffsets()[i]["x"]()), parseFloat(self.extruderOffsets()[i]["y"]())];
} catch (exc) {
log.error("Invalid offset in profile", identifier, "for extruder", i+1, ":", self.extruderOffsets()[i]["x"], ",", self.extruderOffsets()[i]["y"]);
}
}
profile.extruder.offsets.push(offset);
}
}
return profile;
};
self._sanitize = function(name) {
return name.replace(/[^a-zA-Z0-9\-_\.\(\) ]/g, "").replace(/ /g, "_");
};
self.fromProfileData(cleanProfile());
}
function PrinterProfilesViewModel() {
var self = this;
self.requestInProgress = ko.observable(false);
self.profiles = new ItemListHelper(
@ -53,135 +301,19 @@ $(function() {
self.defaultProfile = ko.observable();
self.currentProfile = ko.observable();
self.currentProfileData = ko.observable(ko.mapping.fromJS(self._cleanProfile()));
self.editorNew = ko.observable(false);
self.editorName = ko.observable();
self.editorColor = ko.observable();
self.editorIdentifier = ko.observable();
self.editorIdentifierPlaceholder = ko.observable();
self.editorModel = ko.observable();
self.editorVolumeWidth = ko.observable();
self.editorVolumeDepth = ko.observable();
self.editorVolumeHeight = ko.observable();
self.editorVolumeFormFactor = ko.observable();
self.editorVolumeOrigin = ko.observable();
self.editorVolumeFormFactor.subscribe(function(value) {
if (value == "circular") {
self.editorVolumeOrigin("center");
self.createProfileEditor = function(data) {
var editor = new EditedProfileViewModel(self.profiles);
if (data !== undefined) {
editor.fromProfileData(data);
}
});
return editor;
};
self.editorHeatedBed = ko.observable();
self.editorNozzleDiameter = ko.observable();
self.editorExtruders = ko.observable();
self.editorExtruderOffsets = ko.observableArray();
self.editorAxisXSpeed = ko.observable();
self.editorAxisYSpeed = ko.observable();
self.editorAxisZSpeed = ko.observable();
self.editorAxisESpeed = ko.observable();
self.editorAxisXInverted = ko.observable(false);
self.editorAxisYInverted = ko.observable(false);
self.editorAxisZInverted = ko.observable(false);
self.editorAxisEInverted = ko.observable(false);
self.availableColors = ko.observable([
{key: "default", name: gettext("default")},
{key: "red", name: gettext("red")},
{key: "orange", name: gettext("orange")},
{key: "yellow", name: gettext("yellow")},
{key: "green", name: gettext("green")},
{key: "blue", name: gettext("blue")},
{key: "black", name: gettext("black")}
]);
self.availableOrigins = ko.pureComputed(function() {
var formFactor = self.editorVolumeFormFactor();
var possibleOrigins = {
"lowerleft": gettext("Lower Left"),
"center": gettext("Center")
};
var keys = [];
if (formFactor == "rectangular") {
keys = ["lowerleft", "center"];
} else if (formFactor == "circular") {
keys = ["center"];
}
var result = [];
_.each(keys, function(key) {
result.push({key: key, name: possibleOrigins[key]});
});
return result;
});
self.koEditorExtruderOffsets = ko.pureComputed(function() {
var extruderOffsets = self.editorExtruderOffsets();
var numExtruders = self.editorExtruders();
if (!numExtruders) {
numExtruders = 1;
}
if (numExtruders - 1 > extruderOffsets.length) {
for (var i = extruderOffsets.length; i < numExtruders; i++) {
extruderOffsets[i] = {
idx: i + 1,
x: ko.observable(0),
y: ko.observable(0)
}
}
self.editorExtruderOffsets(extruderOffsets);
}
return extruderOffsets.slice(0, numExtruders - 1);
});
self.editorNameInvalid = ko.pureComputed(function() {
return !self.editorName();
});
self.editorIdentifierInvalid = ko.pureComputed(function() {
var identifier = self.editorIdentifier();
var placeholder = self.editorIdentifierPlaceholder();
var data = identifier;
if (!identifier) {
data = placeholder;
}
var validCharacters = (data && (data == self._sanitize(data)));
var existingProfile = self.profiles.getItem(function(item) {return item.id == data});
return !data || !validCharacters || (self.editorNew() && existingProfile != undefined);
});
self.editorIdentifierInvalidText = ko.pureComputed(function() {
if (!self.editorIdentifierInvalid()) {
return "";
}
if (!self.editorIdentifier() && !self.editorIdentifierPlaceholder()) {
return gettext("Identifier must be set");
} else if (self.editorIdentifier() != self._sanitize(self.editorIdentifier())) {
return gettext("Invalid characters, only a-z, A-Z, 0-9, -, ., _, ( and ) are allowed")
} else {
return gettext("A profile with such an identifier already exists");
}
});
self.editor = self.createProfileEditor();
self.currentProfileData = ko.observable(ko.mapping.fromJS(cleanProfile()));
self.enableEditorSubmitButton = ko.pureComputed(function() {
return !self.editorNameInvalid() && !self.editorIdentifierInvalid() && !self.requestInProgress();
});
self.editorName.subscribe(function() {
self.editorIdentifierPlaceholder(self._sanitize(self.editorName()).toLowerCase());
return self.editor.valid() && !self.requestInProgress();
});
self.makeDefault = function(data) {
@ -194,7 +326,7 @@ $(function() {
};
self.requestData = function() {
OctoPrint.printerprofiles.get()
OctoPrint.printerprofiles.list()
.done(self.fromResponse);
};
@ -222,7 +354,7 @@ $(function() {
};
self.addProfile = function(callback) {
var profile = self._editorData();
var profile = self.editor.toProfileData();
self.requestInProgress(true);
OctoPrint.printerprofiles.add(profile)
.done(function() {
@ -257,7 +389,7 @@ $(function() {
self.updateProfile = function(profile, callback) {
if (profile == undefined) {
profile = self._editorData();
profile = self.editor.toProfileData();
}
self.requestInProgress(true);
@ -278,54 +410,13 @@ $(function() {
};
self.showEditProfileDialog = function(data) {
var add = false;
if (data == undefined) {
data = self._cleanProfile();
add = true;
}
self.editorNew(add);
self.editorIdentifier(data.id);
self.editorName(data.name);
self.editorColor(data.color);
self.editorModel(data.model);
self.editorVolumeWidth(data.volume.width);
self.editorVolumeDepth(data.volume.depth);
self.editorVolumeHeight(data.volume.height);
self.editorVolumeFormFactor(data.volume.formFactor);
self.editorVolumeOrigin(data.volume.origin);
self.editorHeatedBed(data.heatedBed);
self.editorNozzleDiameter(data.extruder.nozzleDiameter);
self.editorExtruders(data.extruder.count);
var offsets = [];
if (data.extruder.count > 1) {
_.each(_.slice(data.extruder.offsets, 1), function(offset, index) {
offsets.push({
idx: index + 1,
x: ko.observable(offset[0]),
y: ko.observable(offset[1])
});
});
}
self.editorExtruderOffsets(offsets);
self.editorAxisXSpeed(data.axes.x.speed);
self.editorAxisXInverted(data.axes.x.inverted);
self.editorAxisYSpeed(data.axes.y.speed);
self.editorAxisYInverted(data.axes.y.inverted);
self.editorAxisZSpeed(data.axes.z.speed);
self.editorAxisZInverted(data.axes.z.inverted);
self.editorAxisESpeed(data.axes.e.speed);
self.editorAxisEInverted(data.axes.e.inverted);
self.editor.fromProfileData(data);
var editDialog = $("#settings_printerProfiles_editDialog");
var confirmButton = $("button.btn-confirm", editDialog);
var dialogTitle = $("h3.modal-title", editDialog);
var add = data === undefined;
dialogTitle.text(add ? gettext("Add Printer Profile") : _.sprintf(gettext("Edit Printer Profile \"%(name)s\""), {name: data.name}));
confirmButton.unbind("click");
confirmButton.bind("click", function() {
@ -348,73 +439,6 @@ $(function() {
}
};
self._editorData = function() {
var identifier = self.editorIdentifier();
if (!identifier) {
identifier = self.editorIdentifierPlaceholder();
}
var profile = {
id: identifier,
name: self.editorName(),
color: self.editorColor(),
model: self.editorModel(),
volume: {
width: parseFloat(self.editorVolumeWidth()),
depth: parseFloat(self.editorVolumeDepth()),
height: parseFloat(self.editorVolumeHeight()),
formFactor: self.editorVolumeFormFactor(),
origin: self.editorVolumeOrigin()
},
heatedBed: self.editorHeatedBed(),
extruder: {
count: parseInt(self.editorExtruders()),
offsets: [
[0.0, 0.0]
],
nozzleDiameter: parseFloat(self.editorNozzleDiameter())
},
axes: {
x: {
speed: parseInt(self.editorAxisXSpeed()),
inverted: self.editorAxisXInverted()
},
y: {
speed: parseInt(self.editorAxisYSpeed()),
inverted: self.editorAxisYInverted()
},
z: {
speed: parseInt(self.editorAxisZSpeed()),
inverted: self.editorAxisZInverted()
},
e: {
speed: parseInt(self.editorAxisESpeed()),
inverted: self.editorAxisEInverted()
}
}
};
if (self.editorExtruders() > 1) {
for (var i = 0; i < self.editorExtruders() - 1; i++) {
var offset = [0.0, 0.0];
if (i < self.editorExtruderOffsets().length) {
try {
offset = [parseFloat(self.editorExtruderOffsets()[i]["x"]()), parseFloat(self.editorExtruderOffsets()[i]["y"]())];
} catch (exc) {
log.error("Invalid offset in profile", identifier, "for extruder", i+1, ":", self.editorExtruderOffsets()[i]["x"], ",", self.editorExtruderOffsets()[i]["y"]);
}
}
profile.extruder.offsets.push(offset);
}
}
return profile;
};
self._sanitize = function(name) {
return name.replace(/[^a-zA-Z0-9\-_\.\(\) ]/g, "").replace(/ /g, "_");
};
self.onSettingsShown = self.requestData;
self.onStartup = self.requestData;
}

View file

@ -0,0 +1,150 @@
<form class="form-horizontal">
<div class="control-group" data-bind="css: {error: nameInvalid()}">
<label class="control-label">{{ _('Name') }}</label>
<div class="controls">
<input type="text" data-bind="value: name, valueUpdate: 'afterkeydown'">
<span data-bind="visible: nameInvalid()"><br><span class="help-inline">{{ _('Name must be set') }}</span></span>
</div>
</div>
<div class="control-group" data-bind="css: {error: identifierInvalid()}">
<label class="control-label">{{ _('Identifier') }}</label>
<div class="controls">
<input type="text" data-bind="value: identifier, valueUpdate: 'afterkeydown', enable: isNew, css: {disabled: !isNew()}, attr: {placeholder: identifierPlaceholder}">
<span data-bind="visible: identifierInvalid()"><br><span class="help-inline" data-bind="text: identifierInvalidText()"></span></span>
</div>
</div>
<div class="control-group">
<label class="control-label">{{ _('Model') }}</label>
<div class="controls">
<input type="text" data-bind="value: model">
</div>
</div>
<div class="control-group">
<label class="control-label">{{ _('Color') }}</label>
<div class="controls">
<select data-bind="value: color, options: availableColors, optionsText: 'name', optionsValue: 'key'">
</select>
</div>
</div>
<div class="control-group">
<label class="control-label">{{ _('Form Factor') }}</label>
<div class="controls">
<label class="radio"><input type="radio" name="printerProfileFormFactorGroup" value="rectangular" data-bind="checked: volumeFormFactor"> {{ _('Rectangular') }}</label>
</div>
<div class="controls">
<label class="radio"><input type="radio" name="printerProfileFormFactorGroup" value="circular" data-bind="checked: volumeFormFactor"> {{ _('Circular') }}</label>
</div>
</div>
<div class="control-group">
<div class="control-group">
<label class="control-label">{{ _('Origin') }}</label>
<div class="controls">
<select data-bind="value: volumeOrigin, options: availableOrigins, optionsText: 'name', optionsValue: 'key'">
</select>
</div>
</div>
</div>
<div class="control-group">
<label class="control-label">{{ _('Volume') }}</label>
<div class="controls form-inline">
<label>{{ _('X') }}</label>
<div class="input-append">
<input type="number" step="0.01" class="input-mini text-right" data-bind="value: volumeWidth">
<span class="add-on">mm</span>
</div>
</div>
<div class="controls form-inline">
<label>{{ _('Y') }}</label>
<div class="input-append">
<input type="number" step="0.01" class="input-mini text-right" data-bind="value: volumeDepth">
<span class="add-on">mm</span>
</div>
</div>
<div class="controls form-inline">
<label>{{ _('Z') }}</label>
<div class="input-append">
<input type="number" step="0.01" class="input-mini text-right" data-bind="value: volumeHeight">
<span class="add-on">mm</span>
</div>
</div>
</div>
<div class="control-group">
<label class="control-label">{{ _('Heated Bed') }}</label>
<div class="controls">
<input type="checkbox" data-bind="checked: heatedBed">
</div>
</div>
<div class="control-group">
<label class="control-label">{{ _('Axis') }}</label>
<div class="controls form-inline">
<label>{{ _('X') }}</label>
<div class="input-append">
<input type="number" class="input-mini text-right" data-bind="value: axisXSpeed">
<span class="add-on">mm/min</span>
</div>
<label class="checkbox">
<input type="checkbox" data-bind="checked: axisXInverted"> {{ _('Invert control') }}
</label>
</div>
<div class="controls form-inline">
<label>{{ _('Y') }}</label>
<div class="input-append">
<input type="number" class="input-mini text-right" data-bind="value: axisYSpeed">
<span class="add-on">mm/min</span>
</div>
<label class="checkbox">
<input type="checkbox" data-bind="checked: axisYInverted"> {{ _('Invert control') }}
</label>
</div>
<div class="controls form-inline">
<label>{{ _('Z') }}</label>
<div class="input-append">
<input type="number" class="input-mini text-right" data-bind="value: axisZSpeed">
<span class="add-on">mm/min</span>
</div>
<label class="checkbox">
<input type="checkbox" data-bind="checked: axisZInverted"> {{ _('Invert control') }}
</label>
</div>
<div class="controls form-inline">
<label>{{ _('E') }}</label>
<div class="input-append">
<input type="number" class="input-mini text-right" data-bind="value: axisESpeed">
<span class="add-on">mm/min</span>
</div>
</div>
</div>
<div class="control-group">
<label class="control-label">{{ _('Nozzle Diameter') }}</label>
<div class="controls">
<div class="input-append">
<input type="number" step="0.01" class="input-mini text-right" data-bind="value: nozzleDiameter">
<span class="add-on">mm</span>
</div>
</div>
</div>
<div class="control-group">
<label class="control-label">{{ _('Number of Extruders') }}</label>
<div class="controls">
<input type="number" class="input-mini text-right" min="1" max="10" data-bind="value: extruders">
</div>
</div>
<div class="control-group" data-bind="visible: extruders() > 1">
<label class="control-label">{{ _('Nozzle Offsets (relative to first nozzle T0)') }}</label>
<!-- ko foreach: koExtruderOffsets -->
<div class="controls form-inline">
<label>T<span data-bind="text: idx"></span>:</label>
<label>X</label>
<div class="input-append">
<input type="number" step="0.01" class="input-mini text-right" data-bind="value: x">
<span class="add-on">mm</span>
</div>
<label>Y</label>
<div class="input-append">
<input type="number" step="0.01" class="input-mini text-right" data-bind="value: y">
<span class="add-on">mm</span>
</div>
</div>
<!-- /ko -->
</div>
</form>

View file

@ -0,0 +1,15 @@
<div id="settings_printerProfiles_editDialog" class="modal hide fade">
<div class="modal-header">
<a href="#" class="close" data-dismiss="modal" aria-hidden="true">&times;</a>
<h3 class="modal-title"></h3>
</div>
<div class="modal-body">
<!-- ko with: printerProfiles.editor -->
{% include '_snippets/settings/printerprofiles/profileEditor.jinja2' %}
<!-- /ko -->
</div>
<div class="modal-footer">
<button class="btn" data-dismiss="modal" aria-hidden="true">{{ _('Abort') }}</button>
<button class="btn btn-primary btn-confirm" data-bind="enabled: printerProfiles.enableEditorSubmitButton, css: {disabled: !printerProfiles.enableEditorSubmitButton()}"><i class="icon-spinner icon-spin" data-bind="visible: printerProfiles.requestInProgress()"></i> {{ _('Confirm') }}</button>
</div>
</div>

View file

@ -0,0 +1,30 @@
<table class="table table-striped table-hover table-condensed table-hover" id="settings_printerProfiles_profiles" data-bind="visible: printerProfiles.profiles.items().length > 0" style="display: none">
<thead>
<tr>
<th class="settings_printerProfiles_profiles_name">{{ _('Name') }}</th>
<th class="settings_printerProfiles_profiles_model">{{ _('Model') }}</th>
<th class="settings_printerProfiles_profiles_action">{{ _('Action') }}</th>
</tr>
</thead>
<tbody data-bind="foreach: printerProfiles.profiles.paginatedItems">
<tr data-bind="attr: {title: name}">
<td class="settings_printerProfiles_profiles_name"><span class="icon-star" data-bind="invisible: !isdefault()"></span> <span data-bind="text: name"></span></td>
<td class="settings_printerProfiles_profiles_model" data-bind="text: model"></td>
<td class="settings_printerProfiles_profiles_action">
<a href="#" class="icon-star" title="{{ _('Set as default profile') }}" data-bind="click: function() { $root.printerProfiles.makeDefault($data); }, css: {disabled: $root.printerProfiles.requestInProgress()}, enabled: !$root.printerProfiles.requestInProgress()"></a>&nbsp;|&nbsp;<a href="#" class="icon-pencil" title="{{ _('Edit Profile') }}" data-bind="click: function() { $root.printerProfiles.showEditProfileDialog($data); }, css: {disabled: $root.printerProfiles.requestInProgress()}, enabled: !$root.printerProfiles.requestInProgress()"></a>&nbsp;|&nbsp;<a href="#" class="icon-trash" title="{{ _('Delete Profile') }}" data-bind="click: function() { $root.printerProfiles.removeProfile($data); }, css: {disabled: $root.printerProfiles.requestInProgress()}, enabled: !$root.printerProfiles.requestInProgress()"></a>
</td>
</tr>
</tbody>
</table>
<div class="pagination pagination-mini pagination-centered">
<ul>
<li data-bind="css: {disabled: printerProfiles.profiles.currentPage() === 0}"><a href="#" data-bind="click: printerProfiles.profiles.prevPage">«</a></li>
</ul>
<ul data-bind="foreach: printerProfiles.profiles.pages">
<li data-bind="css: { active: $data.number === $root.printerProfiles.profiles.currentPage(), disabled: $data.number === -1 }"><a href="#" data-bind="text: $data.text, click: function() { $root.printerProfiles.profiles.changePage($data.number); }"></a></li>
</ul>
<ul>
<li data-bind="css: {disabled: printerProfiles.profiles.currentPage() === printerProfiles.profiles.lastPage()}"><a href="#" data-bind="click: printerProfiles.profiles.nextPage">»</a></li>
</ul>
</div>

View file

@ -1,197 +1,7 @@
<h3>{{ _('Printer Profiles') }}</h3>
<table class="table table-striped table-hover table-condensed table-hover" id="settings_printerProfiles_profiles">
<thead>
<tr>
<th class="settings_printerProfiles_profiles_name">{{ _('Name') }}</th>
<th class="settings_printerProfiles_profiles_model">{{ _('Model') }}</th>
<th class="settings_printerProfiles_profiles_action">{{ _('Action') }}</th>
</tr>
</thead>
<tbody data-bind="foreach: printerProfiles.profiles.paginatedItems">
<tr data-bind="attr: {title: name}">
<td class="settings_printerProfiles_profiles_name"><span class="icon-star" data-bind="invisible: !isdefault()"></span> <span data-bind="text: name"></span></td>
<td class="settings_printerProfiles_profiles_model" data-bind="text: model"></td>
<td class="settings_printerProfiles_profiles_action">
<a href="#" class="icon-star" title="{{ _('Set as default profile') }}" data-bind="click: function() { $root.printerProfiles.makeDefault($data); }, css: {disabled: $root.printerProfiles.requestInProgress()}, enabled: !$root.printerProfiles.requestInProgress()"></a>&nbsp;|&nbsp;<a href="#" class="icon-pencil" title="{{ _('Edit Profile') }}" data-bind="click: function() { $root.printerProfiles.showEditProfileDialog($data); }, css: {disabled: $root.printerProfiles.requestInProgress()}, enabled: !$root.printerProfiles.requestInProgress()"></a>&nbsp;|&nbsp;<a href="#" class="icon-trash" title="{{ _('Delete Profile') }}" data-bind="click: function() { $root.printerProfiles.removeProfile($data); }, css: {disabled: $root.printerProfiles.requestInProgress()}, enabled: !$root.printerProfiles.requestInProgress()"></a>
</td>
</tr>
</tbody>
</table>
<div class="pagination pagination-mini pagination-centered">
<ul>
<li data-bind="css: {disabled: printerProfiles.profiles.currentPage() === 0}"><a href="#" data-bind="click: printerProfiles.profiles.prevPage">«</a></li>
</ul>
<ul data-bind="foreach: printerProfiles.profiles.pages">
<li data-bind="css: { active: $data.number === $root.printerProfiles.profiles.currentPage(), disabled: $data.number === -1 }"><a href="#" data-bind="text: $data.text, click: function() { $root.printerProfiles.profiles.changePage($data.number); }"></a></li>
</ul>
<ul>
<li data-bind="css: {disabled: printerProfiles.profiles.currentPage() === printerProfiles.profiles.lastPage()}"><a href="#" data-bind="click: printerProfiles.profiles.nextPage">»</a></li>
</ul>
</div>
{% include "_snippets/settings/printerprofiles/profiles.jinja2" %}
<button class="btn pull-right" data-bind="click: function() { $root.printerProfiles.showEditProfileDialog(); }">{{ _('Add Profile...') }}</button>
<div id="settings_printerProfiles_editDialog" class="modal hide fade">
<div class="modal-header">
<a href="#" class="close" data-dismiss="modal" aria-hidden="true">&times;</a>
<h3 class="modal-title"></h3>
</div>
<div class="modal-body">
<form class="form-horizontal">
<div class="control-group" data-bind="css: {error: printerProfiles.editorNameInvalid()}">
<label class="control-label">{{ _('Name') }}</label>
<div class="controls">
<input type="text" data-bind="value: printerProfiles.editorName, valueUpdate: 'afterkeydown'">
<span data-bind="visible: printerProfiles.editorNameInvalid()"><br><span class="help-inline">{{ _('Name must be set') }}</span></span>
</div>
</div>
<div class="control-group" data-bind="css: {error: printerProfiles.editorIdentifierInvalid()}">
<label class="control-label">{{ _('Identifier') }}</label>
<div class="controls">
<input type="text" data-bind="value: printerProfiles.editorIdentifier, valueUpdate: 'afterkeydown', enable: printerProfiles.editorNew, css: {disabled: !printerProfiles.editorNew()}, attr: {placeholder: printerProfiles.editorIdentifierPlaceholder}">
<span data-bind="visible: printerProfiles.editorIdentifierInvalid()"><br><span class="help-inline" data-bind="text: printerProfiles.editorIdentifierInvalidText()"></span></span>
</div>
</div>
<div class="control-group">
<label class="control-label">{{ _('Model') }}</label>
<div class="controls">
<input type="text" data-bind="value: printerProfiles.editorModel">
</div>
</div>
<div class="control-group">
<label class="control-label">{{ _('Color') }}</label>
<div class="controls">
<select data-bind="value: printerProfiles.editorColor, options: printerProfiles.availableColors, optionsText: 'name', optionsValue: 'key'">
</select>
</div>
</div>
<div class="control-group">
<label class="control-label">{{ _('Form Factor') }}</label>
<div class="controls">
<label class="radio"><input type="radio" name="printerProfileFormFactorGroup" value="rectangular" data-bind="checked: printerProfiles.editorVolumeFormFactor"> {{ _('Rectangular') }}</label>
</div>
<div class="controls">
<label class="radio"><input type="radio" name="printerProfileFormFactorGroup" value="circular" data-bind="checked: printerProfiles.editorVolumeFormFactor"> {{ _('Circular') }}</label>
</div>
</div>
<div class="control-group">
<div class="control-group">
<label class="control-label">{{ _('Origin') }}</label>
<div class="controls">
<select data-bind="value: printerProfiles.editorVolumeOrigin, options: printerProfiles.availableOrigins, optionsText: 'name', optionsValue: 'key'">
</select>
</div>
</div>
</div>
<div class="control-group">
<label class="control-label">{{ _('Volume') }}</label>
<div class="controls form-inline">
<label>{{ _('X') }}</label>
<div class="input-append">
<input type="number" step="0.01" class="input-mini text-right" data-bind="value: printerProfiles.editorVolumeWidth">
<span class="add-on">mm</span>
</div>
</div>
<div class="controls form-inline">
<label>{{ _('Y') }}</label>
<div class="input-append">
<input type="number" step="0.01" class="input-mini text-right" data-bind="value: printerProfiles.editorVolumeDepth">
<span class="add-on">mm</span>
</div>
</div>
<div class="controls form-inline">
<label>{{ _('Z') }}</label>
<div class="input-append">
<input type="number" step="0.01" class="input-mini text-right" data-bind="value: printerProfiles.editorVolumeHeight">
<span class="add-on">mm</span>
</div>
</div>
</div>
<div class="control-group">
<label class="control-label">{{ _('Heated Bed') }}</label>
<div class="controls">
<input type="checkbox" data-bind="checked: printerProfiles.editorHeatedBed">
</div>
</div>
<div class="control-group">
<label class="control-label">{{ _('Axis') }}</label>
<div class="controls form-inline">
<label>{{ _('X') }}</label>
<div class="input-append">
<input type="number" class="input-mini text-right" data-bind="value: printerProfiles.editorAxisXSpeed">
<span class="add-on">mm/min</span>
</div>
<label class="checkbox">
<input type="checkbox" data-bind="checked: printerProfiles.editorAxisXInverted"> {{ _('Invert control') }}
</label>
</div>
<div class="controls form-inline">
<label>{{ _('Y') }}</label>
<div class="input-append">
<input type="number" class="input-mini text-right" data-bind="value: printerProfiles.editorAxisYSpeed">
<span class="add-on">mm/min</span>
</div>
<label class="checkbox">
<input type="checkbox" data-bind="checked: printerProfiles.editorAxisYInverted"> {{ _('Invert control') }}
</label>
</div>
<div class="controls form-inline">
<label>{{ _('Z') }}</label>
<div class="input-append">
<input type="number" class="input-mini text-right" data-bind="value: printerProfiles.editorAxisZSpeed">
<span class="add-on">mm/min</span>
</div>
<label class="checkbox">
<input type="checkbox" data-bind="checked: printerProfiles.editorAxisZInverted"> {{ _('Invert control') }}
</label>
</div>
<div class="controls form-inline">
<label>{{ _('E') }}</label>
<div class="input-append">
<input type="number" class="input-mini text-right" data-bind="value: printerProfiles.editorAxisESpeed">
<span class="add-on">mm/min</span>
</div>
</div>
</div>
<div class="control-group">
<label class="control-label">{{ _('Nozzle Diameter') }}</label>
<div class="controls">
<div class="input-append">
<input type="number" step="0.01" class="input-mini text-right" data-bind="value: printerProfiles.editorNozzleDiameter">
<span class="add-on">mm</span>
</div>
</div>
</div>
<div class="control-group">
<label class="control-label">{{ _('Number of Extruders') }}</label>
<div class="controls">
<input type="number" class="input-mini text-right" min="1" max="10" data-bind="value: printerProfiles.editorExtruders">
</div>
</div>
<div class="control-group" data-bind="visible: printerProfiles.editorExtruders() > 1">
<label class="control-label">{{ _('Nozzle Offsets (relative to first nozzle T0)') }}</label>
<!-- ko foreach: printerProfiles.koEditorExtruderOffsets -->
<div class="controls form-inline">
<label>T<span data-bind="text: idx"></span>:</label>
<label>X</label>
<div class="input-append">
<input type="number" step="0.01" class="input-mini text-right" data-bind="value: x">
<span class="add-on">mm</span>
</div>
<label>Y</label>
<div class="input-append">
<input type="number" step="0.01" class="input-mini text-right" data-bind="value: y">
<span class="add-on">mm</span>
</div>
</div>
<!-- /ko -->
</div>
</form>
</div>
<div class="modal-footer">
<button class="btn" data-dismiss="modal" aria-hidden="true">{{ _('Abort') }}</button>
<button class="btn btn-primary btn-confirm" data-bind="enabled: printerProfiles.enableEditorSubmitButton, css: {disabled: !printerProfiles.enableEditorSubmitButton()}"><i class="icon-spinner icon-spin" data-bind="visible: printerProfiles.requestInProgress()"></i> {{ _('Confirm') }}</button>
</div>
</div>
{% include "_snippets/settings/printerprofiles/profileImporter.jinja2" %}