diff --git a/babel.cfg b/babel.cfg new file mode 100644 index 0000000..b6f5945 --- /dev/null +++ b/babel.cfg @@ -0,0 +1,6 @@ +[python: */**.py] +[jinja2: */**.jinja2] +extensions=jinja2.ext.autoescape, jinja2.ext.with_ + +[javascript: */**.js] +extract_messages = gettext, ngettext diff --git a/octoprint_customControl/__init__.py b/octoprint_customControl/__init__.py index 5f2eb5e..4656358 100644 --- a/octoprint_customControl/__init__.py +++ b/octoprint_customControl/__init__.py @@ -5,17 +5,30 @@ __author__ = "Marc Hannappel " __license__ = 'GNU Affero General Public License http://www.gnu.org/licenses/agpl.html' __copyright__ = "Copyright (C) 2014 The OctoPrint Project - Released under terms of the AGPLv3 License" +from octoprint.settings import settings + import octoprint.plugin class CustomControlPlugin(octoprint.plugin.SettingsPlugin, octoprint.plugin.TemplatePlugin, octoprint.plugin.AssetPlugin): + def get_settings_defaults(self): - return dict(); + return dict( + controls = [] + ) + + def on_settings_save(self, data): + s = settings() + s.set(["controls"], data["controls"]) def get_assets(self): return { - "js": ["js/customControl.js"], + "js": [ + "js/customControl.js", + "js/containerDialog.js", + ], + "css": ["css/customControls.css"] } # If you want your plugin to be registered within OctoPrint under a different name than what you defined in setup.py diff --git a/octoprint_customControl/static/css/customControls.css b/octoprint_customControl/static/css/customControls.css new file mode 100644 index 0000000..a6a57c2 --- /dev/null +++ b/octoprint_customControl/static/css/customControls.css @@ -0,0 +1,3 @@ +#customControls .custom_section_horizontal{display:inline-block} +#customControls .custom_section_vertical_section{min-width:15px; min-height:15px; margin: 8px 8px; padding: 8px 8px; border:1px dashed #000000} +#customControls .custom_section_horizontal_section{float:left; min-width:15px; min-height:15px; margin: 8px 8px; padding: 8px 8px; border:1px dashed #000000} \ No newline at end of file diff --git a/octoprint_customControl/static/js/containerDialog.js b/octoprint_customControl/static/js/containerDialog.js new file mode 100644 index 0000000..1cbd871 --- /dev/null +++ b/octoprint_customControl/static/js/containerDialog.js @@ -0,0 +1,33 @@ +$(function () { + function ContainerDialogViewModel(parameters) { + var self = this; + + self.element = ko.observable(); + + self.layouts = ko.observableArray([ + { name: gettext("Vertical"), key: "vertical" }, + { name: gettext("Horizontal"), key: "horizontal" }, + { name: gettext("Horizontal grid"), key: "horizontal_grid" } + ]); + + self.show = function (f) { + var dialog = $("#containerDialog"); + var primarybtn = $('.btn-primary', dialog); + + primarybtn.unbind('click').bind('click', f); + + dialog.modal({ + show: 'true', + backdrop: 'static', + keyboard: false + }); + } + } + + // view model class, parameters for constructor, container to bind to + OCTOPRINT_VIEWMODELS.push([ + ContainerDialogViewModel, + [], + "#containerDialog" + ]); +}); \ No newline at end of file diff --git a/octoprint_customControl/static/js/customControl.js b/octoprint_customControl/static/js/customControl.js index 16fdfe3..7ece91a 100644 --- a/octoprint_customControl/static/js/customControl.js +++ b/octoprint_customControl/static/js/customControl.js @@ -1,141 +1,109 @@ $(function () { - $.fn.isChildOf = function (element) { - return $(element).has(this).length > 0; - } - - // from http://jsfiddle.net/KyleMit/X9tgY/ - $.fn.contextMenu = function (settings) { - return this.each(function () { - // Open context menu - $(this).on("contextmenu", function (e) { - // return native menu if pressing control - if (e.ctrlKey) return; - - $(settings.menuSelector) - .data("invokedOn", $(e.target)) - .show() - .css({ - position: "absolute", - left: getMenuPosition(e.clientX, 'width', 'scrollLeft'), - top: getMenuPosition(e.clientY, 'height', 'scrollTop'), - "z-index": 9999 - }).off('click') - .on('click', function (e) { - if (e.target.tagName.toLowerCase() == "input") - return; - - $(this).hide(); - - var $invokedOn = $(this).data("invokedOn"); - var $selectedMenu = $(e.target); - - settings.menuSelected.call(this, $invokedOn, $selectedMenu); - }); - - return false; - }); - - //make sure menu closes on any click - $(document).click(function () { - $(settings.menuSelector).hide(); - }); - }); - - function getMenuPosition(mouse, direction, scrollDir) { - var win = $(window)[direction](), - scroll = $(window)[scrollDir](), - menu = $(settings.menuSelector)[direction](), - position = mouse + scroll; - - // opening menu would pass the side of the page - if (mouse + menu > win && menu < mouse) - position -= menu; - - return position; - } - }; - - ko.bindingHandlers.contextMenu = { - init: function (element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) { - var val = ko.utils.unwrapObservable(valueAccessor()); - - $(element).contextMenu(val); - }, - update: function (element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) { - var val = ko.utils.unwrapObservable(valueAccessor()); - - $(element).contextMenu(val); - } - } - function CustomControlViewModel(parameters) { var self = this; self.loginState = parameters[0]; self.settingsViewModel = parameters[1]; self.controlViewModel = parameters[2]; + self.containerDialogViewModel = parameters[3]; + + self.popup = undefined; self.controls = ko.observableArray([]); self.controlsFromServer = []; self.additionalControls = []; - self.staticID = 0; - - self.onBeforeBinding = function () { - self.settings = self.settingsViewModel.settings; - self.rerenderControls(); + self.staticID = 0; + + self._showPopup = function (options, eventListeners) { + if (self.popup !== undefined) { + self.popup.remove(); + } + self.popup = new PNotify(options); + + if (eventListeners) { + var popupObj = self.popup.get(); + _.each(eventListeners, function (value, key) { + popupObj.on(key, value); + }) + } }; - self.onAllBound = function (allViewModels) { - var additionalControls = []; - _.each(allViewModels, function (viewModel) { - if (viewModel.hasOwnProperty("getAdditionalControls")) { - additionalControls = additionalControls.concat(viewModel.getAdditionalControls()); + self.onStartup = function () { + self.requestData(); + }; + + self.requestData = function () { + $.ajax({ + url: API_BASEURL + "printer/command/custom", + method: "GET", + dataType: "json", + success: function (response) { + self._fromResponse(response); } }); - if (additionalControls.length > 0) { - self.additionalControls = additionalControls; - self.rerenderControls(); - } + }; + + self._fromResponse = function (response) { + self.controlsFromServer = response.controls; + self.rerenderControls(); }; self.rerenderControls = function () { self.staticID = 0; - - var allControls = self.controlsFromServer.concat(self.additionalControls); - self.controls(self._processControls(allControls)) + self.controls(self._processControls(undefined, self.controlsFromServer)) }; - self._processControls = function (controls) { + self._processControls = function (parent, controls) { for (var i = 0; i < controls.length; i++) { - controls[i] = self._processControl(controls[i]); + controls[i] = self._processControl(parent, controls[i]); } return controls; }; - self._processControl = function (control) { - if (control.hasOwnProperty("template") && control.hasOwnProperty("key") && control.hasOwnProperty("template_key") && !control.hasOwnProperty("output")) { + self._processControl = function (parent, control) { + control.id = ko.observable("settingsCustomControl_id" + self.staticID++); + control.parent = parent; + + if (control.hasOwnProperty("template") && control.hasOwnProperty("key") && control.hasOwnProperty("template_key") && !control.hasOwnProperty("output")) control.output = ko.observable(""); - } if (control.hasOwnProperty("children")) { - if (control.processed) - control.children = ko.observableArray(self._processControls(control.children())); - else - control.children = ko.observableArray(self._processControls(control.children)); + if (control.processed) { + control.children(self._processControls(control, control.children())); + if (control.hasOwnProperty("layout") && !(control.layout() == "vertical" || control.layout() == "horizontal" || control.layout() == "horizontal_grid")) + control.layout("vertical"); + else if (!control.hasOwnProperty("layout")) + control.layout = ko.observable("vertical"); + } + else { + control.name = ko.observable(control.name); + control.children = ko.observableArray(self._processControls(control, control.children)); + if (!control.hasOwnProperty("layout") || !(control.layout == "vertical" || control.layout == "horizontal" || control.layout == "horizontal_grid")) + control.layout = ko.observable("vertical"); + else + control.layout = ko.observable(control.layout); - if (!control.hasOwnProperty("layout") || !(control.layout == "vertical" || control.layout == "horizontal")) { - control.layout = "vertical"; + control.width = ko.observable(control.hasOwnProperty("width") ? control.width : "2"); + control.offset = ko.observable(control.hasOwnProperty("offset") ? control.offset : ""); } } if (control.hasOwnProperty("input")) { for (var i = 0; i < control.input.length; i++) { - control.input[i].value = ko.observable(control.input[i].default); - if (!control.input[i].hasOwnProperty("slider")) { - control.input[i].slider = false; + if (!control.processed) { + control.input[i].value = ko.observable(control.input[i].default); + control.input[i].default = ko.observable(control.input[i].default); } + + if (control.processed) + control.input[i].value(control.input[i].default()); + + if (!control.input[i].hasOwnProperty("slider")) + control.input[i].slider = ko.observable(false); + else if (!control.processed) + control.input[i].slider = ko.observable(control.input[i].slider); } } @@ -144,25 +112,24 @@ js = control.javascript; // if js is a function everything's fine already, but if it's a string we need to eval that first - if (!_.isFunction(js)) { + /*if (!_.isFunction(js)) { control.javascript = function (data) { eval(js); }; - } + }*/ } if (control.hasOwnProperty("enabled")) { js = control.enabled; // if js is a function everything's fine already, but if it's a string we need to eval that first - if (!_.isFunction(js)) { + /*if (!_.isFunction(js)) { control.enabled = function (data) { return eval(js); } - } + }*/ } - control.id = "settingsCustomControl_id" + self.staticID++; control.processed = true; return control; }; @@ -174,11 +141,23 @@ return "settingsCustomControls_controlTemplate"; } } + + self.rowCss = function (customControl) { + var span = "span2"; + var offset = ""; + if (customControl.hasOwnProperty("width") && customControl.width() != "") { + span = "span" + customControl.width(); + } + if (customControl.hasOwnProperty("offset") && customControl.offset() != "") { + offset = "offset" + customControl.offset(); + } + return span + " " + offset; + }; self.searchElement = function (list, id) { for (var i = 0; i < list.length; i++) { - if (list[i].id == id) + if (list[i].id() == id) return list[i]; if (list[i].hasOwnProperty("children")) { @@ -191,25 +170,114 @@ return undefined; } - self.controlContextMenu = function (invokedOn, selectedMenu) + self.controlContextMenu = function (invokedOn, contextParent, selectedMenu) { - if (selectedMenu.attr('cmd') == "createContainer") { - if (invokedOn.attr('id') == "base") { - // TODO: make a create dialog - self.controlsFromServer.push({ children: [], id: "settingsCustomControl_id" + self.staticID++ }); - self.rerenderControls(); + switch (selectedMenu.attr('cmd')) { + case "createContainer": { + if (invokedOn.attr('id') == "base") { + self.containerDialogViewModel.element({ + name: undefined, + children:[], + layout: "vertical", + width: "2", + offset: "" + }); + + self.containerDialogViewModel.show(function (e) { + self.controlsFromServer.push(self.containerDialogViewModel.element()); + self.rerenderControls(); + }); + } + else { + var parentElement = self.searchElement(self.controlsFromServer, contextParent.attr('id')); + if (parentElement == undefined) { + self._showPopup({ + title: gettext("Something went wrong while creating the new Element"), + type:"error" + }); + return; + } + + self.containerDialogViewModel.element({ + parent: parentElement, + name: undefined, + children: [], + layout: "vertical", + width: "2", + offset: "" + }); + + self.containerDialogViewModel.show(function (e) { + parentElement.children.push(self._processControl(parentElement, self.containerDialogViewModel.element())); + }); + } + break; } - else { - var parentElement = self.searchElement(self.controlsFromServer, invokedOn.attr('id')); - if (parentElement == undefined) - { - // TODO: make an Warning dialog - alert("Something went wrong while creating the new Element"); + case "editContainer": { + var element = self.searchElement(self.controlsFromServer, contextParent.attr('id')); + if (element == undefined) { + self._showPopup({ + title: gettext("Something went wrong while creating the new Element"), + type: "error" + }); return; } - // TODO: make a create dialog - parentElement.children.push(self._processControl({ children: [], id: "settingsCustomControl_id" + self.staticID++ })); + var dialog = $('#containerDialog'); + var primarybtn = $('.btn-primary', dialog); + + var el = { + parent: element.parent, + name: ko.observable(element.name()), + layout: ko.observable(element.layout()), + width: ko.observable("2"), + offset: ko.observable("") + }; + if (element.hasOwnProperty("width")) + el.width(element.width()); + if (element.hasOwnProperty("offset")) + el.offset(element.offset()); + + self.containerDialogViewModel.element(el); + primarybtn.unbind('click').bind('click', function (e) { + var ele = self.containerDialogViewModel.element(); + + element.name(ele.name()); + element.layout(ele.layout()); + if (ele.parent.layout() == "horizontal_grid") { + if (ele.width() != undefined) + element.width(ele.width()); + + if (ele.offset() != undefined) + element.offset(ele.offset()); + } + }); + + dialog.modal({ + show: 'true', + backdrop: 'static', + keyboard: false + }); + break; + } + case "deleteContainer": { + var element = self.searchElement(self.controlsFromServer, contextParent.attr('id')); + if (element == undefined) { + self._showPopup({ + title: gettext("Something went wrong while creating the new Element"), + type: "error" + }); + return; + } + + showConfirmationDialog("", function (e) { + if (element.parent != undefined) + element.parent.children.remove(element); + else { + self.controlsFromServer = _.without(self.controlsFromServer, element); + self.rerenderControls(); + } + }); } } } @@ -217,12 +285,36 @@ self.editStyle = function (type) { } + self.recursiveDeleteProperties = function (list) { + for (var i = 0; i < list.length; i++) { + if (list[i].parent && list[i].parent.hasOwnProperty("layout") && list[i].parent.layout() != "horizontal_grid") + { + delete list[i].width; + delete list[i].offset; + } + + delete list[i].id; + delete list[i].parent; + delete list[i].processed; + + if (list[i].hasOwnProperty("children")) + self.recursiveDeleteProperties(list[i].children()); + } + } + self.onSettingsBeforeSave = function () { + self.recursiveDeleteProperties(self.controlsFromServer); + self.settingsViewModel.settings.plugins.octoprint_customControl.controls = self.controlsFromServer; + } + self.onSettingsAfterSave = function () { + self.controlViewModel.requestData(); + self.requestData(); + } } // view model class, parameters for constructor, container to bind to OCTOPRINT_VIEWMODELS.push([ CustomControlViewModel, - ["loginStateViewModel", "settingsViewModel", "controlViewModel"], - "#settings_plugin_customControl" + ["loginStateViewModel", "settingsViewModel", "controlViewModel", "containerDialogViewModel"], + "#settings_plugin_octoprint_customControl" ]); }); \ No newline at end of file diff --git a/octoprint_customControl/templates/customControl.jinja2 b/octoprint_customControl/templates/customControl.jinja2 deleted file mode 100644 index a5eeeaa..0000000 --- a/octoprint_customControl/templates/customControl.jinja2 +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - \ No newline at end of file diff --git a/octoprint_customControl/templates/octoprint_customControl.jinja2 b/octoprint_customControl/templates/octoprint_customControl.jinja2 new file mode 100644 index 0000000..37480a9 --- /dev/null +++ b/octoprint_customControl/templates/octoprint_customControl.jinja2 @@ -0,0 +1,39 @@ + \ No newline at end of file diff --git a/octoprint_customControl/templates/customControl_settings.jinja2 b/octoprint_customControl/templates/octoprint_customControl_settings.jinja2 similarity index 70% rename from octoprint_customControl/templates/customControl_settings.jinja2 rename to octoprint_customControl/templates/octoprint_customControl_settings.jinja2 index 011ece9..ad9e9a5 100644 --- a/octoprint_customControl/templates/customControl_settings.jinja2 +++ b/octoprint_customControl/templates/octoprint_customControl_settings.jinja2 @@ -26,14 +26,6 @@
  • Danger
  • - @@ -46,35 +38,26 @@
  • -
  • Edit Container
  • -
  • Delete Container
  • -
  • - +
  • Edit Container
  • +
  • Delete Container