Changed how TemplatePlugin works
get_template_vars get split into get_template_vars (for injection additional template variables only) and get_template_configs (for configuring injection of additional template). It's now possible to inject more than one of a given template type from within plugins, and template injection is more intelligent in that if a plugin's templates don't need special configuration and stick to default naming and behaviour, just declaring them inside the templates directory of the plugin will be enough to fire up everything including links to switch to tabs, sidebars etc (the plugin's name being used in such cases for link text).
This commit is contained in:
parent
19fe770606
commit
5180fc70d2
7 changed files with 354 additions and 195 deletions
|
|
@ -242,6 +242,8 @@ class PluginManager(object):
|
|||
kwargs = dict(additional_injects)
|
||||
kwargs.update(dict(
|
||||
identifier=name,
|
||||
plugin_name=plugin.name,
|
||||
plugin_version=plugin.version,
|
||||
basefolder=os.path.realpath(plugin.location),
|
||||
logger=logging.getLogger("octoprint.plugins." + name),
|
||||
))
|
||||
|
|
|
|||
|
|
@ -113,18 +113,53 @@ class TemplatePlugin(Plugin):
|
|||
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.
|
||||
|
||||
The included template must be called ``<pluginname>_navbar.jinja2`` (e.g. ``myplugin_navbar.jinja2``) unless
|
||||
overridden by the configuration supplied through ``get_template_config``.
|
||||
|
||||
The template will be already wrapped into the necessary structure, plugins just need to supply the pure content. The
|
||||
wrapper structure will have all additional classes and styles applied as specified via the configuration supplied
|
||||
through ``get_template_config``.
|
||||
|
||||
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.
|
||||
|
||||
The included template must be called ``<pluginname>_sidebar.jinja2`` (e.g. ``myplugin_sidebar.jinja2``) unless
|
||||
overridden by the configuration supplied through ``get_template_config``.
|
||||
|
||||
The template will be already wrapped into the necessary structure, plugins just need to supply the pure content. The
|
||||
wrapper divs for both the whole box as well as the content pane will have all additional classes and styles applied
|
||||
as specified via the configuration supplied through ``get_template_config``.
|
||||
|
||||
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.
|
||||
|
||||
The included template must be called ``<pluginname>_tab.jinja2`` (e.g. ``myplugin_tab.jinja2``) unless
|
||||
overridden by the configuration supplied through ``get_template_config``.
|
||||
|
||||
The template will be already wrapped into the necessary structure, plugins just need to supply the pure content. The
|
||||
wrapper div and the link in the navigation will have the additional classes and styles applied as specified via the
|
||||
configuration supplied through ``get_template_config``.
|
||||
|
||||
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.
|
||||
|
||||
The included template must be called ``<pluginname>_settings.jinja2`` (e.g. ``myplugin_settings.jinja2``) unless
|
||||
overridden by the configuration supplied through ``get_template_config``.
|
||||
|
||||
The template will be already wrapped into the necessary structure, plugins just need to supply the pure content. The
|
||||
wrapper div and the link in the navigation will have the additional classes and styles applied as defined via the
|
||||
supplied configuration supplied through ``get_template_config``.
|
||||
|
||||
Generic
|
||||
Plugins may also inject arbitrary templates into the page of the web interface itself, e.g. in order to
|
||||
add overlays or dialogs to be called from within the plugin's javascript code.
|
||||
|
||||
.. figure:: ../images/template-plugin-types-main.png
|
||||
:align: center
|
||||
:alt: Template injection types in the main part of the interface
|
||||
|
|
@ -137,121 +172,182 @@ class TemplatePlugin(Plugin):
|
|||
|
||||
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 ``<plugin name>_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 ``<plugin name>_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 ``<plugin name>_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 ``<plugin name>_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
|
||||
|
||||
<h1>Hello World!</h1>
|
||||
|
||||
This is a custom settings pane.
|
||||
|
||||
``helloworld/templates/helloworld_tab.jinja2``
|
||||
|
||||
.. code-block:: html+jinja
|
||||
|
||||
<h1>Hello World!</h1>
|
||||
|
||||
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
|
||||
|
||||
<li>
|
||||
<a class="pull-right" href="http://www.example.com"><i class="icon-gear"></i> {{ _('Hello World!') }}</a>
|
||||
</li>
|
||||
|
||||
You can find an example for a simple plugin which injects navbar, sidebar, tab and settings content into the interface in
|
||||
`the "helloworld" plugin in OctoPrint's collection of plugin examples <https://github.com/OctoPrint/Plugin-Examples/tree/master/helloworld>`_.
|
||||
"""
|
||||
|
||||
def get_template_configs(self):
|
||||
"""
|
||||
Allows configuration of injected navbar, sidebar, tab and settings templates. Should be a list containing one
|
||||
configuration object per template to inject. Each configuration object is represented by a dictionary with a mandatory key
|
||||
``type`` encoding the template type the configuration is targeting. Possible values here are ``navbar``, ``sidebar``,
|
||||
``tab``, ``settings`` and ``generic``.
|
||||
|
||||
Further keys to be included in the dictionary depend on the type:
|
||||
|
||||
``navbar`` type
|
||||
Configures a navbar component to inject. The following keys are supported:
|
||||
|
||||
template
|
||||
Name of the template to inject, defaults to ``<pluginname>_navbar.jinja2``.
|
||||
suffix
|
||||
Suffix to attach to the element ID of the injected template, will be ``_<index>`` if not provided and not
|
||||
the first template of the type, with ``index`` counting from 1 and increasing for each template of the same
|
||||
type.
|
||||
custom_bindings
|
||||
A boolean value indicating whether the default view model should be bound to the navbar entry (``false``)
|
||||
or if a custom binding will be used by the plugin (``true``, default).
|
||||
data_bind
|
||||
Additional knockout data bindings to apply to the navbar entry, 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.
|
||||
classes
|
||||
Additional classes to apply to the navbar entry, as a list of individual classes
|
||||
(e.g. ``classes=["myclass", "myotherclass"]``) which will be joined into the correct format by the template engine.
|
||||
styles
|
||||
Additional CSS styles to apply to the navbar entry, as a list of individual declarations
|
||||
(e.g. ``styles=["color: red", "display: block"]``) which will be joined into the correct format by the template
|
||||
engine.
|
||||
|
||||
``sidebar`` type
|
||||
Configures a sidebar component to inject. The following keys are supported:
|
||||
|
||||
name
|
||||
The name of the sidebar entry, if not set the name of the plugin will be used.
|
||||
icon
|
||||
Icon to use for the sidebar header, should be the name of a Font Awesome icon without the leading ``icon-`` part.
|
||||
template
|
||||
Name of the template to inject, defaults to ``<pluginname>_sidebar.jinja2``.
|
||||
template_header
|
||||
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.
|
||||
suffix
|
||||
Suffix to attach to the element ID of the injected template, will be ``_<index>`` if not provided and not
|
||||
the first template of the type, with ``index`` counting from 1 and increasing for each template of the same
|
||||
type.
|
||||
custom_bindings
|
||||
A boolean value indicating whether the default view model should be bound to the sidebar container (``false``)
|
||||
or if a custom binding will be used by the plugin (``true``, default).
|
||||
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.
|
||||
classes
|
||||
Additional classes to apply to both the wrapper around the sidebar box as well as the content pane itself, as a
|
||||
list of individual classes (e.g. ``classes=["myclass", "myotherclass"]``) which will be joined into the correct
|
||||
format by the template engine.
|
||||
classes_wrapper
|
||||
Like ``classes`` but only applied to the whole wrapper around the sidebar box.
|
||||
classes_content
|
||||
Like ``classes`` but only applied to the content pane itself.
|
||||
styles
|
||||
Additional CSS styles to apply to both the wrapper around the sidebar box as well as the content pane itself,
|
||||
as a list of individual declarations (e.g. ``styles=["color: red", "display: block"]``) which will be joined
|
||||
into the correct format by the template engine.
|
||||
styles_wrapper
|
||||
Like ``styles`` but only applied to the whole wrapper around the sidebar box.
|
||||
styles_content
|
||||
Like ``styles`` but only applied to the content pane itself
|
||||
|
||||
``tab`` type
|
||||
Configures a tab component to inject. The value must be a dictionary, supported values are the following:
|
||||
|
||||
name
|
||||
The name under which to include the tab, if not set the name of the plugin will be used.
|
||||
template
|
||||
Name of the template to inject, defaults to ``<pluginname>_tab.jinja2``.
|
||||
suffix
|
||||
Suffix to attach to the element ID of the injected template, will be ``_<index>`` if not provided and not
|
||||
the first template of the type, with ``index`` counting from 1 and increasing for each template of the same
|
||||
type.
|
||||
custom_bindings
|
||||
A boolean value indicating whether the default view model should be bound to the tab pane and link
|
||||
in the navigation (``false``) or if a custom binding will be used by the plugin (``true``, default).
|
||||
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.
|
||||
classes
|
||||
Additional classes to apply to both the wrapper around the sidebar box as well as the content pane itself, as a
|
||||
list of individual classes (e.g. ``classes=["myclass", "myotherclass"]``) which will be joined into the correct
|
||||
format by the template engine.
|
||||
classes_link
|
||||
Like ``classes`` but only applied to the link in the navigation.
|
||||
classes_content
|
||||
Like ``classes`` but only applied to the content pane itself.
|
||||
styles
|
||||
Additional CSS styles to apply to both the wrapper around the sidebar box as well as the content pane itself,
|
||||
as a list of individual declarations (e.g. ``styles=["color: red", "display: block"]``) which will be joined
|
||||
into the correct format by the template engine.
|
||||
styles_link
|
||||
Like ``styles`` but only applied to the link in the navigation.
|
||||
styles_content
|
||||
Like ``styles`` but only applied to the content pane itself
|
||||
|
||||
``settings`` type
|
||||
Configures a 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, if not set the name of the plugin will be used.
|
||||
template
|
||||
Name of the template to inject, defaults to ``<pluginname>_settings.jinja2``.
|
||||
suffix
|
||||
Suffix to attach to the element ID of the injected template, will be ``_<index>`` if not provided and not
|
||||
the first template of the type, with ``index`` counting from 1 and increasing for each template of the same
|
||||
type.
|
||||
custom_bindings
|
||||
A boolean value indicating whether the default settings view model should be bound to the settings pane and link
|
||||
in the navigation (``false``) or if a custom binding will be used by the plugin (``true``, default).
|
||||
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.
|
||||
classes
|
||||
Additional classes to apply to both the wrapper around the navigation link as well as the content pane itself, as a
|
||||
list of individual classes (e.g. ``classes=["myclass", "myotherclass"]``) which will be joined into the correct
|
||||
format by the template engine.
|
||||
classes_link
|
||||
Like ``classes`` but only applied to the link in the navigation.
|
||||
classes_content
|
||||
Like ``classes`` but only applied to the content pane itself.
|
||||
styles
|
||||
Additional CSS styles to apply to both the wrapper around the navigation link as well as the content pane itself,
|
||||
as a list of individual declarations (e.g. ``styles=["color: red", "display: block"]``) which will be joined
|
||||
into the correct format by the template engine.
|
||||
styles_link
|
||||
Like ``styles`` but only applied to the link in the navigation.
|
||||
styles_content
|
||||
Like ``styles`` but only applied to the content pane itself
|
||||
|
||||
``generic`` type
|
||||
Configures a generic template to inject. The following keys are supported:
|
||||
|
||||
template
|
||||
Name of the template to inject, defaults to ``<pluginname>_settings.jinja2``.
|
||||
|
||||
.. note::
|
||||
|
||||
As already outlined above, each template type has a default template name (i.e. the default navbar template
|
||||
of a plugin is called ``<pluginname>_navbar.jinja2``), which may be overridden using the template configuration.
|
||||
If a plugin needs to include two navbar templates, it needs to provide two entries here, at least one with ``template``
|
||||
overridden. If only one entry for a given template type is present with an overridden ``template`` parameter,
|
||||
OctoPrint will assume that the default template does not exist and hence only include the defined template,
|
||||
skipping the default one.
|
||||
|
||||
:return: a list containing the configuration options for the plugin's injected templates
|
||||
"""
|
||||
return []
|
||||
|
||||
def get_template_vars(self):
|
||||
"""
|
||||
Defines additional template variables to include into the template renderer.
|
||||
|
||||
:return: a dictionary containing any additional template variables to include in the renderer
|
||||
"""
|
||||
return dict()
|
||||
|
||||
def get_template_folder(self):
|
||||
|
|
|
|||
|
|
@ -169,13 +169,6 @@ class CuraPlugin(octoprint.plugin.SlicerPlugin,
|
|||
self._cura_logger.setLevel(logging.CRITICAL)
|
||||
s.setBoolean(["debug_logging"], new_debug_logging)
|
||||
|
||||
##~~ TemplatePlugin API
|
||||
|
||||
def get_template_vars(self):
|
||||
return dict(
|
||||
_settings=dict(name="Cura", custom_bindings=True)
|
||||
)
|
||||
|
||||
##~~ SlicerPlugin API
|
||||
|
||||
def is_slicer_configured(self):
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ from flask.ext.principal import Principal, Permission, RoleNeed, identity_loaded
|
|||
from flask.ext.babel import Babel, gettext, ngettext
|
||||
from babel import Locale
|
||||
from watchdog.observers import Observer
|
||||
from collections import defaultdict
|
||||
|
||||
import os
|
||||
import logging
|
||||
|
|
@ -99,6 +100,8 @@ def get_locale():
|
|||
@app.route("/")
|
||||
def index():
|
||||
|
||||
|
||||
|
||||
#~~ extract data from asset plugins
|
||||
|
||||
asset_plugins = pluginManager.get_implementations(octoprint.plugin.AssetPlugin)
|
||||
|
|
@ -110,91 +113,85 @@ def index():
|
|||
|
||||
template_plugins = pluginManager.get_implementations(octoprint.plugin.TemplatePlugin)
|
||||
|
||||
# rules for transforming template configs to template entries
|
||||
rules = dict(
|
||||
navbar=dict(div=lambda x: "navbar_plugin_" + x, template=lambda x: x + "_navbar.jinja2", to_entry=lambda data: data),
|
||||
sidebar=dict(div=lambda x: "sidebar_plugin_" + x, template=lambda x: x + "_sidebar.jinja2", to_entry=lambda data: (data["name"], data)),
|
||||
tab=dict(div=lambda x: "tab_plugin_" + x, template=lambda x: x + "_tab.jinja2", to_entry=lambda data: (data["name"], data)),
|
||||
settings=dict(div=lambda x: "settings_plugin_" + x, template=lambda x: x + "_settings.jinja2", to_entry=lambda data: (data["name"], data)),
|
||||
generic=dict(template=lambda x: x + ".jinja2", to_entry=lambda data: data)
|
||||
)
|
||||
|
||||
plugin_vars = dict()
|
||||
plugin_includes_navbar = []
|
||||
plugin_includes_sidebar = []
|
||||
plugin_includes_tabs = []
|
||||
plugin_includes_settings = []
|
||||
plugin_includes_generic = []
|
||||
plugin_names = template_plugins.keys()
|
||||
for name, implementation in template_plugins.items():
|
||||
vars = implementation.get_template_vars()
|
||||
if not isinstance(vars, dict):
|
||||
continue
|
||||
|
||||
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 "_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 "_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"]
|
||||
vars = dict()
|
||||
|
||||
for var_name, var_value in vars.items():
|
||||
plugin_vars["plugin_" + name + "_" + var_name] = var_value
|
||||
|
||||
configs = implementation.get_template_configs()
|
||||
if not isinstance(configs, (list, tuple)):
|
||||
configs = []
|
||||
|
||||
includes = _process_template_configs(name, implementation, configs, rules)
|
||||
|
||||
plugin_includes_navbar += includes["navbar"]
|
||||
plugin_includes_sidebar += includes["sidebar"]
|
||||
plugin_includes_tabs += includes["tab"]
|
||||
plugin_includes_settings += includes["settings"]
|
||||
plugin_includes_generic += includes["generic"]
|
||||
|
||||
#~~ 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"])
|
||||
dict(template="navbar/settings.jinja2", _div="navbar_settings", styles=["display: none"], data_bind="visible: loginState.isAdmin", custom_bindings=False),
|
||||
dict(template="navbar/systemmenu.jinja2", _div="navbar_systemmenu", styles=["display: none"], classes=["dropdown"], data_bind="visible: loginState.isAdmin", custom_bindings=False),
|
||||
dict(template="navbar/login.jinja2", _div="navbar_login", classes=["dropdown"], custom_bindings=False)
|
||||
]
|
||||
|
||||
#~~ 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"))
|
||||
(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"))
|
||||
(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")),
|
||||
(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")),
|
||||
(gettext("Serial Connection"), dict(template="dialogs/settings/serialconnection.jinja2", _div="settings_serialConnection", custom_bindings=False)),
|
||||
(gettext("Printer Profiles"), dict(template="dialogs/settings/printerprofiles.jinja2", _div="settings_printerProfiles", custom_bindings=False)),
|
||||
(gettext("Temperatures"), dict(template="dialogs/settings/temperatures.jinja2", _div="settings_temperature", custom_bindings=False)),
|
||||
(gettext("Terminal Filters"), dict(template="dialogs/settings/terminalfilters.jinja2", _div="settings_terminalFilters", custom_bindings=False)),
|
||||
(gettext("Features"), None),
|
||||
(gettext("Features"), dict(_template="dialogs/settings/features.jinja2", _div="settings_features")),
|
||||
(gettext("Webcam"), dict(_template="dialogs/settings/webcam.jinja2", _div="settings_webcam")),
|
||||
(gettext("Access Control"), dict(_template="dialogs/settings/accesscontrol.jinja2", _div="settings_users")),
|
||||
(gettext("API"), dict(_template="dialogs/settings/api.jinja2", _div="settings_api")),
|
||||
(gettext("Features"), dict(template="dialogs/settings/features.jinja2", _div="settings_features", custom_bindings=False)),
|
||||
(gettext("Webcam"), dict(template="dialogs/settings/webcam.jinja2", _div="settings_webcam", custom_bindings=False)),
|
||||
(gettext("Access Control"), dict(template="dialogs/settings/accesscontrol.jinja2", _div="settings_users", custom_bindings=False)),
|
||||
(gettext("API"), dict(template="dialogs/settings/api.jinja2", _div="settings_api", custom_bindings=False)),
|
||||
(gettext("OctoPrint"), None),
|
||||
(gettext("Folders"), dict(_template="dialogs/settings/folders.jinja2", _div="settings_folders")),
|
||||
(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"))
|
||||
(gettext("Folders"), dict(template="dialogs/settings/folders.jinja2", _div="settings_folders", custom_bindings=False)),
|
||||
(gettext("Appearance"), dict(template="dialogs/settings/appearance.jinja2", _div="settings_appearance", custom_bindings=False)),
|
||||
(gettext("Logs"), dict(template="dialogs/settings/logs.jinja2", _div="settings_logs"))
|
||||
]
|
||||
if len(plugin_includes_settings):
|
||||
settings_entries.append((gettext("Plugins"), None))
|
||||
|
|
@ -222,6 +219,7 @@ def index():
|
|||
sidebarEntries=sidebar_entries,
|
||||
tabEntries=tab_entries,
|
||||
settingsEntries=settings_entries,
|
||||
genericEntries=plugin_includes_generic,
|
||||
pluginNames=plugin_names,
|
||||
assetPlugins=asset_plugin_urls,
|
||||
)
|
||||
|
|
@ -235,6 +233,76 @@ def index():
|
|||
)
|
||||
|
||||
|
||||
def _process_template_configs(name, implementation, configs, rules):
|
||||
from jinja2.exceptions import TemplateNotFound
|
||||
|
||||
counters = dict(
|
||||
navbar=1,
|
||||
sidebar=1,
|
||||
tab=1,
|
||||
settings=1
|
||||
)
|
||||
includes = defaultdict(list)
|
||||
|
||||
for config in configs:
|
||||
if not isinstance(config, dict):
|
||||
continue
|
||||
if not "type" in config:
|
||||
continue
|
||||
|
||||
template_type = config["type"]
|
||||
del config["type"]
|
||||
|
||||
if not template_type in rules:
|
||||
continue
|
||||
rule = rules[template_type]
|
||||
|
||||
data = _process_template_config(name, implementation, rule, config=config, counter=counters[template_type])
|
||||
if data is None:
|
||||
continue
|
||||
|
||||
includes[template_type].append(rule["to_entry"](data))
|
||||
counters[template_type] += 1
|
||||
|
||||
for template_type in rules:
|
||||
if len(includes[template_type]) == 0:
|
||||
# if no template of that type was added by the config, we'll try to use the default template name
|
||||
rule = rules[template_type]
|
||||
data = _process_template_config(name, implementation, rule)
|
||||
if data is not None:
|
||||
try:
|
||||
app.jinja_env.get_or_select_template(data["template"])
|
||||
except TemplateNotFound:
|
||||
pass
|
||||
else:
|
||||
includes[template_type].append(rule["to_entry"](data))
|
||||
|
||||
return includes
|
||||
|
||||
def _process_template_config(name, implementation, rule, config=None, counter=1):
|
||||
if "mandatory" in rule:
|
||||
for mandatory in rule["mandatory"]:
|
||||
if not mandatory in config:
|
||||
return None
|
||||
|
||||
if config is None:
|
||||
config = dict()
|
||||
data = dict(config)
|
||||
|
||||
if "div" in rule:
|
||||
data["_div"] = rule["div"](name)
|
||||
if "suffix" in data:
|
||||
data["_div"] += "_" + data["suffix"]
|
||||
del data["suffix"]
|
||||
elif counter > 1:
|
||||
data["_div"] += "_%d" % counter
|
||||
if not "template" in data:
|
||||
data["template"] = rule["template"](name)
|
||||
if not "name" in data:
|
||||
data["name"] = implementation._plugin_name
|
||||
|
||||
return data
|
||||
|
||||
@app.route("/robots.txt")
|
||||
def robotsTxt():
|
||||
return send_from_directory(app.static_folder, "robots.txt")
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
|
|
@ -11,15 +11,15 @@
|
|||
{% if data is none %}
|
||||
<li class="nav-header">{{ entry }}</li>
|
||||
{% else %}
|
||||
{% if "custom_bindings" in data and data["custom_bindings"] %}<!-- ko allowBindings: false -->{% endif %}
|
||||
{% if "custom_bindings" not in data or data["custom_bindings"] %}<!-- ko allowBindings: false -->{% endif %}
|
||||
<li id="{{ data._div }}_link"
|
||||
{% if "data_bind" in data %} data-bind="{{ data.data_bind }}" {% elif "custom_bindings" in data and data["custom_bindings"] %} data-bind="allowBindings: true" {% endif %}
|
||||
{% if "data_bind" in data %} data-bind="{{ data.data_bind }}" {% elif "custom_bindings" not in data or data["custom_bindings"] %} data-bind="allowBindings: true" {% endif %}
|
||||
class="{% if not active_set %}active{% set active_set = true %}{% endif %} {% if "classes_link" in data %}{{ data.classes_link|join(' ') }}{% elif "classes" in data %}{{ data.classes|join(' ') }}{% endif %}"
|
||||
{% if "styles_link" in data %} style="{{ data.styles_link|join(', ') }}" {% elif "styles" in data %} style="{{ data.styles|join(', ') }}" {% endif %}
|
||||
>
|
||||
<a href="#{{ data._div }}" data-toggle="tab">{{ entry }}</a>
|
||||
</li>
|
||||
{% if "custom_bindings" in data and data["custom_bindings"] %}<!-- /ko -->{% endif %}
|
||||
{% if "custom_bindings" not in data or data["custom_bindings"] %}<!-- /ko -->{% endif %}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</ul>
|
||||
|
|
@ -28,15 +28,15 @@
|
|||
{% set active_set = false %}
|
||||
{% for entry, data in settingsEntries %}
|
||||
{% if data is not none %}
|
||||
{% if "custom_bindings" in data and data["custom_bindings"] %}<!-- ko allowBindings: false -->{% endif %}
|
||||
{% if "custom_bindings" not in data or data["custom_bindings"] %}<!-- ko allowBindings: false -->{% endif %}
|
||||
<div id="{{ data._div }}"
|
||||
{% if "data_bind" in data %} data-bind="{{ data.data_bind }}" {% elif "custom_bindings" in data and data["custom_bindings"] %} data-bind="allowBindings: true" {% endif %}
|
||||
{% if "data_bind" in data %} data-bind="{{ data.data_bind }}" {% elif "custom_bindings" not in data or data["custom_bindings"] %} data-bind="allowBindings: true" {% endif %}
|
||||
class="tab-pane {% if not active_set %}active{% set active_set = true %}{% endif %} {% if classes_content in data %}{{ data.classes_content|join(' ') }}{% elif classes in data %}{{ data.classes|join(' ') }}{% endif %}"
|
||||
{% if "styles_content" in data %} style="{{ data.styles_content|join(', ') }}" {% elif styles in data %} style="{{ data.styles|join(', ') }}" {% endif %}
|
||||
>
|
||||
{% include data._template ignore missing %}
|
||||
{% include data.template ignore missing %}
|
||||
</div>
|
||||
{% if "custom_bindings" in data and data["custom_bindings"] %}<!-- /ko -->{% endif %}
|
||||
{% if "custom_bindings" not in data or data["custom_bindings"] %}<!-- /ko -->{% endif %}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -19,15 +19,15 @@
|
|||
<!-- Navbar -->
|
||||
<ul class="nav pull-right">
|
||||
{% for data in navbarEntries %}
|
||||
{% if "custom_bindings" in data and data["custom_bindings"] %}<!-- ko allowBindings: false -->{% endif %}
|
||||
{% if "custom_bindings" not in data or data["custom_bindings"] %}<!-- ko allowBindings: false -->{% endif %}
|
||||
<li id="{{ data._div }}"
|
||||
data-bind="{% if "data_bind" in data %}{{ data.data_bind }}{% else %}allowBindings: true{% endif %}"
|
||||
data-bind="{% if "data_bind" in data %}{{ data.data_bind }}{% elif "custom_bindings" not in data or data["custom_bindings"] %}allowBindings: true{% endif %}"
|
||||
{% if "classes" in data %}class="{{ data.classes|join(' ') }}"{% endif %}
|
||||
{% if "styles" in data %}style="{{ data.styles|join(', ') }}"{% endif %}
|
||||
>
|
||||
{% include data._template ignore missing %}
|
||||
{% include data.template ignore missing %}
|
||||
</li>
|
||||
{% if "custom_bindings" in data and data["custom_bindings"] %}<!-- /ko -->{% endif %}
|
||||
{% if "custom_bindings" not in data or data["custom_bindings"] %}<!-- /ko -->{% endif %}
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
|
|
@ -39,10 +39,10 @@
|
|||
<!-- Sidebar -->
|
||||
<div class="accordion span4">
|
||||
{% for entry, data in sidebarEntries %}
|
||||
{% if "custom_bindings" in data and data["custom_bindings"] %}<!-- ko allowBindings: false -->{% endif %}
|
||||
{% if "custom_bindings" not in data or data["custom_bindings"] %}<!-- ko allowBindings: false -->{% endif %}
|
||||
<div id="{{ data._div }}_wrapper"
|
||||
class="accordion-group {% if "classes_wrapper" in data %}{{ data.classes_wrapper|join(' ') }}{% elif "classes" in data %}{{ data.classes|join(' ') }}{% endif %}"
|
||||
data-bind="{% if "data_bind" in data %}{{ data.data_bind }}{% elif "custom_bindings" in data and data["custom_bindings"] %}allowBindings: true{% endif %}"
|
||||
data-bind="{% if "data_bind" in data %}{{ data.data_bind }}{% elif "custom_bindings" not in data or data["custom_bindings"] %}allowBindings: true{% endif %}"
|
||||
{% if "styles_wrapper" in data %} style="{{ data.styles_wrapper|join(', ') }}" {% elif "styles" in data %} style="{{ data.styles|join(', ') }}" {% endif %}
|
||||
>
|
||||
<div class="accordion-heading">
|
||||
|
|
@ -58,11 +58,11 @@
|
|||
{% if "styles_content" in data %} style="{{ data.styles_content|join(', ') }}" {% elif "styles" in data %} style="{{ data.styles|join(', ') }}"{% endif %}
|
||||
>
|
||||
<div class="accordion-inner">
|
||||
{% include data._template ignore missing %}
|
||||
{% include data.template ignore missing %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% if "custom_bindings" in data and data["custom_bindings"] %}<!-- /ko -->{% endif %}
|
||||
{% if "custom_bindings" not in data or data["custom_bindings"] %}<!-- /ko -->{% endif %}
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
||||
|
|
@ -70,29 +70,29 @@
|
|||
<div class="span8 tabbable">
|
||||
<ul class="nav nav-tabs" id="tabs">
|
||||
{% for entry, data in tabEntries %}
|
||||
{% if "custom_bindings" in data and data["custom_bindings"] %}<!-- ko allowBindings: false -->{% endif %}
|
||||
{% if "custom_bindings" not in data or data["custom_bindings"] %}<!-- ko allowBindings: false -->{% endif %}
|
||||
<li id="{{ data._div }}_link"
|
||||
class="{% if loop.first %}active{% endif %} {% if "classes_link" in data %}{{ data.classes_link|join(' ') }}{% elif "classes" in data %}{{ data.classes|join(' ') }}{% endif %}"
|
||||
data-bind="{% if "data_bind" in data %}{{ data.data_bind }}{% elif "custom_bindings" in data and data["custom_bindings"] %}allowBindings: true{% endif %}"
|
||||
data-bind="{% if "data_bind" in data %}{{ data.data_bind }}{% elif "custom_bindings" not in data or data["custom_bindings"] %}allowBindings: true{% endif %}"
|
||||
{% if "styles_link" in data %} style="{{ data.styles_link|join(', ') }}" {% elif "styles" in data %} style="{{ data.styles|join(', ') }}" {% endif %}
|
||||
>
|
||||
<a href="#{{ data._div }}" data-toggle="tab">{{ entry }}</a>
|
||||
</li>
|
||||
{% if "custom_bindings" in data and data["custom_bindings"] %}<!-- /ko -->{% endif %}
|
||||
{% if "custom_bindings" not in data or data["custom_bindings"] %}<!-- /ko -->{% endif %}
|
||||
{% endfor %}
|
||||
</ul>
|
||||
|
||||
<div class="tab-content">
|
||||
{% for entry, data in tabEntries %}
|
||||
{% if "custom_bindings" in data and data["custom_bindings"] %}<!-- ko allowBindings: false -->{% endif %}
|
||||
{% if "custom_bindings" not in data or data["custom_bindings"] %}<!-- ko allowBindings: false -->{% endif %}
|
||||
<div id="{{ data._div }}"
|
||||
class="tab-pane{% if loop.first %} active{% endif %}{% if "additional_classes" in data %} {{ data.additional_classes|join(' ') }}{% endif %}"
|
||||
data-bind="{% if "data_bind" in data %}{{ data.data_bind }}{% elif "custom_bindings" in data and data["custom_bindings"] %}allowBindings: true{% endif %}"
|
||||
data-bind="{% if "data_bind" in data %}{{ data.data_bind }}{% elif "custom_bindings" not in data or data["custom_bindings"] %}allowBindings: true{% endif %}"
|
||||
{% if "styles_content" in data %} style="{{ data.styles_content|join(', ') }}" {% elif "styles" in data %} style="{{ data.styles|join(', ') }}" {% endif %}
|
||||
>
|
||||
{% include data._template ignore missing %}
|
||||
{% include data.template ignore missing %}
|
||||
</div>
|
||||
{% if "custom_bindings" in data and data["custom_bindings"] %}<!-- /ko -->{% endif %}
|
||||
{% if "custom_bindings" not in data or data["custom_bindings"] %}<!-- /ko -->{% endif %}
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -123,8 +123,8 @@
|
|||
<!-- End of overlays -->
|
||||
|
||||
<!-- Generic plugin template files -->
|
||||
{% for plugin_name in pluginNames %}
|
||||
{% include plugin_name + ".jinja2" ignore missing %}
|
||||
{% for data in genericEntries %}
|
||||
{% include data.template ignore missing %}
|
||||
{% endfor %}
|
||||
<!-- End of generic plugin template files -->
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue