diff --git a/app.py b/app.py index fbe78b6..a9094de 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.27.1" +BUILD_VERSION = "2026.08.27.2" # 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/manager.py b/ui_builder/manager.py index 2732703..6541df9 100644 --- a/ui_builder/manager.py +++ b/ui_builder/manager.py @@ -574,7 +574,7 @@ class UIBuilderManager: "description": str(selector.get("description") or "")[:300], "group_id": group_id if group_id in valid_group_ids else "", "button_id": button_id if button_id in valid_button_ids else "", - "style": "select" if str(selector.get("style") or "segments") == "select" else "segments", + "style": str(selector.get("style") or "segments") if str(selector.get("style") or "segments") in {"segments", "select", "periods"} else "segments", "options": options, "default_value": default_value, "sort_order": int(selector.get("sort_order") if str(selector.get("sort_order", "")).lstrip("-").isdigit() else selector_index * 10), diff --git a/ui_builder/static/app.js b/ui_builder/static/app.js index c8a8017..9af7586 100644 --- a/ui_builder/static/app.js +++ b/ui_builder/static/app.js @@ -2494,7 +2494,7 @@ function startCustomTooltips() { description: String(selector?.description || "").slice(0, 300), group_id: String(selector?.group_id || "").slice(0, 48), button_id: String(selector?.button_id || "").slice(0, 48), - style: String(selector?.style || "segments") === "select" ? "select" : "segments", + style: ["select", "periods"].includes(String(selector?.style || "segments")) ? String(selector.style) : "segments", options: fallbackOptions, default_value: defaultValue, sort_order: Number.isFinite(Number(selector?.sort_order)) ? Number(selector.sort_order) : index * 10, @@ -8824,7 +8824,7 @@ function openHockeyPrematchButtonsEditor() { let activeEditorSection = "buttons"; if (activeEditorGroupId === "__ungrouped") activeEditorGroupId = ""; if (activeEditorGroupId && !draftGroups.some((group) => group.id === activeEditorGroupId)) activeEditorGroupId = draftGroups[0]?.id || ""; - const collapsedEditorGroups = new Set(); + const collapsedEditorGroups = new Set(["", ...draftGroups.map((group) => String(group.id || ""))]); const groupButtons = (groupId) => draft .filter((button) => String(button.group_id || "") === String(groupId || "")) @@ -8875,9 +8875,12 @@ function openHockeyPrematchButtonsEditor() { - - - + + ${selector.style === "periods" + ? ` + ` + : ` + `} `; @@ -9112,6 +9115,9 @@ function openHockeyPrematchButtonsEditor() { activeEditorGroupId = String(selector.group_id || ""); collapsedEditorGroups.delete(activeEditorGroupId); renderEditor(); + } else if (field === "style") { + if (selector.style === "periods") selector.default_value = "1"; + renderEditor(); } }); }); @@ -9233,11 +9239,57 @@ function hockeyMatchRuntimeValues() { return values && typeof values === "object" ? values : {}; } +function quickPanelPeriodLabel(value) { + const key = String(value || "").toLowerCase(); + if (key === "1" || key === "2" || key === "3") return key; + if (key === "ot") return "ОТ"; + const match = key.match(/^ot(\d+)$/); + return match ? `ОТ${match[1]}` : String(value || ""); +} + +function quickPanelSelectorRuntimeOptions(selector) { + if (String(selector?.style || "") !== "periods") return Array.isArray(selector?.options) ? selector.options : []; + const control = getByPath(state.data, "hockey.game_control") || {}; + const tournament = getByPath(state.data, "hockey.selected_tournament") || {}; + const stage = String(control?.stage || tournament?.stage_key || "").toLowerCase(); + const raw = Array.isArray(control?.period_options) ? control.period_options : []; + let options = raw + .map((item) => ({ value: String(item?.value || ""), label: quickPanelPeriodLabel(item?.value) })) + .filter((item) => { + if (["1", "2", "3"].includes(item.value)) return true; + if (stage === "playoff") return /^ot\d+$/.test(item.value); + return item.value === "ot"; + }); + if (!options.length) { + options = [ + { value: "1", label: "1" }, + { value: "2", label: "2" }, + { value: "3", label: "3" }, + ]; + if (stage === "playoff") { + const current = String(control?.current_period || "").toLowerCase().match(/^ot(\d+)$/); + const count = Math.max(2, Number(current?.[1] || 0) + 1); + for (let number = 1; number <= count; number += 1) options.push({ value: `ot${number}`, label: `ОТ${number}` }); + } else { + options.push({ value: "ot", label: "ОТ" }); + } + } + const seen = new Set(); + return options.filter((item) => { + if (!item.value || seen.has(item.value)) return false; + seen.add(item.value); + return true; + }); +} + function quickPanelSelectorValue(selector) { + const options = quickPanelSelectorRuntimeOptions(selector); const values = hockeyMatchRuntimeValues(); const stored = String(values[`panel.${selector.id}.value`] ?? ""); - if (selector.options.some((option) => option.value === stored)) return stored; - return selector.default_value || selector.options[0]?.value || ""; + if (options.some((option) => option.value === stored)) return stored; + const configuredDefault = String(selector?.default_value || ""); + if (options.some((option) => option.value === configuredDefault)) return configuredDefault; + return options[0]?.value || ""; } async function hockeyRefreshQuickPanelMapping() { @@ -9355,16 +9407,18 @@ function hockeyScoreDockMarkup() { } function quickPanelSelectorMarkup(selector) { + const options = quickPanelSelectorRuntimeOptions(selector); const current = quickPanelSelectorValue(selector); const tooltip = selector.description || selector.label; if (selector.style === "select") { - return ``; + return ``; } - return `
${escapeHtml(selector.label)}
${selector.options.map((option) => ``).join("")}
`; + const modeClass = selector.style === "periods" ? " is-match-periods" : ""; + return `
${escapeHtml(selector.label)}
${options.map((option) => ``).join("")}
`; } async function hockeyApplyQuickSelector(selector, value, label = "") { - const option = selector.options.find((item) => item.value === value); + const option = quickPanelSelectorRuntimeOptions(selector).find((item) => item.value === value); const displayLabel = String(label || option?.label || value); await hockeySetMatchValues({ [`panel.${selector.id}.value`]: value, @@ -9379,7 +9433,7 @@ async function hockeyCommitQuickPanelButtonContext(button, attachedSelectors, { }; for (const selector of attachedSelectors) { const value = quickPanelSelectorValue(selector); - const option = selector.options.find((item) => item.value === value); + const option = quickPanelSelectorRuntimeOptions(selector).find((item) => item.value === value); patch[`panel.${selector.id}.value`] = value; patch[`panel.${selector.id}.label`] = option?.label || value; patch[`panel.last_button.${selector.id}`] = value; @@ -9440,7 +9494,7 @@ function renderHockeyQuickCommandDock() { const selector = selectors.find((item) => item.id === control.dataset.quickSelectorRuntime); if (!selector) return; const value = control.tagName === "SELECT" ? control.value : String(control.dataset.quickSelectorValue || ""); - const option = selector.options.find((item) => item.value === value); + const option = quickPanelSelectorRuntimeOptions(selector).find((item) => item.value === value); try { await hockeyApplyQuickSelector(selector, value, option?.label || value); toast(`Параметр «${selector.label}»: ${option?.label || value}`); diff --git a/ui_builder/static/styles.css b/ui_builder/static/styles.css index 92fa3ff..1d70676 100644 --- a/ui_builder/static/styles.css +++ b/ui_builder/static/styles.css @@ -7837,3 +7837,82 @@ body.hockey-navigation-open .runtime-viewport.has-hockey-pbp { gap: 14px !import .prematch-editor-selector-row .quick-selector-options-field, .prematch-editor-selector-row .quick-selector-default-field { grid-column: auto; } } + + +/* BUILD126 — collapsed lower-panel editor, pinned footer, automatic match-period selector. */ +.modal-card.modal-prematch-full, +.modal-card:has(.prematch-editor) { + display: flex; + flex-direction: column; +} +.modal-prematch-full .modal-head, +.modal-card:has(.prematch-editor) .modal-head { + flex: 0 0 auto; +} +.modal-prematch-full .prematch-editor, +.modal-card:has(.prematch-editor) .prematch-editor { + flex: 1 1 auto; + min-height: 0; + height: auto; + grid-template-rows: auto minmax(0, 1fr) auto; +} +.prematch-editor-section:not(.player-selection-editor) { + min-height: 0; + display: grid; + grid-template-rows: auto minmax(0, 1fr); + overflow: hidden; +} +.prematch-editor-section:not(.player-selection-editor) > .prematch-editor-list { + min-height: 0; + height: 100%; + max-height: none; + overflow-y: auto; + overflow-x: hidden; +} +.modal-prematch-full .prematch-editor-actions, +.modal-card:has(.prematch-editor) .prematch-editor-actions { + position: relative; + inset: auto; + flex: 0 0 auto; + margin: 0; + padding: 10px 0 0; + box-shadow: 0 -10px 22px rgba(5, 12, 20, .18); +} +.quick-selector-auto-note input:disabled { + color: #79a9d7; + border-color: rgba(77, 156, 255, .28); + background: rgba(77, 156, 255, .055); + opacity: 1; +} +.quick-command-selector.is-match-periods { + min-height: 38px; + padding: 3px 6px; + border-color: rgba(77, 156, 255, .34); + background: rgba(77, 156, 255, .045); +} +.quick-command-selector.is-match-periods > span { + color: #6f9bca; +} +.quick-command-selector.is-match-periods > div { + gap: 3px; +} +.quick-command-selector.is-match-periods button { + min-width: 31px; + height: 29px; + padding: 0 8px; + border-color: #324f6d; + border-radius: 7px; + color: #c7d8e9; + background: #0d1c2c; + font-size: 9px; +} +.quick-command-selector.is-match-periods button:hover { + border-color: #4d9cff; + color: #fff; +} +.quick-command-selector.is-match-periods button.active { + color: #eef7ff; + border-color: #4d9cff; + background: #245f9d; + box-shadow: 0 0 0 1px rgba(77, 156, 255, .12), inset 0 -2px 0 rgba(117, 190, 255, .45); +}