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 . import __version__, utils
from .utils import MISSING
from .flags import MessageFlags
_log = logging.getLogger(__name__)
@ -66,7 +67,6 @@ if TYPE_CHECKING:
from .ui.view import View
from .embeds import Embed
from .message import Attachment
from .flags import MessageFlags
from .poll import Poll
from .types import (

46
discord/ui/container.py

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

16
discord/ui/section.py

@ -58,14 +58,14 @@ class Section(Item[V]):
def __init__(
self,
children: List[Union[TextDisplay[Any], str]],
children: List[Union[Item[Any], str]],
*,
accessory: Optional[Item[Any]] = None,
) -> None:
if len(children) > 3:
raise ValueError('maximum number of children exceeded')
self._children: List[TextDisplay[Any]] = [
c if isinstance(c, TextDisplay) else TextDisplay(c) for c in children
self._children: List[Item[Any]] = [
c if isinstance(c, Item) else TextDisplay(c) for c in children
]
self.accessory: Optional[Item[Any]] = accessory
@ -76,7 +76,7 @@ class Section(Item[V]):
def _is_v2(self) -> bool:
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.
This function returns the class instance to allow for fluent-style
@ -98,15 +98,15 @@ class Section(Item[V]):
if len(self._children) >= 3:
raise ValueError('maximum number of children exceeded')
if not isinstance(item, (TextDisplay, str)):
raise TypeError(f'expected TextDisplay or str not {item.__class__.__name__}')
if not isinstance(item, (Item, str)):
raise TypeError(f'expected Item or str not {item.__class__.__name__}')
self._children.append(
item if isinstance(item, TextDisplay) else TextDisplay(item),
item if isinstance(item, Item) else TextDisplay(item),
)
return self
def remove_item(self, item: TextDisplay[Any]) -> Self:
def remove_item(self, item: Item[Any]) -> Self:
"""Removes an item from this section.
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 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 itertools import groupby
@ -95,13 +95,11 @@ class _ViewWeights:
# fmt: off
__slots__ = (
'weights',
'max_weight',
)
# 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.max_weight: int = 5 if container is False else 10
key = lambda i: sys.maxsize if i.row is None else i.row
children = sorted(children, key=key)
@ -109,18 +107,26 @@ class _ViewWeights:
for item in group:
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):
if weight + item.width <= self.max_weight:
if weight + item.width <= 5:
return index
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:
total = self.weights[item.row] + item.width
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
item._rendered_row = item.row
else:
@ -128,7 +134,7 @@ class _ViewWeights:
self.weights[index] += item.width
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:
self.weights[item._rendered_row] -= item.width
item._rendered_row = None
@ -136,6 +142,9 @@ class _ViewWeights:
def clear(self) -> None:
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:
__slots__ = ('view', 'callback', 'item')
@ -176,6 +185,8 @@ class View:
for name, member in base.__dict__.items():
if hasattr(member, '__discord_ui_model_type__'):
children[name] = member
if cls.__discord_ui_container__ and isinstance(member, View):
children[name] = member
if len(children) > 25:
raise TypeError('View cannot have more than 25 children')
@ -192,16 +203,25 @@ class View:
children.append(item)
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._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._cache_key: Optional[int] = None
self.__cancel_callback: Optional[Callable[[View], None]] = None
self.__timeout_expiry: Optional[float] = None
self.__timeout_task: Optional[asyncio.Task[None]] = None
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:
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
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
if message_id is not None and not is_fully_dynamic:
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 None:
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:
data['components'] = []

Loading…
Cancel
Save