From 08c14e2e45e3d0078dd7d17c3e87ce133004d092 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gina=20H=C3=A4u=C3=9Fge?= Date: Fri, 21 Aug 2015 18:24:25 +0200 Subject: [PATCH] Wizard templates can now indicate they are mandatory This will only style them in a special way and influence sorting. Plugins still need to ensure that a step is not skipped through the use of the onWizardTabChange callback in the view model. --- docs/plugins/viewmodels.rst | 23 ++++++++++++++++- src/octoprint/plugin/types.py | 25 +++++++++++++++++-- src/octoprint/plugins/corewizard/__init__.py | 3 +++ src/octoprint/server/views.py | 4 +-- src/octoprint/templates/dialogs/wizard.jinja2 | 12 +++++++-- 5 files changed, 60 insertions(+), 7 deletions(-) diff --git a/docs/plugins/viewmodels.rst b/docs/plugins/viewmodels.rst index d5b1ddcc..6d736e7d 100644 --- a/docs/plugins/viewmodels.rst +++ b/docs/plugins/viewmodels.rst @@ -171,7 +171,7 @@ onAfterTabChange getAdditionalControls Your viewmodel may return additional custom control definitions for inclusion on the "Control" tab of OctoPrint's - interface. See :ref:`the custom control feature`. + interface. See :ref:`the custom control feature`. onSettingsShown Called when the settings dialog is shown. @@ -189,6 +189,27 @@ onUserSettingsShown onUserSettingsHidden Called when the user settings dialog is hidden. +onWizardDetails + Called with the response from the wizard detail API call initiated before opening the wizard dialog. Will contain + the data from all :class:`~octoprint.plugin.WizardPlugin` implementations returned by their :meth:`~octoprint.plugin.WizardPlugin.get_wizard_details` + method, mapped by the plugin identifier. + +onWizardTabChange + Called before the wizard tab/step is changed, with the ids of the current and the next tab as parameters. Return false + in order to prevent the tab change, e.g. if the wizard step is mandatory and not yet completed by the user. Take a look at + the "Core Wizard" plugin bundled with OctoPrint and the ACL wizard step in particular for an example on how to use this. + +onAfterWizardTabChange + Called after the wizard tab/step is changed, with the id of the current tab as parameter. + +onBeforeWizardFinish + Called before executing the finishing of the wizard. Return false here to stop the actual finish, e.g. if some step is + still incomplete. + +onWizardFinish + Called after executing the finishing of the wizard and before closing the dialog. Return ``reload`` here in order to + instruct OctoPrint to reload the UI after the wizard closes. + In order to hook into any of those callbacks, just have your viewmodel define a function named accordingly, e.g. to get called after all viewmodels have been bound during application startup, implement a function ``onAllBound`` on your viewmodel, taking a list of all bound viewmodels: diff --git a/src/octoprint/plugin/types.py b/src/octoprint/plugin/types.py index f42770a0..252c04e2 100644 --- a/src/octoprint/plugin/types.py +++ b/src/octoprint/plugin/types.py @@ -252,8 +252,10 @@ class TemplatePlugin(OctoPrintPlugin, ReloadNeedingPlugin): Wizards Plugins may define wizard dialogs to display to the user if necessary (e.g. in case of missing information that needs to be queried from the user to make the plugin work). Note that with the current implementations, all - wizard dialogs will always be sorted alphabetically by their ``name``. A wizard dialog provided through a - plugin will only be displayed if the plugin reports the wizard as being required through :meth:`~octoprint.plugin.WizardPlugin.is_wizard_required`. + wizard dialogs will be will always be sorted by their ``mandatory`` attribute (which defaults to ``False``) and then + alphabetically by their ``name``. Hence, mandatory wizard steps will come first, sorted alphabetically, then the + optional steps will follow, also alphabetically. A wizard dialog provided through a plugin will only be displayed + if the plugin reports the wizard as being required through :meth:`~octoprint.plugin.WizardPlugin.is_wizard_required`. Please also refer to the :class:`~octoprint.plugin.WizardPlugin` mixin for further details on this. The included template must be called ``_wizard.jinja2`` (e.g. ``myplugin_wizard.jinja2``) unless @@ -263,6 +265,14 @@ class TemplatePlugin(OctoPrintPlugin, ReloadNeedingPlugin): The wrapper div and the link in the wizard navigation will have the additional classes and styles applied as defined via the supplied configuration supplied through :func:`get_template_configs`. + .. note:: + + A note about ``mandatory`` wizard steps: In the current implementation, marking a wizard step as + mandatory will *only* make it styled accordingly. It is the task of the :ref:`view model ` + to actually prevent the user from skipping the dialog by implementing the ``onWizardTabChange`` + callback and returning ``false`` there if it is detected that the user hasn't yet filled in the + wizard step. + 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. @@ -410,6 +420,17 @@ class TemplatePlugin(OctoPrintPlugin, ReloadNeedingPlugin): * - styles_content - Like ``styles`` but only applied to the content pane itself. + ``wizard`` type + + .. list-table:: + :widths: 5 95 + + * - mandatory + - Whether the wizard step is mandatory (True) or not (False). Optional, + defaults to False. If set to True, OctoPrint will sort visually mark + the step as mandatory in the UI (bold in the navigation and a little + alert) and also sort it into the first half. + .. note:: As already outlined above, each template type has a default template name (i.e. the default navbar template diff --git a/src/octoprint/plugins/corewizard/__init__.py b/src/octoprint/plugins/corewizard/__init__.py index c33276cf..75f16e78 100644 --- a/src/octoprint/plugins/corewizard/__init__.py +++ b/src/octoprint/plugins/corewizard/__init__.py @@ -76,6 +76,9 @@ class CoreWizardPlugin(octoprint.plugin.AssetPlugin, def _get_acl_wizard_name(self): return "Access Control" + def _get_acl_additional_wizard_template_data(self): + return dict(mandatory=self._is_acl_wizard_required()) + @octoprint.plugin.BlueprintPlugin.route("/acl", methods=["POST"]) def acl_wizard_api(self): from flask import request diff --git a/src/octoprint/server/views.py b/src/octoprint/server/views.py index a65f9281..e4661c68 100644 --- a/src/octoprint/server/views.py +++ b/src/octoprint/server/views.py @@ -59,7 +59,7 @@ def index(): tab=dict(add="append", key="name"), settings=dict(add="custom_append", key="name", custom_add_entries=lambda missing: dict(section_plugins=(gettext("Plugins"), None)), custom_add_order=lambda missing: ["section_plugins"] + missing), usersettings=dict(add="append", key="name"), - wizard=dict(add="append", key="name"), + wizard=dict(add="append", key="name", key_extractor=lambda d, k: "0:{}".format(d[0]) if "mandatory" in d[1] and d[1]["mandatory"] else "1:{}".format(d[0])), generic=dict(add="append", key=None) ) @@ -178,7 +178,7 @@ def index(): return ["firstrunstart"] + existing + missing + ["firstrunend"] - template_sorting["wizard"] = dict(add="custom_insert", key="name", custom_insert_entries=lambda missing: dict(), custom_insert_order=custom_insert_order) + template_sorting["wizard"].update(dict(add="custom_insert", custom_insert_entries=lambda missing: dict(), custom_insert_order=custom_insert_order)) templates["wizard"]["entries"] = dict( firstrunstart=(gettext("Start"), dict(template="dialogs/wizard/firstrun_start.jinja2", _div="wizard_firstrun_start")), firstrunend=(gettext("Finish"), dict(template="dialogs/wizard/firstrun_end.jinja2", _div="wizard_firstrun_end")), diff --git a/src/octoprint/templates/dialogs/wizard.jinja2 b/src/octoprint/templates/dialogs/wizard.jinja2 index a2675176..c0023a28 100644 --- a/src/octoprint/templates/dialogs/wizard.jinja2 +++ b/src/octoprint/templates/dialogs/wizard.jinja2 @@ -19,7 +19,7 @@ class="{% if mark_active %}active{% set mark_active = False %}{% 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 %} > - {{ entry }} + {% if "mandatory" in data and data.mandatory %}{{ entry }}{% else %}{{ entry }}{% endif %} {% if "custom_bindings" not in data or data["custom_bindings"] %}{% endif %} {% endif %} @@ -37,7 +37,12 @@ class="tab-pane {% if mark_active %}active{% set mark_active = False %}{% 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 %} + {% if "mandatory" in data and data.mandatory %} +
{% trans %} + Mandatory Step! You need to fill this out now. + {% endtrans %}
+ {% endif %} + {% include data.template ignore missing %} {% if "custom_bindings" not in data or data["custom_bindings"] %}{% endif %} {% endif %} @@ -54,5 +59,8 @@
+
+ {{ _('Unless otherwise noted, you may just skip any wizard page by clicking "Next" or "Finish".') }} +