diff --git a/docs/images/template-plugin-types-main.png b/docs/images/template-plugin-types-main.png new file mode 100644 index 00000000..3b4267fa Binary files /dev/null and b/docs/images/template-plugin-types-main.png differ diff --git a/docs/images/template-plugin-types-settings.png b/docs/images/template-plugin-types-settings.png new file mode 100644 index 00000000..2ad2b5dd Binary files /dev/null and b/docs/images/template-plugin-types-settings.png differ diff --git a/src/octoprint/plugin/core.py b/src/octoprint/plugin/core.py index e647052c..1fb70c5b 100644 --- a/src/octoprint/plugin/core.py +++ b/src/octoprint/plugin/core.py @@ -37,6 +37,9 @@ class PluginInfo(object): self._version = version + if self.name is None: + raise ValueError("Plugin {key} doesn't have a name".format(key=key)) + def __str__(self): return "{name} ({version})".format(name=self.name, version=self.version if self.version else "unknown") @@ -109,7 +112,8 @@ class PluginManager(object): self.plugins = dict() self.plugin_hooks = defaultdict(list) - self.plugin_implementations = defaultdict(list) + self.plugin_implementations = defaultdict(set) + self.plugin_implementations_by_type = defaultdict(list) self.registered_clients = [] @@ -223,10 +227,31 @@ class PluginManager(object): # evaluate registered implementations for plugin_type in self.plugin_types: implementations = plugin.get_implementations(plugin_type) - self.plugin_implementations[plugin_type] += ( (name, implementation) for implementation in implementations ) + self.plugin_implementations_by_type[plugin_type] += ( (name, implementation) for implementation in implementations ) + self.plugin_implementations[name].update(plugin.get_implementations()) self.log_registered_plugins() + def initialize_implementations(self, additional_injects=None): + if additional_injects is None: + additional_injects = dict() + + for name, implementations in self.plugin_implementations.items(): + plugin = self.plugins[name] + for implementation in implementations: + kwargs = dict(additional_injects) + kwargs.update(dict( + identifier=name, + basefolder=os.path.realpath(plugin.location), + logger=logging.getLogger("octoprint.plugins." + name), + )) + try: + implementation.pre_initialize(**kwargs) + except: + self.logger.exception("Exception while initializing plugin") + + self.logger.info("Initialized {count} plugin(s)".format(count=len(self.plugin_implementations))) + def log_registered_plugins(self): if len(self.plugins) <= 0: self.logger.info("No plugins found") @@ -247,7 +272,7 @@ class PluginManager(object): result = None for t in types: - implementations = self.plugin_implementations[t] + implementations = self.plugin_implementations_by_type[t] if result is None: result = set(implementations) else: @@ -283,4 +308,10 @@ class PluginManager(object): class Plugin(object): - pass + def pre_initialize(self, **kwargs): + for arg, value in kwargs.items(): + setattr(self, "_" + arg, value) + self.initialize() + + def initialize(self): + pass diff --git a/src/octoprint/plugin/types.py b/src/octoprint/plugin/types.py index ba79106e..af246577 100644 --- a/src/octoprint/plugin/types.py +++ b/src/octoprint/plugin/types.py @@ -63,20 +63,13 @@ class AssetPlugin(Plugin): def get_asset_folder(self): """ - Defines the folder where the plugin stores its static assets as defined in ``get_assets``. Usually an - implementation such as - - .. code-block:: python - - def get_asset_folder(self): - import os - return os.path.join(os.path.dirname(os.path.realpath(__file__)), "static") - - should be sufficient here. This way, assets can be put into a sub folder ``static`` in the plugin directory. + Defines the folder where the plugin stores its static assets as defined in ``get_assets``. Override this if + your plugin stores its assets at some other place than the ``static`` sub folder in the plugin base directory. :return: the absolute path to the folder where the plugin stores its static assets """ - return None + import os + return os.path.join(self._basefolder, "static") def get_assets(self): """ @@ -108,15 +101,168 @@ class AssetPlugin(Plugin): :return: a dictionary describing the static assets to publish for the plugin """ - return [] + return dict() class TemplatePlugin(Plugin): + """ + Using the ``TemplatePlugin`` mixin plugins may inject their own components into the OctoPrint web interface. + + Currently OctoPrint supports the following types of injections: + + Navbar + The right part of the navigation bar located at the top of the UI can be enriched with additional links. Note that + with the current implementation, plugins will always be located *to the left* of the existing links. + Sidebar + The left side bar containing Connection, State and Files sections can be enriched with additional sections. Note + that with the current implementations, plugins will always be located *beneath* the existing sections. + Tabs + The available tabs of the main part of the interface may be extended with additional tabs originating from within + plugins. Note that with the current implementation, plugins will always be located *to the right* of the existing + tabs. + Settings + Plugins may inject a dialog into the existing settings view. Note that with the current implementations, plugins + will always be listed beneath the "Plugins" header in the settings link list, ordered alphabetically after + their displayed name. + + .. figure:: ../images/template-plugin-types-main.png + :align: center + :alt: Template injection types in the main part of the interface + + Template injection types in the main part of the interface + + .. figure:: ../images/template-plugin-types-settings.png + :align: center + :alt: Template injection types in the settings + + Template injection types in the settings + + Which components a plugin supplies is controlled by a number of special template variables returned by the overridden + ``get_template_vars`` method. The following special keys are supported: + + _settings + Configures the settings component to inject. The value must be a dictionary, supported values are the following: + + name + The name under which to include the settings pane, will be visible in the navigation tree on the left of the + settings dialog + custom_bindings + A boolean value indicating whether the default settings view model should be bound to the settings pane (``false``, default) + or if a custom binding will be used by the plugin (``true``) + data_bind + Additional knockout data bindings to apply to the template container, can be used to add further behaviour to + the container based on internal state if necessary. Note that if you include this and set ``custom_bindings`` + to ``True``, you need to also supply ``allowBindings: true`` as part of your custom data binding, otherwise + it won't work. + + The included settings dialog template file must be called ``_settings.jinja2`` (e.g. ``myplugin_settings.jinja2``). + The template will be already included in the necessary wrapper divs, you just need to supply the pure content. + _tab + Configures the tab component to inject. The value must be a dictionary, supported values are the following: + + name + The name under which to include the tab, will be visible in the list of tabs on the top of the main view + data_bind + Additional knockout data bindings to apply to the template container, can be used to add further behaviour to + the container based on internal state if necessary. Note that if you include this and set ``custom_bindings`` + to ``True``, you need to also supply ``allowBindings: true`` as part of your custom data binding, otherwise + it won't work. + + The template included into the tab pane section must be called ``_tab.jinja2`` (e.g. ``myplugin_tab.jinja2``). + The template will be already included in the necessary wrapper divs, you just need to supply the pure content. + _sidebar + Configures the sidebar component to inject. The value must be a dictionary, supported values are the following: + + name + The name of the sidebar entry + icon + Icon to use for the sidebar header, should be the name of a Font Awesome icon without the leading ``icon-`` part + header_addon + Additional template to include in the head section of the sidebar item. For an example of this, see the additional + options included in the "Files" section + data_bind + Additional knockout data bindings to apply to the template container, can be used to add further behaviour to + the container based on internal state if necessary. Note that if you include this and set ``custom_bindings`` + to ``True``, you need to also supply ``allowBindings: true`` as part of your custom data binding, otherwise + it won't work. + + The template included into the sidebar section must be called ``_sidebar.jinja2`` (e.g. ``myplugin_sidebar.jinja2``). + The template will be already included in the necessary wrapper divs, you just need to supply the pure content. + _navbar + Configures the navbar component to inject. The value must be an empty dictionary. + + The template included into the sidebar section must be called ``_navbar.jinja2`` + + The following is an example for a simple plugin which injects all four types of templates into the interfaces: + + ``helloworld/__init__.py`` + + .. code-block:: python + + import octoprint.plugin + + class HelloWorldPlugin(octoprint.plugin.TemplatePlugin, octoprint.plugin.AssetPlugin): + def get_template_vars(self): + return dict( + _settings=dict(name="Hello World"), + _tab=dict(name="Hello World", custom_bindings=True, data_bind="allowBindings: true, visible: self.loginState.isUser()"), + _sidebar=dict(name="Hello World", icon="gear"), + _navbar=dict() + ) + + def get_assets(self): + return dict( + js=["js/helloworld.js"] + ) + + __plugin_name__ = "Hello World" + __plugin_version__ = "1.0" + __plugin_implementations__ = [HelloWorldPlugin()] + + ``helloworld/templates/helloworld_settings.jinja2`` + + .. code-block:: html+jinja + +

Hello World!

+ + This is a custom settings pane. + + ``helloworld/templates/helloworld_tab.jinja2`` + + .. code-block:: html+jinja + +

Hello World!

+ + This is a custom tab. + + ``helloworld/templates/helloworld_sidebar.jinja2`` + + .. code-block:: html+jinja + + Hello World! This is a custom side bar. + + ``helloworld/templates/helloworld_navbar.jinja2`` + + .. code-block:: html+jinja + +
  • + {{ _('Hello World!') }} +
  • + + """ + def get_template_vars(self): return dict() def get_template_folder(self): - return None + """ + Defines the folder where the plugin stores its templates. Override this if your plugin stores its templates at + some other place than the ``templates`` sub folder in the plugin base directory. + + :return: the absolute path to the folder where the plugin stores its jinja2 templates + """ + import os + return os.path.join(self._basefolder, "templates") class SimpleApiPlugin(Plugin): diff --git a/src/octoprint/server/__init__.py b/src/octoprint/server/__init__.py index 962c9c3f..fee5149b 100644 --- a/src/octoprint/server/__init__.py +++ b/src/octoprint/server/__init__.py @@ -111,41 +111,78 @@ def index(): template_plugins = pluginManager.get_implementations(octoprint.plugin.TemplatePlugin) plugin_vars = dict() - plugin_includes = dict() + plugin_includes_navbar = [] + plugin_includes_sidebar = [] + plugin_includes_tabs = [] + plugin_includes_settings = [] + plugin_names = template_plugins.keys() for name, implementation in template_plugins.items(): vars = implementation.get_template_vars() + if not isinstance(vars, dict): + continue - plugin_data = dict() + if "_navbar" in vars and isinstance(vars["_navbar"], dict): + data = dict(vars["_navbar"]) + data["_div"] = "navbar_plugin_" + name + data["_template"] = name + "_navbar.jinja2" + plugin_includes_navbar.append(data) + del vars["_navbar"] - if "_settings" in vars and "name" in vars["_settings"]: - plugin_data["_settings"] = dict(vars["_settings"]) - plugin_data["_settings"]["_div"] = "settings_plugin_" + name - plugin_data["_settings"]["_template"] = name + "_settings.jinja2" - del vars["_settings"] - - if "_tab" in vars and "name" in vars["_tab"]: - plugin_data["_tab"] = dict(vars["_tab"]) - plugin_data["_tab"]["_div"] = "tab_plugin_" + name - plugin_data["_tab"]["_template"] = name + "_tab.jinja2" - del vars["_tab"] - - if "_sidebar" in vars and "name" in vars["_sidebar"]: - plugin_data["_sidebar"] = vars["_sidebar"] - plugin_data["_sidebar"]["_div"] = "sidebar_plugin_" + name - plugin_data["_sidebar"]["_template"] = name + "_sidebar.jinja2" + if "_sidebar" in vars and isinstance(vars["_sidebar"], dict) and "name" in vars["_sidebar"]: + data = dict(vars["_sidebar"]) + data["_div"] = "sidebar_plugin_" + name + data["_template"] = name + "_sidebar.jinja2" + plugin_includes_sidebar.append((data["name"], data)) del vars["_sidebar"] - if len(plugin_data): - plugin_includes[name] = plugin_data + if "_tab" in vars and isinstance(vars["_tab"], dict) and "name" in vars["_tab"]: + data = dict(vars["_tab"]) + data["_div"] = "tab_plugin_" + name + data["_template"] = name + "_tab.jinja2" + plugin_includes_tabs.append((data["name"], data)) + del vars["_tab"] + + if "_settings" in vars and isinstance(vars["_settings"], dict) and "name" in vars["_settings"]: + data = dict(vars["_settings"]) + data["_div"] = "settings_plugin_" + name + data["_template"] = name + "_settings.jinja2" + plugin_includes_settings.append((data["name"], data)) + del vars["_settings"] for var_name, var_value in vars.items(): plugin_vars["plugin_" + name + "_" + var_name] = var_value + #~~ navbar + + navbar_entries = plugin_includes_navbar + [ + dict(_template="navbar/settings.jinja2", _div="navbar_settings", styles=["display: none"], data_bind="visible: loginState.isAdmin"), + dict(_template="navbar/systemmenu.jinja2", _div="navbar_systemmenu", styles=["display: none"], classes=["dropdown"], data_bind="visible: loginState.isAdmin"), + dict(_template="navbar/login.jinja2", _div="navbar_login", classes=["dropdown"]) + ] + + #~~ sidebar + + sidebar_entries = [ + (gettext("Connection"), dict(_template="sidebar/connection.jinja2", _div="connection", icon="signal", styles_wrapper=["display: none"], data_bind="visible: loginState.isAdmin")), + (gettext("State"), dict(_template="sidebar/state.jinja2", _div="state", icon="info-sign")), + (gettext("Files"), dict(_template="sidebar/files.jinja2", _div="files", icon="list", classes_content=["overflow_visible"], header_addon="sidebar/files_header.jinja2")) + ] + plugin_includes_sidebar + + #~~ tabs + + tab_entries = [ + (gettext("Temperature"), dict(_template="tabs/temperature.jinja2", _div="temp")), + (gettext("Control"), dict(_template="tabs/control.jinja2", _div="control")), + (gettext("GCode Viewer"), dict(_template="tabs/gcodeviewer.jinja2", _div="gcode")), + (gettext("Terminal"), dict(_template="tabs/terminal.jinja2", _div="term")), + (gettext("Timelapse"), dict(_template="tabs/timelapse.jinja2", _div="timelapse")) + ] + plugin_includes_tabs + #~~ settings dialog settings_entries = [ (gettext("Printer"), None), - (gettext("Serial Connection"), dict(_template="dialogs/settings/serialconnection.jinja2", _div="settings_serialConnection", _active=True)), + (gettext("Serial Connection"), dict(_template="dialogs/settings/serialconnection.jinja2", _div="settings_serialConnection")), (gettext("Printer Profiles"), dict(_template="dialogs/settings/printerprofiles.jinja2", _div="settings_printerProfiles")), (gettext("Temperatures"), dict(_template="dialogs/settings/temperatures.jinja2", _div="settings_temperature")), (gettext("Terminal Filters"), dict(_template="dialogs/settings/terminalfilters.jinja2", _div="settings_terminalFilters")), @@ -159,14 +196,9 @@ def index(): (gettext("Appearance"), dict(_template="dialogs/settings/appearance.jinja2", _div="settings_appearance")), (gettext("Logs"), dict(_template="dialogs/settings/logs.jinja2", _div="settings_logs", data_bind="allowBindings: false")) ] - if len(plugin_includes): - plugin_tuples = [] - for name, data in plugin_includes.items(): - if "_settings" in data: - plugin_tuples.append((data["_settings"]["name"], data["_settings"])) - if len(plugin_tuples): - settings_entries.append((gettext("Plugins"), None)) - settings_entries.extend(sorted(plugin_tuples, key=lambda x: x[0])) + if len(plugin_includes_settings): + settings_entries.append((gettext("Plugins"), None)) + settings_entries.extend(sorted(plugin_includes_settings, key=lambda x: x[0])) #~~ prepare full set of template vars for rendering @@ -186,7 +218,11 @@ def index(): gcodeMobileThreshold=settings().get(["gcodeViewer", "mobileSizeThreshold"]), gcodeThreshold=settings().get(["gcodeViewer", "sizeThreshold"]), uiApiKey=UI_API_KEY, + navbarEntries=navbar_entries, + sidebarEntries=sidebar_entries, + tabEntries=tab_entries, settingsEntries=settings_entries, + pluginNames=plugin_names, assetPlugins=asset_plugin_urls, ) render_kwargs.update(plugin_vars) @@ -309,6 +345,16 @@ class Server(): printer = Printer(fileManager, analysisQueue, printerProfileManager) appSessionManager = util.flask.AppSessionManager() + pluginManager.initialize_implementations(dict( + printer_profile_manager=printerProfileManager, + event_manager=eventManager, + analysis_queue=analysisQueue, + slicing_manager=slicingManager, + storage_managers=storage_managers, + file_manager=fileManager, + app_session_manager=appSessionManager + )) + # configure additional template folders for jinja2 template_plugins = pluginManager.get_implementations(octoprint.plugin.TemplatePlugin) additional_template_folders = [] diff --git a/src/octoprint/static/js/app/main.js b/src/octoprint/static/js/app/main.js index d4511c55..8a84919f 100644 --- a/src/octoprint/static/js/app/main.js +++ b/src/octoprint/static/js/app/main.js @@ -418,9 +418,9 @@ $(function() { settingsViewModel.requestData(function() { ko.applyBindings(settingsViewModel, document.getElementById("settings_dialog")); - ko.applyBindings(connectionViewModel, document.getElementById("connection_accordion")); - ko.applyBindings(printerStateViewModel, document.getElementById("state_accordion")); - ko.applyBindings(gcodeFilesViewModel, document.getElementById("files_accordion")); + ko.applyBindings(connectionViewModel, document.getElementById("connection_wrapper")); + ko.applyBindings(printerStateViewModel, document.getElementById("state_wrapper")); + ko.applyBindings(gcodeFilesViewModel, document.getElementById("files_wrapper")); ko.applyBindings(temperatureViewModel, document.getElementById("temp")); ko.applyBindings(controlViewModel, document.getElementById("control")); ko.applyBindings(terminalViewModel, document.getElementById("term")); @@ -444,24 +444,31 @@ $(function() { // apply bindings and signal startup _.each(additionalViewModels, function(additionalViewModel) { - if (additionalViewModel[1] === undefined) { + var viewModel = additionalViewModel[0]; + var targets = additionalViewModel[1]; + + if (targets === undefined) { return; } - if (additionalViewModel[0].hasOwnProperty("onBeforeBinding")) { - additionalViewModel[0].onBeforeBinding(); + if (!Array.isArray(targets)) { + targets = [targets]; } - // model instance, target container - try { - ko.applyBindings(additionalViewModel[0], additionalViewModel[1]); - } catch (exc) { - console.log("Could not apply bindings for additional view model " + additionalViewModel[0] + ": " + exc.message); + if (viewModel.hasOwnProperty("onBeforeBinding")) { + viewModel.onBeforeBinding(); } + _.each(targets, function(target) { + try { + ko.applyBindings(viewModel, target); + } catch (exc) { + console.log("Could not apply bindings for additional view model " + viewModel + ": " + exc.message); + } + }); - if (additionalViewModel[0].hasOwnProperty("onAfterBinding")) { - additionalViewModel[0].onAfterBinding(); + if (viewModel.hasOwnProperty("onAfterBinding")) { + viewModel.onAfterBinding(); } }); }); @@ -517,14 +524,14 @@ $(function() { } }); - $(".accordion-toggle[href='#files']").click(function() { + $(".accordion-toggle[data-target='#files']").click(function() { var files = $("#files"); if (files.hasClass("in")) { files.removeClass("overflow_visible"); } else { setTimeout(function() { files.addClass("overflow_visible"); - }, 1000); + }, 100); } }); diff --git a/src/octoprint/static/less/octoprint.less b/src/octoprint/static/less/octoprint.less index e2c5ecfb..34d16856 100644 --- a/src/octoprint/static/less/octoprint.less +++ b/src/octoprint/static/less/octoprint.less @@ -665,6 +665,9 @@ ul.dropdown-menu li a { overflow: visible !important; } +.hidden { + display: none; +} #drop_overlay { diff --git a/src/octoprint/templates/dialogs/settings.jinja2 b/src/octoprint/templates/dialogs/settings.jinja2 index df9e1a4e..ac2e773d 100644 --- a/src/octoprint/templates/dialogs/settings.jinja2 +++ b/src/octoprint/templates/dialogs/settings.jinja2 @@ -10,7 +10,15 @@ {% if data is none %} {% else %} -
  • {{ entry }}
  • + {% if "custom_bindings" in data and data["custom_bindings"] %}{% endif %} + + {% if "custom_bindings" in data and data["custom_bindings"] %}{% endif %} {% endif %} {% endfor %} @@ -19,7 +27,11 @@ {% for entry, data in settingsEntries %} {% if data is not none %} {% if "custom_bindings" in data and data["custom_bindings"] %}{% endif %} -
    +
    {% include data._template ignore missing %}
    {% if "custom_bindings" in data and data["custom_bindings"] %}{% endif %} diff --git a/src/octoprint/templates/index.jinja2 b/src/octoprint/templates/index.jinja2 index d76f0950..071218ba 100644 --- a/src/octoprint/templates/index.jinja2 +++ b/src/octoprint/templates/index.jinja2 @@ -7,68 +7,8 @@ - - - - - - - - {% if stylesheet == "less" %} - - - - {% for name, assets in assetPlugins.items() %} - {% if "less" in assets %} - {% for asset in assets["less"] %} - - {% endfor %} - {% endif %} - {% endfor %} - - - - {% else %} - - - {% for name, assets in assetPlugins.items() %} - {% if "css" in assets %} - {% for asset in assets["css"] %} - - {% endfor %} - {% endif %} - {% endfor %} - - {% endif %} - - + {% include 'stylesheets.jinja2' %} + {% include 'initscript.jinja2' %}
    +
    - {% include 'sidebar/connection.jinja2' %} - {% include 'sidebar/state.jinja2' %} - {% include 'sidebar/files.jinja2' %} - - {% if templatePlugins %} - {% for plugin_name, vars in templatePlugins.items() %} - {% include plugin_name+"_sidebar.jinja2" ignore missing %} - {% endfor %} - {% endif %} + {% for entry, data in sidebarEntries %} + {% if "custom_bindings" in data and data["custom_bindings"] %}{% endif %} +
    +
    + + {% if "icon" in data %} {% endif %}{{ entry }} + + {% if "header_addon" in data %} + {% include data.header_addon ignore missing %} + {% endif %} +
    +
    +
    + {% include data._template ignore missing %} +
    +
    +
    + {% if "custom_bindings" in data and data["custom_bindings"] %}{% endif %} + {% endfor %}
    +
    - {% include 'tabs/temperature.jinja2' %} - {% include 'tabs/control.jinja2' %} - {% include 'tabs/gcodeviewer.jinja2' %} - {% include 'tabs/terminal.jinja2' %} - {% include 'tabs/timelapse.jinja2' %} - - {% if templatePlugins %} - {% for plugin_name, vars in templatePlugins.items() %} - {% if "_tab_entry" in vars %} -
    - {% include plugin_name + "_tab.jinja2" ignore missing %} -
    - {% endif %} - {% endfor %} - {% endif %} - + {% for entry, data in tabEntries %} + {% if "custom_bindings" in data and data["custom_bindings"] %}{% endif %} +
    + {% include data._template ignore missing %} +
    + {% if "custom_bindings" in data and data["custom_bindings"] %}{% endif %} + {% endfor %}
    @@ -161,85 +122,14 @@ {% include 'overlays/offline.jinja2' %} - + {% if templatePlugins %} - {% for plugin_name in templatePlugins %} + {% for plugin_name in pluginNames %} {% include plugin_name + ".jinja2" ignore missing %} {% endfor %} {% endif %} - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - {% for name, assets in assetPlugins.items() %} - {% if "js" in assets %} - {% for asset in assets["js"] %} - - {% endfor %} - {% endif %} - {% endfor %} - - - - - - - - - - {% if g.locale != 'en' %} - - {% endif %} - - - - - + {% include 'javascripts.jinja2' %} diff --git a/src/octoprint/templates/initscript.jinja2 b/src/octoprint/templates/initscript.jinja2 new file mode 100644 index 00000000..a3d203ec --- /dev/null +++ b/src/octoprint/templates/initscript.jinja2 @@ -0,0 +1,28 @@ + diff --git a/src/octoprint/templates/javascripts.jinja2 b/src/octoprint/templates/javascripts.jinja2 new file mode 100644 index 00000000..e9b3401c --- /dev/null +++ b/src/octoprint/templates/javascripts.jinja2 @@ -0,0 +1,72 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +{% for name, assets in assetPlugins.items() %} + {% if "js" in assets %} + {% for asset in assets["js"] %} + + {% endfor %} + {% endif %} +{% endfor %} + + + + + + + + + +{% if g.locale != 'en' %} + +{% endif %} + + + + + diff --git a/src/octoprint/templates/navbar/login.jinja2 b/src/octoprint/templates/navbar/login.jinja2 index cc3154ed..1b776aaf 100644 --- a/src/octoprint/templates/navbar/login.jinja2 +++ b/src/octoprint/templates/navbar/login.jinja2 @@ -1,22 +1,20 @@ {% if enableAccessControl %} - + + {{ _('Login') }} + + + + {% endif %} diff --git a/src/octoprint/templates/navbar/settings.jinja2 b/src/octoprint/templates/navbar/settings.jinja2 index d4940ce4..a2710dbe 100644 --- a/src/octoprint/templates/navbar/settings.jinja2 +++ b/src/octoprint/templates/navbar/settings.jinja2 @@ -1,5 +1,3 @@ -
  • - - {{ _('Settings') }} - -
  • + + {{ _('Settings') }} + diff --git a/src/octoprint/templates/navbar/systemmenu.jinja2 b/src/octoprint/templates/navbar/systemmenu.jinja2 index 0719ab9d..b14c83c3 100644 --- a/src/octoprint/templates/navbar/systemmenu.jinja2 +++ b/src/octoprint/templates/navbar/systemmenu.jinja2 @@ -1,11 +1,9 @@ {% if enableSystemMenu %} - + + {{ _('System') }} + + + {% endif %} diff --git a/src/octoprint/templates/sidebar/connection.jinja2 b/src/octoprint/templates/sidebar/connection.jinja2 index 59570251..5481485e 100644 --- a/src/octoprint/templates/sidebar/connection.jinja2 +++ b/src/octoprint/templates/sidebar/connection.jinja2 @@ -1,22 +1,13 @@ -
    - -
    -
    - - - - - - - - - -
    -
    -
    + + + + + + + + + diff --git a/src/octoprint/templates/sidebar/files.jinja2 b/src/octoprint/templates/sidebar/files.jinja2 index 8ca633a5..e9ca34c2 100644 --- a/src/octoprint/templates/sidebar/files.jinja2 +++ b/src/octoprint/templates/sidebar/files.jinja2 @@ -1,108 +1,65 @@ -
    -
    - {{ _('Files') }} + +
    +
    -
    - - - - + + + + +
    +
    + {{ _('Free') }}: +
    +
    +
    {% if enableSdSupport %} - + + + {{ _('Upload') }} + + + + + {{ _('Upload to SD') }} + + + {% else %} + + + {{ _('Upload') }} + + {% endif %}
    -
    -
    - -
    -
    - - - - - - -
    -
    - {{ _('Free') }}: -
    -
    -
    - {% if enableSdSupport %} - - - {{ _('Upload') }} - - - - - {{ _('Upload to SD') }} - - - {% else %} - - - {{ _('Upload') }} - - - {% endif %} -
    -
    -
    -
    -
    - {{ _('Hint: You can also drag and drop files on this page to upload them.') }} -
    -
    -
    +
    +
    +
    +
    + {{ _('Hint: You can also drag and drop files on this page to upload them.') }}
    diff --git a/src/octoprint/templates/sidebar/files_header.jinja2 b/src/octoprint/templates/sidebar/files_header.jinja2 new file mode 100644 index 00000000..3a4810a0 --- /dev/null +++ b/src/octoprint/templates/sidebar/files_header.jinja2 @@ -0,0 +1,33 @@ + + +{% if enableSdSupport %} + +{% endif %} diff --git a/src/octoprint/templates/sidebar/state.jinja2 b/src/octoprint/templates/sidebar/state.jinja2 index 393aa92b..7bcfdd25 100644 --- a/src/octoprint/templates/sidebar/state.jinja2 +++ b/src/octoprint/templates/sidebar/state.jinja2 @@ -1,29 +1,20 @@ -
    - -
    -
    - {{ _('Machine State') }}:
    - {{ _('File') }}:  (SD)
    - {{ _('Timelapse') }}:
    - -
    - - {{ _('Approx. Total Print Time') }}:
    - {{ _('Print Time') }}:
    - {{ _('Print Time Left') }}:
    - {{ _('Printed') }}:
    +{{ _('Machine State') }}:
    +{{ _('File') }}:  (SD)
    +{{ _('Timelapse') }}:
    + +
    + +{{ _('Approx. Total Print Time') }}:
    +{{ _('Print Time') }}:
    +{{ _('Print Time Left') }}:
    +{{ _('Printed') }}:
    -
    -
    -
    - - -
    -
    +
    +
    +
    + + diff --git a/src/octoprint/templates/stylesheets.jinja2 b/src/octoprint/templates/stylesheets.jinja2 new file mode 100644 index 00000000..1fabcd2c --- /dev/null +++ b/src/octoprint/templates/stylesheets.jinja2 @@ -0,0 +1,34 @@ + + + + + + + +{% if stylesheet == "less" %} + + + + {% for name, assets in assetPlugins.items() %} + {% if "less" in assets %} + {% for asset in assets["less"] %} + + {% endfor %} + {% endif %} + {% endfor %} + + + +{% else %} + + + {% for name, assets in assetPlugins.items() %} + {% if "css" in assets %} + {% for asset in assets["css"] %} + + {% endfor %} + {% endif %} + {% endfor %} + +{% endif %} + diff --git a/src/octoprint/templates/tabs/control.jinja2 b/src/octoprint/templates/tabs/control.jinja2 index a047cd72..93c54ef6 100644 --- a/src/octoprint/templates/tabs/control.jinja2 +++ b/src/octoprint/templates/tabs/control.jinja2 @@ -1,133 +1,131 @@ -
    - {% if webcamStream %} -
    - -
    -
    {{ _("Keyboard controls active") }}
    -
    -
    - / : {{ _("X Axis") }} +/-
    - / : {{ _("Y Axis") }} +/-
    - W, {{ _("Page↑") }} / S, {{ _("Page↓") }}: {{ _("Z Axis") }} +/- -
    -
    - Home: {{ _("Home X/Y") }}
    - End: {{ _("Home Z") }}
    - 1, 2, 3, 4: {{ _("Stepsize") }} 0.1, 1, 10, 100mm -
    +{% if webcamStream %} +
    + +
    +
    {{ _("Keyboard controls active") }}
    +
    +
    + / : {{ _("X Axis") }} +/-
    + / : {{ _("Y Axis") }} +/-
    + W, {{ _("Page↑") }} / S, {{ _("Page↓") }}: {{ _("Z Axis") }} +/- +
    +
    + Home: {{ _("Home X/Y") }}
    + End: {{ _("Home Z") }}
    + 1, 2, 3, 4: {{ _("Stepsize") }} 0.1, 1, 10, 100mm
    -
    - {{ _("Hint: If you move your mouse over the picture, you enter keyboard control mode.") }} -
    - {% endif %} - - - -