изменение текущего времени, тест 2

This commit is contained in:
2026-05-25 14:02:32 +03:00
parent 508d209377
commit 9db889ca4b
3 changed files with 187 additions and 46 deletions

View File

@@ -98,6 +98,45 @@ function formatClock(totalSeconds) {
return `${minutes}:${seconds}`; return `${minutes}:${seconds}`;
} }
function formatVmixClock(totalSeconds) {
const safeSeconds = Math.max(0, Math.floor(Number(totalSeconds) || 0));
const hours = String(Math.floor(safeSeconds / 3600)).padStart(2, "0");
const minutes = String(Math.floor((safeSeconds % 3600) / 60)).padStart(2, "0");
const seconds = String(safeSeconds % 60).padStart(2, "0");
return `${hours}:${minutes}:${seconds}`;
}
function parseClockInput(value) {
const raw = String(value || "").trim().replaceAll(".", ":").replaceAll(",", ":");
if (!raw) return null;
const parts = raw.split(":").map(part => part.trim());
if (parts.length > 3 || parts.some(part => part === "" || !/^\d+$/.test(part))) {
return null;
}
let hours = 0;
let minutes = 0;
let seconds = 0;
if (parts.length === 1) {
minutes = Number(parts[0]);
} else if (parts.length === 2) {
minutes = Number(parts[0]);
seconds = Number(parts[1]);
} else {
hours = Number(parts[0]);
minutes = Number(parts[1]);
seconds = Number(parts[2]);
}
if (![hours, minutes, seconds].every(Number.isFinite) || seconds > 59 || minutes > 999) {
return null;
}
return Math.max(0, hours * 3600 + minutes * 60 + seconds);
}
function getFootballMinute(totalSeconds) { function getFootballMinute(totalSeconds) {
if (currentPeriod === "2H" || currentPeriod === "FT") { if (currentPeriod === "2H" || currentPeriod === "FT") {
const secondHalfSeconds = Math.max(0, totalSeconds - 45 * 60); const secondHalfSeconds = Math.max(0, totalSeconds - 45 * 60);
@@ -353,6 +392,90 @@ function startTimer() {
saveMatchState(); saveMatchState();
} }
async function syncEditedClockToVmix(totalSeconds, shouldRun) {
const commands = [
vmixCmd("Function=SetCountdown&Input=ВЕРХНИЙ СЧЕТ&SelectedName=Таймер.Text&Value=02:00:00"),
vmixCmd(`Function=ChangeCountdown&Input=ВЕРХНИЙ СЧЕТ&SelectedName=Таймер.Text&Value=${formatVmixClock(totalSeconds)}`),
vmixCmd(shouldRun
? "Function=StartCountdown&Input=ВЕРХНИЙ СЧЕТ&SelectedName=Таймер.Text"
: "Function=PauseCountdown&Input=ВЕРХНИЙ СЧЕТ&SelectedName=Таймер.Text"
),
vmixCmd("Function=PreviewInput&Input=ВЕРХНИЙ СЧЕТ")
];
await sendDirectVmixCommands(commands);
}
function closeClockEditor() {
const modal = document.getElementById("clockEditorModal");
if (modal) modal.classList.remove("show");
}
function openClockEditor() {
if (!(currentPeriod === "1H" || currentPeriod === "2H")) {
alert("Редактировать время можно после запуска 1-го или 2-го тайма");
return;
}
updateTimerUI();
const modal = document.getElementById("clockEditorModal");
const input = document.getElementById("clockEditorInput");
const hint = document.getElementById("clockEditorHint");
if (input) {
input.value = formatClock(matchClockSeconds);
}
if (hint) {
hint.textContent = timerId
? "Таймер сейчас идёт. После сохранения он продолжит идти с нового времени."
: "Таймер сейчас на паузе. После сохранения он останется на паузе.";
}
if (modal) modal.classList.add("show");
setTimeout(() => {
if (input) {
input.focus();
input.select();
}
}, 0);
}
async function applyClockEditor() {
const input = document.getElementById("clockEditorInput");
const newSeconds = parseClockInput(input ? input.value : "");
if (newSeconds === null) {
alert("Введите время в формате MM:SS, например 12:30 или 57:20");
return;
}
const wasRunning = !!timerId;
if (timerId) {
clearInterval(timerId);
timerId = null;
}
pausedAccumulatedSeconds = newSeconds;
matchClockSeconds = newSeconds;
periodStartedAt = wasRunning ? Date.now() : null;
if (wasRunning) {
timerId = setInterval(() => {
updateTimerUI();
saveMatchState();
}, 1000);
}
updateTimerUI();
saveMatchState();
closeClockEditor();
await syncEditedClockToVmix(newSeconds, wasRunning);
}
async function triggerVmixExtraTimeAction(minutes, half) { async function triggerVmixExtraTimeAction(minutes, half) {
const title = "ДОБАВЛЕННОЕ ВРЕМЯ ДЛЯ ВЕРХНЕГО СЧЕТА" const title = "ДОБАВЛЕННОЕ ВРЕМЯ ДЛЯ ВЕРХНЕГО СЧЕТА"
const value = `+${minutes}`; const value = `+${minutes}`;
@@ -3761,49 +3884,4 @@ function buildChannelValue(matchId) {
if (hasMatch) return "match"; if (hasMatch) return "match";
return ""; return "";
}
function setClockSeconds(newSeconds) {
const wasRunning = !!timerId;
if (timerId) {
clearInterval(timerId);
timerId = null;
}
pausedAccumulatedSeconds = Math.max(0, Number(newSeconds) || 0);
matchClockSeconds = pausedAccumulatedSeconds;
if (wasRunning) {
periodStartedAt = Date.now();
timerId = setInterval(() => {
updateTimerUI();
saveMatchState();
}, 1000);
} else {
periodStartedAt = null;
}
updateTimerUI();
saveMatchState();
syncClockToVmix(matchClockSeconds, wasRunning);
}
async function syncClockToVmix(seconds, shouldRun) {
const h = String(Math.floor(seconds / 3600)).padStart(2, "0");
const m = String(Math.floor((seconds % 3600) / 60)).padStart(2, "0");
const s = String(seconds % 60).padStart(2, "0");
const value = `${h}:${m}:${s}`;
const commands = [
vmixCmd("Function=SetCountdown&Input=ВЕРХНИЙ СЧЕТ&SelectedName=Таймер.Text&Value=02:00:00"),
vmixCmd(`Function=ChangeCountdown&Input=ВЕРХНИЙ СЧЕТ&SelectedName=Таймер.Text&Value=${value}`),
vmixCmd(
shouldRun
? "Function=StartCountdown&Input=ВЕРХНИЙ СЧЕТ&SelectedName=Таймер.Text"
: "Function=PauseCountdown&Input=ВЕРХНИЙ СЧЕТ&SelectedName=Таймер.Text"
)
];
await sendDirectVmixCommands(commands);
} }

View File

@@ -2351,4 +2351,35 @@ body:not(.edit-mode-on) .referee-search {
.event-time-edit-btn:focus-visible { .event-time-edit-btn:focus-visible {
outline: 2px solid rgba(95,132,255,0.45); outline: 2px solid rgba(95,132,255,0.45);
outline-offset: 2px; outline-offset: 2px;
}
.timer-box-clickable {
cursor: pointer;
}
.clock-editor-modal {
position: fixed;
inset: 0;
background: rgba(0,0,0,.45);
display: flex;
align-items: center;
justify-content: center;
z-index: 9999;
}
.clock-editor-content {
background: #1f1f1f;
padding: 20px;
border-radius: 12px;
width: 320px;
color: white;
}
.clock-editor-content input {
width: 100%;
padding: 10px;
font-size: 24px;
text-align: center;
margin-top: 10px;
margin-bottom: 15px;
} }

View File

@@ -289,7 +289,7 @@
<span class="period-badge" id="periodBadge" <span class="period-badge" id="periodBadge"
>Матч не начат</span >Матч не начат</span
> >
<div class="timer-box" id="matchTimer" onclick="openClockEditor()">00:00</div> <div class="timer-box timer-box-clickable" id="matchTimer" onclick="openClockEditor()" title="Нажмите, чтобы изменить время">00:00</div>
<div class="score-box" id="matchScore">0 : 0</div> <div class="score-box" id="matchScore">0 : 0</div>
<div class="extra-time-wrap"> <div class="extra-time-wrap">
@@ -1114,6 +1114,38 @@ data-role="{{ p.position or '' }}"
awayCoachPool: [] awayCoachPool: []
}; };
</script> </script>
<div class="formation-modal" id="clockEditorModal">
<div class="formation-modal-backdrop" onclick="closeClockEditor()"></div>
<div class="formation-modal-content clock-editor-modal-content">
<div class="formation-modal-header">
<div>
<div class="formation-modal-title">Редактировать время</div>
<div class="formation-modal-subtitle" id="clockEditorHint"></div>
</div>
<button type="button" class="modal-close-btn" onclick="closeClockEditor()">×</button>
</div>
<div class="formation-modal-body clock-editor-body">
<label class="clock-editor-label" for="clockEditorInput">Текущее матчевое время</label>
<input
type="text"
class="clock-editor-input"
id="clockEditorInput"
placeholder="12:30"
inputmode="numeric"
onkeydown="if (event.key === 'Enter') applyClockEditor(); if (event.key === 'Escape') closeClockEditor();"
/>
<div class="clock-editor-note">Можно ввести 12:30, 57:20 или 01:02:03.</div>
</div>
<div class="formation-modal-footer">
<button type="button" class="btn btn-secondary" onclick="closeClockEditor()">Отмена</button>
<button type="button" class="btn btn-primary" onclick="applyClockEditor()">Применить в интерфейсе и vMix</button>
</div>
</div>
</div>
<div class="formation-modal" id="formationAssignModal"> <div class="formation-modal" id="formationAssignModal">
<div <div
class="formation-modal-backdrop" class="formation-modal-backdrop"