(function () { "use strict"; const webview = window.chrome && window.chrome.webview; const connection = document.getElementById("playout-connection-state"); const prepare = document.getElementById("playout-prepare"); const takeIn = document.getElementById("playout-take-in"); const next = document.getElementById("playout-next"); const takeOut = document.getElementById("playout-take-out"); const backgroundNone = document.getElementById("playout-background-none"); const backgroundFile = document.getElementById("playout-background-file"); const backgroundName = document.getElementById("playout-background-name"); const fadeDuration = document.getElementById("playout-fade-duration"); const status = document.getElementById("playout-status"); const controls = [prepare, takeIn, next, takeOut, backgroundNone, backgroundFile, fadeDuration]; let currentState = null; let localBusy = false; function hasOpenDialog() { return Array.from(document.querySelectorAll("[role='dialog'], [role='alertdialog']")) .some(function (dialog) { const container = dialog.closest("[hidden]"); return !container && !dialog.hidden; }); } function post(type, payload) { if (!webview || localBusy || hasOpenDialog()) return; localBusy = true; render(currentState); webview.postMessage({ type: type, payload: payload || {} }); } function setAllDisabled(disabled) { controls.forEach(function (control) { if (control) control.disabled = disabled; }); } function phaseLabel(phase) { if (phase === "prepared") return "PREPARED"; if (phase === "program") return "PROGRAM"; return "IDLE"; } function connectionLabel(playout) { if (!playout) return "미연결"; if (playout.outcomeUnknown) return "결과 불명확"; if (playout.mode === "disabled") return "송출 비활성"; if (playout.mode === "dryRun") return "DRY RUN"; if (playout.isConnected) return "Tornado2 연결됨"; if (playout.isProcessRunning) return "Tornado2 감지됨"; return "미연결"; } function pageLabel(playout) { if (!playout || playout.pageCount <= 0) return ""; return " · Page " + String(playout.pageIndexZeroBased + 1) + "/" + String(playout.pageCount); } function nextLabel(playout) { if (!playout || playout.nextKind === "none") return ""; if (playout.nextKind === "endOfPlaylist") return " · 마지막 항목"; return playout.nextKind === "pageNext" ? " · 다음 페이지" : " · 다음 항목"; } function canTakeInFromState(playout, hasPlaylist) { return playout.phase === "prepared" || hasPlaylist && (playout.phase === "idle" || playout.phase === "program"); } function canAdvanceFromState(playout) { return playout.phase === "program" && playout.nextKind !== "none" && playout.nextKind !== "endOfPlaylist"; } function statusState(playout, busy) { if (!playout) return "error"; if (playout.outcomeUnknown === true || playout.connectionState === "outcomeUnknown") { return "outcome-unknown"; } if (busy) return "busy"; if (playout.mode === "disabled" || playout.connectionState === "faulted" || playout.connectionState === "disposed") { return "error"; } if (playout.phase === "program") return "program"; if (playout.phase === "prepared") return "prepared"; return "idle"; } function render(state) { currentState = state || currentState; const playout = currentState && currentState.playout; const nativeBusy = !!(currentState && currentState.isBusy) || !!(playout && playout.isBusy); const busy = localBusy || nativeBusy; status.dataset.state = statusState(playout, busy); if (!playout) { connection.textContent = "미연결"; status.textContent = "송출 엔진 상태를 사용할 수 없습니다."; status.title = status.textContent; setAllDisabled(true); return; } const commandAvailable = playout.isCommandAvailable === true && playout.outcomeUnknown !== true && playout.isTakeOutCompletionPending !== true; const hasPlaylist = Array.isArray(currentState.playlist) && currentState.playlist.some(function (row) { return row.isEnabled === true; }); const liveTakeInAllowed = playout.mode !== "live" || playout.liveTakeInAllowed === true; const canPrepareToggle = playout.phase === "idle" ? hasPlaylist : playout.phase === "prepared" || playout.phase === "program"; const nextConnectionText = connectionLabel(playout); if (connection.textContent !== nextConnectionText) { connection.textContent = nextConnectionText; } connection.classList.toggle("connected", playout.isConnected === true); connection.classList.toggle("outcome-unknown", playout.outcomeUnknown === true); connection.classList.toggle("dry-run", playout.mode === "dryRun"); prepare.classList.toggle("active", playout.phase !== "idle"); prepare.disabled = busy || !commandAvailable || !canPrepareToggle || playout.isPlayCompletionPending === true; const canTakeIn = canTakeInFromState(playout, hasPlaylist); takeIn.disabled = busy || !commandAvailable || !liveTakeInAllowed || !canTakeIn || playout.isPlayCompletionPending === true; next.disabled = busy || !commandAvailable || !canAdvanceFromState(playout) || playout.isPlayCompletionPending === true; // MainForm's Escape path called StopAll whenever the Tornado player existed, // even if its local m_TakeIn flag had already drifted back to IDLE. takeOut.disabled = busy || !commandAvailable; backgroundNone.disabled = busy || playout.canChangeBackground !== true; backgroundFile.disabled = busy || playout.canChangeBackground !== true; // MainForm.Designer keeps panel3 (which owns ComboDi) invisible. Preserve // the native fade value as read-only state, but never expose an operator // interaction path from the WebView shell. fadeDuration.disabled = true; backgroundNone.checked = playout.backgroundEnabled !== true; backgroundName.value = playout.backgroundEnabled === true ? (playout.backgroundFileName || "선택한 배경") : "배경없음"; fadeDuration.value = String(playout.fadeDuration); const scene = playout.onAirCode || playout.preparedCode; const sceneLabel = scene ? " · " + scene : ""; const refresh = playout.refreshActive ? " · 자동 갱신 " + String(playout.refreshCompletedCount) + "회" : ""; status.textContent = phaseLabel(playout.phase) + sceneLabel + pageLabel(playout) + nextLabel(playout) + refresh + (playout.message ? " · " + playout.message : ""); status.title = status.textContent; } prepare.addEventListener("click", function () { post("prepare-playout", {}); }); takeIn.addEventListener("click", function () { post("take-in", {}); }); next.addEventListener("click", function () { post("next-playout", {}); }); takeOut.addEventListener("click", function () { post("take-out", {}); }); backgroundFile.addEventListener("click", function () { post("choose-background", {}); }); backgroundNone.addEventListener("change", function () { post("toggle-background", {}); }); document.addEventListener("keydown", function (event) { if (event.defaultPrevented) return; const legacyShortcut = event.key === "F2" || event.key === "F3" || event.key === "F8" || event.key === "Escape"; if (hasOpenDialog()) { // ShowDialog/MessageBox own these keys. Consume browser defaults (notably // Chromium's F3 Find Next) without invoking a MainForm playout shortcut. if (legacyShortcut) event.preventDefault(); return; } if (event.repeat) return; if (event.key === "F2") { event.preventDefault(); if (!backgroundFile.disabled) backgroundFile.click(); } else if (event.key === "F3") { event.preventDefault(); if (!backgroundNone.disabled) backgroundNone.click(); } else if (event.key === "F8") { event.preventDefault(); if (!takeIn.disabled) takeIn.click(); } else if (event.key === "Escape") { event.preventDefault(); if (!takeOut.disabled) takeOut.click(); } }); if (webview) { webview.addEventListener("message", function (event) { if (event.data && event.data.type === "state" && event.data.payload) { localBusy = false; render(event.data.payload); } }); } else { setAllDisabled(true); status.dataset.state = "error"; status.textContent = "WebView2 송출 브리지를 사용할 수 없습니다."; status.title = status.textContent; } }());