feat: establish legacy parity migration baseline

This commit is contained in:
2026-07-15 07:10:01 +09:00
parent e468f43886
commit f14800656b
36 changed files with 3166 additions and 0 deletions

View 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 브리지를 사용할 수 없습니다.";
}
}());