Implement background clip functionality for bumpers
Enhanced the bumper system to support a new background type, 'Clip', allowing text to be overlaid on existing video clips. This update includes the addition of a BackgroundClipShowId property in various records and classes, ensuring that the clip's duration dictates the bumper length. Updated validation rules, mapping, and rendering logic to accommodate this new feature. Localization strings were also updated to reflect the new background option and its implications for audio handling.
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import { useMutation } from '@tanstack/react-query'
|
||||
import { useMutation, useQuery } from '@tanstack/react-query'
|
||||
import { useEffect, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import type {
|
||||
@@ -13,13 +13,15 @@ import { Input } from '@/shared/ui/input'
|
||||
import { Label } from '@/shared/ui/label'
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/shared/ui/select'
|
||||
import { toast } from '@/shared/ui/toast-store'
|
||||
import { listInterstitials } from '@/features/admin/interstitials/api'
|
||||
import { qk } from '@/shared/api/query-keys'
|
||||
import { removeBumperVariant, updateBumperVariant } from '../api'
|
||||
import { unknownTokens } from '../placeholders'
|
||||
import { BumperFramePreview } from './BumperFramePreview'
|
||||
import { BumperLinesEditor } from './BumperLinesEditor'
|
||||
|
||||
const TRIGGERS: BumperTrigger[] = ['OnShowChange', 'BetweenEpisodes', 'Both']
|
||||
const BACKGROUNDS: BumperBackground[] = ['Template', 'NextPoster', 'NowPoster']
|
||||
const BACKGROUNDS: BumperBackground[] = ['Template', 'NextPoster', 'NowPoster', 'Clip']
|
||||
|
||||
/**
|
||||
* Подблок: слева строки, справа постоянный предпросмотр кадра. Предпросмотр не опциональный —
|
||||
@@ -39,10 +41,16 @@ export function BumperVariantEditor({
|
||||
onError: (e: unknown) => void
|
||||
}>) {
|
||||
const { t } = useTranslation()
|
||||
// Ролики грузим списком: их немного, а выбирать подложку удобнее из готового перечня.
|
||||
const { data: clips } = useQuery({
|
||||
queryKey: qk.interstitials.all,
|
||||
queryFn: listInterstitials,
|
||||
})
|
||||
const [form, setForm] = useState({
|
||||
name: variant.name,
|
||||
trigger: variant.trigger,
|
||||
background: variant.background,
|
||||
backgroundClipShowId: variant.backgroundClipShowId,
|
||||
weight: variant.weight,
|
||||
lines: variant.lines,
|
||||
})
|
||||
@@ -52,6 +60,7 @@ export function BumperVariantEditor({
|
||||
name: variant.name,
|
||||
trigger: variant.trigger,
|
||||
background: variant.background,
|
||||
backgroundClipShowId: variant.backgroundClipShowId,
|
||||
weight: variant.weight,
|
||||
lines: variant.lines,
|
||||
})
|
||||
@@ -128,6 +137,31 @@ export function BumperVariantEditor({
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
{/* Ролик-подложка — только при своём источнике фона: в остальных случаях поле было бы
|
||||
настройкой, которая ни на что не влияет. */}
|
||||
{form.background === 'Clip' && (
|
||||
<div className="flex flex-col gap-1.5">
|
||||
<Label>{t('admin.bumpers.backgroundClip')}</Label>
|
||||
<Select
|
||||
value={form.backgroundClipShowId ?? ''}
|
||||
onValueChange={(v) => set('backgroundClipShowId', v)}
|
||||
>
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder={t('admin.bumpers.pickClip')} />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{(clips ?? []).map((clip) => (
|
||||
<SelectItem key={clip.id} value={clip.id}>
|
||||
{clip.name}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<span className="text-xs text-muted-foreground">
|
||||
{t('admin.bumpers.backgroundClipHint')}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
<div className="flex flex-col gap-1.5">
|
||||
<Label>{t('admin.bumpers.weight')}</Label>
|
||||
<Input
|
||||
|
||||
@@ -510,7 +510,7 @@ export type BumperTrigger = 'OnShowChange' | 'BetweenEpisodes' | 'Both'
|
||||
export type BumperLineStyle = 'Label' | 'Title' | 'Caption'
|
||||
export type BumperLineColor = 'Accent' | 'Text'
|
||||
/** Что за картинка под текстом: фон блока, постер следующего или предыдущего шоу. */
|
||||
export type BumperBackground = 'Template' | 'NextPoster' | 'NowPoster'
|
||||
export type BumperBackground = 'Template' | 'NextPoster' | 'NowPoster' | 'Clip'
|
||||
|
||||
/** Строка заставки: роль, цвет из палитры блока и текст с плейсхолдерами. */
|
||||
export type BumperLineDto = {
|
||||
@@ -529,6 +529,8 @@ export type BumperTextVariantDto = {
|
||||
/** Вес при выборе подблока на переходе (0 — не выбирается никогда). */
|
||||
weight: number
|
||||
lines: BumperLineDto[]
|
||||
/** Ролик-подложка для фона `Clip`: текст пишется поверх готового видео. */
|
||||
backgroundClipShowId: string | null
|
||||
}
|
||||
|
||||
/** Блок заставки — общий для всех каналов, как группа. */
|
||||
|
||||
@@ -875,7 +875,12 @@ export const en = {
|
||||
Template: 'Block background',
|
||||
NextPoster: 'Next show poster',
|
||||
NowPoster: 'Previous show poster',
|
||||
Clip: 'Existing clip',
|
||||
},
|
||||
backgroundClip: 'Backing clip',
|
||||
pickClip: 'Pick a clip',
|
||||
backgroundClipHint:
|
||||
'The text is drawn over the video. The clip sets the bumper length; the block’s audio is trimmed to it, and without audio the clip’s own track plays.',
|
||||
fileLoaded: 'loaded',
|
||||
fileDefault: 'default',
|
||||
upload: 'Upload',
|
||||
|
||||
@@ -870,7 +870,12 @@ export const ru = {
|
||||
Template: 'Фон блока',
|
||||
NextPoster: 'Постер следующего шоу',
|
||||
NowPoster: 'Постер предыдущего шоу',
|
||||
Clip: 'Готовый ролик',
|
||||
},
|
||||
backgroundClip: 'Ролик-подложка',
|
||||
pickClip: 'Выберите ролик',
|
||||
backgroundClipHint:
|
||||
'Текст пишется поверх видео. Длину заставки задаёт сам ролик; звук блока подрезается под неё, а без звука играет оригинальная дорожка ролика.',
|
||||
fileLoaded: 'загружено',
|
||||
fileDefault: 'по умолчанию',
|
||||
upload: 'Загрузить',
|
||||
|
||||
Reference in New Issue
Block a user