-
-
Notifications
You must be signed in to change notification settings - Fork 482
feat: FileUpload in Modal #2938
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
d8e1eef
9d3b32b
f32b688
9af88cf
b025df5
590a7a2
9c42603
498c28b
033f684
14665b6
7ba0276
a00d79e
a903005
6e36204
7a66cb7
7bff68a
b356c75
4478fcb
9a08c32
a7fa66a
b9283b6
cfc2e5e
0b55aba
99417f9
c0af721
46030b3
f3df97f
a76208a
3c4bcb8
9c753a4
dc011a7
807d4ce
1d89fa2
22629de
bcd9bcb
c8ae70c
c9c7143
6b40844
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||||||
---|---|---|---|---|---|---|---|---|---|---|
|
@@ -50,6 +50,7 @@ | |||||||||
from .types.components import Component as ComponentPayload | ||||||||||
from .types.components import ContainerComponent as ContainerComponentPayload | ||||||||||
from .types.components import FileComponent as FileComponentPayload | ||||||||||
from .types.components import FileUploadComponent as FileUploadComponentPayload | ||||||||||
from .types.components import InputText as InputTextComponentPayload | ||||||||||
from .types.components import LabelComponent as LabelComponentPayload | ||||||||||
from .types.components import MediaGalleryComponent as MediaGalleryComponentPayload | ||||||||||
|
@@ -81,6 +82,7 @@ | |||||||||
"Container", | ||||||||||
"Label", | ||||||||||
"SelectDefaultValue", | ||||||||||
"FileUpload", | ||||||||||
) | ||||||||||
|
||||||||||
C = TypeVar("C", bound="Component") | ||||||||||
|
@@ -938,7 +940,6 @@ def url(self, value: str) -> None: | |||||||||
|
||||||||||
@classmethod | ||||||||||
def from_dict(cls, data: UnfurledMediaItemPayload, state=None) -> UnfurledMediaItem: | ||||||||||
|
||||||||||
r = cls(data.get("url")) | ||||||||||
r.proxy_url = data.get("proxy_url") | ||||||||||
r.height = data.get("height") | ||||||||||
|
@@ -1347,6 +1348,71 @@ def walk_components(self) -> Iterator[Component]: | |||||||||
yield from [self.component] | ||||||||||
|
||||||||||
|
||||||||||
class FileUpload(Component): | ||||||||||
"""Represents an File Upload component from the Discord Bot UI Kit. | ||||||||||
|
||||||||||
This inherits from :class:`Component`. | ||||||||||
|
||||||||||
.. note:: | ||||||||||
|
||||||||||
This class is not useable by end-users; see :class:`discord.ui.FileUpload` instead. | ||||||||||
|
||||||||||
.. versionadded:: 2.7 | ||||||||||
|
||||||||||
Attributes | ||||||||||
---------- | ||||||||||
custom_id: Optional[:class:`str`] | ||||||||||
The custom ID of the file upload field that gets received during an interaction. | ||||||||||
min_values: Optional[:class:`int`] | ||||||||||
The minimum number of files that must be uploaded. | ||||||||||
max_values: Optional[:class:`int`] | ||||||||||
The maximum number of files that can be uploaded. | ||||||||||
Soheab marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||
required: Optional[:class:`bool`] | ||||||||||
Whether the file upload field is required or not. Defaults to `True`. | ||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this sounds weird, how about
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. maybe remove the "upload" part? I'm not sure. |
||||||||||
id: Optional[:class:`int`] | ||||||||||
The file upload's ID. | ||||||||||
""" | ||||||||||
|
||||||||||
__slots__: tuple[str, ...] = ( | ||||||||||
"type", | ||||||||||
"custom_id", | ||||||||||
"min_values", | ||||||||||
"max_values", | ||||||||||
"required", | ||||||||||
"id", | ||||||||||
) | ||||||||||
|
||||||||||
__repr_info__: ClassVar[tuple[str, ...]] = __slots__ | ||||||||||
versions: tuple[int, ...] = (1, 2) | ||||||||||
|
||||||||||
def __init__(self, data: FileUploadComponentPayload): | ||||||||||
self.type = ComponentType.file_upload | ||||||||||
self.id: int | None = data.get("id") | ||||||||||
self.custom_id = data["custom_id"] | ||||||||||
self.min_values: int | None = data.get("min_values", None) | ||||||||||
self.max_values: int | None = data.get("max_values", None) | ||||||||||
self.required: bool = data.get("required", True) | ||||||||||
|
||||||||||
def to_dict(self) -> FileUploadComponentPayload: | ||||||||||
payload = { | ||||||||||
"type": 19, | ||||||||||
"custom_id": self.custom_id, | ||||||||||
} | ||||||||||
if self.id is not None: | ||||||||||
payload["id"] = self.id | ||||||||||
|
||||||||||
if self.min_values is not None: | ||||||||||
payload["min_values"] = self.min_values | ||||||||||
|
||||||||||
if self.max_values is not None: | ||||||||||
payload["max_values"] = self.max_values | ||||||||||
|
||||||||||
if not self.required: | ||||||||||
payload["required"] = self.required | ||||||||||
|
||||||||||
return payload # type: ignore | ||||||||||
|
||||||||||
|
||||||||||
COMPONENT_MAPPINGS = { | ||||||||||
1: ActionRow, | ||||||||||
2: Button, | ||||||||||
|
@@ -1364,6 +1430,7 @@ def walk_components(self) -> Iterator[Component]: | |||||||||
14: Separator, | ||||||||||
17: Container, | ||||||||||
18: Label, | ||||||||||
19: FileUpload, | ||||||||||
} | ||||||||||
|
||||||||||
STATE_COMPONENTS = (Section, Container, Thumbnail, MediaGallery, FileComponent) | ||||||||||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,190 @@ | ||
from __future__ import annotations | ||
|
||
import os | ||
from typing import TYPE_CHECKING | ||
|
||
from ..components import FileUpload as FileUploadComponent | ||
from ..enums import ComponentType | ||
from ..message import Attachment | ||
|
||
__all__ = ("FileUpload",) | ||
|
||
if TYPE_CHECKING: | ||
from ..interactions import Interaction | ||
from ..types.components import FileUploadComponent as FileUploadComponentPayload | ||
|
||
|
||
class FileUpload: | ||
"""Represents a UI File Upload component. | ||
|
||
.. versionadded:: 2.7 | ||
|
||
Parameters | ||
---------- | ||
label: :class:`str` | ||
The label for this component | ||
Must be 45 characters or fewer. | ||
custom_id: Optional[:class:`str`] | ||
The ID of the input text field that gets received during an interaction. | ||
description: Optional[:class:`str`] | ||
The description for the file upload field. | ||
Must be 100 characters or fewer. | ||
min_values: Optional[:class:`int`] | ||
The minimum number of files that must be uploaded. | ||
Defaults to 0 and must be between 0 and 10, inclusive. | ||
max_values: Optional[:class:`int`] | ||
The maximum number of files that can be uploaded. | ||
Must be between 1 and 10, inclusive. | ||
required: Optional[:class:`bool`] | ||
Whether the file upload field is required or not. Defaults to ``True``. | ||
row: Optional[:class:`int`] | ||
The relative row this file upload field belongs to. A modal dialog can only have 5 | ||
rows. By default, items are arranged automatically into those 5 rows. If you'd | ||
like to control the relative positioning of the row then passing an index is advised. | ||
For example, row=1 will show up before row=2. Defaults to ``None``, which is automatic | ||
ordering. The row number must be between 0 and 4 (i.e. zero indexed). | ||
""" | ||
|
||
__item_repr_attributes__: tuple[str, ...] = ( | ||
"label", | ||
"required", | ||
"min_values", | ||
"max_values", | ||
"custom_id", | ||
"id", | ||
"description", | ||
) | ||
|
||
def __init__( | ||
self, | ||
*, | ||
label: str, | ||
custom_id: str | None = None, | ||
min_values: int | None = None, | ||
max_values: int | None = None, | ||
required: bool = True, | ||
row: int | None = None, | ||
id: int | None = None, | ||
description: str | None = None, | ||
): | ||
super().__init__() | ||
if len(str(label)) > 45: | ||
raise ValueError("label must be 45 characters or fewer") | ||
if description and len(description) > 100: | ||
raise ValueError("description must be 100 characters or fewer") | ||
if min_values and (min_values < 0 or min_values > 10): | ||
raise ValueError("min_values must be between 0 and 10") | ||
if max_values and (max_values < 1 or max_values > 10): | ||
raise ValueError("max_length must be between 1 and 10") | ||
if custom_id is not None and not isinstance(custom_id, str): | ||
raise TypeError( | ||
f"expected custom_id to be str, not {custom_id.__class__.__name__}" | ||
) | ||
if not isinstance(required, bool): | ||
raise TypeError(f"required must be bool not {required.__class__.__name__}") # type: ignore | ||
custom_id = os.urandom(16).hex() if custom_id is None else custom_id | ||
self.label: str = str(label) | ||
self.description: str | None = description | ||
|
||
self._underlying: FileUploadComponent = FileUploadComponent._raw_construct( | ||
type=ComponentType.file_upload, | ||
custom_id=custom_id, | ||
min_values=min_values, | ||
max_values=max_values, | ||
required=required, | ||
id=id, | ||
) | ||
self._attachments: list[Attachment] | None = None | ||
self.row = row | ||
self._rendered_row: int | None = None | ||
|
||
def __repr__(self) -> str: | ||
attrs = " ".join( | ||
f"{key}={getattr(self, key)!r}" for key in self.__item_repr_attributes__ | ||
) | ||
return f"<{self.__class__.__name__} {attrs}>" | ||
|
||
@property | ||
def type(self) -> ComponentType: | ||
return self._underlying.type | ||
|
||
@property | ||
def id(self) -> int | None: | ||
"""The ID of this component. If not provided by the user, it is set sequentially by Discord.""" | ||
return self._underlying.id | ||
|
||
@property | ||
def custom_id(self) -> str: | ||
"""The custom id that gets received during an interaction.""" | ||
return self._underlying.custom_id | ||
|
||
@custom_id.setter | ||
def custom_id(self, value: str): | ||
if not isinstance(value, str): | ||
raise TypeError( | ||
f"custom_id must be None or str not {value.__class__.__name__}" | ||
) | ||
self._underlying.custom_id = value | ||
|
||
@property | ||
def min_values(self) -> int | None: | ||
"""The minimum number of files that must be uploaded. Defaults to 0.""" | ||
return self._underlying.min_values | ||
|
||
@min_values.setter | ||
def min_values(self, value: int | None): | ||
if value and not isinstance(value, int): | ||
raise TypeError(f"min_values must be None or int not {value.__class__.__name__}") # type: ignore | ||
if value and (value < 0 or value > 10): | ||
raise ValueError("min_values must be between 0 and 10") | ||
self._underlying.min_values = value | ||
|
||
@property | ||
def max_values(self) -> int | None: | ||
"""The maximum number of files that can be uploaded.""" | ||
return self._underlying.max_values | ||
|
||
@max_values.setter | ||
def max_values(self, value: int | None): | ||
if value and not isinstance(value, int): | ||
raise TypeError(f"max_values must be None or int not {value.__class__.__name__}") # type: ignore | ||
if value and (value < 1 or value > 10): | ||
raise ValueError("max_values must be between 1 and 10") | ||
self._underlying.max_values = value | ||
|
||
@property | ||
def required(self) -> bool: | ||
"""Whether the input file upload is required or not. Defaults to ``True``.""" | ||
return self._underlying.required | ||
|
||
@required.setter | ||
def required(self, value: bool): | ||
if not isinstance(value, bool): | ||
raise TypeError(f"required must be bool not {value.__class__.__name__}") # type: ignore | ||
Soheab marked this conversation as resolved.
Show resolved
Hide resolved
|
||
self._underlying.required = bool(value) | ||
|
||
@property | ||
def values(self) -> list[Attachment] | None: | ||
"""The files that were uploaded to the field.""" | ||
return self._attachments | ||
|
||
@property | ||
def width(self) -> int: | ||
return 5 | ||
|
||
def to_component_dict(self) -> FileUploadComponentPayload: | ||
return self._underlying.to_dict() | ||
|
||
def refresh_from_modal(self, interaction: Interaction, data: dict) -> None: | ||
values = data.get("values", []) | ||
self._attachments = [ | ||
Attachment( | ||
state=interaction._state, | ||
data=interaction.data["resolved"]["attachments"][attachment_id], | ||
) | ||
for attachment_id in values | ||
] | ||
|
||
@staticmethod | ||
def uses_label() -> bool: | ||
return True |
Uh oh!
There was an error while loading. Please reload this page.