BUILD126 - легкие правки по интерфейсу

This commit is contained in:
2026-08-27 10:42:58 +03:00
parent 4218c4e243
commit 783d671303
4 changed files with 147 additions and 14 deletions

2
app.py
View File

@@ -29,7 +29,7 @@ from ui_builder import install_ui_builder
from khl_site.khl_data_center import APP as khl_site_app from khl_site.khl_data_center import APP as khl_site_app
BASE_DIR = Path(__file__).resolve().parent 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.26.1"
# compatibility: BUILD_VERSION = "2026.08.25.1" # compatibility: BUILD_VERSION = "2026.08.25.1"
# compatibility: BUILD_VERSION = "2026.08.24.15" # compatibility: BUILD_VERSION = "2026.08.24.15"

View File

@@ -574,7 +574,7 @@ class UIBuilderManager:
"description": str(selector.get("description") or "")[:300], "description": str(selector.get("description") or "")[:300],
"group_id": group_id if group_id in valid_group_ids else "", "group_id": group_id if group_id in valid_group_ids else "",
"button_id": button_id if button_id in valid_button_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, "options": options,
"default_value": default_value, "default_value": default_value,
"sort_order": int(selector.get("sort_order") if str(selector.get("sort_order", "")).lstrip("-").isdigit() else selector_index * 10), "sort_order": int(selector.get("sort_order") if str(selector.get("sort_order", "")).lstrip("-").isdigit() else selector_index * 10),

View File

@@ -2494,7 +2494,7 @@ function startCustomTooltips() {
description: String(selector?.description || "").slice(0, 300), description: String(selector?.description || "").slice(0, 300),
group_id: String(selector?.group_id || "").slice(0, 48), group_id: String(selector?.group_id || "").slice(0, 48),
button_id: String(selector?.button_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, options: fallbackOptions,
default_value: defaultValue, default_value: defaultValue,
sort_order: Number.isFinite(Number(selector?.sort_order)) ? Number(selector.sort_order) : index * 10, sort_order: Number.isFinite(Number(selector?.sort_order)) ? Number(selector.sort_order) : index * 10,
@@ -8824,7 +8824,7 @@ function openHockeyPrematchButtonsEditor() {
let activeEditorSection = "buttons"; let activeEditorSection = "buttons";
if (activeEditorGroupId === "__ungrouped") activeEditorGroupId = ""; if (activeEditorGroupId === "__ungrouped") activeEditorGroupId = "";
if (activeEditorGroupId && !draftGroups.some((group) => group.id === activeEditorGroupId)) activeEditorGroupId = draftGroups[0]?.id || ""; 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 const groupButtons = (groupId) => draft
.filter((button) => String(button.group_id || "") === String(groupId || "")) .filter((button) => String(button.group_id || "") === String(groupId || ""))
@@ -8875,9 +8875,12 @@ function openHockeyPrematchButtonsEditor() {
<label>ID для SQL<input type="text" maxlength="48" data-quick-selector-field="id" data-quick-selector-id="${escapeHtml(selector.id)}" value="${escapeHtml(selector.id)}"></label> <label>ID для SQL<input type="text" maxlength="48" data-quick-selector-field="id" data-quick-selector-id="${escapeHtml(selector.id)}" value="${escapeHtml(selector.id)}"></label>
<label>Вкладка<select data-quick-selector-field="group_id" data-quick-selector-id="${escapeHtml(selector.id)}">${hockeyPrematchGroupOptions(draftGroups, selector.group_id)}</select></label> <label>Вкладка<select data-quick-selector-field="group_id" data-quick-selector-id="${escapeHtml(selector.id)}">${hockeyPrematchGroupOptions(draftGroups, selector.group_id)}</select></label>
<label>Рядом с кнопкой<select data-quick-selector-field="button_id" data-quick-selector-id="${escapeHtml(selector.id)}">${hockeyQuickPanelButtonOptions(draft, selector.button_id)}</select></label> <label>Рядом с кнопкой<select data-quick-selector-field="button_id" data-quick-selector-id="${escapeHtml(selector.id)}">${hockeyQuickPanelButtonOptions(draft, selector.button_id)}</select></label>
<label>Вид<select data-quick-selector-field="style" data-quick-selector-id="${escapeHtml(selector.id)}">${triggerSelectOptions([["segments","Маленькие кнопки"],["select","Выпадающий список"]], selector.style)}</select></label> <label>Вид<select data-quick-selector-field="style" data-quick-selector-id="${escapeHtml(selector.id)}">${triggerSelectOptions([["segments","Маленькие кнопки"],["select","Выпадающий список"],["periods","Периоды матча · авто"]], selector.style)}</select></label>
<label class="quick-selector-options-field">Варианты <small>значение=подпись; разделитель — точка с запятой</small><input type="text" data-quick-selector-options="${escapeHtml(selector.id)}" value="${escapeHtml(quickPanelSelectorOptionsText(selector))}" placeholder="all=МАТЧ; 1=1; 2=2; 3=3"></label> ${selector.style === "periods"
<label class="quick-selector-default-field">По умолчанию<input type="text" maxlength="64" data-quick-selector-field="default_value" data-quick-selector-id="${escapeHtml(selector.id)}" value="${escapeHtml(selector.default_value)}"></label> ? `<label class="quick-selector-options-field quick-selector-auto-note">Периоды <small>Автоматически из режима текущего матча</small><input type="text" value="1 · 2 · 3 · ОТ / ОТ1, ОТ2…" disabled></label>
<label class="quick-selector-default-field">По умолчанию<input type="text" value="1" disabled></label>`
: `<label class="quick-selector-options-field">Варианты <small>значение=подпись; разделитель — точка с запятой</small><input type="text" data-quick-selector-options="${escapeHtml(selector.id)}" value="${escapeHtml(quickPanelSelectorOptionsText(selector))}" placeholder="all=МАТЧ; 1=1; 2=2; 3=3"></label>
<label class="quick-selector-default-field">По умолчанию<input type="text" maxlength="64" data-quick-selector-field="default_value" data-quick-selector-id="${escapeHtml(selector.id)}" value="${escapeHtml(selector.default_value)}"></label>`}
<button type="button" class="icon-btn danger" data-quick-selector-delete="${escapeHtml(selector.id)}" title="Удалить переключатель">×</button> <button type="button" class="icon-btn danger" data-quick-selector-delete="${escapeHtml(selector.id)}" title="Удалить переключатель">×</button>
</article>`; </article>`;
@@ -9112,6 +9115,9 @@ function openHockeyPrematchButtonsEditor() {
activeEditorGroupId = String(selector.group_id || ""); activeEditorGroupId = String(selector.group_id || "");
collapsedEditorGroups.delete(activeEditorGroupId); collapsedEditorGroups.delete(activeEditorGroupId);
renderEditor(); 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 : {}; 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) { function quickPanelSelectorValue(selector) {
const options = quickPanelSelectorRuntimeOptions(selector);
const values = hockeyMatchRuntimeValues(); const values = hockeyMatchRuntimeValues();
const stored = String(values[`panel.${selector.id}.value`] ?? ""); const stored = String(values[`panel.${selector.id}.value`] ?? "");
if (selector.options.some((option) => option.value === stored)) return stored; if (options.some((option) => option.value === stored)) return stored;
return selector.default_value || selector.options[0]?.value || ""; const configuredDefault = String(selector?.default_value || "");
if (options.some((option) => option.value === configuredDefault)) return configuredDefault;
return options[0]?.value || "";
} }
async function hockeyRefreshQuickPanelMapping() { async function hockeyRefreshQuickPanelMapping() {
@@ -9355,16 +9407,18 @@ function hockeyScoreDockMarkup() {
} }
function quickPanelSelectorMarkup(selector) { function quickPanelSelectorMarkup(selector) {
const options = quickPanelSelectorRuntimeOptions(selector);
const current = quickPanelSelectorValue(selector); const current = quickPanelSelectorValue(selector);
const tooltip = selector.description || selector.label; const tooltip = selector.description || selector.label;
if (selector.style === "select") { if (selector.style === "select") {
return `<label class="quick-command-selector is-select" data-tooltip="${escapeHtml(tooltip)}"><span>${escapeHtml(selector.label)}</span><select data-quick-selector-runtime="${escapeHtml(selector.id)}">${selector.options.map((option) => `<option value="${escapeHtml(option.value)}" ${option.value === current ? "selected" : ""}>${escapeHtml(option.label)}</option>`).join("")}</select></label>`; return `<label class="quick-command-selector is-select" data-tooltip="${escapeHtml(tooltip)}"><span>${escapeHtml(selector.label)}</span><select data-quick-selector-runtime="${escapeHtml(selector.id)}">${options.map((option) => `<option value="${escapeHtml(option.value)}" ${option.value === current ? "selected" : ""}>${escapeHtml(option.label)}</option>`).join("")}</select></label>`;
} }
return `<div class="quick-command-selector is-segments" data-tooltip="${escapeHtml(tooltip)}"><span>${escapeHtml(selector.label)}</span><div>${selector.options.map((option) => `<button type="button" data-quick-selector-runtime="${escapeHtml(selector.id)}" data-quick-selector-value="${escapeHtml(option.value)}" class="${option.value === current ? "active" : ""}">${escapeHtml(option.label)}</button>`).join("")}</div></div>`; const modeClass = selector.style === "periods" ? " is-match-periods" : "";
return `<div class="quick-command-selector is-segments${modeClass}" data-tooltip="${escapeHtml(tooltip)}"><span>${escapeHtml(selector.label)}</span><div>${options.map((option) => `<button type="button" data-quick-selector-runtime="${escapeHtml(selector.id)}" data-quick-selector-value="${escapeHtml(option.value)}" class="${option.value === current ? "active" : ""}">${escapeHtml(option.label)}</button>`).join("")}</div></div>`;
} }
async function hockeyApplyQuickSelector(selector, value, label = "") { 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); const displayLabel = String(label || option?.label || value);
await hockeySetMatchValues({ await hockeySetMatchValues({
[`panel.${selector.id}.value`]: value, [`panel.${selector.id}.value`]: value,
@@ -9379,7 +9433,7 @@ async function hockeyCommitQuickPanelButtonContext(button, attachedSelectors, {
}; };
for (const selector of attachedSelectors) { for (const selector of attachedSelectors) {
const value = quickPanelSelectorValue(selector); 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}.value`] = value;
patch[`panel.${selector.id}.label`] = option?.label || value; patch[`panel.${selector.id}.label`] = option?.label || value;
patch[`panel.last_button.${selector.id}`] = value; patch[`panel.last_button.${selector.id}`] = value;
@@ -9440,7 +9494,7 @@ function renderHockeyQuickCommandDock() {
const selector = selectors.find((item) => item.id === control.dataset.quickSelectorRuntime); const selector = selectors.find((item) => item.id === control.dataset.quickSelectorRuntime);
if (!selector) return; if (!selector) return;
const value = control.tagName === "SELECT" ? control.value : String(control.dataset.quickSelectorValue || ""); 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 { try {
await hockeyApplyQuickSelector(selector, value, option?.label || value); await hockeyApplyQuickSelector(selector, value, option?.label || value);
toast(`Параметр «${selector.label}»: ${option?.label || value}`); toast(`Параметр «${selector.label}»: ${option?.label || value}`);

View File

@@ -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-options-field,
.prematch-editor-selector-row .quick-selector-default-field { grid-column: auto; } .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);
}