экспорт маппинга
This commit is contained in:
@@ -5951,8 +5951,14 @@ function openTimerQuickEditor(focusActionId = "") {
|
||||
function normalizeHockeyPlayer(component, row, side, index) {
|
||||
const props = component.props || {};
|
||||
const get = (fieldName, fallback = "") => formatValue(getByPath(row, props[fieldName]), fallback);
|
||||
const externalId = String(get("idField", "") || row?.external_id || row?.id || "");
|
||||
const databaseId = String(row?.db_id ?? row?.database_id ?? "");
|
||||
return {
|
||||
id: String(get("idField", `${side}-${index + 1}`)),
|
||||
// `id` остаётся external_id для совместимости со старым Mapping.
|
||||
// `dbId` — реальный PK hockey_players.id из нашей базы.
|
||||
id: externalId || `${side}-${index + 1}`,
|
||||
externalId,
|
||||
dbId: databaseId,
|
||||
side,
|
||||
number: get("numberField", ""),
|
||||
name: get("nameField", `Игрок ${index + 1}`),
|
||||
@@ -6028,6 +6034,9 @@ function openTimerQuickEditor(focusActionId = "") {
|
||||
selectedInfraction: null,
|
||||
selectedPreset: null,
|
||||
selectedEventId: null,
|
||||
// Последний выбранный для редактирования штраф остаётся общим, а
|
||||
// preview/Mapping-выбор хранится отдельно для левой и правой команды.
|
||||
selectedPreviewEventIds: { home: "", away: "" },
|
||||
penalties: [],
|
||||
history: [],
|
||||
historyOpen: false,
|
||||
@@ -6128,11 +6137,15 @@ function openTimerQuickEditor(focusActionId = "") {
|
||||
}
|
||||
|
||||
function hockeyPenaltyContext(event) {
|
||||
const playerExternalId = String(event.player?.externalId || event.player?.external_id || event.player?.id || "");
|
||||
const playerDbId = String(event.player?.dbId || event.player?.db_id || event.player?.database_id || event.player?.raw?.db_id || "");
|
||||
return {
|
||||
penalty_id: event.id,
|
||||
event_time: event.eventTime,
|
||||
event_time_ms: event.eventTimeMs,
|
||||
player: event.player ? clone(event.player) : null,
|
||||
player_id: playerExternalId,
|
||||
player_db_id: playerDbId,
|
||||
team_penalty: Boolean(event.teamPenalty),
|
||||
infraction: event.infraction ? clone(event.infraction) : null,
|
||||
preset: event.preset,
|
||||
@@ -6458,6 +6471,7 @@ function openTimerQuickEditor(focusActionId = "") {
|
||||
const side = String(event.player?.side || event.side || "").toLowerCase();
|
||||
const remainingOnSide = board.penalties.filter((item) => !item.finished && hockeyEventReady(item) && String(item.player?.side || item.side || "").toLowerCase() === side).length;
|
||||
if (board.selectedEventId === event.id) board.selectedEventId = null;
|
||||
if (board.selectedPreviewEventIds?.[side] === event.id) board.selectedPreviewEventIds[side] = "";
|
||||
persistHockeyBoard(component, board, true);
|
||||
refreshHockeyBoardNodes(component);
|
||||
rebalanceVmixPenaltyTargets({ force: true, hideUnused: true })
|
||||
@@ -6476,6 +6490,10 @@ function openTimerQuickEditor(focusActionId = "") {
|
||||
} else if (command === "remove") {
|
||||
board.penalties = board.penalties.filter((item) => item.id !== eventId);
|
||||
if (board.selectedEventId === eventId) board.selectedEventId = null;
|
||||
{
|
||||
const removedSide = String(event.player?.side || event.side || "").toLowerCase();
|
||||
if (board.selectedPreviewEventIds?.[removedSide] === eventId) board.selectedPreviewEventIds[removedSide] = "";
|
||||
}
|
||||
addHockeyHistory(
|
||||
component,
|
||||
board,
|
||||
@@ -7043,21 +7061,71 @@ function hockeyInfractionDropdown(component, runtime) {
|
||||
return field;
|
||||
}
|
||||
|
||||
function hockeyPenaltyPlayerIdentifiers(component, event) {
|
||||
const side = String(event.player?.side || event.side || "").toLowerCase();
|
||||
let externalId = String(
|
||||
event.player?.externalId
|
||||
|| event.player?.external_id
|
||||
|| event.player?.id
|
||||
|| event.player?.raw?.external_id
|
||||
|| event.player?.raw?.id
|
||||
|| ""
|
||||
);
|
||||
let dbId = String(
|
||||
event.player?.dbId
|
||||
|| event.player?.db_id
|
||||
|| event.player?.database_id
|
||||
|| event.player?.raw?.db_id
|
||||
|| event.player?.raw?.database_id
|
||||
|| ""
|
||||
);
|
||||
|
||||
// Для сохранённых удалений из старых сборок dbId мог отсутствовать.
|
||||
// В этом случае восстанавливаем его по актуальному составу из БД.
|
||||
if (externalId && !dbId && ["home", "away"].includes(side)) {
|
||||
const rosterPlayer = hockeyRoster(component, side).find((player) =>
|
||||
String(player.externalId || player.id || "") === externalId
|
||||
);
|
||||
if (rosterPlayer) {
|
||||
dbId = String(rosterPlayer.dbId || rosterPlayer.raw?.db_id || "");
|
||||
externalId = String(rosterPlayer.externalId || rosterPlayer.id || externalId);
|
||||
}
|
||||
}
|
||||
return { externalId, dbId };
|
||||
}
|
||||
|
||||
function hockeyPenaltyIsPreviewSelected(board, event) {
|
||||
const side = String(event.player?.side || event.side || "").toLowerCase();
|
||||
return ["home", "away"].includes(side)
|
||||
&& String(board.selectedPreviewEventIds?.[side] || "") === String(event.id || "");
|
||||
}
|
||||
|
||||
async function hockeySelectPenaltyForPreview(component, event) {
|
||||
const board = ensureHockeyBoardState(component);
|
||||
board.selectedEventId = event.id;
|
||||
if (!board.selectedPreviewEventIds || typeof board.selectedPreviewEventIds !== "object") {
|
||||
board.selectedPreviewEventIds = { home: "", away: "" };
|
||||
}
|
||||
|
||||
const side = String(event.player?.side || event.side || "").toLowerCase();
|
||||
if (["home", "away"].includes(side)) board.selectedPreviewEventIds[side] = event.id;
|
||||
|
||||
const teamId = side === "home"
|
||||
? String(getByPath(state.data, "hockey.selected_game.home.id") || getByPath(state.data, "hockey.selected_game.home.external_id") || "")
|
||||
: side === "away"
|
||||
? String(getByPath(state.data, "hockey.selected_game.away.id") || getByPath(state.data, "hockey.selected_game.away.external_id") || "")
|
||||
: "";
|
||||
const playerIds = hockeyPenaltyPlayerIdentifiers(component, event);
|
||||
const detail = {
|
||||
item_id: String(event.external_id || event.id || ""),
|
||||
value: String(event.external_id || event.id || ""),
|
||||
penalty_id: String(event.external_id || event.id || ""),
|
||||
player_id: String(event.player?.id || ""),
|
||||
// Старый player_id остаётся external_id, чтобы не ломать существующие SQL.
|
||||
player_id: playerIds.externalId,
|
||||
player_external_id: playerIds.externalId,
|
||||
player_db_id: playerIds.dbId,
|
||||
team_id: teamId,
|
||||
team_penalty: Boolean(event.teamPenalty),
|
||||
infraction_id: String(event.infraction?.id || ""),
|
||||
preset_id: String(event.preset || ""),
|
||||
side,
|
||||
@@ -7067,7 +7135,14 @@ async function hockeySelectPenaltyForPreview(component, event) {
|
||||
status: event.finished ? "finished" : event.running ? "running" : hockeyEventReady(event) ? "ready" : "draft",
|
||||
selected: true,
|
||||
};
|
||||
window.UIBuilderRuntime?.patchData?.({ hockey: { selected_penalty: detail } }, { render: false });
|
||||
|
||||
const sidePatch = ["home", "away"].includes(side) ? { [side]: detail } : {};
|
||||
window.UIBuilderRuntime?.patchData?.({
|
||||
hockey: {
|
||||
selected_penalty: detail,
|
||||
selected_penalties: sidePatch,
|
||||
}
|
||||
}, { render: false });
|
||||
refreshHockeyBoardNodes(component);
|
||||
|
||||
const gameId = hockeyTimerSelectedGameId();
|
||||
@@ -7075,24 +7150,45 @@ async function hockeySelectPenaltyForPreview(component, event) {
|
||||
emitInteraction(component, "penalty_selected", detail);
|
||||
return detail;
|
||||
}
|
||||
|
||||
const values = {
|
||||
selected_penalty_id: detail.penalty_id,
|
||||
selected_penalty_player_id: detail.player_id,
|
||||
selected_penalty_player_db_id: detail.player_db_id,
|
||||
selected_penalty_team_penalty: detail.team_penalty ? "1" : "0",
|
||||
selected_penalty_team_id: detail.team_id,
|
||||
selected_penalty_infraction_id: detail.infraction_id,
|
||||
selected_penalty_preset_id: detail.preset_id,
|
||||
selected_penalty_side: detail.side,
|
||||
selected_penalty_event_time: detail.event_time,
|
||||
selected_penalty_status: detail.status,
|
||||
selected_penalty_remaining_ms: String(detail.remaining_ms),
|
||||
selected_penalty_duration_ms: String(detail.duration_ms),
|
||||
selected_event_id: detail.penalty_id,
|
||||
};
|
||||
|
||||
if (side === "home") {
|
||||
Object.assign(values, {
|
||||
selected_home_penalty_id: detail.penalty_id,
|
||||
selected_home_penalty_player_id: detail.player_id,
|
||||
selected_home_penalty_player_db_id: detail.player_db_id,
|
||||
selected_home_penalty_team_penalty: detail.team_penalty ? "1" : "0",
|
||||
});
|
||||
} else if (side === "away") {
|
||||
Object.assign(values, {
|
||||
selected_away_penalty_id: detail.penalty_id,
|
||||
selected_away_penalty_player_id: detail.player_id,
|
||||
selected_away_penalty_player_db_id: detail.player_db_id,
|
||||
selected_away_penalty_team_penalty: detail.team_penalty ? "1" : "0",
|
||||
});
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch("/api/hockey/context/batch", {
|
||||
method: "POST", cache: "no-store", credentials: "same-origin",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({
|
||||
values: {
|
||||
selected_penalty_id: detail.penalty_id,
|
||||
selected_penalty_player_id: detail.player_id,
|
||||
selected_penalty_team_id: detail.team_id,
|
||||
selected_penalty_infraction_id: detail.infraction_id,
|
||||
selected_penalty_preset_id: detail.preset_id,
|
||||
selected_penalty_side: detail.side,
|
||||
selected_penalty_event_time: detail.event_time,
|
||||
selected_penalty_status: detail.status,
|
||||
selected_penalty_remaining_ms: String(detail.remaining_ms),
|
||||
selected_penalty_duration_ms: String(detail.duration_ms),
|
||||
selected_event_id: detail.penalty_id,
|
||||
},
|
||||
values,
|
||||
context: {
|
||||
game_id: gameId,
|
||||
device_id: currentRuntimeVmixDeviceId(),
|
||||
@@ -7119,6 +7215,7 @@ function hockeyPenaltyCard(component, event, runtime) {
|
||||
const card = div(`hpd-team-penalty-card team-${side}`);
|
||||
card.dataset.penaltyId = event.id;
|
||||
card.classList.toggle("is-selected", board.selectedEventId === event.id);
|
||||
card.classList.toggle("is-preview-selected", hockeyPenaltyIsPreviewSelected(board, event));
|
||||
card.classList.toggle("is-running", event.running);
|
||||
card.classList.toggle("is-finished", event.finished);
|
||||
card.classList.toggle("is-ready", hockeyEventReady(event) && !event.running && !event.finished);
|
||||
@@ -7134,6 +7231,8 @@ function hockeyPenaltyCard(component, event, runtime) {
|
||||
const presetText = event.preset
|
||||
? `${escapeHtml(event.preset)} · ${escapeHtml(formatHockeyPenaltyTime(event.durationMs))}`
|
||||
: "Выберите длительность";
|
||||
const previewSelected = hockeyPenaltyIsPreviewSelected(board, event);
|
||||
const playerIds = hockeyPenaltyPlayerIdentifiers(component, event);
|
||||
|
||||
card.innerHTML = `
|
||||
<div class="hpd-team-card-accent"></div>
|
||||
@@ -7165,9 +7264,18 @@ function hockeyPenaltyCard(component, event, runtime) {
|
||||
<span>Командное удаление — без игрока</span>
|
||||
</label>
|
||||
|
||||
${previewSelected ? `
|
||||
<div class="hpd-team-card-identifiers" title="Идентификаторы выбранного удаления для Mapping">
|
||||
<span><b>ID удаления</b>${escapeHtml(String(event.external_id || event.id || "—"))}</span>
|
||||
<span><b>Player external ID</b>${escapeHtml(playerIds.externalId || "—")}</span>
|
||||
<span><b>Player DB ID</b>${escapeHtml(playerIds.dbId || "—")}</span>
|
||||
<span><b>Командное</b>${event.teamPenalty ? "1" : "0"}</span>
|
||||
</div>
|
||||
` : ""}
|
||||
|
||||
<div class="hpd-team-card-footer">
|
||||
<span class="hpd-team-card-status">${
|
||||
board.selectedEventId === event.id
|
||||
previewSelected
|
||||
? "Выбрано · подсмотр"
|
||||
: event.finished
|
||||
? "Завершено"
|
||||
@@ -11116,7 +11224,25 @@ function renderHockeyPenaltyDashboard(node, component, runtime) {
|
||||
el.runtimeSizer.style.height = `${Math.round(canvasHeight * scale * 1000) / 1000}px`;
|
||||
el.runtimeStage.style.width = `${canvasWidth}px`;
|
||||
el.runtimeStage.style.height = `${canvasHeight}px`;
|
||||
el.runtimeStage.style.transform = `translate3d(0, 0, 0) scale(${scale})`;
|
||||
|
||||
// BUILD 70: scale the operator UI with CSS zoom when available. Unlike a
|
||||
// fractional transform: scale(), CSS zoom asks Chromium to lay out and
|
||||
// rasterize text/1px borders at the requested size instead of enlarging a
|
||||
// pre-rendered texture. This keeps every runtime tab visibly sharper.
|
||||
const canUseCssZoom = typeof CSS !== "undefined"
|
||||
&& typeof CSS.supports === "function"
|
||||
&& CSS.supports("zoom", "1");
|
||||
if (canUseCssZoom) {
|
||||
el.runtimeStage.style.zoom = String(scale);
|
||||
el.runtimeStage.style.transform = "none";
|
||||
el.runtimeStage.style.willChange = "auto";
|
||||
} else {
|
||||
// Old embedded browsers still get the previous responsive behaviour,
|
||||
// but avoid translate3d so we do not force an extra GPU texture layer.
|
||||
el.runtimeStage.style.zoom = "";
|
||||
el.runtimeStage.style.transform = `scale(${scale})`;
|
||||
el.runtimeStage.style.willChange = "transform";
|
||||
}
|
||||
el.runtimeStage.style.background = state.config.canvas.background || "#0c1421";
|
||||
el.runtimeStage.dataset.runtimeScale = String(scale);
|
||||
}
|
||||
@@ -12718,6 +12844,9 @@ function renderHockeyPenaltyDashboard(node, component, runtime) {
|
||||
state.modifierShortcutChordUsedKey = true;
|
||||
}
|
||||
if (editorHotkeyPressed(event)) {
|
||||
// In runtime the editor entry button is revealed only for admins by
|
||||
// the hockey account bootstrap. Hidden means editor access is denied.
|
||||
if (boot.mode !== "editor" && el.runtimeEditorBtn?.classList.contains("hidden")) return;
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
if (boot.mode === "editor") window.location.href = boot.runtimeUrl;
|
||||
|
||||
Reference in New Issue
Block a user