Files
DemonGatanjieu-Anomalous_Mo…/web/modules/shortcut_controls.js
T

142 lines
5.1 KiB
JavaScript

export const DEFAULT_BROWSER_SHORTCUT = Object.freeze({
key: 'm',
ctrl: true,
shift: true
});
export const DEFAULT_MATERIALS_SHORTCUT = Object.freeze({
key: 'l',
ctrl: true,
shift: true
});
function normalizedShortcutKey(value) {
const key = String(value || '').toLowerCase();
return key.startsWith('key') && key.length === 4 ? key.slice(3) : key;
}
export function shortcutEventMatches(event, combo) {
if (!event || !combo || event.isComposing) return false;
return normalizedShortcutKey(event.key || event.code) === normalizedShortcutKey(combo.key)
&& !!event.ctrlKey === !!combo.ctrl
&& !!event.shiftKey === !!combo.shift
&& !!event.altKey === !!combo.alt
&& !!event.metaKey === !!combo.meta;
}
export function installDeferredShortcutFallback({ target, getCombo, isHandled, onFallback, onError = console.error }) {
const handler = event => {
if (event.repeat || !shortcutEventMatches(event, getCombo?.())) return;
queueMicrotask(() => {
if (isHandled?.()) return;
Promise.resolve(onFallback?.()).catch(onError);
});
};
target.addEventListener('keydown', handler, true);
return () => target.removeEventListener('keydown', handler, true);
}
export function formatKeyCombo(combo) {
if (!combo) return '';
if (typeof combo.getKeySequences === 'function') {
return combo.getKeySequences().join(' + ');
}
const keys = [];
if (combo.ctrl || combo.meta) keys.push('Ctrl');
if (combo.alt) keys.push('Alt');
if (combo.shift) keys.push('Shift');
if (combo.key) {
keys.push(combo.key.length === 1 ? combo.key.toUpperCase() : combo.key);
}
return keys.join(' + ');
}
function getCommand(app, commandId) {
return app.extensionManager?.command?.commands?.find((command) => command.id === commandId);
}
function waitForElement(selector, timeout = 1500) {
return new Promise((resolve) => {
const existing = document.querySelector(selector);
if (existing) {
resolve(existing);
return;
}
const observer = new MutationObserver(() => {
const element = document.querySelector(selector);
if (!element) return;
observer.disconnect();
clearTimeout(timer);
resolve(element);
});
observer.observe(document.body, { childList: true, subtree: true });
const timer = setTimeout(() => {
observer.disconnect();
resolve(null);
}, timeout);
});
}
function setInputValue(input, value) {
const setter = Object.getOwnPropertyDescriptor(HTMLInputElement.prototype, 'value')?.set;
if (setter) setter.call(input, value);
else input.value = value;
input.dispatchEvent(new Event('input', { bubbles: true }));
}
export async function openNativeKeybindingEditor(commandId) {
const settingsDialog = document.querySelector('[data-testid="settings-dialog"]');
const keybindingNav = settingsDialog?.querySelector('[data-nav-id="keybinding"]');
if (!(keybindingNav instanceof HTMLElement)) return false;
keybindingNav.click();
const searchInput = await waitForElement('#keybinding-panel-header input');
if (!(searchInput instanceof HTMLInputElement)) return false;
setInputValue(searchInput, commandId);
const commandTitle = await waitForElement(`.keybinding-panel [title="${CSS.escape(commandId)}"]`);
const commandRow = commandTitle?.closest('tr');
if (!commandRow) return false;
const actionButtons = commandRow.querySelectorAll('.actions button');
const bindingCellText = commandRow.children[1]?.textContent?.trim();
const canOpenRecorderDirectly = actionButtons.length >= 4 || bindingCellText === '-';
if (canOpenRecorderDirectly && actionButtons[0] instanceof HTMLButtonElement) {
actionButtons[0].click();
}
return true;
}
export function createShortcutSettingControl({ app, commandId, translate, settingLabelKey = 'mainShortcutSetting' }) {
const container = document.createElement('div');
container.className = 'anomalous-shortcut-setting';
const current = document.createElement('kbd');
current.className = 'anomalous-shortcut-current';
const combo = getCommand(app, commandId)?.keybinding?.combo;
current.textContent = formatKeyCombo(combo) || translate('mainShortcutUnassigned');
const customize = document.createElement('button');
customize.type = 'button';
customize.className = 'anomalous-shortcut-customize';
customize.textContent = translate('mainShortcutCustomize');
customize.addEventListener('click', async () => {
customize.disabled = true;
const opened = await openNativeKeybindingEditor(commandId);
customize.disabled = false;
if (!opened) {
app.extensionManager?.toast?.add?.({
severity: 'warn',
summary: translate(settingLabelKey),
detail: translate('mainShortcutOpenFailed'),
life: 3500
});
}
});
container.append(current, customize);
return container;
}