Docs/components v2 :wires: (#3162)

* new pages :3

* fimished intro page

* fimished interaction page

* remove unused shit

* I think we are done lmao

* I lied, fixed some small mistakes

* Update docs/guides/components_v2/interaction.md

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

* misha quality assurance :3 + breakings pages

* Apply suggestions from code review

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

* component types guide expanded

* :3

* Apply suggestions from code review

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

---------

Co-authored-by: Mihail Gribkov <61027276+Misha-133@users.noreply.github.com>
This commit is contained in:
Adriaan Waem
2025-07-18 21:14:24 +02:00
committed by GitHub
parent 80b4328578
commit cf66ab4520
20 changed files with 455 additions and 5 deletions

View File

@@ -0,0 +1,57 @@
private static ComponentBuilderV2 BuildComponentUnsafe(Recipe recipe, Units units = Units.Si)
{
StringBuilder ingredients = new($"## Ingredients{Environment.NewLine}");
StringBuilder instructions = new($"## Instructions{Environment.NewLine}");
ButtonBuilder servingsModalButton = new ButtonBuilder()
.WithCustomId(RecipeServingsModal)
.WithLabel("Set servings")
.WithStyle(ButtonStyle.Primary);
foreach (RecipeIngredient recipeIngredient in recipe.RecipeIngredients)
{
ingredients.Append($"`{recipeIngredient.Quantity} {recipeIngredient.Unit.ToSymbol()}` {recipeIngredient.Ingredient.Name} ");
if (recipeIngredient.Optional)
ingredients.AppendLine("[Optional]");
else
ingredients.Append(Environment.NewLine);
}
for (var i = 0; i < recipe.Instruction.Length; i++)
instructions.AppendLine($"`{i + 1}.` {recipe.Instruction[i]}{Environment.NewLine}");
return new ComponentBuilderV2()
.WithTextDisplay($"# {recipe.Name}", RecipeNameDisplay)
.WithTextDisplay($"-# {recipe.Servings} servings", RecipeServingsDisplay)
.WithMediaGallery([
"https://cdn.discordapp.com/attachments/964253122547552349/1336440069892083712/7Q3S.gif?ex=67a3d04e&is=67a27ece&hm=059c9d28466f43a50c4b450ca26fc01298a2080356421d8524384bf67ea8f3ab&"
])
.WithActionRow([servingsModalButton])
.WithTextDisplay(ingredients.ToString())
.WithTextDisplay($"""
## Oven Settings
Mode: `{recipe.OvenMode.ToHumanReadable()}`
Temperature: `{recipe.Temperature.Convert(Unit.Temperature, Units.Si, units)} {units.ToSymbol()}`
""")
.WithActionRow([
new SelectMenuBuilder(
RecipeUnitInput,
options:[
new SelectMenuOptionBuilder(
"Metric",
"1",
isDefault: units == Units.Metric),
new SelectMenuOptionBuilder(
"Imperial",
"2",
isDefault: units == Units.Imperial),
new SelectMenuOptionBuilder(
"Kelvin",
"0",
isDefault: units == Units.Si)
],
id: RecipeUnitSelectMenu
)
])
.WithTextDisplay(instructions.ToString());
}

View File

@@ -0,0 +1,31 @@
private async Task ClientOnInteractionCreatedAsync(SocketInteraction arg)
{
switch (arg)
{
case SocketMessageComponent component:
switch (component.Data.CustomId)
{
// Non dynamic cases ...
default:
var customId = component.Data.CustomId;
var lastPartStartIndex = customId.LastIndexOf('-');
if (lastPartStartIndex == -1)
return;
if (customId[..lastPartStartIndex] == RecipesLookInsideButton) // "recipes-show-me-button"
await component.UpdateAsync(m => m.Components = BuildComponentUnsafe(_recipes.First(r => r.RecipeId == int.Parse(customId[(lastPartStartIndex + 1)..]))).Build()); // _recipes is a list of Recipe objects ; int.Parse({recipe.RecipeId}) (in this case it is 1)
break;
}
break;
case SocketModal modal:
// Interaction came from a modal
break;
default:
return;
}
}

View File

@@ -0,0 +1,59 @@
private async Task ClientOnInteractionCreatedAsync(SocketInteraction arg)
{
switch (arg)
{
case SocketMessageComponent component:
switch (component.Data.CustomId)
{
// SET SERVINGS BUTTON CLICKED
case RecipeServingsModal:
var servings = short.Parse(component.Message.Components.FindComponentById<TextDisplayComponent>(RecipeServingsDisplay).Content.Split(' ')[1]);
await component.RespondWithModalAsync(CreateServingsModal(servings).Build());
break;
// ITEM IN COMBOXBOX CHANGED
case RecipeUnitInput:
SelectMenuComponent selectedItem = component.Message.Components.FindComponentById<SelectMenuComponent>(RecipeUnitSelectMenu);
var unitValue = short.Parse(component.Data.Values.First());
var recipeName = component.Message.Components.FindComponentById<TextDisplayComponent>(RecipeNameDisplay).Content[2..];
Recipe recipe = _recipes.First(r => r.Name == recipeName);
Recipe recipe0 = recipe.Clone();
var unit = (Units)Enum.ToObject(typeof(Units), unitValue);
ComponentBuilderV2 newComponentContainer = BuildComponentUnsafe(recipe0, unit);
await component.UpdateAsync(m => m.Components = newComponentContainer.Build());
break;
default:
// Ununsed here
}
break;
// MODAL SUBMIT
case SocketModal modal:
if (modal.Data.CustomId == RecipeServingsButton)
{
var success = short.TryParse(modal.Data.Components.First(c => c.CustomId == RecipeServingsInput).Value, out var servings);
if (!success || servings <= 0)
break;
Recipe recipe = _recipes.First(r => r.Name == modal.Message.Components.FindComponentById<TextDisplayComponent>(RecipeNameDisplay).Content[2..]);
Recipe? recipe0 = recipe.Clone();
recipe0.ChangeServings(servings, true);
ComponentBuilderV2 newComponentContainer = BuildComponentUnsafe(recipe0);
await modal.UpdateAsync(m => m.Components = newComponentContainer.Build());
}
break;
default:
return;
}
}

View File

@@ -0,0 +1,15 @@
private static ModalBuilder CreateServingsModal(short servings)
{
TextInputBuilder? textInput = new TextInputBuilder()
.WithCustomId(RecipeServingsInput)
.WithLabel("Servings")
.WithValue(servings.ToString())
.WithMinLength(1)
.WithMaxLength(3)
.WithStyle(TextInputStyle.Short);
return new ModalBuilder()
.WithCustomId(RecipeServingsButton)
.WithTitle("Set Servings")
.AddTextInput(textInput);
}

View File

@@ -0,0 +1,14 @@
[SlashCommand("recipes", "Gets all recipes")]
public async Task GetRecipesAsync()
{
MessageComponent? embed = (await recipeService.GetRecipesComponentAsync())?.Build();
if (embed is null)
{
await RespondAsync($"No recipes found.", ephemeral: true);
return;
}
await RespondAsync(components: embed);
}

View File

@@ -0,0 +1,32 @@
private async Task<ComponentBuilderV2> BuildComponentsUnsafeAsync()
{
if (!_recipes.Any()) // _recipes is simply a list of recipe objects
{
return new ComponentBuilderV2()
.WithTextDisplay(
"""
# No recipes found
You should consider adding some.
""");
}
var builder = new ComponentBuilderV2();
Emote? emote = await _clientProvider.Client.GetApplicationEmoteAsync(1393996479357517925);
foreach (Recipe recipe in _recipes)
{
var buttonBuilder = new ButtonBuilder("Look inside", $"{RecipesLookInsideButton}-{recipe.RecipeId}"); // RecipesLookInsideButton is a constant string
if (emote is not null)
buttonBuilder.WithEmote(emote);
builder
.WithTextDisplay($"# {recipe.Name}")
.WithMediaGallery(["https://cdn.discordapp.com/attachments/964253122547552349/1336440069892083712/7Q3S.gif?ex=67a3d04e&is=67a27ece&hm=059c9d28466f43a50c4b450ca26fc01298a2080356421d8524384bf67ea8f3ab&"])
.WithActionRow([
buttonBuilder
]);
}
return builder;
}