From 60b9653fd36bee55d1ebf7b3b5fed2a14e37dfb8 Mon Sep 17 00:00:00 2001 From: Gabe Date: Wed, 22 Oct 2025 01:50:49 +0800 Subject: [PATCH] feat: more touch operations --- src/config/i18n.js | 15 ++ src/config/msg.js | 3 + src/config/setting.js | 2 +- src/libs/fabManager.js | 8 +- src/libs/popupManager.js | 18 +- src/libs/touch.js | 45 +++- src/libs/translator.js | 11 + src/libs/translatorManager.js | 76 ++++-- src/views/Action/ContentFab.js | 8 +- src/views/Action/index.js | 88 ++++-- src/views/Options/Setting.js | 2 +- src/views/Popup/PopupCont.js | 422 +++++++++++++++++++++++++++++ src/views/Popup/index.js | 473 ++------------------------------- src/views/Selection/TranBox.js | 81 +++--- src/views/Selection/index.js | 28 +- 15 files changed, 730 insertions(+), 550 deletions(-) create mode 100644 src/views/Popup/PopupCont.js diff --git a/src/config/i18n.js b/src/config/i18n.js index 33fd548..1f3b9d2 100644 --- a/src/config/i18n.js +++ b/src/config/i18n.js @@ -1184,6 +1184,21 @@ export const I18N = { en: `Four finger tap`, zh_TW: `四指輕觸`, }, + touch_tap_5: { + zh: `单指双击`, + en: `Double-click`, + zh_TW: `單指雙擊`, + }, + touch_tap_6: { + zh: `单指三击`, + en: `Triple-click`, + zh_TW: `單指三擊`, + }, + touch_tap_7: { + zh: `双指双击`, + en: `Two-finger double-click`, + zh_TW: `雙指雙擊`, + }, translate_blacklist: { zh: `禁用翻译名单`, en: `Translate Blacklist`, diff --git a/src/config/msg.js b/src/config/msg.js index f74f542..6d867a9 100644 --- a/src/config/msg.js +++ b/src/config/msg.js @@ -15,6 +15,7 @@ export const MSG_TRANS_GETRULE = "trans_getrule"; export const MSG_TRANS_PUTRULE = "trans_putrule"; export const MSG_TRANS_CURRULE = "trans_currule"; export const MSG_TRANSBOX_TOGGLE = "transbox_toggle"; +export const MSG_POPUP_TOGGLE = "popup_toggle"; export const MSG_MOUSEHOVER_TOGGLE = "mousehover_toggle"; export const MSG_TRANSINPUT_TOGGLE = "transinput_toggle"; export const MSG_CONTEXT_MENUS = "context_menus"; @@ -27,6 +28,8 @@ export const MSG_BUILTINAI_TRANSLATE = "builtinai_translte"; export const MSG_SET_LOGLEVEL = "set_loglevel"; export const MSG_CLEAR_CACHES = "clear_caches"; +export const EVENT_KISS = "event_kiss_translate"; + export const MSG_XHR_DATA_YOUTUBE = "KISS_XHR_DATA_YOUTUBE"; // export const MSG_GLOBAL_VAR_FETCH = "KISS_GLOBAL_VAR_FETCH"; // export const MSG_GLOBAL_VAR_BACK = "KISS_GLOBAL_VAR_BACK"; diff --git a/src/config/setting.js b/src/config/setting.js index c936b7a..44d8da7 100644 --- a/src/config/setting.js +++ b/src/config/setting.js @@ -166,7 +166,7 @@ export const DEFAULT_SETTING = { shortcuts: DEFAULT_SHORTCUTS, // 快捷键 inputRule: DEFAULT_INPUT_RULE, // 输入框设置 tranboxSetting: DEFAULT_TRANBOX_SETTING, // 划词翻译设置 - touchTranslate: 2, // 触屏翻译 + touchTranslate: 2, // 触屏翻译 {5:单指双击,6:单指三击,7:双指双击} blacklist: DEFAULT_BLACKLIST.join(",\n"), // 禁用翻译名单 csplist: DEFAULT_CSPLIST.join(",\n"), // 禁用CSP名单 orilist: DEFAULT_ORILIST.join(",\n"), // 禁用CSP名单 diff --git a/src/libs/fabManager.js b/src/libs/fabManager.js index d2e7b45..25a44af 100644 --- a/src/libs/fabManager.js +++ b/src/libs/fabManager.js @@ -3,12 +3,16 @@ import { APP_CONSTS } from "../config"; import ContentFab from "../views/Action/ContentFab"; export class FabManager extends ShadowDomManager { - constructor({ translator, popupManager, fabConfig }) { + constructor({ translator, processActions, fabConfig }) { super({ id: APP_CONSTS.fabID, className: "notranslate", reactComponent: ContentFab, - props: { translator, popupManager, fabConfig }, + props: { translator, processActions, fabConfig }, }); + + if (!fabConfig?.isHide) { + this.show(); + } } } diff --git a/src/libs/popupManager.js b/src/libs/popupManager.js index d9a9906..ba9cb1d 100644 --- a/src/libs/popupManager.js +++ b/src/libs/popupManager.js @@ -1,14 +1,26 @@ import ShadowDomManager from "./shadowDomManager"; -import { APP_CONSTS } from "../config"; +import { APP_CONSTS, EVENT_KISS, MSG_POPUP_TOGGLE } from "../config"; import Action from "../views/Action"; export class PopupManager extends ShadowDomManager { - constructor({ translator }) { + constructor({ translator, processActions }) { super({ id: APP_CONSTS.popupID, className: "notranslate", reactComponent: Action, - props: { translator }, + props: { translator, processActions }, }); } + + toggle(props) { + if (this.isVisible) { + document.dispatchEvent( + new CustomEvent(EVENT_KISS, { + detail: { action: MSG_POPUP_TOGGLE }, + }) + ); + } else { + this.show(props || this._props); + } + } } diff --git a/src/libs/touch.js b/src/libs/touch.js index 4b0e066..5d0ee3d 100644 --- a/src/libs/touch.js +++ b/src/libs/touch.js @@ -1,12 +1,47 @@ -export function touchTapListener(fn, touchsLength) { +export function touchTapListener(fn, options = {}) { + const config = { + taps: 2, + fingers: 1, + delay: 300, + ...options, + }; + + let maxTouches = 0; + let tapCount = 0; + let tapTimer = null; + + const handleTouchStart = (e) => { + maxTouches = Math.max(maxTouches, e.touches.length); + }; + const handleTouchend = (e) => { - if (e.touches.length === touchsLength) { - fn(); + if (e.touches.length === 0) { + if (maxTouches === config.fingers) { + tapCount++; + clearTimeout(tapTimer); + + if (tapCount === config.taps) { + fn(e); + tapCount = 0; + } else { + tapTimer = setTimeout(() => { + tapCount = 0; + }, config.delay); + } + } else { + tapCount = 0; + clearTimeout(tapTimer); + } + maxTouches = 0; } }; - document.addEventListener("touchstart", handleTouchend); + document.addEventListener("touchstart", handleTouchStart, { passive: true }); + document.addEventListener("touchend", handleTouchend, { passive: true }); + return () => { - document.removeEventListener("touchstart", handleTouchend); + clearTimeout(tapTimer); + document.removeEventListener("touchstart", handleTouchStart); + document.removeEventListener("touchend", handleTouchend); }; } diff --git a/src/libs/translator.js b/src/libs/translator.js index ad3e0da..76d46d2 100644 --- a/src/libs/translator.js +++ b/src/libs/translator.js @@ -1644,6 +1644,17 @@ export class Translator { this.updateRule({ textStyle }); } + // 切换划词翻译 + toggleTransbox() { + this.#setting.tranboxSetting.transOpen = + !this.#setting.tranboxSetting.transOpen; + } + + // 切换输入框翻译 + toggleInputTranslate() { + this.#setting.inputRule.transOpen = !this.#setting.inputRule.transOpen; + } + // 停止运行 stop() { this.disable(); diff --git a/src/libs/translatorManager.js b/src/libs/translatorManager.js index 8d8266c..66c2a56 100644 --- a/src/libs/translatorManager.js +++ b/src/libs/translatorManager.js @@ -4,9 +4,8 @@ import { InputTranslator } from "./inputTranslate"; import { TransboxManager } from "./tranbox"; import { shortcutRegister } from "./shortcut"; import { sendIframeMsg } from "./iframe"; -import { newI18n } from "../config"; +import { EVENT_KISS, newI18n } from "../config"; import { touchTapListener } from "./touch"; -import { debounce } from "./utils"; import { PopupManager } from "./popupManager"; import { FabManager } from "./fabManager"; import { @@ -20,6 +19,7 @@ import { MSG_TRANS_PUTRULE, MSG_OPEN_TRANBOX, MSG_TRANSBOX_TOGGLE, + MSG_POPUP_TOGGLE, MSG_MOUSEHOVER_TOGGLE, MSG_TRANSINPUT_TOGGLE, } from "../config"; @@ -57,16 +57,15 @@ export default class TranslatorManager { 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._popupManager = new PopupManager({ + translator: this._translator, + processActions: this.#processActions.bind(this), + }); + this._fabManager = new FabManager({ + translator: this._translator, + processActions: this.#processActions.bind(this), + fabConfig, + }); } this.#windowMessageHandler = this.#handleWindowMessage.bind(this); @@ -125,8 +124,8 @@ export default class TranslatorManager { } // 子模块 - this._popupManager?.hide(); - this._fabManager?.hide(); + this._popupManager?.destroy(); + this._fabManager?.destroy(); this._transboxManager?.disable(); this._inputTranslator?.disable(); this._translator.stop(); @@ -151,11 +150,39 @@ export default class TranslatorManager { return; } - const handleTap = debounce(() => { + const handleTap = () => { this.#processActions({ action: MSG_TRANS_TOGGLE }); - }, 300); + }; - this.#clearTouchListener = touchTapListener(handleTap, touchTranslate); + switch (touchTranslate) { + case 2: + case 3: + case 4: + this.#clearTouchListener = touchTapListener(handleTap, { + taps: 1, + fingers: touchTranslate, + }); + break; + case 5: + this.#clearTouchListener = touchTapListener(handleTap, { + taps: 2, + fingers: 1, + }); + break; + case 6: + this.#clearTouchListener = touchTapListener(handleTap, { + taps: 3, + fingers: 1, + }); + break; + case 7: + this.#clearTouchListener = touchTapListener(handleTap, { + taps: 2, + fingers: 2, + }); + break; + default: + } } #handleWindowMessage(event) { @@ -182,7 +209,7 @@ export default class TranslatorManager { this.#processActions({ action: MSG_TRANS_TOGGLE_STYLE }) ), shortcutRegister(shortcuts[OPT_SHORTCUT_POPUP], () => - this._popupManager.toggle() + this.#processActions({ action: MSG_POPUP_TOGGLE }) ), shortcutRegister(shortcuts[OPT_SHORTCUT_SETTING], () => window.open(process.env.REACT_APP_OPTIONSPAGE, "_blank") @@ -210,7 +237,7 @@ export default class TranslatorManager { ), GM.registerMenuCommand( i18n("open_menu"), - () => this._popupManager.toggle(), + () => this.#processActions({ action: MSG_POPUP_TOGGLE }), "K" ), GM.registerMenuCommand( @@ -239,16 +266,25 @@ export default class TranslatorManager { this._translator.updateRule(args); break; case MSG_OPEN_TRANBOX: - this._transboxManager?.enable(); + document.dispatchEvent( + new CustomEvent(EVENT_KISS, { + detail: { action: MSG_OPEN_TRANBOX }, + }) + ); + break; + case MSG_POPUP_TOGGLE: + this._popupManager?.toggle(); break; case MSG_TRANSBOX_TOGGLE: this._transboxManager?.toggle(); + this._translator.toggleTransbox(); break; case MSG_MOUSEHOVER_TOGGLE: this._translator.toggleMouseHover(); break; case MSG_TRANSINPUT_TOGGLE: this._inputTranslator?.toggle(); + this._translator.toggleInputTranslate(); break; default: logger.info(`Message action is unavailable: ${action}`); diff --git a/src/views/Action/ContentFab.js b/src/views/Action/ContentFab.js index 4e66ca6..397fd38 100644 --- a/src/views/Action/ContentFab.js +++ b/src/views/Action/ContentFab.js @@ -4,14 +4,14 @@ import ThemeProvider from "../../hooks/Theme"; import Draggable from "./Draggable"; import { useState, useMemo, useCallback } from "react"; import { SettingProvider } from "../../hooks/Setting"; -import { MSG_TRANS_TOGGLE } from "../../config"; +import { MSG_TRANS_TOGGLE, MSG_POPUP_TOGGLE } from "../../config"; import { sendIframeMsg } from "../../libs/iframe"; import useWindowSize from "../../hooks/WindowSize"; export default function ContentFab({ translator, fabConfig: { x: fabX, y: fabY, fabClickAction = 0 } = {}, - popupManager, + processActions, }) { const fabWidth = 40; const windowSize = useWindowSize(); @@ -31,10 +31,10 @@ export default function ContentFab({ translator.toggle(); sendIframeMsg(MSG_TRANS_TOGGLE); } else { - popupManager.toggle(); + processActions({ action: MSG_POPUP_TOGGLE }); } } - }, [moved, translator, popupManager, fabClickAction]); + }, [moved, translator, fabClickAction, processActions]); const fabProps = useMemo( () => ({ diff --git a/src/views/Action/index.js b/src/views/Action/index.js index ac99dbd..3967a89 100644 --- a/src/views/Action/index.js +++ b/src/views/Action/index.js @@ -1,27 +1,59 @@ import ThemeProvider from "../../hooks/Theme"; import Draggable from "./Draggable"; -import { useEffect, useMemo, useCallback } from "react"; +import { useEffect, useMemo, useCallback, useState } from "react"; import { SettingProvider } from "../../hooks/Setting"; -import Popup from "../Popup"; import Header from "../Popup/Header"; import Box from "@mui/material/Box"; import Divider from "@mui/material/Divider"; import useWindowSize from "../../hooks/WindowSize"; +import { EVENT_KISS, MSG_OPEN_OPTIONS, MSG_POPUP_TOGGLE } from "../../config"; +import PopupCont from "../Popup/PopupCont"; +import { isExt } from "../../libs/client"; +import { sendBgMsg } from "../../libs/msg"; -export default function Action({ translator, onClose }) { +export default function Action({ translator, processActions }) { + const [showPopup, setShowPopup] = useState(true); + const [rule, setRule] = useState(translator.rule); + const [setting, setSetting] = useState(translator.setting); const windowSize = useWindowSize(); - const handleWindowClick = useCallback(() => { - onClose(); - }, [onClose]); + const handleOpenSetting = useCallback(() => { + if (isExt) { + sendBgMsg(MSG_OPEN_OPTIONS); + } else { + window.open(process.env.REACT_APP_OPTIONSPAGE, "_blank"); + } + }, []); useEffect(() => { + const handleWindowClick = () => { + setShowPopup(false); + }; window.addEventListener("click", handleWindowClick); - return () => { window.removeEventListener("click", handleWindowClick); }; - }, [handleWindowClick]); + }, []); + + useEffect(() => { + const handleStatusUpdate = (event) => { + if (event.detail?.action === MSG_POPUP_TOGGLE) { + setShowPopup((pre) => !pre); + } + }; + + document.addEventListener(EVENT_KISS, handleStatusUpdate); + return () => { + document.removeEventListener(EVENT_KISS, handleStatusUpdate); + }; + }, []); + + useEffect(() => { + if (showPopup) { + setRule(translator.rule); + setSetting(translator.setting); + } + }, [showPopup, translator]); const popProps = useMemo(() => { const width = Math.min(windowSize.w, 360); @@ -40,19 +72,35 @@ export default function Action({ translator, onClose }) { return ( - -
- + {showPopup && ( + +
{ + setShowPopup(false); + }} + /> + + + } + > + + - } - > - - + + )} ); diff --git a/src/views/Options/Setting.js b/src/views/Options/Setting.js index 7ce0159..7cc1ff1 100644 --- a/src/views/Options/Setting.js +++ b/src/views/Options/Setting.js @@ -273,7 +273,7 @@ export default function Settings() { label={i18n("touch_translate_shortcut")} onChange={handleChange} > - {[0, 2, 3, 4].map((item) => ( + {[0, 2, 3, 4, 5, 6, 7].map((item) => ( {i18n(`touch_tap_${item}`)} diff --git a/src/views/Popup/PopupCont.js b/src/views/Popup/PopupCont.js new file mode 100644 index 0000000..299d9db --- /dev/null +++ b/src/views/Popup/PopupCont.js @@ -0,0 +1,422 @@ +import { useState, useEffect, useMemo } from "react"; +import Stack from "@mui/material/Stack"; +import MenuItem from "@mui/material/MenuItem"; +import FormControlLabel from "@mui/material/FormControlLabel"; +import Switch from "@mui/material/Switch"; +import Button from "@mui/material/Button"; +import Grid from "@mui/material/Grid"; +import { sendBgMsg, sendTabMsg, getCurTab } from "../../libs/msg"; +import { isExt } from "../../libs/client"; +import { useI18n } from "../../hooks/I18n"; +import TextField from "@mui/material/TextField"; +import { + MSG_TRANS_TOGGLE, + MSG_TRANS_PUTRULE, + MSG_SAVE_RULE, + MSG_COMMAND_SHORTCUTS, + MSG_TRANSBOX_TOGGLE, + MSG_MOUSEHOVER_TOGGLE, + MSG_TRANSINPUT_TOGGLE, + OPT_LANGS_FROM, + OPT_LANGS_TO, + OPT_STYLE_ALL, +} from "../../config"; +import { saveRule } from "../../libs/rules"; +import { tryClearCaches } from "../../libs/cache"; +import { kissLog } from "../../libs/log"; +import { parseUrlPattern } from "../../libs/utils"; + +export default function PopupCont({ + rule, + setting, + setRule, + setSetting, + handleOpenSetting, + processActions, + isContent = false, +}) { + const i18n = useI18n(); + const [commands, setCommands] = useState({}); + + const handleTransToggle = async (e) => { + try { + setRule({ ...rule, transOpen: e.target.checked ? "true" : "false" }); + + if (!processActions) { + await sendTabMsg(MSG_TRANS_TOGGLE); + } else { + processActions({ action: MSG_TRANS_TOGGLE }); + } + } catch (err) { + kissLog("toggle trans", err); + } + }; + + const handleTransboxToggle = async (e) => { + try { + setSetting((pre) => ({ + ...pre, + tranboxSetting: { ...pre.tranboxSetting, transOpen: e.target.checked }, + })); + + if (!processActions) { + await sendTabMsg(MSG_TRANSBOX_TOGGLE); + } else { + processActions({ action: MSG_TRANSBOX_TOGGLE }); + } + } catch (err) { + kissLog("toggle transbox", err); + } + }; + + const handleMousehoverToggle = async (e) => { + try { + setSetting((pre) => ({ + ...pre, + mouseHoverSetting: { + ...pre.mouseHoverSetting, + useMouseHover: e.target.checked, + }, + })); + + if (!processActions) { + await sendTabMsg(MSG_MOUSEHOVER_TOGGLE); + } else { + processActions({ action: MSG_MOUSEHOVER_TOGGLE }); + } + } catch (err) { + kissLog("toggle mousehover", err); + } + }; + + const handleInputTransToggle = async (e) => { + try { + setSetting((pre) => ({ + ...pre, + inputRule: { + ...pre.inputRule, + transOpen: e.target.checked, + }, + })); + + if (!processActions) { + await sendTabMsg(MSG_TRANSINPUT_TOGGLE); + } else { + processActions({ action: MSG_TRANSINPUT_TOGGLE }); + } + } catch (err) { + kissLog("toggle inputtrans", err); + } + }; + + const handleChange = async (e) => { + try { + const { name, value } = e.target; + setRule((pre) => ({ ...pre, [name]: value })); + + if (!processActions) { + await sendTabMsg(MSG_TRANS_PUTRULE, { [name]: value }); + } else { + processActions({ action: MSG_TRANS_PUTRULE, args: { [name]: value } }); + } + } catch (err) { + kissLog("update rule", err); + } + }; + + const handleClearCache = () => { + tryClearCaches(); + }; + + const handleSaveRule = async () => { + try { + let href = ""; + if (!isContent) { + const tab = await getCurTab(); + href = tab.url; + } else { + href = window.location?.href; + } + + if (!href || typeof href !== "string") { + return; + } + + const pattern = parseUrlPattern(href); + const curRule = { ...rule, pattern }; + if (isExt && isContent) { + sendBgMsg(MSG_SAVE_RULE, curRule); + } else { + saveRule(curRule); + } + } catch (err) { + kissLog("save rule", err); + } + }; + + useEffect(() => { + (async () => { + try { + const commands = {}; + if (isExt) { + const res = await sendBgMsg(MSG_COMMAND_SHORTCUTS); + res.forEach(({ name, shortcut }) => { + commands[name] = shortcut; + }); + } else { + const shortcuts = setting.shortcuts; + if (shortcuts) { + Object.entries(shortcuts).forEach(([key, val]) => { + commands[key] = val.join("+"); + }); + } + } + setCommands(commands); + } catch (err) { + kissLog("query cmds", err); + } + })(); + }, [setting.shortcuts]); + + const optApis = useMemo( + () => + setting.transApis + .filter((api) => !api.isDisabled) + .map((api) => ({ + key: api.apiSlug, + name: api.apiName || api.apiSlug, + })), + [setting.transApis] + ); + + const tranboxEnabled = setting.tranboxSetting.transOpen; + const mouseHoverEnabled = setting.mouseHoverSetting.useMouseHover; + const inputTransEnabled = setting.inputRule.transOpen; + + const { + transOpen, + apiSlug, + fromLang, + toLang, + textStyle, + autoScan, + transOnly, + hasRichText, + hasShadowroot, + } = rule; + + return ( + + + + + } + label={ + commands["toggleTranslate"] + ? `${i18n("translate_alt")}(${commands["toggleTranslate"]})` + : i18n("translate_alt") + } + /> + + + + } + label={i18n("autoscan_alt")} + /> + + + + } + label={i18n("shadowroot_alt")} + /> + + + + } + label={i18n("richtext_alt")} + /> + + + + } + label={i18n("transonly_alt")} + /> + + + + } + label={i18n("selection_translate")} + /> + + + + } + label={i18n("mousehover_translate")} + /> + + + + } + label={i18n("input_translate")} + /> + + + + + {optApis.map(({ key, name }) => ( + + {name} + + ))} + + + + {OPT_LANGS_FROM.map(([lang, name]) => ( + + {name} + + ))} + + + + {OPT_LANGS_TO.map(([lang, name]) => ( + + {name} + + ))} + + + + {OPT_STYLE_ALL.map((item) => ( + + {i18n(item)} + + ))} + + + {/* {OPT_STYLE_USE_COLOR.includes(textStyle) && ( + + )} */} + + + + + + + + ); +} diff --git a/src/views/Popup/index.js b/src/views/Popup/index.js index 9bc2447..c34fce4 100644 --- a/src/views/Popup/index.js +++ b/src/views/Popup/index.js @@ -1,181 +1,26 @@ -import { useState, useEffect, useMemo } from "react"; +import { useState, useEffect, useCallback } from "react"; import Box from "@mui/material/Box"; import Stack from "@mui/material/Stack"; -import MenuItem from "@mui/material/MenuItem"; -import FormControlLabel from "@mui/material/FormControlLabel"; -import Switch from "@mui/material/Switch"; import Button from "@mui/material/Button"; -import Grid from "@mui/material/Grid"; -import { sendBgMsg, sendTabMsg, getCurTab } from "../../libs/msg"; +import { sendTabMsg } from "../../libs/msg"; import { browser } from "../../libs/browser"; -import { isExt } from "../../libs/client"; import { useI18n } from "../../hooks/I18n"; -import TextField from "@mui/material/TextField"; import Divider from "@mui/material/Divider"; import Header from "./Header"; -import { - MSG_TRANS_TOGGLE, - MSG_TRANS_GETRULE, - MSG_TRANS_PUTRULE, - MSG_OPEN_OPTIONS, - MSG_SAVE_RULE, - MSG_COMMAND_SHORTCUTS, - MSG_TRANSBOX_TOGGLE, - MSG_MOUSEHOVER_TOGGLE, - MSG_TRANSINPUT_TOGGLE, - OPT_LANGS_FROM, - OPT_LANGS_TO, - OPT_STYLE_ALL, -} from "../../config"; -import { sendIframeMsg } from "../../libs/iframe"; -import { saveRule } from "../../libs/rules"; -import { tryClearCaches } from "../../libs/cache"; +import { MSG_TRANS_GETRULE } from "../../config"; import { kissLog } from "../../libs/log"; -import { parseUrlPattern } from "../../libs/utils"; +import PopupCont from "./PopupCont"; -// 插件popup没有参数 -// 网页弹框有 -export default function Popup({ translator }) { +export default function Popup() { const i18n = useI18n(); - const [rule, setRule] = useState(translator?.rule); - const [setting, setSetting] = useState(translator?.setting); - const [commands, setCommands] = useState({}); + const [rule, setRule] = useState(null); + const [setting, setSetting] = useState(null); - const handleOpenSetting = () => { - if (!translator) { - browser?.runtime.openOptionsPage(); - } else if (isExt) { - sendBgMsg(MSG_OPEN_OPTIONS); - } else { - window.open(process.env.REACT_APP_OPTIONSPAGE, "_blank"); - } - }; - - const handleTransToggle = async (e) => { - try { - setRule({ ...rule, transOpen: e.target.checked ? "true" : "false" }); - - if (!translator) { - await sendTabMsg(MSG_TRANS_TOGGLE); - } else { - translator.toggle(); - sendIframeMsg(MSG_TRANS_TOGGLE); - } - } catch (err) { - kissLog("toggle trans", err); - } - }; - - const handleTransboxToggle = async (e) => { - try { - setSetting((pre) => ({ - ...pre, - tranboxSetting: { ...pre.tranboxSetting, transOpen: e.target.checked }, - })); - - if (!translator) { - await sendTabMsg(MSG_TRANSBOX_TOGGLE); - } else { - translator.toggleTransbox(); - sendIframeMsg(MSG_TRANSBOX_TOGGLE); - } - } catch (err) { - kissLog("toggle transbox", err); - } - }; - - const handleMousehoverToggle = async (e) => { - try { - setSetting((pre) => ({ - ...pre, - mouseHoverSetting: { - ...pre.mouseHoverSetting, - useMouseHover: e.target.checked, - }, - })); - - if (!translator) { - await sendTabMsg(MSG_MOUSEHOVER_TOGGLE); - } else { - translator.toggleMouseHover(); - sendIframeMsg(MSG_MOUSEHOVER_TOGGLE); - } - } catch (err) { - kissLog("toggle mousehover", err); - } - }; - - const handleInputTransToggle = async (e) => { - try { - setSetting((pre) => ({ - ...pre, - inputRule: { - ...pre.inputRule, - transOpen: e.target.checked, - }, - })); - - if (!translator) { - await sendTabMsg(MSG_TRANSINPUT_TOGGLE); - } else { - translator.toggleInputTranslate(); - sendIframeMsg(MSG_TRANSINPUT_TOGGLE); - } - } catch (err) { - kissLog("toggle inputtrans", err); - } - }; - - const handleChange = async (e) => { - try { - const { name, value } = e.target; - setRule((pre) => ({ ...pre, [name]: value })); - - if (!translator) { - await sendTabMsg(MSG_TRANS_PUTRULE, { [name]: value }); - } else { - translator.updateRule({ [name]: value }); - sendIframeMsg(MSG_TRANS_PUTRULE, { [name]: value }); - } - } catch (err) { - kissLog("update rule", err); - } - }; - - const handleClearCache = () => { - tryClearCaches(); - }; - - const handleSaveRule = async () => { - try { - let href = ""; - if (!translator) { - const tab = await getCurTab(); - href = tab.url; - } else { - href = window.location?.href; - } - - if (!href || typeof href !== "string") { - return; - } - - const pattern = parseUrlPattern(href); - const curRule = { ...rule, pattern }; - if (isExt && translator) { - sendBgMsg(MSG_SAVE_RULE, curRule); - } else { - saveRule(curRule); - } - } catch (err) { - kissLog("save rule", err); - } - }; + const handleOpenSetting = useCallback(() => { + browser?.runtime.openOptionsPage(); + }, []); useEffect(() => { - if (translator) { - return; - } (async () => { try { const res = await sendTabMsg(MSG_TRANS_GETRULE); @@ -187,297 +32,27 @@ export default function Popup({ translator }) { kissLog("query rule", err); } })(); - }, [translator]); + }, []); - useEffect(() => { - (async () => { - try { - const commands = {}; - if (isExt) { - const res = await sendBgMsg(MSG_COMMAND_SHORTCUTS); - res.forEach(({ name, shortcut }) => { - commands[name] = shortcut; - }); - } else { - const shortcuts = translator.setting.shortcuts; - if (shortcuts) { - Object.entries(shortcuts).forEach(([key, val]) => { - commands[key] = val.join("+"); - }); - } - } - setCommands(commands); - } catch (err) { - kissLog("query cmds", err); - } - })(); - }, [translator]); - - const optApis = useMemo( - () => - setting?.transApis - .filter((api) => !api.isDisabled) - .map((api) => ({ - key: api.apiSlug, - name: api.apiName || api.apiSlug, - })), - [setting] - ); - - const tranboxEnabled = setting?.tranboxSetting.transOpen; - const mouseHoverEnabled = setting?.mouseHoverSetting.useMouseHover; - const inputTransEnabled = setting?.inputRule.transOpen; - - if (!rule) { - return ( - - {!translator && ( - <> -
- - - )} + return ( + +
+ + {rule && setting ? ( + + ) : ( - - ); - } - - const { - transOpen, - apiSlug, - fromLang, - toLang, - textStyle, - autoScan, - transOnly, - hasRichText, - hasShadowroot, - } = rule; - - return ( - - {!translator && ( - <> -
- - )} - - - - - } - label={ - commands["toggleTranslate"] - ? `${i18n("translate_alt")}(${commands["toggleTranslate"]})` - : i18n("translate_alt") - } - /> - - - - } - label={i18n("autoscan_alt")} - /> - - - - } - label={i18n("shadowroot_alt")} - /> - - - - } - label={i18n("richtext_alt")} - /> - - - - } - label={i18n("transonly_alt")} - /> - - - - } - label={i18n("selection_translate")} - /> - - - - } - label={i18n("mousehover_translate")} - /> - - - - } - label={i18n("input_translate")} - /> - - - - - {optApis.map(({ key, name }) => ( - - {name} - - ))} - - - - {OPT_LANGS_FROM.map(([lang, name]) => ( - - {name} - - ))} - - - - {OPT_LANGS_TO.map(([lang, name]) => ( - - {name} - - ))} - - - - {OPT_STYLE_ALL.map((item) => ( - - {i18n(item)} - - ))} - - - {/* {OPT_STYLE_USE_COLOR.includes(textStyle) && ( - - )} */} - - - - - - - ); } diff --git a/src/views/Selection/TranBox.js b/src/views/Selection/TranBox.js index 2b929c8..c3c9f4b 100644 --- a/src/views/Selection/TranBox.js +++ b/src/views/Selection/TranBox.js @@ -20,7 +20,7 @@ import { isMobile } from "../../libs/mobile"; import TranForm from "./TranForm.js"; function Header({ - setShowPopup, + setShowBox, simpleStyle, setSimpleStyle, hideClickAway, @@ -98,7 +98,7 @@ function Header({ { - setShowPopup(false); + setShowBox(false); }} > @@ -111,6 +111,7 @@ function Header({ } export default function TranBox({ + showBox, text, setText, setShowBox, @@ -134,43 +135,45 @@ export default function TranBox({ return ( - - } - onClick={(e) => e.stopPropagation()} - onMouseEnter={() => setMouseHover(true)} - onMouseLeave={() => setMouseHover(false)} - > - - - - + {showBox && ( + + } + onClick={(e) => e.stopPropagation()} + onMouseEnter={() => setMouseHover(true)} + onMouseLeave={() => setMouseHover(false)} + > + + + + + )} ); diff --git a/src/views/Selection/index.js b/src/views/Selection/index.js index c3d0bef..b54f827 100644 --- a/src/views/Selection/index.js +++ b/src/views/Selection/index.js @@ -10,6 +10,7 @@ import { OPT_TRANBOX_TRIGGER_CLICK, OPT_TRANBOX_TRIGGER_HOVER, OPT_TRANBOX_TRIGGER_SELECT, + EVENT_KISS, } from "../../config"; import { isMobile } from "../../libs/mobile"; import { kissLog } from "../../libs/log"; @@ -167,12 +168,26 @@ export default function Slection({ }; }, [tranboxShortcut, handleTranbox]); + const handleToggle = useCallback(() => { + if (showBox) { + setShowBox(false); + } else { + handleTranbox(); + } + }, [showBox, handleTranbox]); + useEffect(() => { - window.addEventListener(MSG_OPEN_TRANBOX, handleTranbox); - return () => { - window.removeEventListener(MSG_OPEN_TRANBOX, handleTranbox); + const handleStatusUpdate = (event) => { + if (event.detail?.action === MSG_OPEN_TRANBOX) { + handleToggle(); + } }; - }, [handleTranbox]); + + document.addEventListener(EVENT_KISS, handleStatusUpdate); + return () => { + document.removeEventListener(EVENT_KISS, handleStatusUpdate); + }; + }, [handleToggle]); useEffect(() => { if (!isGm) { @@ -217,8 +232,9 @@ export default function Slection({ return ( <> - {showBox && ( + { - )} + } {showBtn && (