From 84ff4b641642307694d7d1c5d7428fdcabed9367 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Gina=20H=C3=A4u=C3=9Fge?=
Date: Sun, 11 Aug 2013 19:15:37 +0200
Subject: [PATCH] Refactored ui.js into single files, cleaned up js folder
TODO: minimizer for merging all files belonging to the actual app into one
---
octoprint/static/js/app/dataupdater.js | 73 +
octoprint/static/js/app/helpers.js | 275 ++
octoprint/static/js/app/main.js | 305 +++
.../static/js/app/viewmodels/appearance.js | 20 +
.../static/js/app/viewmodels/connection.js | 115 +
octoprint/static/js/app/viewmodels/control.js | 170 ++
.../static/js/app/viewmodels/firstrun.js | 75 +
octoprint/static/js/app/viewmodels/gcode.js | 75 +
.../static/js/app/viewmodels/gcodefiles.js | 202 ++
.../static/js/app/viewmodels/loginstate.js | 88 +
.../static/js/app/viewmodels/navigation.js | 33 +
.../static/js/app/viewmodels/printerstate.js | 132 +
.../static/js/app/viewmodels/settings.js | 182 ++
.../static/js/app/viewmodels/temperature.js | 175 ++
.../static/js/app/viewmodels/terminal.js | 83 +
.../static/js/app/viewmodels/timelapse.js | 120 +
octoprint/static/js/app/viewmodels/users.js | 190 ++
octoprint/static/js/{ => lib}/avltree.js | 0
.../js/{ => lib}/bootstrap/bootstrap-modal.js | 0
.../bootstrap/bootstrap-modalmanager.js | 0
.../js/{ => lib}/bootstrap/bootstrap.js | 0
.../js/{ => lib}/bootstrap/bootstrap.min.js | 0
.../js/{ => lib}/jquery/jquery.fileupload.js | 0
.../static/js/{ => lib}/jquery/jquery.flot.js | 0
.../jquery/jquery.iframe-transport.js | 0
.../static/js/{ => lib}/jquery/jquery.min.js | 0
.../js/{ => lib}/jquery/jquery.pnotify.min.js | 0
.../js/{ => lib}/jquery/jquery.ui.core.js | 0
.../js/{ => lib}/jquery/jquery.ui.mouse.js | 0
.../js/{ => lib}/jquery/jquery.ui.slider.js | 0
.../js/{ => lib}/jquery/jquery.ui.widget.js | 0
octoprint/static/js/{ => lib}/knockout.js | 0
.../static/js/{ => lib}/less-1.3.3.min.js | 0
.../static/js/{ => lib}/modernizr.custom.js | 0
.../js/{ => lib}/socket.io/WebSocketMain.swf | Bin
.../socket.io/WebSocketMainInsecure.swf | Bin
.../js/{ => lib}/socket.io/socket.io.js | 0
.../js/{ => lib}/socket.io/socket.io.min.js | 0
octoprint/static/js/{ => lib}/underscore.js | 0
octoprint/static/js/ui.js | 2329 -----------------
octoprint/templates/index.jinja2 | 60 +-
41 files changed, 2354 insertions(+), 2348 deletions(-)
create mode 100644 octoprint/static/js/app/dataupdater.js
create mode 100644 octoprint/static/js/app/helpers.js
create mode 100644 octoprint/static/js/app/main.js
create mode 100644 octoprint/static/js/app/viewmodels/appearance.js
create mode 100644 octoprint/static/js/app/viewmodels/connection.js
create mode 100644 octoprint/static/js/app/viewmodels/control.js
create mode 100644 octoprint/static/js/app/viewmodels/firstrun.js
create mode 100644 octoprint/static/js/app/viewmodels/gcode.js
create mode 100644 octoprint/static/js/app/viewmodels/gcodefiles.js
create mode 100644 octoprint/static/js/app/viewmodels/loginstate.js
create mode 100644 octoprint/static/js/app/viewmodels/navigation.js
create mode 100644 octoprint/static/js/app/viewmodels/printerstate.js
create mode 100644 octoprint/static/js/app/viewmodels/settings.js
create mode 100644 octoprint/static/js/app/viewmodels/temperature.js
create mode 100644 octoprint/static/js/app/viewmodels/terminal.js
create mode 100644 octoprint/static/js/app/viewmodels/timelapse.js
create mode 100644 octoprint/static/js/app/viewmodels/users.js
rename octoprint/static/js/{ => lib}/avltree.js (100%)
rename octoprint/static/js/{ => lib}/bootstrap/bootstrap-modal.js (100%)
rename octoprint/static/js/{ => lib}/bootstrap/bootstrap-modalmanager.js (100%)
rename octoprint/static/js/{ => lib}/bootstrap/bootstrap.js (100%)
rename octoprint/static/js/{ => lib}/bootstrap/bootstrap.min.js (100%)
rename octoprint/static/js/{ => lib}/jquery/jquery.fileupload.js (100%)
rename octoprint/static/js/{ => lib}/jquery/jquery.flot.js (100%)
rename octoprint/static/js/{ => lib}/jquery/jquery.iframe-transport.js (100%)
rename octoprint/static/js/{ => lib}/jquery/jquery.min.js (100%)
rename octoprint/static/js/{ => lib}/jquery/jquery.pnotify.min.js (100%)
rename octoprint/static/js/{ => lib}/jquery/jquery.ui.core.js (100%)
rename octoprint/static/js/{ => lib}/jquery/jquery.ui.mouse.js (100%)
rename octoprint/static/js/{ => lib}/jquery/jquery.ui.slider.js (100%)
rename octoprint/static/js/{ => lib}/jquery/jquery.ui.widget.js (100%)
rename octoprint/static/js/{ => lib}/knockout.js (100%)
rename octoprint/static/js/{ => lib}/less-1.3.3.min.js (100%)
rename octoprint/static/js/{ => lib}/modernizr.custom.js (100%)
rename octoprint/static/js/{ => lib}/socket.io/WebSocketMain.swf (100%)
rename octoprint/static/js/{ => lib}/socket.io/WebSocketMainInsecure.swf (100%)
rename octoprint/static/js/{ => lib}/socket.io/socket.io.js (100%)
rename octoprint/static/js/{ => lib}/socket.io/socket.io.min.js (100%)
rename octoprint/static/js/{ => lib}/underscore.js (100%)
delete mode 100644 octoprint/static/js/ui.js
diff --git a/octoprint/static/js/app/dataupdater.js b/octoprint/static/js/app/dataupdater.js
new file mode 100644
index 00000000..a81a912b
--- /dev/null
+++ b/octoprint/static/js/app/dataupdater.js
@@ -0,0 +1,73 @@
+function DataUpdater(loginStateViewModel, connectionViewModel, printerStateViewModel, temperatureViewModel, controlViewModel, terminalViewModel, gcodeFilesViewModel, timelapseViewModel, gcodeViewModel) {
+ var self = this;
+
+ self.loginStateViewModel = loginStateViewModel;
+ self.connectionViewModel = connectionViewModel;
+ self.printerStateViewModel = printerStateViewModel;
+ self.temperatureViewModel = temperatureViewModel;
+ self.controlViewModel = controlViewModel;
+ self.terminalViewModel = terminalViewModel;
+ self.gcodeFilesViewModel = gcodeFilesViewModel;
+ self.timelapseViewModel = timelapseViewModel;
+ self.gcodeViewModel = gcodeViewModel;
+
+ self._socket = io.connect();
+ self._socket.on("connect", function() {
+ if ($("#offline_overlay").is(":visible")) {
+ $("#offline_overlay").hide();
+ self.timelapseViewModel.requestData();
+ $("#webcam_image").attr("src", CONFIG_WEBCAM_STREAM + "?" + new Date().getTime());
+ self.loginStateViewModel.requestData();
+ self.gcodeFilesViewModel.requestData();
+ }
+ });
+ self._socket.on("disconnect", function() {
+ $("#offline_overlay_message").html(
+ "The server appears to be offline, at least I'm not getting any response from it. I'll try to reconnect " +
+ "automatically over the next couple of minutes, however you are welcome to try a manual reconnect " +
+ "anytime using the button below."
+ );
+ if (!$("#offline_overlay").is(":visible"))
+ $("#offline_overlay").show();
+ });
+ self._socket.on("reconnect_failed", function() {
+ $("#offline_overlay_message").html(
+ "The server appears to be offline, at least I'm not getting any response from it. I could not reconnect automatically, " +
+ "but you may try a manual reconnect using the button below."
+ );
+ });
+ self._socket.on("history", function(data) {
+ self.connectionViewModel.fromHistoryData(data);
+ self.printerStateViewModel.fromHistoryData(data);
+ self.temperatureViewModel.fromHistoryData(data);
+ self.controlViewModel.fromHistoryData(data);
+ self.terminalViewModel.fromHistoryData(data);
+ self.timelapseViewModel.fromHistoryData(data);
+ self.gcodeViewModel.fromHistoryData(data);
+ self.gcodeFilesViewModel.fromCurrentData(data);
+ });
+ self._socket.on("current", function(data) {
+ self.connectionViewModel.fromCurrentData(data);
+ self.printerStateViewModel.fromCurrentData(data);
+ self.temperatureViewModel.fromCurrentData(data);
+ self.controlViewModel.fromCurrentData(data);
+ self.terminalViewModel.fromCurrentData(data);
+ self.timelapseViewModel.fromCurrentData(data);
+ self.gcodeViewModel.fromCurrentData(data);
+ self.gcodeFilesViewModel.fromCurrentData(data);
+ });
+ self._socket.on("updateTrigger", function(type) {
+ if (type == "gcodeFiles") {
+ gcodeFilesViewModel.requestData();
+ } else if (type == "timelapseFiles") {
+ timelapseViewModel.requestData();
+ }
+ });
+ self._socket.on("feedbackCommandOutput", function(data) {
+ self.controlViewModel.fromFeedbackCommandData(data);
+ });
+
+ self.reconnect = function() {
+ self._socket.socket.connect();
+ }
+}
diff --git a/octoprint/static/js/app/helpers.js b/octoprint/static/js/app/helpers.js
new file mode 100644
index 00000000..16a7ee3b
--- /dev/null
+++ b/octoprint/static/js/app/helpers.js
@@ -0,0 +1,275 @@
+function ItemListHelper(listType, supportedSorting, supportedFilters, defaultSorting, defaultFilters, exclusiveFilters, filesPerPage) {
+ var self = this;
+
+ self.listType = listType;
+ self.supportedSorting = supportedSorting;
+ self.supportedFilters = supportedFilters;
+ self.defaultSorting = defaultSorting;
+ self.defaultFilters = defaultFilters;
+ self.exclusiveFilters = exclusiveFilters;
+
+ self.allItems = [];
+
+ self.items = ko.observableArray([]);
+ self.pageSize = ko.observable(filesPerPage);
+ self.currentPage = ko.observable(0);
+ self.currentSorting = ko.observable(self.defaultSorting);
+ self.currentFilters = ko.observableArray(self.defaultFilters);
+ self.selectedItem = ko.observable(undefined);
+
+ //~~ item handling
+
+ self.updateItems = function(items) {
+ self.allItems = items;
+ self._updateItems();
+ }
+
+ self.selectItem = function(matcher) {
+ var itemList = self.items();
+ for (var i = 0; i < itemList.length; i++) {
+ if (matcher(itemList[i])) {
+ self.selectedItem(itemList[i]);
+ break;
+ }
+ }
+ }
+
+ self.selectNone = function() {
+ self.selectedItem(undefined);
+ }
+
+ self.isSelected = function(data) {
+ return self.selectedItem() == data;
+ }
+
+ self.isSelectedByMatcher = function(matcher) {
+ return matcher(self.selectedItem());
+ }
+
+ //~~ pagination
+
+ self.paginatedItems = ko.dependentObservable(function() {
+ if (self.items() == undefined) {
+ return [];
+ } else {
+ var from = Math.max(self.currentPage() * self.pageSize(), 0);
+ var to = Math.min(from + self.pageSize(), self.items().length);
+ return self.items().slice(from, to);
+ }
+ })
+ self.lastPage = ko.dependentObservable(function() {
+ return Math.ceil(self.items().length / self.pageSize()) - 1;
+ })
+ self.pages = ko.dependentObservable(function() {
+ var pages = [];
+ if (self.lastPage() < 7) {
+ for (var i = 0; i < self.lastPage() + 1; i++) {
+ pages.push({ number: i, text: i+1 });
+ }
+ } else {
+ pages.push({ number: 0, text: 1 });
+ if (self.currentPage() < 5) {
+ for (var i = 1; i < 5; i++) {
+ pages.push({ number: i, text: i+1 });
+ }
+ pages.push({ number: -1, text: "…"});
+ } else if (self.currentPage() > self.lastPage() - 5) {
+ pages.push({ number: -1, text: "…"});
+ for (var i = self.lastPage() - 4; i < self.lastPage(); i++) {
+ pages.push({ number: i, text: i+1 });
+ }
+ } else {
+ pages.push({ number: -1, text: "…"});
+ for (var i = self.currentPage() - 1; i <= self.currentPage() + 1; i++) {
+ pages.push({ number: i, text: i+1 });
+ }
+ pages.push({ number: -1, text: "…"});
+ }
+ pages.push({ number: self.lastPage(), text: self.lastPage() + 1})
+ }
+ return pages;
+ })
+
+ self.switchToItem = function(matcher) {
+ var pos = -1;
+ var itemList = self.items();
+ for (var i = 0; i < itemList.length; i++) {
+ if (matcher(itemList[i])) {
+ pos = i;
+ break;
+ }
+ }
+
+ if (pos > -1) {
+ var page = Math.floor(pos / self.pageSize());
+ self.changePage(page);
+ }
+ }
+
+ self.changePage = function(newPage) {
+ if (newPage < 0 || newPage > self.lastPage())
+ return;
+ self.currentPage(newPage);
+ }
+ self.prevPage = function() {
+ if (self.currentPage() > 0) {
+ self.currentPage(self.currentPage() - 1);
+ }
+ }
+ self.nextPage = function() {
+ if (self.currentPage() < self.lastPage()) {
+ self.currentPage(self.currentPage() + 1);
+ }
+ }
+
+ self.getItem = function(matcher) {
+ var itemList = self.items();
+ for (var i = 0; i < itemList.length; i++) {
+ if (matcher(itemList[i])) {
+ return itemList[i];
+ }
+ }
+
+ return undefined;
+ }
+
+ //~~ sorting
+
+ self.changeSorting = function(sorting) {
+ if (!_.contains(_.keys(self.supportedSorting), sorting))
+ return;
+
+ self.currentSorting(sorting);
+ self._saveCurrentSortingToLocalStorage();
+
+ self.changePage(0);
+ self._updateItems();
+ }
+
+ //~~ filtering
+
+ self.toggleFilter = function(filter) {
+ if (!_.contains(_.keys(self.supportedFilters), filter))
+ return;
+
+ if (_.contains(self.currentFilters(), filter)) {
+ self.removeFilter(filter);
+ } else {
+ self.addFilter(filter);
+ }
+ }
+
+ self.addFilter = function(filter) {
+ if (!_.contains(_.keys(self.supportedFilters), filter))
+ return;
+
+ for (var i = 0; i < self.exclusiveFilters.length; i++) {
+ if (_.contains(self.exclusiveFilters[i], filter)) {
+ for (var j = 0; j < self.exclusiveFilters[i].length; j++) {
+ if (self.exclusiveFilters[i][j] == filter)
+ continue;
+ self.removeFilter(self.exclusiveFilters[i][j]);
+ }
+ }
+ }
+
+ var filters = self.currentFilters();
+ filters.push(filter);
+ self.currentFilters(filters);
+ self._saveCurrentFiltersToLocalStorage();
+
+ self.changePage(0);
+ self._updateItems();
+ }
+
+ self.removeFilter = function(filter) {
+ if (!_.contains(_.keys(self.supportedFilters), filter))
+ return;
+
+ var filters = self.currentFilters();
+ filters.pop(filter);
+ self.currentFilters(filters);
+ self._saveCurrentFiltersToLocalStorage();
+
+ self.changePage(0);
+ self._updateItems();
+ }
+
+ //~~ update for sorted and filtered view
+
+ self._updateItems = function() {
+ // determine comparator
+ var comparator = undefined;
+ var currentSorting = self.currentSorting();
+ if (typeof currentSorting !== undefined && typeof self.supportedSorting[currentSorting] !== undefined) {
+ comparator = self.supportedSorting[currentSorting];
+ }
+
+ // work on all items
+ var result = self.allItems;
+
+ // filter if necessary
+ var filters = self.currentFilters();
+ _.each(filters, function(filter) {
+ if (typeof filter !== undefined && typeof supportedFilters[filter] !== undefined)
+ result = _.filter(result, supportedFilters[filter]);
+ });
+
+ // sort if necessary
+ if (typeof comparator !== undefined)
+ result.sort(comparator);
+
+ // set result list
+ self.items(result);
+ }
+
+ //~~ local storage
+
+ self._saveCurrentSortingToLocalStorage = function() {
+ if ( self._initializeLocalStorage() ) {
+ var currentSorting = self.currentSorting();
+ if (currentSorting !== undefined)
+ localStorage[self.listType + "." + "currentSorting"] = currentSorting;
+ else
+ localStorage[self.listType + "." + "currentSorting"] = undefined;
+ }
+ }
+
+ self._loadCurrentSortingFromLocalStorage = function() {
+ if ( self._initializeLocalStorage() ) {
+ if (_.contains(_.keys(supportedSorting), localStorage[self.listType + "." + "currentSorting"]))
+ self.currentSorting(localStorage[self.listType + "." + "currentSorting"]);
+ else
+ self.currentSorting(defaultSorting);
+ }
+ }
+
+ self._saveCurrentFiltersToLocalStorage = function() {
+ if ( self._initializeLocalStorage() ) {
+ var filters = _.intersection(_.keys(self.supportedFilters), self.currentFilters());
+ localStorage[self.listType + "." + "currentFilters"] = JSON.stringify(filters);
+ }
+ }
+
+ self._loadCurrentFiltersFromLocalStorage = function() {
+ if ( self._initializeLocalStorage() ) {
+ self.currentFilters(_.intersection(_.keys(self.supportedFilters), JSON.parse(localStorage[self.listType + "." + "currentFilters"])));
+ }
+ }
+
+ self._initializeLocalStorage = function() {
+ if (!Modernizr.localstorage)
+ return false;
+
+ if (localStorage[self.listType + "." + "currentSorting"] !== undefined && localStorage[self.listType + "." + "currentFilters"] !== undefined && JSON.parse(localStorage[self.listType + "." + "currentFilters"]) instanceof Array)
+ return true;
+
+ localStorage[self.listType + "." + "currentSorting"] = self.defaultSorting;
+ localStorage[self.listType + "." + "currentFilters"] = JSON.stringify(self.defaultFilters);
+
+ return true;
+ }
+
+ self._loadCurrentFiltersFromLocalStorage();
+ self._loadCurrentSortingFromLocalStorage();
+}
diff --git a/octoprint/static/js/app/main.js b/octoprint/static/js/app/main.js
new file mode 100644
index 00000000..a8f4d0e5
--- /dev/null
+++ b/octoprint/static/js/app/main.js
@@ -0,0 +1,305 @@
+$(function() {
+
+ //~~ Initialize view models
+ var loginStateViewModel = new LoginStateViewModel();
+ var usersViewModel = new UsersViewModel(loginStateViewModel);
+ var settingsViewModel = new SettingsViewModel(loginStateViewModel, usersViewModel);
+ var connectionViewModel = new ConnectionViewModel(loginStateViewModel, settingsViewModel);
+ var printerStateViewModel = new PrinterStateViewModel(loginStateViewModel);
+ var appearanceViewModel = new AppearanceViewModel(settingsViewModel);
+ var temperatureViewModel = new TemperatureViewModel(loginStateViewModel, settingsViewModel);
+ var controlViewModel = new ControlViewModel(loginStateViewModel, settingsViewModel);
+ var terminalViewModel = new TerminalViewModel(loginStateViewModel);
+ var gcodeFilesViewModel = new GcodeFilesViewModel(printerStateViewModel, loginStateViewModel);
+ var timelapseViewModel = new TimelapseViewModel(loginStateViewModel);
+ var gcodeViewModel = new GcodeViewModel(loginStateViewModel);
+ var navigationViewModel = new NavigationViewModel(loginStateViewModel, appearanceViewModel, settingsViewModel, usersViewModel);
+
+ var dataUpdater = new DataUpdater(
+ loginStateViewModel,
+ connectionViewModel,
+ printerStateViewModel,
+ temperatureViewModel,
+ controlViewModel,
+ terminalViewModel,
+ gcodeFilesViewModel,
+ timelapseViewModel,
+ gcodeViewModel
+ );
+
+ // work around a stupid iOS6 bug where ajax requests get cached and only work once, as described at
+ // http://stackoverflow.com/questions/12506897/is-safari-on-ios-6-caching-ajax-results
+ $.ajaxSetup({
+ type: 'POST',
+ headers: { "cache-control": "no-cache" }
+ });
+
+ //~~ Show settings - to ensure centered
+ $('#navbar_show_settings').click(function() {
+ $('#settings_dialog').modal()
+ .css({
+ width: 'auto',
+ 'margin-left': function() { return -($(this).width() /2); }
+ });
+ return false;
+ })
+
+ //~~ Temperature control (should really move to knockout click binding)
+
+ $("#temp_newTemp_set").click(function() {
+ var newTemp = $("#temp_newTemp").val();
+ $.ajax({
+ url: AJAX_BASEURL + "control/temperature",
+ type: "POST",
+ dataType: "json",
+ data: { temp: newTemp },
+ success: function() {$("#temp_newTemp").val("")}
+ })
+ })
+ $("#temp_newBedTemp_set").click(function() {
+ var newBedTemp = $("#temp_newBedTemp").val();
+ $.ajax({
+ url: AJAX_BASEURL + "control/temperature",
+ type: "POST",
+ dataType: "json",
+ data: { bedTemp: newBedTemp },
+ success: function() {$("#temp_newBedTemp").val("")}
+ })
+ })
+ $('#tabs a[data-toggle="tab"]').on('shown', function (e) {
+ temperatureViewModel.updatePlot();
+ terminalViewModel.updateOutput();
+ });
+
+ //~~ Terminal
+
+ $("#terminal-send").click(function () {
+ var command = $("#terminal-command").val();
+
+ /*
+ var re = /^([gm][0-9]+)(\s.*)?/;
+ var commandMatch = command.match(re);
+ if (commandMatch != null) {
+ command = commandMatch[1].toUpperCase() + ((commandMatch[2] !== undefined) ? commandMatch[2] : "");
+ }
+ */
+
+ if (command) {
+ $.ajax({
+ url: AJAX_BASEURL + "control/command",
+ type: "POST",
+ dataType: "json",
+ contentType: "application/json; charset=UTF-8",
+ data: JSON.stringify({"command": command})
+ })
+ $("#terminal-command").val('')
+ }
+
+ })
+
+ $("#terminal-command").keyup(function (event) {
+ if (event.keyCode == 13) {
+ $("#terminal-send").click()
+ }
+ })
+
+ //~~ Gcode upload
+
+ function gcode_upload_done(e, data) {
+ gcodeFilesViewModel.fromResponse(data.result);
+ $("#gcode_upload_progress .bar").css("width", "0%");
+ $("#gcode_upload_progress").removeClass("progress-striped").removeClass("active");
+ $("#gcode_upload_progress .bar").text("");
+ }
+
+ function gcode_upload_progress(e, data) {
+ var progress = parseInt(data.loaded / data.total * 100, 10);
+ $("#gcode_upload_progress .bar").css("width", progress + "%");
+ $("#gcode_upload_progress .bar").text("Uploading ...");
+ if (progress >= 100) {
+ $("#gcode_upload_progress").addClass("progress-striped").addClass("active");
+ $("#gcode_upload_progress .bar").text("Saving ...");
+ }
+ }
+
+ var localTarget;
+ if (CONFIG_SD_SUPPORT) {
+ localTarget = $("#drop_locally");
+ } else {
+ localTarget = $("#drop");
+ }
+
+ $("#gcode_upload").fileupload({
+ dataType: "json",
+ dropZone: localTarget,
+ formData: {target: "local"},
+ done: gcode_upload_done,
+ progressall: gcode_upload_progress
+ });
+
+ if (CONFIG_SD_SUPPORT) {
+ $("#gcode_upload_sd").fileupload({
+ dataType: "json",
+ dropZone: $("#drop_sd"),
+ formData: {target: "sd"},
+ done: gcode_upload_done,
+ progressall: gcode_upload_progress
+ });
+ }
+
+ $(document).bind("dragover", function (e) {
+ var dropOverlay = $("#drop_overlay");
+ var dropZone = $("#drop");
+ var dropZoneLocal = $("#drop_locally");
+ var dropZoneSd = $("#drop_sd");
+ var dropZoneBackground = $("#drop_background");
+ var dropZoneLocalBackground = $("#drop_locally_background");
+ var dropZoneSdBackground = $("#drop_sd_background");
+ var timeout = window.dropZoneTimeout;
+
+ if (!timeout) {
+ dropOverlay.addClass('in');
+ } else {
+ clearTimeout(timeout);
+ }
+
+ var foundLocal = false;
+ var foundSd = false;
+ var found = false
+ var node = e.target;
+ do {
+ if (dropZoneLocal && node === dropZoneLocal[0]) {
+ foundLocal = true;
+ break;
+ } else if (dropZoneSd && node === dropZoneSd[0]) {
+ foundSd = true;
+ break;
+ } else if (dropZone && node === dropZone[0]) {
+ found = true;
+ break;
+ }
+ node = node.parentNode;
+ } while (node != null);
+
+ if (foundLocal) {
+ dropZoneLocalBackground.addClass("hover");
+ dropZoneSdBackground.removeClass("hover");
+ } else if (foundSd) {
+ dropZoneSdBackground.addClass("hover");
+ dropZoneLocalBackground.removeClass("hover");
+ } else if (found) {
+ dropZoneBackground.addClass("hover");
+ } else {
+ if (dropZoneLocalBackground) dropZoneLocalBackground.removeClass("hover");
+ if (dropZoneSdBackground) dropZoneSdBackground.removeClass("hover");
+ if (dropZoneBackground) dropZoneBackground.removeClass("hover");
+ }
+
+ window.dropZoneTimeout = setTimeout(function () {
+ window.dropZoneTimeout = null;
+ dropOverlay.removeClass("in");
+ if (dropZoneLocal) dropZoneLocalBackground.removeClass("hover");
+ if (dropZoneSd) dropZoneSdBackground.removeClass("hover");
+ if (dropZone) dropZoneBackground.removeClass("hover");
+ }, 100);
+ });
+
+ //~~ Offline overlay
+ $("#offline_overlay_reconnect").click(function() {dataUpdater.reconnect()});
+
+ //~~ knockout.js bindings
+
+ ko.bindingHandlers.popover = {
+ init: function(element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {
+ var val = ko.utils.unwrapObservable(valueAccessor());
+
+ var options = {
+ title: val.title,
+ animation: val.animation,
+ placement: val.placement,
+ trigger: val.trigger,
+ delay: val.delay,
+ content: val.content,
+ html: val.html
+ };
+ $(element).popover(options);
+ }
+ }
+
+ ko.applyBindings(connectionViewModel, document.getElementById("connection_accordion"));
+ ko.applyBindings(printerStateViewModel, document.getElementById("state_accordion"));
+ ko.applyBindings(gcodeFilesViewModel, document.getElementById("files_accordion"));
+ ko.applyBindings(temperatureViewModel, document.getElementById("temp"));
+ ko.applyBindings(controlViewModel, document.getElementById("control"));
+ ko.applyBindings(terminalViewModel, document.getElementById("term"));
+ ko.applyBindings(gcodeViewModel, document.getElementById("gcode"));
+ ko.applyBindings(settingsViewModel, document.getElementById("settings_dialog"));
+ ko.applyBindings(navigationViewModel, document.getElementById("navbar"));
+ ko.applyBindings(appearanceViewModel, document.getElementsByTagName("head")[0]);
+
+ var timelapseElement = document.getElementById("timelapse");
+ if (timelapseElement) {
+ ko.applyBindings(timelapseViewModel, timelapseElement);
+ }
+ var gCodeVisualizerElement = document.getElementById("gcode");
+ if (gCodeVisualizerElement) {
+ gcodeViewModel.initialize();
+ }
+
+ //~~ startup commands
+
+ loginStateViewModel.requestData();
+ connectionViewModel.requestData();
+ controlViewModel.requestData();
+ gcodeFilesViewModel.requestData();
+ timelapseViewModel.requestData();
+
+ loginStateViewModel.subscribe(function(change, data) {
+ if ("login" == change) {
+ $("#gcode_upload").fileupload("enable");
+
+ settingsViewModel.requestData();
+ if (data.admin) {
+ usersViewModel.requestData();
+ }
+ } else {
+ $("#gcode_upload").fileupload("disable");
+ }
+ });
+
+ //~~ UI stuff
+
+ $(".accordion-toggle[href='#files']").click(function() {
+ if ($("#files").hasClass("in")) {
+ $("#files").removeClass("overflow_visible");
+ } else {
+ setTimeout(function() {
+ $("#files").addClass("overflow_visible");
+ }, 1000);
+ }
+ })
+
+ $.pnotify.defaults.history = false;
+
+ $.fn.modal.defaults.maxHeight = function(){
+ // subtract the height of the modal header and footer
+ return $(window).height() - 165;
+ }
+
+ // Fix input element click problem on login dialog
+ $(".dropdown input, .dropdown label").click(function(e) {
+ e.stopPropagation();
+ });
+
+ $(document).bind("drop dragover", function (e) {
+ e.preventDefault();
+ });
+
+ if (CONFIG_FIRST_RUN) {
+ var firstRunViewModel = new FirstRunViewModel();
+ ko.applyBindings(firstRunViewModel, document.getElementById("first_run_dialog"));
+ firstRunViewModel.showDialog();
+ }
+ }
+);
+
diff --git a/octoprint/static/js/app/viewmodels/appearance.js b/octoprint/static/js/app/viewmodels/appearance.js
new file mode 100644
index 00000000..fc09c9ee
--- /dev/null
+++ b/octoprint/static/js/app/viewmodels/appearance.js
@@ -0,0 +1,20 @@
+function AppearanceViewModel(settingsViewModel) {
+ var self = this;
+
+ self.name = settingsViewModel.appearance_name;
+ self.color = settingsViewModel.appearance_color;
+
+ self.brand = ko.computed(function() {
+ if (self.name())
+ return "OctoPrint: " + self.name();
+ else
+ return "OctoPrint";
+ })
+
+ self.title = ko.computed(function() {
+ if (self.name())
+ return self.name() + " [OctoPrint]";
+ else
+ return "OctoPrint";
+ })
+}
diff --git a/octoprint/static/js/app/viewmodels/connection.js b/octoprint/static/js/app/viewmodels/connection.js
new file mode 100644
index 00000000..9baa9c4f
--- /dev/null
+++ b/octoprint/static/js/app/viewmodels/connection.js
@@ -0,0 +1,115 @@
+function ConnectionViewModel(loginStateViewModel, settingsViewModel) {
+ var self = this;
+
+ self.loginState = loginStateViewModel;
+ self.settings = settingsViewModel;
+
+ self.portOptions = ko.observableArray(undefined);
+ self.baudrateOptions = ko.observableArray(undefined);
+ self.selectedPort = ko.observable(undefined);
+ self.selectedBaudrate = ko.observable(undefined);
+ self.saveSettings = ko.observable(undefined);
+
+ 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.buttonText = ko.computed(function() {
+ if (self.isErrorOrClosed())
+ return "Connect";
+ else
+ return "Disconnect";
+ })
+
+ self.previousIsOperational = undefined;
+
+ self.requestData = function() {
+ $.ajax({
+ url: AJAX_BASEURL + "control/connection/options",
+ method: "GET",
+ dataType: "json",
+ success: function(response) {
+ self.fromResponse(response);
+ }
+ })
+ }
+
+ self.fromResponse = function(response) {
+ self.portOptions(response.ports);
+ self.baudrateOptions(response.baudrates);
+
+ if (!self.selectedPort() && response.ports && response.ports.indexOf(response.portPreference) >= 0)
+ self.selectedPort(response.portPreference);
+ if (!self.selectedBaudrate() && response.baudrates && response.baudrates.indexOf(response.baudratePreference) >= 0)
+ self.selectedBaudrate(response.baudratePreference);
+
+ self.saveSettings(false);
+ }
+
+ self.fromHistoryData = function(data) {
+ self._processStateData(data.state);
+ }
+
+ self.fromCurrentData = function(data) {
+ self._processStateData(data.state);
+ }
+
+ self._processStateData = function(data) {
+ self.previousIsOperational = self.isOperational();
+
+ 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);
+
+ var connectionTab = $("#connection");
+ if (self.previousIsOperational != self.isOperational()) {
+ if (self.isOperational() && connectionTab.hasClass("in")) {
+ // connection just got established, close connection tab for now
+ connectionTab.collapse("hide");
+ } else if (!connectionTab.hasClass("in")) {
+ // connection just dropped, make sure connection tab is open
+ connectionTab.collapse("show");
+ }
+ }
+ }
+
+ self.connect = function() {
+ if (self.isErrorOrClosed()) {
+ var data = {
+ "command": "connect",
+ "port": self.selectedPort(),
+ "baudrate": self.selectedBaudrate()
+ };
+
+ if (self.saveSettings())
+ data["save"] = true;
+
+ $.ajax({
+ url: AJAX_BASEURL + "control/connection",
+ type: "POST",
+ dataType: "json",
+ data: data
+ })
+
+ self.settings.serial_port(self.selectedPort())
+ self.settings.serial_baudrate(self.selectedBaudrate())
+ self.settings.saveData();
+ } else {
+ self.requestData();
+ $.ajax({
+ url: AJAX_BASEURL + "control/connection",
+ type: "POST",
+ dataType: "json",
+ data: {"command": "disconnect"}
+ })
+ }
+ }
+}
diff --git a/octoprint/static/js/app/viewmodels/control.js b/octoprint/static/js/app/viewmodels/control.js
new file mode 100644
index 00000000..bfab9e03
--- /dev/null
+++ b/octoprint/static/js/app/viewmodels/control.js
@@ -0,0 +1,170 @@
+function ControlViewModel(loginStateViewModel, settingsViewModel) {
+ var self = this;
+
+ self.loginState = loginStateViewModel;
+ self.settings = settingsViewModel;
+
+ 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.feedbackControlLookup = {};
+
+ 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.fromFeedbackCommandData = function(data) {
+ if (data.name in self.feedbackControlLookup) {
+ self.feedbackControlLookup[data.name](data.output);
+ }
+ }
+
+ self.requestData = function() {
+ $.ajax({
+ url: AJAX_BASEURL + "control/custom",
+ method: "GET",
+ dataType: "json",
+ success: function(response) {
+ self._fromResponse(response);
+ }
+ });
+ }
+
+ self._fromResponse = function(response) {
+ self.controls(self._processControls(response.controls));
+ }
+
+ 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.type == "parametric_command" || control.type == "parametric_commands") {
+ for (var i = 0; i < control.input.length; i++) {
+ control.input[i].value = control.input[i].default;
+ }
+ } else if (control.type == "feedback_command") {
+ control.output = ko.observable("");
+ self.feedbackControlLookup[control.name] = control.output;
+ } else if (control.type == "section") {
+ control.children = self._processControls(control.children);
+ }
+ return control;
+ }
+
+ self.sendJogCommand = function(axis, multiplier, distance) {
+ if (typeof distance === "undefined")
+ distance = $('#jog_distance button.active').data('distance');
+ $.ajax({
+ url: AJAX_BASEURL + "control/jog",
+ type: "POST",
+ dataType: "json",
+ data: axis + "=" + ( distance * multiplier )
+ })
+ }
+
+ self.sendHomeCommand = function(axis) {
+ $.ajax({
+ url: AJAX_BASEURL + "control/jog",
+ type: "POST",
+ dataType: "json",
+ data: "home" + axis
+ })
+ }
+
+ self.sendExtrudeCommand = function() {
+ self._sendECommand(1);
+ }
+
+ self.sendRetractCommand = function() {
+ self._sendECommand(-1);
+ }
+
+ self._sendECommand = function(dir) {
+ var length = self.extrusionAmount();
+ if (!length)
+ length = 5;
+ $.ajax({
+ url: AJAX_BASEURL + "control/jog",
+ type: "POST",
+ dataType: "json",
+ data: "extrude=" + (dir * length)
+ })
+ }
+
+ self.sendCustomCommand = function(command) {
+ if (!command)
+ return;
+
+ var data = undefined;
+ if (command.type == "command" || command.type == "parametric_command" || command.type == "feedback_command") {
+ // single command
+ data = {"command" : command.command};
+ } else if (command.type == "commands" || command.type == "parametric_commands") {
+ // multi command
+ data = {"commands": command.commands};
+ }
+
+ if (command.type == "parametric_command" || command.type == "parametric_commands") {
+ // parametric command(s)
+ data["parameters"] = {};
+ for (var i = 0; i < command.input.length; i++) {
+ data["parameters"][command.input[i].parameter] = command.input[i].value;
+ }
+ }
+
+ if (!data)
+ return;
+
+ $.ajax({
+ url: AJAX_BASEURL + "control/command",
+ type: "POST",
+ dataType: "json",
+ contentType: "application/json; charset=UTF-8",
+ data: JSON.stringify(data)
+ })
+ }
+
+ self.displayMode = function(customControl) {
+ switch (customControl.type) {
+ case "section":
+ return "customControls_sectionTemplate";
+ case "command":
+ case "commands":
+ return "customControls_commandTemplate";
+ case "parametric_command":
+ case "parametric_commands":
+ return "customControls_parametricCommandTemplate";
+ case "feedback_command":
+ return "customControls_feedbackCommandTemplate";
+ default:
+ return "customControls_emptyTemplate";
+ }
+ }
+
+}
diff --git a/octoprint/static/js/app/viewmodels/firstrun.js b/octoprint/static/js/app/viewmodels/firstrun.js
new file mode 100644
index 00000000..8697547e
--- /dev/null
+++ b/octoprint/static/js/app/viewmodels/firstrun.js
@@ -0,0 +1,75 @@
+function FirstRunViewModel() {
+ var self = this;
+
+ self.username = ko.observable(undefined);
+ self.password = ko.observable(undefined);
+ self.confirmedPassword = ko.observable(undefined);
+
+ self.passwordMismatch = ko.computed(function() {
+ return self.password() != self.confirmedPassword();
+ });
+
+ self.validUsername = ko.computed(function() {
+ return self.username() && self.username().trim() != "";
+ });
+
+ self.validPassword = ko.computed(function() {
+ return self.password() && self.password().trim() != "";
+ });
+
+ self.validData = ko.computed(function() {
+ return !self.passwordMismatch() && self.validUsername() && self.validPassword();
+ });
+
+ self.keepAccessControl = function() {
+ if (!self.validData()) return;
+
+ var data = {
+ "ac": true,
+ "user": self.username(),
+ "pass1": self.password(),
+ "pass2": self.confirmedPassword()
+ };
+ self._sendData(data);
+ };
+
+ self.disableAccessControl = function() {
+ $("#confirmation_dialog .confirmation_dialog_message").html("If you disable Access Control and your OctoPrint " +
+ "installation is accessible from the internet, your printer will be accessible by everyone - " +
+ "that also includes the bad guys!");
+ $("#confirmation_dialog .confirmation_dialog_acknowledge").click(function(e) {
+ e.preventDefault();
+ $("#confirmation_dialog").modal("hide");
+
+ var data = {
+ "ac": false
+ };
+ self._sendData(data, function() {
+ // if the user indeed disables access control, we'll need to reload the page for this to take effect
+ location.reload();
+ });
+ });
+ $("#confirmation_dialog").modal("show");
+ };
+
+ self._sendData = function(data, callback) {
+ $.ajax({
+ url: AJAX_BASEURL + "setup",
+ type: "POST",
+ dataType: "json",
+ data: data,
+ success: function() {
+ self.closeDialog();
+ if (callback) callback();
+ }
+ });
+ }
+
+ self.showDialog = function() {
+ $("#first_run_dialog").modal("show");
+ }
+
+ self.closeDialog = function() {
+ $("#first_run_dialog").modal("hide");
+ }
+}
diff --git a/octoprint/static/js/app/viewmodels/gcode.js b/octoprint/static/js/app/viewmodels/gcode.js
new file mode 100644
index 00000000..e24d14aa
--- /dev/null
+++ b/octoprint/static/js/app/viewmodels/gcode.js
@@ -0,0 +1,75 @@
+function GcodeViewModel(loginStateViewModel) {
+ var self = this;
+
+ self.loginState = loginStateViewModel;
+
+ self.loadedFilename = undefined;
+ self.loadedFileMTime = undefined;
+ self.status = 'idle';
+ self.enabled = false;
+
+ self.errorCount = 0;
+
+ self.initialize = function(){
+ self.enabled = true;
+ GCODE.ui.initHandlers();
+ }
+
+ self.loadFile = function(filename, mtime){
+ if (self.status == 'idle' && self.errorCount < 3) {
+ self.status = 'request';
+ $.ajax({
+ url: AJAX_BASEURL + "gcodefiles/" + filename,
+ data: { "mtime": mtime },
+ type: "GET",
+ success: function(response, rstatus) {
+ if(rstatus === 'success'){
+ self.showGCodeViewer(response, rstatus);
+ self.loadedFilename=filename;
+ self.loadedFileMTime=mtime;
+ self.status = 'idle';
+ }
+ },
+ error: function() {
+ self.status = 'idle';
+ self.errorCount++;
+ }
+ })
+ }
+ }
+
+ self.showGCodeViewer = function(response, rstatus) {
+ var par = {};
+ par.target = {};
+ par.target.result = response;
+ GCODE.gCodeReader.loadFile(par);
+ }
+
+ self.fromHistoryData = function(data) {
+ self._processData(data);
+ }
+
+ self.fromCurrentData = function(data) {
+ self._processData(data);
+ }
+
+ self._processData = function(data) {
+ if (!self.enabled) return;
+ if (!data.job.filename) return;
+
+ if(self.loadedFilename && self.loadedFilename == data.job.filename &&
+ self.loadedFileMTime == data.job.mtime) {
+ if (data.state.flags && (data.state.flags.printing || data.state.flags.paused)) {
+ var cmdIndex = GCODE.gCodeReader.getCmdIndexForPercentage(data.progress.progress * 100);
+ if(cmdIndex){
+ GCODE.renderer.render(cmdIndex.layer, 0, cmdIndex.cmd);
+ GCODE.ui.updateLayerInfo(cmdIndex.layer);
+ }
+ }
+ self.errorCount = 0
+ } else if (data.job.filename) {
+ self.loadFile(data.job.filename, data.job.mtime);
+ }
+ }
+
+}
diff --git a/octoprint/static/js/app/viewmodels/gcodefiles.js b/octoprint/static/js/app/viewmodels/gcodefiles.js
new file mode 100644
index 00000000..49661266
--- /dev/null
+++ b/octoprint/static/js/app/viewmodels/gcodefiles.js
@@ -0,0 +1,202 @@
+function GcodeFilesViewModel(printerStateViewModel, loginStateViewModel) {
+ var self = this;
+
+ self.printerState = printerStateViewModel;
+ self.loginState = loginStateViewModel;
+
+ 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.isSdReady = ko.observable(undefined);
+
+ self.freeSpace = ko.observable(undefined);
+
+ // initialize list helper
+ self.listHelper = new ItemListHelper(
+ "gcodeFiles",
+ {
+ "name": function(a, b) {
+ // sorts ascending
+ if (a["name"].toLocaleLowerCase() < b["name"].toLocaleLowerCase()) return -1;
+ if (a["name"].toLocaleLowerCase() > b["name"].toLocaleLowerCase()) return 1;
+ return 0;
+ },
+ "upload": function(a, b) {
+ // sorts descending
+ if (b["date"] === undefined || a["date"] > b["date"]) return -1;
+ if (a["date"] < b["date"]) return 1;
+ return 0;
+ },
+ "size": function(a, b) {
+ // sorts descending
+ if (b["bytes"] === undefined || a["bytes"] > b["bytes"]) return -1;
+ if (a["bytes"] < b["bytes"]) return 1;
+ return 0;
+ }
+ },
+ {
+ "printed": function(file) {
+ return !(file["prints"] && file["prints"]["success"] && file["prints"]["success"] > 0);
+ },
+ "sd": function(file) {
+ return file["origin"] && file["origin"] == "sd";
+ },
+ "local": function(file) {
+ return !(file["origin"] && file["origin"] == "sd");
+ }
+ },
+ "name",
+ [],
+ [["sd", "local"]],
+ CONFIG_GCODEFILESPERPAGE
+ );
+
+ self.isLoadActionPossible = ko.computed(function() {
+ return self.loginState.isUser() && !self.isPrinting() && !self.isPaused() && !self.isLoading();
+ });
+
+ self.isLoadAndPrintActionPossible = ko.computed(function() {
+ return self.loginState.isUser() && self.isOperational() && self.isLoadActionPossible();
+ });
+
+ self.printerState.filename.subscribe(function(newValue) {
+ self.highlightFilename(newValue);
+ });
+
+ self.highlightFilename = function(filename) {
+ if (filename == undefined) {
+ self.listHelper.selectNone();
+ } else {
+ self.listHelper.selectItem(function(item) {
+ return item.name == filename;
+ })
+ }
+ }
+
+ 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.isSdReady(data.flags.sdReady);
+ }
+
+ self.requestData = function() {
+ $.ajax({
+ url: AJAX_BASEURL + "gcodefiles",
+ method: "GET",
+ dataType: "json",
+ success: function(response) {
+ self.fromResponse(response);
+ }
+ });
+ }
+
+ self.fromResponse = function(response) {
+ self.listHelper.updateItems(response.files);
+
+ if (response.filename) {
+ // got a file to scroll to
+ self.listHelper.switchToItem(function(item) {return item.name == response.filename});
+ }
+
+ self.freeSpace(response.free);
+
+ self.highlightFilename(self.printerState.filename());
+ }
+
+ self.loadFile = function(filename, printAfterLoad) {
+ var file = self.listHelper.getItem(function(item) {return item.name == filename});
+ if (!file) return;
+
+ $.ajax({
+ url: AJAX_BASEURL + "gcodefiles/load",
+ type: "POST",
+ dataType: "json",
+ data: {filename: filename, print: printAfterLoad, target: file.origin}
+ })
+ }
+
+ self.removeFile = function(filename) {
+ var file = self.listHelper.getItem(function(item) {return item.name == filename});
+ if (!file) return;
+
+ $.ajax({
+ url: AJAX_BASEURL + "gcodefiles/delete",
+ type: "POST",
+ dataType: "json",
+ data: {filename: filename, target: file.origin},
+ success: self.fromResponse
+ })
+ }
+
+ self.initSdCard = function() {
+ self._sendSdCommand("init");
+ }
+
+ self.releaseSdCard = function() {
+ self._sendSdCommand("release");
+ }
+
+ self.refreshSdFiles = function() {
+ self._sendSdCommand("refresh");
+ }
+
+ self._sendSdCommand = function(command) {
+ $.ajax({
+ url: AJAX_BASEURL + "control/sd",
+ type: "POST",
+ dataType: "json",
+ data: {command: command}
+ });
+ }
+
+ self.getPopoverContent = function(data) {
+ var output = "Uploaded: " + data["date"] + "
";
+ if (data["gcodeAnalysis"]) {
+ output += "";
+ output += "Filament: " + data["gcodeAnalysis"]["filament"] + "
";
+ output += "Estimated Print Time: " + data["gcodeAnalysis"]["estimatedPrintTime"];
+ output += "
";
+ }
+ if (data["prints"] && data["prints"]["last"]) {
+ output += "";
+ output += "Last Print: " + data["prints"]["last"]["date"] + "";
+ output += "
";
+ }
+ return output;
+ }
+
+ self.getSuccessClass = function(data) {
+ if (!data["prints"] || !data["prints"]["last"]) {
+ return "";
+ }
+ return data["prints"]["last"]["success"] ? "text-success" : "text-error";
+ }
+
+ self.enableRemove = function(data) {
+ return self.loginState.isUser() && !(self.listHelper.isSelected(data) && (self.isPrinting() || self.isPaused()));
+ }
+
+ self.enableSelect = function(data, printAfterSelect) {
+ var isLoadActionPossible = self.loginState.isUser() && !(self.isPrinting() || self.isPaused() || self.isLoading());
+ return isLoadActionPossible && !self.listHelper.isSelected(data);
+ }
+
+}
+
diff --git a/octoprint/static/js/app/viewmodels/loginstate.js b/octoprint/static/js/app/viewmodels/loginstate.js
new file mode 100644
index 00000000..7859665e
--- /dev/null
+++ b/octoprint/static/js/app/viewmodels/loginstate.js
@@ -0,0 +1,88 @@
+function LoginStateViewModel() {
+ var self = this;
+
+ self.loggedIn = ko.observable(false);
+ self.username = ko.observable(undefined);
+ self.isAdmin = ko.observable(false);
+ self.isUser = ko.observable(false);
+
+ self.currentUser = ko.observable(undefined);
+
+ self.userMenuText = ko.computed(function() {
+ if (self.loggedIn()) {
+ return "\"" + self.username() + "\"";
+ } else {
+ return "Login";
+ }
+ })
+
+ self.subscribers = [];
+ self.subscribe = function(callback) {
+ self.subscribers.push(callback);
+ }
+
+ self.requestData = function() {
+ $.ajax({
+ url: AJAX_BASEURL + "login",
+ type: "POST",
+ data: {"passive": true},
+ success: self.fromResponse
+ })
+ }
+
+ self.fromResponse = function(response) {
+ if (response && response.name) {
+ self.loggedIn(true);
+ self.username(response.name);
+ self.isUser(response.user);
+ self.isAdmin(response.admin);
+
+ self.currentUser(response);
+
+ _.each(self.subscribers, function(callback) { callback("login", response); });
+ } else {
+ self.loggedIn(false);
+ self.username(undefined);
+ self.isUser(false);
+ self.isAdmin(false);
+
+ self.currentUser(undefined);
+
+ _.each(self.subscribers, function(callback) { callback("logout", {}); });
+ }
+ }
+
+ self.login = function() {
+ var username = $("#login_user").val();
+ var password = $("#login_pass").val();
+ var remember = $("#login_remember").is(":checked");
+
+ $("#login_user").val("");
+ $("#login_pass").val("");
+ $("#login_remember").prop("checked", false);
+
+ $.ajax({
+ url: AJAX_BASEURL + "login",
+ type: "POST",
+ data: {"user": username, "pass": password, "remember": remember},
+ success: function(response) {
+ $.pnotify({title: "Login successful", text: "You are now logged in as \"" + response.name + "\"", type: "success"});
+ self.fromResponse(response);
+ },
+ error: function(jqXHR, textStatus, errorThrown) {
+ $.pnotify({title: "Login failed", text: "User unknown or wrong password", type: "error"});
+ }
+ })
+ }
+
+ self.logout = function() {
+ $.ajax({
+ url: AJAX_BASEURL + "logout",
+ type: "POST",
+ success: function(response) {
+ $.pnotify({title: "Logout successful", text: "You are now logged out", type: "success"});
+ self.fromResponse(response);
+ }
+ })
+ }
+}
diff --git a/octoprint/static/js/app/viewmodels/navigation.js b/octoprint/static/js/app/viewmodels/navigation.js
new file mode 100644
index 00000000..c12ce0ed
--- /dev/null
+++ b/octoprint/static/js/app/viewmodels/navigation.js
@@ -0,0 +1,33 @@
+function NavigationViewModel(loginStateViewModel, appearanceViewModel, settingsViewModel, usersViewModel) {
+ var self = this;
+
+ self.loginState = loginStateViewModel;
+ self.appearance = appearanceViewModel;
+ self.systemActions = settingsViewModel.system_actions;
+ self.users = usersViewModel;
+
+ self.triggerAction = function(action) {
+ var callback = function() {
+ $.ajax({
+ url: AJAX_BASEURL + "system",
+ type: "POST",
+ dataType: "json",
+ data: "action=" + action.action,
+ success: function() {
+ $.pnotify({title: "Success", text: "The command \""+ action.name +"\" executed successfully", type: "success"});
+ },
+ error: function(jqXHR, textStatus, errorThrown) {
+ $.pnotify({title: "Error", text: "The command \"" + action.name + "\" could not be executed.
Reason:
" + jqXHR.responseText + "
", type: "error"});
+ }
+ })
+ }
+ if (action.confirm) {
+ $("#confirmation_dialog .confirmation_dialog_message").text(action.confirm);
+ $("#confirmation_dialog .confirmation_dialog_acknowledge").click(function(e) {e.preventDefault(); $("#confirmation_dialog").modal("hide"); callback(); });
+ $("#confirmation_dialog").modal("show");
+ } else {
+ callback();
+ }
+ }
+}
+
diff --git a/octoprint/static/js/app/viewmodels/printerstate.js b/octoprint/static/js/app/viewmodels/printerstate.js
new file mode 100644
index 00000000..91bc5059
--- /dev/null
+++ b/octoprint/static/js/app/viewmodels/printerstate.js
@@ -0,0 +1,132 @@
+function PrinterStateViewModel(loginStateViewModel) {
+ var self = this;
+
+ self.loginState = loginStateViewModel;
+
+ self.stateString = ko.observable(undefined);
+ 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.isSdReady = ko.observable(undefined);
+
+ self.filename = ko.observable(undefined);
+ self.progress = ko.observable(undefined);
+ self.filesize = ko.observable(undefined);
+ self.filepos = ko.observable(undefined);
+ self.printTime = ko.observable(undefined);
+ self.printTimeLeft = ko.observable(undefined);
+ self.sd = ko.observable(undefined);
+
+ self.filament = ko.observable(undefined);
+ self.estimatedPrintTime = ko.observable(undefined);
+
+ self.currentHeight = ko.observable(undefined);
+
+ self.byteString = ko.computed(function() {
+ if (!self.filesize())
+ return "-";
+ var filepos = self.filepos() ? self.filepos() : "-";
+ return filepos + " / " + self.filesize();
+ });
+ self.heightString = ko.computed(function() {
+ if (!self.currentHeight())
+ return "-";
+ return self.currentHeight();
+ })
+ self.progressString = ko.computed(function() {
+ if (!self.progress())
+ return 0;
+ return self.progress();
+ });
+ self.pauseString = ko.computed(function() {
+ if (self.isPaused())
+ return "Continue";
+ else
+ return "Pause";
+ });
+
+ self.fromCurrentData = function(data) {
+ self._fromData(data);
+ }
+
+ self.fromHistoryData = function(data) {
+ self._fromData(data);
+ }
+
+ self._fromData = function(data) {
+ self._processStateData(data.state)
+ self._processJobData(data.job);
+ self._processProgressData(data.progress);
+ self._processZData(data.currentZ);
+ }
+
+ self._processStateData = function(data) {
+ self.stateString(data.stateString);
+ 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.isSdReady(data.flags.sdReady);
+ }
+
+ self._processJobData = function(data) {
+ self.filename(data.filename);
+ self.filesize(data.filesize);
+ self.estimatedPrintTime(data.estimatedPrintTime);
+ self.filament(data.filament);
+ self.sd(data.sd);
+ }
+
+ self._processProgressData = function(data) {
+ if (data.progress) {
+ self.progress(Math.round(data.progress * 100));
+ } else {
+ self.progress(undefined);
+ }
+ self.filepos(data.filepos);
+ self.printTime(data.printTime);
+ self.printTimeLeft(data.printTimeLeft);
+ }
+
+ self._processZData = function(data) {
+ self.currentHeight(data);
+ }
+
+ self.print = function() {
+ var printAction = function() {
+ self._jobCommand("start");
+ }
+
+ if (self.isPaused()) {
+ $("#confirmation_dialog .confirmation_dialog_message").text("This will restart the print job from the beginning.");
+ $("#confirmation_dialog .confirmation_dialog_acknowledge").click(function(e) {e.preventDefault(); $("#confirmation_dialog").modal("hide"); printAction(); });
+ $("#confirmation_dialog").modal("show");
+ } else {
+ printAction();
+ }
+
+ }
+
+ self.pause = function() {
+ self._jobCommand("pause");
+ }
+
+ self.cancel = function() {
+ self._jobCommand("cancel");
+ }
+
+ self._jobCommand = function(command) {
+ $.ajax({
+ url: AJAX_BASEURL + "control/job",
+ type: "POST",
+ dataType: "json",
+ data: {command: command}
+ });
+ }
+}
diff --git a/octoprint/static/js/app/viewmodels/settings.js b/octoprint/static/js/app/viewmodels/settings.js
new file mode 100644
index 00000000..7c01f9d7
--- /dev/null
+++ b/octoprint/static/js/app/viewmodels/settings.js
@@ -0,0 +1,182 @@
+function SettingsViewModel(loginStateViewModel, usersViewModel) {
+ var self = this;
+
+ self.loginState = loginStateViewModel;
+ self.users = usersViewModel;
+
+ self.api_enabled = ko.observable(undefined);
+ self.api_key = ko.observable(undefined);
+
+ self.appearance_name = ko.observable(undefined);
+ self.appearance_color = ko.observable(undefined);
+
+ /* I did attempt to allow arbitrary gradients but cross browser support via knockout or jquery was going to be horrible */
+ self.appearance_available_colors = ko.observable(["default", "red", "orange", "yellow", "green", "blue", "violet", "black"]);
+
+ 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.webcam_watermark = ko.observable(undefined);
+ self.webcam_flipH = ko.observable(undefined);
+ self.webcam_flipV = ko.observable(undefined);
+
+ self.feature_gcodeViewer = ko.observable(undefined);
+ self.feature_waitForStart = ko.observable(undefined);
+ self.feature_alwaysSendChecksum = ko.observable(undefined);
+ self.feature_sdSupport = ko.observable(undefined);
+
+ self.serial_port = ko.observable();
+ self.serial_baudrate = ko.observable();
+ self.serial_portOptions = ko.observableArray([]);
+ self.serial_baudrateOptions = ko.observableArray([]);
+ self.serial_autoconnect = ko.observable(undefined);
+ self.serial_timeoutConnection = ko.observable(undefined);
+ self.serial_timeoutDetection = ko.observable(undefined);
+ self.serial_timeoutCommunication = ko.observable(undefined);
+ self.serial_log = 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.temperature_profiles = ko.observableArray(undefined);
+
+ self.system_actions = ko.observableArray([]);
+
+ self.addTemperatureProfile = function() {
+ self.temperature_profiles.push({name: "New", extruder:0, bed:0});
+ };
+
+ self.removeTemperatureProfile = function(profile) {
+ self.temperature_profiles.remove(profile);
+ };
+
+ self.requestData = function() {
+ $.ajax({
+ url: AJAX_BASEURL + "settings",
+ type: "GET",
+ dataType: "json",
+ success: self.fromResponse
+ });
+ }
+
+ self.fromResponse = function(response) {
+ self.api_enabled(response.api.enabled);
+ self.api_key(response.api.key);
+
+ self.appearance_name(response.appearance.name);
+ self.appearance_color(response.appearance.color);
+
+ 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.webcam_watermark(response.webcam.watermark);
+ self.webcam_flipH(response.webcam.flipH);
+ self.webcam_flipV(response.webcam.flipV);
+
+ self.feature_gcodeViewer(response.feature.gcodeViewer);
+ self.feature_waitForStart(response.feature.waitForStart);
+ self.feature_alwaysSendChecksum(response.feature.alwaysSendChecksum);
+ self.feature_sdSupport(response.feature.sdSupport);
+
+ self.serial_port(response.serial.port);
+ self.serial_baudrate(response.serial.baudrate);
+ self.serial_portOptions(response.serial.portOptions);
+ self.serial_baudrateOptions(response.serial.baudrateOptions);
+ self.serial_autoconnect(response.serial.autoconnect);
+ self.serial_timeoutConnection(response.serial.timeoutConnection);
+ self.serial_timeoutDetection(response.serial.timeoutDetection);
+ self.serial_timeoutCommunication(response.serial.timeoutCommunication);
+ self.serial_log(response.serial.log);
+
+ 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.temperature_profiles(response.temperature.profiles);
+
+ self.system_actions(response.system.actions);
+ }
+
+ self.saveData = function() {
+ var data = {
+ "api" : {
+ "enabled": self.api_enabled(),
+ "key": self.api_key()
+ },
+ "appearance" : {
+ "name": self.appearance_name(),
+ "color": self.appearance_color()
+ },
+ "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(),
+ "watermark": self.webcam_watermark(),
+ "flipH": self.webcam_flipH(),
+ "flipV": self.webcam_flipV()
+ },
+ "feature": {
+ "gcodeViewer": self.feature_gcodeViewer(),
+ "waitForStart": self.feature_waitForStart(),
+ "alwaysSendChecksum": self.feature_alwaysSendChecksum(),
+ "sdSupport": self.feature_sdSupport()
+ },
+ "serial": {
+ "port": self.serial_port(),
+ "baudrate": self.serial_baudrate(),
+ "autoconnect": self.serial_autoconnect(),
+ "timeoutConnection": self.serial_timeoutConnection(),
+ "timeoutDetection": self.serial_timeoutDetection(),
+ "timeoutCommunication": self.serial_timeoutCommunication(),
+ "log": self.serial_log()
+ },
+ "folder": {
+ "uploads": self.folder_uploads(),
+ "timelapse": self.folder_timelapse(),
+ "timelapseTmp": self.folder_timelapseTmp(),
+ "logs": self.folder_logs()
+ },
+ "temperature": {
+ "profiles": self.temperature_profiles()
+ },
+ "system": {
+ "actions": self.system_actions()
+ }
+ }
+
+ $.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");
+ }
+ })
+ }
+
+}
diff --git a/octoprint/static/js/app/viewmodels/temperature.js b/octoprint/static/js/app/viewmodels/temperature.js
new file mode 100644
index 00000000..30b1a47d
--- /dev/null
+++ b/octoprint/static/js/app/viewmodels/temperature.js
@@ -0,0 +1,175 @@
+function TemperatureViewModel(loginStateViewModel, settingsViewModel) {
+ var self = this;
+
+ self.loginState = loginStateViewModel;
+
+ self.temp = ko.observable(undefined);
+ self.bedTemp = ko.observable(undefined);
+ self.targetTemp = ko.observable(undefined);
+ self.bedTargetTemp = ko.observable(undefined);
+
+ 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.temperature_profiles = settingsViewModel.temperature_profiles;
+
+ self.setTempFromProfile = function(profile) {
+ if (!profile)
+ return;
+ self.setTemp(profile.extruder);
+ }
+
+ self.setTemp = function(temp) {
+ $.ajax({
+ url: AJAX_BASEURL + "control/temperature",
+ type: "POST",
+ dataType: "json",
+ data: { temp: temp },
+ success: function() {$("#temp_newTemp").val("")}
+ })
+ };
+
+ self.setBedTempFromProfile = function(profile) {
+ if (!profile)
+ return;
+ self.setBedTemp(profile.bed);
+ }
+
+ self.setBedTemp = function(bedTemp) {
+ $.ajax({
+ url: AJAX_BASEURL + "control/temperature",
+ type: "POST",
+ dataType: "json",
+ data: { bedTemp: bedTemp },
+ success: function() {$("#temp_newBedTemp").val("")}
+ })
+ };
+
+ self.tempString = ko.computed(function() {
+ if (!self.temp())
+ return "-";
+ return self.temp() + " °C";
+ });
+ self.bedTempString = ko.computed(function() {
+ if (!self.bedTemp())
+ return "-";
+ return self.bedTemp() + " °C";
+ });
+ self.targetTempString = ko.computed(function() {
+ if (!self.targetTemp())
+ return "-";
+ return self.targetTemp() + " °C";
+ });
+ self.bedTargetTempString = ko.computed(function() {
+ if (!self.bedTargetTemp())
+ return "-";
+ return self.bedTargetTemp() + " °C";
+ });
+
+ self.temperatures = [];
+ self.plotOptions = {
+ yaxis: {
+ min: 0,
+ max: 310,
+ ticks: 10
+ },
+ xaxis: {
+ mode: "time",
+ minTickSize: [2, "minute"],
+ tickFormatter: function(val, axis) {
+ if (val == undefined || val == 0)
+ return ""; // we don't want to display the minutes since the epoch if not connected yet ;)
+
+ // current time in milliseconds in UTC
+ var timestampUtc = Date.now();
+
+ // calculate difference in milliseconds
+ var diff = timestampUtc - val;
+
+ // convert to minutes
+ var diffInMins = Math.round(diff / (60 * 1000));
+ if (diffInMins == 0)
+ return "just now";
+ else
+ return "- " + diffInMins + " min";
+ }
+ },
+ legend: {
+ noColumns: 4
+ }
+ }
+
+ self.fromCurrentData = function(data) {
+ self._processStateData(data.state);
+ self._processTemperatureUpdateData(data.temperatures);
+ }
+
+ self.fromHistoryData = function(data) {
+ self._processStateData(data.state);
+ self._processTemperatureHistoryData(data.temperatureHistory);
+ }
+
+ 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._processTemperatureUpdateData = function(data) {
+ if (data.length == 0)
+ return;
+
+ self.temp(data[data.length - 1].temp);
+ self.bedTemp(data[data.length - 1].bedTemp);
+ self.targetTemp(data[data.length - 1].targetTemp);
+ self.bedTargetTemp(data[data.length - 1].targetBedTemp);
+
+ if (!self.temperatures)
+ self.temperatures = [];
+ if (!self.temperatures.actual)
+ self.temperatures.actual = [];
+ if (!self.temperatures.target)
+ self.temperatures.target = [];
+ if (!self.temperatures.actualBed)
+ self.temperatures.actualBed = [];
+ if (!self.temperatures.targetBed)
+ self.temperatures.targetBed = [];
+
+ for (var i = 0; i < data.length; i++) {
+ self.temperatures.actual.push([data[i].currentTime, data[i].temp])
+ self.temperatures.target.push([data[i].currentTime, data[i].targetTemp])
+ self.temperatures.actualBed.push([data[i].currentTime, data[i].bedTemp])
+ self.temperatures.targetBed.push([data[i].currentTime, data[i].targetBedTemp])
+ }
+ self.temperatures.actual = self.temperatures.actual.slice(-300);
+ self.temperatures.target = self.temperatures.target.slice(-300);
+ self.temperatures.actualBed = self.temperatures.actualBed.slice(-300);
+ self.temperatures.targetBed = self.temperatures.targetBed.slice(-300);
+
+ self.updatePlot();
+ }
+
+ self._processTemperatureHistoryData = function(data) {
+ self.temperatures = data;
+ self.updatePlot();
+ }
+
+ self.updatePlot = function() {
+ var data = [
+ {label: "Actual", color: "#FF4040", data: self.temperatures.actual},
+ {label: "Target", color: "#FFA0A0", data: self.temperatures.target},
+ {label: "Bed Actual", color: "#4040FF", data: self.temperatures.actualBed},
+ {label: "Bed Target", color: "#A0A0FF", data: self.temperatures.targetBed}
+ ]
+ $.plot($("#temperature-graph"), data, self.plotOptions);
+ }
+}
diff --git a/octoprint/static/js/app/viewmodels/terminal.js b/octoprint/static/js/app/viewmodels/terminal.js
new file mode 100644
index 00000000..14c8ef0a
--- /dev/null
+++ b/octoprint/static/js/app/viewmodels/terminal.js
@@ -0,0 +1,83 @@
+function TerminalViewModel(loginStateViewModel) {
+ var self = this;
+
+ self.loginState = loginStateViewModel;
+
+ self.log = [];
+
+ 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.autoscrollEnabled = ko.observable(true);
+ self.filterM105 = ko.observable(false);
+ self.filterM27 = ko.observable(false);
+
+ self.regexM105 = /(Send: M105)|(Recv: ok T:)/;
+ self.regexM27 = /(Send: M27)|(Recv: SD printing byte)/;
+
+ self.filterM105.subscribe(function(newValue) {
+ self.updateOutput();
+ });
+
+ self.filterM27.subscribe(function(newValue) {
+ self.updateOutput();
+ });
+
+ self.fromCurrentData = function(data) {
+ self._processStateData(data.state);
+ self._processCurrentLogData(data.logs);
+ }
+
+ self.fromHistoryData = function(data) {
+ self._processStateData(data.state);
+ self._processHistoryLogData(data.logHistory);
+ }
+
+ self._processCurrentLogData = function(data) {
+ if (!self.log)
+ self.log = []
+ self.log = self.log.concat(data)
+ self.log = self.log.slice(-300)
+ self.updateOutput();
+ }
+
+ self._processHistoryLogData = function(data) {
+ self.log = data;
+ self.updateOutput();
+ }
+
+ 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.updateOutput = function() {
+ if (!self.log)
+ return;
+
+ var output = "";
+ for (var i = 0; i < self.log.length; i++) {
+ if (self.filterM105() && self.log[i].match(self.regexM105)) continue;
+ if (self.filterM27() && self.log[i].match(self.regexM27)) continue;
+
+ output += self.log[i] + "\n";
+ }
+
+ var container = $("#terminal-output");
+ container.text(output);
+
+ if (self.autoscrollEnabled()) {
+ container.scrollTop(container[0].scrollHeight - container.height())
+ }
+ }
+}
diff --git a/octoprint/static/js/app/viewmodels/timelapse.js b/octoprint/static/js/app/viewmodels/timelapse.js
new file mode 100644
index 00000000..6278fe2d
--- /dev/null
+++ b/octoprint/static/js/app/viewmodels/timelapse.js
@@ -0,0 +1,120 @@
+function TimelapseViewModel(loginStateViewModel) {
+ var self = this;
+
+ self.loginState = loginStateViewModel;
+
+ self.timelapseType = ko.observable(undefined);
+ self.timelapseTimedInterval = ko.observable(undefined);
+
+ 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.intervalInputEnabled = ko.computed(function() {
+ return ("timed" == self.timelapseType());
+ })
+
+ self.isOperational.subscribe(function(newValue) {
+ self.requestData();
+ })
+
+ // initialize list helper
+ self.listHelper = new ItemListHelper(
+ "timelapseFiles",
+ {
+ "name": function(a, b) {
+ // sorts ascending
+ if (a["name"].toLocaleLowerCase() < b["name"].toLocaleLowerCase()) return -1;
+ if (a["name"].toLocaleLowerCase() > b["name"].toLocaleLowerCase()) return 1;
+ return 0;
+ },
+ "creation": function(a, b) {
+ // sorts descending
+ if (a["date"] > b["date"]) return -1;
+ if (a["date"] < b["date"]) return 1;
+ return 0;
+ },
+ "size": function(a, b) {
+ // sorts descending
+ if (a["bytes"] > b["bytes"]) return -1;
+ if (a["bytes"] < b["bytes"]) return 1;
+ return 0;
+ }
+ },
+ {
+ },
+ "name",
+ [],
+ [],
+ CONFIG_TIMELAPSEFILESPERPAGE
+ )
+
+ self.requestData = function() {
+ $.ajax({
+ url: AJAX_BASEURL + "timelapse",
+ type: "GET",
+ dataType: "json",
+ success: self.fromResponse
+ });
+ }
+
+ self.fromResponse = function(response) {
+ self.timelapseType(response.type);
+ self.listHelper.updateItems(response.files);
+
+ if (response.type == "timed" && response.config && response.config.interval) {
+ self.timelapseTimedInterval(response.config.interval)
+ } else {
+ self.timelapseTimedInterval(undefined)
+ }
+ }
+
+ 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.removeFile = function(filename) {
+ $.ajax({
+ url: AJAX_BASEURL + "timelapse/" + filename,
+ type: "DELETE",
+ dataType: "json",
+ success: self.requestData
+ })
+ }
+
+ self.save = function() {
+ var data = {
+ "type": self.timelapseType()
+ }
+
+ if (self.timelapseType() == "timed") {
+ data["interval"] = self.timelapseTimedInterval();
+ }
+
+ $.ajax({
+ url: AJAX_BASEURL + "timelapse",
+ type: "POST",
+ dataType: "json",
+ data: data,
+ success: self.fromResponse
+ })
+ }
+}
diff --git a/octoprint/static/js/app/viewmodels/users.js b/octoprint/static/js/app/viewmodels/users.js
new file mode 100644
index 00000000..70c8534a
--- /dev/null
+++ b/octoprint/static/js/app/viewmodels/users.js
@@ -0,0 +1,190 @@
+function UsersViewModel(loginStateViewModel) {
+ var self = this;
+
+ self.loginState = loginStateViewModel;
+
+ // initialize list helper
+ self.listHelper = new ItemListHelper(
+ "users",
+ {
+ "name": function(a, b) {
+ // sorts ascending
+ if (a["name"].toLocaleLowerCase() < b["name"].toLocaleLowerCase()) return -1;
+ if (a["name"].toLocaleLowerCase() > b["name"].toLocaleLowerCase()) return 1;
+ return 0;
+ }
+ },
+ {},
+ "name",
+ [],
+ [],
+ CONFIG_USERSPERPAGE
+ );
+
+ self.emptyUser = {name: "", admin: false, active: false};
+
+ self.currentUser = ko.observable(self.emptyUser);
+
+ self.editorUsername = ko.observable(undefined);
+ self.editorPassword = ko.observable(undefined);
+ self.editorRepeatedPassword = ko.observable(undefined);
+ self.editorAdmin = ko.observable(undefined);
+ self.editorActive = ko.observable(undefined);
+
+ self.currentUser.subscribe(function(newValue) {
+ if (newValue === undefined) {
+ self.editorUsername(undefined);
+ self.editorAdmin(undefined);
+ self.editorActive(undefined);
+ } else {
+ self.editorUsername(newValue.name);
+ self.editorAdmin(newValue.admin);
+ self.editorActive(newValue.active);
+ }
+ self.editorPassword(undefined);
+ self.editorRepeatedPassword(undefined);
+ });
+
+ self.editorPasswordMismatch = ko.computed(function() {
+ return self.editorPassword() != self.editorRepeatedPassword();
+ });
+
+ self.requestData = function() {
+ if (!CONFIG_ACCESS_CONTROL) return;
+
+ $.ajax({
+ url: AJAX_BASEURL + "users",
+ type: "GET",
+ dataType: "json",
+ success: self.fromResponse
+ });
+ }
+
+ self.fromResponse = function(response) {
+ self.listHelper.updateItems(response.users);
+ }
+
+ self.showAddUserDialog = function() {
+ if (!CONFIG_ACCESS_CONTROL) return;
+
+ self.currentUser(undefined);
+ self.editorActive(true);
+ $("#settings-usersDialogAddUser").modal("show");
+ }
+
+ self.confirmAddUser = function() {
+ if (!CONFIG_ACCESS_CONTROL) return;
+
+ var user = {name: self.editorUsername(), password: self.editorPassword(), admin: self.editorAdmin(), active: self.editorActive()};
+ self.addUser(user, function() {
+ // close dialog
+ self.currentUser(undefined);
+ $("#settings-usersDialogAddUser").modal("hide");
+ });
+ }
+
+ self.showEditUserDialog = function(user) {
+ if (!CONFIG_ACCESS_CONTROL) return;
+
+ self.currentUser(user);
+ $("#settings-usersDialogEditUser").modal("show");
+ }
+
+ self.confirmEditUser = function() {
+ if (!CONFIG_ACCESS_CONTROL) return;
+
+ var user = self.currentUser();
+ user.active = self.editorActive();
+ user.admin = self.editorAdmin();
+
+ // make AJAX call
+ self.updateUser(user, function() {
+ // close dialog
+ self.currentUser(undefined);
+ $("#settings-usersDialogEditUser").modal("hide");
+ });
+ }
+
+ self.showChangePasswordDialog = function(user) {
+ if (!CONFIG_ACCESS_CONTROL) return;
+
+ self.currentUser(user);
+ $("#settings-usersDialogChangePassword").modal("show");
+ }
+
+ self.confirmChangePassword = function() {
+ if (!CONFIG_ACCESS_CONTROL) return;
+
+ self.updatePassword(self.currentUser().name, self.editorPassword(), function() {
+ // close dialog
+ self.currentUser(undefined);
+ $("#settings-usersDialogChangePassword").modal("hide");
+ });
+ }
+
+ //~~ AJAX calls
+
+ self.addUser = function(user, callback) {
+ if (!CONFIG_ACCESS_CONTROL) return;
+ if (user === undefined) return;
+
+ $.ajax({
+ url: AJAX_BASEURL + "users",
+ type: "POST",
+ contentType: "application/json; charset=UTF-8",
+ data: JSON.stringify(user),
+ success: function(response) {
+ self.fromResponse(response);
+ callback();
+ }
+ });
+ }
+
+ self.removeUser = function(user, callback) {
+ if (!CONFIG_ACCESS_CONTROL) return;
+ if (user === undefined) return;
+
+ if (user.name == loginStateViewModel.username()) {
+ // we do not allow to delete ourselves
+ $.pnotify({title: "Not possible", text: "You may not delete your own account.", type: "error"});
+ return;
+ }
+
+ $.ajax({
+ url: AJAX_BASEURL + "users/" + user.name,
+ type: "DELETE",
+ success: function(response) {
+ self.fromResponse(response);
+ callback();
+ }
+ });
+ }
+
+ self.updateUser = function(user, callback) {
+ if (!CONFIG_ACCESS_CONTROL) return;
+ if (user === undefined) return;
+
+ $.ajax({
+ url: AJAX_BASEURL + "users/" + user.name,
+ type: "PUT",
+ contentType: "application/json; charset=UTF-8",
+ data: JSON.stringify(user),
+ success: function(response) {
+ self.fromResponse(response);
+ callback();
+ }
+ });
+ }
+
+ self.updatePassword = function(username, password, callback) {
+ if (!CONFIG_ACCESS_CONTROL) return;
+
+ $.ajax({
+ url: AJAX_BASEURL + "users/" + username + "/password",
+ type: "PUT",
+ contentType: "application/json; charset=UTF-8",
+ data: JSON.stringify({password: password}),
+ success: callback
+ });
+ }
+}
diff --git a/octoprint/static/js/avltree.js b/octoprint/static/js/lib/avltree.js
similarity index 100%
rename from octoprint/static/js/avltree.js
rename to octoprint/static/js/lib/avltree.js
diff --git a/octoprint/static/js/bootstrap/bootstrap-modal.js b/octoprint/static/js/lib/bootstrap/bootstrap-modal.js
similarity index 100%
rename from octoprint/static/js/bootstrap/bootstrap-modal.js
rename to octoprint/static/js/lib/bootstrap/bootstrap-modal.js
diff --git a/octoprint/static/js/bootstrap/bootstrap-modalmanager.js b/octoprint/static/js/lib/bootstrap/bootstrap-modalmanager.js
similarity index 100%
rename from octoprint/static/js/bootstrap/bootstrap-modalmanager.js
rename to octoprint/static/js/lib/bootstrap/bootstrap-modalmanager.js
diff --git a/octoprint/static/js/bootstrap/bootstrap.js b/octoprint/static/js/lib/bootstrap/bootstrap.js
similarity index 100%
rename from octoprint/static/js/bootstrap/bootstrap.js
rename to octoprint/static/js/lib/bootstrap/bootstrap.js
diff --git a/octoprint/static/js/bootstrap/bootstrap.min.js b/octoprint/static/js/lib/bootstrap/bootstrap.min.js
similarity index 100%
rename from octoprint/static/js/bootstrap/bootstrap.min.js
rename to octoprint/static/js/lib/bootstrap/bootstrap.min.js
diff --git a/octoprint/static/js/jquery/jquery.fileupload.js b/octoprint/static/js/lib/jquery/jquery.fileupload.js
similarity index 100%
rename from octoprint/static/js/jquery/jquery.fileupload.js
rename to octoprint/static/js/lib/jquery/jquery.fileupload.js
diff --git a/octoprint/static/js/jquery/jquery.flot.js b/octoprint/static/js/lib/jquery/jquery.flot.js
similarity index 100%
rename from octoprint/static/js/jquery/jquery.flot.js
rename to octoprint/static/js/lib/jquery/jquery.flot.js
diff --git a/octoprint/static/js/jquery/jquery.iframe-transport.js b/octoprint/static/js/lib/jquery/jquery.iframe-transport.js
similarity index 100%
rename from octoprint/static/js/jquery/jquery.iframe-transport.js
rename to octoprint/static/js/lib/jquery/jquery.iframe-transport.js
diff --git a/octoprint/static/js/jquery/jquery.min.js b/octoprint/static/js/lib/jquery/jquery.min.js
similarity index 100%
rename from octoprint/static/js/jquery/jquery.min.js
rename to octoprint/static/js/lib/jquery/jquery.min.js
diff --git a/octoprint/static/js/jquery/jquery.pnotify.min.js b/octoprint/static/js/lib/jquery/jquery.pnotify.min.js
similarity index 100%
rename from octoprint/static/js/jquery/jquery.pnotify.min.js
rename to octoprint/static/js/lib/jquery/jquery.pnotify.min.js
diff --git a/octoprint/static/js/jquery/jquery.ui.core.js b/octoprint/static/js/lib/jquery/jquery.ui.core.js
similarity index 100%
rename from octoprint/static/js/jquery/jquery.ui.core.js
rename to octoprint/static/js/lib/jquery/jquery.ui.core.js
diff --git a/octoprint/static/js/jquery/jquery.ui.mouse.js b/octoprint/static/js/lib/jquery/jquery.ui.mouse.js
similarity index 100%
rename from octoprint/static/js/jquery/jquery.ui.mouse.js
rename to octoprint/static/js/lib/jquery/jquery.ui.mouse.js
diff --git a/octoprint/static/js/jquery/jquery.ui.slider.js b/octoprint/static/js/lib/jquery/jquery.ui.slider.js
similarity index 100%
rename from octoprint/static/js/jquery/jquery.ui.slider.js
rename to octoprint/static/js/lib/jquery/jquery.ui.slider.js
diff --git a/octoprint/static/js/jquery/jquery.ui.widget.js b/octoprint/static/js/lib/jquery/jquery.ui.widget.js
similarity index 100%
rename from octoprint/static/js/jquery/jquery.ui.widget.js
rename to octoprint/static/js/lib/jquery/jquery.ui.widget.js
diff --git a/octoprint/static/js/knockout.js b/octoprint/static/js/lib/knockout.js
similarity index 100%
rename from octoprint/static/js/knockout.js
rename to octoprint/static/js/lib/knockout.js
diff --git a/octoprint/static/js/less-1.3.3.min.js b/octoprint/static/js/lib/less-1.3.3.min.js
similarity index 100%
rename from octoprint/static/js/less-1.3.3.min.js
rename to octoprint/static/js/lib/less-1.3.3.min.js
diff --git a/octoprint/static/js/modernizr.custom.js b/octoprint/static/js/lib/modernizr.custom.js
similarity index 100%
rename from octoprint/static/js/modernizr.custom.js
rename to octoprint/static/js/lib/modernizr.custom.js
diff --git a/octoprint/static/js/socket.io/WebSocketMain.swf b/octoprint/static/js/lib/socket.io/WebSocketMain.swf
similarity index 100%
rename from octoprint/static/js/socket.io/WebSocketMain.swf
rename to octoprint/static/js/lib/socket.io/WebSocketMain.swf
diff --git a/octoprint/static/js/socket.io/WebSocketMainInsecure.swf b/octoprint/static/js/lib/socket.io/WebSocketMainInsecure.swf
similarity index 100%
rename from octoprint/static/js/socket.io/WebSocketMainInsecure.swf
rename to octoprint/static/js/lib/socket.io/WebSocketMainInsecure.swf
diff --git a/octoprint/static/js/socket.io/socket.io.js b/octoprint/static/js/lib/socket.io/socket.io.js
similarity index 100%
rename from octoprint/static/js/socket.io/socket.io.js
rename to octoprint/static/js/lib/socket.io/socket.io.js
diff --git a/octoprint/static/js/socket.io/socket.io.min.js b/octoprint/static/js/lib/socket.io/socket.io.min.js
similarity index 100%
rename from octoprint/static/js/socket.io/socket.io.min.js
rename to octoprint/static/js/lib/socket.io/socket.io.min.js
diff --git a/octoprint/static/js/underscore.js b/octoprint/static/js/lib/underscore.js
similarity index 100%
rename from octoprint/static/js/underscore.js
rename to octoprint/static/js/lib/underscore.js
diff --git a/octoprint/static/js/ui.js b/octoprint/static/js/ui.js
deleted file mode 100644
index 12cc4ed5..00000000
--- a/octoprint/static/js/ui.js
+++ /dev/null
@@ -1,2329 +0,0 @@
-//~~ View models
-
-function LoginStateViewModel() {
- var self = this;
-
- self.loggedIn = ko.observable(false);
- self.username = ko.observable(undefined);
- self.isAdmin = ko.observable(false);
- self.isUser = ko.observable(false);
-
- self.currentUser = ko.observable(undefined);
-
- self.userMenuText = ko.computed(function() {
- if (self.loggedIn()) {
- return "\"" + self.username() + "\"";
- } else {
- return "Login";
- }
- })
-
- self.subscribers = [];
- self.subscribe = function(callback) {
- self.subscribers.push(callback);
- }
-
- self.requestData = function() {
- $.ajax({
- url: AJAX_BASEURL + "login",
- type: "POST",
- data: {"passive": true},
- success: self.fromResponse
- })
- }
-
- self.fromResponse = function(response) {
- if (response && response.name) {
- self.loggedIn(true);
- self.username(response.name);
- self.isUser(response.user);
- self.isAdmin(response.admin);
-
- self.currentUser(response);
-
- _.each(self.subscribers, function(callback) { callback("login", response); });
- } else {
- self.loggedIn(false);
- self.username(undefined);
- self.isUser(false);
- self.isAdmin(false);
-
- self.currentUser(undefined);
-
- _.each(self.subscribers, function(callback) { callback("logout", {}); });
- }
- }
-
- self.login = function() {
- var username = $("#login_user").val();
- var password = $("#login_pass").val();
- var remember = $("#login_remember").is(":checked");
-
- $("#login_user").val("");
- $("#login_pass").val("");
- $("#login_remember").prop("checked", false);
-
- $.ajax({
- url: AJAX_BASEURL + "login",
- type: "POST",
- data: {"user": username, "pass": password, "remember": remember},
- success: function(response) {
- $.pnotify({title: "Login successful", text: "You are now logged in as \"" + response.name + "\"", type: "success"});
- self.fromResponse(response);
- },
- error: function(jqXHR, textStatus, errorThrown) {
- $.pnotify({title: "Login failed", text: "User unknown or wrong password", type: "error"});
- }
- })
- }
-
- self.logout = function() {
- $.ajax({
- url: AJAX_BASEURL + "logout",
- type: "POST",
- success: function(response) {
- $.pnotify({title: "Logout successful", text: "You are now logged out", type: "success"});
- self.fromResponse(response);
- }
- })
- }
-}
-
-function ConnectionViewModel(loginStateViewModel, settingsViewModel) {
- var self = this;
-
- self.loginState = loginStateViewModel;
- self.settings = settingsViewModel;
-
- self.portOptions = ko.observableArray(undefined);
- self.baudrateOptions = ko.observableArray(undefined);
- self.selectedPort = ko.observable(undefined);
- self.selectedBaudrate = ko.observable(undefined);
- self.saveSettings = ko.observable(undefined);
-
- 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.buttonText = ko.computed(function() {
- if (self.isErrorOrClosed())
- return "Connect";
- else
- return "Disconnect";
- })
-
- self.previousIsOperational = undefined;
-
- self.requestData = function() {
- $.ajax({
- url: AJAX_BASEURL + "control/connection/options",
- method: "GET",
- dataType: "json",
- success: function(response) {
- self.fromResponse(response);
- }
- })
- }
-
- self.fromResponse = function(response) {
- self.portOptions(response.ports);
- self.baudrateOptions(response.baudrates);
-
- if (!self.selectedPort() && response.ports && response.ports.indexOf(response.portPreference) >= 0)
- self.selectedPort(response.portPreference);
- if (!self.selectedBaudrate() && response.baudrates && response.baudrates.indexOf(response.baudratePreference) >= 0)
- self.selectedBaudrate(response.baudratePreference);
-
- self.saveSettings(false);
- }
-
- self.fromHistoryData = function(data) {
- self._processStateData(data.state);
- }
-
- self.fromCurrentData = function(data) {
- self._processStateData(data.state);
- }
-
- self._processStateData = function(data) {
- self.previousIsOperational = self.isOperational();
-
- 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);
-
- var connectionTab = $("#connection");
- if (self.previousIsOperational != self.isOperational()) {
- if (self.isOperational() && connectionTab.hasClass("in")) {
- // connection just got established, close connection tab for now
- connectionTab.collapse("hide");
- } else if (!connectionTab.hasClass("in")) {
- // connection just dropped, make sure connection tab is open
- connectionTab.collapse("show");
- }
- }
- }
-
- self.connect = function() {
- if (self.isErrorOrClosed()) {
- var data = {
- "command": "connect",
- "port": self.selectedPort(),
- "baudrate": self.selectedBaudrate()
- };
-
- if (self.saveSettings())
- data["save"] = true;
-
- $.ajax({
- url: AJAX_BASEURL + "control/connection",
- type: "POST",
- dataType: "json",
- data: data
- })
-
- self.settings.serial_port(self.selectedPort())
- self.settings.serial_baudrate(self.selectedBaudrate())
- self.settings.saveData();
- } else {
- self.requestData();
- $.ajax({
- url: AJAX_BASEURL + "control/connection",
- type: "POST",
- dataType: "json",
- data: {"command": "disconnect"}
- })
- }
- }
-}
-
-function PrinterStateViewModel(loginStateViewModel) {
- var self = this;
-
- self.loginState = loginStateViewModel;
-
- self.stateString = ko.observable(undefined);
- 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.isSdReady = ko.observable(undefined);
-
- self.filename = ko.observable(undefined);
- self.progress = ko.observable(undefined);
- self.filesize = ko.observable(undefined);
- self.filepos = ko.observable(undefined);
- self.printTime = ko.observable(undefined);
- self.printTimeLeft = ko.observable(undefined);
- self.sd = ko.observable(undefined);
-
- self.filament = ko.observable(undefined);
- self.estimatedPrintTime = ko.observable(undefined);
-
- self.currentHeight = ko.observable(undefined);
-
- self.byteString = ko.computed(function() {
- if (!self.filesize())
- return "-";
- var filepos = self.filepos() ? self.filepos() : "-";
- return filepos + " / " + self.filesize();
- });
- self.heightString = ko.computed(function() {
- if (!self.currentHeight())
- return "-";
- return self.currentHeight();
- })
- self.progressString = ko.computed(function() {
- if (!self.progress())
- return 0;
- return self.progress();
- });
- self.pauseString = ko.computed(function() {
- if (self.isPaused())
- return "Continue";
- else
- return "Pause";
- });
-
- self.fromCurrentData = function(data) {
- self._fromData(data);
- }
-
- self.fromHistoryData = function(data) {
- self._fromData(data);
- }
-
- self._fromData = function(data) {
- self._processStateData(data.state)
- self._processJobData(data.job);
- self._processProgressData(data.progress);
- self._processZData(data.currentZ);
- }
-
- self._processStateData = function(data) {
- self.stateString(data.stateString);
- 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.isSdReady(data.flags.sdReady);
- }
-
- self._processJobData = function(data) {
- self.filename(data.filename);
- self.filesize(data.filesize);
- self.estimatedPrintTime(data.estimatedPrintTime);
- self.filament(data.filament);
- self.sd(data.sd);
- }
-
- self._processProgressData = function(data) {
- if (data.progress) {
- self.progress(Math.round(data.progress * 100));
- } else {
- self.progress(undefined);
- }
- self.filepos(data.filepos);
- self.printTime(data.printTime);
- self.printTimeLeft(data.printTimeLeft);
- }
-
- self._processZData = function(data) {
- self.currentHeight(data);
- }
-
- self.print = function() {
- var printAction = function() {
- self._jobCommand("start");
- }
-
- if (self.isPaused()) {
- $("#confirmation_dialog .confirmation_dialog_message").text("This will restart the print job from the beginning.");
- $("#confirmation_dialog .confirmation_dialog_acknowledge").click(function(e) {e.preventDefault(); $("#confirmation_dialog").modal("hide"); printAction(); });
- $("#confirmation_dialog").modal("show");
- } else {
- printAction();
- }
-
- }
-
- self.pause = function() {
- self._jobCommand("pause");
- }
-
- self.cancel = function() {
- self._jobCommand("cancel");
- }
-
- self._jobCommand = function(command) {
- $.ajax({
- url: AJAX_BASEURL + "control/job",
- type: "POST",
- dataType: "json",
- data: {command: command}
- });
- }
-}
-
-function TemperatureViewModel(loginStateViewModel, settingsViewModel) {
- var self = this;
-
- self.loginState = loginStateViewModel;
-
- self.temp = ko.observable(undefined);
- self.bedTemp = ko.observable(undefined);
- self.targetTemp = ko.observable(undefined);
- self.bedTargetTemp = ko.observable(undefined);
-
- 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.temperature_profiles = settingsViewModel.temperature_profiles;
-
- self.setTempFromProfile = function(profile) {
- if (!profile)
- return;
- self.setTemp(profile.extruder);
- }
-
- self.setTemp = function(temp) {
- $.ajax({
- url: AJAX_BASEURL + "control/temperature",
- type: "POST",
- dataType: "json",
- data: { temp: temp },
- success: function() {$("#temp_newTemp").val("")}
- })
- };
-
- self.setBedTempFromProfile = function(profile) {
- if (!profile)
- return;
- self.setBedTemp(profile.bed);
- }
-
- self.setBedTemp = function(bedTemp) {
- $.ajax({
- url: AJAX_BASEURL + "control/temperature",
- type: "POST",
- dataType: "json",
- data: { bedTemp: bedTemp },
- success: function() {$("#temp_newBedTemp").val("")}
- })
- };
-
- self.tempString = ko.computed(function() {
- if (!self.temp())
- return "-";
- return self.temp() + " °C";
- });
- self.bedTempString = ko.computed(function() {
- if (!self.bedTemp())
- return "-";
- return self.bedTemp() + " °C";
- });
- self.targetTempString = ko.computed(function() {
- if (!self.targetTemp())
- return "-";
- return self.targetTemp() + " °C";
- });
- self.bedTargetTempString = ko.computed(function() {
- if (!self.bedTargetTemp())
- return "-";
- return self.bedTargetTemp() + " °C";
- });
-
- self.temperatures = [];
- self.plotOptions = {
- yaxis: {
- min: 0,
- max: 310,
- ticks: 10
- },
- xaxis: {
- mode: "time",
- minTickSize: [2, "minute"],
- tickFormatter: function(val, axis) {
- if (val == undefined || val == 0)
- return ""; // we don't want to display the minutes since the epoch if not connected yet ;)
-
- // current time in milliseconds in UTC
- var timestampUtc = Date.now();
-
- // calculate difference in milliseconds
- var diff = timestampUtc - val;
-
- // convert to minutes
- var diffInMins = Math.round(diff / (60 * 1000));
- if (diffInMins == 0)
- return "just now";
- else
- return "- " + diffInMins + " min";
- }
- },
- legend: {
- noColumns: 4
- }
- }
-
- self.fromCurrentData = function(data) {
- self._processStateData(data.state);
- self._processTemperatureUpdateData(data.temperatures);
- }
-
- self.fromHistoryData = function(data) {
- self._processStateData(data.state);
- self._processTemperatureHistoryData(data.temperatureHistory);
- }
-
- 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._processTemperatureUpdateData = function(data) {
- if (data.length == 0)
- return;
-
- self.temp(data[data.length - 1].temp);
- self.bedTemp(data[data.length - 1].bedTemp);
- self.targetTemp(data[data.length - 1].targetTemp);
- self.bedTargetTemp(data[data.length - 1].targetBedTemp);
-
- if (!self.temperatures)
- self.temperatures = [];
- if (!self.temperatures.actual)
- self.temperatures.actual = [];
- if (!self.temperatures.target)
- self.temperatures.target = [];
- if (!self.temperatures.actualBed)
- self.temperatures.actualBed = [];
- if (!self.temperatures.targetBed)
- self.temperatures.targetBed = [];
-
- for (var i = 0; i < data.length; i++) {
- self.temperatures.actual.push([data[i].currentTime, data[i].temp])
- self.temperatures.target.push([data[i].currentTime, data[i].targetTemp])
- self.temperatures.actualBed.push([data[i].currentTime, data[i].bedTemp])
- self.temperatures.targetBed.push([data[i].currentTime, data[i].targetBedTemp])
- }
- self.temperatures.actual = self.temperatures.actual.slice(-300);
- self.temperatures.target = self.temperatures.target.slice(-300);
- self.temperatures.actualBed = self.temperatures.actualBed.slice(-300);
- self.temperatures.targetBed = self.temperatures.targetBed.slice(-300);
-
- self.updatePlot();
- }
-
- self._processTemperatureHistoryData = function(data) {
- self.temperatures = data;
- self.updatePlot();
- }
-
- self.updatePlot = function() {
- var data = [
- {label: "Actual", color: "#FF4040", data: self.temperatures.actual},
- {label: "Target", color: "#FFA0A0", data: self.temperatures.target},
- {label: "Bed Actual", color: "#4040FF", data: self.temperatures.actualBed},
- {label: "Bed Target", color: "#A0A0FF", data: self.temperatures.targetBed}
- ]
- $.plot($("#temperature-graph"), data, self.plotOptions);
- }
-}
-
-function ControlViewModel(loginStateViewModel, settingsViewModel) {
- var self = this;
-
- self.loginState = loginStateViewModel;
- self.settings = settingsViewModel;
-
- 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.feedbackControlLookup = {};
-
- 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.fromFeedbackCommandData = function(data) {
- if (data.name in self.feedbackControlLookup) {
- self.feedbackControlLookup[data.name](data.output);
- }
- }
-
- self.requestData = function() {
- $.ajax({
- url: AJAX_BASEURL + "control/custom",
- method: "GET",
- dataType: "json",
- success: function(response) {
- self._fromResponse(response);
- }
- });
- }
-
- self._fromResponse = function(response) {
- self.controls(self._processControls(response.controls));
- }
-
- 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.type == "parametric_command" || control.type == "parametric_commands") {
- for (var i = 0; i < control.input.length; i++) {
- control.input[i].value = control.input[i].default;
- }
- } else if (control.type == "feedback_command") {
- control.output = ko.observable("");
- self.feedbackControlLookup[control.name] = control.output;
- } else if (control.type == "section") {
- control.children = self._processControls(control.children);
- }
- return control;
- }
-
- self.sendJogCommand = function(axis, multiplier, distance) {
- if (typeof distance === "undefined")
- distance = $('#jog_distance button.active').data('distance');
- $.ajax({
- url: AJAX_BASEURL + "control/jog",
- type: "POST",
- dataType: "json",
- data: axis + "=" + ( distance * multiplier )
- })
- }
-
- self.sendHomeCommand = function(axis) {
- $.ajax({
- url: AJAX_BASEURL + "control/jog",
- type: "POST",
- dataType: "json",
- data: "home" + axis
- })
- }
-
- self.sendExtrudeCommand = function() {
- self._sendECommand(1);
- }
-
- self.sendRetractCommand = function() {
- self._sendECommand(-1);
- }
-
- self._sendECommand = function(dir) {
- var length = self.extrusionAmount();
- if (!length)
- length = 5;
- $.ajax({
- url: AJAX_BASEURL + "control/jog",
- type: "POST",
- dataType: "json",
- data: "extrude=" + (dir * length)
- })
- }
-
- self.sendCustomCommand = function(command) {
- if (!command)
- return;
-
- var data = undefined;
- if (command.type == "command" || command.type == "parametric_command" || command.type == "feedback_command") {
- // single command
- data = {"command" : command.command};
- } else if (command.type == "commands" || command.type == "parametric_commands") {
- // multi command
- data = {"commands": command.commands};
- }
-
- if (command.type == "parametric_command" || command.type == "parametric_commands") {
- // parametric command(s)
- data["parameters"] = {};
- for (var i = 0; i < command.input.length; i++) {
- data["parameters"][command.input[i].parameter] = command.input[i].value;
- }
- }
-
- if (!data)
- return;
-
- $.ajax({
- url: AJAX_BASEURL + "control/command",
- type: "POST",
- dataType: "json",
- contentType: "application/json; charset=UTF-8",
- data: JSON.stringify(data)
- })
- }
-
- self.displayMode = function(customControl) {
- switch (customControl.type) {
- case "section":
- return "customControls_sectionTemplate";
- case "command":
- case "commands":
- return "customControls_commandTemplate";
- case "parametric_command":
- case "parametric_commands":
- return "customControls_parametricCommandTemplate";
- case "feedback_command":
- return "customControls_feedbackCommandTemplate";
- default:
- return "customControls_emptyTemplate";
- }
- }
-
-}
-
-function TerminalViewModel(loginStateViewModel) {
- var self = this;
-
- self.loginState = loginStateViewModel;
-
- self.log = [];
-
- 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.autoscrollEnabled = ko.observable(true);
- self.filterM105 = ko.observable(false);
- self.filterM27 = ko.observable(false);
-
- self.regexM105 = /(Send: M105)|(Recv: ok T:)/;
- self.regexM27 = /(Send: M27)|(Recv: SD printing byte)/;
-
- self.filterM105.subscribe(function(newValue) {
- self.updateOutput();
- });
-
- self.filterM27.subscribe(function(newValue) {
- self.updateOutput();
- });
-
- self.fromCurrentData = function(data) {
- self._processStateData(data.state);
- self._processCurrentLogData(data.logs);
- }
-
- self.fromHistoryData = function(data) {
- self._processStateData(data.state);
- self._processHistoryLogData(data.logHistory);
- }
-
- self._processCurrentLogData = function(data) {
- if (!self.log)
- self.log = []
- self.log = self.log.concat(data)
- self.log = self.log.slice(-300)
- self.updateOutput();
- }
-
- self._processHistoryLogData = function(data) {
- self.log = data;
- self.updateOutput();
- }
-
- 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.updateOutput = function() {
- if (!self.log)
- return;
-
- var output = "";
- for (var i = 0; i < self.log.length; i++) {
- if (self.filterM105() && self.log[i].match(self.regexM105)) continue;
- if (self.filterM27() && self.log[i].match(self.regexM27)) continue;
-
- output += self.log[i] + "\n";
- }
-
- var container = $("#terminal-output");
- container.text(output);
-
- if (self.autoscrollEnabled()) {
- container.scrollTop(container[0].scrollHeight - container.height())
- }
- }
-}
-
-function GcodeFilesViewModel(printerStateViewModel, loginStateViewModel) {
- var self = this;
-
- self.printerState = printerStateViewModel;
- self.loginState = loginStateViewModel;
-
- 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.isSdReady = ko.observable(undefined);
-
- self.freeSpace = ko.observable(undefined);
-
- // initialize list helper
- self.listHelper = new ItemListHelper(
- "gcodeFiles",
- {
- "name": function(a, b) {
- // sorts ascending
- if (a["name"].toLocaleLowerCase() < b["name"].toLocaleLowerCase()) return -1;
- if (a["name"].toLocaleLowerCase() > b["name"].toLocaleLowerCase()) return 1;
- return 0;
- },
- "upload": function(a, b) {
- // sorts descending
- if (b["date"] === undefined || a["date"] > b["date"]) return -1;
- if (a["date"] < b["date"]) return 1;
- return 0;
- },
- "size": function(a, b) {
- // sorts descending
- if (b["bytes"] === undefined || a["bytes"] > b["bytes"]) return -1;
- if (a["bytes"] < b["bytes"]) return 1;
- return 0;
- }
- },
- {
- "printed": function(file) {
- return !(file["prints"] && file["prints"]["success"] && file["prints"]["success"] > 0);
- },
- "sd": function(file) {
- return file["origin"] && file["origin"] == "sd";
- },
- "local": function(file) {
- return !(file["origin"] && file["origin"] == "sd");
- }
- },
- "name",
- [],
- [["sd", "local"]],
- CONFIG_GCODEFILESPERPAGE
- );
-
- self.isLoadActionPossible = ko.computed(function() {
- return self.loginState.isUser() && !self.isPrinting() && !self.isPaused() && !self.isLoading();
- });
-
- self.isLoadAndPrintActionPossible = ko.computed(function() {
- return self.loginState.isUser() && self.isOperational() && self.isLoadActionPossible();
- });
-
- self.printerState.filename.subscribe(function(newValue) {
- self.highlightFilename(newValue);
- });
-
- self.highlightFilename = function(filename) {
- if (filename == undefined) {
- self.listHelper.selectNone();
- } else {
- self.listHelper.selectItem(function(item) {
- return item.name == filename;
- })
- }
- }
-
- 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.isSdReady(data.flags.sdReady);
- }
-
- self.requestData = function() {
- $.ajax({
- url: AJAX_BASEURL + "gcodefiles",
- method: "GET",
- dataType: "json",
- success: function(response) {
- self.fromResponse(response);
- }
- });
- }
-
- self.fromResponse = function(response) {
- self.listHelper.updateItems(response.files);
-
- if (response.filename) {
- // got a file to scroll to
- self.listHelper.switchToItem(function(item) {return item.name == response.filename});
- }
-
- self.freeSpace(response.free);
-
- self.highlightFilename(self.printerState.filename());
- }
-
- self.loadFile = function(filename, printAfterLoad) {
- var file = self.listHelper.getItem(function(item) {return item.name == filename});
- if (!file) return;
-
- $.ajax({
- url: AJAX_BASEURL + "gcodefiles/load",
- type: "POST",
- dataType: "json",
- data: {filename: filename, print: printAfterLoad, target: file.origin}
- })
- }
-
- self.removeFile = function(filename) {
- var file = self.listHelper.getItem(function(item) {return item.name == filename});
- if (!file) return;
-
- $.ajax({
- url: AJAX_BASEURL + "gcodefiles/delete",
- type: "POST",
- dataType: "json",
- data: {filename: filename, target: file.origin},
- success: self.fromResponse
- })
- }
-
- self.initSdCard = function() {
- self._sendSdCommand("init");
- }
-
- self.releaseSdCard = function() {
- self._sendSdCommand("release");
- }
-
- self.refreshSdFiles = function() {
- self._sendSdCommand("refresh");
- }
-
- self._sendSdCommand = function(command) {
- $.ajax({
- url: AJAX_BASEURL + "control/sd",
- type: "POST",
- dataType: "json",
- data: {command: command}
- });
- }
-
- self.getPopoverContent = function(data) {
- var output = "Uploaded: " + data["date"] + "
";
- if (data["gcodeAnalysis"]) {
- output += "";
- output += "Filament: " + data["gcodeAnalysis"]["filament"] + "
";
- output += "Estimated Print Time: " + data["gcodeAnalysis"]["estimatedPrintTime"];
- output += "
";
- }
- if (data["prints"] && data["prints"]["last"]) {
- output += "";
- output += "Last Print: " + data["prints"]["last"]["date"] + "";
- output += "
";
- }
- return output;
- }
-
- self.getSuccessClass = function(data) {
- if (!data["prints"] || !data["prints"]["last"]) {
- return "";
- }
- return data["prints"]["last"]["success"] ? "text-success" : "text-error";
- }
-
- self.enableRemove = function(data) {
- return self.loginState.isUser() && !(self.listHelper.isSelected(data) && (self.isPrinting() || self.isPaused()));
- }
-
- self.enableSelect = function(data, printAfterSelect) {
- var isLoadActionPossible = self.loginState.isUser() && !(self.isPrinting() || self.isPaused() || self.isLoading());
- return isLoadActionPossible && !self.listHelper.isSelected(data);
- }
-
-}
-
-function TimelapseViewModel(loginStateViewModel) {
- var self = this;
-
- self.loginState = loginStateViewModel;
-
- self.timelapseType = ko.observable(undefined);
- self.timelapseTimedInterval = ko.observable(undefined);
-
- 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.intervalInputEnabled = ko.computed(function() {
- return ("timed" == self.timelapseType());
- })
-
- self.isOperational.subscribe(function(newValue) {
- self.requestData();
- })
-
- // initialize list helper
- self.listHelper = new ItemListHelper(
- "timelapseFiles",
- {
- "name": function(a, b) {
- // sorts ascending
- if (a["name"].toLocaleLowerCase() < b["name"].toLocaleLowerCase()) return -1;
- if (a["name"].toLocaleLowerCase() > b["name"].toLocaleLowerCase()) return 1;
- return 0;
- },
- "creation": function(a, b) {
- // sorts descending
- if (a["date"] > b["date"]) return -1;
- if (a["date"] < b["date"]) return 1;
- return 0;
- },
- "size": function(a, b) {
- // sorts descending
- if (a["bytes"] > b["bytes"]) return -1;
- if (a["bytes"] < b["bytes"]) return 1;
- return 0;
- }
- },
- {
- },
- "name",
- [],
- [],
- CONFIG_TIMELAPSEFILESPERPAGE
- )
-
- self.requestData = function() {
- $.ajax({
- url: AJAX_BASEURL + "timelapse",
- type: "GET",
- dataType: "json",
- success: self.fromResponse
- });
- }
-
- self.fromResponse = function(response) {
- self.timelapseType(response.type);
- self.listHelper.updateItems(response.files);
-
- if (response.type == "timed" && response.config && response.config.interval) {
- self.timelapseTimedInterval(response.config.interval)
- } else {
- self.timelapseTimedInterval(undefined)
- }
- }
-
- 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.removeFile = function(filename) {
- $.ajax({
- url: AJAX_BASEURL + "timelapse/" + filename,
- type: "DELETE",
- dataType: "json",
- success: self.requestData
- })
- }
-
- self.save = function() {
- var data = {
- "type": self.timelapseType()
- }
-
- if (self.timelapseType() == "timed") {
- data["interval"] = self.timelapseTimedInterval();
- }
-
- $.ajax({
- url: AJAX_BASEURL + "timelapse",
- type: "POST",
- dataType: "json",
- data: data,
- success: self.fromResponse
- })
- }
-}
-
-function GcodeViewModel(loginStateViewModel) {
- var self = this;
-
- self.loginState = loginStateViewModel;
-
- self.loadedFilename = undefined;
- self.loadedFileMTime = undefined;
- self.status = 'idle';
- self.enabled = false;
-
- self.errorCount = 0;
-
- self.initialize = function(){
- self.enabled = true;
- GCODE.ui.initHandlers();
- }
-
- self.loadFile = function(filename, mtime){
- if (self.status == 'idle' && self.errorCount < 3) {
- self.status = 'request';
- $.ajax({
- url: AJAX_BASEURL + "gcodefiles/" + filename,
- data: { "mtime": mtime },
- type: "GET",
- success: function(response, rstatus) {
- if(rstatus === 'success'){
- self.showGCodeViewer(response, rstatus);
- self.loadedFilename=filename;
- self.loadedFileMTime=mtime;
- self.status = 'idle';
- }
- },
- error: function() {
- self.status = 'idle';
- self.errorCount++;
- }
- })
- }
- }
-
- self.showGCodeViewer = function(response, rstatus) {
- var par = {};
- par.target = {};
- par.target.result = response;
- GCODE.gCodeReader.loadFile(par);
- }
-
- self.fromHistoryData = function(data) {
- self._processData(data);
- }
-
- self.fromCurrentData = function(data) {
- self._processData(data);
- }
-
- self._processData = function(data) {
- if (!self.enabled) return;
- if (!data.job.filename) return;
-
- if(self.loadedFilename && self.loadedFilename == data.job.filename &&
- self.loadedFileMTime == data.job.mtime) {
- if (data.state.flags && (data.state.flags.printing || data.state.flags.paused)) {
- var cmdIndex = GCODE.gCodeReader.getCmdIndexForPercentage(data.progress.progress * 100);
- if(cmdIndex){
- GCODE.renderer.render(cmdIndex.layer, 0, cmdIndex.cmd);
- GCODE.ui.updateLayerInfo(cmdIndex.layer);
- }
- }
- self.errorCount = 0
- } else if (data.job.filename) {
- self.loadFile(data.job.filename, data.job.mtime);
- }
- }
-
-}
-
-function UsersViewModel(loginStateViewModel) {
- var self = this;
-
- self.loginState = loginStateViewModel;
-
- // initialize list helper
- self.listHelper = new ItemListHelper(
- "users",
- {
- "name": function(a, b) {
- // sorts ascending
- if (a["name"].toLocaleLowerCase() < b["name"].toLocaleLowerCase()) return -1;
- if (a["name"].toLocaleLowerCase() > b["name"].toLocaleLowerCase()) return 1;
- return 0;
- }
- },
- {},
- "name",
- [],
- [],
- CONFIG_USERSPERPAGE
- );
-
- self.emptyUser = {name: "", admin: false, active: false};
-
- self.currentUser = ko.observable(self.emptyUser);
-
- self.editorUsername = ko.observable(undefined);
- self.editorPassword = ko.observable(undefined);
- self.editorRepeatedPassword = ko.observable(undefined);
- self.editorAdmin = ko.observable(undefined);
- self.editorActive = ko.observable(undefined);
-
- self.currentUser.subscribe(function(newValue) {
- if (newValue === undefined) {
- self.editorUsername(undefined);
- self.editorAdmin(undefined);
- self.editorActive(undefined);
- } else {
- self.editorUsername(newValue.name);
- self.editorAdmin(newValue.admin);
- self.editorActive(newValue.active);
- }
- self.editorPassword(undefined);
- self.editorRepeatedPassword(undefined);
- });
-
- self.editorPasswordMismatch = ko.computed(function() {
- return self.editorPassword() != self.editorRepeatedPassword();
- });
-
- self.requestData = function() {
- if (!CONFIG_ACCESS_CONTROL) return;
-
- $.ajax({
- url: AJAX_BASEURL + "users",
- type: "GET",
- dataType: "json",
- success: self.fromResponse
- });
- }
-
- self.fromResponse = function(response) {
- self.listHelper.updateItems(response.users);
- }
-
- self.showAddUserDialog = function() {
- if (!CONFIG_ACCESS_CONTROL) return;
-
- self.currentUser(undefined);
- self.editorActive(true);
- $("#settings-usersDialogAddUser").modal("show");
- }
-
- self.confirmAddUser = function() {
- if (!CONFIG_ACCESS_CONTROL) return;
-
- var user = {name: self.editorUsername(), password: self.editorPassword(), admin: self.editorAdmin(), active: self.editorActive()};
- self.addUser(user, function() {
- // close dialog
- self.currentUser(undefined);
- $("#settings-usersDialogAddUser").modal("hide");
- });
- }
-
- self.showEditUserDialog = function(user) {
- if (!CONFIG_ACCESS_CONTROL) return;
-
- self.currentUser(user);
- $("#settings-usersDialogEditUser").modal("show");
- }
-
- self.confirmEditUser = function() {
- if (!CONFIG_ACCESS_CONTROL) return;
-
- var user = self.currentUser();
- user.active = self.editorActive();
- user.admin = self.editorAdmin();
-
- // make AJAX call
- self.updateUser(user, function() {
- // close dialog
- self.currentUser(undefined);
- $("#settings-usersDialogEditUser").modal("hide");
- });
- }
-
- self.showChangePasswordDialog = function(user) {
- if (!CONFIG_ACCESS_CONTROL) return;
-
- self.currentUser(user);
- $("#settings-usersDialogChangePassword").modal("show");
- }
-
- self.confirmChangePassword = function() {
- if (!CONFIG_ACCESS_CONTROL) return;
-
- self.updatePassword(self.currentUser().name, self.editorPassword(), function() {
- // close dialog
- self.currentUser(undefined);
- $("#settings-usersDialogChangePassword").modal("hide");
- });
- }
-
- //~~ AJAX calls
-
- self.addUser = function(user, callback) {
- if (!CONFIG_ACCESS_CONTROL) return;
- if (user === undefined) return;
-
- $.ajax({
- url: AJAX_BASEURL + "users",
- type: "POST",
- contentType: "application/json; charset=UTF-8",
- data: JSON.stringify(user),
- success: function(response) {
- self.fromResponse(response);
- callback();
- }
- });
- }
-
- self.removeUser = function(user, callback) {
- if (!CONFIG_ACCESS_CONTROL) return;
- if (user === undefined) return;
-
- if (user.name == loginStateViewModel.username()) {
- // we do not allow to delete ourselves
- $.pnotify({title: "Not possible", text: "You may not delete your own account.", type: "error"});
- return;
- }
-
- $.ajax({
- url: AJAX_BASEURL + "users/" + user.name,
- type: "DELETE",
- success: function(response) {
- self.fromResponse(response);
- callback();
- }
- });
- }
-
- self.updateUser = function(user, callback) {
- if (!CONFIG_ACCESS_CONTROL) return;
- if (user === undefined) return;
-
- $.ajax({
- url: AJAX_BASEURL + "users/" + user.name,
- type: "PUT",
- contentType: "application/json; charset=UTF-8",
- data: JSON.stringify(user),
- success: function(response) {
- self.fromResponse(response);
- callback();
- }
- });
- }
-
- self.updatePassword = function(username, password, callback) {
- if (!CONFIG_ACCESS_CONTROL) return;
-
- $.ajax({
- url: AJAX_BASEURL + "users/" + username + "/password",
- type: "PUT",
- contentType: "application/json; charset=UTF-8",
- data: JSON.stringify({password: password}),
- success: callback
- });
- }
-}
-
-function SettingsViewModel(loginStateViewModel, usersViewModel) {
- var self = this;
-
- self.loginState = loginStateViewModel;
- self.users = usersViewModel;
-
- self.api_enabled = ko.observable(undefined);
- self.api_key = ko.observable(undefined);
-
- self.appearance_name = ko.observable(undefined);
- self.appearance_color = ko.observable(undefined);
-
- /* I did attempt to allow arbitrary gradients but cross browser support via knockout or jquery was going to be horrible */
- self.appearance_available_colors = ko.observable(["default", "red", "orange", "yellow", "green", "blue", "violet", "black"]);
-
- 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.webcam_watermark = ko.observable(undefined);
- self.webcam_flipH = ko.observable(undefined);
- self.webcam_flipV = ko.observable(undefined);
-
- self.feature_gcodeViewer = ko.observable(undefined);
- self.feature_waitForStart = ko.observable(undefined);
- self.feature_alwaysSendChecksum = ko.observable(undefined);
- self.feature_sdSupport = ko.observable(undefined);
-
- self.serial_port = ko.observable();
- self.serial_baudrate = ko.observable();
- self.serial_portOptions = ko.observableArray([]);
- self.serial_baudrateOptions = ko.observableArray([]);
- self.serial_autoconnect = ko.observable(undefined);
- self.serial_timeoutConnection = ko.observable(undefined);
- self.serial_timeoutDetection = ko.observable(undefined);
- self.serial_timeoutCommunication = ko.observable(undefined);
- self.serial_log = 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.temperature_profiles = ko.observableArray(undefined);
-
- self.system_actions = ko.observableArray([]);
-
- self.addTemperatureProfile = function() {
- self.temperature_profiles.push({name: "New", extruder:0, bed:0});
- };
-
- self.removeTemperatureProfile = function(profile) {
- self.temperature_profiles.remove(profile);
- };
-
- self.requestData = function() {
- $.ajax({
- url: AJAX_BASEURL + "settings",
- type: "GET",
- dataType: "json",
- success: self.fromResponse
- });
- }
-
- self.fromResponse = function(response) {
- self.api_enabled(response.api.enabled);
- self.api_key(response.api.key);
-
- self.appearance_name(response.appearance.name);
- self.appearance_color(response.appearance.color);
-
- 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.webcam_watermark(response.webcam.watermark);
- self.webcam_flipH(response.webcam.flipH);
- self.webcam_flipV(response.webcam.flipV);
-
- self.feature_gcodeViewer(response.feature.gcodeViewer);
- self.feature_waitForStart(response.feature.waitForStart);
- self.feature_alwaysSendChecksum(response.feature.alwaysSendChecksum);
- self.feature_sdSupport(response.feature.sdSupport);
-
- self.serial_port(response.serial.port);
- self.serial_baudrate(response.serial.baudrate);
- self.serial_portOptions(response.serial.portOptions);
- self.serial_baudrateOptions(response.serial.baudrateOptions);
- self.serial_autoconnect(response.serial.autoconnect);
- self.serial_timeoutConnection(response.serial.timeoutConnection);
- self.serial_timeoutDetection(response.serial.timeoutDetection);
- self.serial_timeoutCommunication(response.serial.timeoutCommunication);
- self.serial_log(response.serial.log);
-
- 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.temperature_profiles(response.temperature.profiles);
-
- self.system_actions(response.system.actions);
- }
-
- self.saveData = function() {
- var data = {
- "api" : {
- "enabled": self.api_enabled(),
- "key": self.api_key()
- },
- "appearance" : {
- "name": self.appearance_name(),
- "color": self.appearance_color()
- },
- "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(),
- "watermark": self.webcam_watermark(),
- "flipH": self.webcam_flipH(),
- "flipV": self.webcam_flipV()
- },
- "feature": {
- "gcodeViewer": self.feature_gcodeViewer(),
- "waitForStart": self.feature_waitForStart(),
- "alwaysSendChecksum": self.feature_alwaysSendChecksum(),
- "sdSupport": self.feature_sdSupport()
- },
- "serial": {
- "port": self.serial_port(),
- "baudrate": self.serial_baudrate(),
- "autoconnect": self.serial_autoconnect(),
- "timeoutConnection": self.serial_timeoutConnection(),
- "timeoutDetection": self.serial_timeoutDetection(),
- "timeoutCommunication": self.serial_timeoutCommunication(),
- "log": self.serial_log()
- },
- "folder": {
- "uploads": self.folder_uploads(),
- "timelapse": self.folder_timelapse(),
- "timelapseTmp": self.folder_timelapseTmp(),
- "logs": self.folder_logs()
- },
- "temperature": {
- "profiles": self.temperature_profiles()
- },
- "system": {
- "actions": self.system_actions()
- }
- }
-
- $.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 NavigationViewModel(loginStateViewModel, appearanceViewModel, settingsViewModel, usersViewModel) {
- var self = this;
-
- self.loginState = loginStateViewModel;
- self.appearance = appearanceViewModel;
- self.systemActions = settingsViewModel.system_actions;
- self.users = usersViewModel;
-
- self.triggerAction = function(action) {
- var callback = function() {
- $.ajax({
- url: AJAX_BASEURL + "system",
- type: "POST",
- dataType: "json",
- data: "action=" + action.action,
- success: function() {
- $.pnotify({title: "Success", text: "The command \""+ action.name +"\" executed successfully", type: "success"});
- },
- error: function(jqXHR, textStatus, errorThrown) {
- $.pnotify({title: "Error", text: "The command \"" + action.name + "\" could not be executed.
Reason:
" + jqXHR.responseText + "
", type: "error"});
- }
- })
- }
- if (action.confirm) {
- $("#confirmation_dialog .confirmation_dialog_message").text(action.confirm);
- $("#confirmation_dialog .confirmation_dialog_acknowledge").click(function(e) {e.preventDefault(); $("#confirmation_dialog").modal("hide"); callback(); });
- $("#confirmation_dialog").modal("show");
- } else {
- callback();
- }
- }
-}
-
-function DataUpdater(loginStateViewModel, connectionViewModel, printerStateViewModel, temperatureViewModel, controlViewModel, terminalViewModel, gcodeFilesViewModel, timelapseViewModel, gcodeViewModel) {
- var self = this;
-
- self.loginStateViewModel = loginStateViewModel;
- self.connectionViewModel = connectionViewModel;
- self.printerStateViewModel = printerStateViewModel;
- self.temperatureViewModel = temperatureViewModel;
- self.controlViewModel = controlViewModel;
- self.terminalViewModel = terminalViewModel;
- self.gcodeFilesViewModel = gcodeFilesViewModel;
- self.timelapseViewModel = timelapseViewModel;
- self.gcodeViewModel = gcodeViewModel;
-
- self._socket = io.connect();
- self._socket.on("connect", function() {
- if ($("#offline_overlay").is(":visible")) {
- $("#offline_overlay").hide();
- self.timelapseViewModel.requestData();
- $("#webcam_image").attr("src", CONFIG_WEBCAM_STREAM + "?" + new Date().getTime());
- self.loginStateViewModel.requestData();
- self.gcodeFilesViewModel.requestData();
- }
- });
- self._socket.on("disconnect", function() {
- $("#offline_overlay_message").html(
- "The server appears to be offline, at least I'm not getting any response from it. I'll try to reconnect " +
- "automatically over the next couple of minutes, however you are welcome to try a manual reconnect " +
- "anytime using the button below."
- );
- if (!$("#offline_overlay").is(":visible"))
- $("#offline_overlay").show();
- });
- self._socket.on("reconnect_failed", function() {
- $("#offline_overlay_message").html(
- "The server appears to be offline, at least I'm not getting any response from it. I could not reconnect automatically, " +
- "but you may try a manual reconnect using the button below."
- );
- });
- self._socket.on("history", function(data) {
- self.connectionViewModel.fromHistoryData(data);
- self.printerStateViewModel.fromHistoryData(data);
- self.temperatureViewModel.fromHistoryData(data);
- self.controlViewModel.fromHistoryData(data);
- self.terminalViewModel.fromHistoryData(data);
- self.timelapseViewModel.fromHistoryData(data);
- self.gcodeViewModel.fromHistoryData(data);
- self.gcodeFilesViewModel.fromCurrentData(data);
- });
- self._socket.on("current", function(data) {
- self.connectionViewModel.fromCurrentData(data);
- self.printerStateViewModel.fromCurrentData(data);
- self.temperatureViewModel.fromCurrentData(data);
- self.controlViewModel.fromCurrentData(data);
- self.terminalViewModel.fromCurrentData(data);
- self.timelapseViewModel.fromCurrentData(data);
- self.gcodeViewModel.fromCurrentData(data);
- self.gcodeFilesViewModel.fromCurrentData(data);
- });
- self._socket.on("updateTrigger", function(type) {
- if (type == "gcodeFiles") {
- gcodeFilesViewModel.requestData();
- } else if (type == "timelapseFiles") {
- timelapseViewModel.requestData();
- }
- });
- self._socket.on("feedbackCommandOutput", function(data) {
- self.controlViewModel.fromFeedbackCommandData(data);
- });
-
- self.reconnect = function() {
- self._socket.socket.connect();
- }
-}
-
-function ItemListHelper(listType, supportedSorting, supportedFilters, defaultSorting, defaultFilters, exclusiveFilters, filesPerPage) {
- var self = this;
-
- self.listType = listType;
- self.supportedSorting = supportedSorting;
- self.supportedFilters = supportedFilters;
- self.defaultSorting = defaultSorting;
- self.defaultFilters = defaultFilters;
- self.exclusiveFilters = exclusiveFilters;
-
- self.allItems = [];
-
- self.items = ko.observableArray([]);
- self.pageSize = ko.observable(filesPerPage);
- self.currentPage = ko.observable(0);
- self.currentSorting = ko.observable(self.defaultSorting);
- self.currentFilters = ko.observableArray(self.defaultFilters);
- self.selectedItem = ko.observable(undefined);
-
- //~~ item handling
-
- self.updateItems = function(items) {
- self.allItems = items;
- self._updateItems();
- }
-
- self.selectItem = function(matcher) {
- var itemList = self.items();
- for (var i = 0; i < itemList.length; i++) {
- if (matcher(itemList[i])) {
- self.selectedItem(itemList[i]);
- break;
- }
- }
- }
-
- self.selectNone = function() {
- self.selectedItem(undefined);
- }
-
- self.isSelected = function(data) {
- return self.selectedItem() == data;
- }
-
- self.isSelectedByMatcher = function(matcher) {
- return matcher(self.selectedItem());
- }
-
- //~~ pagination
-
- self.paginatedItems = ko.dependentObservable(function() {
- if (self.items() == undefined) {
- return [];
- } else {
- var from = Math.max(self.currentPage() * self.pageSize(), 0);
- var to = Math.min(from + self.pageSize(), self.items().length);
- return self.items().slice(from, to);
- }
- })
- self.lastPage = ko.dependentObservable(function() {
- return Math.ceil(self.items().length / self.pageSize()) - 1;
- })
- self.pages = ko.dependentObservable(function() {
- var pages = [];
- if (self.lastPage() < 7) {
- for (var i = 0; i < self.lastPage() + 1; i++) {
- pages.push({ number: i, text: i+1 });
- }
- } else {
- pages.push({ number: 0, text: 1 });
- if (self.currentPage() < 5) {
- for (var i = 1; i < 5; i++) {
- pages.push({ number: i, text: i+1 });
- }
- pages.push({ number: -1, text: "…"});
- } else if (self.currentPage() > self.lastPage() - 5) {
- pages.push({ number: -1, text: "…"});
- for (var i = self.lastPage() - 4; i < self.lastPage(); i++) {
- pages.push({ number: i, text: i+1 });
- }
- } else {
- pages.push({ number: -1, text: "…"});
- for (var i = self.currentPage() - 1; i <= self.currentPage() + 1; i++) {
- pages.push({ number: i, text: i+1 });
- }
- pages.push({ number: -1, text: "…"});
- }
- pages.push({ number: self.lastPage(), text: self.lastPage() + 1})
- }
- return pages;
- })
-
- self.switchToItem = function(matcher) {
- var pos = -1;
- var itemList = self.items();
- for (var i = 0; i < itemList.length; i++) {
- if (matcher(itemList[i])) {
- pos = i;
- break;
- }
- }
-
- if (pos > -1) {
- var page = Math.floor(pos / self.pageSize());
- self.changePage(page);
- }
- }
-
- self.changePage = function(newPage) {
- if (newPage < 0 || newPage > self.lastPage())
- return;
- self.currentPage(newPage);
- }
- self.prevPage = function() {
- if (self.currentPage() > 0) {
- self.currentPage(self.currentPage() - 1);
- }
- }
- self.nextPage = function() {
- if (self.currentPage() < self.lastPage()) {
- self.currentPage(self.currentPage() + 1);
- }
- }
-
- self.getItem = function(matcher) {
- var itemList = self.items();
- for (var i = 0; i < itemList.length; i++) {
- if (matcher(itemList[i])) {
- return itemList[i];
- }
- }
-
- return undefined;
- }
-
- //~~ sorting
-
- self.changeSorting = function(sorting) {
- if (!_.contains(_.keys(self.supportedSorting), sorting))
- return;
-
- self.currentSorting(sorting);
- self._saveCurrentSortingToLocalStorage();
-
- self.changePage(0);
- self._updateItems();
- }
-
- //~~ filtering
-
- self.toggleFilter = function(filter) {
- if (!_.contains(_.keys(self.supportedFilters), filter))
- return;
-
- if (_.contains(self.currentFilters(), filter)) {
- self.removeFilter(filter);
- } else {
- self.addFilter(filter);
- }
- }
-
- self.addFilter = function(filter) {
- if (!_.contains(_.keys(self.supportedFilters), filter))
- return;
-
- for (var i = 0; i < self.exclusiveFilters.length; i++) {
- if (_.contains(self.exclusiveFilters[i], filter)) {
- for (var j = 0; j < self.exclusiveFilters[i].length; j++) {
- if (self.exclusiveFilters[i][j] == filter)
- continue;
- self.removeFilter(self.exclusiveFilters[i][j]);
- }
- }
- }
-
- var filters = self.currentFilters();
- filters.push(filter);
- self.currentFilters(filters);
- self._saveCurrentFiltersToLocalStorage();
-
- self.changePage(0);
- self._updateItems();
- }
-
- self.removeFilter = function(filter) {
- if (!_.contains(_.keys(self.supportedFilters), filter))
- return;
-
- var filters = self.currentFilters();
- filters.pop(filter);
- self.currentFilters(filters);
- self._saveCurrentFiltersToLocalStorage();
-
- self.changePage(0);
- self._updateItems();
- }
-
- //~~ update for sorted and filtered view
-
- self._updateItems = function() {
- // determine comparator
- var comparator = undefined;
- var currentSorting = self.currentSorting();
- if (typeof currentSorting !== undefined && typeof self.supportedSorting[currentSorting] !== undefined) {
- comparator = self.supportedSorting[currentSorting];
- }
-
- // work on all items
- var result = self.allItems;
-
- // filter if necessary
- var filters = self.currentFilters();
- _.each(filters, function(filter) {
- if (typeof filter !== undefined && typeof supportedFilters[filter] !== undefined)
- result = _.filter(result, supportedFilters[filter]);
- });
-
- // sort if necessary
- if (typeof comparator !== undefined)
- result.sort(comparator);
-
- // set result list
- self.items(result);
- }
-
- //~~ local storage
-
- self._saveCurrentSortingToLocalStorage = function() {
- if ( self._initializeLocalStorage() ) {
- var currentSorting = self.currentSorting();
- if (currentSorting !== undefined)
- localStorage[self.listType + "." + "currentSorting"] = currentSorting;
- else
- localStorage[self.listType + "." + "currentSorting"] = undefined;
- }
- }
-
- self._loadCurrentSortingFromLocalStorage = function() {
- if ( self._initializeLocalStorage() ) {
- if (_.contains(_.keys(supportedSorting), localStorage[self.listType + "." + "currentSorting"]))
- self.currentSorting(localStorage[self.listType + "." + "currentSorting"]);
- else
- self.currentSorting(defaultSorting);
- }
- }
-
- self._saveCurrentFiltersToLocalStorage = function() {
- if ( self._initializeLocalStorage() ) {
- var filters = _.intersection(_.keys(self.supportedFilters), self.currentFilters());
- localStorage[self.listType + "." + "currentFilters"] = JSON.stringify(filters);
- }
- }
-
- self._loadCurrentFiltersFromLocalStorage = function() {
- if ( self._initializeLocalStorage() ) {
- self.currentFilters(_.intersection(_.keys(self.supportedFilters), JSON.parse(localStorage[self.listType + "." + "currentFilters"])));
- }
- }
-
- self._initializeLocalStorage = function() {
- if (!Modernizr.localstorage)
- return false;
-
- if (localStorage[self.listType + "." + "currentSorting"] !== undefined && localStorage[self.listType + "." + "currentFilters"] !== undefined && JSON.parse(localStorage[self.listType + "." + "currentFilters"]) instanceof Array)
- return true;
-
- localStorage[self.listType + "." + "currentSorting"] = self.defaultSorting;
- localStorage[self.listType + "." + "currentFilters"] = JSON.stringify(self.defaultFilters);
-
- return true;
- }
-
- self._loadCurrentFiltersFromLocalStorage();
- self._loadCurrentSortingFromLocalStorage();
-}
-
-function AppearanceViewModel(settingsViewModel) {
- var self = this;
-
- self.name = settingsViewModel.appearance_name;
- self.color = settingsViewModel.appearance_color;
-
- self.brand = ko.computed(function() {
- if (self.name())
- return "OctoPrint: " + self.name();
- else
- return "OctoPrint";
- })
-
- self.title = ko.computed(function() {
- if (self.name())
- return self.name() + " [OctoPrint]";
- else
- return "OctoPrint";
- })
-}
-
-function FirstRunViewModel() {
- var self = this;
-
- self.username = ko.observable(undefined);
- self.password = ko.observable(undefined);
- self.confirmedPassword = ko.observable(undefined);
-
- self.passwordMismatch = ko.computed(function() {
- return self.password() != self.confirmedPassword();
- });
-
- self.validUsername = ko.computed(function() {
- return self.username() && self.username().trim() != "";
- });
-
- self.validPassword = ko.computed(function() {
- return self.password() && self.password().trim() != "";
- });
-
- self.validData = ko.computed(function() {
- return !self.passwordMismatch() && self.validUsername() && self.validPassword();
- });
-
- self.keepAccessControl = function() {
- if (!self.validData()) return;
-
- var data = {
- "ac": true,
- "user": self.username(),
- "pass1": self.password(),
- "pass2": self.confirmedPassword()
- };
- self._sendData(data);
- };
-
- self.disableAccessControl = function() {
- $("#confirmation_dialog .confirmation_dialog_message").html("If you disable Access Control and your OctoPrint " +
- "installation is accessible from the internet, your printer will be accessible by everyone - " +
- "that also includes the bad guys!");
- $("#confirmation_dialog .confirmation_dialog_acknowledge").click(function(e) {
- e.preventDefault();
- $("#confirmation_dialog").modal("hide");
-
- var data = {
- "ac": false
- };
- self._sendData(data, function() {
- // if the user indeed disables access control, we'll need to reload the page for this to take effect
- location.reload();
- });
- });
- $("#confirmation_dialog").modal("show");
- };
-
- self._sendData = function(data, callback) {
- $.ajax({
- url: AJAX_BASEURL + "setup",
- type: "POST",
- dataType: "json",
- data: data,
- success: function() {
- self.closeDialog();
- if (callback) callback();
- }
- });
- }
-
- self.showDialog = function() {
- $("#first_run_dialog").modal("show");
- }
-
- self.closeDialog = function() {
- $("#first_run_dialog").modal("hide");
- }
-}
-
-$(function() {
-
- //~~ View models
- var loginStateViewModel = new LoginStateViewModel();
- var usersViewModel = new UsersViewModel(loginStateViewModel);
- var settingsViewModel = new SettingsViewModel(loginStateViewModel, usersViewModel);
- var connectionViewModel = new ConnectionViewModel(loginStateViewModel, settingsViewModel);
- var printerStateViewModel = new PrinterStateViewModel(loginStateViewModel);
- var appearanceViewModel = new AppearanceViewModel(settingsViewModel);
- var temperatureViewModel = new TemperatureViewModel(loginStateViewModel, settingsViewModel);
- var controlViewModel = new ControlViewModel(loginStateViewModel, settingsViewModel);
- var terminalViewModel = new TerminalViewModel(loginStateViewModel);
- var gcodeFilesViewModel = new GcodeFilesViewModel(printerStateViewModel, loginStateViewModel);
- var timelapseViewModel = new TimelapseViewModel(loginStateViewModel);
- var gcodeViewModel = new GcodeViewModel(loginStateViewModel);
- var navigationViewModel = new NavigationViewModel(loginStateViewModel, appearanceViewModel, settingsViewModel, usersViewModel);
-
- var dataUpdater = new DataUpdater(
- loginStateViewModel,
- connectionViewModel,
- printerStateViewModel,
- temperatureViewModel,
- controlViewModel,
- terminalViewModel,
- gcodeFilesViewModel,
- timelapseViewModel,
- gcodeViewModel
- );
-
- // work around a stupid iOS6 bug where ajax requests get cached and only work once, as described at
- // http://stackoverflow.com/questions/12506897/is-safari-on-ios-6-caching-ajax-results
- $.ajaxSetup({
- type: 'POST',
- headers: { "cache-control": "no-cache" }
- });
-
- //~~ Show settings - to ensure centered
- $('#navbar_show_settings').click(function() {
- $('#settings_dialog').modal()
- .css({
- width: 'auto',
- 'margin-left': function() { return -($(this).width() /2); }
- });
- return false;
- })
-
- //~~ Temperature control (should really move to knockout click binding)
-
- $("#temp_newTemp_set").click(function() {
- var newTemp = $("#temp_newTemp").val();
- $.ajax({
- url: AJAX_BASEURL + "control/temperature",
- type: "POST",
- dataType: "json",
- data: { temp: newTemp },
- success: function() {$("#temp_newTemp").val("")}
- })
- })
- $("#temp_newBedTemp_set").click(function() {
- var newBedTemp = $("#temp_newBedTemp").val();
- $.ajax({
- url: AJAX_BASEURL + "control/temperature",
- type: "POST",
- dataType: "json",
- data: { bedTemp: newBedTemp },
- success: function() {$("#temp_newBedTemp").val("")}
- })
- })
- $('#tabs a[data-toggle="tab"]').on('shown', function (e) {
- temperatureViewModel.updatePlot();
- terminalViewModel.updateOutput();
- });
-
- //~~ Terminal
-
- $("#terminal-send").click(function () {
- var command = $("#terminal-command").val();
-
- /*
- var re = /^([gm][0-9]+)(\s.*)?/;
- var commandMatch = command.match(re);
- if (commandMatch != null) {
- command = commandMatch[1].toUpperCase() + ((commandMatch[2] !== undefined) ? commandMatch[2] : "");
- }
- */
-
- if (command) {
- $.ajax({
- url: AJAX_BASEURL + "control/command",
- type: "POST",
- dataType: "json",
- contentType: "application/json; charset=UTF-8",
- data: JSON.stringify({"command": command})
- })
- $("#terminal-command").val('')
- }
-
- })
-
- $("#terminal-command").keyup(function (event) {
- if (event.keyCode == 13) {
- $("#terminal-send").click()
- }
- })
-
- //~~ Gcode upload
-
- function gcode_upload_done(e, data) {
- gcodeFilesViewModel.fromResponse(data.result);
- $("#gcode_upload_progress .bar").css("width", "0%");
- $("#gcode_upload_progress").removeClass("progress-striped").removeClass("active");
- $("#gcode_upload_progress .bar").text("");
- }
-
- function gcode_upload_progress(e, data) {
- var progress = parseInt(data.loaded / data.total * 100, 10);
- $("#gcode_upload_progress .bar").css("width", progress + "%");
- $("#gcode_upload_progress .bar").text("Uploading ...");
- if (progress >= 100) {
- $("#gcode_upload_progress").addClass("progress-striped").addClass("active");
- $("#gcode_upload_progress .bar").text("Saving ...");
- }
- }
-
- var localTarget;
- if (CONFIG_SD_SUPPORT) {
- localTarget = $("#drop_locally");
- } else {
- localTarget = $("#drop");
- }
-
- $("#gcode_upload").fileupload({
- dataType: "json",
- dropZone: localTarget,
- formData: {target: "local"},
- done: gcode_upload_done,
- progressall: gcode_upload_progress
- });
-
- if (CONFIG_SD_SUPPORT) {
- $("#gcode_upload_sd").fileupload({
- dataType: "json",
- dropZone: $("#drop_sd"),
- formData: {target: "sd"},
- done: gcode_upload_done,
- progressall: gcode_upload_progress
- });
- }
-
- $(document).bind("dragover", function (e) {
- var dropOverlay = $("#drop_overlay");
- var dropZone = $("#drop");
- var dropZoneLocal = $("#drop_locally");
- var dropZoneSd = $("#drop_sd");
- var dropZoneBackground = $("#drop_background");
- var dropZoneLocalBackground = $("#drop_locally_background");
- var dropZoneSdBackground = $("#drop_sd_background");
- var timeout = window.dropZoneTimeout;
-
- if (!timeout) {
- dropOverlay.addClass('in');
- } else {
- clearTimeout(timeout);
- }
-
- var foundLocal = false;
- var foundSd = false;
- var found = false
- var node = e.target;
- do {
- if (dropZoneLocal && node === dropZoneLocal[0]) {
- foundLocal = true;
- break;
- } else if (dropZoneSd && node === dropZoneSd[0]) {
- foundSd = true;
- break;
- } else if (dropZone && node === dropZone[0]) {
- found = true;
- break;
- }
- node = node.parentNode;
- } while (node != null);
-
- if (foundLocal) {
- dropZoneLocalBackground.addClass("hover");
- dropZoneSdBackground.removeClass("hover");
- } else if (foundSd) {
- dropZoneSdBackground.addClass("hover");
- dropZoneLocalBackground.removeClass("hover");
- } else if (found) {
- dropZoneBackground.addClass("hover");
- } else {
- if (dropZoneLocalBackground) dropZoneLocalBackground.removeClass("hover");
- if (dropZoneSdBackground) dropZoneSdBackground.removeClass("hover");
- if (dropZoneBackground) dropZoneBackground.removeClass("hover");
- }
-
- window.dropZoneTimeout = setTimeout(function () {
- window.dropZoneTimeout = null;
- dropOverlay.removeClass("in");
- if (dropZoneLocal) dropZoneLocalBackground.removeClass("hover");
- if (dropZoneSd) dropZoneSdBackground.removeClass("hover");
- if (dropZone) dropZoneBackground.removeClass("hover");
- }, 100);
- });
-
- //~~ Offline overlay
- $("#offline_overlay_reconnect").click(function() {dataUpdater.reconnect()});
-
- //~~ knockout.js bindings
-
- ko.bindingHandlers.popover = {
- init: function(element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {
- var val = ko.utils.unwrapObservable(valueAccessor());
-
- var options = {
- title: val.title,
- animation: val.animation,
- placement: val.placement,
- trigger: val.trigger,
- delay: val.delay,
- content: val.content,
- html: val.html
- };
- $(element).popover(options);
- }
- }
-
- ko.applyBindings(connectionViewModel, document.getElementById("connection_accordion"));
- ko.applyBindings(printerStateViewModel, document.getElementById("state_accordion"));
- ko.applyBindings(gcodeFilesViewModel, document.getElementById("files_accordion"));
- ko.applyBindings(temperatureViewModel, document.getElementById("temp"));
- ko.applyBindings(controlViewModel, document.getElementById("control"));
- ko.applyBindings(terminalViewModel, document.getElementById("term"));
- ko.applyBindings(gcodeViewModel, document.getElementById("gcode"));
- ko.applyBindings(settingsViewModel, document.getElementById("settings_dialog"));
- ko.applyBindings(navigationViewModel, document.getElementById("navbar"));
- ko.applyBindings(appearanceViewModel, document.getElementsByTagName("head")[0]);
-
- var timelapseElement = document.getElementById("timelapse");
- if (timelapseElement) {
- ko.applyBindings(timelapseViewModel, timelapseElement);
- }
- var gCodeVisualizerElement = document.getElementById("gcode");
- if (gCodeVisualizerElement) {
- gcodeViewModel.initialize();
- }
-
- //~~ startup commands
-
- loginStateViewModel.requestData();
- connectionViewModel.requestData();
- controlViewModel.requestData();
- gcodeFilesViewModel.requestData();
- timelapseViewModel.requestData();
-
- loginStateViewModel.subscribe(function(change, data) {
- if ("login" == change) {
- $("#gcode_upload").fileupload("enable");
-
- settingsViewModel.requestData();
- if (data.admin) {
- usersViewModel.requestData();
- }
- } else {
- $("#gcode_upload").fileupload("disable");
- }
- });
-
- //~~ UI stuff
-
- $(".accordion-toggle[href='#files']").click(function() {
- if ($("#files").hasClass("in")) {
- $("#files").removeClass("overflow_visible");
- } else {
- setTimeout(function() {
- $("#files").addClass("overflow_visible");
- }, 1000);
- }
- })
-
- $.pnotify.defaults.history = false;
-
- $.fn.modal.defaults.maxHeight = function(){
- // subtract the height of the modal header and footer
- return $(window).height() - 165;
- }
-
- // Fix input element click problem on login dialog
- $(".dropdown input, .dropdown label").click(function(e) {
- e.stopPropagation();
- });
-
- $(document).bind("drop dragover", function (e) {
- e.preventDefault();
- });
-
- if (CONFIG_FIRST_RUN) {
- var firstRunViewModel = new FirstRunViewModel();
- ko.applyBindings(firstRunViewModel, document.getElementById("first_run_dialog"));
- firstRunViewModel.showDialog();
- }
- }
-);
-
diff --git a/octoprint/templates/index.jinja2 b/octoprint/templates/index.jinja2
index ec0682b8..2bc71d28 100644
--- a/octoprint/templates/index.jinja2
+++ b/octoprint/templates/index.jinja2
@@ -30,7 +30,7 @@
var WEB_SOCKET_SWF_LOCATION = "{{ url_for('static', filename='js/socket.io/WebSocketMain.swf') }}";
var WEB_SOCKET_DEBUG = true;
-
+
@@ -598,24 +598,46 @@
{% include 'settings.jinja2' %}
{% include 'dialogs.jinja2' %}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+