diff --git a/app.py b/app.py index b175646..970dc1e 100644 --- a/app.py +++ b/app.py @@ -35,7 +35,7 @@ from khl_site.khl_data_center import APP as khl_site_app, configure_storage, syn from hockey_data.khl_career import KhlCareerService BASE_DIR = Path(__file__).resolve().parent -BUILD_VERSION = "2026.09.29.4" +BUILD_VERSION = "2026.09.29.5" # compatibility: BUILD_VERSION = "2026.09.24.2" # compatibility: BUILD_VERSION = "2026.09.23.8" # compatibility: BUILD_VERSION = "2026.09.23.7" diff --git a/ui_builder/static/app.js b/ui_builder/static/app.js index c43875d..aa2bc5f 100644 --- a/ui_builder/static/app.js +++ b/ui_builder/static/app.js @@ -5430,7 +5430,7 @@ function startCustomTooltips() { return `${String(step?.id || "")}:${side}:${String(target?.id || "")}`; } - async function rebalanceVmixPenaltyTargets({ force = false, hideUnused = true, preservePausedCountdown = false, editedEventKey = "", scoreboardActive = null, forceOverlay = false } = {}) { + async function rebalanceVmixPenaltyTargets({ force = false, hideUnused = true, preservePausedCountdown = false, editedEventKey = "", scoreboardActive = null, forceOverlay = false, forceHideEmpty = false } = {}) { if (!hockeyCanControl()) return 0; const scoreboardLive = scoreboardActive === null ? hockeyScoreboardIsLive() : Boolean(scoreboardActive); const countdownCommands = []; @@ -5440,6 +5440,29 @@ function startCustomTooltips() { const runCommands = []; const inCommands = []; const assignedMirrorKeys = new Set(); + // Explicit deletion of the final card must clear configured plates even + // after a reload, when this browser has no remembered target assignments. + const clearEmptyPlates = forceHideEmpty && currentHockeyPenaltyEntries().length === 0; + const clearUnusedTarget = (target, previous) => { + const input = String(previous?.input || target.input || ""); + const selectedName = String(previous?.selectedName || target.selected_name || ""); + const rawOverlay = String(previous?.overlay || target.overlay || "2"); + const overlay = /^[1-8]$/.test(rawOverlay) ? rawOverlay : "2"; + if (input && selectedName && (previous?.mode === "countdown" || clearEmptyPlates)) { + if (!stopCommands.some((command) => command.Input === input && command.SelectedName === selectedName)) { + stopCommands.push({ Function: "StopCountdown", Input: input, SelectedName: selectedName }); + } + } + if (!hideUnused || !input || (!clearEmptyPlates && (!previous || target.auto_hide_on_finish === false))) return; + // Overlay OUT operates on a layer. Do not remove an unrelated title that + // has since replaced this penalty Input on that layer. + const current = state.vmixOverlayRuntime.get(overlay); + if (current && runtimeOverlayInputRefs(current).size && !runtimeOverlayMatchesInput(current, input)) return; + const functionName = `OverlayInput${overlay}Out`; + if (!outCommands.some((command) => command.Function === functionName)) { + outCommands.push({ Function: functionName, Input: input }); + } + }; for (const stepId of Array.from(state.activeHockeyVmixTimerSteps)) { const step = hockeyTimerSyncStepById(stepId); if (!step || step.enabled === false || !step.sync_vmix_penalties) { @@ -5522,26 +5545,14 @@ function startCustomTooltips() { } } } else { - if (previous?.mode === "countdown" && previous.input) { - stopCommands.push({ Function: "StopCountdown", Input: previous.input, SelectedName: previous.selectedName || target.selected_name }); - } - if (previous && hideUnused && target.auto_hide_on_finish !== false && target.input) { - const overlay = ["1", "2", "3", "4", "5", "6", "7", "8"].includes(String(target.overlay || "")) ? String(target.overlay) : "2"; - outCommands.push({ Function: `OverlayInput${overlay}Out`, Input: target.input }); - } + clearUnusedTarget(target, previous); state.vmixPenaltyTargetAssignments.delete(assignmentKey); } }); inactiveTargets.forEach((target) => { const assignmentKey = penaltyTargetAssignmentKey(step, side, target); const previous = state.vmixPenaltyTargetAssignments.get(assignmentKey); - if (previous?.mode === "countdown" && previous.input) { - stopCommands.push({ Function: "StopCountdown", Input: previous.input, SelectedName: previous.selectedName || target.selected_name }); - } - if (previous && hideUnused && target.auto_hide_on_finish !== false && target.input) { - const overlay = ["1", "2", "3", "4", "5", "6", "7", "8"].includes(String(target.overlay || "")) ? String(target.overlay) : "2"; - outCommands.push({ Function: `OverlayInput${overlay}Out`, Input: target.input }); - } + clearUnusedTarget(target, previous); state.vmixPenaltyTargetAssignments.delete(assignmentKey); }); } @@ -8124,11 +8135,12 @@ function openTimerQuickEditor(focusActionId = "") { }); state.vmixPenaltyMirrors.delete(penaltyMirrorKey(component, event)); hockeySyncPenaltySideMappingContext({ force: true }).catch(() => {}); - if (!board.penalties.some((item) => !item.finished && hockeyEventReady(item))) { + if (!currentHockeyPenaltyEntries().length) { resetPenaltyAdvantageCycle(); hockeyScheduleTimerSave(true); } - rebalanceVmixPenaltyTargets({ force: true, hideUnused: true }).catch((error) => console.error("Penalty target remove rebalance error", error)); + activateConfiguredPenaltyCountdowns(); + rebalanceVmixPenaltyTargets({ force: true, hideUnused: true, forceHideEmpty: true }).catch((error) => console.error("Penalty target remove rebalance error", error)); return true; }