Added settings dialog for configuring OctoPrint
Warning: Many settings will need a restart of OctoPrint to take effect, adding corresponding notes is still a TODO. There's also no proper validation and error handling yet, so use at your own risk.master
parent
cbae792dbe
commit
1c4203b708
|
@ -328,20 +328,64 @@ def setTimelapseConfig():
|
||||||
@app.route(BASEURL + "settings", methods=["GET"])
|
@app.route(BASEURL + "settings", methods=["GET"])
|
||||||
def getSettings():
|
def getSettings():
|
||||||
s = settings()
|
s = settings()
|
||||||
|
|
||||||
|
[movementSpeedX, movementSpeedY, movementSpeedZ, movementSpeedE] = s.get(["printerParameters", "movementSpeed", ["x", "y", "z", "e"]])
|
||||||
|
|
||||||
return jsonify({
|
return jsonify({
|
||||||
"serial_port": s.get("serial", "port"),
|
"printer": {
|
||||||
"serial_baudrate": s.get("serial", "baudrate")
|
"movementSpeedX": movementSpeedX,
|
||||||
|
"movementSpeedY": movementSpeedY,
|
||||||
|
"movementSpeedZ": movementSpeedZ,
|
||||||
|
"movementSpeedE": movementSpeedE,
|
||||||
|
},
|
||||||
|
"webcam": {
|
||||||
|
"streamUrl": s.get(["webcam", "stream"]),
|
||||||
|
"snapshotUrl": s.get(["webcam", "snapshot"]),
|
||||||
|
"ffmpegPath": s.get(["webcam", "ffmpeg"]),
|
||||||
|
"bitrate": s.get(["webcam", "bitrate"])
|
||||||
|
},
|
||||||
|
"feature": {
|
||||||
|
"gcodeViewer": s.getBoolean(["feature", "gCodeVisualizer"]),
|
||||||
|
"waitForStart": s.getBoolean(["feature", "waitForStartOnConnect"])
|
||||||
|
},
|
||||||
|
"folder": {
|
||||||
|
"uploads": s.getBaseFolder("uploads"),
|
||||||
|
"timelapse": s.getBaseFolder("timelapse"),
|
||||||
|
"timelapseTmp": s.getBaseFolder("timelapse_tmp"),
|
||||||
|
"logs": s.getBaseFolder("logs")
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
@app.route(BASEURL + "settings", methods=["POST"])
|
@app.route(BASEURL + "settings", methods=["POST"])
|
||||||
def setSettings():
|
def setSettings():
|
||||||
|
if "application/json" in request.headers["Content-Type"]:
|
||||||
|
data = request.json
|
||||||
s = settings()
|
s = settings()
|
||||||
if request.values.has_key("serial_port"):
|
|
||||||
s.set("serial", "port", request.values["serial_port"])
|
if "printer" in data.keys():
|
||||||
if request.values.has_key("serial_baudrate"):
|
if "movementSpeedX" in data["printer"].keys(): s.setInt(["printerParameters", "movementSpeed", "x"], data["printer"]["movementSpeedX"])
|
||||||
s.set("serial", "baudrate", request.values["serial_baudrate"])
|
if "movementSpeedY" in data["printer"].keys(): s.setInt(["printerParameters", "movementSpeed", "y"], data["printer"]["movementSpeedY"])
|
||||||
|
if "movementSpeedZ" in data["printer"].keys(): s.setInt(["printerParameters", "movementSpeed", "z"], data["printer"]["movementSpeedZ"])
|
||||||
|
if "movementSpeedE" in data["printer"].keys(): s.setInt(["printerParameters", "movementSpeed", "e"], data["printer"]["movementSpeedE"])
|
||||||
|
|
||||||
|
if "webcam" in data.keys():
|
||||||
|
if "streamUrl" in data["webcam"].keys(): s.set(["webcam", "stream"], data["webcam"]["streamUrl"])
|
||||||
|
if "snapshot" in data["webcam"].keys(): s.set(["webcam", "snapshot"], data["webcam"]["snapshotUrl"])
|
||||||
|
if "ffmpeg" in data["webcam"].keys(): s.set(["webcam", "ffmpeg"], data["webcam"]["ffmpeg"])
|
||||||
|
if "bitrate" in data["webcam"].keys(): s.set(["webcam", "bitrate"], data["webcam"]["bitrate"])
|
||||||
|
|
||||||
|
if "feature" in data.keys():
|
||||||
|
if "gcodeViewer" in data["feature"].keys(): s.setBoolean(["feature", "gCodeVisualizer"], data["feature"]["gcodeViewer"])
|
||||||
|
if "waitForStart" in data["feature"].keys(): s.setBoolean(["feature", "waitForStartOnConnect"], data["feature"]["waitForStart"])
|
||||||
|
|
||||||
|
if "folder" in data.keys():
|
||||||
|
if "uploads" in data["folder"].keys(): s.setBaseFolder("uploads", data["folder"]["uploads"])
|
||||||
|
if "timelapse" in data["folder"].keys(): s.setBaseFolder("timelapse", data["folder"]["timelapse"])
|
||||||
|
if "timelapseTmp" in data["folder"].keys(): s.setBaseFolder("timelapse_tmp", data["folder"]["timelapseTmp"])
|
||||||
|
if "logs" in data["folder"].keys(): s.setBaseFolder("logs", data["folder"]["logs"])
|
||||||
|
|
||||||
s.save()
|
s.save()
|
||||||
|
|
||||||
return getSettings()
|
return getSettings()
|
||||||
|
|
||||||
#~~ startup code
|
#~~ startup code
|
||||||
|
|
|
@ -6,6 +6,7 @@ import ConfigParser
|
||||||
import sys
|
import sys
|
||||||
import os
|
import os
|
||||||
import yaml
|
import yaml
|
||||||
|
import logging
|
||||||
|
|
||||||
APPNAME="OctoPrint"
|
APPNAME="OctoPrint"
|
||||||
OLD_APPNAME="PrinterWebUI"
|
OLD_APPNAME="PrinterWebUI"
|
||||||
|
@ -63,6 +64,8 @@ valid_boolean_trues = ["true", "yes", "y", "1"]
|
||||||
class Settings(object):
|
class Settings(object):
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
|
self._logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
self.settings_dir = None
|
self.settings_dir = None
|
||||||
|
|
||||||
self._config = None
|
self._config = None
|
||||||
|
@ -79,6 +82,12 @@ class Settings(object):
|
||||||
if os.path.exists(old_settings_dir) and os.path.isdir(old_settings_dir) and not os.path.exists(self.settings_dir):
|
if os.path.exists(old_settings_dir) and os.path.isdir(old_settings_dir) and not os.path.exists(self.settings_dir):
|
||||||
os.rename(old_settings_dir, self.settings_dir)
|
os.rename(old_settings_dir, self.settings_dir)
|
||||||
|
|
||||||
|
def _getDefaultFolder(self, type):
|
||||||
|
folder = default_settings["folder"][type]
|
||||||
|
if folder is None:
|
||||||
|
folder = os.path.join(self.settings_dir, type.replace("_", os.path.sep))
|
||||||
|
return folder
|
||||||
|
|
||||||
#~~ load and save
|
#~~ load and save
|
||||||
|
|
||||||
def load(self):
|
def load(self):
|
||||||
|
@ -164,6 +173,7 @@ class Settings(object):
|
||||||
try:
|
try:
|
||||||
return int(value)
|
return int(value)
|
||||||
except ValueError:
|
except ValueError:
|
||||||
|
self._logger.warn("Could not convert %r to a valid integer when getting option %r" % (value, path))
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def getBoolean(self, path):
|
def getBoolean(self, path):
|
||||||
|
@ -175,12 +185,12 @@ class Settings(object):
|
||||||
return value.lower() in valid_boolean_trues
|
return value.lower() in valid_boolean_trues
|
||||||
|
|
||||||
def getBaseFolder(self, type):
|
def getBaseFolder(self, type):
|
||||||
if type not in old_default_settings["folder"].keys():
|
if type not in default_settings["folder"].keys():
|
||||||
return None
|
return None
|
||||||
|
|
||||||
folder = self.get(["folder", type])
|
folder = self.get(["folder", type])
|
||||||
if folder is None:
|
if folder is None:
|
||||||
folder = os.path.join(self.settings_dir, type.replace("_", os.path.sep))
|
folder = self._getDefaultFolder(type)
|
||||||
|
|
||||||
if not os.path.isdir(folder):
|
if not os.path.isdir(folder):
|
||||||
os.makedirs(folder)
|
os.makedirs(folder)
|
||||||
|
@ -189,7 +199,7 @@ class Settings(object):
|
||||||
|
|
||||||
#~~ setter
|
#~~ setter
|
||||||
|
|
||||||
def set(self, path, value):
|
def set(self, path, value, force=False):
|
||||||
if len(path) == 0:
|
if len(path) == 0:
|
||||||
return
|
return
|
||||||
|
|
||||||
|
@ -198,38 +208,63 @@ class Settings(object):
|
||||||
|
|
||||||
while len(path) > 1:
|
while len(path) > 1:
|
||||||
key = path.pop(0)
|
key = path.pop(0)
|
||||||
if key in config.keys():
|
if key in config.keys() and key in defaults.keys():
|
||||||
config = config[key]
|
config = config[key]
|
||||||
|
defaults = defaults[key]
|
||||||
elif key in defaults.keys():
|
elif key in defaults.keys():
|
||||||
config[key] = {}
|
config[key] = {}
|
||||||
config = config[key]
|
config = config[key]
|
||||||
|
defaults = defaults[key]
|
||||||
else:
|
else:
|
||||||
return
|
return
|
||||||
|
|
||||||
key = path.pop(0)
|
key = path.pop(0)
|
||||||
|
if not force and key in defaults.keys() and key in config.keys() and defaults[key] == value:
|
||||||
|
del config[key]
|
||||||
|
self._dirty = True
|
||||||
|
elif force or (not key in config.keys() and defaults[key] != value) or (key in config.keys() and config[key] != value):
|
||||||
|
if value is None:
|
||||||
|
del config[key]
|
||||||
|
else:
|
||||||
config[key] = value
|
config[key] = value
|
||||||
self._dirty = True
|
self._dirty = True
|
||||||
|
|
||||||
def setInt(self, path, value):
|
def setInt(self, path, value, force=False):
|
||||||
if value is None:
|
if value is None:
|
||||||
return
|
self.set(path, None, force)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
intValue = int(value)
|
intValue = int(value)
|
||||||
except ValueError:
|
except ValueError:
|
||||||
|
self._logger.warn("Could not convert %r to a valid integer when setting option %r" % (value, path))
|
||||||
return
|
return
|
||||||
|
|
||||||
self.set(path, intValue)
|
self.set(path, intValue, force)
|
||||||
|
|
||||||
def setBoolean(self, path, value):
|
def setBoolean(self, path, value, force=False):
|
||||||
if value is None:
|
if value is None or isinstance(value, bool):
|
||||||
return
|
self.set(path, value, force)
|
||||||
elif isinstance(value, bool):
|
|
||||||
self.set(path, value)
|
|
||||||
elif value.lower() in valid_boolean_trues:
|
elif value.lower() in valid_boolean_trues:
|
||||||
self.set(path, True)
|
self.set(path, True, force)
|
||||||
else:
|
else:
|
||||||
self.set(path, False)
|
self.set(path, False, force)
|
||||||
|
|
||||||
|
def setBaseFolder(self, type, path, force=False):
|
||||||
|
if type not in default_settings["folder"].keys():
|
||||||
|
return None
|
||||||
|
|
||||||
|
currentPath = self.getBaseFolder(type)
|
||||||
|
defaultPath = self._getDefaultFolder(type)
|
||||||
|
if (path is None or path == defaultPath) and "folder" in self._config.keys() and type in self._config["folder"].keys():
|
||||||
|
del self._config["folder"][type]
|
||||||
|
if not self._config["folder"]:
|
||||||
|
del self._config["folder"]
|
||||||
|
self._dirty = True
|
||||||
|
elif (path != currentPath and path != defaultPath) or force:
|
||||||
|
if not "folder" in self._config.keys():
|
||||||
|
self._config["folder"] = {}
|
||||||
|
self._config["folder"][type] = path
|
||||||
|
self._dirty = True
|
||||||
|
|
||||||
def _resolveSettingsDir(applicationName):
|
def _resolveSettingsDir(applicationName):
|
||||||
# taken from http://stackoverflow.com/questions/1084697/how-do-i-store-desktop-application-data-in-a-cross-platform-way-for-python
|
# taken from http://stackoverflow.com/questions/1084697/how-do-i-store-desktop-application-data-in-a-cross-platform-way-for-python
|
||||||
|
|
|
@ -1,8 +1,12 @@
|
||||||
|
/** Top bar */
|
||||||
|
|
||||||
body {
|
body {
|
||||||
padding-top: 60px;
|
padding-top: 60px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tab-content {
|
/** OctoPrint application tabs */
|
||||||
|
|
||||||
|
.octoprint-container .tab-content {
|
||||||
padding: 9px 15px;
|
padding: 9px 15px;
|
||||||
border-left: 1px solid #DDD;
|
border-left: 1px solid #DDD;
|
||||||
border-right: 1px solid #DDD;
|
border-right: 1px solid #DDD;
|
||||||
|
@ -16,13 +20,11 @@ body {
|
||||||
border-bottom-left-radius: 4px;
|
border-bottom-left-radius: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.accordion-heading a.accordion-toggle { display: inline-block; }
|
.octoprint-container .nav {
|
||||||
|
|
||||||
.nav {
|
|
||||||
margin-bottom: 0px;
|
margin-bottom: 0px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tab-content h1 {
|
.octoprint-container .tab-content h1 {
|
||||||
display: block;
|
display: block;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
|
@ -35,6 +37,17 @@ body {
|
||||||
font-weight: normal;
|
font-weight: normal;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Accordions */
|
||||||
|
|
||||||
|
.octoprint-container .accordion-heading a.accordion-toggle { display: inline-block; }
|
||||||
|
|
||||||
|
.octoprint-container .accordion-heading .settings-trigger {
|
||||||
|
float: right;
|
||||||
|
padding: 0px 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Tables */
|
||||||
|
|
||||||
table {
|
table {
|
||||||
table-layout: fixed;
|
table-layout: fixed;
|
||||||
}
|
}
|
||||||
|
@ -59,6 +72,24 @@ table th.gcode_files_action, table td.gcode_files_action {
|
||||||
width: 20%;
|
width: 20%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
table th.timelapse_files_name, table td.timelapse_files_name {
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
text-align: left;
|
||||||
|
width: 55%;
|
||||||
|
}
|
||||||
|
|
||||||
|
table th.timelapse_files_size, table td.timelapse_files_size {
|
||||||
|
text-align: right;
|
||||||
|
width: 25%;
|
||||||
|
}
|
||||||
|
|
||||||
|
table th.timelapse_files_action, table td.timelapse_files_action {
|
||||||
|
text-align: center;
|
||||||
|
width: 20%;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Temperature tab */
|
||||||
|
|
||||||
#temperature-graph {
|
#temperature-graph {
|
||||||
height: 350px;
|
height: 350px;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
@ -75,11 +106,14 @@ table th.gcode_files_action, table td.gcode_files_action {
|
||||||
text-align: right;
|
text-align: right;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Connection settings */
|
||||||
|
|
||||||
#connection_ports, #connection_baudrates {
|
#connection_ports, #connection_baudrates {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Offline overlay */
|
||||||
|
|
||||||
#offline_overlay {
|
#offline_overlay {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
top: 0;
|
top: 0;
|
||||||
|
@ -116,26 +150,14 @@ table th.gcode_files_action, table td.gcode_files_action {
|
||||||
margin: auto;
|
margin: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
table th.timelapse_files_name, table td.timelapse_files_name {
|
/** Webcam */
|
||||||
text-overflow: ellipsis;
|
|
||||||
text-align: left;
|
|
||||||
width: 55%;
|
|
||||||
}
|
|
||||||
|
|
||||||
table th.timelapse_files_size, table td.timelapse_files_size {
|
|
||||||
text-align: right;
|
|
||||||
width: 25%;
|
|
||||||
}
|
|
||||||
|
|
||||||
table th.timelapse_files_action, table td.timelapse_files_action {
|
|
||||||
text-align: center;
|
|
||||||
width: 20%;
|
|
||||||
}
|
|
||||||
|
|
||||||
#webcam_container {
|
#webcam_container {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** GCODE file manager */
|
||||||
|
|
||||||
#files .popover {
|
#files .popover {
|
||||||
font-size: 85%;
|
font-size: 85%;
|
||||||
}
|
}
|
||||||
|
@ -144,9 +166,7 @@ table th.timelapse_files_action, table td.timelapse_files_action {
|
||||||
margin-bottom: 0;
|
margin-bottom: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.overflow_visible {
|
/** Controls */
|
||||||
overflow: visible !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
#controls {
|
#controls {
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
@ -193,11 +213,13 @@ table th.timelapse_files_action, table td.timelapse_files_action {
|
||||||
height: 30px;
|
height: 30px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.accordion-heading .settings-trigger {
|
/** General helper classes */
|
||||||
float: right;
|
|
||||||
padding: 0px 15px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.text-right {
|
.text-right {
|
||||||
text-align: right;
|
text-align: right;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.overflow_visible {
|
||||||
|
overflow: visible !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
|
@ -85,7 +85,7 @@
|
||||||
margin-right: 5px;
|
margin-right: 5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mbut{
|
#gcode .mbut{
|
||||||
height:60px;
|
height:60px;
|
||||||
width: 250px;
|
width: 250px;
|
||||||
font-size: 1em;
|
font-size: 1em;
|
||||||
|
@ -96,20 +96,20 @@
|
||||||
padding: 0px;
|
padding: 0px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mtab{
|
#gcode .mtab{
|
||||||
background: transparent;
|
background: transparent;
|
||||||
border: none;
|
border: none;
|
||||||
font-weight: normal;
|
font-weight: normal;
|
||||||
font-size: 0.5em;
|
font-size: 0.5em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mtab-defstate{
|
#gcode .mtab-defstate{
|
||||||
background: transparent;
|
background: transparent;
|
||||||
border: none;
|
border: none;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.mtab-content{
|
#gcode .mtab-content{
|
||||||
background: #ffffff;
|
background: #ffffff;
|
||||||
background-image: none;
|
background-image: none;
|
||||||
border: 0px none #dddddd;
|
border: 0px none #dddddd;
|
||||||
|
@ -117,22 +117,22 @@
|
||||||
margin: 0px;
|
margin: 0px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.bar {
|
#gcode .bar {
|
||||||
-webkit-transition: width 0s linear !important;
|
-webkit-transition: width 0s linear !important;
|
||||||
-moz-transition: width 0s linear !important;
|
-moz-transition: width 0s linear !important;
|
||||||
-o-transition: width 0s linear !important;
|
-o-transition: width 0s linear !important;
|
||||||
transition: width 0s linear !important;
|
transition: width 0s linear !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.nav {
|
#gcode .nav {
|
||||||
margin-bottom: 0px !important;
|
margin-bottom: 0px !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tab-content {
|
#gcode .tab-content {
|
||||||
overflow: visible;
|
overflow: visible;
|
||||||
}
|
}
|
||||||
|
|
||||||
.aboutpage {
|
#gcode .aboutpage {
|
||||||
margin: 10px;
|
margin: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -150,12 +150,12 @@
|
||||||
margin-bottom: 10px;
|
margin-bottom: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.colorBox {
|
#gcode .colorBox {
|
||||||
width: 50px;
|
width: 50px;
|
||||||
height: 15px;
|
height: 15px;
|
||||||
border: 1px solid #000000;
|
border: 1px solid #000000;
|
||||||
float:left;
|
float:left;
|
||||||
}
|
}
|
||||||
|
|
||||||
.activeline {background: #fff0b6 !important;}
|
#gcode .activeline {background: #fff0b6 !important;}
|
||||||
|
|
||||||
|
|
|
@ -998,6 +998,97 @@ function GcodeViewModel() {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function SettingsViewModel() {
|
||||||
|
var self = this;
|
||||||
|
|
||||||
|
self.printer_movementSpeedX = ko.observable(undefined);
|
||||||
|
self.printer_movementSpeedY = ko.observable(undefined);
|
||||||
|
self.printer_movementSpeedZ = ko.observable(undefined);
|
||||||
|
self.printer_movementSpeedE = ko.observable(undefined);
|
||||||
|
|
||||||
|
self.webcam_streamUrl = ko.observable(undefined);
|
||||||
|
self.webcam_snapshotUrl = ko.observable(undefined);
|
||||||
|
self.webcam_ffmpegPath = ko.observable(undefined);
|
||||||
|
self.webcam_bitrate = ko.observable(undefined);
|
||||||
|
|
||||||
|
self.feature_gcodeViewer = ko.observable(undefined);
|
||||||
|
self.feature_waitForStart = ko.observable(undefined);
|
||||||
|
|
||||||
|
self.folder_uploads = ko.observable(undefined);
|
||||||
|
self.folder_timelapse = ko.observable(undefined);
|
||||||
|
self.folder_timelapseTmp = ko.observable(undefined);
|
||||||
|
self.folder_logs = ko.observable(undefined);
|
||||||
|
|
||||||
|
self.requestData = function() {
|
||||||
|
$.ajax({
|
||||||
|
url: AJAX_BASEURL + "settings",
|
||||||
|
type: "GET",
|
||||||
|
dataType: "json",
|
||||||
|
success: self.fromResponse
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
self.fromResponse = function(response) {
|
||||||
|
self.printer_movementSpeedX(response.printer.movementSpeedX);
|
||||||
|
self.printer_movementSpeedY(response.printer.movementSpeedY);
|
||||||
|
self.printer_movementSpeedZ(response.printer.movementSpeedZ);
|
||||||
|
self.printer_movementSpeedE(response.printer.movementSpeedE);
|
||||||
|
|
||||||
|
self.webcam_streamUrl(response.webcam.streamUrl);
|
||||||
|
self.webcam_snapshotUrl(response.webcam.snapshotUrl);
|
||||||
|
self.webcam_ffmpegPath(response.webcam.ffmpegPath);
|
||||||
|
self.webcam_bitrate(response.webcam.bitrate);
|
||||||
|
|
||||||
|
self.feature_gcodeViewer(response.feature.gcodeViewer);
|
||||||
|
self.feature_waitForStart(response.feature.waitForStart);
|
||||||
|
|
||||||
|
self.folder_uploads(response.folder.uploads);
|
||||||
|
self.folder_timelapse(response.folder.timelapse);
|
||||||
|
self.folder_timelapseTmp(response.folder.timelapseTmp);
|
||||||
|
self.folder_logs(response.folder.logs);
|
||||||
|
}
|
||||||
|
|
||||||
|
self.saveData = function() {
|
||||||
|
var data = {
|
||||||
|
"printer": {
|
||||||
|
"movementSpeedX": self.printer_movementSpeedX(),
|
||||||
|
"movementSpeedY": self.printer_movementSpeedY(),
|
||||||
|
"movementSpeedZ": self.printer_movementSpeedZ(),
|
||||||
|
"movementSpeedE": self.printer_movementSpeedE()
|
||||||
|
},
|
||||||
|
"webcam": {
|
||||||
|
"streamUrl": self.webcam_streamUrl(),
|
||||||
|
"snapshotUrl": self.webcam_snapshotUrl(),
|
||||||
|
"ffmpegPath": self.webcam_ffmpegPath(),
|
||||||
|
"bitrate": self.webcam_bitrate()
|
||||||
|
},
|
||||||
|
"feature": {
|
||||||
|
"gcodeViewer": self.feature_gcodeViewer(),
|
||||||
|
"waitForStart": self.feature_waitForStart()
|
||||||
|
},
|
||||||
|
"folder": {
|
||||||
|
"uploads": self.folder_uploads(),
|
||||||
|
"timelapse": self.folder_timelapse(),
|
||||||
|
"timelapseTmp": self.folder_timelapseTmp(),
|
||||||
|
"logs": self.folder_logs()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$.ajax({
|
||||||
|
url: AJAX_BASEURL + "settings",
|
||||||
|
type: "POST",
|
||||||
|
dataType: "json",
|
||||||
|
contentType: "application/json; charset=UTF-8",
|
||||||
|
data: JSON.stringify(data),
|
||||||
|
success: function(response) {
|
||||||
|
self.fromResponse(response);
|
||||||
|
$("#settings_dialog").modal("hide");
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
function DataUpdater(connectionViewModel, printerStateViewModel, temperatureViewModel, controlsViewModel, speedViewModel, terminalViewModel, gcodeFilesViewModel, webcamViewModel, gcodeViewModel) {
|
function DataUpdater(connectionViewModel, printerStateViewModel, temperatureViewModel, controlsViewModel, speedViewModel, terminalViewModel, gcodeFilesViewModel, webcamViewModel, gcodeViewModel) {
|
||||||
var self = this;
|
var self = this;
|
||||||
|
|
||||||
|
@ -1074,6 +1165,7 @@ $(function() {
|
||||||
var gcodeFilesViewModel = new GcodeFilesViewModel();
|
var gcodeFilesViewModel = new GcodeFilesViewModel();
|
||||||
var webcamViewModel = new WebcamViewModel();
|
var webcamViewModel = new WebcamViewModel();
|
||||||
var gcodeViewModel = new GcodeViewModel();
|
var gcodeViewModel = new GcodeViewModel();
|
||||||
|
var settingsViewModel = new SettingsViewModel();
|
||||||
|
|
||||||
var dataUpdater = new DataUpdater(
|
var dataUpdater = new DataUpdater(
|
||||||
connectionViewModel,
|
connectionViewModel,
|
||||||
|
@ -1237,6 +1329,7 @@ $(function() {
|
||||||
ko.applyBindings(terminalViewModel, document.getElementById("term"));
|
ko.applyBindings(terminalViewModel, document.getElementById("term"));
|
||||||
ko.applyBindings(speedViewModel, document.getElementById("speed"));
|
ko.applyBindings(speedViewModel, document.getElementById("speed"));
|
||||||
ko.applyBindings(gcodeViewModel, document.getElementById("gcode"));
|
ko.applyBindings(gcodeViewModel, document.getElementById("gcode"));
|
||||||
|
ko.applyBindings(settingsViewModel, document.getElementById("settings_dialog"));
|
||||||
|
|
||||||
var webcamElement = document.getElementById("webcam");
|
var webcamElement = document.getElementById("webcam");
|
||||||
if (webcamElement) {
|
if (webcamElement) {
|
||||||
|
@ -1252,6 +1345,7 @@ $(function() {
|
||||||
controlsViewModel.requestData();
|
controlsViewModel.requestData();
|
||||||
gcodeFilesViewModel.requestData();
|
gcodeFilesViewModel.requestData();
|
||||||
webcamViewModel.requestData();
|
webcamViewModel.requestData();
|
||||||
|
settingsViewModel.requestData();
|
||||||
|
|
||||||
//~~ UI stuff
|
//~~ UI stuff
|
||||||
|
|
||||||
|
|
|
@ -25,10 +25,15 @@
|
||||||
<div class="navbar-inner">
|
<div class="navbar-inner">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<a class="brand" href="#"><img src="{{ url_for('static', filename='img/tentacle-20x20.png') }}"> OctoPrint</a>
|
<a class="brand" href="#"><img src="{{ url_for('static', filename='img/tentacle-20x20.png') }}"> OctoPrint</a>
|
||||||
|
<div class="nav-collapse">
|
||||||
|
<ul class="nav pull-right">
|
||||||
|
<li><a class="pull-right" href="#settings_dialog" data-toggle="modal"><i class="icon-wrench"></i> Settings</a></li>
|
||||||
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="container">
|
</div>
|
||||||
|
<div class="container octoprint-container">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="accordion span4">
|
<div class="accordion span4">
|
||||||
<div class="accordion-group">
|
<div class="accordion-group">
|
||||||
|
@ -450,6 +455,7 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="offline_overlay">
|
<div id="offline_overlay">
|
||||||
<div id="offline_overlay_background"></div>
|
<div id="offline_overlay_background"></div>
|
||||||
<div id="offline_overlay_wrapper">
|
<div id="offline_overlay_wrapper">
|
||||||
|
@ -469,6 +475,132 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div id="settings_dialog" class="modal hide fade" tabindex="-1" role="dialog" aria-labelledby="settings_dialog_label" aria-hidden="true">
|
||||||
|
<div class="modal-header">
|
||||||
|
<button type="button" class="close" data-dismiss="modal">×</button>
|
||||||
|
<h3 id="settings_dialog_label">OctoPrint Settings</h3>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<div class="tabbable">
|
||||||
|
<ul class="nav nav-pills" id="settingsTabs">
|
||||||
|
<li class="active"><a href="#settings_printerParameters" data-toggle="tab">Printer Parameters</a></li>
|
||||||
|
<li><a href="#settings_webcam" data-toggle="tab">Webcam</a></li>
|
||||||
|
<li><a href="#settings_features" data-toggle="tab">Features</a></li>
|
||||||
|
<li><a href="#settings_folder" data-toggle="tab">Folder</a></li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<div class="tab-content">
|
||||||
|
<div class="tab-pane active" id="settings_printerParameters">
|
||||||
|
<form class="form-horizontal">
|
||||||
|
<div class="control-group">
|
||||||
|
<label class="control-label" for="settings-movementSpeedX">Movement Speed X Axis</label>
|
||||||
|
<div class="controls">
|
||||||
|
<input type="text" class="input-mini text-right" data-bind="value: printer_movementSpeedX" id="settings-movementSpeedX">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="control-group">
|
||||||
|
<label class="control-label" for="settings-movementSpeedY">Movement Speed Y Axis</label>
|
||||||
|
<div class="controls">
|
||||||
|
<input type="text" class="input-mini text-right" data-bind="value: printer_movementSpeedY" id="settings-movementSpeedY">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="control-group">
|
||||||
|
<label class="control-label" for="settings-movementSpeedZ">Movement Speed Z Axis</label>
|
||||||
|
<div class="controls">
|
||||||
|
<input type="text" class="input-mini text-right" data-bind="value: printer_movementSpeedZ" id="settings-movementSpeedZ">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="control-group">
|
||||||
|
<label class="control-label" for="settings-movementSpeedE">Movement Speed Extruder</label>
|
||||||
|
<div class="controls">
|
||||||
|
<input type="text" class="input-mini text-right" data-bind="value: printer_movementSpeedE" id="settings-movementSpeedE">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
<div class="tab-pane" id="settings_webcam">
|
||||||
|
<form class="form-horizontal">
|
||||||
|
<div class="control-group">
|
||||||
|
<label class="control-label" for="settings-webcamStreamUrl">Stream URL</label>
|
||||||
|
<div class="controls">
|
||||||
|
<input type="text" class="input-block-level" data-bind="value: webcam_streamUrl" id="settings-webcamStreamUrl">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="control-group">
|
||||||
|
<label class="control-label" for="settings-webcamStreamUrl">Snapshot URL</label>
|
||||||
|
<div class="controls">
|
||||||
|
<input type="text" class="input-block-level" data-bind="value: webcam_snapshotUrl" id="settings-webcamSnapshotUrl">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="control-group">
|
||||||
|
<label class="control-label" for="settings-webcamStreamUrl">Path to FFMPEG</label>
|
||||||
|
<div class="controls">
|
||||||
|
<input type="text" class="input-block-level" data-bind="value: webcam_ffmpegPath" id="settings-webcamFfmpegPath">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="control-group">
|
||||||
|
<label class="control-label" for="settings-webcamBitrate">Timelapse bitrate</label>
|
||||||
|
<div class="controls">
|
||||||
|
<input type="text" class="input-block-level" data-bind="value: webcam_bitrate" id="settings-webcamBitrate">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
<div class="tab-pane" id="settings_features">
|
||||||
|
<form class="form-horizontal">
|
||||||
|
<div class="control-group">
|
||||||
|
<div class="controls">
|
||||||
|
<label class="checkbox">
|
||||||
|
<input type="checkbox" data-bind="checked: feature_gcodeViewer" id="settings-featureGcodeViewer"> Enable GCode Visualizer
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="control-group">
|
||||||
|
<div class="controls">
|
||||||
|
<label class="checkbox">
|
||||||
|
<input type="checkbox" data-bind="checked: feature_waitForStart" id="settings-featureWaitForStart"> Wait for start on connect
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
<div class="tab-pane" id="settings_folder">
|
||||||
|
<form class="form-horizontal">
|
||||||
|
<div class="control-group">
|
||||||
|
<label class="control-label" for="settings-folderUploads">Upload Folder</label>
|
||||||
|
<div class="controls">
|
||||||
|
<input type="text" class="input-block-level" data-bind="value: folder_uploads" id="settings-folderUploads">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="control-group">
|
||||||
|
<label class="control-label" for="settings-folderTimelapse">Timelapse Folder</label>
|
||||||
|
<div class="controls">
|
||||||
|
<input type="text" class="input-block-level" data-bind="value: folder_timelapse" id="settings-folderTimelapse">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="control-group">
|
||||||
|
<label class="control-label" for="settings-folderTimelapseTemp">Timelapse Temp Folder</label>
|
||||||
|
<div class="controls">
|
||||||
|
<input type="text" class="input-block-level" data-bind="value: folder_timelapseTmp" id="settings-folderTimelapseTemp">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="control-group">
|
||||||
|
<label class="control-label" for="settings-folderLogs">Logs Folder</label>
|
||||||
|
<div class="controls">
|
||||||
|
<input type="text" class="input-block-level" data-bind="value: folder_logs" id="settings-folderLogs">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button class="btn" data-dismiss="modal" aria-hidden="true">Cancel</button>
|
||||||
|
<button class="btn btn-primary" data-bind="click: saveData">Save</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<script type="text/javascript" src="http://code.jquery.com/jquery-latest.js"></script>
|
<script type="text/javascript" src="http://code.jquery.com/jquery-latest.js"></script>
|
||||||
<script type="text/javascript" src="{{ url_for('static', filename='js/underscore-min.js') }}"></script>
|
<script type="text/javascript" src="{{ url_for('static', filename='js/underscore-min.js') }}"></script>
|
||||||
<script type="text/javascript" src="{{ url_for('static', filename='js/knockout-2.2.1.js') }}"></script>
|
<script type="text/javascript" src="{{ url_for('static', filename='js/knockout-2.2.1.js') }}"></script>
|
||||||
|
|
Loading…
Reference in New Issue