Refactor bumper preview functionality: update API endpoints to support rendering previews for all bumper sub-blocks, modify related query and handler to generate individual asset previews, and enhance frontend components for improved user experience with variant-specific previews. Update translations for consistency in terminology.

This commit is contained in:
Leonid Pershin
2026-07-25 13:31:38 +03:00
parent a65bcf4258
commit 18325fdfcc
6 changed files with 116 additions and 89 deletions
@@ -4,8 +4,7 @@ using TeleWave.Application.Common.Models;
namespace TeleWave.Application.Broadcast.Bumpers;
/// <summary>
/// Синхронно рендерит пример заставки блока (с примерными названиями шоу) и возвращает id
/// ассета-превью. БД не меняет — это read-side генерация артефакта для предпросмотра.
/// Синхронно рендерит примеры всех подблоков блока (с примерными названиями шоу). Каждый подблок —
/// в свой ассет-превью (id детерминирован по подблоку). БД не меняет — read-side генерация артефактов.
/// </summary>
public sealed record RenderBumperPreviewQuery(Guid ChannelId, Guid TemplateId)
: IQuery<Result<Guid>>;
public sealed record RenderBumperPreviewQuery(Guid ChannelId, Guid TemplateId) : IQuery<Result>;
@@ -15,7 +15,7 @@ public sealed class RenderBumperPreviewQueryHandler(
IImageStore imageStore,
IOptions<BumperOptions> bumperOptions,
IOptions<StreamingOptions> streamingOptions
) : IQueryHandler<RenderBumperPreviewQuery, Result<Guid>>
) : IQueryHandler<RenderBumperPreviewQuery, Result>
{
private readonly BumperOptions _bumper = bumperOptions.Value;
private readonly int _segmentSeconds = Math.Max(1, streamingOptions.Value.SegmentSeconds);
@@ -23,7 +23,7 @@ public sealed class RenderBumperPreviewQueryHandler(
/// <summary>Длительность заставки без загруженного звука (сек) — как в генераторе.</summary>
private const int DefaultBumperDurationSeconds = 8;
public async Task<Result<Guid>> Handle(
public async Task<Result> Handle(
RenderBumperPreviewQuery query,
CancellationToken cancellationToken
)
@@ -35,21 +35,17 @@ public sealed class RenderBumperPreviewQueryHandler(
.AsSplitQuery()
.FirstOrDefaultAsync(c => c.Id == query.ChannelId, cancellationToken);
if (channel is null)
return Result.Failure<Guid>(ChannelErrors.NotFound);
return Result.Failure(ChannelErrors.NotFound);
var template = channel.BumperTemplates.FirstOrDefault(t => t.Id == query.TemplateId);
if (template is null)
return Result.Failure<Guid>(ChannelErrors.BumperTemplateNotFound);
// Превью показываем по первому подблоку (стиль/звук блока + его текст).
var variant = template.Variants.OrderBy(v => v.Position).FirstOrDefault();
if (variant is null)
return Result.Failure<Guid>(ChannelErrors.BumperTemplateNotFound);
return Result.Failure(ChannelErrors.BumperTemplateNotFound);
var (fromName, toName) = await SampleNamesAsync(channel, cancellationToken);
var free = variant.Kind == BumperTextKind.Free;
var fontFile =
channel.BumperFont == BumperFont.Serif ? _bumper.FontFileSerif : _bumper.FontFileSans;
// Фон блока — из общего реестра по id.
// Фон блока — из общего реестра по id (общий для всех подблоков).
string? backgroundPath = null;
if (template.BackgroundImageId is { } bgId)
{
@@ -66,32 +62,37 @@ public sealed class RenderBumperPreviewQueryHandler(
var aligned = (int)(
Math.Ceiling(Math.Max(_segmentSeconds, seconds) / _segmentSeconds) * _segmentSeconds
);
var audioPath = storage.AudioPath(template.Id, template.AudioExtension);
var spec = new BumperRenderSpec(
aligned,
_bumper.Width,
_bumper.Height,
template.BackgroundColor,
template.BackgroundColor2,
template.AccentColor,
template.TextColor,
channel.BumperFont == BumperFont.Serif ? _bumper.FontFileSerif : _bumper.FontFileSans,
free ? "" : variant.NowLabel,
free ? "" : fromName,
free ? "" : variant.NextLabel,
free ? "" : toName,
backgroundPath,
storage.AudioPath(template.Id, template.AudioExtension),
// Постер зависит от конкретного «следующего» шоу — в превью не подставляем.
null,
free,
variant.Line1,
variant.Line2
);
// Рендерим каждый подблок в свой ассет-превью (id по подблоку).
foreach (var variant in template.Variants.OrderBy(v => v.Position))
{
var free = variant.Kind == BumperTextKind.Free;
var spec = new BumperRenderSpec(
aligned,
_bumper.Width,
_bumper.Height,
template.BackgroundColor,
template.BackgroundColor2,
template.AccentColor,
template.TextColor,
fontFile,
free ? "" : variant.NowLabel,
free ? "" : fromName,
free ? "" : variant.NextLabel,
free ? "" : toName,
backgroundPath,
audioPath,
// Постер зависит от конкретного «следующего» шоу — в превью не подставляем.
null,
free,
variant.Line1,
variant.Line2
);
await renderer.RenderAsync(BumperPreview.AssetId(variant.Id), spec, cancellationToken);
}
var previewId = BumperPreview.AssetId(template.Id);
await renderer.RenderAsync(previewId, spec, cancellationToken);
return Result.Success(previewId);
return Result.Success();
}
/// <summary>Примерные названия «из/в» — берём первые два шоу канала, иначе заглушки.</summary>