175 build - идентификатор для кнопки

This commit is contained in:
2026-09-29 17:14:51 +03:00
parent f200066f7b
commit ec4e22a1a7
6 changed files with 190 additions and 21 deletions

View File

@@ -9412,6 +9412,7 @@ async function openHockeyPrematchButtonsEditor({ section = "buttons", panelId =
<label>Иконка<input type="text" maxlength="32" data-panel-title-button-field="icon" data-panel-title-button-id="${escapeHtml(button.id)}" value="${escapeHtml(button.icon)}" placeholder="▶ ★ 📊 👤"></label>
<label class="shortcut-inline-check"><input type="checkbox" data-panel-title-button-field="icon_only" data-panel-title-button-id="${escapeHtml(button.id)}" ${button.icon_only ? "checked" : ""}> Только иконка</label>
<label class="panel-title-button-description">Подсказка<input type="text" maxlength="300" data-panel-title-button-field="description" data-panel-title-button-id="${escapeHtml(button.id)}" value="${escapeHtml(button.description)}" placeholder="Например: крупная статистика игрока"></label>
<label class="panel-title-button-text">Надпись в титре<input type="text" maxlength="512" data-panel-title-button-field="title_text" data-panel-title-button-id="${escapeHtml(button.id)}" value="${escapeHtml(button.title_text || "")}" placeholder="Например: СТАТИСТИКА ЗА СЕЗОН"></label>
<div class="player-selection-editor-order"><button type="button" class="mini-btn" data-panel-title-button-up="${escapeHtml(button.id)}" ${index <= 0 ? "disabled" : ""}>↑</button><button type="button" class="mini-btn" data-panel-title-button-down="${escapeHtml(button.id)}" ${index >= draftPanelTitleButtons.length - 1 ? "disabled" : ""}>↓</button></div>
<button type="button" class="icon-btn danger" data-panel-title-button-delete="${escapeHtml(button.id)}" title="Удалить кнопку">×</button>
</article>`;
@@ -9477,7 +9478,7 @@ async function openHockeyPrematchButtonsEditor({ section = "buttons", panelId =
</section>
` : `
<section class="panel-title-button-editor prematch-editor-section" data-prematch-editor-section="panel_titles">
<header><div><span>PLAYER BLOCK → VMIX PREVIEW</span><strong>Кнопки титров под блоками игроков</strong><small>Один блок может иметь несколько кнопок. Перед Preview обновляется Mapping выбранного блока, затем выбранный vMix Input отправляется в Preview.</small></div><div class="prematch-editor-head-actions"><button type="button" class="btn" data-panel-title-refresh-inputs>↻ Inputs</button><button type="button" class="btn btn-accent" data-panel-title-button-add>+ Кнопка титра</button></div></header>
<header><div><span>PLAYER BLOCK → VMIX PREVIEW</span><strong>Кнопки титров под блоками игроков</strong><small>Нескольким кнопкам можно назначить один Input. В Mapping свяжите текстовое поле с title_button.text; ID нажатой кнопки доступен как title_button.id (в SQL — :title_button_id). Данные обновляются до Preview.</small></div><div class="prematch-editor-head-actions"><button type="button" class="btn" data-panel-title-refresh-inputs>↻ Inputs</button><button type="button" class="btn btn-accent" data-panel-title-button-add>+ Кнопка титра</button></div></header>
<div class="panel-title-button-editor-list">${draftPanelTitleButtons.length ? draftPanelTitleButtons.map(renderPanelTitleButtonRow).join("") : `<div class="prematch-group-empty">Кнопок титров пока нет. Создайте блок игроков, затем назначьте ему один или несколько vMix Input.</div>`}</div>
</section>
`}
@@ -10402,6 +10403,7 @@ function normalisePlayerPanelTitleButtons(source) {
id,
panel_id: panelId,
label,
title_text: String(button?.title_text || "").trim().slice(0, 512),
description: String(button?.description || "").trim().slice(0, 300),
icon: String(button?.icon || "▶").trim().slice(0, 32) || "▶",
icon_only: button?.icon_only === true,
@@ -10450,8 +10452,23 @@ async function hockeySavePlayerPanelTitleButtons(buttons) {
}
}
function hockeyRefreshPlayerPanelTitleButtonState() {
const values = hockeyMatchRuntimeValues();
const panels = hockeyPlayerSelectionPanels();
document.querySelectorAll("[data-player-panel-title-button]").forEach((node) => {
const panel = panels.find((item) => item.id === node.dataset.playerPanelId);
const filled = panel && Array.from({ length: panel.slots }, (_, index) => hockeyPlayerSelectionSlot(panel, index + 1))
.some((slot) => slot.id || slot.dbId);
node.disabled = !hockeyCanControl() || state.playerPanelTitleBusy.size > 0 || !filled;
const selected = values["title_button.id"] === node.dataset.playerPanelTitleButton
&& values["title_button.panel_id"] === node.dataset.playerPanelId;
node.classList.toggle("is-selected", selected);
node.setAttribute("aria-pressed", String(selected));
});
}
async function hockeyPreviewPlayerPanelTitle(panel, button) {
if (!panel || !button || !hockeyCanControl() || state.playerPanelTitleBusy.has(panel.id)) return false;
if (!panel || !button || !hockeyCanControl() || state.playerPanelTitleBusy.size) return false;
if (!button.input) {
toast(`Для «${button.label}» не назначен vMix Input`, true);
return false;
@@ -10462,22 +10479,24 @@ async function hockeyPreviewPlayerPanelTitle(panel, button) {
return false;
}
state.playerPanelTitleBusy.add(panel.id);
const syncBusy = () => {
document.querySelectorAll("[data-player-panel-title-button]").forEach((node) => {
if (node.dataset.playerPanelId === panel.id) node.disabled = state.playerPanelTitleBusy.has(panel.id) || !slots.some((slot) => slot.id || slot.dbId);
});
};
syncBusy();
hockeyRefreshPlayerPanelTitleButtonState();
const gameId = hockeyTimerSelectedGameId();
try {
// Publish which panel button is being used before PreviewInput. SQL/Mapping
// can therefore distinguish several graphics that use the same selected
// player block while the player identifiers themselves stay unchanged.
await hockeySetMatchValues({
const saved = await hockeySetMatchValues({
"title_button.id": button.id,
"title_button.label": button.label,
"title_button.text": button.title_text || "",
"title_button.panel_id": panel.id,
"title_button.input": button.input,
[`player_select.${panel.id}._preview_button_id`]: button.id,
[`player_select.${panel.id}._preview_button_label`]: button.label,
[`player_select.${panel.id}._preview_button_text`]: button.title_text || "",
[`player_select.${panel.id}._preview_input`]: button.input,
}, { refreshMapping: false });
if (!saved || gameId !== hockeyTimerSelectedGameId() || !hockeyCanControl()) throw new Error("Выбранный матч или права управления изменились");
if (!(await hockeyRefreshQuickPanelMapping())) throw new Error("Не удалось обновить данные титра в vMix");
if (gameId !== hockeyTimerSelectedGameId() || !hockeyCanControl()) throw new Error("Выбранный матч изменился");
const result = await sendRuntimeVmixSequence([{ Function: "PreviewInput", Input: button.input }]);
@@ -10489,7 +10508,7 @@ async function hockeyPreviewPlayerPanelTitle(panel, button) {
return false;
} finally {
state.playerPanelTitleBusy.delete(panel.id);
syncBusy();
hockeyRefreshPlayerPanelTitleButtonState();
}
}
@@ -10822,6 +10841,7 @@ function renderHockeyPlayerSelectionWindows() {
}
}));
}
hockeyRefreshPlayerPanelTitleButtonState();
setTimeout(() => hockeyEnsurePlayerSelectionRuntimeValues().catch(() => {}), 0);
return true;
}

View File

@@ -7574,6 +7574,12 @@ body.hockey-navigation-open .runtime-viewport.has-hockey-pbp { gap: 14px !import
border-color:#54e5c4;
background:#48dfbd;
}
.hockey-player-select-title-preview.is-selected {
border-color:#54e5c4;
box-shadow:inset 0 0 0 1px #54e5c4;
color:#b6ffed;
}
.hockey-player-select-title-preview.is-selected:hover:not(:disabled) { color:#061713; }
.hockey-player-select-title-preview:disabled { opacity:.42; cursor:not-allowed; }
.hockey-player-select-title-preview.is-sending { opacity:.7; cursor:wait; }
.hockey-player-select-footer-tools {
@@ -8442,6 +8448,7 @@ body.hockey-navigation-open .runtime-viewport.has-hockey-pbp { gap: 14px !import
.panel-title-button-editor-row > label:nth-of-type(6) { grid-column:5; grid-row:2; }
.panel-title-button-editor-row > label:nth-of-type(7) { grid-column:6 / 9; grid-row:2; }
.panel-title-button-editor-row > .panel-title-button-description { grid-column:3 / 9; grid-row:3; }
.panel-title-button-editor-row > .panel-title-button-text { grid-column:3 / 9; grid-row:4; }
/* BUILD166: the account's dock size scales every control, including the score. */
.runtime-button-dock {