Browse Source

chore: Finish ActionRow and fix ViewStore.add_view

pull/10166/head
DA-344 5 months ago
parent
commit
cbdc618e3e
  1. 198
      discord/ui/action_row.py
  2. 19
      discord/ui/view.py

198
discord/ui/action_row.py

@ -39,16 +39,20 @@ from typing import (
Type, Type,
TypeVar, TypeVar,
Union, Union,
overload,
) )
from .item import Item, ItemCallbackType from .item import Item, ItemCallbackType
from .button import Button from .button import Button
from .select import Select, SelectCallbackDecorator from .dynamic import DynamicItem
from .select import select as _select, Select, SelectCallbackDecorator, UserSelect, RoleSelect, ChannelSelect, MentionableSelect
from ..enums import ButtonStyle, ComponentType, ChannelType from ..enums import ButtonStyle, ComponentType, ChannelType
from ..partial_emoji import PartialEmoji from ..partial_emoji import PartialEmoji
from ..utils import MISSING from ..utils import MISSING
if TYPE_CHECKING: if TYPE_CHECKING:
from typing_extensions import Self
from .view import LayoutView from .view import LayoutView
from .select import ( from .select import (
BaseSelectT, BaseSelectT,
@ -122,11 +126,26 @@ class ActionRow(Item[V]):
for func in self.__action_row_children_items__: for func in self.__action_row_children_items__:
item: Item = func.__discord_ui_model_type__(**func.__discord_ui_model_kwargs__) item: Item = func.__discord_ui_model_type__(**func.__discord_ui_model_kwargs__)
item.callback = _ActionRowCallback(func, self, item) item.callback = _ActionRowCallback(func, self, item)
item._parent = self # type: ignore item._parent = getattr(func, '__discord_ui_parent__', self) # type: ignore
setattr(self, func.__name__, item) setattr(self, func.__name__, item)
children.append(item) children.append(item)
return children return children
def _update_store_data(self, dispatch_info: Dict, dynamic_items: Dict) -> bool:
is_fully_dynamic = True
for item in self._children:
if isinstance(item, DynamicItem):
pattern = item.__discord_ui_compiled_template__
dynamic_items[pattern] = item.__class__
elif item.is_dispatchable():
dispatch_info[(item.type.value, item.custom_id)] = item # type: ignore
is_fully_dynamic = False
return is_fully_dynamic
def is_dispatchable(self) -> bool:
return any(c.is_dispatchable() for c in self.children)
def _update_children_view(self, view: LayoutView) -> None: def _update_children_view(self, view: LayoutView) -> None:
for child in self._children: for child in self._children:
child._view = view child._view = view
@ -147,6 +166,77 @@ class ActionRow(Item[V]):
def type(self) -> Literal[ComponentType.action_row]: def type(self) -> Literal[ComponentType.action_row]:
return ComponentType.action_row return ComponentType.action_row
@property
def children(self) -> List[Item[V]]:
"""List[:class:`Item`]: The list of children attached to this action row."""
return self._children.copy()
def add_item(self, item: Item[Any]) -> Self:
"""Adds an item to this row.
This function returns the class instance to allow for fluent-style
chaining.
Parameters
----------
item: :class:`Item`
The item to add to the row.
Raises
------
TypeError
An :class:`Item` was not passed.
ValueError
Maximum number of children has been exceeded (5).
"""
if len(self._children) >= 5:
raise ValueError('maximum number of children exceeded')
if not isinstance(item, Item):
raise TypeError(f'expected Item not {item.__class__.__name__}')
self._children.append(item)
return self
def remove_item(self, item: Item[Any]) -> Self:
"""Removes an item from the row.
This function returns the class instance to allow for fluent-style
chaining.
Parameters
----------
item: :class:`Item`
The item to remove from the view.
"""
try:
self._children.remove(item)
except ValueError:
pass
return self
def clear_items(self) -> Self:
"""Removes all items from the row.
This function returns the class instance to allow for fluent-style
chaining.
"""
self._children.clear()
return self
def to_component_dict(self) -> Dict[str, Any]:
components = []
for item in self._children:
components.append(item.to_component_dict())
return {
'type': self.type.value,
'components': components,
}
def button( def button(
self, self,
*, *,
@ -192,6 +282,7 @@ class ActionRow(Item[V]):
if not inspect.iscoroutinefunction(func): if not inspect.iscoroutinefunction(func):
raise TypeError('button function must be a coroutine function') raise TypeError('button function must be a coroutine function')
func.__discord_ui_parent__ = self
func.__discord_ui_modal_type__ = Button func.__discord_ui_modal_type__ = Button
func.__discord_ui_model_kwargs__ = { func.__discord_ui_model_kwargs__ = {
'style': style, 'style': style,
@ -207,7 +298,90 @@ class ActionRow(Item[V]):
return decorator # type: ignore return decorator # type: ignore
@overload
def select( def select(
self,
*,
cls: Type[SelectT] = Select[Any],
options: List[SelectOption] = MISSING,
channel_types: List[ChannelType] = ...,
placeholder: Optional[str] = ...,
custom_id: str = ...,
min_values: int = ...,
max_values: int = ...,
disabled: bool = ...,
) -> SelectCallbackDecorator[V, SelectT]:
...
@overload
def select(
self,
*,
cls: Type[UserSelectT] = UserSelect[Any],
options: List[SelectOption] = MISSING,
channel_types: List[ChannelType] = ...,
placeholder: Optional[str] = ...,
custom_id: str = ...,
min_values: int = ...,
max_values: int = ...,
disabled: bool = ...,
default_values: Sequence[ValidDefaultValues] = ...,
) -> SelectCallbackDecorator[V, UserSelectT]:
...
@overload
def select(
self,
*,
cls: Type[RoleSelectT] = RoleSelect[Any],
options: List[SelectOption] = MISSING,
channel_types: List[ChannelType] = ...,
placeholder: Optional[str] = ...,
custom_id: str = ...,
min_values: int = ...,
max_values: int = ...,
disabled: bool = ...,
default_values: Sequence[ValidDefaultValues] = ...,
) -> SelectCallbackDecorator[V, RoleSelectT]:
...
@overload
def select(
self,
*,
cls: Type[ChannelSelectT] = ChannelSelect[Any],
options: List[SelectOption] = MISSING,
channel_types: List[ChannelType] = ...,
placeholder: Optional[str] = ...,
custom_id: str = ...,
min_values: int = ...,
max_values: int = ...,
disabled: bool = ...,
default_values: Sequence[ValidDefaultValues] = ...,
) -> SelectCallbackDecorator[V, ChannelSelectT]:
...
@overload
def select(
self,
*,
cls: Type[MentionableSelectT] = MentionableSelect[Any],
options: List[SelectOption] = MISSING,
channel_types: List[ChannelType] = MISSING,
placeholder: Optional[str] = ...,
custom_id: str = ...,
min_values: int = ...,
max_values: int = ...,
disabled: bool = ...,
default_values: Sequence[ValidDefaultValues] = ...,
) -> SelectCallbackDecorator[V, MentionableSelectT]:
...
def select(
self,
*, *,
cls: Type[BaseSelectT] = Select[Any], cls: Type[BaseSelectT] = Select[Any],
options: List[SelectOption] = MISSING, options: List[SelectOption] = MISSING,
@ -242,9 +416,6 @@ class ActionRow(Item[V]):
| :class:`discord.ui.ChannelSelect` | List[Union[:class:`~discord.app_commands.AppCommandChannel`, :class:`~discord.app_commands.AppCommandThread`]] | | :class:`discord.ui.ChannelSelect` | List[Union[:class:`~discord.app_commands.AppCommandChannel`, :class:`~discord.app_commands.AppCommandThread`]] |
+----------------------------------------+-----------------------------------------------------------------------------------------------------------------+ +----------------------------------------+-----------------------------------------------------------------------------------------------------------------+
.. versionchanged:: 2.1
Added the following keyword-arguments: ``cls``, ``channel_types``
Example Example
--------- ---------
.. code-block:: python3 .. code-block:: python3
@ -290,3 +461,20 @@ class ActionRow(Item[V]):
If ``cls`` is :class:`MentionableSelect` and :class:`.Object` is passed, then the type must be specified in the constructor. If ``cls`` is :class:`MentionableSelect` and :class:`.Object` is passed, then the type must be specified in the constructor.
Number of items must be in range of ``min_values`` and ``max_values``. Number of items must be in range of ``min_values`` and ``max_values``.
""" """
def decorator(func: ItemCallbackType[V, BaseSelectT]) -> ItemCallbackType[V, BaseSelectT]:
r = _select( # type: ignore
cls=cls, # type: ignore
placeholder=placeholder,
custom_id=custom_id,
min_values=min_values,
max_values=max_values,
options=options,
channel_types=channel_types,
disabled=disabled,
default_values=default_values,
)(func)
r.__discord_ui_parent__ = self
return r
return decorator # type: ignore

19
discord/ui/view.py

@ -601,6 +601,7 @@ class View: # NOTE: maybe add a deprecation warning in favour of LayoutView?
class LayoutView(View): class LayoutView(View):
__view_children_items__: ClassVar[List[Item[Any]]] = [] __view_children_items__: ClassVar[List[Item[Any]]] = []
__view_pending_children__: ClassVar[List[ItemCallbackType[Any, Any]]] = []
def __init__(self, *, timeout: Optional[float] = 180) -> None: def __init__(self, *, timeout: Optional[float] = 180) -> None:
super().__init__(timeout=timeout) super().__init__(timeout=timeout)
@ -608,20 +609,32 @@ class LayoutView(View):
def __init_subclass__(cls) -> None: def __init_subclass__(cls) -> None:
children: Dict[str, Item[Any]] = {} children: Dict[str, Item[Any]] = {}
pending: Dict[str, ItemCallbackType[Any, Any]] = {}
for base in reversed(cls.__mro__): for base in reversed(cls.__mro__):
for name, member in base.__dict__.items(): for name, member in base.__dict__.items():
if isinstance(member, Item): if isinstance(member, Item):
children[name] = member children[name] = member
elif hasattr(member, '__discord_ui_model_type__') and getattr(member, '__discord_ui_parent__', None):
pending[name] = member
if len(children) > 10: if len(children) > 10:
raise TypeError('LayoutView cannot have more than 10 top-level children') raise TypeError('LayoutView cannot have more than 10 top-level children')
cls.__view_children_items__ = list(children.values()) cls.__view_children_items__ = list(children.values())
cls.__view_pending_children__ = list(pending.values())
def _init_children(self) -> List[Item[Self]]: def _init_children(self) -> List[Item[Self]]:
children = [] children = []
for func in self.__view_pending_children__:
item: Item = func.__discord_ui_model_type__(**func.__discord_ui_model_kwargs__)
item.callback = _ViewCallback(func, self, item)
item._view = self
setattr(self, func.__name__, item)
parent: ActionRow = func.__discord_ui_parent__
parent.add_item(item)
for i in self.__view_children_items__: for i in self.__view_children_items__:
if isinstance(i, Item): if isinstance(i, Item):
if getattr(i, '_parent', None): if getattr(i, '_parent', None):
@ -639,6 +652,7 @@ class LayoutView(View):
raise TypeError( raise TypeError(
'LayoutView can only have items' 'LayoutView can only have items'
) )
return children return children
def _is_v2(self) -> bool: def _is_v2(self) -> bool:
@ -709,6 +723,11 @@ class ViewStore:
dispatch_info, dispatch_info,
self._dynamic_items, self._dynamic_items,
) )
elif getattr(item, '__discord_ui_action_row__', False):
is_fully_dynamic = item._update_store_data( # type: ignore
dispatch_info,
self._dynamic_items,
) or is_fully_dynamic
else: else:
dispatch_info[(item.type.value, item.custom_id)] = item # type: ignore dispatch_info[(item.type.value, item.custom_id)] = item # type: ignore
is_fully_dynamic = False is_fully_dynamic = False

Loading…
Cancel
Save