feat: restore legacy modal and drag behavior
This commit is contained in:
@@ -34,6 +34,14 @@
|
||||
const namedPlaylistDeleteButton = document.getElementById("named-playlist-delete-button");
|
||||
const namedPlaylistCancelButton = document.getElementById("named-playlist-cancel-button");
|
||||
const namedPlaylistConfirmButton = document.getElementById("named-playlist-confirm-button");
|
||||
const namedPlaylistConfirmationModal = document.getElementById(
|
||||
"named-playlist-confirmation");
|
||||
const namedPlaylistConfirmationMessage = document.getElementById(
|
||||
"named-playlist-confirmation-message");
|
||||
const namedPlaylistConfirmationYes = document.getElementById(
|
||||
"named-playlist-confirmation-yes");
|
||||
const namedPlaylistConfirmationNo = document.getElementById(
|
||||
"named-playlist-confirmation-no");
|
||||
const manualButtons = Array.from(document.querySelectorAll("button[data-manual-screen]"));
|
||||
const manualModal = document.getElementById("manual-financial-modal");
|
||||
const manualTitle = document.getElementById("manual-financial-title");
|
||||
@@ -73,9 +81,33 @@
|
||||
const operatorCatalogDelete = document.getElementById("operator-catalog-delete");
|
||||
const operatorCatalogCancel = document.getElementById("operator-catalog-cancel");
|
||||
const operatorCatalogSave = document.getElementById("operator-catalog-save");
|
||||
const namedPlaylistDialog = namedPlaylistModal.querySelector("[role='dialog']");
|
||||
const namedPlaylistConfirmationDialog = namedPlaylistConfirmationModal.querySelector(
|
||||
"[role='alertdialog']");
|
||||
const manualDialog = manualModal.querySelector("[role='dialog']");
|
||||
const manualListDialog = manualListModal.querySelector("[role='dialog']");
|
||||
const operatorCatalogDialog = operatorCatalogModal.querySelector("[role='dialog']");
|
||||
const legacyAlertDialog = dialog.querySelector("[role='alertdialog']");
|
||||
if (!window.LegacyModalFocus || typeof window.LegacyModalFocus.create !== "function") {
|
||||
throw new Error("Legacy modal focus support was not loaded.");
|
||||
}
|
||||
if (!window.LegacyCutDrag ||
|
||||
typeof window.LegacyCutDrag.createDragRectangle !== "function" ||
|
||||
typeof window.LegacyCutDrag.shouldStart !== "function" ||
|
||||
typeof window.LegacyCutDrag.toCssDragSize !== "function") {
|
||||
throw new Error("Legacy cut drag support was not loaded.");
|
||||
}
|
||||
if (!window.LegacyNamedPlaylistSelection ||
|
||||
typeof window.LegacyNamedPlaylistSelection.create !== "function") {
|
||||
throw new Error("Legacy named-playlist selection support was not loaded.");
|
||||
}
|
||||
const modalFocusManager = window.LegacyModalFocus.create({ document: document });
|
||||
const namedPlaylistSelection = window.LegacyNamedPlaylistSelection.create();
|
||||
modalFocusManager.start();
|
||||
let uiBusy = false;
|
||||
let namedPlaylistMode = null;
|
||||
let namedPlaylistState = null;
|
||||
let namedPlaylistConfirmation = null;
|
||||
let manualDraft = null;
|
||||
let manualDraftKey = "";
|
||||
let operatorCatalogState = null;
|
||||
@@ -86,10 +118,10 @@
|
||||
let comparisonTargetClickTimer = null;
|
||||
let manualViResultClickTimer = null;
|
||||
let operatorCatalogStockClickTimer = null;
|
||||
let namedPlaylistResultClickTimer = null;
|
||||
let pendingNamedPlaylistCommand = null;
|
||||
let nextNamedPlaylistRequestId = 1;
|
||||
let lastCommandSequence = 0;
|
||||
const selectedCutsDragToken = "legacy-selected-cuts";
|
||||
let operatorMutationLocked = true;
|
||||
const playlistDragMime = "text/x-mbn-playlist-row";
|
||||
const operatorCatalogDragMime = "text/x-mbn-operator-catalog-row";
|
||||
let draggedPlaylistRowId = null;
|
||||
@@ -97,6 +129,8 @@
|
||||
let playlistDragBlockedRowId = null;
|
||||
let playlistMutationLocked = false;
|
||||
let cutSelectionEnabled = false;
|
||||
let cutDragSize = { width: 4, height: 4 };
|
||||
let cutDragGesture = null;
|
||||
let lastPresentedPlayoutRowKey = "";
|
||||
let draggedTabId = null;
|
||||
let tabDragLastTargetId = null;
|
||||
@@ -106,9 +140,80 @@
|
||||
let operatorCatalogMutationLocked = false;
|
||||
let renderedTreeTabId = null;
|
||||
let treeInteractionLocked = false;
|
||||
let treeFocusOwnedBeforeRender = false;
|
||||
const treeUiStateByTab = new Map();
|
||||
const pendingTreeResetTabs = new Set();
|
||||
|
||||
function preferredModalFocus(modal) {
|
||||
if (modal === legacyAlertDialog) return dialogOk;
|
||||
if (modal === namedPlaylistConfirmationDialog) return namedPlaylistConfirmationYes;
|
||||
if (modal === namedPlaylistDialog) {
|
||||
return namedPlaylistDefinitions.querySelector("button[aria-selected='true']") ||
|
||||
namedPlaylistDefinitions.querySelector("button") || namedPlaylistNewTitle;
|
||||
}
|
||||
if (modal === manualDialog) {
|
||||
return manualWorkspace.querySelector("button.selected[data-manual-result-id]") ||
|
||||
manualWorkspace.querySelector("button[data-manual-result-id]") ||
|
||||
manualWorkspace.querySelector(".manual-financial-list-header");
|
||||
}
|
||||
if (modal === manualListDialog) {
|
||||
return manualListWorkspace.querySelector("input:not([disabled])") ||
|
||||
manualListWorkspace.querySelector("button:not([disabled])");
|
||||
}
|
||||
if (modal === operatorCatalogDialog) {
|
||||
if (!operatorCatalogEditor.hidden && !operatorCatalogName.disabled) {
|
||||
return operatorCatalogName;
|
||||
}
|
||||
return operatorCatalogResults.querySelector("button[aria-selected='true']") ||
|
||||
operatorCatalogResults.querySelector("button") || operatorCatalogQuery;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function createModalOpenerReference() {
|
||||
const opener = document.activeElement;
|
||||
if (!renderedTreeTabId || !categoryTree.contains(opener)) return opener;
|
||||
|
||||
captureTreeUiState(renderedTreeTabId);
|
||||
const node = opener.closest("[data-tree-node-key]");
|
||||
const descriptor = {
|
||||
tabId: renderedTreeTabId,
|
||||
controlId: opener.id || null,
|
||||
nodeKey: node ? node.dataset.treeNodeKey : null,
|
||||
selectionStart: Number.isInteger(opener.selectionStart) ? opener.selectionStart : null,
|
||||
selectionEnd: Number.isInteger(opener.selectionEnd) ? opener.selectionEnd : null
|
||||
};
|
||||
return function resolveTreeModalOpener() {
|
||||
if (descriptor.tabId !== renderedTreeTabId) return null;
|
||||
let resolved = descriptor.controlId
|
||||
? document.getElementById(descriptor.controlId) : null;
|
||||
if (!resolved && descriptor.nodeKey) {
|
||||
resolved = findTreeNodeByKey(descriptor.nodeKey);
|
||||
}
|
||||
if (!resolved || !categoryTree.contains(resolved) || resolved.disabled === true) {
|
||||
return null;
|
||||
}
|
||||
if (Number.isInteger(descriptor.selectionStart) &&
|
||||
Number.isInteger(descriptor.selectionEnd) &&
|
||||
typeof resolved.setSelectionRange === "function") {
|
||||
try {
|
||||
resolved.setSelectionRange(descriptor.selectionStart, descriptor.selectionEnd);
|
||||
} catch (_) {
|
||||
// A recreated non-text control may expose, but reject, selection ranges.
|
||||
}
|
||||
}
|
||||
return resolved;
|
||||
};
|
||||
}
|
||||
|
||||
function rememberModalOpener(modal) {
|
||||
if (modal) modalFocusManager.rememberOpener(modal, createModalOpenerReference());
|
||||
}
|
||||
|
||||
function syncModalFocus() {
|
||||
modalFocusManager.sync(preferredModalFocus);
|
||||
}
|
||||
|
||||
function activeTreeTabId(state) {
|
||||
if (!state || !Array.isArray(state.tabs)) return null;
|
||||
const active = state.tabs.find(function (tab) { return tab.isActive === true; });
|
||||
@@ -214,19 +319,33 @@
|
||||
});
|
||||
const selected = categoryTree.querySelector(
|
||||
"[data-tree-node-key].tree-node-selected");
|
||||
const active = categoryTree.contains(document.activeElement)
|
||||
? document.activeElement.closest("[data-tree-node-key]")
|
||||
const activeElement = categoryTree.contains(document.activeElement)
|
||||
? document.activeElement
|
||||
: null;
|
||||
const active = activeElement
|
||||
? activeElement.closest("[data-tree-node-key]")
|
||||
: null;
|
||||
const controlFocus = activeElement && activeElement.id
|
||||
? {
|
||||
id: activeElement.id,
|
||||
selectionStart: Number.isInteger(activeElement.selectionStart)
|
||||
? activeElement.selectionStart : null,
|
||||
selectionEnd: Number.isInteger(activeElement.selectionEnd)
|
||||
? activeElement.selectionEnd : null
|
||||
}
|
||||
: null;
|
||||
treeUiStateByTab.set(tabId, {
|
||||
openSections: openSections,
|
||||
selectedKey: selected ? selected.dataset.treeNodeKey : previous.selectedKey || null,
|
||||
focusKey: active ? active.dataset.treeNodeKey : null,
|
||||
focusKey: active ? active.dataset.treeNodeKey : previous.focusKey || null,
|
||||
controlFocus: activeElement ? controlFocus : previous.controlFocus || null,
|
||||
scrollTop: categoryTree.scrollTop
|
||||
});
|
||||
}
|
||||
|
||||
function beginTreeRender(state) {
|
||||
const nextTabId = activeTreeTabId(state);
|
||||
treeFocusOwnedBeforeRender = categoryTree.contains(document.activeElement);
|
||||
if (renderedTreeTabId) captureTreeUiState(renderedTreeTabId);
|
||||
if (nextTabId !== renderedTreeTabId) {
|
||||
renderedTreeTabId = nextTabId;
|
||||
@@ -250,7 +369,7 @@
|
||||
syncTreeSectionExpanded(section);
|
||||
});
|
||||
const firstRoot = roots[0];
|
||||
selectTreeNode(firstRoot, true);
|
||||
selectTreeNode(firstRoot, !modalFocusManager.getActiveModal());
|
||||
if (typeof firstRoot.scrollIntoView === "function") {
|
||||
firstRoot.scrollIntoView({ block: "nearest", inline: "nearest" });
|
||||
}
|
||||
@@ -278,15 +397,41 @@
|
||||
}
|
||||
const selected = findTreeNodeByKey(saved.selectedKey);
|
||||
if (selected) selectTreeNode(selected, false);
|
||||
const canRestoreFocus = treeFocusOwnedBeforeRender &&
|
||||
!modalFocusManager.getActiveModal();
|
||||
const focused = findTreeNodeByKey(saved.focusKey);
|
||||
if (focused && typeof focused.focus === "function") {
|
||||
if (canRestoreFocus && focused && typeof focused.focus === "function") {
|
||||
focused.focus({ preventScroll: true });
|
||||
}
|
||||
if (canRestoreFocus && saved.controlFocus && saved.controlFocus.id) {
|
||||
const control = document.getElementById(saved.controlFocus.id);
|
||||
if (control && categoryTree.contains(control) && control.disabled !== true &&
|
||||
typeof control.focus === "function") {
|
||||
control.focus({ preventScroll: true });
|
||||
if (Number.isInteger(saved.controlFocus.selectionStart) &&
|
||||
Number.isInteger(saved.controlFocus.selectionEnd) &&
|
||||
typeof control.setSelectionRange === "function") {
|
||||
try {
|
||||
control.setSelectionRange(
|
||||
saved.controlFocus.selectionStart,
|
||||
saved.controlFocus.selectionEnd);
|
||||
} catch (_) {
|
||||
// Non-text controls can expose selection properties without accepting a range.
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (Number.isFinite(saved.scrollTop)) categoryTree.scrollTop = saved.scrollTop;
|
||||
}
|
||||
|
||||
function send(type, payload) {
|
||||
if (webview && !uiBusy) webview.postMessage({ type: type, payload: payload || {} });
|
||||
if (!webview || uiBusy) return false;
|
||||
try {
|
||||
webview.postMessage({ type: type, payload: payload || {} });
|
||||
return true;
|
||||
} catch (_) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function isOperatorMutationLocked(state) {
|
||||
@@ -491,11 +636,94 @@
|
||||
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))
|
||||
x: event.clientX - bounds.left,
|
||||
y: event.clientY - bounds.top
|
||||
};
|
||||
}
|
||||
|
||||
function pointInsideElement(element, event) {
|
||||
if (!element || typeof element.getBoundingClientRect !== "function") return false;
|
||||
const bounds = element.getBoundingClientRect();
|
||||
return event.clientX >= bounds.left && event.clientX < bounds.right &&
|
||||
event.clientY >= bounds.top && event.clientY < bounds.bottom;
|
||||
}
|
||||
|
||||
function clearCutDragGesture(releaseCapture) {
|
||||
const gesture = cutDragGesture;
|
||||
if (!gesture) return;
|
||||
cutDragGesture = null;
|
||||
gesture.element.classList.remove("dragging");
|
||||
playlistDropZone.classList.remove("cut-drag-over");
|
||||
if (releaseCapture !== false &&
|
||||
typeof gesture.element.hasPointerCapture === "function" &&
|
||||
gesture.element.hasPointerCapture(gesture.pointerId) &&
|
||||
typeof gesture.element.releasePointerCapture === "function") {
|
||||
try {
|
||||
gesture.element.releasePointerCapture(gesture.pointerId);
|
||||
} catch (_) {
|
||||
// The browser may release capture between the key event and cleanup.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function beginCutDragGesture(element, event, position) {
|
||||
clearCutDragGesture();
|
||||
if (event.button !== 0) return;
|
||||
cutDragGesture = {
|
||||
element: element,
|
||||
pointerId: event.pointerId,
|
||||
down: position,
|
||||
rectangle: window.LegacyCutDrag.createDragRectangle(position, cutDragSize),
|
||||
started: false
|
||||
};
|
||||
if (typeof element.setPointerCapture === "function") {
|
||||
try {
|
||||
element.setPointerCapture(event.pointerId);
|
||||
} catch (_) {
|
||||
// Pointer capture can fail if the WebView has already released it.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function onCutPointerMove(event) {
|
||||
const gesture = cutDragGesture;
|
||||
if (!gesture || gesture.element !== event.currentTarget ||
|
||||
gesture.pointerId !== event.pointerId) return;
|
||||
if ((event.buttons & 1) !== 1) {
|
||||
clearCutDragGesture();
|
||||
return;
|
||||
}
|
||||
if (!gesture.started &&
|
||||
window.LegacyCutDrag.shouldStart(gesture.rectangle, pointerPosition(event))) {
|
||||
gesture.started = true;
|
||||
gesture.element.classList.add("dragging");
|
||||
}
|
||||
if (gesture.started) {
|
||||
event.preventDefault();
|
||||
playlistDropZone.classList.toggle(
|
||||
"cut-drag-over",
|
||||
!playlistMutationLocked && pointInsideElement(playlistDropZone, event));
|
||||
}
|
||||
}
|
||||
|
||||
function onCutPointerUp(event) {
|
||||
const gesture = cutDragGesture;
|
||||
if (!gesture || gesture.element !== event.currentTarget ||
|
||||
gesture.pointerId !== event.pointerId) return;
|
||||
const shouldDrop = gesture.started && !playlistMutationLocked &&
|
||||
pointInsideElement(playlistDropZone, event);
|
||||
clearCutDragGesture();
|
||||
if (shouldDrop) send("drop-selected-cuts", {});
|
||||
}
|
||||
|
||||
function onCutPointerCancel(event) {
|
||||
const gesture = cutDragGesture;
|
||||
if (gesture && gesture.element === event.currentTarget &&
|
||||
gesture.pointerId === event.pointerId) {
|
||||
clearCutDragGesture(event.type !== "lostpointercapture");
|
||||
}
|
||||
}
|
||||
|
||||
function legacyCutKey(key) {
|
||||
return {
|
||||
ArrowUp: "arrow-up",
|
||||
@@ -533,7 +761,7 @@
|
||||
element.setAttribute("role", "option");
|
||||
element.dataset.physicalIndex = String(row.physicalIndex);
|
||||
element.tabIndex = -1;
|
||||
element.draggable = true;
|
||||
element.draggable = false;
|
||||
|
||||
const number = document.createElement("span");
|
||||
number.className = "cut-number";
|
||||
@@ -551,11 +779,18 @@
|
||||
physicalIndex: row.physicalIndex,
|
||||
control: event.ctrlKey,
|
||||
shift: event.shiftKey,
|
||||
x: position.x,
|
||||
y: position.y,
|
||||
// C# retains integer coordinates only for the native double-click
|
||||
// equality contract. Drag geometry keeps the raw fractional CSS point.
|
||||
x: Math.round(position.x),
|
||||
y: Math.round(position.y),
|
||||
timestampMilliseconds: Math.max(0, Math.round(event.timeStamp))
|
||||
});
|
||||
beginCutDragGesture(element, event, position);
|
||||
});
|
||||
element.addEventListener("pointermove", onCutPointerMove);
|
||||
element.addEventListener("pointerup", onCutPointerUp);
|
||||
element.addEventListener("pointercancel", onCutPointerCancel);
|
||||
element.addEventListener("lostpointercapture", onCutPointerCancel);
|
||||
element.addEventListener("keydown", function (event) {
|
||||
const key = legacyCutKey(event.key);
|
||||
if (!key) return;
|
||||
@@ -572,14 +807,6 @@
|
||||
shift: event.shiftKey
|
||||
});
|
||||
});
|
||||
element.addEventListener("dragstart", function (event) {
|
||||
if (playlistMutationLocked || !event.dataTransfer) {
|
||||
event.preventDefault();
|
||||
return;
|
||||
}
|
||||
event.dataTransfer.effectAllowed = "move";
|
||||
event.dataTransfer.setData("text/plain", selectedCutsDragToken);
|
||||
});
|
||||
return element;
|
||||
}
|
||||
|
||||
@@ -592,6 +819,10 @@
|
||||
// renderCuts precedes renderPlaylist, so publish the same lock before a
|
||||
// cut row can initiate an append drag for this state revision.
|
||||
playlistMutationLocked = cutDragLocked;
|
||||
if (!cutSelectionEnabled || cutDragLocked ||
|
||||
(cutDragGesture && !cutList.contains(cutDragGesture.element))) {
|
||||
clearCutDragGesture();
|
||||
}
|
||||
state.cutRows.forEach(function (row, index) {
|
||||
let element = cutList.children.item(index);
|
||||
if (!element || element.dataset.physicalIndex !== String(row.physicalIndex)) {
|
||||
@@ -605,18 +836,24 @@
|
||||
|
||||
element.className = "cut-row" +
|
||||
(row.isSelected ? " selected" : "") +
|
||||
(row.isSeparator ? " separator" : "");
|
||||
(row.isSeparator ? " separator" : "") +
|
||||
(cutDragGesture && cutDragGesture.element === element && cutDragGesture.started
|
||||
? " dragging" : "");
|
||||
element.setAttribute("aria-selected", row.isSelected ? "true" : "false");
|
||||
element.setAttribute("aria-disabled", state.isBusy === true ? "true" : "false");
|
||||
element.tabIndex = row.isFocused === true || (focusedCutIndex < 0 && index === 0)
|
||||
? 0
|
||||
: -1;
|
||||
element.draggable = !cutDragLocked;
|
||||
element.draggable = false;
|
||||
element.dataset.cutDragEnabled = cutSelectionEnabled ? "true" : "false";
|
||||
});
|
||||
|
||||
while (cutList.children.length > state.cutRows.length) {
|
||||
cutList.lastElementChild.remove();
|
||||
}
|
||||
if (cutDragGesture && !cutList.contains(cutDragGesture.element)) {
|
||||
clearCutDragGesture();
|
||||
}
|
||||
}
|
||||
|
||||
cutList.addEventListener("pointerdown", function (event) {
|
||||
@@ -1772,6 +2009,7 @@
|
||||
return;
|
||||
}
|
||||
|
||||
if (manualModal.hidden) rememberModalOpener(manualDialog);
|
||||
manualModal.hidden = false;
|
||||
manualTitle.textContent = manual.label + " · GraphE 수동 입력";
|
||||
const detailKey = manual.screen + "|" + (manual.selectedRowId || "new") + "|" +
|
||||
@@ -1914,6 +2152,7 @@
|
||||
return;
|
||||
}
|
||||
|
||||
if (manualListModal.hidden) rememberModalOpener(manualListDialog);
|
||||
manualListModal.hidden = false;
|
||||
const busy = state.isBusy === true;
|
||||
if (manual.screen !== "vi") {
|
||||
@@ -2144,24 +2383,210 @@
|
||||
}
|
||||
|
||||
function openNamedPlaylist(mode) {
|
||||
if (operatorMutationLocked) return false;
|
||||
namedPlaylistSelection.cancel();
|
||||
namedPlaylistMode = mode;
|
||||
namedPlaylistTitle.textContent = mode === "save"
|
||||
? "DB 재생목록 저장" : "DB 재생목록 불러오기";
|
||||
namedPlaylistConfirmButton.textContent = mode === "save" ? "현재 목록 저장" : "불러오기";
|
||||
namedPlaylistNewTitle.value = "";
|
||||
if (namedPlaylistModal.hidden) rememberModalOpener(namedPlaylistDialog);
|
||||
namedPlaylistModal.hidden = false;
|
||||
if (namedPlaylistState) {
|
||||
renderNamedPlaylist({ namedPlaylist: namedPlaylistState, isBusy: uiBusy });
|
||||
}
|
||||
send("refresh-named-playlists", {});
|
||||
syncModalFocus();
|
||||
beginNamedPlaylistBusyCycle("refresh-named-playlists", {});
|
||||
return true;
|
||||
}
|
||||
|
||||
function closeNamedPlaylist() {
|
||||
clearDelayedClick(namedPlaylistResultClickTimer);
|
||||
namedPlaylistResultClickTimer = null;
|
||||
pendingNamedPlaylistCommand = null;
|
||||
if (isNamedPlaylistModalLocked() || namedPlaylistSelection.isPending()) return false;
|
||||
namedPlaylistMode = null;
|
||||
namedPlaylistModal.hidden = true;
|
||||
syncModalFocus();
|
||||
return true;
|
||||
}
|
||||
|
||||
function isNamedPlaylistModalLocked() {
|
||||
return uiBusy || pendingNamedPlaylistCommand !== null ||
|
||||
namedPlaylistConfirmation !== null ||
|
||||
!!(namedPlaylistState && namedPlaylistState.isWriteInProgress === true);
|
||||
}
|
||||
|
||||
function isNamedPlaylistActionLocked() {
|
||||
return isNamedPlaylistModalLocked() || operatorMutationLocked ||
|
||||
namedPlaylistSelection.isPending();
|
||||
}
|
||||
|
||||
function isNamedPlaylistDefinitionLocked() {
|
||||
return operatorMutationLocked || pendingNamedPlaylistCommand !== null ||
|
||||
namedPlaylistConfirmation !== null ||
|
||||
!!(namedPlaylistState && namedPlaylistState.isWriteInProgress === true) ||
|
||||
(uiBusy && !namedPlaylistSelection.isPending());
|
||||
}
|
||||
|
||||
function markNamedPlaylistVisualSelection(definitionId) {
|
||||
namedPlaylistDefinitions
|
||||
.querySelectorAll("button[data-named-playlist-definition-id]")
|
||||
.forEach(function (button) {
|
||||
const selected = button.dataset.namedPlaylistDefinitionId === definitionId;
|
||||
button.setAttribute("aria-selected", selected ? "true" : "false");
|
||||
button.classList.toggle("selected", selected);
|
||||
});
|
||||
}
|
||||
|
||||
function lockNamedPlaylistSelectionActions() {
|
||||
namedPlaylistClose.disabled = true;
|
||||
namedPlaylistCancelButton.disabled = true;
|
||||
namedPlaylistNewTitle.disabled = true;
|
||||
namedPlaylistCreateButton.disabled = true;
|
||||
namedPlaylistDeleteButton.disabled = true;
|
||||
namedPlaylistConfirmButton.disabled = true;
|
||||
}
|
||||
|
||||
function beginNamedPlaylistSelection(definitionId) {
|
||||
if (namedPlaylistSelection.isPending()) {
|
||||
const pending = namedPlaylistSelection.begin(
|
||||
definitionId,
|
||||
namedPlaylistState && namedPlaylistState.selectedDefinitionId);
|
||||
if (pending.accepted) markNamedPlaylistVisualSelection(definitionId);
|
||||
return pending.accepted;
|
||||
}
|
||||
if (isNamedPlaylistModalLocked() || operatorMutationLocked) return false;
|
||||
|
||||
const requestId = "named-ui-" + String(nextNamedPlaylistRequestId++);
|
||||
const started = namedPlaylistSelection.begin(
|
||||
definitionId,
|
||||
namedPlaylistState && namedPlaylistState.selectedDefinitionId,
|
||||
requestId,
|
||||
lastCommandSequence + 1);
|
||||
if (!started.accepted) return false;
|
||||
markNamedPlaylistVisualSelection(definitionId);
|
||||
if (!started.shouldSend) return true;
|
||||
|
||||
lockNamedPlaylistSelectionActions();
|
||||
if (!send("select-named-playlist", {
|
||||
definitionId: definitionId,
|
||||
requestId: started.requestId
|
||||
})) {
|
||||
namedPlaylistSelection.cancel();
|
||||
restoreNamedPlaylistAfterConfirmation();
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
function runNamedPlaylistDoubleClick(definitionId, mode) {
|
||||
if (mode === "save") {
|
||||
return openNamedPlaylistConfirmation({
|
||||
kind: "save",
|
||||
definitionId: definitionId,
|
||||
message: "저장하겠습니까?",
|
||||
showSavedAlert: false,
|
||||
closeOnNo: true
|
||||
});
|
||||
}
|
||||
return beginNamedPlaylistCommand("load-named-playlist-by-id", definitionId, false);
|
||||
}
|
||||
|
||||
function lockNamedPlaylistControls() {
|
||||
namedPlaylistModal.querySelectorAll("button, input").forEach(function (control) {
|
||||
control.disabled = true;
|
||||
});
|
||||
}
|
||||
|
||||
function beginNamedPlaylistBusyCycle(type, payload) {
|
||||
if (isNamedPlaylistActionLocked()) return false;
|
||||
const requestId = "named-ui-" + String(nextNamedPlaylistRequestId++);
|
||||
pendingNamedPlaylistCommand = {
|
||||
command: type,
|
||||
targetId: requestId,
|
||||
minimumSequence: lastCommandSequence + 1,
|
||||
closeOnSuccess: false,
|
||||
showSavedAlert: false
|
||||
};
|
||||
const correlatedPayload = Object.assign({}, payload || {}, { requestId: requestId });
|
||||
if (!send(type, correlatedPayload)) {
|
||||
pendingNamedPlaylistCommand = null;
|
||||
return false;
|
||||
}
|
||||
lockNamedPlaylistControls();
|
||||
return true;
|
||||
}
|
||||
|
||||
function beginNamedPlaylistCommand(command, definitionId, showSavedAlert) {
|
||||
if (isNamedPlaylistActionLocked() || !definitionId) return false;
|
||||
pendingNamedPlaylistCommand = {
|
||||
command: command,
|
||||
targetId: definitionId,
|
||||
definitionId: definitionId,
|
||||
minimumSequence: lastCommandSequence + 1,
|
||||
closeOnSuccess: true,
|
||||
showSavedAlert: showSavedAlert === true
|
||||
};
|
||||
if (!send(command, { definitionId: definitionId })) {
|
||||
pendingNamedPlaylistCommand = null;
|
||||
return false;
|
||||
}
|
||||
lockNamedPlaylistControls();
|
||||
return true;
|
||||
}
|
||||
|
||||
function isMatchingNamedPlaylistReceipt(busy, pending, commandResult) {
|
||||
const exact = !!pending && !!commandResult &&
|
||||
Number.isSafeInteger(commandResult.sequence) &&
|
||||
commandResult.sequence >= pending.minimumSequence &&
|
||||
commandResult.command === pending.command &&
|
||||
commandResult.targetId === pending.targetId;
|
||||
return exact && (commandResult.succeeded === false ||
|
||||
(busy !== true && commandResult.succeeded === true));
|
||||
}
|
||||
|
||||
function openNamedPlaylistConfirmation(options) {
|
||||
if (!options || isNamedPlaylistActionLocked()) return false;
|
||||
namedPlaylistConfirmation = options;
|
||||
namedPlaylistConfirmationMessage.textContent = options.message;
|
||||
if (namedPlaylistConfirmationModal.hidden) {
|
||||
rememberModalOpener(namedPlaylistConfirmationDialog);
|
||||
}
|
||||
namedPlaylistConfirmationModal.hidden = false;
|
||||
lockNamedPlaylistControls();
|
||||
syncModalFocus();
|
||||
return true;
|
||||
}
|
||||
|
||||
function restoreNamedPlaylistAfterConfirmation() {
|
||||
if (!namedPlaylistState || namedPlaylistModal.hidden) return;
|
||||
renderNamedPlaylist({
|
||||
namedPlaylist: namedPlaylistState,
|
||||
isBusy: uiBusy,
|
||||
commandResult: null,
|
||||
statusKind: null
|
||||
});
|
||||
}
|
||||
|
||||
function resolveNamedPlaylistConfirmation(confirmed) {
|
||||
const confirmation = namedPlaylistConfirmation;
|
||||
if (!confirmation) return false;
|
||||
namedPlaylistConfirmation = null;
|
||||
namedPlaylistConfirmationModal.hidden = true;
|
||||
syncModalFocus();
|
||||
|
||||
if (!confirmed) {
|
||||
if (confirmation.closeOnNo === true) closeNamedPlaylist();
|
||||
else restoreNamedPlaylistAfterConfirmation();
|
||||
return true;
|
||||
}
|
||||
|
||||
const started = confirmation.kind === "delete"
|
||||
? beginNamedPlaylistBusyCycle("delete-selected-named-playlist", {})
|
||||
: beginNamedPlaylistCommand(
|
||||
"save-current-named-playlist-to",
|
||||
confirmation.definitionId,
|
||||
confirmation.showSavedAlert === true);
|
||||
if (!started) restoreNamedPlaylistAfterConfirmation();
|
||||
return started;
|
||||
}
|
||||
|
||||
function renderNamedPlaylist(state) {
|
||||
@@ -2169,32 +2594,49 @@
|
||||
namedPlaylistState = named || null;
|
||||
const busy = state.isBusy === true;
|
||||
const commandResult = state.commandResult;
|
||||
const selectionResult = namedPlaylistSelection.reconcile(
|
||||
commandResult,
|
||||
named && named.selectedDefinitionId);
|
||||
const deferredSelectionAction = selectionResult.deferredAction;
|
||||
if (commandResult && Number.isSafeInteger(commandResult.sequence)) {
|
||||
lastCommandSequence = Math.max(lastCommandSequence, commandResult.sequence);
|
||||
}
|
||||
if (!busy && pendingNamedPlaylistCommand && commandResult &&
|
||||
commandResult.sequence >= pendingNamedPlaylistCommand.minimumSequence &&
|
||||
commandResult.command === pendingNamedPlaylistCommand.command &&
|
||||
commandResult.targetId === pendingNamedPlaylistCommand.definitionId) {
|
||||
if (isMatchingNamedPlaylistReceipt(
|
||||
busy,
|
||||
pendingNamedPlaylistCommand,
|
||||
commandResult
|
||||
)) {
|
||||
const completed = pendingNamedPlaylistCommand;
|
||||
const succeeded = commandResult.succeeded === true;
|
||||
pendingNamedPlaylistCommand = null;
|
||||
if (succeeded) {
|
||||
if (succeeded && completed.closeOnSuccess) {
|
||||
if (completed.showSavedAlert) window.alert("저장되었습니다");
|
||||
closeNamedPlaylist();
|
||||
}
|
||||
}
|
||||
dbLoadButton.disabled = busy || !named;
|
||||
dbSaveButton.disabled = busy || !named || named.canMutate !== true;
|
||||
dbLoadButton.disabled = operatorMutationLocked || busy || !named;
|
||||
dbSaveButton.disabled = operatorMutationLocked || busy || !named ||
|
||||
named.canMutate !== true;
|
||||
if (!named || namedPlaylistModal.hidden) return;
|
||||
|
||||
const modalLocked = isNamedPlaylistActionLocked();
|
||||
const definitionLocked = isNamedPlaylistDefinitionLocked();
|
||||
const existingDefinitions = new Map();
|
||||
namedPlaylistDefinitions
|
||||
.querySelectorAll("button[data-named-playlist-definition-id]")
|
||||
.forEach(function (button) {
|
||||
existingDefinitions.set(button.dataset.namedPlaylistDefinitionId, button);
|
||||
});
|
||||
const fragment = document.createDocumentFragment();
|
||||
named.definitions.forEach(function (definition) {
|
||||
const button = document.createElement("button");
|
||||
const button = existingDefinitions.get(definition.definitionId) ||
|
||||
document.createElement("button");
|
||||
button.type = "button";
|
||||
button.dataset.namedPlaylistDefinitionId = definition.definitionId;
|
||||
button.setAttribute("role", "option");
|
||||
button.setAttribute("aria-selected", definition.isSelected ? "true" : "false");
|
||||
button.className = definition.isSelected ? "selected" : "";
|
||||
button.disabled = busy || named.isWriteInProgress === true;
|
||||
button.disabled = definitionLocked;
|
||||
button.textContent = definition.title;
|
||||
fragment.appendChild(button);
|
||||
});
|
||||
@@ -2212,11 +2654,24 @@
|
||||
namedPlaylistStatus.classList.toggle("error", named.isWriteQuarantined === true ||
|
||||
named.statusKind === "error");
|
||||
|
||||
namedPlaylistCreateButton.disabled = busy || named.canCreate !== true ||
|
||||
const closeLocked = isNamedPlaylistModalLocked() ||
|
||||
namedPlaylistSelection.isPending();
|
||||
namedPlaylistClose.disabled = closeLocked;
|
||||
namedPlaylistCancelButton.disabled = closeLocked;
|
||||
namedPlaylistNewTitle.disabled = modalLocked;
|
||||
namedPlaylistCreateButton.disabled = modalLocked || named.canCreate !== true ||
|
||||
!isCanonicalNamedPlaylistTitle(namedPlaylistNewTitle.value);
|
||||
namedPlaylistDeleteButton.disabled = busy || named.canDelete !== true;
|
||||
namedPlaylistConfirmButton.disabled = busy ||
|
||||
namedPlaylistDeleteButton.disabled = modalLocked || named.canDelete !== true;
|
||||
namedPlaylistConfirmButton.disabled = modalLocked ||
|
||||
(namedPlaylistMode === "save" ? named.canSave !== true : named.canLoad !== true);
|
||||
namedPlaylistConfirmationYes.disabled = operatorMutationLocked || uiBusy ||
|
||||
pendingNamedPlaylistCommand !== null ||
|
||||
!!(namedPlaylistState && namedPlaylistState.isWriteInProgress === true);
|
||||
if (deferredSelectionAction) {
|
||||
runNamedPlaylistDoubleClick(
|
||||
deferredSelectionAction.definitionId,
|
||||
deferredSelectionAction.mode);
|
||||
}
|
||||
}
|
||||
|
||||
function clearOperatorCatalogDragVisuals() {
|
||||
@@ -2329,6 +2784,7 @@
|
||||
clearOperatorCatalogDragVisuals();
|
||||
}
|
||||
|
||||
if (operatorCatalogModal.hidden) rememberModalOpener(operatorCatalogDialog);
|
||||
operatorCatalogModal.hidden = false;
|
||||
const isTheme = catalog.entity === "theme";
|
||||
const editing = catalog.mode === "create" || catalog.mode === "edit";
|
||||
@@ -2512,22 +2968,46 @@
|
||||
function renderDialog(state) {
|
||||
if (state.dialog && state.dialog.message) {
|
||||
dialogMessage.textContent = state.dialog.message;
|
||||
if (dialog.hidden) rememberModalOpener(legacyAlertDialog);
|
||||
dialog.hidden = false;
|
||||
dialogOk.focus();
|
||||
} else {
|
||||
dialog.hidden = true;
|
||||
}
|
||||
}
|
||||
|
||||
function updateCutDragSize(state) {
|
||||
const metrics = state && state.interactionMetrics;
|
||||
if (!metrics || metrics.coordinateSpace !== "host-dip" ||
|
||||
!Number.isInteger(metrics.cutDragWidthDips) ||
|
||||
!Number.isInteger(metrics.cutDragHeightDips) ||
|
||||
metrics.cutDragWidthDips <= 0 || metrics.cutDragHeightDips <= 0 ||
|
||||
typeof metrics.hostRasterizationScale !== "number") return;
|
||||
const nextDragSize = window.LegacyCutDrag.toCssDragSize({
|
||||
width: metrics.cutDragWidthDips,
|
||||
height: metrics.cutDragHeightDips
|
||||
}, metrics.hostRasterizationScale, window.devicePixelRatio);
|
||||
cutDragSize = nextDragSize;
|
||||
if (cutDragGesture && !cutDragGesture.started) {
|
||||
// The cut-pointer-down reply is also a native state boundary. If a
|
||||
// Windows preference change raced the previous rendered state, update
|
||||
// the still-pending gesture before its next pointermove decision.
|
||||
cutDragGesture.rectangle = window.LegacyCutDrag.createDragRectangle(
|
||||
cutDragGesture.down,
|
||||
nextDragSize);
|
||||
}
|
||||
}
|
||||
|
||||
function render(state) {
|
||||
const isBusy = state.isBusy === true;
|
||||
const treeTabId = beginTreeRender(state);
|
||||
uiBusy = isBusy;
|
||||
operatorMutationLocked = isOperatorMutationLocked(state);
|
||||
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 || "";
|
||||
updateCutDragSize(state);
|
||||
renderStockResults(state);
|
||||
renderCuts(state);
|
||||
renderTabs(state);
|
||||
@@ -2550,6 +3030,7 @@
|
||||
renderPlaylist(state);
|
||||
renderNamedPlaylist(state);
|
||||
renderDialog(state);
|
||||
syncModalFocus();
|
||||
status.textContent = isBusy ? "종목을 검색하고 있습니다." : (state.statusMessage || "");
|
||||
}
|
||||
|
||||
@@ -2576,36 +3057,25 @@
|
||||
const definition = event.target.closest("button[data-named-playlist-definition-id]");
|
||||
if (!definition || definition.disabled) return;
|
||||
event.preventDefault();
|
||||
clearDelayedClick(namedPlaylistResultClickTimer);
|
||||
namedPlaylistResultClickTimer = null;
|
||||
const definitionId = definition.dataset.namedPlaylistDefinitionId;
|
||||
if (namedPlaylistMode === "save" &&
|
||||
!window.confirm("선택한 DB 재생목록에 현재 목록을 저장하시겠습니까?")) {
|
||||
if (namedPlaylistSelection.isPending()) {
|
||||
namedPlaylistSelection.defer(definitionId, {
|
||||
definitionId: definitionId,
|
||||
mode: namedPlaylistMode
|
||||
});
|
||||
return;
|
||||
}
|
||||
const command = namedPlaylistMode === "save"
|
||||
? "save-current-named-playlist-to" : "load-named-playlist-by-id";
|
||||
pendingNamedPlaylistCommand = {
|
||||
command: command,
|
||||
definitionId: definitionId,
|
||||
minimumSequence: lastCommandSequence + 1
|
||||
};
|
||||
send(command, { definitionId: definitionId });
|
||||
runNamedPlaylistDoubleClick(definitionId, namedPlaylistMode);
|
||||
});
|
||||
namedPlaylistDefinitions.addEventListener("click", function (event) {
|
||||
const definition = event.target.closest("button[data-named-playlist-definition-id]");
|
||||
if (definition && !definition.disabled) {
|
||||
clearDelayedClick(namedPlaylistResultClickTimer);
|
||||
namedPlaylistResultClickTimer = window.setTimeout(function () {
|
||||
namedPlaylistResultClickTimer = null;
|
||||
send("select-named-playlist", {
|
||||
definitionId: definition.dataset.namedPlaylistDefinitionId
|
||||
});
|
||||
}, 250);
|
||||
beginNamedPlaylistSelection(definition.dataset.namedPlaylistDefinitionId);
|
||||
}
|
||||
});
|
||||
namedPlaylistNewTitle.addEventListener("input", function () {
|
||||
namedPlaylistCreateButton.disabled = uiBusy || !namedPlaylistState ||
|
||||
namedPlaylistCreateButton.disabled = isNamedPlaylistActionLocked() ||
|
||||
!namedPlaylistState ||
|
||||
namedPlaylistState.canCreate !== true ||
|
||||
!isCanonicalNamedPlaylistTitle(namedPlaylistNewTitle.value);
|
||||
});
|
||||
@@ -2618,23 +3088,42 @@
|
||||
namedPlaylistCreateButton.addEventListener("click", function () {
|
||||
const title = namedPlaylistNewTitle.value;
|
||||
if (isCanonicalNamedPlaylistTitle(title)) {
|
||||
send("create-named-playlist", { title: title });
|
||||
beginNamedPlaylistBusyCycle("create-named-playlist", { title: title });
|
||||
}
|
||||
});
|
||||
namedPlaylistDeleteButton.addEventListener("click", function () {
|
||||
const title = namedPlaylistState && namedPlaylistState.selectedTitle
|
||||
? "'" + namedPlaylistState.selectedTitle + "'" : "선택한 재생목록";
|
||||
if (window.confirm(title + "을(를) DB에서 삭제하겠습니까?")) {
|
||||
send("delete-selected-named-playlist", {});
|
||||
}
|
||||
openNamedPlaylistConfirmation({
|
||||
kind: "delete",
|
||||
message: "프로그램이 삭제됩니다. 계속 하시겠습니까?",
|
||||
closeOnNo: false
|
||||
});
|
||||
});
|
||||
namedPlaylistConfirmButton.addEventListener("click", function () {
|
||||
const definitionId = namedPlaylistState && namedPlaylistState.selectedDefinitionId;
|
||||
if (!definitionId) return;
|
||||
if (namedPlaylistMode === "save") {
|
||||
send("save-current-named-playlist", {});
|
||||
openNamedPlaylistConfirmation({
|
||||
kind: "save",
|
||||
definitionId: definitionId,
|
||||
message: "저장하겠습니까?",
|
||||
showSavedAlert: true,
|
||||
closeOnNo: true
|
||||
});
|
||||
} else if (namedPlaylistMode === "load") {
|
||||
send("load-selected-named-playlist", {});
|
||||
beginNamedPlaylistCommand(
|
||||
"load-named-playlist-by-id",
|
||||
definitionId,
|
||||
false);
|
||||
}
|
||||
});
|
||||
namedPlaylistConfirmationYes.addEventListener("click", function () {
|
||||
if (!namedPlaylistConfirmationYes.disabled) {
|
||||
resolveNamedPlaylistConfirmation(true);
|
||||
}
|
||||
});
|
||||
namedPlaylistConfirmationNo.addEventListener("click", function () {
|
||||
resolveNamedPlaylistConfirmation(false);
|
||||
});
|
||||
|
||||
manualButtons.forEach(function (button) {
|
||||
button.addEventListener("click", function () {
|
||||
@@ -2962,31 +3451,6 @@
|
||||
searchOperatorCatalog();
|
||||
});
|
||||
|
||||
function hasDragType(dataTransfer, expectedType) {
|
||||
return !!dataTransfer && Array.from(dataTransfer.types || [])
|
||||
.some(function (type) { return type === expectedType; });
|
||||
}
|
||||
|
||||
function isSelectedCutsDrop(dataTransfer) {
|
||||
return hasDragType(dataTransfer, "text/plain") &&
|
||||
dataTransfer.getData("text/plain") === selectedCutsDragToken;
|
||||
}
|
||||
|
||||
playlistDropZone.addEventListener("dragover", function (event) {
|
||||
// A playlist row also drops inside this ancestor. Only advertise the
|
||||
// drop zone for the cut-list payload; otherwise the bubbled row drop
|
||||
// would append the still-selected cut as an extra playlist entry.
|
||||
if (playlistMutationLocked ||
|
||||
!hasDragType(event.dataTransfer, "text/plain")) return;
|
||||
event.preventDefault();
|
||||
if (event.dataTransfer) event.dataTransfer.dropEffect = "move";
|
||||
});
|
||||
playlistDropZone.addEventListener("drop", function (event) {
|
||||
if (playlistMutationLocked || !isSelectedCutsDrop(event.dataTransfer)) return;
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
send("drop-selected-cuts", {});
|
||||
});
|
||||
playlistMoveUp.addEventListener("click", function () {
|
||||
send("move-selected-playlist-rows", { direction: "up" });
|
||||
});
|
||||
@@ -3420,13 +3884,31 @@
|
||||
operatorCatalogDragBlockedRowId = null;
|
||||
});
|
||||
document.addEventListener("keydown", function (event) {
|
||||
if (event.key === "Delete" && !event.repeat && dialog.hidden && manualModal.hidden &&
|
||||
manualListModal.hidden && namedPlaylistModal.hidden && operatorCatalogModal.hidden) {
|
||||
const cutDragShortcut = event.key === "Escape" || event.key === "F2" ||
|
||||
event.key === "F3" || event.key === "F8";
|
||||
if (cutDragGesture && cutDragGesture.started && cutDragShortcut) {
|
||||
// Native DoDragDrop runs its own modal loop. MainForm shortcuts cannot run
|
||||
// until that loop ends; Escape cancels the drag without issuing TAKE OUT.
|
||||
event.preventDefault();
|
||||
if (event.key === "Escape") clearCutDragGesture();
|
||||
} else if (event.key === "Escape" && modalFocusManager.getActiveModal()) {
|
||||
// The legacy child forms did not assign a CancelButton and none handled
|
||||
// Escape. ShowDialog isolated MainForm, so Escape cannot fall through to
|
||||
// MainForm's TAKE OUT shortcut. The one-button MessageBox is the exception:
|
||||
// its Escape is equivalent to acknowledging OK.
|
||||
event.preventDefault();
|
||||
if (!event.repeat && modalFocusManager.getActiveModal() === legacyAlertDialog) {
|
||||
send("dismiss-dialog", {});
|
||||
}
|
||||
} else if (event.key === "Delete" && !event.repeat && dialog.hidden && manualModal.hidden &&
|
||||
manualListModal.hidden && namedPlaylistModal.hidden &&
|
||||
namedPlaylistConfirmationModal.hidden && operatorCatalogModal.hidden) {
|
||||
// MainForm.KeyPreview invoked playlist deletion but did not suppress the key,
|
||||
// so an editor that owns focus must still perform its normal text deletion.
|
||||
if (!playlistMutationLocked) send("delete-selected-playlist-rows", {});
|
||||
} else if (event.key === " " && !event.repeat && dialog.hidden && manualModal.hidden &&
|
||||
manualListModal.hidden && namedPlaylistModal.hidden && operatorCatalogModal.hidden &&
|
||||
manualListModal.hidden && namedPlaylistModal.hidden &&
|
||||
namedPlaylistConfirmationModal.hidden && operatorCatalogModal.hidden &&
|
||||
event.target.closest(".playlist-data-row")) {
|
||||
event.preventDefault();
|
||||
if (!playlistMutationLocked) {
|
||||
@@ -3439,26 +3921,11 @@
|
||||
}
|
||||
} else if ((event.key === "Home" || event.key === "End") &&
|
||||
!event.repeat && dialog.hidden && manualModal.hidden && manualListModal.hidden &&
|
||||
namedPlaylistModal.hidden && operatorCatalogModal.hidden) {
|
||||
namedPlaylistModal.hidden && namedPlaylistConfirmationModal.hidden &&
|
||||
operatorCatalogModal.hidden) {
|
||||
// MainForm handled its playlist/timer side effect and then returned the
|
||||
// base key result, so a focused textbox/list control still receives Home/End.
|
||||
selectPlaylistBoundary(event.key === "Home" ? "first" : "last");
|
||||
} else if (event.key === "Escape" && !event.repeat && !namedPlaylistModal.hidden) {
|
||||
event.preventDefault();
|
||||
closeNamedPlaylist();
|
||||
} else if (event.key === "Escape" && !event.repeat && !manualModal.hidden) {
|
||||
event.preventDefault();
|
||||
clearDelayedClick(manualResultClickTimer);
|
||||
manualResultClickTimer = null;
|
||||
send("close-manual-financial", {});
|
||||
} else if (event.key === "Escape" && !event.repeat && !manualListModal.hidden) {
|
||||
event.preventDefault();
|
||||
send("close-manual-list", {});
|
||||
} else if (event.key === "Escape" && !event.repeat && !operatorCatalogModal.hidden) {
|
||||
event.preventDefault();
|
||||
clearDelayedClick(operatorCatalogStockClickTimer);
|
||||
operatorCatalogStockClickTimer = null;
|
||||
send("close-operator-catalog", {});
|
||||
}
|
||||
});
|
||||
dialogOk.addEventListener("click", function () {
|
||||
|
||||
Reference in New Issue
Block a user