feat: establish legacy parity migration baseline
This commit is contained in:
232
src/MBN_STOCK_WEBVIEW.LegacyParityApp/Web/app.js
Normal file
232
src/MBN_STOCK_WEBVIEW.LegacyParityApp/Web/app.js
Normal file
@@ -0,0 +1,232 @@
|
||||
(function () {
|
||||
"use strict";
|
||||
|
||||
const webview = window.chrome && window.chrome.webview;
|
||||
const searchInput = document.getElementById("stock-search");
|
||||
const searchButton = document.getElementById("stock-search-button");
|
||||
const stockResults = document.getElementById("stock-results");
|
||||
const cutList = document.getElementById("cut-list");
|
||||
const playlistRows = document.getElementById("playlist-rows");
|
||||
const playlistDropZone = document.getElementById("playlist-drop-zone");
|
||||
const status = document.getElementById("operator-status");
|
||||
const dialog = document.getElementById("legacy-dialog");
|
||||
const dialogMessage = document.getElementById("legacy-dialog-message");
|
||||
const dialogOk = document.getElementById("legacy-dialog-ok");
|
||||
let uiBusy = false;
|
||||
|
||||
function send(type, payload) {
|
||||
if (webview && !uiBusy) webview.postMessage({ type: type, payload: payload || {} });
|
||||
}
|
||||
|
||||
function createStockResultElement(row) {
|
||||
const button = document.createElement("button");
|
||||
button.type = "button";
|
||||
button.dataset.resultIndex = String(row.index);
|
||||
button.setAttribute("role", "option");
|
||||
button.addEventListener("click", function () {
|
||||
send("select-stock", { resultIndex: Number(button.dataset.resultIndex) });
|
||||
});
|
||||
return button;
|
||||
}
|
||||
|
||||
function renderStockResults(state) {
|
||||
state.searchResults.forEach(function (row, position) {
|
||||
let button = stockResults.children.item(position);
|
||||
if (!button || button.dataset.resultIndex !== String(row.index)) {
|
||||
button = createStockResultElement(row);
|
||||
if (position < stockResults.children.length) {
|
||||
stockResults.replaceChild(button, stockResults.children.item(position));
|
||||
} else {
|
||||
stockResults.appendChild(button);
|
||||
}
|
||||
}
|
||||
|
||||
button.className = "stock-result" +
|
||||
(state.selectedStockIndex === row.index ? " selected" : "");
|
||||
button.setAttribute("aria-disabled", state.isBusy === true ? "true" : "false");
|
||||
button.textContent = row.displayName;
|
||||
button.setAttribute("aria-selected", state.selectedStockIndex === row.index ? "true" : "false");
|
||||
});
|
||||
|
||||
while (stockResults.children.length > state.searchResults.length) {
|
||||
stockResults.lastElementChild.remove();
|
||||
}
|
||||
}
|
||||
|
||||
function pointerPosition(event) {
|
||||
const bounds = cutList.getBoundingClientRect();
|
||||
return {
|
||||
x: Math.max(0, Math.round(event.clientX - bounds.left)),
|
||||
y: Math.max(0, Math.round(event.clientY - bounds.top))
|
||||
};
|
||||
}
|
||||
|
||||
function createCutElement(row) {
|
||||
const element = document.createElement("div");
|
||||
element.setAttribute("role", "option");
|
||||
element.dataset.physicalIndex = String(row.physicalIndex);
|
||||
element.draggable = true;
|
||||
|
||||
const number = document.createElement("span");
|
||||
number.className = "cut-number";
|
||||
number.textContent = row.displayOrdinal === null ? "" : String(row.displayOrdinal);
|
||||
const label = document.createElement("span");
|
||||
label.textContent = row.rawLabel;
|
||||
element.append(number, label);
|
||||
|
||||
element.addEventListener("pointerdown", function (event) {
|
||||
const position = pointerPosition(event);
|
||||
send("cut-pointer-down", {
|
||||
physicalIndex: row.physicalIndex,
|
||||
control: event.ctrlKey,
|
||||
shift: event.shiftKey,
|
||||
x: position.x,
|
||||
y: position.y,
|
||||
timestampMilliseconds: Math.max(0, Math.round(event.timeStamp))
|
||||
});
|
||||
});
|
||||
element.addEventListener("dragstart", function (event) {
|
||||
if (event.dataTransfer) {
|
||||
event.dataTransfer.effectAllowed = "move";
|
||||
event.dataTransfer.setData("text/plain", "legacy-selected-cuts");
|
||||
}
|
||||
});
|
||||
return element;
|
||||
}
|
||||
|
||||
function renderCuts(state) {
|
||||
state.cutRows.forEach(function (row, index) {
|
||||
let element = cutList.children.item(index);
|
||||
if (!element || element.dataset.physicalIndex !== String(row.physicalIndex)) {
|
||||
element = createCutElement(row);
|
||||
if (index < cutList.children.length) {
|
||||
cutList.replaceChild(element, cutList.children.item(index));
|
||||
} else {
|
||||
cutList.appendChild(element);
|
||||
}
|
||||
}
|
||||
|
||||
element.className = "cut-row" +
|
||||
(row.isSelected ? " selected" : "") +
|
||||
(row.isSeparator ? " separator" : "");
|
||||
element.setAttribute("aria-selected", row.isSelected ? "true" : "false");
|
||||
element.setAttribute("aria-disabled", state.isBusy === true ? "true" : "false");
|
||||
element.draggable = state.isBusy !== true;
|
||||
});
|
||||
|
||||
while (cutList.children.length > state.cutRows.length) {
|
||||
cutList.lastElementChild.remove();
|
||||
}
|
||||
}
|
||||
|
||||
function createPlaylistElement(row) {
|
||||
const element = document.createElement("div");
|
||||
element.className = "playlist-row playlist-data-row";
|
||||
element.dataset.rowId = row.rowId;
|
||||
|
||||
const enabledCell = document.createElement("div");
|
||||
enabledCell.className = "enabled-cell";
|
||||
const enabled = document.createElement("input");
|
||||
enabled.type = "checkbox";
|
||||
enabled.addEventListener("change", function () {
|
||||
send("set-playlist-enabled", {
|
||||
rowId: element.dataset.rowId,
|
||||
enabled: enabled.checked
|
||||
});
|
||||
});
|
||||
enabledCell.appendChild(enabled);
|
||||
element.appendChild(enabledCell);
|
||||
|
||||
for (let index = 0; index < 5; index += 1) {
|
||||
element.appendChild(document.createElement("div"));
|
||||
}
|
||||
return element;
|
||||
}
|
||||
|
||||
function renderPlaylist(state) {
|
||||
state.playlist.forEach(function (row, position) {
|
||||
let element = playlistRows.children.item(position);
|
||||
if (!element || element.dataset.rowId !== row.rowId) {
|
||||
element = createPlaylistElement(row);
|
||||
if (position < playlistRows.children.length) {
|
||||
playlistRows.replaceChild(element, playlistRows.children.item(position));
|
||||
} else {
|
||||
playlistRows.appendChild(element);
|
||||
}
|
||||
}
|
||||
|
||||
const enabled = element.querySelector("input");
|
||||
enabled.checked = row.isEnabled;
|
||||
enabled.setAttribute("aria-disabled", state.isBusy === true ? "true" : "false");
|
||||
[row.marketText, row.stockName, row.graphicType, row.subtype, row.pageText]
|
||||
.forEach(function (text, cellIndex) {
|
||||
const cell = element.children.item(cellIndex + 1);
|
||||
cell.textContent = text;
|
||||
cell.title = text;
|
||||
});
|
||||
});
|
||||
|
||||
while (playlistRows.children.length > state.playlist.length) {
|
||||
playlistRows.lastElementChild.remove();
|
||||
}
|
||||
}
|
||||
|
||||
function renderDialog(state) {
|
||||
if (state.dialog && state.dialog.message) {
|
||||
dialogMessage.textContent = state.dialog.message;
|
||||
dialog.hidden = false;
|
||||
dialogOk.focus();
|
||||
} else {
|
||||
dialog.hidden = true;
|
||||
}
|
||||
}
|
||||
|
||||
function render(state) {
|
||||
const isBusy = state.isBusy === true;
|
||||
uiBusy = isBusy;
|
||||
document.body.classList.toggle("busy", isBusy);
|
||||
document.body.setAttribute("aria-busy", isBusy ? "true" : "false");
|
||||
searchInput.readOnly = isBusy;
|
||||
searchButton.setAttribute("aria-disabled", isBusy ? "true" : "false");
|
||||
if (document.activeElement !== searchInput) searchInput.value = state.searchText || "";
|
||||
renderStockResults(state);
|
||||
renderCuts(state);
|
||||
renderPlaylist(state);
|
||||
renderDialog(state);
|
||||
status.textContent = isBusy ? "종목을 검색하고 있습니다." : (state.statusMessage || "");
|
||||
}
|
||||
|
||||
searchButton.addEventListener("click", function () {
|
||||
send("search-stocks", { text: searchInput.value, trigger: "button" });
|
||||
});
|
||||
|
||||
searchInput.addEventListener("keypress", function (event) {
|
||||
if (event.key === "Enter") {
|
||||
event.preventDefault();
|
||||
send("search-stocks", { text: searchInput.value, trigger: "enter" });
|
||||
}
|
||||
});
|
||||
|
||||
playlistDropZone.addEventListener("dragover", function (event) {
|
||||
event.preventDefault();
|
||||
if (event.dataTransfer) event.dataTransfer.dropEffect = "move";
|
||||
});
|
||||
playlistDropZone.addEventListener("drop", function (event) {
|
||||
event.preventDefault();
|
||||
send("drop-selected-cuts", {});
|
||||
});
|
||||
dialogOk.addEventListener("click", function () {
|
||||
send("dismiss-dialog", {});
|
||||
});
|
||||
|
||||
if (webview) {
|
||||
webview.addEventListener("message", function (event) {
|
||||
if (event.data && event.data.type === "state" && event.data.payload) {
|
||||
render(event.data.payload);
|
||||
}
|
||||
});
|
||||
send("ready", {});
|
||||
} else {
|
||||
status.textContent = "WebView2 브리지를 사용할 수 없습니다.";
|
||||
}
|
||||
}());
|
||||
Reference in New Issue
Block a user