mirror of
https://github.com/lichess-org/lila.git
synced 2026-05-26 13:51:00 +00:00
604 lines
20 KiB
TypeScript
604 lines
20 KiB
TypeScript
import type { VNode } from 'snabbdom';
|
|
|
|
import { defined, memoize } from 'lib';
|
|
import { renderChat } from 'lib/chat/renderChat';
|
|
import { displayColumns } from 'lib/device';
|
|
import { commonDateFormat, timeago } from 'lib/i18n';
|
|
import * as licon from 'lib/licon';
|
|
import { pubsub } from 'lib/pubsub';
|
|
import { innerHTML, richHTML } from 'lib/richText';
|
|
import { bind, dataIcon, onInsert, hl, type LooseVNode, copyMeInput } from 'lib/view';
|
|
import { cmnToggleWrap } from 'lib/view/cmn-toggle';
|
|
import { userLink } from 'lib/view/userLink';
|
|
import { verticalResize } from 'lib/view/verticalResize';
|
|
import { watchers } from 'lib/view/watchers';
|
|
import { text as xhrText } from 'lib/xhr';
|
|
|
|
import type AnalyseCtrl from '@/ctrl';
|
|
import { type RelayViewContext } from '@/view/components';
|
|
import { baseUrl } from '@/view/util';
|
|
|
|
import { view as multiBoardView } from '../multiBoard';
|
|
import { gameLinksListener } from '../studyChapters';
|
|
import type StudyCtrl from '../studyCtrl';
|
|
import type {
|
|
RelayData,
|
|
RelayGroup,
|
|
RelayRound,
|
|
RelayTourDates,
|
|
RelayTourInfo,
|
|
RelayTourPreview,
|
|
} from './interfaces';
|
|
import RelayCtrl, { type RelayTab } from './relayCtrl';
|
|
import { gamesList } from './relayGames';
|
|
import { playersView } from './relayPlayers';
|
|
import { statsView } from './relayStats';
|
|
import { teamsView } from './relayTeams';
|
|
import { renderStreamerMenu } from './relayView';
|
|
|
|
export function renderRelayTour(ctx: RelayViewContext): VNode | undefined {
|
|
const tab = ctx.relay.tab();
|
|
const content =
|
|
tab === 'boards'
|
|
? games(ctx)
|
|
: tab === 'teams'
|
|
? teams(ctx)
|
|
: tab === 'team-results'
|
|
? teamResults(ctx)
|
|
: tab === 'stats'
|
|
? stats(ctx)
|
|
: tab === 'players'
|
|
? players(ctx)
|
|
: overview(ctx);
|
|
|
|
return hl('div.box.relay-tour', content);
|
|
}
|
|
|
|
export const tourSide = (ctx: RelayViewContext, kid: LooseVNode) => {
|
|
const { ctrl, study, relay } = ctx;
|
|
const empty = study.chapters.list.looksNew();
|
|
const resizeId =
|
|
!ctrl.isEmbed && displayColumns() > (ctx.hasRelayTour ? 1 : 2) && `relayTour/${relay.data.tour.id}`;
|
|
return hl(
|
|
'aside.relay-tour__side',
|
|
{
|
|
hook: {
|
|
insert: gameLinksListener(study.chapterSelect),
|
|
update: v => {
|
|
if (resizeId) return;
|
|
(v.elm as HTMLElement).querySelectorAll<HTMLElement>('.relay-games, .mchat').forEach(el => {
|
|
el.style.height = el.style.flex = '';
|
|
});
|
|
},
|
|
},
|
|
},
|
|
[
|
|
empty
|
|
? [startCountdown(relay)]
|
|
: [
|
|
hl('div.relay-tour__side__header', [
|
|
hl(
|
|
'button.relay-tour__side__name',
|
|
{ hook: bind('mousedown', relay.tourShow.toggle, relay.redraw) },
|
|
relay.round.name,
|
|
),
|
|
!ctrl.isEmbed &&
|
|
hl('button.streamer-show.data-count', {
|
|
attrs: {
|
|
'data-icon': licon.Mic,
|
|
'data-count': relay.streams.length,
|
|
title: i18n.site.streamersMenu,
|
|
},
|
|
class: {
|
|
disabled: !relay.streams.length,
|
|
active: relay.showStreamerMenu(),
|
|
streaming: relay.isStreamer(),
|
|
},
|
|
hook: bind('click', relay.showStreamerMenu.toggle, relay.redraw),
|
|
}),
|
|
hl('button.relay-tour__side__search', {
|
|
attrs: { 'data-icon': licon.Search },
|
|
hook: bind('click', study.search.open.toggle),
|
|
}),
|
|
]),
|
|
],
|
|
!ctrl.isEmbed && relay.showStreamerMenu() && renderStreamerMenu(relay),
|
|
!empty ? gamesList(study, relay) : hl('div.vertical-spacer'),
|
|
!empty &&
|
|
resizeId &&
|
|
verticalResize({
|
|
key: `relay-games.${resizeId}`,
|
|
min: () => 50, // Height of one .relay-game in _tour.scss
|
|
max: () => 50 * study.chapters.list.size(),
|
|
initialMaxHeight: () => window.innerHeight / 2,
|
|
}),
|
|
ctx.ctrl.chatCtrl && renderChat(ctx.ctrl.chatCtrl),
|
|
resizeId &&
|
|
verticalResize({
|
|
key: 'relay-chat',
|
|
id: resizeId,
|
|
min: () => 0,
|
|
max: () => window.innerHeight,
|
|
initialMaxHeight: () => window.innerHeight / 3,
|
|
kid: hl('div.chat__members', { hook: onInsert(el => watchers(el, false)) }),
|
|
}),
|
|
kid,
|
|
],
|
|
);
|
|
};
|
|
|
|
const startCountdown = (relay: RelayCtrl) => {
|
|
const round = relay.round,
|
|
startsAt = defined(round.startsAt) && new Date(round.startsAt),
|
|
date = startsAt && hl('time', commonDateFormat(startsAt));
|
|
return hl('div.relay-tour__side__empty', { attrs: dataIcon(licon.RadioTower) }, [
|
|
hl('strong', round.name),
|
|
startsAt
|
|
? startsAt.getTime() < Date.now() + 1000 * 10 * 60 // in the last 10 minutes, only say it's soon.
|
|
? [i18n.broadcast.startVerySoon, date]
|
|
: [hl('strong', timeago(startsAt)), date]
|
|
: [i18n.broadcast.notYetStarted],
|
|
]);
|
|
};
|
|
|
|
const players = (ctx: RelayViewContext) => [header(ctx), playersView(ctx.relay.players)];
|
|
|
|
export const showInfo = (i: RelayTourInfo, dates?: RelayTourDates) => {
|
|
const contents = [
|
|
['dates', dates && showDates(dates), 'objects.spiral-calendar', 'Dates'],
|
|
['format', i.format, 'objects.crown', 'Format'],
|
|
['tc', i.tc, 'objects.mantelpiece-clock', 'Time control'],
|
|
['location', i.location, 'travel-places.globe-showing-europe-africa', 'Location'],
|
|
['players', i.players, 'activity.sparkles', 'Star players'],
|
|
['website', i.website, null, null, i18n.broadcast.officialWebsite],
|
|
['standings', i.standings, null, null, i18n.site.standings],
|
|
]
|
|
.map(
|
|
([key, value, icon, textAlternative, linkName]) =>
|
|
key &&
|
|
value &&
|
|
hl('div.relay-tour__info__' + key, [
|
|
icon && hl('img', { attrs: { src: site.asset.flairSrc(icon), alt: textAlternative! } }),
|
|
linkName
|
|
? hl('a', { attrs: { href: value, target: '_blank', rel: 'nofollow noreferrer' } }, linkName)
|
|
: value,
|
|
]),
|
|
)
|
|
.filter(defined);
|
|
|
|
return contents.length ? hl('div.relay-tour__info', contents) : undefined;
|
|
};
|
|
|
|
const dateFormat = memoize(() =>
|
|
window.Intl && Intl.DateTimeFormat
|
|
? new Intl.DateTimeFormat(site.displayLocale, {
|
|
month: 'short',
|
|
day: '2-digit',
|
|
}).format
|
|
: (d: Date) => d.toLocaleDateString(),
|
|
);
|
|
|
|
const showDates = (dates: RelayTourDates) => {
|
|
const rendered = dates.map(date => dateFormat()(new Date(date)));
|
|
// if the tournament only lasts one day, don't show the end date
|
|
return rendered[1] ? `${rendered[0]} - ${rendered[1]}` : rendered[0];
|
|
};
|
|
|
|
const showSource = (data: RelayData) =>
|
|
data.lcc
|
|
? hl('div.relay-tour__source', [
|
|
'PGN source: ',
|
|
hl('a', { attrs: { href: 'https://www.livechesscloud.com' } }, 'LiveChessCloud'),
|
|
])
|
|
: undefined;
|
|
|
|
const overview = (ctx: RelayViewContext) => {
|
|
const tour = ctx.relay.data.tour;
|
|
return [
|
|
header(ctx),
|
|
showInfo(tour.info, tour.dates),
|
|
tour.description &&
|
|
hl('div.relay-tour__markup', {
|
|
hook: innerHTML(tour.description, () => tour.description!),
|
|
}),
|
|
ctx.ctrl.isEmbed || [showSource(ctx.relay.data), share(ctx)],
|
|
];
|
|
};
|
|
|
|
export const relayIframe = (path: string) =>
|
|
`<iframe src="${baseUrl()}/embed${path}" style="width: 100%; aspect-ratio: 4/3;" frameborder="0"></iframe>`;
|
|
|
|
const share = (ctx: RelayViewContext) => {
|
|
const iframeHelp = hl(
|
|
'div.form-help',
|
|
i18n.broadcast.iframeHelp.asArray(
|
|
hl('a', { attrs: { href: '/developers#broadcast' } }, i18n.broadcast.webmastersPage),
|
|
),
|
|
);
|
|
const link = (text: string, path: string, help?: VNode) =>
|
|
hl('div.form-group', [
|
|
hl('label.form-label', text),
|
|
copyMeInput(path.startsWith('/') ? `${baseUrl()}${path}` : path),
|
|
help,
|
|
]);
|
|
const roundName = ctx.relay.round.name;
|
|
const { tour, group } = ctx.relay.data;
|
|
return hl(
|
|
'div.relay-tour__share-all',
|
|
{
|
|
hook: onInsert(_ => pubsub.emit('content-loaded')),
|
|
},
|
|
[
|
|
hl('fieldset.relay-tour__share.toggle-box.toggle-box--toggle', [
|
|
hl('legend', { attrs: { tabindex: 0 } }, 'Share this broadcast by URL'),
|
|
group && link(group.name, `/broadcast/${group.slug}/${group.id}`),
|
|
link(tour.name, ctx.relay.tourPath()),
|
|
link(tour.name + ' | ' + roundName, ctx.relay.roundPath()),
|
|
]),
|
|
hl('fieldset.relay-tour__share.toggle-box.toggle-box--toggle.toggle-box--toggle-off', [
|
|
hl('legend', { attrs: { tabindex: 0 } }, 'Download PGN'),
|
|
hl('p.form-group', [
|
|
'We offer full PGN downloads for all our broadcasts.',
|
|
hl('br'),
|
|
'To synchronize ongoing games, use ',
|
|
hl(
|
|
'a',
|
|
{ attrs: { href: '/api#tag/broadcasts/GET/api/stream/broadcast/round/{broadcastRoundId}.pgn' } },
|
|
'our free streaming API',
|
|
),
|
|
' for stupendous speed and efficiency.',
|
|
hl('br'),
|
|
'To download all the broadcasts, use ',
|
|
hl(
|
|
'a',
|
|
{ attrs: { href: 'https://database.lichess.org/#broadcasts' } },
|
|
'our full database exports',
|
|
),
|
|
'.',
|
|
hl('br'),
|
|
hl('p.form-help', [
|
|
'You can remove clocks and evals from the PGN by adding query parameters, for example: ',
|
|
hl('br'),
|
|
hl('code', '?clocks=false&comments=false'),
|
|
]),
|
|
]),
|
|
link('This round: ' + roundName, `${ctx.relay.roundPath()}.pgn`),
|
|
link(
|
|
'This tournament: ' + tour.name,
|
|
`/api/broadcast/${tour.id}.pgn`,
|
|
hl('div.form-help', 'All games of all rounds of this tournament. It may take a while to download.'),
|
|
),
|
|
hl('p.form-group', 'Individual game download is available on each game page.'),
|
|
]),
|
|
hl('fieldset.relay-tour__share.toggle-box.toggle-box--toggle.toggle-box--toggle-off', [
|
|
hl('legend', { attrs: { tabindex: 0 } }, i18n.broadcast.embedThisBroadcast),
|
|
group &&
|
|
link('Follow ongoing tournament', relayIframe(`/broadcast/${group.slug}/${group.id}`), iframeHelp),
|
|
link('This tournament: ' + tour.name, relayIframe(ctx.relay.tourPath()), iframeHelp),
|
|
link('This round: ' + roundName, relayIframe(ctx.relay.roundPath()), iframeHelp),
|
|
]),
|
|
],
|
|
);
|
|
};
|
|
|
|
const tourSelect = (ctx: RelayViewContext, group: RelayGroup) => {
|
|
const { relay, study } = ctx;
|
|
const inputId = 'mselect-relay-tour';
|
|
|
|
const updateCheckboxAndToggle = () => {
|
|
const checkbox = document.querySelector<HTMLInputElement>(`#${inputId}`);
|
|
if (checkbox) checkbox.checked = false;
|
|
relay.tourSelectShow(!checkbox);
|
|
};
|
|
|
|
return hl(
|
|
'div.mselect.relay-tour__mselect.relay-tour__tour-select',
|
|
{
|
|
class: { mselect__active: relay.tourSelectShow() },
|
|
},
|
|
[
|
|
hl('input.mselect__toggle', {
|
|
attrs: { type: 'checkbox', id: inputId },
|
|
on: { change: relay.tourSelectShow.toggle },
|
|
}),
|
|
hl(
|
|
'label.mselect__label',
|
|
{ attrs: { for: inputId } },
|
|
group.tours.find(t => t.id === relay.data.tour.id)?.name || relay.data.tour.name,
|
|
),
|
|
relay.tourSelectShow() && [
|
|
hl('label.fullscreen-mask', { on: { click: updateCheckboxAndToggle } }),
|
|
hl(
|
|
'nav.mselect__list',
|
|
group.tours.map(tour =>
|
|
hl(
|
|
'a.mselect__item',
|
|
{
|
|
class: {
|
|
current: tour.id === relay.data.tour.id,
|
|
},
|
|
attrs: { href: study.embeddablePath(`/broadcast/-/${tour.id}`) },
|
|
},
|
|
[tour.name, tourStateIcon(tour, false)],
|
|
),
|
|
),
|
|
),
|
|
],
|
|
],
|
|
);
|
|
};
|
|
|
|
const tourStateIcon = (tour: RelayTourPreview, titleAsText: boolean) =>
|
|
tour.live
|
|
? hl('span.tour-state.ongoing', {
|
|
attrs: { ...dataIcon(licon.DiscBig), title: i18n.broadcast.ongoing },
|
|
})
|
|
: !tour.active
|
|
? hl(
|
|
'span.tour-state.finished',
|
|
{ attrs: { ...dataIcon(licon.Checkmark), title: !titleAsText && i18n.site.finished } },
|
|
titleAsText && i18n.site.finished,
|
|
)
|
|
: undefined;
|
|
|
|
const roundSelect = (relay: RelayCtrl, study: StudyCtrl) => {
|
|
const { round } = relay;
|
|
const icon = roundStateIcon(round, true);
|
|
const inputId = 'mselect-relay-round';
|
|
|
|
const updateCheckboxAndToggle = () => {
|
|
const checkbox = document.querySelector<HTMLInputElement>(`#${inputId}`);
|
|
if (checkbox) checkbox.checked = false;
|
|
relay.roundSelectShow(!checkbox);
|
|
};
|
|
const extractHrefAndNavigate = (e: PointerEvent, round: RelayRound) => {
|
|
if (e.metaKey) return;
|
|
const href = study.embeddablePath(relay.roundUrlWithHash(round));
|
|
if (href && href.split('#')[0] !== window.location.pathname) {
|
|
site.redirect(href);
|
|
} else {
|
|
e.preventDefault();
|
|
updateCheckboxAndToggle();
|
|
}
|
|
};
|
|
|
|
return hl(
|
|
'div.mselect.relay-tour__mselect.relay-tour__round-select',
|
|
{
|
|
class: { mselect__active: relay.roundSelectShow() },
|
|
},
|
|
[
|
|
hl('input.mselect__toggle', {
|
|
attrs: { type: 'checkbox', id: inputId },
|
|
on: { change: relay.roundSelectShow.toggle },
|
|
}),
|
|
hl(
|
|
'label.mselect__label.relay-tour__round-select__label',
|
|
{
|
|
attrs: { for: inputId },
|
|
},
|
|
[
|
|
hl('span.relay-tour__round-select__name', round.name),
|
|
hl('span.relay-tour__round-select__status', icon || (!!round.startsAt && timeago(round.startsAt))),
|
|
],
|
|
),
|
|
relay.roundSelectShow() && [
|
|
hl('label.fullscreen-mask', { on: { click: updateCheckboxAndToggle } }),
|
|
hl(
|
|
'div.relay-tour__round-select__list.mselect__list',
|
|
{
|
|
hook: onInsert(el => {
|
|
const goTo = el.querySelector('.ongoing-round') ?? el.querySelector('.current-round');
|
|
goTo
|
|
?.closest('.relay-tour__round-select')
|
|
?.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
|
|
}),
|
|
},
|
|
relay.data.rounds.map((round, i) =>
|
|
hl(
|
|
'a.mselect__item',
|
|
{
|
|
attrs: { href: study.embeddablePath(relay.roundUrlWithHash(round)) },
|
|
class: {
|
|
'current-round': round.id === study.data.id,
|
|
'ongoing-round': !!round.ongoing,
|
|
},
|
|
on: {
|
|
click: (e: PointerEvent) => extractHrefAndNavigate(e, round),
|
|
},
|
|
},
|
|
[
|
|
hl('span.name', round.name),
|
|
hl(
|
|
'span.time',
|
|
round.startsAt
|
|
? commonDateFormat(new Date(round.startsAt))
|
|
: round.startsAfterPrevious &&
|
|
i18n.broadcast.startsAfter(
|
|
relay.data.rounds[i - 1] ? relay.data.rounds[i - 1].name : 'the previous round',
|
|
),
|
|
),
|
|
hl(
|
|
'span.status',
|
|
roundStateIcon(round, false) || (!!round.startsAt && timeago(round.startsAt)),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
],
|
|
],
|
|
);
|
|
};
|
|
|
|
const games = (ctx: RelayViewContext) => [
|
|
header(ctx),
|
|
ctx.study.chapters.list.looksNew()
|
|
? renderNote(
|
|
hl('div', i18n.broadcast.noBoardsYet),
|
|
ctx.study.members.myMember() &&
|
|
hl(
|
|
'small',
|
|
i18n.broadcast.boardsCanBeLoaded.asArray(
|
|
hl('a', { attrs: { href: '/broadcast/app' } }, 'Broadcaster App'),
|
|
),
|
|
),
|
|
)
|
|
: multiBoardView(ctx.study.multiBoard, ctx.study),
|
|
!ctx.ctrl.isEmbed && showSource(ctx.relay.data),
|
|
];
|
|
|
|
const teams = (ctx: RelayViewContext) => [
|
|
header(ctx),
|
|
ctx.relay.teams && teamsView(ctx.relay.teams, ctx.study.chapters.list, ctx.relay.players),
|
|
];
|
|
|
|
const teamResults = (ctx: RelayViewContext) => [
|
|
header(ctx),
|
|
ctx.relay.teams && ctx.relay.teamLeaderboard.view(),
|
|
];
|
|
|
|
const stats = (ctx: RelayViewContext) => [header(ctx), statsView(ctx.relay.stats)];
|
|
|
|
const renderNote = (title: VNode, desc?: VNode) => hl('div.relay-tour__note', hl('div', [title, desc]));
|
|
|
|
const header = (ctx: RelayViewContext) => {
|
|
const { ctrl, relay } = ctx;
|
|
const d = relay.data,
|
|
group = d.group,
|
|
studyD = ctrl.study?.data.description;
|
|
|
|
return [
|
|
hl('div.relay-tour__header', [
|
|
hl('div.relay-tour__header__content', [
|
|
hl('h1', group?.name || d.tour.name),
|
|
hl('div.relay-tour__header__selectors', [
|
|
group && tourSelect(ctx, group),
|
|
roundSelect(relay, ctx.study),
|
|
]),
|
|
]),
|
|
broadcastImageOrStream(ctx),
|
|
]),
|
|
studyD && hl('div.relay-tour__note.pinned', hl('div', [hl('div', { hook: richHTML(studyD, false) })])),
|
|
d.tour.communityOwner &&
|
|
renderNote(
|
|
hl('div', i18n.broadcast.communityBroadcast),
|
|
hl(
|
|
'small',
|
|
i18n.broadcast.createdAndManagedBy.asArray(
|
|
userLink({
|
|
...d.tour.communityOwner,
|
|
flair: undefined,
|
|
}),
|
|
),
|
|
),
|
|
),
|
|
d.note &&
|
|
renderNote(
|
|
hl('div', { hook: richHTML(d.note, false) }),
|
|
hl('small', 'This note is visible to contributors only.'),
|
|
),
|
|
delayedUntil(ctx),
|
|
hl('div.relay-tour__nav', [makeTabs(ctrl), subscribe(relay, ctrl)]),
|
|
];
|
|
};
|
|
|
|
const delayedUntil = (ctx: RelayViewContext) => {
|
|
const date = ctx.relay.data.delayedUntil;
|
|
return (
|
|
date &&
|
|
renderNote(
|
|
hl('div', ['Transmission will start ', date > Date.now() ? timeago(date) : 'momentarily']),
|
|
hl('small', 'The tournament organizers have requested that moves be delayed.'),
|
|
)
|
|
);
|
|
};
|
|
|
|
const subscribe = (relay: RelayCtrl, ctrl: AnalyseCtrl) =>
|
|
defined(relay.data.isSubscribed)
|
|
? [
|
|
cmnToggleWrap({
|
|
id: 'tour-subscribe',
|
|
name: i18n.site.subscribe,
|
|
title: i18n.broadcast.subscribeTitle,
|
|
checked: relay.data.isSubscribed,
|
|
change(v) {
|
|
xhrText(`/broadcast/${relay.data.tour.id}/subscribe?set=${v}`, { method: 'post' });
|
|
relay.data.isSubscribed = v;
|
|
},
|
|
redraw: ctrl.redraw,
|
|
}),
|
|
]
|
|
: [];
|
|
|
|
const makeTabs = (ctrl: AnalyseCtrl) => {
|
|
const study = ctrl.study,
|
|
relay = study?.relay;
|
|
if (!relay) return undefined;
|
|
|
|
const makeTab = (key: RelayTab, name: string) =>
|
|
hl(
|
|
`button.relay-tour__tabs--${key}`,
|
|
{
|
|
class: { active: relay.tab() === key },
|
|
attrs: { role: 'tab' },
|
|
on: {
|
|
click: () => relay.openTab(key),
|
|
},
|
|
},
|
|
name,
|
|
);
|
|
return hl('nav.relay-tour__tabs', { attrs: { role: 'tablist' } }, [
|
|
makeTab('overview', i18n.broadcast.overview),
|
|
makeTab('boards', i18n.broadcast.boards),
|
|
makeTab('players', i18n.site.players),
|
|
relay.teams && makeTab('teams', i18n.broadcast.teams),
|
|
relay.data.tour.showTeamScores && makeTab('team-results', i18n.broadcast.teamResults),
|
|
study.members.myMember() && !!relay.data.tour.tier
|
|
? makeTab('stats', i18n.site.stats)
|
|
: ctrl.isEmbed &&
|
|
hl(
|
|
'a.relay-tour__tabs--open.text',
|
|
{
|
|
attrs: { href: relay.tourPath(), target: '_blank', 'data-icon': licon.Expand },
|
|
},
|
|
i18n.broadcast.openLichess,
|
|
),
|
|
]);
|
|
};
|
|
|
|
const roundStateIcon = (round: RelayRound, titleAsText: boolean) =>
|
|
round.ongoing
|
|
? hl(
|
|
'span.round-state.ongoing',
|
|
{ attrs: { ...dataIcon(licon.DiscBig), title: !titleAsText && i18n.broadcast.ongoing } },
|
|
titleAsText && i18n.broadcast.ongoing,
|
|
)
|
|
: round.finished &&
|
|
hl(
|
|
'span.round-state.finished',
|
|
{ attrs: { ...dataIcon(licon.Checkmark), title: !titleAsText && i18n.site.finished } },
|
|
titleAsText && i18n.site.finished,
|
|
);
|
|
|
|
const broadcastImageOrStream = (ctx: RelayViewContext) => {
|
|
const { relay, allowVideo } = ctx;
|
|
const d = relay.data,
|
|
embedVideo = (d.videoUrls || relay.isPinnedStreamOngoing()) && allowVideo;
|
|
|
|
return hl(
|
|
`div.relay-tour__header__image${embedVideo ? '.video' : ''}`,
|
|
embedVideo
|
|
? relay.videoPlayer?.render()
|
|
: d.tour.image
|
|
? hl('img', { attrs: { src: d.tour.image, alt: '' } })
|
|
: ctx.study.members.isOwner()
|
|
? hl(
|
|
'a.button.relay-tour__header__image-upload',
|
|
{ attrs: { href: `/broadcast/${d.tour.id}/edit` } },
|
|
i18n.broadcast.uploadImage,
|
|
)
|
|
: undefined,
|
|
);
|
|
};
|