diff --git a/app.py b/app.py index b3a3364..c508112 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.09.15.9" +BUILD_VERSION = "2026.09.15.10" # compatibility: BUILD_VERSION = "2026.08.26.1" # compatibility: BUILD_VERSION = "2026.08.25.1" # compatibility: BUILD_VERSION = "2026.08.24.15" diff --git a/hockey_data/static/admin-directories.css b/hockey_data/static/admin-directories.css index 381770c..56b0460 100644 --- a/hockey_data/static/admin-directories.css +++ b/hockey_data/static/admin-directories.css @@ -2474,3 +2474,121 @@ body.hockey-admin-settings-open { overflow: hidden; } .hockey-map-table-column-nav>small{font-size:0} .hockey-map-table-column-nav>small::after{content:"⇆";font-size:11px} } + + +/* BUILD142: row + field browser for manual SQL Mapping. No horizontal spreadsheet scrolling. */ +.hockey-map-table-picker-v2{max-height:56%!important;min-height:190px!important} +.hockey-map-table-browser{ + height:min(310px,31dvh); + min-height:160px; + display:grid; + grid-template-columns:minmax(118px,.42fr) minmax(0,1.58fr); + gap:8px; + overflow:hidden; +} +.hockey-map-table-rows,.hockey-map-table-fields{ + min-height:0; + border:1px solid #263d51; + border-radius:9px; + background:#0a131c; + overflow:hidden; +} +.hockey-map-table-rows{display:grid;grid-template-rows:auto minmax(0,1fr)} +.hockey-map-table-rows>header,.hockey-map-table-fields>header{ + min-height:31px; + box-sizing:border-box; + display:flex; + align-items:center; + justify-content:space-between; + gap:8px; + padding:6px 8px; + border-bottom:1px solid #263d51; + background:#132235; +} +.hockey-map-table-rows>header span,.hockey-map-table-fields>header span{ + color:#69c9ef; + font-size:8px; + font-weight:950; + letter-spacing:.09em; +} +.hockey-map-table-rows>header b,.hockey-map-table-fields>header>b{ + flex:0 0 auto; + min-width:22px; + padding:3px 5px; + border-radius:999px; + background:#173148; + color:#8ed5ef; + font-size:8px; + text-align:center; +} +.hockey-map-table-rows>div{ + min-height:0; + overflow-y:auto; + overflow-x:hidden; + scrollbar-width:thin; + scrollbar-color:#3d6486 #09131d; +} +.hockey-map-table-row{ + width:100%; + min-width:0; + display:grid; + grid-template-columns:30px minmax(0,1fr); + gap:1px 6px; + padding:7px 8px; + border:0; + border-bottom:1px solid #213448; + background:#0f1a26; + color:#dce7f2; + text-align:left; + cursor:pointer; +} +.hockey-map-table-row:hover{background:#132638} +.hockey-map-table-row.is-active{background:#15322f;box-shadow:inset 3px 0 0 #48dfbd} +.hockey-map-table-row>b{grid-row:1/3;color:#69c9ef;font-size:10px;align-self:center} +.hockey-map-table-row>span{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:#dce7f2;font-size:9px;font-weight:800} +.hockey-map-table-row>small{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:#7189a0;font-size:8px} +.hockey-map-table-fields{display:grid;grid-template-rows:auto minmax(0,1fr) auto} +.hockey-map-table-fields>header>div{min-width:0;display:grid;gap:2px} +.hockey-map-table-fields>header strong{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:#dceaf6;font-size:10px} +.hockey-map-table-fields-list{ + min-height:0; + overflow-y:auto; + overflow-x:hidden; + display:grid; + grid-template-columns:repeat(2,minmax(0,1fr)); + align-content:start; + gap:6px; + padding:7px; + scrollbar-width:thin; + scrollbar-color:#3d6486 #09131d; +} +.hockey-map-table-field{ + min-width:0; + display:grid; + gap:4px; + padding:7px 8px; + border:1px solid #2d455d; + border-radius:8px; + background:#101c29; + color:#dce7f2; + text-align:left; + cursor:pointer; +} +.hockey-map-table-field:hover{border-color:#4f7898;background:#132536} +.hockey-map-table-field.is-selected{border-color:#48dfbd;background:#15322f;box-shadow:inset 0 0 0 1px rgba(72,223,189,.18)} +.hockey-map-table-field:disabled{opacity:.34;cursor:not-allowed} +.hockey-map-table-field-head{min-width:0;display:flex;align-items:center;gap:5px} +.hockey-map-table-field-head strong{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:#9fc4df;font-size:9px;text-transform:none} +.hockey-map-table-field em{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:#f2f7fb;font-size:11px;font-style:normal;font-weight:800} +.hockey-map-table-field code{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:#68839b;font:8px Consolas,monospace} +.hockey-map-table-fields-hint{padding:6px 8px;border-top:1px solid #22364a;color:#71889e;font-size:8px} +.hockey-map-table-no-rows{padding:12px;color:#71889e;font-size:9px;text-align:center} +@media(max-width:1180px){ + .hockey-map-table-browser{grid-template-columns:105px minmax(0,1fr)} + .hockey-map-table-fields-list{grid-template-columns:1fr} +} +@media(max-width:760px){ + .hockey-map-table-picker-v2{max-height:62%!important} + .hockey-map-table-browser{grid-template-columns:1fr;grid-template-rows:minmax(90px,.55fr) minmax(130px,1.45fr)} + .hockey-map-table-fields-list{grid-template-columns:repeat(2,minmax(0,1fr))} +} diff --git a/hockey_data/static/admin-directories.js b/hockey_data/static/admin-directories.js index 2394435..60b9580 100644 --- a/hockey_data/static/admin-directories.js +++ b/hockey_data/static/admin-directories.js @@ -2372,37 +2372,56 @@ }); const visibleRows = filteredRows.slice(0, 120); const columns = manualColumns; - // BUILD141: keep the horizontal scrollbar inside the visible Mapping viewport. - // A tall SQL table used to extend below the clipped data browser, hiding the - // horizontal scrollbar and making right-side columns unreachable. - const tableGridHeight = Math.min(360, Math.max(210, visibleRows.length * 44 + 42)); - const cells = visibleRows.map((row) => ` - - ${Number(row.index || 0)}${escapeHtml(row.label || `Строка ${row.index}`)} - ${columns.map((column) => { - const key = `${selectedTable.code}.row.${row.index}.${column.key}`; - const source = mappingSourceByKey(key); - const compatible = source && mappingCompatible(source, targetKind); - return ``; - }).join("")} - `).join(""); + // BUILD142: manual SQL linking no longer uses a wide spreadsheet. + // The operator chooses a row in a compact list and then sees every SQL + // column of that row as a vertical/card field browser. This keeps all + // columns reachable on narrow Mapping panels without horizontal scrolling. + let browserRow = visibleRows.find((row) => Number(row.index || 0) === Number(state.mappingSelectedTableRow || 0)) || visibleRows[0] || selectedTableRow || null; + if (browserRow) state.mappingSelectedTableRow = Number(browserRow.index || 1); + const rowButtons = visibleRows.map((row) => { + const preview = columns.slice(0, 4) + .map((column) => mappingDisplayValue(row.values?.[column.key], column.kind || "text")) + .map((value) => String(value || "").trim()) + .filter(Boolean) + .slice(0, 2) + .join(" · "); + const active = Number(row.index || 0) === Number(browserRow?.index || 0); + return ``; + }).join(""); + const fieldCards = browserRow ? columns.map((column) => { + const key = `${selectedTable.code}.row.${browserRow.index}.${column.key}`; + const source = mappingSourceByKey(key); + const compatible = source && (!targetKind || mappingCompatible(source, targetKind)); + const value = source ? source.value : browserRow.values?.[column.key]; + const selected = selectedSource === key; + return ``; + }).join("") : ""; tableMarkup = ` -
-
ТАБЛИЦА SQLВыберите конкретную строку и столбец
${Number(selectedTable?.row_count || 0)} строк · ${columns.length} колонок
+
+
ТАБЛИЦА SQLСтрока → поле
${Number(selectedTable?.row_count || 0)} строк · ${columns.length} колонок
-
- КОЛОНКИ - - - горизонтальная прокрутка - - -
${selectedTable?.skipped ? `
Не хватает параметров: ${escapeHtml((selectedTable.missing || []).map((x) => `:${x}`).join(", "))}
` : selectedTable?.error ? `
${escapeHtml(selectedTable.error)}
` : ` -
${columns.map((column) => ``).join("")}${cells || ``}
Строка${escapeHtml(column.label || column.key)}${column.localized ? ` AUTO ${String(column.language || selectedTable?.language || "ru").toUpperCase()}` : ""}${escapeHtml(column.key)}
Нет строк
+
+ +
+
+
2 · ПОЛЯ ВЫБРАННОЙ СТРОКИ${browserRow ? `#${Number(browserRow.index || 0)} · ${escapeHtml(browserRow.label || `Строка ${browserRow.index}`)}` : "Строка не выбрана"}
+ ${columns.length} +
+
${fieldCards || `
В строке нет доступных полей.
`}
+ Нажмите нужное поле SQL, затем поле vMix — или сначала выберите поле vMix справа. +
+
${filteredRows.length > visibleRows.length ? `Показаны первые ${visibleRows.length} из ${filteredRows.length} строк. Используйте поиск строки.` : ""}`}
`; } @@ -3340,6 +3359,13 @@ modal.querySelector("[data-map-sql-table-details]")?.addEventListener("toggle", (event) => { state.mappingSqlTableOpen = Boolean(event.currentTarget.open); }); + modal.querySelectorAll("[data-map-table-row-pick]").forEach((button) => button.addEventListener("click", () => { + state.mappingSelectedTableRow = Number(button.dataset.mapTableRowPick || 1); + state.mappingSelectedTableColumn = ""; + state.mappingSelectedSourceKey = ""; + state.mappingSqlTableOpen = true; + renderMapping(); + })); // BUILD141: the SQL picker lives inside a clipped flex panel. Keep its own // scroll position and provide explicit horizontal navigation so every SQL