Files
kiss-translator/src/views/Selection/index.js

297 lines
7.7 KiB
JavaScript
Raw Normal View History

2024-04-16 12:47:55 +08:00
import { useState, useEffect, useCallback, useMemo } from "react";
2023-10-26 12:24:24 +08:00
import TranBtn from "./TranBtn";
import TranBox from "./TranBox";
2024-01-04 12:18:36 +08:00
import { shortcutRegister } from "../../libs/shortcut";
import { sleep, limitNumber } from "../../libs/utils";
2024-01-04 12:18:36 +08:00
import { isGm, isExt } from "../../libs/client";
2024-04-16 12:47:55 +08:00
import {
MSG_OPEN_TRANBOX,
DEFAULT_TRANBOX_SHORTCUT,
OPT_TRANBOX_TRIGGER_CLICK,
OPT_TRANBOX_TRIGGER_HOVER,
OPT_TRANBOX_TRIGGER_SELECT,
2025-10-22 01:50:49 +08:00
EVENT_KISS,
2024-04-16 12:47:55 +08:00
} from "../../config";
import { isMobile } from "../../libs/mobile";
2024-03-19 18:07:18 +08:00
import { kissLog } from "../../libs/log";
2024-04-20 15:54:41 +08:00
import { useLangMap } from "../../hooks/I18n";
import { debouncePutTranBox, getTranBox } from "../../libs/storage";
2023-10-23 18:02:42 +08:00
2024-02-05 11:28:34 +08:00
export default function Slection({
contextMenuType,
tranboxSetting,
transApis,
2024-04-20 15:54:41 +08:00
uiLang,
2024-05-22 23:33:30 +08:00
langDetector,
2024-02-05 11:28:34 +08:00
}) {
2024-04-16 12:47:55 +08:00
const {
hideTranBtn = false,
simpleStyle: initSimpleStyle = false,
hideClickAway: initHideClickAway = false,
2024-04-20 18:07:16 +08:00
followSelection: initFollowMouse = false,
2024-04-16 12:47:55 +08:00
tranboxShortcut = DEFAULT_TRANBOX_SHORTCUT,
triggerMode = OPT_TRANBOX_TRIGGER_CLICK,
2025-10-02 21:59:31 +08:00
// extStyles,
2024-04-21 19:19:06 +08:00
btnOffsetX,
btnOffsetY,
boxOffsetX = 0,
boxOffsetY = 10,
2024-04-16 12:47:55 +08:00
} = tranboxSetting;
2024-04-16 00:54:37 +08:00
const boxWidth =
2024-04-16 12:47:55 +08:00
isMobile || initSimpleStyle
2024-04-16 00:54:37 +08:00
? 300
: limitNumber(window.innerWidth, 300, 600);
const boxHeight =
2024-04-16 12:47:55 +08:00
isMobile || initSimpleStyle
2024-04-16 00:54:37 +08:00
? 200
: limitNumber(window.innerHeight, 200, 400);
2024-04-20 15:54:41 +08:00
const langMap = useLangMap(uiLang);
2023-10-23 18:02:42 +08:00
const [showBox, setShowBox] = useState(false);
const [showBtn, setShowBtn] = useState(false);
2023-10-24 17:58:37 +08:00
const [selectedText, setSelText] = useState("");
2023-10-23 18:02:42 +08:00
const [text, setText] = useState("");
const [position, setPosition] = useState({ x: 0, y: 0 });
const [boxSize, setBoxSize] = useState({
w: boxWidth,
h: boxHeight,
});
2023-10-24 17:58:37 +08:00
const [boxPosition, setBoxPosition] = useState({
x: (window.innerWidth - boxWidth) / 2,
y: (window.innerHeight - boxHeight) / 2,
2023-10-24 17:58:37 +08:00
});
2024-04-16 12:47:55 +08:00
const [simpleStyle, setSimpleStyle] = useState(initSimpleStyle);
const [hideClickAway, setHideClickAway] = useState(initHideClickAway);
2024-04-20 18:07:16 +08:00
const [followSelection, setFollowSelection] = useState(initFollowMouse);
2024-04-16 12:47:55 +08:00
const handleTrigger = useCallback(
(text) => {
setShowBtn(false);
setText(text || selectedText);
setShowBox(true);
},
[selectedText]
2024-04-16 00:54:37 +08:00
);
2023-10-23 18:02:42 +08:00
const handleTranbox = useCallback(() => {
2023-11-21 11:20:05 +08:00
setShowBtn(false);
2023-11-11 17:22:43 +08:00
2024-04-20 18:07:16 +08:00
const selection = window.getSelection();
const selectedText = selection?.toString()?.trim() || "";
2023-10-24 17:58:37 +08:00
if (!selectedText) {
setShowBox((pre) => !pre);
2023-10-24 17:58:37 +08:00
return;
}
2024-04-20 18:07:16 +08:00
const rect = selection?.getRangeAt(0)?.getBoundingClientRect();
if (rect && followSelection) {
2024-04-21 19:19:06 +08:00
const x = (rect.left + rect.right) / 2 + boxOffsetX;
const y = rect.bottom + boxOffsetY;
2024-04-20 18:07:16 +08:00
setBoxPosition({
2024-04-21 19:19:06 +08:00
x: limitNumber(x, 0, window.innerWidth - 300),
y: limitNumber(y, 0, window.innerHeight - 200),
2024-04-20 18:07:16 +08:00
});
}
2023-10-24 17:58:37 +08:00
setSelText(selectedText);
2023-11-21 11:20:05 +08:00
setText(selectedText);
setShowBox(true);
2024-04-21 19:19:06 +08:00
}, [followSelection, boxOffsetX, boxOffsetY]);
2023-10-23 18:02:42 +08:00
2024-04-16 12:47:55 +08:00
const btnEvent = useMemo(() => {
if (isMobile) {
return "onTouchEnd";
} else if (triggerMode === OPT_TRANBOX_TRIGGER_HOVER) {
return "onMouseOver";
}
return "onMouseUp";
}, [triggerMode]);
useEffect(() => {
(async () => {
try {
const { w, h, x, y } = (await getTranBox()) || {};
if (w !== undefined && h !== undefined) {
setBoxSize({ w, h });
}
if (x !== undefined && y !== undefined) {
setBoxPosition({
x: limitNumber(x, 0, window.innerWidth),
y: limitNumber(y, 0, window.innerHeight),
});
}
} catch (err) {
//
}
})();
}, []);
useEffect(() => {
debouncePutTranBox({ ...boxSize, ...boxPosition });
}, [boxSize, boxPosition]);
2023-11-21 11:20:05 +08:00
useEffect(() => {
async function handleMouseup(e) {
e.stopPropagation();
2024-04-16 12:47:55 +08:00
await sleep(200);
2023-10-24 17:58:37 +08:00
2024-04-20 18:07:16 +08:00
const selection = window.getSelection();
const selectedText = selection?.toString()?.trim() || "";
2023-11-21 11:20:05 +08:00
setSelText(selectedText);
if (!selectedText) {
setShowBtn(false);
return;
}
2024-04-20 18:07:16 +08:00
const rect = selection?.getRangeAt(0)?.getBoundingClientRect();
if (rect && followSelection) {
2024-04-21 19:19:06 +08:00
const x = (rect.left + rect.right) / 2 + boxOffsetX;
const y = rect.bottom + boxOffsetY;
2024-04-20 18:07:16 +08:00
setBoxPosition({
2024-04-21 19:19:06 +08:00
x: limitNumber(x, 0, window.innerWidth - 300),
y: limitNumber(y, 0, window.innerHeight - 200),
2024-04-20 18:07:16 +08:00
});
}
2024-04-16 12:47:55 +08:00
if (triggerMode === OPT_TRANBOX_TRIGGER_SELECT) {
handleTrigger(selectedText);
return;
}
2024-04-16 00:54:37 +08:00
const { clientX, clientY } = isMobile ? e.changedTouches[0] : e;
2024-04-16 12:47:55 +08:00
setShowBtn(!hideTranBtn);
2024-04-16 00:54:37 +08:00
setPosition({ x: clientX, y: clientY });
2023-10-23 18:02:42 +08:00
}
2024-01-04 10:39:40 +08:00
// todo: mobile support
2024-04-07 16:55:54 +08:00
// window.addEventListener("mouseup", handleMouseup);
window.addEventListener(isMobile ? "touchend" : "mouseup", handleMouseup);
2023-10-23 18:02:42 +08:00
return () => {
window.removeEventListener(
isMobile ? "touchend" : "mouseup",
handleMouseup
);
2023-10-23 18:02:42 +08:00
};
2024-04-21 19:19:06 +08:00
}, [
hideTranBtn,
triggerMode,
followSelection,
boxOffsetX,
boxOffsetY,
handleTrigger,
]);
2023-10-23 18:02:42 +08:00
2024-01-04 12:18:36 +08:00
useEffect(() => {
if (isExt) {
return;
}
2024-04-16 12:47:55 +08:00
const clearShortcut = shortcutRegister(tranboxShortcut, handleTranbox);
2024-01-04 12:18:36 +08:00
return () => {
clearShortcut();
};
2024-04-16 12:47:55 +08:00
}, [tranboxShortcut, handleTranbox]);
2024-01-04 12:18:36 +08:00
2025-10-22 01:50:49 +08:00
const handleToggle = useCallback(() => {
if (showBox) {
setShowBox(false);
} else {
handleTranbox();
}
}, [showBox, handleTranbox]);
2023-11-21 11:20:05 +08:00
useEffect(() => {
2025-10-22 01:50:49 +08:00
const handleStatusUpdate = (event) => {
if (event.detail?.action === MSG_OPEN_TRANBOX) {
handleToggle();
}
};
document.addEventListener(EVENT_KISS, handleStatusUpdate);
2023-11-21 11:20:05 +08:00
return () => {
2025-10-22 01:50:49 +08:00
document.removeEventListener(EVENT_KISS, handleStatusUpdate);
2023-11-21 11:20:05 +08:00
};
2025-10-22 01:50:49 +08:00
}, [handleToggle]);
2023-11-22 11:02:48 +08:00
useEffect(() => {
if (!isGm) {
return;
}
// 注册菜单
try {
const menuCommandIds = [];
2024-02-05 11:28:34 +08:00
contextMenuType !== 0 &&
2023-12-15 11:43:01 +08:00
menuCommandIds.push(
GM.registerMenuCommand(
2024-04-20 15:54:41 +08:00
langMap("translate_selected_text"),
2023-12-15 11:43:01 +08:00
(event) => {
handleTranbox();
},
"S"
)
);
return () => {
menuCommandIds.forEach((id) => {
GM.unregisterMenuCommand(id);
});
};
} catch (err) {
kissLog("registerMenuCommand", err);
}
2024-04-20 15:54:41 +08:00
}, [handleTranbox, contextMenuType, langMap]);
2024-04-01 12:25:59 +08:00
useEffect(() => {
2024-04-16 00:54:37 +08:00
if (hideClickAway) {
2024-04-01 12:25:59 +08:00
const handleHideBox = () => {
setShowBox(false);
};
window.addEventListener("click", handleHideBox);
return () => {
window.removeEventListener("click", handleHideBox);
};
}
2024-04-16 00:54:37 +08:00
}, [hideClickAway]);
2024-04-01 12:25:59 +08:00
2023-10-23 18:02:42 +08:00
return (
<>
2025-10-22 01:50:49 +08:00
{
2023-10-23 18:02:42 +08:00
<TranBox
2025-10-22 01:50:49 +08:00
showBox={showBox}
2023-10-24 17:58:37 +08:00
text={text}
setText={setText}
boxSize={boxSize}
setBoxSize={setBoxSize}
boxPosition={boxPosition}
setBoxPosition={setBoxPosition}
2023-10-23 18:02:42 +08:00
tranboxSetting={tranboxSetting}
2023-10-24 17:58:37 +08:00
transApis={transApis}
2023-10-23 18:02:42 +08:00
setShowBox={setShowBox}
2024-04-16 00:54:37 +08:00
simpleStyle={simpleStyle}
setSimpleStyle={setSimpleStyle}
hideClickAway={hideClickAway}
setHideClickAway={setHideClickAway}
2024-04-20 18:07:16 +08:00
followSelection={followSelection}
setFollowSelection={setFollowSelection}
2025-10-02 21:59:31 +08:00
// extStyles={extStyles}
2024-05-22 23:33:30 +08:00
langDetector={langDetector}
2023-10-23 18:02:42 +08:00
/>
2025-10-22 01:50:49 +08:00
}
2023-10-23 18:02:42 +08:00
{showBtn && (
<TranBtn
position={position}
2024-04-21 19:19:06 +08:00
btnOffsetX={btnOffsetX}
btnOffsetY={btnOffsetY}
2024-04-16 12:47:55 +08:00
btnEvent={btnEvent}
onTrigger={(e) => {
e.stopPropagation();
handleTrigger();
}}
2023-10-23 18:02:42 +08:00
/>
)}
</>
);
}