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.
This commit is contained in:
parent
990a44c7c9
commit
08c14e2e45
5 changed files with 60 additions and 7 deletions
|
|
@ -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<sec-features-custom_controls>`.
|
||||
interface. See :ref:`the custom control feature<sec-features-custom_controls>`.
|
||||
|
||||
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:
|
||||
|
|
|
|||
|
|
@ -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 ``<plugin identifier>_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 <sec-plugins-viewmodels>`
|
||||
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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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")),
|
||||
|
|
|
|||
|
|
@ -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 %}
|
||||
>
|
||||
<a href="#{{ data._div }}" data-toggle="tab">{{ entry }}</a>
|
||||
<a href="#{{ data._div }}" data-toggle="tab">{% if "mandatory" in data and data.mandatory %}<strong>{{ entry }}</strong>{% else %}{{ entry }}{% endif %}</a>
|
||||
</li>
|
||||
{% if "custom_bindings" not in data or data["custom_bindings"] %}<!-- /ko -->{% 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 %}
|
||||
<div class="alert">{% trans %}
|
||||
<strong>Mandatory Step!</strong> You need to fill this out now.
|
||||
{% endtrans %}</div>
|
||||
{% endif %}
|
||||
{% include data.template ignore missing %}
|
||||
</div>
|
||||
{% if "custom_bindings" not in data or data["custom_bindings"] %}<!-- /ko -->{% endif %}
|
||||
{% endif %}
|
||||
|
|
@ -54,5 +59,8 @@
|
|||
<div class="pull-left">
|
||||
<button class="btn button-previous" name="previous">{{ _('Previous') }}</button>
|
||||
</div>
|
||||
<div class="text-center" style="line-height: 20px; padding: 4px 12px;">
|
||||
{{ _('Unless otherwise noted, you may just skip any wizard page by clicking "Next" or "Finish".') }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
Loading…
Reference in a new issue