refactor: add TranslatorManager

This commit is contained in:
Gabe
2025-10-21 02:07:33 +08:00
parent ed279cf8a1
commit 53e32d3031
17 changed files with 566 additions and 405 deletions

View File

@@ -0,0 +1,258 @@
import { browser } from "./browser";
import { Translator } from "./translator";
import { InputTranslator } from "./inputTranslate";
import { TransboxManager } from "./tranbox";
import { shortcutRegister } from "./shortcut";
import { sendIframeMsg } from "./iframe";
import { newI18n } from "../config";
import { touchTapListener } from "./touch";
import { debounce } from "./utils";
import { PopupManager } from "./popupManager";
import { FabManager } from "./fabManager";
import {
OPT_SHORTCUT_TRANSLATE,
OPT_SHORTCUT_STYLE,
OPT_SHORTCUT_POPUP,
OPT_SHORTCUT_SETTING,
MSG_TRANS_TOGGLE,
MSG_TRANS_TOGGLE_STYLE,
MSG_TRANS_GETRULE,
MSG_TRANS_PUTRULE,
MSG_OPEN_TRANBOX,
MSG_TRANSBOX_TOGGLE,
MSG_MOUSEHOVER_TOGGLE,
MSG_TRANSINPUT_TOGGLE,
} from "../config";
import { logger } from "./log";
export default class TranslatorManager {
#clearShortcuts = [];
#menuCommandIds = [];
#clearTouchListener = null;
#isActive = false;
#isUserscript;
#isIframe;
#windowMessageHandler = null;
#browserMessageHandler = null;
_translator;
_transboxManager;
_inputTranslator;
_popupManager;
_fabManager;
constructor({ setting, rule, fabConfig, favWords, isIframe, isUserscript }) {
this.#isIframe = isIframe;
this.#isUserscript = isUserscript;
this._translator = new Translator({
rule,
setting,
favWords,
isUserscript,
isIframe,
});
if (!isIframe) {
this._transboxManager = new TransboxManager(setting);
this._inputTranslator = new InputTranslator(setting);
this._popupManager = new PopupManager({ translator: this._translator });
if (fabConfig && !fabConfig.isHide) {
this._fabManager = new FabManager({
translator: this._translator,
popupManager: this._popupManager,
fabConfig,
});
this._fabManager.show();
}
}
this.#windowMessageHandler = this.#handleWindowMessage.bind(this);
this.#browserMessageHandler = this.#handleBrowserMessage.bind(this);
}
start() {
if (this.#isActive) {
logger.info("TranslatorManager is already started.");
return;
}
this.#setupMessageListeners();
this.#setupTouchOperations();
if (!this.#isIframe && this.#isUserscript) {
this.#registerShortcuts();
this.#registerMenus();
}
this.#isActive = true;
logger.info("TranslatorManager started.");
}
stop() {
if (!this.#isActive) {
logger.info("TranslatorManager is not running.");
return;
}
// 移除消息监听器
if (this.#isUserscript) {
window.removeEventListener("message", this.#windowMessageHandler);
} else if (
browser.runtime.onMessage.hasListener(this.#browserMessageHandler)
) {
browser.runtime.onMessage.removeListener(this.#browserMessageHandler);
}
// 已注册的快捷键
this.#clearShortcuts.forEach((clear) => clear());
this.#clearShortcuts = [];
// 触屏
if (this.#clearTouchListener) {
this.#clearTouchListener();
this.#clearTouchListener = null;
}
// 油猴菜单
if (globalThis.GM && this.#menuCommandIds.length > 0) {
this.#menuCommandIds.forEach((id) =>
globalThis.GM.unregisterMenuCommand(id)
);
this.#menuCommandIds = [];
}
// 子模块
this._popupManager?.hide();
this._fabManager?.hide();
this._transboxManager?.disable();
this._inputTranslator?.disable();
this._translator.stop();
this.#isActive = false;
logger.info("TranslatorManager stopped.");
}
#setupMessageListeners() {
if (this.#isUserscript) {
window.addEventListener("message", this.#windowMessageHandler);
} else {
browser.runtime.onMessage.addListener(this.#browserMessageHandler);
}
}
#setupTouchOperations() {
if (this.#isIframe) return;
const { touchTranslate = 2 } = this._translator.setting;
if (touchTranslate === 0) {
return;
}
const handleTap = debounce(() => {
this.#processActions({ action: MSG_TRANS_TOGGLE });
}, 300);
this.#clearTouchListener = touchTapListener(handleTap, touchTranslate);
}
#handleWindowMessage(event) {
this.#processActions(event.data);
}
#handleBrowserMessage(message, sender, sendResponse) {
const result = this.#processActions(message);
const response = result || {
rule: this._translator.rule,
setting: this._translator.setting,
};
sendResponse(response);
return true;
}
#registerShortcuts() {
const { shortcuts } = this._translator.setting;
this.#clearShortcuts = [
shortcutRegister(shortcuts[OPT_SHORTCUT_TRANSLATE], () =>
this.#processActions({ action: MSG_TRANS_TOGGLE })
),
shortcutRegister(shortcuts[OPT_SHORTCUT_STYLE], () =>
this.#processActions({ action: MSG_TRANS_TOGGLE_STYLE })
),
shortcutRegister(shortcuts[OPT_SHORTCUT_POPUP], () =>
this._popupManager.toggle()
),
shortcutRegister(shortcuts[OPT_SHORTCUT_SETTING], () =>
window.open(process.env.REACT_APP_OPTIONSPAGE, "_blank")
),
];
}
#registerMenus() {
if (!globalThis.GM) return;
const { contextMenuType, uiLang } = this._translator.setting;
if (contextMenuType === 0) return;
const i18n = newI18n(uiLang || "zh");
const GM = globalThis.GM;
this.#menuCommandIds = [
GM.registerMenuCommand(
i18n("translate_switch"),
() => this.#processActions({ action: MSG_TRANS_TOGGLE }),
"Q"
),
GM.registerMenuCommand(
i18n("toggle_style"),
() => this.#processActions({ action: MSG_TRANS_TOGGLE_STYLE }),
"C"
),
GM.registerMenuCommand(
i18n("open_menu"),
() => this._popupManager.toggle(),
"K"
),
GM.registerMenuCommand(
i18n("open_setting"),
() => window.open(process.env.REACT_APP_OPTIONSPAGE, "_blank"),
"O"
),
];
}
#processActions({ action, args } = {}) {
if (this.#isUserscript) {
sendIframeMsg(action);
}
switch (action) {
case MSG_TRANS_TOGGLE:
this._translator.toggle();
break;
case MSG_TRANS_TOGGLE_STYLE:
this._translator.toggleStyle();
break;
case MSG_TRANS_GETRULE:
break;
case MSG_TRANS_PUTRULE:
this._translator.updateRule(args);
break;
case MSG_OPEN_TRANBOX:
this._transboxManager?.enable();
break;
case MSG_TRANSBOX_TOGGLE:
this._transboxManager?.toggle();
break;
case MSG_MOUSEHOVER_TOGGLE:
this._translator.toggleMouseHover();
break;
case MSG_TRANSINPUT_TOGGLE:
this._inputTranslator?.toggle();
break;
default:
logger.info(`Message action is unavailable: ${action}`);
return { error: `Message action is unavailable: ${action}` };
}
}
}