Feature: Implement modals (#2087)
* Implement Modals (#428) * Socket Modal Support * fix shareded client support * Properly use `HasResponded` instead of `_hasResponded` * `ModalBuilder` and `TextInputBuilder` validation. * make orginisation more consistant. * Rest Modals. * Docs + add missing methods * fix message signatures and missing abstract members * modal changes * um????? * update modal docs * update docs - again for some reason * cleanup * fix message signatures * add modal commands support to interaction service * Fix _hasResponded * update to new unsupported standard. * Sending modals with Interaction service. * fix spelling in ComponentBuilder * sending IModals when responding to interactions * interaction service modals * fix rest modals * spelling and minor improvements. * improve interaction service modal proformance * use precompiled lambda for interaction service modals * respect user compiled lambda choice * changes to modals in the interaction service (more) * support compiled lambdas in modal properties. * modal interactions tweaks * fix inline doc * more modal docs * configure responce to faild modal component * init * solve runtime errors * solve build errors * add default value parsing * make modal info caching static * make ModalUtils static * add inline docs * fix build errors * code cleanup * Introduce Required and Label properties as seperate attributes. * replace internal dictionary of ModalInfo with a list * change input building logic of modals * update RespondWithModalAsync method * add initial value parameter back to ModalTextInput and fix optional modal field * add missing inline docs * dispose the reference modal instance after building * code cleanup on modalcommandbuilder * Update docs/guides/int_basics/message-components/text-input.md Co-authored-by: Jared L <48422312+lhjt@users.noreply.github.com> * Update docs/guides/int_basics/message-components/text-input.md Co-authored-by: Jared L <48422312+lhjt@users.noreply.github.com> * Update docs/guides/int_basics/modals/intro.md Co-authored-by: Jared L <48422312+lhjt@users.noreply.github.com> * Update docs/guides/int_basics/modals/intro.md Co-authored-by: Jared L <48422312+lhjt@users.noreply.github.com> * Update docs/guides/int_basics/modals/intro.md Co-authored-by: Jared L <48422312+lhjt@users.noreply.github.com> * Update docs/guides/int_basics/modals/intro.md Co-authored-by: Jared L <48422312+lhjt@users.noreply.github.com> * Update docs/guides/int_basics/modals/intro.md Co-authored-by: Jared L <48422312+lhjt@users.noreply.github.com> * Update docs/guides/int_basics/modals/intro.md Co-authored-by: Jared L <48422312+lhjt@users.noreply.github.com> * Update docs/guides/int_basics/modals/intro.md Co-authored-by: Jared L <48422312+lhjt@users.noreply.github.com> * Update docs/guides/int_framework/intro.md Co-authored-by: Jared L <48422312+lhjt@users.noreply.github.com> * Update docs/guides/int_framework/intro.md Co-authored-by: Jared L <48422312+lhjt@users.noreply.github.com> * Update docs/guides/int_framework/samples/intro/modal.cs Co-authored-by: Jared L <48422312+lhjt@users.noreply.github.com> * Update src/Discord.Net.Core/Entities/Interactions/MessageComponents/IComponentInteractionData.cs Co-authored-by: Jared L <48422312+lhjt@users.noreply.github.com> * Update src/Discord.Net.Core/Entities/Interactions/MessageComponents/TextInputComponent.cs Co-authored-by: Jared L <48422312+lhjt@users.noreply.github.com> * Update src/Discord.Net.Core/Entities/Interactions/Modals/IModalInteraction.cs Co-authored-by: Jared L <48422312+lhjt@users.noreply.github.com> * Update src/Discord.Net.Core/Entities/Interactions/Modals/ModalBuilder.cs Co-authored-by: Jared L <48422312+lhjt@users.noreply.github.com> * Update src/Discord.Net.Core/Entities/Interactions/Modals/ModalBuilder.cs Co-authored-by: Jared L <48422312+lhjt@users.noreply.github.com> * Update src/Discord.Net.Core/Entities/Interactions/Modals/ModalBuilder.cs Co-authored-by: Jared L <48422312+lhjt@users.noreply.github.com> * Update src/Discord.Net.Core/Entities/Interactions/Modals/ModalBuilder.cs Co-authored-by: Jared L <48422312+lhjt@users.noreply.github.com> * Update src/Discord.Net.Core/Entities/Interactions/Modals/ModalBuilder.cs Co-authored-by: Jared L <48422312+lhjt@users.noreply.github.com> * Update src/Discord.Net.Core/Entities/Interactions/Modals/ModalBuilder.cs Co-authored-by: Jared L <48422312+lhjt@users.noreply.github.com> * Update src/Discord.Net.Interactions/Attributes/Commands/ModalInteractionAttribute.cs Co-authored-by: Jared L <48422312+lhjt@users.noreply.github.com> * Update src/Discord.Net.Interactions/Attributes/Modals/RequiredInputAttribute.cs Co-authored-by: Jared L <48422312+lhjt@users.noreply.github.com> * Update src/Discord.Net.Interactions/InteractionServiceConfig.cs Co-authored-by: Jared L <48422312+lhjt@users.noreply.github.com> * Update src/Discord.Net.WebSocket/Entities/Interaction/MessageComponents/SocketMessageComponentData.cs Co-authored-by: Jared L <48422312+lhjt@users.noreply.github.com> * Update src/Discord.Net.WebSocket/Entities/Interaction/Modals/SocketModalData.cs Co-authored-by: Jared L <48422312+lhjt@users.noreply.github.com> * update interaction service modal docs * implements ExitOnMissingmModalField config option and adds Type field to modal info * Add WithValue to text input builders * Fix rare NRE on component enumeration * Fix RequestOptions being required in some methods * Use 'OfType' instead of 'Where' * Remove android unsported warning * Change publicity of properties in IInputComponeontBuilder.cs Co-authored-by: Cenk Ergen <57065323+Cenngo@users.noreply.github.com> Co-authored-by: Jared L <48422312+lhjt@users.noreply.github.com> * Remove complex parameter ref Co-authored-by: CottageDwellingCat <80918250+CottageDwellingCat@users.noreply.github.com> Co-authored-by: Cenk Ergen <57065323+Cenngo@users.noreply.github.com> Co-authored-by: Jared L <48422312+lhjt@users.noreply.github.com>
This commit is contained in:
@@ -0,0 +1,105 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Discord.Interactions.Builders
|
||||
{
|
||||
/// <summary>
|
||||
/// Represent a builder for creating <see cref="InputComponentInfo"/>.
|
||||
/// </summary>
|
||||
public interface IInputComponentBuilder
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the parent modal of this input component.
|
||||
/// </summary>
|
||||
ModalBuilder Modal { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the custom id of this input component.
|
||||
/// </summary>
|
||||
string CustomId { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the label of this input component.
|
||||
/// </summary>
|
||||
string Label { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets whether this input component is required.
|
||||
/// </summary>
|
||||
bool IsRequired { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the component type of this input component.
|
||||
/// </summary>
|
||||
ComponentType ComponentType { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Get the reference type of this input component.
|
||||
/// </summary>
|
||||
Type Type { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the default value of this input component.
|
||||
/// </summary>
|
||||
object DefaultValue { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a collection of the attributes of this component.
|
||||
/// </summary>
|
||||
IReadOnlyCollection<Attribute> Attributes { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Sets <see cref="CustomId"/>.
|
||||
/// </summary>
|
||||
/// <param name="customId">New value of the <see cref="CustomId"/>.</param>
|
||||
/// <returns>
|
||||
/// The builder instance.
|
||||
/// </returns>
|
||||
IInputComponentBuilder WithCustomId(string customId);
|
||||
|
||||
/// <summary>
|
||||
/// Sets <see cref="Label"/>.
|
||||
/// </summary>
|
||||
/// <param name="label">New value of the <see cref="Label"/>.</param>
|
||||
/// <returns>
|
||||
/// The builder instance.
|
||||
/// </returns>
|
||||
IInputComponentBuilder WithLabel(string label);
|
||||
|
||||
/// <summary>
|
||||
/// Sets <see cref="IsRequired"/>.
|
||||
/// </summary>
|
||||
/// <param name="isRequired">New value of the <see cref="IsRequired"/>.</param>
|
||||
/// <returns>
|
||||
/// The builder instance.
|
||||
/// </returns>
|
||||
IInputComponentBuilder SetIsRequired(bool isRequired);
|
||||
|
||||
/// <summary>
|
||||
/// Sets <see cref="Type"/>.
|
||||
/// </summary>
|
||||
/// <param name="type">New value of the <see cref="Type"/>.</param>
|
||||
/// <returns>
|
||||
/// The builder instance.
|
||||
/// </returns>
|
||||
IInputComponentBuilder WithType(Type type);
|
||||
|
||||
/// <summary>
|
||||
/// Sets <see cref="DefaultValue"/>.
|
||||
/// </summary>
|
||||
/// <param name="value">New value of the <see cref="DefaultValue"/>.</param>
|
||||
/// <returns>
|
||||
/// The builder instance.
|
||||
/// </returns>
|
||||
IInputComponentBuilder SetDefaultValue(object value);
|
||||
|
||||
/// <summary>
|
||||
/// Adds attributes to <see cref="Attributes"/>.
|
||||
/// </summary>
|
||||
/// <param name="attributes">New attributes to be added to <see cref="Attributes"/>.</param>
|
||||
/// <returns>
|
||||
/// The builder instance.
|
||||
/// </returns>
|
||||
IInputComponentBuilder WithAttributes(params Attribute[] attributes);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,164 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Discord.Interactions.Builders
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents the base builder class for creating <see cref="InputComponentInfo"/>.
|
||||
/// </summary>
|
||||
/// <typeparam name="TInfo">The <see cref="InputComponentInfo"/> this builder yields when built.</typeparam>
|
||||
/// <typeparam name="TBuilder">Inherited <see cref="InputComponentBuilder{TInfo, TBuilder}"/> type.</typeparam>
|
||||
public abstract class InputComponentBuilder<TInfo, TBuilder> : IInputComponentBuilder
|
||||
where TInfo : InputComponentInfo
|
||||
where TBuilder : InputComponentBuilder<TInfo, TBuilder>
|
||||
{
|
||||
private readonly List<Attribute> _attributes;
|
||||
protected abstract TBuilder Instance { get; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public ModalBuilder Modal { get; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public string CustomId { get; set; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public string Label { get; set; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool IsRequired { get; set; } = true;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public ComponentType ComponentType { get; internal set; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public Type Type { get; private set; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public object DefaultValue { get; set; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public IReadOnlyCollection<Attribute> Attributes => _attributes;
|
||||
|
||||
/// <summary>
|
||||
/// Creates an instance of <see cref="InputComponentBuilder{TInfo, TBuilder}"/>
|
||||
/// </summary>
|
||||
/// <param name="modal">Parent modal of this input component.</param>
|
||||
public InputComponentBuilder(ModalBuilder modal)
|
||||
{
|
||||
Modal = modal;
|
||||
_attributes = new();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets <see cref="CustomId"/>.
|
||||
/// </summary>
|
||||
/// <param name="customId">New value of the <see cref="CustomId"/>.</param>
|
||||
/// <returns>
|
||||
/// The builder instance.
|
||||
/// </returns>
|
||||
public TBuilder WithCustomId(string customId)
|
||||
{
|
||||
CustomId = customId;
|
||||
return Instance;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets <see cref="Label"/>.
|
||||
/// </summary>
|
||||
/// <param name="label">New value of the <see cref="Label"/>.</param>
|
||||
/// <returns>
|
||||
/// The builder instance.
|
||||
/// </returns>
|
||||
public TBuilder WithLabel(string label)
|
||||
{
|
||||
Label = label;
|
||||
return Instance;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets <see cref="IsRequired"/>.
|
||||
/// </summary>
|
||||
/// <param name="isRequired">New value of the <see cref="IsRequired"/>.</param>
|
||||
/// <returns>
|
||||
/// The builder instance.
|
||||
/// </returns>
|
||||
public TBuilder SetIsRequired(bool isRequired)
|
||||
{
|
||||
IsRequired = isRequired;
|
||||
return Instance;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets <see cref="ComponentType"/>.
|
||||
/// </summary>
|
||||
/// <param name="componentType">New value of the <see cref="ComponentType"/>.</param>
|
||||
/// <returns>
|
||||
/// The builder instance.
|
||||
/// </returns>
|
||||
public TBuilder WithComponentType(ComponentType componentType)
|
||||
{
|
||||
ComponentType = componentType;
|
||||
return Instance;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets <see cref="Type"/>.
|
||||
/// </summary>
|
||||
/// <param name="type">New value of the <see cref="Type"/>.</param>
|
||||
/// <returns>
|
||||
/// The builder instance.
|
||||
/// </returns>
|
||||
public TBuilder WithType(Type type)
|
||||
{
|
||||
Type = type;
|
||||
return Instance;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets <see cref="DefaultValue"/>.
|
||||
/// </summary>
|
||||
/// <param name="value">New value of the <see cref="DefaultValue"/>.</param>
|
||||
/// <returns>
|
||||
/// The builder instance.
|
||||
/// </returns>
|
||||
public TBuilder SetDefaultValue(object value)
|
||||
{
|
||||
DefaultValue = value;
|
||||
return Instance;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds attributes to <see cref="Attributes"/>.
|
||||
/// </summary>
|
||||
/// <param name="attributes">New attributes to be added to <see cref="Attributes"/>.</param>
|
||||
/// <returns>
|
||||
/// The builder instance.
|
||||
/// </returns>
|
||||
public TBuilder WithAttributes(params Attribute[] attributes)
|
||||
{
|
||||
_attributes.AddRange(attributes);
|
||||
return Instance;
|
||||
}
|
||||
|
||||
internal abstract TInfo Build(ModalInfo modal);
|
||||
|
||||
//IInputComponentBuilder
|
||||
/// <inheritdoc/>
|
||||
IInputComponentBuilder IInputComponentBuilder.WithCustomId(string customId) => WithCustomId(customId);
|
||||
|
||||
/// <inheritdoc/>
|
||||
IInputComponentBuilder IInputComponentBuilder.WithLabel(string label) => WithCustomId(label);
|
||||
|
||||
/// <inheritdoc/>
|
||||
IInputComponentBuilder IInputComponentBuilder.WithType(Type type) => WithType(type);
|
||||
|
||||
/// <inheritdoc/>
|
||||
IInputComponentBuilder IInputComponentBuilder.SetDefaultValue(object value) => SetDefaultValue(value);
|
||||
|
||||
/// <inheritdoc/>
|
||||
IInputComponentBuilder IInputComponentBuilder.WithAttributes(params Attribute[] attributes) => WithAttributes(attributes);
|
||||
|
||||
/// <inheritdoc/>
|
||||
IInputComponentBuilder IInputComponentBuilder.SetIsRequired(bool isRequired) => SetIsRequired(isRequired);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
namespace Discord.Interactions.Builders
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a builder for creating <see cref="TextInputComponentInfo"/>.
|
||||
/// </summary>
|
||||
public class TextInputComponentBuilder : InputComponentBuilder<TextInputComponentInfo, TextInputComponentBuilder>
|
||||
{
|
||||
protected override TextInputComponentBuilder Instance => this;
|
||||
|
||||
/// <summary>
|
||||
/// Gets and sets the style of the text input.
|
||||
/// </summary>
|
||||
public TextInputStyle Style { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets and sets the placeholder of the text input.
|
||||
/// </summary>
|
||||
public string Placeholder { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets and sets the minimum length of the text input.
|
||||
/// </summary>
|
||||
public int MinLength { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets and sets the maximum length of the text input.
|
||||
/// </summary>
|
||||
public int MaxLength { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets and sets the initial value to be displayed by this input.
|
||||
/// </summary>
|
||||
public string InitialValue { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new <see cref="TextInputComponentBuilder"/>.
|
||||
/// </summary>
|
||||
/// <param name="modal">Parent modal of this component.</param>
|
||||
public TextInputComponentBuilder(ModalBuilder modal) : base(modal) { }
|
||||
|
||||
/// <summary>
|
||||
/// Sets <see cref="Style"/>.
|
||||
/// </summary>
|
||||
/// <param name="style">New value of the <see cref="SetValue(string)"/>.</param>
|
||||
/// <returns>
|
||||
/// The builder instance.
|
||||
/// </returns>
|
||||
public TextInputComponentBuilder WithStyle(TextInputStyle style)
|
||||
{
|
||||
Style = style;
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets <see cref="Placeholder"/>.
|
||||
/// </summary>
|
||||
/// <param name="placeholder">New value of the <see cref="Placeholder"/>.</param>
|
||||
/// <returns>
|
||||
/// The builder instance.
|
||||
/// </returns>
|
||||
public TextInputComponentBuilder WithPlaceholder(string placeholder)
|
||||
{
|
||||
Placeholder = placeholder;
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets <see cref="MinLength"/>.
|
||||
/// </summary>
|
||||
/// <param name="minLenght">New value of the <see cref="MinLength"/>.</param>
|
||||
/// <returns>
|
||||
/// The builder instance.
|
||||
/// </returns>
|
||||
public TextInputComponentBuilder WithMinLenght(int minLenght)
|
||||
{
|
||||
MinLength = minLenght;
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets <see cref="MaxLength"/>.
|
||||
/// </summary>
|
||||
/// <param name="maxLenght">New value of the <see cref="MaxLength"/>.</param>
|
||||
/// <returns>
|
||||
/// The builder instance.
|
||||
/// </returns>
|
||||
public TextInputComponentBuilder WithMaxLenght(int maxLenght)
|
||||
{
|
||||
MaxLength = maxLenght;
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets <see cref="InitialValue"/>.
|
||||
/// </summary>
|
||||
/// <param name="value">New value of the <see cref="InitialValue"/>.</param>
|
||||
/// <returns>
|
||||
/// The builder instance.
|
||||
/// </returns>
|
||||
public TextInputComponentBuilder WithInitialValue(string value)
|
||||
{
|
||||
InitialValue = value;
|
||||
return this;
|
||||
}
|
||||
|
||||
internal override TextInputComponentInfo Build(ModalInfo modal) =>
|
||||
new(this, modal);
|
||||
}
|
||||
}
|
||||
81
src/Discord.Net.Interactions/Builders/Modals/ModalBuilder.cs
Normal file
81
src/Discord.Net.Interactions/Builders/Modals/ModalBuilder.cs
Normal file
@@ -0,0 +1,81 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Discord.Interactions.Builders
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a builder for creating <see cref="ModalInfo"/>.
|
||||
/// </summary>
|
||||
public class ModalBuilder
|
||||
{
|
||||
internal readonly List<IInputComponentBuilder> _components;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the initialization delegate for this modal.
|
||||
/// </summary>
|
||||
public ModalInitializer ModalInitializer { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the title of this modal.
|
||||
/// </summary>
|
||||
public string Title { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the <see cref="IModal"/> implementation used to initialize this object.
|
||||
/// </summary>
|
||||
public Type Type { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a collection of the components of this modal.
|
||||
/// </summary>
|
||||
public IReadOnlyCollection<IInputComponentBuilder> Components => _components;
|
||||
|
||||
internal ModalBuilder(Type type)
|
||||
{
|
||||
if (!typeof(IModal).IsAssignableFrom(type))
|
||||
throw new ArgumentException($"Must be an implementation of {nameof(IModal)}", nameof(type));
|
||||
|
||||
_components = new();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new <see cref="ModalBuilder"/>
|
||||
/// </summary>
|
||||
/// <param name="modalInitializer">The initialization delegate for this modal.</param>
|
||||
public ModalBuilder(Type type, ModalInitializer modalInitializer) : this(type)
|
||||
{
|
||||
ModalInitializer = modalInitializer;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets <see cref="Title"/>.
|
||||
/// </summary>
|
||||
/// <param name="title">New value of the <see cref="Title"/>.</param>
|
||||
/// <returns>
|
||||
/// The builder instance.
|
||||
/// </returns>
|
||||
public ModalBuilder WithTitle(string title)
|
||||
{
|
||||
Title = title;
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds text components to <see cref="TextComponents"/>.
|
||||
/// </summary>
|
||||
/// <param name="configure">Text Component builder factory.</param>
|
||||
/// <returns>
|
||||
/// The builder instance.
|
||||
/// </returns>
|
||||
public ModalBuilder AddTextComponent(Action<TextInputComponentBuilder> configure)
|
||||
{
|
||||
var builder = new TextInputComponentBuilder(this);
|
||||
configure(builder);
|
||||
_components.Add(builder);
|
||||
return this;
|
||||
}
|
||||
|
||||
internal ModalInfo Build() => new(this);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user