From 34f956cc9dc2e08fbb0f2fff26f17ff53a203fe2 Mon Sep 17 00:00:00 2001 From: tandemdude <43570299+tandemdude@users.noreply.github.com> Date: Tue, 13 Aug 2024 17:51:46 +0100 Subject: [PATCH] doc: finish usage guide for menus and modals --- lightbulb/__init__.py | 2 +- lightbulb/components/__init__.py | 101 ++++++++++++++++++++++++++++--- lightbulb/components/modals.py | 3 +- 3 files changed, 97 insertions(+), 9 deletions(-) diff --git a/lightbulb/__init__.py b/lightbulb/__init__.py index 8ff42ef6..39fccc1d 100644 --- a/lightbulb/__init__.py +++ b/lightbulb/__init__.py @@ -18,7 +18,7 @@ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. -"""A simple, elegant and powerful command handler for Hikari.""" +"""A simple, elegant, and powerful command handler for Hikari.""" from lightbulb import components from lightbulb import di diff --git a/lightbulb/components/__init__.py b/lightbulb/components/__init__.py index 2b7cab1c..1addb91d 100644 --- a/lightbulb/components/__init__.py +++ b/lightbulb/components/__init__.py @@ -35,8 +35,6 @@ .. dropdown:: Example - Creating a menu. - .. code-block:: python import lightbulb @@ -73,8 +71,6 @@ def __init__(self) -> None: .. dropdown:: Example - Adding a component to a menu. - .. code-block:: python import lightbulb @@ -112,10 +108,9 @@ async def on_button_press(self, ctx: lightbulb.components.MenuContext) -> None: .. dropdown:: Example - Attaching the menu to a client instance within a command. - .. code-block:: python + import asyncio import lightbulb class MyMenu(lightbulb.components.Menu): @@ -190,7 +185,99 @@ async def on_select(self, ctx: lightbulb.components.MenuContext) -> None: Modal Handling -------------- -bar +Creating a Modal +^^^^^^^^^^^^^^^^ + +Modals are handled in a very similar way to components. Instead of subclassing ``Menu``, you will instead +have to subclass :obj:`~lightbulb.components.modals.Modal`. + +.. dropdown:: Example + + .. code-block:: python + + import lightbulb + + class MyModal(lightbulb.components.Modal): + def __init__(self) -> None: + ... + +Adding Components to Modals +^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Like menus, you can add components to modals using the relevant methods: + +- :meth:`~lightbulb.components.modals.Modal.add_short_text_input` +- :meth:`~lightbulb.components.modals.Modal.add_paragraph_text_input` + +Just like menus, the modal will lay the added components out into rows automatically. You can use the same methods +``next_row``, ``previous_row``, etc. to further customise how the layout is created. + +When you add a component, the created component object is returned. You should store this within an instance variable +- it will be needed later in order to get the submitted value from the modal context. + +.. important:: + Your modal subclass **must** implement the ``on_submit`` method. This will be called when an interaction for + the modal is received and should perform any logic you require. + +.. dropdown:: Example + + .. code-block:: python + + import lightbulb + + class MyModal(lightbulb.components.Modal): + def __init__(self) -> None: + self.text = self.add_short_text_input("Enter some text") + + async def on_submit(self, ctx: lightbulb.components.ModalContext) -> None: + await ctx.respond(f"submitted: {ctx.value_for(self.text)}") + +Running Modals +^^^^^^^^^^^^^^ + +Sending a modal with a response is similar to using a menu - you should pass the modal instance to the ``components=`` +argument of ``respond_with_modal`` of the context or interaction. + +Like menus, you need the Lightbulb :obj:`~lightbulb.client.Client` instance in order for it to listen for the +relevant interaction. However, unlike menus, when attaching a modal to the client it will **always** wait for the +interaction to be received before continuing. You must also pass a timeout after which an :obj:`asyncio.TimeoutError` +will be raised - if you do not pass a timeout, it will default to 30 seconds. + +When attaching a modal to the client, you must pass the same custom ID you used when sending the modal response, +otherwise Lightbulb will not be able to resolve the correct interaction for the modal submission. + +To get your ``Client`` instance within a command, you can use dependency injection as seen in the following example. +Check the "Dependencies" guide within the by-example section of the documentation for more details about dependency +injection. + +.. dropdown:: Example + + .. code-block:: python + + import asyncio + import uuid + import lightbulb + + class MyModal(lightbulb.components.Modal): + def __init__(self) -> None: + self.text = self.add_short_text_input("Enter some text") + + async def on_submit(self, ctx: lightbulb.components.ModalContext) -> None: + await ctx.respond(f"submitted: {ctx.value_for(self.text)}") + + class MyCommand(lightbulb.SlashCommand, name="test", description="test"): + @lightbulb.invoke + async def invoke(self, ctx: lightbulb.Context, client: lightbulb.Client) -> None: + modal = MyModal() + + # Using a uuid as the custom ID for this modal means it is very unlikely that there will + # be any custom ID conflicts - if you used a set value instead then it may pick up a submission + # from a previous or future invocation of this command + await ctx.respond_with_modal("Test Modal", c_id := str(uuid.uuid4()), components=modal) + try: + await modal.attach(client, c_id) + except asyncio.TimeoutError: + await ctx.respond("Modal timed out") ---- """ diff --git a/lightbulb/components/modals.py b/lightbulb/components/modals.py index 9e80dd90..9aae8259 100644 --- a/lightbulb/components/modals.py +++ b/lightbulb/components/modals.py @@ -35,6 +35,7 @@ from hikari.api import special_endpoints from hikari.impl import special_endpoints as special_endpoints_impl +from lightbulb import context from lightbulb.components import base if t.TYPE_CHECKING: @@ -94,7 +95,7 @@ def add_to_row(self, row: special_endpoints.ModalActionRowBuilder) -> special_en ) -class ModalContext(base.MessageResponseMixinWithEdit[hikari.ModalInteraction]): +class ModalContext(context.MessageResponseMixin[hikari.ModalInteraction]): """Class representing the context for a modal interaction.""" __slots__ = ("_interaction", "modal")