Browse Source

chore: more things to components v2

pull/10166/head
DA-344 3 months ago
parent
commit
86897182ba
  1. 2
      discord/http.py
  2. 46
      discord/ui/container.py
  3. 16
      discord/ui/section.py
  4. 55
      discord/ui/view.py
  5. 7
      discord/webhook/async_.py

2
discord/http.py

@ -57,6 +57,7 @@ from .file import File
from .mentions import AllowedMentions from .mentions import AllowedMentions
from . import __version__, utils from . import __version__, utils
from .utils import MISSING from .utils import MISSING
from .flags import MessageFlags
_log = logging.getLogger(__name__) _log = logging.getLogger(__name__)
@ -66,7 +67,6 @@ if TYPE_CHECKING:
from .ui.view import View from .ui.view import View
from .embeds import Embed from .embeds import Embed
from .message import Attachment from .message import Attachment
from .flags import MessageFlags
from .poll import Poll from .poll import Poll
from .types import ( from .types import (

46
discord/ui/container.py

@ -23,16 +23,16 @@ DEALINGS IN THE SOFTWARE.
""" """
from __future__ import annotations from __future__ import annotations
from typing import TYPE_CHECKING, Any, Dict, List, Literal, Optional, TypeVar import sys
from typing import TYPE_CHECKING, Any, Dict, List, Literal, Optional, TypeVar, Union
from .item import Item from .item import Item
from .view import View, _component_to_item
from ..enums import ComponentType from ..enums import ComponentType
if TYPE_CHECKING: if TYPE_CHECKING:
from typing_extensions import Self from typing_extensions import Self
from .view import View
from ..colour import Colour, Color from ..colour import Colour, Color
from ..components import Container as ContainerComponent from ..components import Container as ContainerComponent
@ -41,15 +41,16 @@ V = TypeVar('V', bound='View', covariant=True)
__all__ = ('Container',) __all__ = ('Container',)
class Container(Item[V]): class Container(View, Item[V]):
"""Represents a Components V2 Container. """Represents a Components V2 Container.
.. versionadded:: 2.6 .. versionadded:: 2.6
Parameters Parameters
---------- ----------
children: List[:class:`Item`] children: List[Union[:class:`Item`, :class:`View`]]
The initial children of this container. The initial children or :class:`View`s of this container. Can have up to 10
items.
accent_colour: Optional[:class:`~discord.Colour`] accent_colour: Optional[:class:`~discord.Colour`]
The colour of the container. Defaults to ``None``. The colour of the container. Defaults to ``None``.
accent_color: Optional[:class:`~discord.Color`] accent_color: Optional[:class:`~discord.Color`]
@ -57,31 +58,34 @@ class Container(Item[V]):
spoiler: :class:`bool` spoiler: :class:`bool`
Whether to flag this container as a spoiler. Defaults Whether to flag this container as a spoiler. Defaults
to ``False``. to ``False``.
timeout: Optional[:class:`float`]
The timeout to set to this container items. Defaults to ``180``.
""" """
__discord_ui_container__ = True __discord_ui_container__ = True
def __init__( def __init__(
self, self,
children: List[Item[Any]], children: List[Union[Item[Any], View]],
*, *,
accent_colour: Optional[Colour] = None, accent_colour: Optional[Colour] = None,
accent_color: Optional[Color] = None, accent_color: Optional[Color] = None,
spoiler: bool = False, spoiler: bool = False,
timeout: Optional[float] = 180,
) -> None: ) -> None:
self._children: List[Item[Any]] = children if len(children) > 10:
raise ValueError('maximum number of components exceeded')
self._children: List[Union[Item[Any], View]] = children
self.spoiler: bool = spoiler self.spoiler: bool = spoiler
self._colour = accent_colour or accent_color self._colour = accent_colour or accent_color
super().__init__(timeout=timeout)
@property @property
def children(self) -> List[Item[Any]]: def children(self) -> List[Union[Item[Any], View]]:
"""List[:class:`Item`]: The children of this container.""" """List[:class:`Item`]: The children of this container."""
return self._children.copy() return self._children.copy()
@children.setter @children.setter
def children(self, value: List[Item[Any]]) -> None: def children(self, value: List[Union[Item[Any], View]]) -> None:
self._children = value self._children = value
@property @property
@ -100,22 +104,24 @@ class Container(Item[V]):
def type(self) -> Literal[ComponentType.container]: def type(self) -> Literal[ComponentType.container]:
return ComponentType.container return ComponentType.container
@property
def _views(self) -> List[View]:
return [c for c in self._children if isinstance(c, View)]
def _is_v2(self) -> bool: def _is_v2(self) -> bool:
return True return True
def to_component_dict(self) -> Dict[str, Any]: def to_components(self) -> List[Dict[str, Any]]:
base = { components = super().to_components()
return [{
'type': self.type.value, 'type': self.type.value,
'accent_color': self._colour.value if self._colour else None,
'spoiler': self.spoiler, 'spoiler': self.spoiler,
'components': [c.to_component_dict() for c in self._children] 'components': components,
} }]
if self._colour is not None:
base['accent_color'] = self._colour.value
return base
@classmethod @classmethod
def from_component(cls, component: ContainerComponent) -> Self: def from_component(cls, component: ContainerComponent) -> Self:
from .view import _component_to_item
return cls( return cls(
children=[_component_to_item(c) for c in component.children], children=[_component_to_item(c) for c in component.children],
accent_colour=component.accent_colour, accent_colour=component.accent_colour,

16
discord/ui/section.py

@ -58,14 +58,14 @@ class Section(Item[V]):
def __init__( def __init__(
self, self,
children: List[Union[TextDisplay[Any], str]], children: List[Union[Item[Any], str]],
*, *,
accessory: Optional[Item[Any]] = None, accessory: Optional[Item[Any]] = None,
) -> None: ) -> None:
if len(children) > 3: if len(children) > 3:
raise ValueError('maximum number of children exceeded') raise ValueError('maximum number of children exceeded')
self._children: List[TextDisplay[Any]] = [ self._children: List[Item[Any]] = [
c if isinstance(c, TextDisplay) else TextDisplay(c) for c in children c if isinstance(c, Item) else TextDisplay(c) for c in children
] ]
self.accessory: Optional[Item[Any]] = accessory self.accessory: Optional[Item[Any]] = accessory
@ -76,7 +76,7 @@ class Section(Item[V]):
def _is_v2(self) -> bool: def _is_v2(self) -> bool:
return True return True
def add_item(self, item: Union[str, TextDisplay[Any]]) -> Self: def add_item(self, item: Union[str, Item[Any]]) -> Self:
"""Adds an item to this section. """Adds an item to this section.
This function returns the class instance to allow for fluent-style This function returns the class instance to allow for fluent-style
@ -98,15 +98,15 @@ class Section(Item[V]):
if len(self._children) >= 3: if len(self._children) >= 3:
raise ValueError('maximum number of children exceeded') raise ValueError('maximum number of children exceeded')
if not isinstance(item, (TextDisplay, str)): if not isinstance(item, (Item, str)):
raise TypeError(f'expected TextDisplay or str not {item.__class__.__name__}') raise TypeError(f'expected Item or str not {item.__class__.__name__}')
self._children.append( self._children.append(
item if isinstance(item, TextDisplay) else TextDisplay(item), item if isinstance(item, Item) else TextDisplay(item),
) )
return self return self
def remove_item(self, item: TextDisplay[Any]) -> Self: def remove_item(self, item: Item[Any]) -> Self:
"""Removes an item from this section. """Removes an item from this section.
This function returns the class instance to allow for fluent-style This function returns the class instance to allow for fluent-style

55
discord/ui/view.py

@ -23,7 +23,7 @@ DEALINGS IN THE SOFTWARE.
""" """
from __future__ import annotations from __future__ import annotations
from typing import Any, Callable, ClassVar, Coroutine, Dict, Iterator, List, Optional, Sequence, TYPE_CHECKING, Tuple, Type from typing import Any, Callable, ClassVar, Coroutine, Dict, Iterator, List, Optional, Sequence, TYPE_CHECKING, Tuple, Type, Union
from functools import partial from functools import partial
from itertools import groupby from itertools import groupby
@ -95,13 +95,11 @@ class _ViewWeights:
# fmt: off # fmt: off
__slots__ = ( __slots__ = (
'weights', 'weights',
'max_weight',
) )
# fmt: on # fmt: on
def __init__(self, children: List[Item], container: bool): def __init__(self, children: List[Item]):
self.weights: List[int] = [0, 0, 0, 0, 0] self.weights: List[int] = [0, 0, 0, 0, 0]
self.max_weight: int = 5 if container is False else 10
key = lambda i: sys.maxsize if i.row is None else i.row key = lambda i: sys.maxsize if i.row is None else i.row
children = sorted(children, key=key) children = sorted(children, key=key)
@ -109,18 +107,26 @@ class _ViewWeights:
for item in group: for item in group:
self.add_item(item) self.add_item(item)
def find_open_space(self, item: Item) -> int: def find_open_space(self, item: Union[Item, View]) -> int:
for index, weight in enumerate(self.weights): for index, weight in enumerate(self.weights):
if weight + item.width <= self.max_weight: if weight + item.width <= 5:
return index return index
raise ValueError('could not find open space for item') raise ValueError('could not find open space for item')
def add_item(self, item: Item) -> None: def add_item(self, item: Union[Item, View]) -> None:
if hasattr(item, '__discord_ui_container__') and item.__discord_ui_container__ is True:
raise TypeError(
'containers cannot be added to views'
)
if item._is_v2() and not self.v2_weights():
# v2 components allow up to 10 rows
self.weights.extend([0, 0, 0, 0, 0])
if item.row is not None: if item.row is not None:
total = self.weights[item.row] + item.width total = self.weights[item.row] + item.width
if total > 10: if total > 10:
raise ValueError(f'item would not fit at row {item.row} ({total} > {self.max_weight} width)') raise ValueError(f'item would not fit at row {item.row} ({total} > 5 width)')
self.weights[item.row] = total self.weights[item.row] = total
item._rendered_row = item.row item._rendered_row = item.row
else: else:
@ -128,7 +134,7 @@ class _ViewWeights:
self.weights[index] += item.width self.weights[index] += item.width
item._rendered_row = index item._rendered_row = index
def remove_item(self, item: Item) -> None: def remove_item(self, item: Union[Item, View]) -> None:
if item._rendered_row is not None: if item._rendered_row is not None:
self.weights[item._rendered_row] -= item.width self.weights[item._rendered_row] -= item.width
item._rendered_row = None item._rendered_row = None
@ -136,6 +142,9 @@ class _ViewWeights:
def clear(self) -> None: def clear(self) -> None:
self.weights = [0, 0, 0, 0, 0] self.weights = [0, 0, 0, 0, 0]
def v2_weights(self) -> bool:
return sum(1 if w > 0 else 0 for w in self.weights) > 5
class _ViewCallback: class _ViewCallback:
__slots__ = ('view', 'callback', 'item') __slots__ = ('view', 'callback', 'item')
@ -176,6 +185,8 @@ class View:
for name, member in base.__dict__.items(): for name, member in base.__dict__.items():
if hasattr(member, '__discord_ui_model_type__'): if hasattr(member, '__discord_ui_model_type__'):
children[name] = member children[name] = member
if cls.__discord_ui_container__ and isinstance(member, View):
children[name] = member
if len(children) > 25: if len(children) > 25:
raise TypeError('View cannot have more than 25 children') raise TypeError('View cannot have more than 25 children')
@ -192,16 +203,25 @@ class View:
children.append(item) children.append(item)
return children return children
def __init__(self, *, timeout: Optional[float] = 180.0): def __init__(self, *, timeout: Optional[float] = 180.0, row: Optional[int] = None):
self.__timeout = timeout self.__timeout = timeout
self._children: List[Item[Self]] = self._init_children() self._children: List[Item[Self]] = self._init_children()
self.__weights = _ViewWeights(self._children, self.__discord_ui_container__) self.__weights = _ViewWeights(self._children)
self.id: str = os.urandom(16).hex() self.id: str = os.urandom(16).hex()
self._cache_key: Optional[int] = None self._cache_key: Optional[int] = None
self.__cancel_callback: Optional[Callable[[View], None]] = None self.__cancel_callback: Optional[Callable[[View], None]] = None
self.__timeout_expiry: Optional[float] = None self.__timeout_expiry: Optional[float] = None
self.__timeout_task: Optional[asyncio.Task[None]] = None self.__timeout_task: Optional[asyncio.Task[None]] = None
self.__stopped: asyncio.Future[bool] = asyncio.get_running_loop().create_future() self.__stopped: asyncio.Future[bool] = asyncio.get_running_loop().create_future()
self.row: Optional[int] = row
self._rendered_row: Optional[int] = None
def _is_v2(self) -> bool:
return False
@property
def width(self):
return 5
def __repr__(self) -> str: def __repr__(self) -> str:
return f'<{self.__class__.__name__} timeout={self.timeout} children={len(self._children)}>' return f'<{self.__class__.__name__} timeout={self.timeout} children={len(self._children)}>'
@ -602,6 +622,19 @@ class ViewStore:
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
# components V2 containers allow for views to exist inside them
# with dispatchable items, so we iterate over it and add it
# to the store
if hasattr(view, '_views'):
for v in view._views:
for item in v._children:
if isinstance(item, DynamicItem):
pattern = item.__discord_ui_compiled_template__
self._dynamic_items[pattern] = item.__class__
elif item.is_dispatchable():
dispatch_info[(item.type.value, item.custom_id)] = item
is_fully_dynamic = False
view._cache_key = message_id view._cache_key = message_id
if message_id is not None and not is_fully_dynamic: if message_id is not None and not is_fully_dynamic:
self._synced_message_views[message_id] = view self._synced_message_views[message_id] = view

7
discord/webhook/async_.py

@ -592,6 +592,13 @@ def interaction_message_response_params(
if view is not MISSING: if view is not MISSING:
if view is not None: if view is not None:
data['components'] = view.to_components() data['components'] = view.to_components()
if view.has_components_v2():
if flags is not MISSING:
flags.components_v2 = True
else:
flags = MessageFlags(components_v2=True)
else: else:
data['components'] = [] data['components'] = []

Loading…
Cancel
Save