настройки Mapping
This commit is contained in:
@@ -134,6 +134,10 @@
|
||||
shortcutInventory: { device_id: "", device_name: "", online: false, vmix_connected: false, inventory: { inputs: [] }, devices: [] },
|
||||
shortcutInventoryLoading: false,
|
||||
shortcutEditorScrollTop: 0,
|
||||
// BUILD162: an async vMix inventory response must never rewrite Mapping
|
||||
// controls after the operator has started editing the settings modal.
|
||||
shortcutEditorInteractionRevision: 0,
|
||||
shortcutInventoryRefreshDeferred: false,
|
||||
preparedTitles: [],
|
||||
preparedTitleInventory: { device_id: "", device_name: "", inventory: { inputs: [] } },
|
||||
preparedTitlesLoading: false,
|
||||
@@ -1331,6 +1335,15 @@
|
||||
popup.remove();
|
||||
anchor?.classList.remove("control-popover-open");
|
||||
activeControlPopover = null;
|
||||
|
||||
// BUILD162: if vMix inventory arrived while Mapping was being edited,
|
||||
// apply it only after the open popover is gone. This keeps the current
|
||||
// dropdown usable without leaving the inventory permanently stale.
|
||||
const editor = document.querySelector(".shortcut-sequence-editor");
|
||||
if (editor?.isConnected && state.shortcutInventoryRefreshDeferred && !shortcutEditorHasActiveInteraction(editor)) {
|
||||
state.shortcutInventoryRefreshDeferred = false;
|
||||
refreshShortcutEditorInventoryControls(editor, { force: true });
|
||||
}
|
||||
}
|
||||
|
||||
function positionControlPopover(anchor, popup) {
|
||||
@@ -15242,8 +15255,17 @@ function renderHockeyPenaltyDashboard(node, component, runtime) {
|
||||
if (!target) return;
|
||||
target[field] = control.type === "checkbox" ? control.checked : control.value;
|
||||
if (field === "input") {
|
||||
// BUILD162: changing the Mapping Input must not rebuild the whole
|
||||
// shortcut card. A full rerender destroys the styled popover,
|
||||
// closes the menu and makes the operator lose the current context.
|
||||
target.selected_name = "";
|
||||
rerender();
|
||||
const row = control.closest("[data-penalty-target-row]");
|
||||
const selectedName = row?.querySelector('[data-penalty-target-field="selected_name"]');
|
||||
if (selectedName) {
|
||||
selectedName.innerHTML = vmixTextSelectedNameOptions(target.input, "");
|
||||
selectedName.value = "";
|
||||
refreshEnhancedControl(selectedName);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -15442,8 +15464,26 @@ function renderHockeyPenaltyDashboard(node, component, runtime) {
|
||||
return true;
|
||||
}
|
||||
|
||||
function refreshShortcutEditorInventoryControls(editor) {
|
||||
function markShortcutEditorInteraction() {
|
||||
state.shortcutEditorInteractionRevision += 1;
|
||||
}
|
||||
|
||||
function shortcutEditorHasActiveInteraction(editor) {
|
||||
if (!editor?.isConnected) return false;
|
||||
if (activeControlPopover?.anchor?.isConnected && editor.contains(activeControlPopover.anchor)) return true;
|
||||
const active = document.activeElement;
|
||||
if (!active || !editor.contains(active)) return false;
|
||||
// A focused text field can safely coexist with a vMix inventory refresh;
|
||||
// only a native select/popover interaction is sensitive to option rebuilds.
|
||||
return Boolean(active.matches?.("select, .css-select-trigger"));
|
||||
}
|
||||
|
||||
function refreshShortcutEditorInventoryControls(editor, { force = false } = {}) {
|
||||
if (!editor?.isConnected) return;
|
||||
if (!force && shortcutEditorHasActiveInteraction(editor)) {
|
||||
state.shortcutInventoryRefreshDeferred = true;
|
||||
return;
|
||||
}
|
||||
|
||||
const status = editor.querySelector("[data-vmix-inventory-status]");
|
||||
if (status) status.textContent = shortcutInventoryLabel();
|
||||
@@ -15542,6 +15582,24 @@ function renderHockeyPenaltyDashboard(node, component, runtime) {
|
||||
const modalCard = editor?.closest(".modal-card");
|
||||
const searchInput = editor.querySelector("[data-shortcut-search]");
|
||||
|
||||
// BUILD162: remember that the operator has started interacting with Mapping.
|
||||
// A late vMix inventory response may update state, but it must not rewrite
|
||||
// the controls while the operator is choosing an Input/Text/Overlay.
|
||||
editor.addEventListener("pointerdown", (event) => {
|
||||
if (event.target?.closest?.("input, textarea, select, button, .css-select-trigger")) {
|
||||
markShortcutEditorInteraction();
|
||||
}
|
||||
}, true);
|
||||
editor.addEventListener("focusin", (event) => {
|
||||
if (event.target?.matches?.("input, textarea, select, button")) markShortcutEditorInteraction();
|
||||
}, true);
|
||||
editor.addEventListener("input", (event) => {
|
||||
if (event.target?.matches?.("input, textarea, select")) markShortcutEditorInteraction();
|
||||
}, true);
|
||||
editor.addEventListener("change", (event) => {
|
||||
if (event.target?.matches?.("input, textarea, select")) markShortcutEditorInteraction();
|
||||
}, true);
|
||||
|
||||
const render = ({ focusId = "" } = {}) => {
|
||||
const previousScroll = modalCard?.scrollTop ?? state.shortcutEditorScrollTop ?? 0;
|
||||
const list = editor.querySelector(".shortcut-sequence-list");
|
||||
@@ -15604,16 +15662,28 @@ function renderHockeyPenaltyDashboard(node, component, runtime) {
|
||||
});
|
||||
editor.querySelector("[data-open-shortcut-reference]").addEventListener("click", showShortcutsReference);
|
||||
editor.querySelector("[data-refresh-vmix-inventory]").addEventListener("click", async () => {
|
||||
// Explicit refresh is an operator action, so it is allowed to rebuild
|
||||
// the inventory after the request. The active control itself is still
|
||||
// protected by refreshShortcutInventorySelect().
|
||||
await loadShortcutInventory();
|
||||
refreshShortcutEditorInventoryControls(editor);
|
||||
state.shortcutInventoryRefreshDeferred = false;
|
||||
refreshShortcutEditorInventoryControls(editor, { force: true });
|
||||
});
|
||||
editor.querySelector("[data-save-shortcuts]").addEventListener("click", () => saveConfig());
|
||||
editor.querySelector("[data-close-shortcuts]").addEventListener("click", closeModal);
|
||||
render();
|
||||
const inventoryInteractionRevision = state.shortcutEditorInteractionRevision;
|
||||
loadShortcutInventory({ silent: true }).then(() => {
|
||||
// BUILD160: do not replace the whole settings DOM after the async vMix
|
||||
// inventory request completes. Replacing it can close an already-open
|
||||
// dropdown and visually restore defaults while the operator is editing.
|
||||
// BUILD160/162: inventory may arrive after the modal is already in use.
|
||||
// Never rewrite Mapping controls if the operator has interacted since
|
||||
// the request started. The current DOM/state remains authoritative for
|
||||
// this editing session; the next opening gets a fresh inventory.
|
||||
if (!editor.isConnected) return;
|
||||
if (state.shortcutEditorInteractionRevision !== inventoryInteractionRevision || shortcutEditorHasActiveInteraction(editor)) {
|
||||
state.shortcutInventoryRefreshDeferred = true;
|
||||
return;
|
||||
}
|
||||
state.shortcutInventoryRefreshDeferred = false;
|
||||
refreshShortcutEditorInventoryControls(editor);
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user