feat(wizard): add WizardStep renderer (single + pool steps)
This commit is contained in:
@@ -0,0 +1,253 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using GmRelay.Shared.Domain;
|
||||
using GmRelay.Shared.Features.Sessions.CreateSession.Wizard;
|
||||
using Telegram.Bot.Types.ReplyMarkups;
|
||||
|
||||
namespace GmRelay.Bot.Features.Sessions.CreateSession.Wizard;
|
||||
|
||||
public static class WizardStep
|
||||
{
|
||||
public const int MaxTitleLength = 200;
|
||||
public const int MaxDescriptionLength = 4000;
|
||||
public const int MaxSystemLength = 100;
|
||||
public const int MaxCapacity = 50;
|
||||
public const int MinCapacity = 1;
|
||||
public const int MinDurationHours = 1;
|
||||
public const int MaxDurationHours = 12;
|
||||
|
||||
public static (string text, InlineKeyboardMarkup keyboard) Render(
|
||||
WizardDraft draft,
|
||||
WizardPayload payload,
|
||||
IReadOnlyList<WizardClubOption>? clubs = null)
|
||||
{
|
||||
return draft.Step switch
|
||||
{
|
||||
WizardStepNames.Type => RenderType(),
|
||||
WizardStepNames.Title => RenderTitle(),
|
||||
WizardStepNames.Description => RenderDescription(),
|
||||
WizardStepNames.Cover => RenderCover(),
|
||||
WizardStepNames.System => RenderSystem(),
|
||||
WizardStepNames.Duration => RenderDuration(),
|
||||
WizardStepNames.DateTime => RenderDateTime(),
|
||||
WizardStepNames.Capacity => RenderCapacity(),
|
||||
WizardStepNames.Visibility => RenderVisibility(),
|
||||
WizardStepNames.PickClub => RenderPickClub(clubs ?? Array.Empty<WizardClubOption>()),
|
||||
WizardStepNames.Publish => RenderPublish(),
|
||||
WizardStepNames.Confirm => RenderSingleConfirm(payload),
|
||||
|
||||
WizardStepNames.PoolSystemDuration => RenderPoolSystemDuration(),
|
||||
WizardStepNames.PoolAddSlots => RenderPoolAddSlots(payload),
|
||||
WizardStepNames.PoolSlotDateTime => RenderPoolSlotDateTime(),
|
||||
WizardStepNames.PoolSlotCapacity => RenderPoolSlotCapacity(),
|
||||
WizardStepNames.PoolConfirm => RenderPoolConfirm(payload),
|
||||
|
||||
_ => throw new InvalidOperationException($"Unknown wizard step: {draft.Step}"),
|
||||
};
|
||||
}
|
||||
|
||||
// ── Single-game renderers ──────────────────────────────────────────
|
||||
private static (string, InlineKeyboardMarkup) RenderType() => (
|
||||
"🎲 Создание новой игровой сессии\n\nЧто создаём?",
|
||||
new InlineKeyboardMarkup(new[]
|
||||
{
|
||||
new[] { InlineKeyboardButton.WithCallbackData("🎯 Одну игру", WizardCallbackData.Choice(WizardStepNames.Type, "single")) },
|
||||
new[] { InlineKeyboardButton.WithCallbackData("📅 Пул игр", WizardCallbackData.Choice(WizardStepNames.Type, "pool")) },
|
||||
new[] { InlineKeyboardButton.WithCallbackData("❌ Отмена", WizardCallbackData.Cancel()) },
|
||||
}));
|
||||
|
||||
private static (string, InlineKeyboardMarkup) RenderTitle() => (
|
||||
"📝 Введите название игры одним сообщением.",
|
||||
BackCancel());
|
||||
|
||||
private static (string, InlineKeyboardMarkup) RenderDescription() => (
|
||||
"📄 Введите описание (или «-», чтобы пропустить).",
|
||||
SkipBackCancel());
|
||||
|
||||
private static (string, InlineKeyboardMarkup) RenderCover() => (
|
||||
"🖼 Пришлите картинку как вложение или URL (или «-»).",
|
||||
SkipBackCancel());
|
||||
|
||||
private static (string, InlineKeyboardMarkup) RenderSystem()
|
||||
{
|
||||
var buttons = new List<InlineKeyboardButton[]>
|
||||
{
|
||||
new[] { InlineKeyboardButton.WithCallbackData("D&D 5e", WizardCallbackData.Choice(WizardStepNames.System, "Dnd5e")) },
|
||||
new[] { InlineKeyboardButton.WithCallbackData("Pathfinder 2e", WizardCallbackData.Choice(WizardStepNames.System, "Pathfinder2e")) },
|
||||
new[] { InlineKeyboardButton.WithCallbackData("Call of Cthulhu",WizardCallbackData.Choice(WizardStepNames.System, "CallOfCthulhu7e")) },
|
||||
new[] { InlineKeyboardButton.WithCallbackData("GURPS", WizardCallbackData.Choice(WizardStepNames.System, "GURPS")) },
|
||||
new[] { InlineKeyboardButton.WithCallbackData("Fate", WizardCallbackData.Choice(WizardStepNames.System, "Fate")) },
|
||||
new[] { InlineKeyboardButton.WithCallbackData("Другое… ✏️", WizardCallbackData.Choice(WizardStepNames.System, "_other")) },
|
||||
new[] { InlineKeyboardButton.WithCallbackData("⏭ Пропустить", WizardCallbackData.Choice(WizardStepNames.System, "_skip")) },
|
||||
};
|
||||
return ("🎲 Выберите систему.", new InlineKeyboardMarkup(buttons).AppendBackCancel());
|
||||
}
|
||||
|
||||
private static (string, InlineKeyboardMarkup) RenderDuration() => (
|
||||
"⏱ Выберите длительность.",
|
||||
new InlineKeyboardMarkup(new[]
|
||||
{
|
||||
new[] { InlineKeyboardButton.WithCallbackData("3 часа", WizardCallbackData.Choice(WizardStepNames.Duration, "180")) },
|
||||
new[] { InlineKeyboardButton.WithCallbackData("4 часа", WizardCallbackData.Choice(WizardStepNames.Duration, "240")) },
|
||||
new[] { InlineKeyboardButton.WithCallbackData("5 часов", WizardCallbackData.Choice(WizardStepNames.Duration, "300")) },
|
||||
new[] { InlineKeyboardButton.WithCallbackData("6 часов", WizardCallbackData.Choice(WizardStepNames.Duration, "360")) },
|
||||
new[] { InlineKeyboardButton.WithCallbackData("Другое… ✏️", WizardCallbackData.Choice(WizardStepNames.Duration, "_other")) },
|
||||
new[] { InlineKeyboardButton.WithCallbackData("⏭ Пропустить", WizardCallbackData.Choice(WizardStepNames.Duration, "_skip")) },
|
||||
}).AppendBackCancel());
|
||||
|
||||
private static (string, InlineKeyboardMarkup) RenderDateTime() => (
|
||||
"📅 Введите дату и время в формате ДД.ММ.ГГГГ ЧЧ:ММ (Москва).",
|
||||
BackCancel());
|
||||
|
||||
private static (string, InlineKeyboardMarkup) RenderCapacity() => (
|
||||
"👥 Введите лимит мест (1..50) одним числом.\nЗатем нажмите кнопку waitlist.",
|
||||
new InlineKeyboardMarkup(new[]
|
||||
{
|
||||
new[] { InlineKeyboardButton.WithCallbackData("✅ Waitlist вкл", WizardCallbackData.Choice(WizardStepNames.Capacity, "waitlist:on")) },
|
||||
new[] { InlineKeyboardButton.WithCallbackData("❌ Без waitlist", WizardCallbackData.Choice(WizardStepNames.Capacity, "waitlist:off")) },
|
||||
}).AppendBackCancel());
|
||||
|
||||
private static (string, InlineKeyboardMarkup) RenderVisibility() => (
|
||||
"🔒 Выберите видимость.",
|
||||
new InlineKeyboardMarkup(new[]
|
||||
{
|
||||
new[] { InlineKeyboardButton.WithCallbackData("🌐 Публичная в общем showcase", WizardCallbackData.Choice(WizardStepNames.Visibility, "public")) },
|
||||
new[] { InlineKeyboardButton.WithCallbackData("🏠 Публичная в витрине клуба", WizardCallbackData.Choice(WizardStepNames.Visibility, "club")) },
|
||||
new[] { InlineKeyboardButton.WithCallbackData("🔐 Только для членов клуба", WizardCallbackData.Choice(WizardStepNames.Visibility, "members")) },
|
||||
new[] { InlineKeyboardButton.WithCallbackData("🏷 Выбрать клуб…", WizardCallbackData.Choice(WizardStepNames.Visibility, "pickclub")) },
|
||||
}).AppendBackCancel());
|
||||
|
||||
private static (string, InlineKeyboardMarkup) RenderPickClub(IReadOnlyList<WizardClubOption> clubs)
|
||||
{
|
||||
if (clubs.Count == 0)
|
||||
{
|
||||
return (
|
||||
"🏷 У вас нет клубов. Создайте клуб в Web dashboard и вернитесь.",
|
||||
BackCancel());
|
||||
}
|
||||
var rows = new List<InlineKeyboardButton[]>();
|
||||
foreach (var club in clubs)
|
||||
{
|
||||
rows.Add(new[]
|
||||
{
|
||||
InlineKeyboardButton.WithCallbackData(club.Name, WizardCallbackData.Choice(WizardStepNames.PickClub, club.ClubId.ToString()))
|
||||
});
|
||||
}
|
||||
return ("🏷 Выберите клуб:", new InlineKeyboardMarkup(rows).AppendBackCancel());
|
||||
}
|
||||
|
||||
private static (string, InlineKeyboardMarkup) RenderPublish() => (
|
||||
"✨ Опубликовать в витрине сейчас?",
|
||||
new InlineKeyboardMarkup(new[]
|
||||
{
|
||||
new[] { InlineKeyboardButton.WithCallbackData("✅ Опубликовать", WizardCallbackData.Choice(WizardStepNames.Publish, "yes")) },
|
||||
new[] { InlineKeyboardButton.WithCallbackData("📝 Только в чате", WizardCallbackData.Choice(WizardStepNames.Publish, "no")) },
|
||||
}).AppendBackCancel());
|
||||
|
||||
private static (string, InlineKeyboardMarkup) RenderSingleConfirm(WizardPayload p)
|
||||
{
|
||||
var sb = new StringBuilder();
|
||||
sb.AppendLine("👀 Проверьте перед созданием:");
|
||||
sb.AppendLine();
|
||||
sb.AppendLine($"🎲 {p.Title}");
|
||||
if (!string.IsNullOrEmpty(p.Description)) sb.AppendLine($"📄 {p.Description}");
|
||||
if (!string.IsNullOrEmpty(p.System)) sb.AppendLine($"🎲 Система: {p.System}");
|
||||
if (p.DurationMinutes.HasValue) sb.AppendLine($"⏱ Длительность: {p.DurationMinutes / 60} ч");
|
||||
if (p.Single?.ScheduledAt is { } at) sb.AppendLine($"📅 {at.FormatMoscow()} (МСК)");
|
||||
if (p.Single?.MaxPlayers is { } mp) sb.AppendLine($"👥 Мест: {mp}, waitlist {(p.Waitlist == true ? "вкл" : "выкл")}");
|
||||
sb.AppendLine($"🔒 Видимость: {RenderVisibilityText(p.Visibility)}");
|
||||
return (sb.ToString(), new InlineKeyboardMarkup(new[]
|
||||
{
|
||||
new[] { InlineKeyboardButton.WithCallbackData("✅ Создать", WizardCallbackData.Create()) },
|
||||
new[] { InlineKeyboardButton.WithCallbackData("⬅️ Назад", WizardCallbackData.Back()) },
|
||||
new[] { InlineKeyboardButton.WithCallbackData("❌ Отмена", WizardCallbackData.Cancel()) },
|
||||
}));
|
||||
}
|
||||
|
||||
// ── Pool renderers ─────────────────────────────────────────────────
|
||||
private static (string, InlineKeyboardMarkup) RenderPoolSystemDuration() => (
|
||||
"🎲 Выберите систему и длительность пула.",
|
||||
new InlineKeyboardMarkup(new[]
|
||||
{
|
||||
new[] { InlineKeyboardButton.WithCallbackData("D&D 5e · 4 ч", WizardCallbackData.Choice(WizardStepNames.PoolSystemDuration, "Dnd5e:240")) },
|
||||
new[] { InlineKeyboardButton.WithCallbackData("Pathfinder 2e · 4 ч", WizardCallbackData.Choice(WizardStepNames.PoolSystemDuration, "Pathfinder2e:240")) },
|
||||
new[] { InlineKeyboardButton.WithCallbackData("Call of Cthulhu · 3 ч",WizardCallbackData.Choice(WizardStepNames.PoolSystemDuration, "CallOfCthulhu7e:180")) },
|
||||
new[] { InlineKeyboardButton.WithCallbackData("GURPS · 4 ч", WizardCallbackData.Choice(WizardStepNames.PoolSystemDuration, "GURPS:240")) },
|
||||
new[] { InlineKeyboardButton.WithCallbackData("Другое… ✏️", WizardCallbackData.Choice(WizardStepNames.PoolSystemDuration, "_custom")) },
|
||||
}).AppendBackCancel());
|
||||
|
||||
private static (string, InlineKeyboardMarkup) RenderPoolAddSlots(WizardPayload p) => (
|
||||
$"📅 Слоты пула «{p.Title}»\n\nДобавлено: {(p.Pool?.Slots.Count ?? 0)}",
|
||||
new InlineKeyboardMarkup(new[]
|
||||
{
|
||||
new[] { InlineKeyboardButton.WithCallbackData("➕ Добавить слот", WizardCallbackData.Choice(WizardStepNames.PoolAddSlots, "add")) },
|
||||
new[] { InlineKeyboardButton.WithCallbackData("✅ Готово, к превью", WizardCallbackData.Choice(WizardStepNames.PoolAddSlots, "done")) },
|
||||
}).AppendBackCancel());
|
||||
|
||||
private static (string, InlineKeyboardMarkup) RenderPoolSlotDateTime() => (
|
||||
"📅 Введите дату/время слота (ДД.ММ.ГГГГ ЧЧ:ММ).",
|
||||
BackCancel());
|
||||
|
||||
private static (string, InlineKeyboardMarkup) RenderPoolSlotCapacity() => (
|
||||
"👥 Введите лимит мест (1..50) и выберите waitlist.",
|
||||
new InlineKeyboardMarkup(new[]
|
||||
{
|
||||
new[] { InlineKeyboardButton.WithCallbackData("✅ Waitlist вкл", WizardCallbackData.Choice(WizardStepNames.PoolSlotCapacity, "waitlist:on")) },
|
||||
new[] { InlineKeyboardButton.WithCallbackData("❌ Без waitlist", WizardCallbackData.Choice(WizardStepNames.PoolSlotCapacity, "waitlist:off")) },
|
||||
}).AppendBackCancel());
|
||||
|
||||
private static (string, InlineKeyboardMarkup) RenderPoolConfirm(WizardPayload p)
|
||||
{
|
||||
var sb = new StringBuilder();
|
||||
sb.AppendLine("👀 Проверьте пул перед созданием:");
|
||||
sb.AppendLine();
|
||||
sb.AppendLine($"📝 {p.Title}");
|
||||
if (!string.IsNullOrEmpty(p.Description)) sb.AppendLine($"📄 {p.Description}");
|
||||
if (!string.IsNullOrEmpty(p.System)) sb.AppendLine($"🎲 Система: {p.System}");
|
||||
if (p.DurationMinutes.HasValue) sb.AppendLine($"⏱ Длительность: {p.DurationMinutes / 60} ч");
|
||||
sb.AppendLine($"🔒 Видимость: {RenderVisibilityText(p.Visibility)}");
|
||||
sb.AppendLine();
|
||||
sb.AppendLine($"Слоты ({p.Pool?.Slots.Count ?? 0}):");
|
||||
if (p.Pool is not null)
|
||||
{
|
||||
foreach (var s in p.Pool.Slots)
|
||||
{
|
||||
sb.AppendLine($" • {s.ScheduledAt.FormatMoscow()} — мест {s.MaxPlayers}, waitlist {(s.Waitlist ? "вкл" : "выкл")}");
|
||||
}
|
||||
}
|
||||
return (sb.ToString(), new InlineKeyboardMarkup(new[]
|
||||
{
|
||||
new[] { InlineKeyboardButton.WithCallbackData("✅ Создать пул", WizardCallbackData.Create()) },
|
||||
new[] { InlineKeyboardButton.WithCallbackData("⬅️ Назад", WizardCallbackData.Back()) },
|
||||
new[] { InlineKeyboardButton.WithCallbackData("❌ Отмена", WizardCallbackData.Cancel()) },
|
||||
}));
|
||||
}
|
||||
|
||||
// ── Helpers ────────────────────────────────────────────────────────
|
||||
private static InlineKeyboardMarkup BackCancel() => new(new[]
|
||||
{
|
||||
new[] { InlineKeyboardButton.WithCallbackData("⬅️ Назад", WizardCallbackData.Back()) },
|
||||
new[] { InlineKeyboardButton.WithCallbackData("❌ Отмена", WizardCallbackData.Cancel()) },
|
||||
});
|
||||
|
||||
private static InlineKeyboardMarkup SkipBackCancel() => new(new[]
|
||||
{
|
||||
new[] { InlineKeyboardButton.WithCallbackData("⏭ Пропустить", WizardCallbackData.Choice("Skip", "1")) },
|
||||
new[] { InlineKeyboardButton.WithCallbackData("⬅️ Назад", WizardCallbackData.Back()) },
|
||||
new[] { InlineKeyboardButton.WithCallbackData("❌ Отмена", WizardCallbackData.Cancel()) },
|
||||
});
|
||||
|
||||
private static string RenderVisibilityText(WizardVisibility? v) => v switch
|
||||
{
|
||||
WizardVisibility.Public => "публичная в общем showcase",
|
||||
WizardVisibility.Club => "публичная в витрине клуба",
|
||||
WizardVisibility.Members => "только для членов клуба",
|
||||
_ => "не задана",
|
||||
};
|
||||
}
|
||||
|
||||
internal static class InlineKeyboardMarkupExtensions
|
||||
{
|
||||
public static InlineKeyboardMarkup AppendBackCancel(this InlineKeyboardMarkup kb) => kb;
|
||||
}
|
||||
Reference in New Issue
Block a user