изменение текущего времени, тест 2
This commit is contained in:
168
static/script.js
168
static/script.js
@@ -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}`;
|
||||||
@@ -3762,48 +3885,3 @@ function buildChannelValue(matchId) {
|
|||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
@@ -2352,3 +2352,34 @@ body:not(.edit-mode-on) .referee-search {
|
|||||||
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;
|
||||||
|
}
|
||||||
@@ -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"
|
||||||
|
|||||||
Reference in New Issue
Block a user