diff --git a/app.py b/app.py index be3d1f4..2f69d58 100644 --- a/app.py +++ b/app.py @@ -29,7 +29,7 @@ from ui_builder import install_ui_builder from khl_site.khl_data_center import APP as khl_site_app BASE_DIR = Path(__file__).resolve().parent -BUILD_VERSION = "2026.08.26.7" +BUILD_VERSION = "2026.08.26.8" # compatibility: BUILD_VERSION = "2026.08.26.1" # compatibility: BUILD_VERSION = "2026.08.25.1" # compatibility: BUILD_VERSION = "2026.08.24.15" diff --git a/ui_builder/static/app.js b/ui_builder/static/app.js index d073430..8aba320 100644 --- a/ui_builder/static/app.js +++ b/ui_builder/static/app.js @@ -2538,14 +2538,13 @@ function startCustomTooltips() { prevent_default: sequence.prevent_default !== false, allow_in_inputs: Boolean(sequence.allow_in_inputs), toggle_all_overlays_on_repeat: Boolean(sequence.toggle_all_overlays_on_repeat), - // BUILD117: keep the legacy toggle flag for config compatibility, but let the - // operator choose exactly which vMix overlay channels are taken OUT on repeat. - // Existing scoreboard configs had no list, so the safe default remains 1/2/3. - // BUILD118 exposes vMix 29 Overlay channels 1..8 while leaving 4..8 untouched by default. + // BUILD121: overlay repeat-out targets are optional. Generic shortcuts do not + // necessarily control graphics, so an empty selection is valid and is the default. + // Existing saved selections are preserved exactly. The dedicated scoreboard preset + // still explicitly creates O1/O2/O3 because that preset is a graphics shortcut. repeat_overlay_outs: (() => { - const raw = Array.isArray(sequence.repeat_overlay_outs) ? sequence.repeat_overlay_outs : [1, 2, 3]; - const normalized = [...new Set(raw.map((value) => Number(value)).filter((value) => Number.isInteger(value) && value >= 1 && value <= 8))]; - return normalized.length ? normalized : [1, 2, 3]; + const raw = Array.isArray(sequence.repeat_overlay_outs) ? sequence.repeat_overlay_outs : []; + return [...new Set(raw.map((value) => Number(value)).filter((value) => Number.isInteger(value) && value >= 1 && value <= 8))]; })(), sync_hockey_team_states: sequence.sync_hockey_team_states === undefined ? Boolean(sequence.toggle_all_overlays_on_repeat) @@ -5564,22 +5563,25 @@ function startCustomTooltips() { // BUILD118: repeat OUT is configurable across vMix Overlay channels 1..8. Do not use // OverlayInputAllOff: explicit OverlayInputNOut preserves each configured // transition-out and avoids touching unrelated channels that are not selected. - const repeatOverlayOuts = (Array.isArray(sequence.repeat_overlay_outs) ? sequence.repeat_overlay_outs : [1, 2, 3]) + const selectedOverlayOuts = (Array.isArray(sequence.repeat_overlay_outs) ? sequence.repeat_overlay_outs : []) .map((value) => Number(value)) .filter((value, index, values) => Number.isInteger(value) && value >= 1 && value <= 8 && values.indexOf(value) === index); - const selectedOverlayOuts = repeatOverlayOuts.length ? repeatOverlayOuts : [1, 2, 3]; - await sendRuntimeVmixSequence( - selectedOverlayOuts.map((number) => ({ Function: `OverlayInput${number}Out` })), - execution, - ); - if (sequence.sync_hockey_team_states) hockeyClearTeamStateOverlayTracking(); - state.shortcutSequenceOverlayState.set(sequence.id, false); - state.quickPanelOnAirSequences.delete(String(sequence.id || "")); - refreshQuickPanelOnAirClasses(); - window.dispatchEvent(new CustomEvent("ui-builder:shortcut-sequence-finish", { detail: { sequence: clone(sequence), meta: { ...meta, overlay_group_action: "off" } } })); - if (meta.source === "quick-panel-button") toast(`Выполнено: ${sequence.name} · кнопка «${meta.button_label || sequence.name}»`); - else if (String(meta.source || "").startsWith("keyboard")) toast(`Шорткат выполнен: ${sequence.name}`); - return true; + // Empty is intentionally valid: a shortcut may have no graphics/Overlay action. + // In that case do not swallow the repeat press; execute its normal steps below. + if (selectedOverlayOuts.length) { + await sendRuntimeVmixSequence( + selectedOverlayOuts.map((number) => ({ Function: `OverlayInput${number}Out` })), + execution, + ); + if (sequence.sync_hockey_team_states) hockeyClearTeamStateOverlayTracking(); + state.shortcutSequenceOverlayState.set(sequence.id, false); + state.quickPanelOnAirSequences.delete(String(sequence.id || "")); + refreshQuickPanelOnAirClasses(); + window.dispatchEvent(new CustomEvent("ui-builder:shortcut-sequence-finish", { detail: { sequence: clone(sequence), meta: { ...meta, overlay_group_action: "off" } } })); + if (meta.source === "quick-panel-button") toast(`Выполнено: ${sequence.name} · кнопка «${meta.button_label || sequence.name}»`); + else if (String(meta.source || "").startsWith("keyboard")) toast(`Шорткат выполнен: ${sequence.name}`); + return true; + } } if (sequence.is_scoreboard_sequence) { await syncConfiguredScoreboardCountdownsToRuntime(); @@ -14391,7 +14393,7 @@ function renderHockeyPenaltyDashboard(node, component, runtime) { снять: - ${[1, 2, 3, 4, 5, 6, 7, 8].map((number) => ``).join("")} + ${[1, 2, 3, 4, 5, 6, 7, 8].map((number) => ``).join("")} @@ -14430,11 +14432,8 @@ function renderHockeyPenaltyDashboard(node, component, runtime) { const selected = [...card.querySelectorAll("[data-sequence-overlay-out]:checked")] .map((item) => Number(item.dataset.sequenceOverlayOut)) .filter((value) => Number.isInteger(value) && value >= 1 && value <= 8); - if (!selected.length) { - event.target.checked = true; - toast("Для повторного снятия выберите хотя бы один Overlay"); - return; - } + // BUILD121: zero selected overlays is valid. Some shortcuts do not put any + // graphics on air, so there is nothing that must be taken OUT on repeat. sequence.repeat_overlay_outs = selected; })); card.querySelector("[data-sequence-scoreboard]").addEventListener("change", (event) => {