userscript...
This commit is contained in:
1
.env
1
.env
@@ -3,3 +3,4 @@ GENERATE_SOURCEMAP=false
|
|||||||
REACT_APP_NAME=KISS Translator
|
REACT_APP_NAME=KISS Translator
|
||||||
REACT_APP_VERSION=1.2.3
|
REACT_APP_VERSION=1.2.3
|
||||||
REACT_APP_HOMEPAGE=https://github.com/fishjar/kiss-translator
|
REACT_APP_HOMEPAGE=https://github.com/fishjar/kiss-translator
|
||||||
|
REACT_APP_OPTIONSPAGE=https://github.com/fishjar/kiss-translator
|
||||||
|
|||||||
@@ -4,36 +4,6 @@ const { WebpackManifestPlugin } = require("webpack-manifest-plugin");
|
|||||||
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
|
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
|
||||||
const webpack = require("webpack");
|
const webpack = require("webpack");
|
||||||
|
|
||||||
const isEnvUserscript = process.env.REACT_APP_CLIENT === "userscript";
|
|
||||||
|
|
||||||
const banner = `// ==UserScript==
|
|
||||||
// @name ${process.env.REACT_APP_NAME}
|
|
||||||
// @namespace ${process.env.REACT_APP_HOMEPAGE}
|
|
||||||
// @version ${process.env.REACT_APP_VERSION}
|
|
||||||
// @description A minimalist bilingual translation extension.
|
|
||||||
// @author Gabe<yugang2002@gmail.com>
|
|
||||||
// @homepageURL ${process.env.REACT_APP_HOMEPAGE}
|
|
||||||
// @match *://*/*
|
|
||||||
// @icon https://raw.githubusercontent.com/fishjar/kiss-translator/master/public/images/logo192.png
|
|
||||||
// @downloadURL https://raw.githubusercontent.com/fishjar/kiss-translator/master/dist/userscript/kiss-translator.user.js
|
|
||||||
// @updateURL https://raw.githubusercontent.com/fishjar/kiss-translator/master/dist/userscript/kiss-translator.user.js
|
|
||||||
// @grant GM_xmlhttpRequest
|
|
||||||
// @grant GM.xmlhttpRequest
|
|
||||||
// @grant GM_setValue
|
|
||||||
// @grant GM.setValue
|
|
||||||
// @grant GM_getValue
|
|
||||||
// @grant GM.getValue
|
|
||||||
// @grant GM_deleteValue
|
|
||||||
// @grant GM.deleteValue
|
|
||||||
// @connect translate.googleapis.com
|
|
||||||
// @connect api-edge.cognitive.microsofttranslator.com
|
|
||||||
// @connect edge.microsoft.com
|
|
||||||
// @connect api.openai.com
|
|
||||||
// @connect localhost
|
|
||||||
// ==/UserScript==
|
|
||||||
|
|
||||||
`;
|
|
||||||
|
|
||||||
// 扩展
|
// 扩展
|
||||||
const extWebpack = (config, env) => {
|
const extWebpack = (config, env) => {
|
||||||
const isEnvProduction = env === "production";
|
const isEnvProduction = env === "production";
|
||||||
@@ -106,22 +76,60 @@ const extWebpack = (config, env) => {
|
|||||||
|
|
||||||
// 油猴
|
// 油猴
|
||||||
const userscriptWebpack = (config, env) => {
|
const userscriptWebpack = (config, env) => {
|
||||||
const names = [
|
const banner = `// ==UserScript==
|
||||||
"HtmlWebpackPlugin",
|
// @name ${process.env.REACT_APP_NAME}
|
||||||
"WebpackManifestPlugin",
|
// @namespace ${process.env.REACT_APP_HOMEPAGE}
|
||||||
"MiniCssExtractPlugin",
|
// @version ${process.env.REACT_APP_VERSION}
|
||||||
];
|
// @description A minimalist bilingual translation extension.
|
||||||
|
// @author Gabe<yugang2002@gmail.com>
|
||||||
|
// @homepageURL ${process.env.REACT_APP_HOMEPAGE}
|
||||||
|
// @match *://*/*
|
||||||
|
// @icon https://raw.githubusercontent.com/fishjar/kiss-translator/master/public/images/logo192.png
|
||||||
|
// @downloadURL https://raw.githubusercontent.com/fishjar/kiss-translator/master/dist/userscript/kiss-translator.user.js
|
||||||
|
// @updateURL https://raw.githubusercontent.com/fishjar/kiss-translator/master/dist/userscript/kiss-translator.user.js
|
||||||
|
// @grant GM_xmlhttpRequest
|
||||||
|
// @grant GM.xmlhttpRequest
|
||||||
|
// @grant GM_setValue
|
||||||
|
// @grant GM.setValue
|
||||||
|
// @grant GM_getValue
|
||||||
|
// @grant GM.getValue
|
||||||
|
// @grant GM_deleteValue
|
||||||
|
// @grant GM.deleteValue
|
||||||
|
// @connect translate.googleapis.com
|
||||||
|
// @connect api-edge.cognitive.microsofttranslator.com
|
||||||
|
// @connect edge.microsoft.com
|
||||||
|
// @connect api.openai.com
|
||||||
|
// @connect localhost
|
||||||
|
// ==/UserScript==
|
||||||
|
|
||||||
config.entry = {
|
`;
|
||||||
"kiss-translator-options": paths.appSrc + "/userscriptOptions.js",
|
|
||||||
"kiss-translator.user": paths.appSrc + "/userscript.js",
|
|
||||||
};
|
|
||||||
|
|
||||||
config.output.filename = "[name].js";
|
config.entry = paths.appSrc + "/userscript.js";
|
||||||
|
config.output.filename = "kiss-translator.user.js";
|
||||||
config.optimization.splitChunks = { cacheGroups: { default: false } };
|
config.optimization.splitChunks = { cacheGroups: { default: false } };
|
||||||
config.optimization.runtimeChunk = false;
|
config.optimization.runtimeChunk = false;
|
||||||
config.optimization.minimize = false;
|
config.optimization.minimize = false;
|
||||||
|
|
||||||
|
config.plugins.push(
|
||||||
|
new webpack.BannerPlugin({
|
||||||
|
banner,
|
||||||
|
raw: true,
|
||||||
|
entryOnly: true,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
return config;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 文档
|
||||||
|
const webWebpack = (config, env) => {
|
||||||
|
const names = ["HtmlWebpackPlugin"];
|
||||||
|
|
||||||
|
config.entry = {
|
||||||
|
main: paths.appSrc + "/userscriptIndex.js",
|
||||||
|
options: paths.appSrc + "/userscriptOptions.js",
|
||||||
|
};
|
||||||
|
|
||||||
config.plugins = config.plugins.filter(
|
config.plugins = config.plugins.filter(
|
||||||
(plugin) => !names.includes(plugin.constructor.name)
|
(plugin) => !names.includes(plugin.constructor.name)
|
||||||
);
|
);
|
||||||
@@ -129,21 +137,33 @@ const userscriptWebpack = (config, env) => {
|
|||||||
config.plugins.push(
|
config.plugins.push(
|
||||||
new HtmlWebpackPlugin({
|
new HtmlWebpackPlugin({
|
||||||
inject: true,
|
inject: true,
|
||||||
chunks: ["kiss-translator-options"],
|
chunks: ["main"],
|
||||||
template: paths.appHtml,
|
template: paths.appHtml,
|
||||||
filename: "kiss-translator-options.html",
|
filename: "index.html",
|
||||||
}),
|
}),
|
||||||
new webpack.BannerPlugin({
|
new HtmlWebpackPlugin({
|
||||||
banner,
|
inject: true,
|
||||||
raw: true,
|
chunks: ["options"],
|
||||||
entryOnly: true,
|
template: paths.appHtml,
|
||||||
test: "kiss-translator.user.js",
|
filename: "options.html",
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
return config;
|
return config;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let webpackConfig;
|
||||||
|
switch (process.env.REACT_APP_CLIENT) {
|
||||||
|
case "userscript":
|
||||||
|
webpackConfig = userscriptWebpack;
|
||||||
|
break;
|
||||||
|
case "web":
|
||||||
|
webpackConfig = webWebpack;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
webpackConfig = extWebpack;
|
||||||
|
}
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
webpack: isEnvUserscript ? userscriptWebpack : extWebpack,
|
webpack: webpackConfig,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -24,7 +24,8 @@
|
|||||||
"build:edge": "BUILD_PATH=./build/edge REACT_APP_CLIENT=edge react-app-rewired build && rm ./build/edge/manifest.firefox.json",
|
"build:edge": "BUILD_PATH=./build/edge REACT_APP_CLIENT=edge react-app-rewired build && rm ./build/edge/manifest.firefox.json",
|
||||||
"build:firefox": "BUILD_PATH=./build/firefox REACT_APP_CLIENT=firefox react-app-rewired build && rm ./build/firefox/manifest.json && mv ./build/firefox/manifest.firefox.json ./build/firefox/manifest.json",
|
"build:firefox": "BUILD_PATH=./build/firefox REACT_APP_CLIENT=firefox react-app-rewired build && rm ./build/firefox/manifest.json && mv ./build/firefox/manifest.firefox.json ./build/firefox/manifest.json",
|
||||||
"build:userscript": "BUILD_PATH=./build/userscript REACT_APP_CLIENT=userscript react-app-rewired build",
|
"build:userscript": "BUILD_PATH=./build/userscript REACT_APP_CLIENT=userscript react-app-rewired build",
|
||||||
"build:all": "yarn build && yarn build:edge && yarn build:firefox",
|
"build:web": "BUILD_PATH=./build/web REACT_APP_CLIENT=web react-app-rewired build",
|
||||||
|
"build:all": "yarn build && yarn build:edge && yarn build:firefox && yarn build:userscript && yarn build:web",
|
||||||
"dist": "yarn build:all && rm -r dist && cp -r build dist",
|
"dist": "yarn build:all && rm -r dist && cp -r build dist",
|
||||||
"test": "react-app-rewired test",
|
"test": "react-app-rewired test",
|
||||||
"eject": "react-scripts eject"
|
"eject": "react-scripts eject"
|
||||||
|
|||||||
@@ -28,6 +28,9 @@ export const MSG_FETCH_LIMIT = "fetch_limit";
|
|||||||
export const MSG_TRANS_TOGGLE = "trans_toggle";
|
export const MSG_TRANS_TOGGLE = "trans_toggle";
|
||||||
export const MSG_TRANS_GETRULE = "trans_getrule";
|
export const MSG_TRANS_GETRULE = "trans_getrule";
|
||||||
export const MSG_TRANS_PUTRULE = "trans_putrule";
|
export const MSG_TRANS_PUTRULE = "trans_putrule";
|
||||||
|
export const MSG_TRANS_CURRULE = "trans_currule";
|
||||||
|
|
||||||
|
export const EVENT_KISS = "kissEvent";
|
||||||
|
|
||||||
export const THEME_LIGHT = "light";
|
export const THEME_LIGHT = "light";
|
||||||
export const THEME_DARK = "dark";
|
export const THEME_DARK = "dark";
|
||||||
|
|||||||
32
src/hooks/Fetch.js
Normal file
32
src/hooks/Fetch.js
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
import { useEffect, useState } from "react";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* fetch data hook
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export const useFetch = (url) => {
|
||||||
|
const [data, setData] = useState(null);
|
||||||
|
const [loading, setLoading] = useState(false);
|
||||||
|
const [error, setError] = useState(null);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!url) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setLoading(true);
|
||||||
|
fetch(url)
|
||||||
|
.then((res) => {
|
||||||
|
if (res.ok) {
|
||||||
|
if (res.headers.get("Content-Type")?.includes("json")) {
|
||||||
|
return res.json().then(setData);
|
||||||
|
}
|
||||||
|
return res.text().then(setData);
|
||||||
|
}
|
||||||
|
setError(`[${res.status}] ${res.statusText}`);
|
||||||
|
})
|
||||||
|
.catch(setError)
|
||||||
|
.finally(() => setLoading(false));
|
||||||
|
}, [url]);
|
||||||
|
|
||||||
|
return [data, loading, error];
|
||||||
|
};
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
import { useSetting } from "./Setting";
|
import { useSetting } from "./Setting";
|
||||||
import { I18N, URL_RAW_PREFIX } from "../config";
|
import { I18N, URL_RAW_PREFIX } from "../config";
|
||||||
import { useEffect, useState } from "react";
|
import { useFetch } from "./Fetch";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 多语言 hook
|
* 多语言 hook
|
||||||
@@ -13,29 +13,7 @@ export const useI18n = () => {
|
|||||||
|
|
||||||
export const useI18nMd = (key) => {
|
export const useI18nMd = (key) => {
|
||||||
const i18n = useI18n();
|
const i18n = useI18n();
|
||||||
const [md, setMd] = useState("");
|
|
||||||
const [loading, setLoading] = useState(false);
|
|
||||||
const [error, setError] = useState("");
|
|
||||||
|
|
||||||
const fileName = i18n(key);
|
const fileName = i18n(key);
|
||||||
|
const url = `${URL_RAW_PREFIX}/${fileName}`;
|
||||||
useEffect(() => {
|
return useFetch(url);
|
||||||
if (!fileName) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const url = `${URL_RAW_PREFIX}/${fileName}`;
|
|
||||||
setLoading(true);
|
|
||||||
fetch(url)
|
|
||||||
.then((res) => {
|
|
||||||
if (res.ok) {
|
|
||||||
return res.text().then(setMd);
|
|
||||||
}
|
|
||||||
setError(`[${res.status}] ${res.statusText}`);
|
|
||||||
})
|
|
||||||
.catch(setError)
|
|
||||||
.finally(() => setLoading(false));
|
|
||||||
}, [fileName]);
|
|
||||||
|
|
||||||
return [md, loading, error];
|
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -2,8 +2,9 @@ import { useEffect } from "react";
|
|||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { transPool } from "../libs/pool";
|
import { transPool } from "../libs/pool";
|
||||||
import { browser } from "../libs/browser";
|
import { browser } from "../libs/browser";
|
||||||
import { MSG_TRANS_PUTRULE } from "../config";
|
import { MSG_TRANS_PUTRULE, EVENT_KISS } from "../config";
|
||||||
import { detectLang } from "../libs";
|
import { detectLang } from "../libs";
|
||||||
|
import { isExt } from "../libs/browser";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 翻译hook
|
* 翻译hook
|
||||||
@@ -25,10 +26,31 @@ export function useTranslate(q, initRule) {
|
|||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleKissEvent = (e) => {
|
||||||
|
const action = e?.detail?.action;
|
||||||
|
const args = e?.detail?.args || {};
|
||||||
|
switch (action) {
|
||||||
|
case MSG_TRANS_PUTRULE:
|
||||||
|
setRule((pre) => ({ ...pre, ...args }));
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
// console.log(`[popup] kissEvent action skip: ${action}`);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
browser?.runtime.onMessage.addListener(handleMessage);
|
if (isExt) {
|
||||||
|
browser?.runtime.onMessage.addListener(handleMessage);
|
||||||
|
} else {
|
||||||
|
window.addEventListener(EVENT_KISS, handleKissEvent);
|
||||||
|
}
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
browser?.runtime.onMessage.removeListener(handleMessage);
|
if (isExt) {
|
||||||
|
browser?.runtime.onMessage.removeListener(handleMessage);
|
||||||
|
} else {
|
||||||
|
window.removeEventListener(EVENT_KISS, handleKissEvent);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
|||||||
@@ -5,11 +5,12 @@ import Action from "./views/Action";
|
|||||||
import createCache from "@emotion/cache";
|
import createCache from "@emotion/cache";
|
||||||
import { CacheProvider } from "@emotion/react";
|
import { CacheProvider } from "@emotion/react";
|
||||||
|
|
||||||
import { browser } from "./libs/browser";
|
|
||||||
import {
|
import {
|
||||||
MSG_TRANS_TOGGLE,
|
MSG_TRANS_TOGGLE,
|
||||||
MSG_TRANS_GETRULE,
|
MSG_TRANS_GETRULE,
|
||||||
MSG_TRANS_PUTRULE,
|
MSG_TRANS_PUTRULE,
|
||||||
|
MSG_TRANS_CURRULE,
|
||||||
|
EVENT_KISS,
|
||||||
} from "./config";
|
} from "./config";
|
||||||
import { getRules, matchRule } from "./libs";
|
import { getRules, matchRule } from "./libs";
|
||||||
import { getSetting } from "./libs";
|
import { getSetting } from "./libs";
|
||||||
@@ -48,7 +49,10 @@ class ActionElement extends HTMLElement {
|
|||||||
*/
|
*/
|
||||||
(async () => {
|
(async () => {
|
||||||
// 设置页面
|
// 设置页面
|
||||||
if (document.location.href.includes("kiss-translator-options")) {
|
if (
|
||||||
|
document.location.href.includes("http://localhost:3000/options.html") ||
|
||||||
|
document.location.href.includes(process.env.REACT_APP_OPTIONSPAGE)
|
||||||
|
) {
|
||||||
const root = ReactDOM.createRoot(document.getElementById("root"));
|
const root = ReactDOM.createRoot(document.getElementById("root"));
|
||||||
root.render(
|
root.render(
|
||||||
<React.StrictMode>
|
<React.StrictMode>
|
||||||
@@ -72,19 +76,28 @@ class ActionElement extends HTMLElement {
|
|||||||
const translator = new Translator(rule);
|
const translator = new Translator(rule);
|
||||||
|
|
||||||
// 监听消息
|
// 监听消息
|
||||||
browser?.runtime.onMessage.addListener(async ({ action, args }) => {
|
window.addEventListener(EVENT_KISS, (e) => {
|
||||||
|
const action = e?.detail?.action;
|
||||||
|
const args = e?.detail?.args || {};
|
||||||
switch (action) {
|
switch (action) {
|
||||||
case MSG_TRANS_TOGGLE:
|
case MSG_TRANS_TOGGLE:
|
||||||
translator.toggle();
|
translator.toggle();
|
||||||
break;
|
break;
|
||||||
case MSG_TRANS_GETRULE:
|
case MSG_TRANS_GETRULE:
|
||||||
|
window.dispatchEvent(
|
||||||
|
new CustomEvent(EVENT_KISS, {
|
||||||
|
detail: {
|
||||||
|
action: MSG_TRANS_CURRULE,
|
||||||
|
args: translator.rule,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
);
|
||||||
break;
|
break;
|
||||||
case MSG_TRANS_PUTRULE:
|
case MSG_TRANS_PUTRULE:
|
||||||
translator.updateRule(args);
|
translator.updateRule(args);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
return { error: `message action is unavailable: ${action}` };
|
// console.log(`[entry] kissEvent action skip: ${action}`);
|
||||||
}
|
}
|
||||||
return { data: translator.rule };
|
|
||||||
});
|
});
|
||||||
})();
|
})();
|
||||||
|
|||||||
31
src/userscriptIndex.js
Normal file
31
src/userscriptIndex.js
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
import React from "react";
|
||||||
|
import ReactDOM from "react-dom/client";
|
||||||
|
import CircularProgress from "@mui/material/CircularProgress";
|
||||||
|
import ReactMarkdown from "react-markdown";
|
||||||
|
import Paper from "@mui/material/Paper";
|
||||||
|
import { useFetch } from "./hooks/Fetch";
|
||||||
|
import { I18N, URL_RAW_PREFIX } from "./config";
|
||||||
|
|
||||||
|
function App() {
|
||||||
|
const [data, loading, error] = useFetch(
|
||||||
|
`${URL_RAW_PREFIX}/${I18N?.["about_md"]?.["zh"]}`
|
||||||
|
);
|
||||||
|
return (
|
||||||
|
<Paper sx={{ padding: 2, margin: 2 }}>
|
||||||
|
{loading ? (
|
||||||
|
<center>
|
||||||
|
<CircularProgress />
|
||||||
|
</center>
|
||||||
|
) : (
|
||||||
|
<ReactMarkdown children={error ? error.message : data} />
|
||||||
|
)}
|
||||||
|
</Paper>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const root = ReactDOM.createRoot(document.getElementById("root"));
|
||||||
|
root.render(
|
||||||
|
<React.StrictMode>
|
||||||
|
<App />
|
||||||
|
</React.StrictMode>
|
||||||
|
);
|
||||||
@@ -16,19 +16,24 @@ const App = () => {
|
|||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
if (loading) {
|
if (loading) {
|
||||||
return <p style={{ textAlign: "center" }}>loading...</p>;
|
return (
|
||||||
|
<center>
|
||||||
|
<p>KISS Translator</p>
|
||||||
|
<h1 style={{ textAlign: "center" }}>loading...</h1>
|
||||||
|
</center>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div style={{ textAlign: "center" }}>
|
<center>
|
||||||
<p>
|
<p>
|
||||||
<a href={process.env.REACT_APP_HOMEPAGE}>KISS Translator</a> Script not
|
<a href={process.env.REACT_APP_HOMEPAGE}>KISS Translator</a> Script not
|
||||||
installed or disabled!
|
installed or disabled!
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<h1>
|
||||||
<a href={process.env.REACT_APP_HOMEPAGE}>Click here read more!</a>
|
<a href={process.env.REACT_APP_HOMEPAGE}>Click here read more!</a>
|
||||||
</p>
|
</h1>
|
||||||
</div>
|
</center>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
72
src/views/Action/Draggable.js
Normal file
72
src/views/Action/Draggable.js
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
import { useEffect, useState } from "react";
|
||||||
|
import { limitNumber } from "../../libs/utils";
|
||||||
|
|
||||||
|
export default function Draggable(props) {
|
||||||
|
const [origin, setOrigin] = useState(null);
|
||||||
|
const [position, setPosition] = useState({
|
||||||
|
x: props.left,
|
||||||
|
y: props.top,
|
||||||
|
});
|
||||||
|
|
||||||
|
const handlePointerDown = (e) => {
|
||||||
|
e.target.setPointerCapture(e.pointerId);
|
||||||
|
props?.onStart();
|
||||||
|
setOrigin({
|
||||||
|
x: position.x,
|
||||||
|
y: position.y,
|
||||||
|
px: e.clientX,
|
||||||
|
py: e.clientY,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const handlePointerMove = (e) => {
|
||||||
|
props?.onMove();
|
||||||
|
if (origin) {
|
||||||
|
const dx = e.clientX - origin.px;
|
||||||
|
const dy = e.clientY - origin.py;
|
||||||
|
let x = origin.x + dx;
|
||||||
|
let y = origin.y + dy;
|
||||||
|
const { w, h } = props.windowSize;
|
||||||
|
x = limitNumber(x, 0, w - props.width);
|
||||||
|
y = limitNumber(y, 0, h - props.height);
|
||||||
|
setPosition({ x, y });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handlePointerUp = (e) => {
|
||||||
|
setOrigin(null);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleClick = (e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const { w, h } = props.windowSize;
|
||||||
|
setPosition(({ x, y }) => ({
|
||||||
|
x: limitNumber(x, 0, w - props.width),
|
||||||
|
y: limitNumber(y, 0, h - props.height),
|
||||||
|
}));
|
||||||
|
}, [props.windowSize, props.width, props.height]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
position: "fixed",
|
||||||
|
left: position.x,
|
||||||
|
top: position.y,
|
||||||
|
zIndex: 2147483647,
|
||||||
|
}}
|
||||||
|
onClick={handleClick}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
onPointerDown={handlePointerDown}
|
||||||
|
onPointerMove={handlePointerMove}
|
||||||
|
onPointerUp={handlePointerUp}
|
||||||
|
>
|
||||||
|
{props.handler}
|
||||||
|
</div>
|
||||||
|
<div>{props.children}</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,16 +1,132 @@
|
|||||||
|
import Paper from "@mui/material/Paper";
|
||||||
import Box from "@mui/material/Box";
|
import Box from "@mui/material/Box";
|
||||||
import Fab from "@mui/material/Fab";
|
import Fab from "@mui/material/Fab";
|
||||||
import AddIcon from "@mui/icons-material/Add";
|
import AddIcon from "@mui/icons-material/Add";
|
||||||
import ThemeProvider from "../../hooks/Theme";
|
import ThemeProvider from "../../hooks/Theme";
|
||||||
|
import Draggable from "./Draggable";
|
||||||
|
import IconButton from "@mui/material/IconButton";
|
||||||
|
import CloseIcon from "@mui/icons-material/Close";
|
||||||
|
import Stack from "@mui/material/Stack";
|
||||||
|
import { useEffect, useState, useMemo, useCallback } from "react";
|
||||||
|
import { StoragesProvider } from "../../hooks/Storage";
|
||||||
|
import Popup from "../Popup";
|
||||||
|
|
||||||
export default function Action() {
|
export default function Action() {
|
||||||
|
const fabWidth = 56;
|
||||||
|
const [showPopup, setShowPopup] = useState(false);
|
||||||
|
const [windowSize, setWindowSize] = useState({
|
||||||
|
w: window.innerWidth,
|
||||||
|
h: window.innerHeight,
|
||||||
|
});
|
||||||
|
const [moved, setMoved] = useState(false);
|
||||||
|
|
||||||
|
const handleWindowResize = (e) => {
|
||||||
|
setWindowSize({
|
||||||
|
w: window.innerWidth,
|
||||||
|
h: window.innerHeight,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleWindowClick = (e) => {
|
||||||
|
setShowPopup(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleStart = useCallback(() => {
|
||||||
|
setMoved(false);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const handleMove = useCallback(() => {
|
||||||
|
setMoved(true);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
window.addEventListener("resize", handleWindowResize);
|
||||||
|
window.addEventListener("click", handleWindowClick);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
window.removeEventListener("resize", handleWindowResize);
|
||||||
|
window.removeEventListener("click", handleWindowClick);
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const popProps = useMemo(() => {
|
||||||
|
const width = Math.min(windowSize.w, 300);
|
||||||
|
const height = Math.min(windowSize.h, 386);
|
||||||
|
const left = (windowSize.w - width) / 2;
|
||||||
|
const top = (windowSize.h - height) / 2;
|
||||||
|
return {
|
||||||
|
windowSize,
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
left,
|
||||||
|
top,
|
||||||
|
};
|
||||||
|
}, [windowSize]);
|
||||||
|
|
||||||
|
const fabProps = {
|
||||||
|
windowSize,
|
||||||
|
width: fabWidth,
|
||||||
|
height: fabWidth,
|
||||||
|
left: window.innerWidth - fabWidth - fabWidth,
|
||||||
|
top: window.innerHeight - fabWidth - fabWidth,
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ThemeProvider>
|
<StoragesProvider>
|
||||||
<Box style={{ position: "fixed", top: 16, right: 16, zIndex: 10001 }}>
|
<ThemeProvider>
|
||||||
<Fab color="primary">
|
{showPopup ? (
|
||||||
<AddIcon />
|
<Draggable
|
||||||
</Fab>
|
key="pop"
|
||||||
</Box>
|
{...popProps}
|
||||||
</ThemeProvider>
|
onStart={handleStart}
|
||||||
|
onMove={handleMove}
|
||||||
|
handler={
|
||||||
|
<Paper style={{ cursor: "move" }} elevation={3}>
|
||||||
|
<Stack
|
||||||
|
direction="row"
|
||||||
|
justifyContent="space-between"
|
||||||
|
alignItems="center"
|
||||||
|
spacing={2}
|
||||||
|
>
|
||||||
|
<Box style={{ marginLeft: 16 }}>
|
||||||
|
{process.env.REACT_APP_NAME}
|
||||||
|
</Box>
|
||||||
|
<IconButton
|
||||||
|
onClick={() => {
|
||||||
|
setShowPopup(false);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<CloseIcon />
|
||||||
|
</IconButton>
|
||||||
|
</Stack>
|
||||||
|
</Paper>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<Paper>
|
||||||
|
<Popup />
|
||||||
|
</Paper>
|
||||||
|
</Draggable>
|
||||||
|
) : (
|
||||||
|
<Draggable
|
||||||
|
key="fab"
|
||||||
|
{...fabProps}
|
||||||
|
onStart={handleStart}
|
||||||
|
onMove={handleMove}
|
||||||
|
handler={
|
||||||
|
<Fab
|
||||||
|
color="primary"
|
||||||
|
onClick={(e) => {
|
||||||
|
if (!moved) {
|
||||||
|
setShowPopup((pre) => !pre);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<AddIcon />
|
||||||
|
</Fab>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</ThemeProvider>
|
||||||
|
</StoragesProvider>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,13 +5,15 @@ import { useI18n, useI18nMd } from "../../hooks/I18n";
|
|||||||
|
|
||||||
export default function About() {
|
export default function About() {
|
||||||
const i18n = useI18n();
|
const i18n = useI18n();
|
||||||
const [md, loading, error] = useI18nMd("about_md");
|
const [data, loading, error] = useI18nMd("about_md");
|
||||||
return (
|
return (
|
||||||
<Box>
|
<Box>
|
||||||
{loading ? (
|
{loading ? (
|
||||||
<CircularProgress />
|
<center>
|
||||||
|
<CircularProgress />
|
||||||
|
</center>
|
||||||
) : (
|
) : (
|
||||||
<ReactMarkdown children={error ? i18n("about_md_local") : md} />
|
<ReactMarkdown children={error ? i18n("about_md_local") : data} />
|
||||||
)}
|
)}
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import FormControlLabel from "@mui/material/FormControlLabel";
|
|||||||
import Switch from "@mui/material/Switch";
|
import Switch from "@mui/material/Switch";
|
||||||
import Button from "@mui/material/Button";
|
import Button from "@mui/material/Button";
|
||||||
import { sendTabMsg } from "../../libs/msg";
|
import { sendTabMsg } from "../../libs/msg";
|
||||||
import { browser } from "../../libs/browser";
|
import { browser, isExt } from "../../libs/browser";
|
||||||
import { useI18n } from "../../hooks/I18n";
|
import { useI18n } from "../../hooks/I18n";
|
||||||
import TextField from "@mui/material/TextField";
|
import TextField from "@mui/material/TextField";
|
||||||
import {
|
import {
|
||||||
@@ -17,6 +17,8 @@ import {
|
|||||||
OPT_LANGS_FROM,
|
OPT_LANGS_FROM,
|
||||||
OPT_LANGS_TO,
|
OPT_LANGS_TO,
|
||||||
OPT_STYLE_ALL,
|
OPT_STYLE_ALL,
|
||||||
|
EVENT_KISS,
|
||||||
|
MSG_TRANS_CURRULE,
|
||||||
} from "../../config";
|
} from "../../config";
|
||||||
|
|
||||||
export default function Popup() {
|
export default function Popup() {
|
||||||
@@ -24,13 +26,28 @@ export default function Popup() {
|
|||||||
const [rule, setRule] = useState(null);
|
const [rule, setRule] = useState(null);
|
||||||
|
|
||||||
const handleOpenSetting = () => {
|
const handleOpenSetting = () => {
|
||||||
browser?.runtime.openOptionsPage();
|
if (isExt) {
|
||||||
|
browser?.runtime.openOptionsPage();
|
||||||
|
} else {
|
||||||
|
window.open(process.env.REACT_APP_OPTIONSPAGE, "_blank");
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleTransToggle = async (e) => {
|
const handleTransToggle = async (e) => {
|
||||||
try {
|
try {
|
||||||
setRule({ ...rule, transOpen: e.target.checked });
|
setRule({ ...rule, transOpen: e.target.checked });
|
||||||
await sendTabMsg(MSG_TRANS_TOGGLE);
|
|
||||||
|
if (isExt) {
|
||||||
|
await sendTabMsg(MSG_TRANS_TOGGLE);
|
||||||
|
} else {
|
||||||
|
window.dispatchEvent(
|
||||||
|
new CustomEvent(EVENT_KISS, {
|
||||||
|
detail: {
|
||||||
|
action: MSG_TRANS_TOGGLE,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.log("[toggle trans]", err);
|
console.log("[toggle trans]", err);
|
||||||
}
|
}
|
||||||
@@ -40,13 +57,50 @@ export default function Popup() {
|
|||||||
try {
|
try {
|
||||||
const { name, value } = e.target;
|
const { name, value } = e.target;
|
||||||
setRule((pre) => ({ ...pre, [name]: value }));
|
setRule((pre) => ({ ...pre, [name]: value }));
|
||||||
await sendTabMsg(MSG_TRANS_PUTRULE, { [name]: value });
|
|
||||||
|
if (isExt) {
|
||||||
|
await sendTabMsg(MSG_TRANS_PUTRULE, { [name]: value });
|
||||||
|
} else {
|
||||||
|
window.dispatchEvent(
|
||||||
|
new CustomEvent(EVENT_KISS, {
|
||||||
|
detail: {
|
||||||
|
action: MSG_TRANS_PUTRULE,
|
||||||
|
args: { [name]: value },
|
||||||
|
},
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.log("[update rule]", err);
|
console.log("[update rule]", err);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleKissEvent = (e) => {
|
||||||
|
const action = e?.detail?.action;
|
||||||
|
const args = e?.detail?.args || {};
|
||||||
|
switch (action) {
|
||||||
|
case MSG_TRANS_CURRULE:
|
||||||
|
setRule(args);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
// console.log(`[popup] kissEvent action skip: ${action}`);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
if (!isExt) {
|
||||||
|
window.addEventListener(EVENT_KISS, handleKissEvent);
|
||||||
|
window.dispatchEvent(
|
||||||
|
new CustomEvent(EVENT_KISS, {
|
||||||
|
detail: { action: MSG_TRANS_GETRULE },
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
window.removeEventListener(EVENT_KISS, handleKissEvent);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
(async () => {
|
(async () => {
|
||||||
try {
|
try {
|
||||||
const res = await sendTabMsg(MSG_TRANS_GETRULE);
|
const res = await sendTabMsg(MSG_TRANS_GETRULE);
|
||||||
@@ -75,7 +129,7 @@ export default function Popup() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Box minWidth={300} sx={{ p: 2 }}>
|
<Box minWidth={300} sx={{ p: 2 }}>
|
||||||
<Stack spacing={3}>
|
<Stack spacing={2}>
|
||||||
<FormControlLabel
|
<FormControlLabel
|
||||||
control={<Switch checked={transOpen} onChange={handleTransToggle} />}
|
control={<Switch checked={transOpen} onChange={handleTransToggle} />}
|
||||||
label={i18n("translate")}
|
label={i18n("translate")}
|
||||||
@@ -83,6 +137,7 @@ export default function Popup() {
|
|||||||
|
|
||||||
<TextField
|
<TextField
|
||||||
select
|
select
|
||||||
|
SelectProps={{ MenuProps: { disablePortal: true } }}
|
||||||
size="small"
|
size="small"
|
||||||
value={translator}
|
value={translator}
|
||||||
name="translator"
|
name="translator"
|
||||||
@@ -98,6 +153,7 @@ export default function Popup() {
|
|||||||
|
|
||||||
<TextField
|
<TextField
|
||||||
select
|
select
|
||||||
|
SelectProps={{ MenuProps: { disablePortal: true } }}
|
||||||
size="small"
|
size="small"
|
||||||
value={fromLang}
|
value={fromLang}
|
||||||
name="fromLang"
|
name="fromLang"
|
||||||
@@ -113,6 +169,7 @@ export default function Popup() {
|
|||||||
|
|
||||||
<TextField
|
<TextField
|
||||||
select
|
select
|
||||||
|
SelectProps={{ MenuProps: { disablePortal: true } }}
|
||||||
size="small"
|
size="small"
|
||||||
value={toLang}
|
value={toLang}
|
||||||
name="toLang"
|
name="toLang"
|
||||||
@@ -128,6 +185,7 @@ export default function Popup() {
|
|||||||
|
|
||||||
<TextField
|
<TextField
|
||||||
select
|
select
|
||||||
|
SelectProps={{ MenuProps: { disablePortal: true } }}
|
||||||
size="small"
|
size="small"
|
||||||
value={textStyle}
|
value={textStyle}
|
||||||
name="textStyle"
|
name="textStyle"
|
||||||
|
|||||||
Reference in New Issue
Block a user