Skip to content

Commit

Permalink
[Feature] Add useful utility methods to ModalBuilder (#2773)
Browse files Browse the repository at this point in the history
* Add method to get a component of type

* Add methods to update a component

* Add methods to remove a component(s)

* Add missing `row` parameter to wrapper

* Update XML documentation

* Reorder properties to follow coding style

* Remove unnecessary usings to follow coding style

* Fix build usings

* Fix XML documentation

* Add `null` checks to remove methods

* Fix empty constructor

* Fix `CustomId` setter

* Update src/Discord.Net.Core/Entities/Interactions/Modals/ModalBuilder.cs

Co-authored-by: Misha133 <61027276+Misha-133@users.noreply.github.com>

* Fix `GetComponent` to return null

* Fix XML documentation

* Add `null` checks

* Update src/Discord.Net.Core/Entities/Interactions/Modals/ModalBuilder.cs

* Update src/Discord.Net.Core/Entities/Interactions/Modals/ModalBuilder.cs

* Update src/Discord.Net.Core/Entities/Interactions/Modals/ModalBuilder.cs

Co-authored-by: Misha133 <61027276+Misha-133@users.noreply.github.com>

---------

Co-authored-by: Misha133 <61027276+Misha-133@users.noreply.github.com>
  • Loading branch information
zobweyt and Misha-133 committed Sep 2, 2023
1 parent a668757 commit 8591de7
Showing 1 changed file with 121 additions and 29 deletions.
150 changes: 121 additions & 29 deletions src/Discord.Net.Core/Entities/Interactions/Modals/ModalBuilder.cs
Original file line number Diff line number Diff line change
@@ -1,54 +1,55 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Discord
{
/// <summary>
/// Represents a builder for creating a <see cref="Modal"/>.
/// </summary>
public class ModalBuilder
{
private string _customId;

public ModalBuilder() { }

/// <summary>
/// Gets or sets the components of the current modal.
/// Creates a new instance of the <see cref="ModalBuilder"/>.
/// </summary>
public ModalComponentBuilder Components { get; set; } = new();
/// <param name="title">The modal's title.</param>
/// <param name="customId">The modal's customId.</param>
/// <param name="components">The modal's components.</param>
/// <exception cref="ArgumentException">Only TextInputComponents are allowed.</exception>
public ModalBuilder(string title, string customId, ModalComponentBuilder components = null)
{
Title = title;
CustomId = customId;
Components = components ?? new();
}

/// <summary>
/// Gets or sets the title of the current modal.
/// </summary>
public string Title { get; set; }

/// <summary>
/// Gets or sets the custom id of the current modal.
/// Gets or sets the custom ID of the current modal.
/// </summary>
public string CustomId
{
get => _customId;
set => _customId = value?.Length switch
{
> ComponentBuilder.MaxCustomIdLength => throw new ArgumentOutOfRangeException(nameof(value), $"Custom Id length must be less or equal to {ComponentBuilder.MaxCustomIdLength}."),
0 => throw new ArgumentOutOfRangeException(nameof(value), "Custom Id length must be at least 1."),
> ComponentBuilder.MaxCustomIdLength => throw new ArgumentOutOfRangeException(nameof(value), $"Custom ID length must be less or equal to {ComponentBuilder.MaxCustomIdLength}."),
0 => throw new ArgumentOutOfRangeException(nameof(value), "Custom ID length must be at least 1."),
_ => value
};
}

private string _customId;

public ModalBuilder() { }

/// <summary>
/// Creates a new instance of a <see cref="ModalBuilder"/>
/// Gets or sets the components of the current modal.
/// </summary>
/// <param name="title">The modal's title.</param>
/// <param name="customId">The modal's customId.</param>
/// <param name="components">The modal's components.</param>
/// <exception cref="ArgumentException">Only TextInputComponents are allowed.</exception>
public ModalBuilder(string title, string customId, ModalComponentBuilder components = null)
{
Title = title;
CustomId = customId;
Components = components ?? new();
}
public ModalComponentBuilder Components { get; set; } = new();

/// <summary>
/// Sets the title of the current modal.
Expand Down Expand Up @@ -76,10 +77,11 @@ public ModalBuilder WithCustomId(string customId)
/// Adds a component to the current builder.
/// </summary>
/// <param name="component">The component to add.</param>
/// <param name="row">The row to add the text input.</param>
/// <returns>The current builder.</returns>
public ModalBuilder AddTextInput(TextInputBuilder component)
public ModalBuilder AddTextInput(TextInputBuilder component, int row = 0)
{
Components.WithTextInput(component);
Components.WithTextInput(component, row);
return this;
}

Expand Down Expand Up @@ -108,21 +110,111 @@ public ModalBuilder AddComponents(List<IMessageComponent> components, int row)
return this;
}

/// <summary>
/// Gets a <typeparamref name="TMessageComponent"/> by the specified <paramref name="customId"/>.
/// </summary>
/// <typeparam name="TMessageComponent">The type of the component to get.</typeparam>
/// <param name="customId">The <see cref="IMessageComponent.CustomId"/> of the component to get.</param>
/// <returns>
/// The component of type <typeparamref name="TMessageComponent"/> that was found, <see langword="null"/> otherwise.
/// </returns>
public TMessageComponent GetComponent<TMessageComponent>(string customId)
where TMessageComponent : class, IMessageComponent
{
Preconditions.NotNull(customId, nameof(customId));

return Components.ActionRows
?.SelectMany(r => r.Components.OfType<TMessageComponent>())
.FirstOrDefault(c => c?.CustomId == customId);
}

/// <summary>
/// Updates a <see cref="TextInputComponent"/> by the specified <paramref name="customId"/>.
/// </summary>
/// <param name="customId">The <see cref="TextInputComponent.CustomId"/> of the input to update.</param>
/// <param name="updateTextInput">An action that configures the updated text input.</param>
/// <returns>The current builder.</returns>
/// <exception cref="ArgumentException">
/// Thrown when the <see cref="TextInputComponent"/> to be updated was not found.
/// </exception>
public ModalBuilder UpdateTextInput(string customId, Action<TextInputBuilder> updateTextInput)
{
Preconditions.NotNull(customId, nameof(customId));

var component = GetComponent<TextInputComponent>(customId) ?? throw new ArgumentException($"There is no component of type {nameof(TextInputComponent)} with the specified custom ID in this modal builder.", nameof(customId));
var row = Components.ActionRows.First(r => r.Components.Contains(component));

var builder = new TextInputBuilder
{
Label = component.Label,
CustomId = component.CustomId,
Style = component.Style,
Placeholder = component.Placeholder,
MinLength = component.MinLength,
MaxLength = component.MaxLength,
Required = component.Required,
Value = component.Value
};

updateTextInput(builder);

row.Components.Remove(component);
row.AddComponent(builder.Build());

return this;
}

/// <summary>
/// Updates the value of a <see cref="TextInputComponent"/> by the specified <paramref name="customId"/>.
/// </summary>
/// <param name="customId">The <see cref="TextInputComponent.CustomId"/> of the input to update.</param>
/// <param name="value">The new value to put.</param>
/// <returns>The current builder.</returns>
public ModalBuilder UpdateTextInput(string customId, object value)
{
UpdateTextInput(customId, x => x.Value = value?.ToString());
return this;
}

/// <summary>
/// Removes a component from this builder by the specified <paramref name="customId"/>.
/// </summary>
/// <param name="customId">The <see cref="IMessageComponent.CustomId"/> of the component to remove.</param>
/// <returns>The current builder.</returns>
public ModalBuilder RemoveComponent(string customId)
{
Preconditions.NotNull(customId, nameof(customId));

Components.ActionRows?.ForEach(r => r.Components.RemoveAll(c => c.CustomId == customId));
return this;
}

/// <summary>
/// Removes all components of the given <paramref name="type"/> from this builder.
/// </summary>
/// <param name="type">The <see cref="ComponentType"/> to remove.</param>
/// <returns>The current builder.</returns>
public ModalBuilder RemoveComponentsOfType(ComponentType type)
{
Components.ActionRows?.ForEach(r => r.Components.RemoveAll(c => c.Type == type));
return this;
}

/// <summary>
/// Builds this builder into a <see cref="Modal"/>.
/// </summary>
/// <returns>A <see cref="Modal"/> with the same values as this builder.</returns>
/// <exception cref="ArgumentException">Only TextInputComponents are allowed.</exception>
/// <exception cref="ArgumentException">Modals must have a custom id.</exception>
/// <exception cref="ArgumentException">Modals must have a custom ID.</exception>
/// <exception cref="ArgumentException">Modals must have a title.</exception>
/// <exception cref="ArgumentException">Only components of type <see cref="TextInputComponent"/> are allowed.</exception>
public Modal Build()
{
if (string.IsNullOrEmpty(CustomId))
throw new ArgumentException("Modals must have a custom id.", nameof(CustomId));
throw new ArgumentException("Modals must have a custom ID.", nameof(CustomId));
if (string.IsNullOrWhiteSpace(Title))
throw new ArgumentException("Modals must have a title.", nameof(Title));
if (Components.ActionRows?.SelectMany(x => x.Components).Any(x => x.Type != ComponentType.TextInput) ?? false)
throw new ArgumentException($"Only TextInputComponents are allowed.", nameof(Components));
if (Components.ActionRows?.SelectMany(r => r.Components).Any(c => c.Type != ComponentType.TextInput) ?? false)
throw new ArgumentException($"Only components of type {nameof(TextInputComponent)} are allowed.", nameof(Components));

return new(Title, CustomId, Components.Build());
}
Expand Down

0 comments on commit 8591de7

Please sign in to comment.