build 118 - оверлеи
This commit is contained in:
11
agent/README_UPDATE.txt
Normal file
11
agent/README_UPDATE.txt
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
Hockey vMix Agent 1.5.1
|
||||||
|
|
||||||
|
Changes:
|
||||||
|
- vMix 29 tally now reads Overlay channels 1 through 8.
|
||||||
|
- Existing agent_config.json is NOT included. Keep your current config next to agent.exe.
|
||||||
|
|
||||||
|
Update:
|
||||||
|
1. Close Agent.
|
||||||
|
2. Replace only agent.exe.
|
||||||
|
3. Keep the existing agent_config.json.
|
||||||
|
4. Start Agent again.
|
||||||
BIN
agent/agent.exe
BIN
agent/agent.exe
Binary file not shown.
31306
agent/agent.log.1
Normal file
31306
agent/agent.log.1
Normal file
File diff suppressed because it is too large
Load Diff
2
app.py
2
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
|
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.26.2"
|
BUILD_VERSION = "2026.08.26.5"
|
||||||
# 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"
|
||||||
|
|||||||
@@ -277,7 +277,7 @@ class VmixAgentHub:
|
|||||||
text = re.sub(r"\s+", "", str(function_name or ""))
|
text = re.sub(r"\s+", "", str(function_name or ""))
|
||||||
if text.lower() == "overlayinputalloff":
|
if text.lower() == "overlayinputalloff":
|
||||||
return ("all", "off")
|
return ("all", "off")
|
||||||
match = re.search(r"OverlayInput([1-4])(In|Out|Off)?", text, flags=re.IGNORECASE)
|
match = re.search(r"OverlayInput([1-8])(In|Out|Off)?", text, flags=re.IGNORECASE)
|
||||||
if not match:
|
if not match:
|
||||||
return None
|
return None
|
||||||
raw = str(match.group(2) or "toggle").lower()
|
raw = str(match.group(2) or "toggle").lower()
|
||||||
@@ -333,7 +333,7 @@ class VmixAgentHub:
|
|||||||
|
|
||||||
def _runtime_overlay_payload(self, device_id: str) -> dict[str, Any]:
|
def _runtime_overlay_payload(self, device_id: str) -> dict[str, Any]:
|
||||||
source = self._runtime_overlay_state.get(device_id, {})
|
source = self._runtime_overlay_state.get(device_id, {})
|
||||||
overlays = {str(layer): dict(value) for layer, value in source.items() if str(layer) in {"1", "2", "3", "4"}}
|
overlays = {str(layer): dict(value) for layer, value in source.items() if str(layer) in {"1", "2", "3", "4", "5", "6", "7", "8"}}
|
||||||
authoritative = bool(getattr(self, "_runtime_overlay_authoritative", {}).get(device_id, False))
|
authoritative = bool(getattr(self, "_runtime_overlay_authoritative", {}).get(device_id, False))
|
||||||
observed = str(getattr(self, "_runtime_overlay_observed_at", {}).get(device_id) or "")
|
observed = str(getattr(self, "_runtime_overlay_observed_at", {}).get(device_id) or "")
|
||||||
return {
|
return {
|
||||||
@@ -697,7 +697,7 @@ class VmixAgentHub:
|
|||||||
async def receive_overlay_state(self, device_id: str, message: dict[str, Any]) -> None:
|
async def receive_overlay_state(self, device_id: str, message: dict[str, Any]) -> None:
|
||||||
raw_overlays = message.get("overlays") if isinstance(message.get("overlays"), dict) else {}
|
raw_overlays = message.get("overlays") if isinstance(message.get("overlays"), dict) else {}
|
||||||
next_state: dict[str, dict[str, str]] = {}
|
next_state: dict[str, dict[str, str]] = {}
|
||||||
for layer in ("1", "2", "3", "4"):
|
for layer in ("1", "2", "3", "4", "5", "6", "7", "8"):
|
||||||
raw = raw_overlays.get(layer)
|
raw = raw_overlays.get(layer)
|
||||||
if raw is None:
|
if raw is None:
|
||||||
raw = raw_overlays.get(int(layer))
|
raw = raw_overlays.get(int(layer))
|
||||||
|
|||||||
@@ -340,7 +340,7 @@ class HockeySettingsStore:
|
|||||||
source = source_team_states.get(state_key)
|
source = source_team_states.get(state_key)
|
||||||
source = source if isinstance(source, dict) else {}
|
source = source if isinstance(source, dict) else {}
|
||||||
overlay = str(source.get("overlay") or defaults["overlay"]).strip()
|
overlay = str(source.get("overlay") or defaults["overlay"]).strip()
|
||||||
if overlay not in {"1", "2", "3", "4"}:
|
if overlay not in {"1", "2", "3", "4", "5", "6", "7", "8"}:
|
||||||
overlay = str(defaults["overlay"])
|
overlay = str(defaults["overlay"])
|
||||||
normalised_team_states[state_key] = {
|
normalised_team_states[state_key] = {
|
||||||
"ru": str(source.get("ru", defaults["ru"]) or "").strip()[:48],
|
"ru": str(source.get("ru", defaults["ru"]) or "").strip()[:48],
|
||||||
|
|||||||
@@ -653,14 +653,14 @@
|
|||||||
function scoreboardTeamStatesMarkup(settings) {
|
function scoreboardTeamStatesMarkup(settings) {
|
||||||
return SCOREBOARD_TEAM_STATE_ROWS.map((row) => {
|
return SCOREBOARD_TEAM_STATE_ROWS.map((row) => {
|
||||||
const value = scoreboardTeamStateValue(settings, row.key);
|
const value = scoreboardTeamStateValue(settings, row.key);
|
||||||
const overlay = ["1", "2", "3", "4"].includes(String(value.overlay || "")) ? String(value.overlay) : (row.key.includes("empty_net") ? "3" : "2");
|
const overlay = ["1", "2", "3", "4", "5", "6", "7", "8"].includes(String(value.overlay || "")) ? String(value.overlay) : (row.key.includes("empty_net") ? "3" : "2");
|
||||||
return `
|
return `
|
||||||
<div class="hockey-team-state-setting-row">
|
<div class="hockey-team-state-setting-row">
|
||||||
<div class="hockey-team-state-setting-kind"><b>${escapeHtml(row.side)}</b><strong>${escapeHtml(row.title)}</strong></div>
|
<div class="hockey-team-state-setting-kind"><b>${escapeHtml(row.side)}</b><strong>${escapeHtml(row.title)}</strong></div>
|
||||||
<label><span>Русская надпись</span><input name="team_state_${row.key}_ru" maxlength="48" value="${escapeHtml(value.ru || row.defaultRu)}"></label>
|
<label><span>Русская надпись</span><input name="team_state_${row.key}_ru" maxlength="48" value="${escapeHtml(value.ru || row.defaultRu)}"></label>
|
||||||
<label><span>English label</span><input name="team_state_${row.key}_en" maxlength="48" value="${escapeHtml(value.en || row.defaultEn)}"></label>
|
<label><span>English label</span><input name="team_state_${row.key}_en" maxlength="48" value="${escapeHtml(value.en || row.defaultEn)}"></label>
|
||||||
<label><span>vMix Input</span><select name="team_state_${row.key}_input">${scoreboardInputOptions(value.input, value.input_title)}</select></label>
|
<label><span>vMix Input</span><select name="team_state_${row.key}_input">${scoreboardInputOptions(value.input, value.input_title)}</select></label>
|
||||||
<label><span>Overlay</span><select name="team_state_${row.key}_overlay">${[1,2,3,4].map((item) => `<option value="${item}" ${String(item) === overlay ? "selected" : ""}>Overlay ${item}</option>`).join("")}</select></label>
|
<label><span>Overlay</span><select name="team_state_${row.key}_overlay">${[1,2,3,4,5,6,7,8].map((item) => `<option value="${item}" ${String(item) === overlay ? "selected" : ""}>Overlay ${item}</option>`).join("")}</select></label>
|
||||||
<label class="hockey-rules-switch compact"><input name="team_state_${row.key}_enabled" type="checkbox" ${value.enabled !== false ? "checked" : ""}><i></i><span><strong>Использовать</strong></span></label>
|
<label class="hockey-rules-switch compact"><input name="team_state_${row.key}_enabled" type="checkbox" ${value.enabled !== false ? "checked" : ""}><i></i><span><strong>Использовать</strong></span></label>
|
||||||
</div>`;
|
</div>`;
|
||||||
}).join("");
|
}).join("");
|
||||||
|
|||||||
@@ -2357,7 +2357,7 @@ function startCustomTooltips() {
|
|||||||
id: String(target?.id || `penalty-target-${index + 1}-${Math.random().toString(36).slice(2, 7)}`),
|
id: String(target?.id || `penalty-target-${index + 1}-${Math.random().toString(36).slice(2, 7)}`),
|
||||||
input: String(target?.input || ""),
|
input: String(target?.input || ""),
|
||||||
selected_name: String(target?.selected_name || target?.selectedName || ""),
|
selected_name: String(target?.selected_name || target?.selectedName || ""),
|
||||||
overlay: ["1", "2", "3", "4"].includes(String(target?.overlay || "")) ? String(target.overlay) : "2",
|
overlay: ["1", "2", "3", "4", "5", "6", "7", "8"].includes(String(target?.overlay || "")) ? String(target.overlay) : "2",
|
||||||
auto_hide_on_finish: target?.auto_hide_on_finish !== false,
|
auto_hide_on_finish: target?.auto_hide_on_finish !== false,
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
@@ -2370,7 +2370,7 @@ function startCustomTooltips() {
|
|||||||
enabled: action?.enabled !== false,
|
enabled: action?.enabled !== false,
|
||||||
source: allowedSources.has(String(action?.source || "")) ? String(action.source) : "game",
|
source: allowedSources.has(String(action?.source || "")) ? String(action.source) : "game",
|
||||||
input: String(action?.input || ""),
|
input: String(action?.input || ""),
|
||||||
overlay: ["1", "2", "3", "4"].includes(String(action?.overlay || "")) ? String(action.overlay) : "1",
|
overlay: ["1", "2", "3", "4", "5", "6", "7", "8"].includes(String(action?.overlay || "")) ? String(action.overlay) : "1",
|
||||||
duration_ms: clamp(Number(action?.duration_ms) || 3000, 100, 120000),
|
duration_ms: clamp(Number(action?.duration_ms) || 3000, 100, 120000),
|
||||||
only_when_side_clear: action?.only_when_side_clear !== false,
|
only_when_side_clear: action?.only_when_side_clear !== false,
|
||||||
}));
|
}));
|
||||||
@@ -2537,6 +2537,15 @@ function startCustomTooltips() {
|
|||||||
prevent_default: sequence.prevent_default !== false,
|
prevent_default: sequence.prevent_default !== false,
|
||||||
allow_in_inputs: Boolean(sequence.allow_in_inputs),
|
allow_in_inputs: Boolean(sequence.allow_in_inputs),
|
||||||
toggle_all_overlays_on_repeat: Boolean(sequence.toggle_all_overlays_on_repeat),
|
toggle_all_overlays_on_repeat: Boolean(sequence.toggle_all_overlays_on_repeat),
|
||||||
|
// BUILD117: keep the legacy toggle flag for config compatibility, but let the
|
||||||
|
// operator choose exactly which vMix overlay channels are taken OUT on repeat.
|
||||||
|
// Existing scoreboard configs had no list, so the safe default remains 1/2/3.
|
||||||
|
// BUILD118 exposes vMix 29 Overlay channels 1..8 while leaving 4..8 untouched by default.
|
||||||
|
repeat_overlay_outs: (() => {
|
||||||
|
const raw = Array.isArray(sequence.repeat_overlay_outs) ? sequence.repeat_overlay_outs : [1, 2, 3];
|
||||||
|
const normalized = [...new Set(raw.map((value) => Number(value)).filter((value) => Number.isInteger(value) && value >= 1 && value <= 8))];
|
||||||
|
return normalized.length ? normalized : [1, 2, 3];
|
||||||
|
})(),
|
||||||
sync_hockey_team_states: sequence.sync_hockey_team_states === undefined
|
sync_hockey_team_states: sequence.sync_hockey_team_states === undefined
|
||||||
? Boolean(sequence.toggle_all_overlays_on_repeat)
|
? Boolean(sequence.toggle_all_overlays_on_repeat)
|
||||||
: Boolean(sequence.sync_hockey_team_states),
|
: Boolean(sequence.sync_hockey_team_states),
|
||||||
@@ -4322,8 +4331,8 @@ function startCustomTooltips() {
|
|||||||
// executes the command, but the strict BUILD58 parser did not recognise it,
|
// executes the command, but the strict BUILD58 parser did not recognise it,
|
||||||
// so the quick-panel button never received the on-air state.
|
// so the quick-panel button never received the on-air state.
|
||||||
const normalized = String(functionName || "").trim().replace(/\s+/g, "");
|
const normalized = String(functionName || "").trim().replace(/\s+/g, "");
|
||||||
const match = normalized.match(/(?:^|[^a-z0-9])OverlayInput([1-4])(In|Out|Off)?(?=$|[^a-z])/i)
|
const match = normalized.match(/(?:^|[^a-z0-9])OverlayInput([1-8])(In|Out|Off)?(?=$|[^a-z])/i)
|
||||||
|| normalized.match(/^OverlayInput([1-4])(In|Out|Off)?/i);
|
|| normalized.match(/^OverlayInput([1-8])(In|Out|Off)?/i);
|
||||||
if (!match) return null;
|
if (!match) return null;
|
||||||
const raw = String(match[2] || "toggle").toLowerCase();
|
const raw = String(match[2] || "toggle").toLowerCase();
|
||||||
const action = raw === "in" ? "In" : raw === "out" ? "Out" : raw === "off" ? "Off" : "toggle";
|
const action = raw === "in" ? "In" : raw === "out" ? "Out" : raw === "off" ? "Off" : "toggle";
|
||||||
@@ -4467,7 +4476,7 @@ function startCustomTooltips() {
|
|||||||
// original PreviewInput GUID can now be matched to the real on-air Input.
|
// original PreviewInput GUID can now be matched to the real on-air Input.
|
||||||
const previewTargets = shortcutSequencePreviewTargets(sequenceOrId);
|
const previewTargets = shortcutSequencePreviewTargets(sequenceOrId);
|
||||||
if (!previewTargets.length) return false;
|
if (!previewTargets.length) return false;
|
||||||
for (const layer of ["1", "2", "3", "4"]) {
|
for (const layer of ["1", "2", "3", "4", "5", "6", "7", "8"]) {
|
||||||
const current = state.vmixOverlayRuntime.get(layer);
|
const current = state.vmixOverlayRuntime.get(layer);
|
||||||
if (previewTargets.some((input) => runtimeOverlayMatchesInput(current, input))) return true;
|
if (previewTargets.some((input) => runtimeOverlayMatchesInput(current, input))) return true;
|
||||||
}
|
}
|
||||||
@@ -4523,7 +4532,7 @@ function startCustomTooltips() {
|
|||||||
const previousServerIds = new Set(state.quickPanelServerOnAirSequences);
|
const previousServerIds = new Set(state.quickPanelServerOnAirSequences);
|
||||||
const nextServerIds = new Set();
|
const nextServerIds = new Set();
|
||||||
|
|
||||||
for (const layer of ["1", "2", "3", "4"]) {
|
for (const layer of ["1", "2", "3", "4", "5", "6", "7", "8"]) {
|
||||||
const raw = overlays[layer];
|
const raw = overlays[layer];
|
||||||
if (raw && typeof raw === "object") {
|
if (raw && typeof raw === "object") {
|
||||||
const entry = {
|
const entry = {
|
||||||
@@ -5203,7 +5212,7 @@ function startCustomTooltips() {
|
|||||||
if (entry && target.input && target.selected_name) {
|
if (entry && target.input && target.selected_name) {
|
||||||
const eventKey = penaltyMirrorKey(entry.component, entry.event);
|
const eventKey = penaltyMirrorKey(entry.component, entry.event);
|
||||||
const sourceSide = String(entry.side || entry.event?.side || entry.event?.player?.side || side);
|
const sourceSide = String(entry.side || entry.event?.side || entry.event?.player?.side || side);
|
||||||
const overlay = ["1", "2", "3", "4"].includes(String(target.overlay || "")) ? String(target.overlay) : "2";
|
const overlay = ["1", "2", "3", "4", "5", "6", "7", "8"].includes(String(target.overlay || "")) ? String(target.overlay) : "2";
|
||||||
const assignmentChanged = !previous
|
const assignmentChanged = !previous
|
||||||
|| previous.eventKey !== eventKey
|
|| previous.eventKey !== eventKey
|
||||||
|| String(previous.input || "") !== String(target.input)
|
|| String(previous.input || "") !== String(target.input)
|
||||||
@@ -5261,7 +5270,7 @@ function startCustomTooltips() {
|
|||||||
&& String(previous.overlay || overlay) === overlay;
|
&& String(previous.overlay || overlay) === overlay;
|
||||||
if (!targetWasVisible) {
|
if (!targetWasVisible) {
|
||||||
if (previous?.input && (String(previous.input) !== String(target.input) || String(previous.overlay || overlay) !== overlay)) {
|
if (previous?.input && (String(previous.input) !== String(target.input) || String(previous.overlay || overlay) !== overlay)) {
|
||||||
const previousOverlay = ["1", "2", "3", "4"].includes(String(previous.overlay || "")) ? String(previous.overlay) : overlay;
|
const previousOverlay = ["1", "2", "3", "4", "5", "6", "7", "8"].includes(String(previous.overlay || "")) ? String(previous.overlay) : overlay;
|
||||||
outCommands.push({ Function: `OverlayInput${previousOverlay}Out`, Input: previous.input });
|
outCommands.push({ Function: `OverlayInput${previousOverlay}Out`, Input: previous.input });
|
||||||
}
|
}
|
||||||
inCommands.push({ Function: `OverlayInput${overlay}In`, Input: target.input });
|
inCommands.push({ Function: `OverlayInput${overlay}In`, Input: target.input });
|
||||||
@@ -5272,7 +5281,7 @@ function startCustomTooltips() {
|
|||||||
stopCommands.push({ Function: "StopCountdown", Input: previous.input, SelectedName: previous.selectedName || target.selected_name });
|
stopCommands.push({ Function: "StopCountdown", Input: previous.input, SelectedName: previous.selectedName || target.selected_name });
|
||||||
}
|
}
|
||||||
if (previous && hideUnused && target.auto_hide_on_finish !== false && target.input) {
|
if (previous && hideUnused && target.auto_hide_on_finish !== false && target.input) {
|
||||||
const overlay = ["1", "2", "3", "4"].includes(String(target.overlay || "")) ? String(target.overlay) : "2";
|
const overlay = ["1", "2", "3", "4", "5", "6", "7", "8"].includes(String(target.overlay || "")) ? String(target.overlay) : "2";
|
||||||
outCommands.push({ Function: `OverlayInput${overlay}Out`, Input: target.input });
|
outCommands.push({ Function: `OverlayInput${overlay}Out`, Input: target.input });
|
||||||
}
|
}
|
||||||
state.vmixPenaltyTargetAssignments.delete(assignmentKey);
|
state.vmixPenaltyTargetAssignments.delete(assignmentKey);
|
||||||
@@ -5285,7 +5294,7 @@ function startCustomTooltips() {
|
|||||||
stopCommands.push({ Function: "StopCountdown", Input: previous.input, SelectedName: previous.selectedName || target.selected_name });
|
stopCommands.push({ Function: "StopCountdown", Input: previous.input, SelectedName: previous.selectedName || target.selected_name });
|
||||||
}
|
}
|
||||||
if (previous && hideUnused && target.auto_hide_on_finish !== false && target.input) {
|
if (previous && hideUnused && target.auto_hide_on_finish !== false && target.input) {
|
||||||
const overlay = ["1", "2", "3", "4"].includes(String(target.overlay || "")) ? String(target.overlay) : "2";
|
const overlay = ["1", "2", "3", "4", "5", "6", "7", "8"].includes(String(target.overlay || "")) ? String(target.overlay) : "2";
|
||||||
outCommands.push({ Function: `OverlayInput${overlay}Out`, Input: target.input });
|
outCommands.push({ Function: `OverlayInput${overlay}Out`, Input: target.input });
|
||||||
}
|
}
|
||||||
state.vmixPenaltyTargetAssignments.delete(assignmentKey);
|
state.vmixPenaltyTargetAssignments.delete(assignmentKey);
|
||||||
@@ -5314,7 +5323,7 @@ function startCustomTooltips() {
|
|||||||
async function runVmixTimedFinishAction(action, meta = {}) {
|
async function runVmixTimedFinishAction(action, meta = {}) {
|
||||||
const input = String(action?.input || "").trim();
|
const input = String(action?.input || "").trim();
|
||||||
if (!input || action?.enabled === false) return false;
|
if (!input || action?.enabled === false) return false;
|
||||||
const overlay = ["1", "2", "3", "4"].includes(String(action.overlay || "")) ? String(action.overlay) : "1";
|
const overlay = ["1", "2", "3", "4", "5", "6", "7", "8"].includes(String(action.overlay || "")) ? String(action.overlay) : "1";
|
||||||
const durationMs = clamp(Number(action.duration_ms) || 3000, 100, 120000);
|
const durationMs = clamp(Number(action.duration_ms) || 3000, 100, 120000);
|
||||||
const timerKey = `${action.id || input}:${overlay}:${input}`;
|
const timerKey = `${action.id || input}:${overlay}:${input}`;
|
||||||
const previousTimer = state.vmixFinishOverlayTimers.get(timerKey);
|
const previousTimer = state.vmixFinishOverlayTimers.get(timerKey);
|
||||||
@@ -5542,17 +5551,23 @@ function startCustomTooltips() {
|
|||||||
const execution = { sequence_id: String(sequence.id || ""), sequence_name: String(sequence.name || ""), button_id: String(meta.button_id || "") };
|
const execution = { sequence_id: String(sequence.id || ""), sequence_name: String(sequence.name || ""), button_id: String(meta.button_id || "") };
|
||||||
window.dispatchEvent(new CustomEvent("ui-builder:shortcut-sequence-start", { detail: { sequence: clone(sequence), meta } }));
|
window.dispatchEvent(new CustomEvent("ui-builder:shortcut-sequence-start", { detail: { sequence: clone(sequence), meta } }));
|
||||||
try {
|
try {
|
||||||
if (sequence.toggle_all_overlays_on_repeat && state.shortcutSequenceOverlayState.get(sequence.id)) {
|
// BUILD116: repeat-toggle must follow the same ON AIR decision as the UI.
|
||||||
// IMPORTANT: do not use OverlayInputAllOff here. vMix documents AllOff as an
|
// With authoritative Agent/vMix tally, a scoreboard can be truly live even when
|
||||||
// immediate cut, which bypasses the configured overlay/GT transition-out.
|
// the legacy local latch is false (for example after reload or external vMix IN).
|
||||||
// Explicit OverlayInputNOut commands preserve the exit animation while still
|
// `wasOnAir` already resolves to the real tally when available, and falls back
|
||||||
// clearing every overlay channel controlled by vMix.
|
// to the legacy latch when tally is unavailable.
|
||||||
await sendRuntimeVmixSequence([
|
if (sequence.toggle_all_overlays_on_repeat && wasOnAir) {
|
||||||
{ Function: "OverlayInput1Out" },
|
// BUILD118: repeat OUT is configurable across vMix Overlay channels 1..8. Do not use
|
||||||
{ Function: "OverlayInput2Out" },
|
// OverlayInputAllOff: explicit OverlayInputNOut preserves each configured
|
||||||
{ Function: "OverlayInput3Out" },
|
// transition-out and avoids touching unrelated channels that are not selected.
|
||||||
{ Function: "OverlayInput4Out" },
|
const repeatOverlayOuts = (Array.isArray(sequence.repeat_overlay_outs) ? sequence.repeat_overlay_outs : [1, 2, 3])
|
||||||
], execution);
|
.map((value) => Number(value))
|
||||||
|
.filter((value, index, values) => Number.isInteger(value) && value >= 1 && value <= 8 && values.indexOf(value) === index);
|
||||||
|
const selectedOverlayOuts = repeatOverlayOuts.length ? repeatOverlayOuts : [1, 2, 3];
|
||||||
|
await sendRuntimeVmixSequence(
|
||||||
|
selectedOverlayOuts.map((number) => ({ Function: `OverlayInput${number}Out` })),
|
||||||
|
execution,
|
||||||
|
);
|
||||||
if (sequence.sync_hockey_team_states) hockeyClearTeamStateOverlayTracking();
|
if (sequence.sync_hockey_team_states) hockeyClearTeamStateOverlayTracking();
|
||||||
state.shortcutSequenceOverlayState.set(sequence.id, false);
|
state.shortcutSequenceOverlayState.set(sequence.id, false);
|
||||||
state.quickPanelOnAirSequences.delete(String(sequence.id || ""));
|
state.quickPanelOnAirSequences.delete(String(sequence.id || ""));
|
||||||
@@ -11918,7 +11933,7 @@ function renderHockeyPenaltyDashboard(node, component, runtime) {
|
|||||||
label: String(value.label || value[language] || fallback),
|
label: String(value.label || value[language] || fallback),
|
||||||
input: String(value.input || "").trim(),
|
input: String(value.input || "").trim(),
|
||||||
input_title: String(value.input_title || "").trim(),
|
input_title: String(value.input_title || "").trim(),
|
||||||
overlay: ["1", "2", "3", "4"].includes(String(value.overlay || "")) ? String(value.overlay) : (key.includes("empty_net") ? "3" : "2"),
|
overlay: ["1", "2", "3", "4", "5", "6", "7", "8"].includes(String(value.overlay || "")) ? String(value.overlay) : (key.includes("empty_net") ? "3" : "2"),
|
||||||
enabled: value.enabled !== false,
|
enabled: value.enabled !== false,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -13700,7 +13715,7 @@ function renderHockeyPenaltyDashboard(node, component, runtime) {
|
|||||||
// Colour correction
|
// Colour correction
|
||||||
"SetCCGainB","SetCCGainG","SetCCGainR","SetCCGainRGB","SetCCGainY","SetCCGammaB","SetCCGammaG","SetCCGammaR","SetCCGammaRGB","SetCCGammaY","SetCCHue","SetCCLiftB","SetCCLiftG","SetCCLiftR","SetCCLiftRGB","SetCCLiftY","SetCCSaturation",
|
"SetCCGainB","SetCCGainG","SetCCGainR","SetCCGainRGB","SetCCGainY","SetCCGammaB","SetCCGammaG","SetCCGammaR","SetCCGammaRGB","SetCCGammaY","SetCCHue","SetCCLiftB","SetCCLiftG","SetCCLiftR","SetCCLiftRGB","SetCCLiftY","SetCCSaturation",
|
||||||
// Overlay
|
// Overlay
|
||||||
"OverlayInput1","OverlayInput1In","OverlayInput1Out","OverlayInput2","OverlayInput2In","OverlayInput2Out","OverlayInput3","OverlayInput3In","OverlayInput3Out","OverlayInput4","OverlayInput4In","OverlayInput4Out",
|
"OverlayInput1","OverlayInput1In","OverlayInput1Out","OverlayInput2","OverlayInput2In","OverlayInput2Out","OverlayInput3","OverlayInput3In","OverlayInput3Out","OverlayInput4","OverlayInput4In","OverlayInput4Out","OverlayInput5","OverlayInput5In","OverlayInput5Out","OverlayInput6","OverlayInput6In","OverlayInput6Out","OverlayInput7","OverlayInput7In","OverlayInput7Out","OverlayInput8","OverlayInput8In","OverlayInput8Out",
|
||||||
// Data sources / browser / scripts
|
// Data sources / browser / scripts
|
||||||
"DataSourceAutoNext","DataSourceAutoNextOff","DataSourceAutoNextOn","DataSourceNextRow","DataSourcePreviousRow","DataSourceSelectRow","BrowserBack","BrowserForward","BrowserHome","BrowserNavigate","BrowserReload","BrowserSetURL","BrowserExecuteJavascript","ScriptStart","ScriptStop","ScriptStartDynamic",
|
"DataSourceAutoNext","DataSourceAutoNextOff","DataSourceAutoNextOn","DataSourceNextRow","DataSourcePreviousRow","DataSourceSelectRow","BrowserBack","BrowserForward","BrowserHome","BrowserNavigate","BrowserReload","BrowserSetURL","BrowserExecuteJavascript","ScriptStart","ScriptStop","ScriptStartDynamic",
|
||||||
// NDI / OMT
|
// NDI / OMT
|
||||||
@@ -13889,7 +13904,7 @@ function renderHockeyPenaltyDashboard(node, component, runtime) {
|
|||||||
<span class="shortcut-target-index">${sideLabel} ${index + 1}</span>
|
<span class="shortcut-target-index">${sideLabel} ${index + 1}</span>
|
||||||
<label>Input<select data-penalty-target-side="${side}" data-penalty-target-id="${escapeHtml(target.id)}" data-penalty-target-field="input">${vmixInputOptions(target.input)}</select></label>
|
<label>Input<select data-penalty-target-side="${side}" data-penalty-target-id="${escapeHtml(target.id)}" data-penalty-target-field="input">${vmixInputOptions(target.input)}</select></label>
|
||||||
<label>Text / SelectedName<select data-penalty-target-side="${side}" data-penalty-target-id="${escapeHtml(target.id)}" data-penalty-target-field="selected_name">${vmixTextSelectedNameOptions(target.input, target.selected_name)}</select></label>
|
<label>Text / SelectedName<select data-penalty-target-side="${side}" data-penalty-target-id="${escapeHtml(target.id)}" data-penalty-target-field="selected_name">${vmixTextSelectedNameOptions(target.input, target.selected_name)}</select></label>
|
||||||
<label>Overlay<select data-penalty-target-side="${side}" data-penalty-target-id="${escapeHtml(target.id)}" data-penalty-target-field="overlay">${triggerSelectOptions([["1","Overlay 1"],["2","Overlay 2"],["3","Overlay 3"],["4","Overlay 4"]], target.overlay)}</select></label>
|
<label>Overlay<select data-penalty-target-side="${side}" data-penalty-target-id="${escapeHtml(target.id)}" data-penalty-target-field="overlay">${triggerSelectOptions([["1","Overlay 1"],["2","Overlay 2"],["3","Overlay 3"],["4","Overlay 4"],["5","Overlay 5"],["6","Overlay 6"],["7","Overlay 7"],["8","Overlay 8"]], target.overlay)}</select></label>
|
||||||
<label class="shortcut-inline-check"><input type="checkbox" data-penalty-target-side="${side}" data-penalty-target-id="${escapeHtml(target.id)}" data-penalty-target-field="auto_hide_on_finish" ${target.auto_hide_on_finish !== false ? "checked" : ""}> Снимать после последнего удаления</label>
|
<label class="shortcut-inline-check"><input type="checkbox" data-penalty-target-side="${side}" data-penalty-target-id="${escapeHtml(target.id)}" data-penalty-target-field="auto_hide_on_finish" ${target.auto_hide_on_finish !== false ? "checked" : ""}> Снимать после последнего удаления</label>
|
||||||
<button type="button" class="icon-btn danger" data-penalty-target-delete="${escapeHtml(target.id)}" data-penalty-target-side="${side}" title="Удалить слот">×</button>
|
<button type="button" class="icon-btn danger" data-penalty-target-delete="${escapeHtml(target.id)}" data-penalty-target-side="${side}" title="Удалить слот">×</button>
|
||||||
</div>`).join("");
|
</div>`).join("");
|
||||||
@@ -13911,7 +13926,7 @@ function renderHockeyPenaltyDashboard(node, component, runtime) {
|
|||||||
["away_penalty","Удаление AWAY завершено"],
|
["away_penalty","Удаление AWAY завершено"],
|
||||||
], action.source)}</select></label>
|
], action.source)}</select></label>
|
||||||
<label>Показать Input<select data-finish-action-id="${escapeHtml(action.id)}" data-finish-action-field="input">${vmixInputOptions(action.input)}</select></label>
|
<label>Показать Input<select data-finish-action-id="${escapeHtml(action.id)}" data-finish-action-field="input">${vmixInputOptions(action.input)}</select></label>
|
||||||
<label>Overlay<select data-finish-action-id="${escapeHtml(action.id)}" data-finish-action-field="overlay">${triggerSelectOptions([["1","Overlay 1"],["2","Overlay 2"],["3","Overlay 3"],["4","Overlay 4"]], action.overlay)}</select></label>
|
<label>Overlay<select data-finish-action-id="${escapeHtml(action.id)}" data-finish-action-field="overlay">${triggerSelectOptions([["1","Overlay 1"],["2","Overlay 2"],["3","Overlay 3"],["4","Overlay 4"],["5","Overlay 5"],["6","Overlay 6"],["7","Overlay 7"],["8","Overlay 8"]], action.overlay)}</select></label>
|
||||||
<label>Показывать, мс<input type="number" min="100" max="120000" step="100" data-finish-action-id="${escapeHtml(action.id)}" data-finish-action-field="duration_ms" value="${Number(action.duration_ms) || 3000}"></label>
|
<label>Показывать, мс<input type="number" min="100" max="120000" step="100" data-finish-action-id="${escapeHtml(action.id)}" data-finish-action-field="duration_ms" value="${Number(action.duration_ms) || 3000}"></label>
|
||||||
<label class="shortcut-inline-check"><input type="checkbox" data-finish-action-id="${escapeHtml(action.id)}" data-finish-action-field="only_when_side_clear" ${action.only_when_side_clear !== false ? "checked" : ""}> Только когда у команды больше нет удалений</label>
|
<label class="shortcut-inline-check"><input type="checkbox" data-finish-action-id="${escapeHtml(action.id)}" data-finish-action-field="only_when_side_clear" ${action.only_when_side_clear !== false ? "checked" : ""}> Только когда у команды больше нет удалений</label>
|
||||||
<button type="button" class="icon-btn danger" data-finish-action-delete="${escapeHtml(action.id)}" title="Удалить действие">×</button>
|
<button type="button" class="icon-btn danger" data-finish-action-delete="${escapeHtml(action.id)}" title="Удалить действие">×</button>
|
||||||
@@ -14221,7 +14236,11 @@ function renderHockeyPenaltyDashboard(node, component, runtime) {
|
|||||||
<label><span>Где работает</span><select data-sequence-scope>${triggerSelectOptions([["runtime","Runtime и предпросмотр"],["all","Также в редакторе"]], sequence.scope)}</select></label>
|
<label><span>Где работает</span><select data-sequence-scope>${triggerSelectOptions([["runtime","Runtime и предпросмотр"],["all","Также в редакторе"]], sequence.scope)}</select></label>
|
||||||
<label class="check-inline"><input type="checkbox" data-sequence-prevent ${sequence.prevent_default ? "checked" : ""}> блокировать действие браузера</label>
|
<label class="check-inline"><input type="checkbox" data-sequence-prevent ${sequence.prevent_default ? "checked" : ""}> блокировать действие браузера</label>
|
||||||
<label class="check-inline"><input type="checkbox" data-sequence-inputs ${sequence.allow_in_inputs ? "checked" : ""}> во время ввода</label>
|
<label class="check-inline"><input type="checkbox" data-sequence-inputs ${sequence.allow_in_inputs ? "checked" : ""}> во время ввода</label>
|
||||||
<label class="check-inline"><input type="checkbox" data-sequence-overlay-group ${sequence.toggle_all_overlays_on_repeat ? "checked" : ""}> повторное нажатие снимает все Overlay</label>
|
<label class="check-inline"><input type="checkbox" data-sequence-overlay-group ${sequence.toggle_all_overlays_on_repeat ? "checked" : ""}> повторное нажатие снимает выбранные Overlay</label>
|
||||||
|
<span class="check-inline" data-sequence-overlay-outs-wrap>
|
||||||
|
снять:
|
||||||
|
${[1, 2, 3, 4, 5, 6, 7, 8].map((number) => `<label class="check-inline"><input type="checkbox" data-sequence-overlay-out="${number}" ${(sequence.repeat_overlay_outs || [1, 2, 3]).includes(number) ? "checked" : ""}> O${number}</label>`).join("")}
|
||||||
|
</span>
|
||||||
<label class="check-inline"><input type="checkbox" data-sequence-scoreboard ${sequence.is_scoreboard_sequence ? "checked" : ""}> этот сценарий — верхний счёт</label>
|
<label class="check-inline"><input type="checkbox" data-sequence-scoreboard ${sequence.is_scoreboard_sequence ? "checked" : ""}> этот сценарий — верхний счёт</label>
|
||||||
<label class="check-inline"><input type="checkbox" data-sequence-team-states ${sequence.sync_hockey_team_states ? "checked" : ""}> добавлять состояния команд к верхнему счёту</label>
|
<label class="check-inline"><input type="checkbox" data-sequence-team-states ${sequence.sync_hockey_team_states ? "checked" : ""}> добавлять состояния команд к верхнему счёту</label>
|
||||||
</div>
|
</div>
|
||||||
@@ -14254,6 +14273,17 @@ function renderHockeyPenaltyDashboard(node, component, runtime) {
|
|||||||
sequence.toggle_all_overlays_on_repeat = event.target.checked;
|
sequence.toggle_all_overlays_on_repeat = event.target.checked;
|
||||||
if (!sequence.toggle_all_overlays_on_repeat) state.shortcutSequenceOverlayState.delete(sequence.id);
|
if (!sequence.toggle_all_overlays_on_repeat) state.shortcutSequenceOverlayState.delete(sequence.id);
|
||||||
});
|
});
|
||||||
|
card.querySelectorAll("[data-sequence-overlay-out]").forEach((input) => input.addEventListener("change", (event) => {
|
||||||
|
const selected = [...card.querySelectorAll("[data-sequence-overlay-out]:checked")]
|
||||||
|
.map((item) => Number(item.dataset.sequenceOverlayOut))
|
||||||
|
.filter((value) => Number.isInteger(value) && value >= 1 && value <= 8);
|
||||||
|
if (!selected.length) {
|
||||||
|
event.target.checked = true;
|
||||||
|
toast("Для повторного снятия выберите хотя бы один Overlay");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
sequence.repeat_overlay_outs = selected;
|
||||||
|
}));
|
||||||
card.querySelector("[data-sequence-scoreboard]").addEventListener("change", (event) => {
|
card.querySelector("[data-sequence-scoreboard]").addEventListener("change", (event) => {
|
||||||
sequence.is_scoreboard_sequence = event.target.checked;
|
sequence.is_scoreboard_sequence = event.target.checked;
|
||||||
});
|
});
|
||||||
@@ -14345,8 +14375,8 @@ function renderHockeyPenaltyDashboard(node, component, runtime) {
|
|||||||
const penaltyStep = createSequenceStep("vmix_command");
|
const penaltyStep = createSequenceStep("vmix_command");
|
||||||
Object.assign(penaltyStep, { function: "OverlayInput2In", condition: "has_penalties", label: "Если есть удаление — показать слой удаления" });
|
Object.assign(penaltyStep, { function: "OverlayInput2In", condition: "has_penalties", label: "Если есть удаление — показать слой удаления" });
|
||||||
const sequence = normalizeShortcutSequence({
|
const sequence = normalizeShortcutSequence({
|
||||||
id: uid(), name: "Счёт", description: "Показывает счёт; повторное нажатие снимает все Overlay.",
|
id: uid(), name: "Счёт", description: "Показывает счёт; повторное нажатие снимает выбранные Overlay (по умолчанию 1–3).",
|
||||||
enabled: true, combo: "F1", prevent_default: true, toggle_all_overlays_on_repeat: true, sync_hockey_team_states: true, is_scoreboard_sequence: true, scope: "runtime", steps: [scoreStep, penaltyStep],
|
enabled: true, combo: "F1", prevent_default: true, toggle_all_overlays_on_repeat: true, repeat_overlay_outs: [1, 2, 3], sync_hockey_team_states: true, is_scoreboard_sequence: true, scope: "runtime", steps: [scoreStep, penaltyStep],
|
||||||
}, state.config.shortcut_sequences.length);
|
}, state.config.shortcut_sequences.length);
|
||||||
state.config.shortcut_sequences.push(sequence);
|
state.config.shortcut_sequences.push(sequence);
|
||||||
state.shortcutSequenceOpenId = sequence.id;
|
state.shortcutSequenceOpenId = sequence.id;
|
||||||
|
|||||||
Reference in New Issue
Block a user