tranbox...

This commit is contained in:
Gabe Yuan
2023-10-24 17:58:37 +08:00
parent 4125aba808
commit 02f26af592
9 changed files with 274 additions and 35 deletions

View File

@@ -114,7 +114,7 @@ export const apiTranslate = async ({
OPT_LANGS_SPECIAL[translator].get(fromLang) ?? OPT_LANGS_SPECIAL[translator].get(fromLang) ??
OPT_LANGS_SPECIAL[translator].get("auto"); OPT_LANGS_SPECIAL[translator].get("auto");
const to = OPT_LANGS_SPECIAL[translator].get(toLang); const to = OPT_LANGS_SPECIAL[translator].get(toLang);
if (!to) { if (!text || !to) {
console.log(`[trans] target lang: ${toLang} not support`); console.log(`[trans] target lang: ${toLang} not support`);
return [trText, isSame]; return [trText, isSame];
} }

View File

@@ -78,7 +78,10 @@ export async function showFab(translator) {
); );
} }
export function showTransbox({ tranboxSetting = DEFAULT_TRANBOX_SETTING }) { export function showTransbox({
tranboxSetting = DEFAULT_TRANBOX_SETTING,
transApis,
}) {
if (!tranboxSetting?.transOpen) { if (!tranboxSetting?.transOpen) {
return; return;
} }
@@ -99,7 +102,7 @@ export function showTransbox({ tranboxSetting = DEFAULT_TRANBOX_SETTING }) {
ReactDOM.createRoot(shadowRootElement).render( ReactDOM.createRoot(shadowRootElement).render(
<React.StrictMode> <React.StrictMode>
<CacheProvider value={cache}> <CacheProvider value={cache}>
<Slection tranboxSetting={tranboxSetting} /> <Slection tranboxSetting={tranboxSetting} transApis={transApis} />
</CacheProvider> </CacheProvider>
</React.StrictMode> </React.StrictMode>
); );

View File

@@ -623,12 +623,20 @@ export const I18N = {
zh: `显示翻译框快捷键`, zh: `显示翻译框快捷键`,
en: `Toggle Translate Box Shortcut`, en: `Toggle Translate Box Shortcut`,
}, },
tanbtn_offset_x: { tranbtn_offset_x: {
zh: `翻译按钮偏移X`, zh: `翻译按钮偏移X`,
en: `Translate Button Offset (X)`, en: `Translate Button Offset (X)`,
}, },
tanbtn_offset_y: { tranbtn_offset_y: {
zh: `翻译按钮偏移Y`, zh: `翻译按钮偏移Y`,
en: `Translate Button Offset (Y)`, en: `Translate Button Offset (Y)`,
}, },
translated_text: {
zh: `译文`,
en: `Translated Text`,
},
original_text: {
zh: `原文`,
en: `Original Text`,
},
}; };

View File

@@ -223,3 +223,13 @@ export const matchInputStr = (str, sign) => {
} }
return str.match(reg); return str.match(reg);
}; };
/**
* 判断是否英文单词
* @param {*} str
* @returns
*/
export const isValidWord = (str) => {
const regex = /^[a-zA-Z-]+$/;
return regex.test(str);
};

View File

@@ -110,7 +110,7 @@ export default function Tranbox() {
<TextField <TextField
size="small" size="small"
label={i18n("tanbtn_offset_x")} label={i18n("tranbtn_offset_x")}
type="number" type="number"
name="btnOffsetX" name="btnOffsetX"
defaultValue={btnOffsetX} defaultValue={btnOffsetX}
@@ -119,7 +119,7 @@ export default function Tranbox() {
<TextField <TextField
size="small" size="small"
label={i18n("tanbtn_offset_y")} label={i18n("tranbtn_offset_y")}
type="number" type="number"
name="btnOffsetY" name="btnOffsetY"
defaultValue={btnOffsetY} defaultValue={btnOffsetY}

View File

@@ -1,4 +1,4 @@
import { useState } from "react"; import { useEffect, useState } from "react";
import Paper from "@mui/material/Paper"; import Paper from "@mui/material/Paper";
import Box from "@mui/material/Box"; import Box from "@mui/material/Box";
@@ -75,6 +75,7 @@ function Pointer({
w = origin.w + dx; w = origin.w + dx;
h = origin.h + dy; h = origin.h + dy;
break; break;
default:
} }
if (w < minSize.w) { if (w < minSize.w) {
@@ -134,7 +135,8 @@ export default function DraggableResizable({
w: 1200, w: 1200,
h: 1200, h: 1200,
}, },
sx, onChangeSize,
onChangePosition,
}) { }) {
const lineWidth = 4; const lineWidth = 4;
const [position, setPosition] = useState(defaultPosition); const [position, setPosition] = useState(defaultPosition);
@@ -149,6 +151,14 @@ export default function DraggableResizable({
maxSize, maxSize,
}; };
useEffect(() => {
onChangeSize && onChangeSize(size);
}, [size, onChangeSize]);
useEffect(() => {
onChangePosition && onChangePosition(position);
}, [position, onChangePosition]);
return ( return (
<Box <Box
style={{ style={{
@@ -158,7 +168,7 @@ export default function DraggableResizable({
display: "grid", display: "grid",
gridTemplateColumns: `${lineWidth * 2}px auto ${lineWidth * 2}px`, gridTemplateColumns: `${lineWidth * 2}px auto ${lineWidth * 2}px`,
gridTemplateRows: `${lineWidth * 2}px auto ${lineWidth * 2}px`, gridTemplateRows: `${lineWidth * 2}px auto ${lineWidth * 2}px`,
zIndex: 10000, zIndex: 2147483647,
}} }}
> >
<Pointer <Pointer

View File

@@ -8,21 +8,139 @@ import MenuItem from "@mui/material/MenuItem";
import Grid from "@mui/material/Grid"; import Grid from "@mui/material/Grid";
import Box from "@mui/material/Box"; import Box from "@mui/material/Box";
import Divider from "@mui/material/Divider"; import Divider from "@mui/material/Divider";
import Alert from "@mui/material/Alert";
import CircularProgress from "@mui/material/CircularProgress";
import { useI18n } from "../../hooks/I18n"; import { useI18n } from "../../hooks/I18n";
import { OPT_TRANS_ALL, OPT_LANGS_FROM, OPT_LANGS_TO } from "../../config"; import {
OPT_TRANS_ALL,
OPT_LANGS_FROM,
OPT_LANGS_TO,
DEFAULT_TRANS_APIS,
OPT_TRANS_BAIDU,
} from "../../config";
import { useEffect, useState, useRef } from "react";
import { apiTranslate } from "../../apis";
import { isValidWord } from "../../libs/utils";
function TranForm({ tranboxSetting }) { const exchangeMap = {
word_third: "第三人称单数",
word_ing: "现在分词",
word_done: "过去式",
word_past: "过去分词",
word_pl: "复数",
word_proto: "原词",
};
function DictCont({ dictResult }) {
if (!dictResult) {
return;
}
return (
<Box>
<h4>{dictResult.simple_means?.word_name}</h4>
<p>
{Object.entries(dictResult.simple_means?.exchange || {}).map(
([key, val]) => (
<span key={key}>{`${exchangeMap[key] || key}: ${val.join(
","
)}; `}</span>
)
)}
</p>
<p>
{Object.values(dictResult.simple_means?.tags || {})
.map((vals) => vals.join(", "))
.join(", ")}
</p>
{dictResult.simple_means?.symbols?.map(({ ph_en, ph_am, parts }, idx) => (
<div key={idx}>
<p>{`英: [${ph_en}] 美: [${ph_am}]`}</p>
{parts.map(({ part, means }, idx) => (
<p key={idx}>{`[${part}]: ${means.join("; ")}`}</p>
))}
</div>
))}
</Box>
);
}
function TranCont({ text, translator, fromLang, toLang, transApis }) {
const i18n = useI18n();
const [trText, setTrText] = useState("");
const [loading, setLoading] = useState(false);
const [error, setError] = useState("");
const [dictResult, setDictResult] = useState(null);
useEffect(() => {
(async () => {
try {
setLoading(true);
setTrText("");
setError("");
setDictResult(null);
const apis = { ...transApis, ...DEFAULT_TRANS_APIS };
const apiSetting = apis[translator];
const tranRes = await apiTranslate({
text,
translator,
fromLang,
toLang,
apiSetting,
});
setTrText(tranRes[0]);
// 词典
if (isValidWord(text) && toLang.startsWith("zh")) {
if (fromLang === "en" && translator === OPT_TRANS_BAIDU) {
setDictResult(tranRes[2]);
} else {
const dictRes = await apiTranslate({
text,
translator: OPT_TRANS_BAIDU,
fromLang: "en",
toLang: "zh-CN",
apiSetting: apis[OPT_TRANS_BAIDU],
});
setDictResult(dictRes[2].dict_result);
}
}
} catch (err) {
setError(err.message);
} finally {
setLoading(false);
}
})();
}, [text, translator, fromLang, toLang, transApis]);
return (
<>
<Box>
<TextField
label={i18n("translated_text")}
fullWidth
multiline
value={trText}
/>
</Box>
{loading && <CircularProgress size={24} />}
{error && <Alert severity="error">{error}</Alert>}
{dictResult && <DictCont dictResult={dictResult} />}
</>
);
}
function TranForm({ text, setText, tranboxSetting, transApis }) {
const i18n = useI18n(); const i18n = useI18n();
const { const [editMode, setEditMode] = useState(false);
transOpen, const [editText, setEditText] = useState("");
translator, const [translator, setTranslator] = useState(tranboxSetting.translator);
fromLang, const [fromLang, setFromLang] = useState(tranboxSetting.fromLang);
toLang, const [toLang, setToLang] = useState(tranboxSetting.toLang);
tranboxShortcut, const inputRef = useRef(null);
btnOffsetX,
btnOffsetY,
} = tranboxSetting;
return ( return (
<Stack sx={{ p: 2 }} spacing={2}> <Stack sx={{ p: 2 }} spacing={2}>
@@ -37,7 +155,9 @@ function TranForm({ tranboxSetting }) {
name="fromLang" name="fromLang"
value={fromLang} value={fromLang}
label={i18n("from_lang")} label={i18n("from_lang")}
// onChange={handleChange} onChange={(e) => {
setFromLang(e.target.value);
}}
> >
{OPT_LANGS_FROM.map(([lang, name]) => ( {OPT_LANGS_FROM.map(([lang, name]) => (
<MenuItem key={lang} value={lang}> <MenuItem key={lang} value={lang}>
@@ -55,7 +175,9 @@ function TranForm({ tranboxSetting }) {
name="toLang" name="toLang"
value={toLang} value={toLang}
label={i18n("to_lang")} label={i18n("to_lang")}
// onChange={handleChange} onChange={(e) => {
setToLang(e.target.value);
}}
> >
{OPT_LANGS_TO.map(([lang, name]) => ( {OPT_LANGS_TO.map(([lang, name]) => (
<MenuItem key={lang} value={lang}> <MenuItem key={lang} value={lang}>
@@ -73,7 +195,9 @@ function TranForm({ tranboxSetting }) {
value={translator} value={translator}
name="translator" name="translator"
label={i18n("translate_service")} label={i18n("translate_service")}
// onChange={handleChange} onChange={(e) => {
setTranslator(e.target.value);
}}
> >
{OPT_TRANS_ALL.map((item) => ( {OPT_TRANS_ALL.map((item) => (
<MenuItem key={item} value={item}> <MenuItem key={item} value={item}>
@@ -84,20 +208,72 @@ function TranForm({ tranboxSetting }) {
</Grid> </Grid>
</Grid> </Grid>
</Box> </Box>
<Box>
<TextField
label={i18n("original_text")}
inputRef={inputRef}
fullWidth
multiline
value={editMode ? editText : text}
disabled={!editMode}
onChange={(e) => {
setEditText(e.target.value);
}}
onClick={() => {
setEditMode(true);
setEditText(text);
const timer = setTimeout(() => {
clearTimeout(timer);
inputRef.current?.focus();
}, 100);
}}
onBlur={() => {
setEditMode(false);
setText(editText.trim());
}}
/>
</Box>
<TranCont
text={text}
translator={translator}
fromLang={fromLang}
toLang={toLang}
transApis={transApis}
/>
</Stack> </Stack>
); );
} }
export default function TranBox({ position, setShowBox, tranboxSetting }) { export default function TranBox({
text,
setText,
setShowBox,
tranboxSetting,
transApis,
boxSize,
setBoxSize,
boxPosition,
setBoxPosition,
}) {
return ( return (
<SettingProvider> <SettingProvider>
<ThemeProvider> <ThemeProvider>
<DraggableResizable <DraggableResizable
defaultPosition={position} defaultPosition={boxPosition}
defaultSize={boxSize}
header={<Header setShowPopup={setShowBox} />} header={<Header setShowPopup={setShowBox} />}
onChangeSize={setBoxSize}
onChangePosition={setBoxPosition}
> >
<Divider /> <Divider />
<TranForm tranboxSetting={tranboxSetting} /> <TranForm
text={text}
setText={setText}
tranboxSetting={tranboxSetting}
transApis={transApis}
/>
</DraggableResizable> </DraggableResizable>
</ThemeProvider> </ThemeProvider>
</SettingProvider> </SettingProvider>

View File

@@ -6,6 +6,7 @@ export default function TranBtn({ onClick, position, tranboxSetting }) {
position: "fixed", position: "fixed",
left: position.x + tranboxSetting.btnOffsetX, left: position.x + tranboxSetting.btnOffsetX,
top: position.y + tranboxSetting.btnOffsetY, top: position.y + tranboxSetting.btnOffsetY,
zIndex: 2147483647,
}} }}
onClick={onClick} onClick={onClick}
onMouseUp={(e) => { onMouseUp={(e) => {

View File

@@ -1,26 +1,38 @@
import { useState, useEffect } from "react"; import { useState, useEffect } from "react";
import TranBtn from "./Tranbtn"; import TranBtn from "./Tranbtn";
import TranBox from "./Tranbox"; import TranBox from "./Tranbox";
import { shortcutRegister } from "../../libs/shortcut";
export default function Slection({ tranboxSetting }) { export default function Slection({ tranboxSetting, transApis }) {
const [showBox, setShowBox] = useState(false); const [showBox, setShowBox] = useState(false);
const [showBtn, setShowBtn] = useState(false); const [showBtn, setShowBtn] = useState(false);
const [selectedText, setSelText] = useState("");
const [text, setText] = useState(""); const [text, setText] = useState("");
const [position, setPosition] = useState({ x: 0, y: 0 }); const [position, setPosition] = useState({ x: 0, y: 0 });
const [boxSize, setBoxSize] = useState({ w: 600, h: 400 });
console.log("tranboxSetting", tranboxSetting); const [boxPosition, setBoxPosition] = useState({
x: (window.innerWidth - 600) / 2,
y: (window.innerHeight - 400) / 2,
});
function handleMouseup(e) { function handleMouseup(e) {
const text = window.getSelection()?.toString()?.trim() || ""; const selectedText = window.getSelection()?.toString()?.trim() || "";
if (!selectedText) {
setShowBtn(false);
return;
}
setSelText(selectedText);
setShowBtn(true);
setPosition({ x: e.clientX, y: e.clientY }); setPosition({ x: e.clientX, y: e.clientY });
setText(text);
setShowBtn(!!text);
} }
const handleClick = (e) => { const handleClick = (e) => {
e.stopPropagation(); e.stopPropagation();
setShowBtn(false); setShowBtn(false);
if (!!text) {
setText(selectedText);
if (!showBox) {
setShowBox(true); setShowBox(true);
} }
}; };
@@ -32,12 +44,31 @@ export default function Slection({ tranboxSetting }) {
}; };
}, []); }, []);
useEffect(() => {
const clearShortcut = shortcutRegister(
tranboxSetting.tranboxShortcut,
() => {
setShowBox((pre) => !pre);
}
);
return () => {
clearShortcut();
};
}, [tranboxSetting.tranboxShortcut, setShowBox]);
return ( return (
<> <>
{showBox && ( {showBox && (
<TranBox <TranBox
position={position} text={text}
setText={setText}
boxSize={boxSize}
setBoxSize={setBoxSize}
boxPosition={boxPosition}
setBoxPosition={setBoxPosition}
tranboxSetting={tranboxSetting} tranboxSetting={tranboxSetting}
transApis={transApis}
setShowBox={setShowBox} setShowBox={setShowBox}
/> />
)} )}