diff --git a/octoprint_customControl/__init__.py b/octoprint_customControl/__init__.py new file mode 100644 index 0000000..5f2eb5e --- /dev/null +++ b/octoprint_customControl/__init__.py @@ -0,0 +1,26 @@ +# coding=utf-8 +from __future__ import absolute_import + +__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" + +import octoprint.plugin + +class CustomControlPlugin(octoprint.plugin.SettingsPlugin, + octoprint.plugin.TemplatePlugin, + octoprint.plugin.AssetPlugin): + def get_settings_defaults(self): + return dict(); + + def get_assets(self): + return { + "js": ["js/customControl.js"], + } + +# If you want your plugin to be registered within OctoPrint under a different name than what you defined in setup.py +# ("OctoPrint-PluginSkeleton"), you may define that here. Same goes for the other metadata derived from setup.py that +# can be overwritten via __plugin_xyz__ control properties. See the documentation for that. +__plugin_name__ = "CustomControl" +__plugin_license__ = "AGPLv3" +__plugin_implementation__ = CustomControlPlugin() diff --git a/octoprint_customControl/static/js/customControl.js b/octoprint_customControl/static/js/customControl.js new file mode 100644 index 0000000..16fdfe3 --- /dev/null +++ b/octoprint_customControl/static/js/customControl.js @@ -0,0 +1,228 @@ +$(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.controls = ko.observableArray([]); + + self.controlsFromServer = []; + self.additionalControls = []; + + self.staticID = 0; + + self.onBeforeBinding = function () { + self.settings = self.settingsViewModel.settings; + self.rerenderControls(); + }; + + self.onAllBound = function (allViewModels) { + var additionalControls = []; + _.each(allViewModels, function (viewModel) { + if (viewModel.hasOwnProperty("getAdditionalControls")) { + additionalControls = additionalControls.concat(viewModel.getAdditionalControls()); + } + }); + if (additionalControls.length > 0) { + self.additionalControls = additionalControls; + self.rerenderControls(); + } + }; + + self.rerenderControls = function () { + self.staticID = 0; + + var allControls = self.controlsFromServer.concat(self.additionalControls); + self.controls(self._processControls(allControls)) + }; + + 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.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.hasOwnProperty("layout") || !(control.layout == "vertical" || control.layout == "horizontal")) { + control.layout = "vertical"; + } + } + + 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; + } + } + } + + var js; + if (control.hasOwnProperty("javascript")) { + 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)) { + 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)) { + control.enabled = function (data) { + return eval(js); + } + } + } + + control.id = "settingsCustomControl_id" + self.staticID++; + control.processed = true; + return control; + }; + + self.displayMode = function (customControl) { + if (customControl.hasOwnProperty("children")) { + return "settingsCustomControls_containerTemplate"; + } else { + return "settingsCustomControls_controlTemplate"; + } + } + + self.searchElement = function (list, id) { + for (var i = 0; i < list.length; i++) + { + if (list[i].id == id) + return list[i]; + + if (list[i].hasOwnProperty("children")) { + var element = self.searchElement(list[i].children(), id); + if (element != undefined) + return element; + } + } + + return undefined; + } + + self.controlContextMenu = function (invokedOn, 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(); + } + 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"); + return; + } + + // TODO: make a create dialog + parentElement.children.push(self._processControl({ children: [], id: "settingsCustomControl_id" + self.staticID++ })); + } + } + } + + self.editStyle = function (type) { + } + + } + + // view model class, parameters for constructor, container to bind to + OCTOPRINT_VIEWMODELS.push([ + CustomControlViewModel, + ["loginStateViewModel", "settingsViewModel", "controlViewModel"], + "#settings_plugin_customControl" + ]); +}); \ No newline at end of file diff --git a/octoprint_customControl/templates/customControl.jinja2 b/octoprint_customControl/templates/customControl.jinja2 new file mode 100644 index 0000000..a5eeeaa --- /dev/null +++ b/octoprint_customControl/templates/customControl.jinja2 @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/octoprint_customControl/templates/customControl_settings.jinja2 b/octoprint_customControl/templates/customControl_settings.jinja2 new file mode 100644 index 0000000..011ece9 --- /dev/null +++ b/octoprint_customControl/templates/customControl_settings.jinja2 @@ -0,0 +1,110 @@ +

{{ _('Control') }}

+ +
+
+ + + + + + + + + + + + + + \ No newline at end of file