mirror of
https://github.com/AkiChase/scrcpy-mask
synced 2024-11-12 20:11:21 +08:00
commit
61a9355354
65
README-zh.md
Normal file
65
README-zh.md
Normal file
@ -0,0 +1,65 @@
|
||||
# Scrcpy-mask
|
||||
|
||||
为了实现电脑控制安卓设备,本人使用 Tarui + Vue 3 + Rust 开发了一款跨平台桌面客户端。该客户端能够提供可视化的鼠标和键盘按键映射配置。通过按键映射实现了实现类似安卓模拟器的多点触控操作,具有毫秒级响应速度。该工具可广泛用于电脑控制安卓设备玩手游等等,提供流畅的触控体验。
|
||||
|
||||
本项目仅实现了 Scrcpy 控制协议,**不提供投屏功能**。因为投屏会存在延迟和模糊问题,本项目另辟蹊径,直接放弃投屏,而使用透明的蒙版显示窗口背后的内容(可以使用 AVD 、手机厂商提供的低延迟投屏等),从根本上杜绝了 Scrcpy 的投屏体验差的问题。
|
||||
|
||||
## 特性
|
||||
|
||||
- [x] 有线、无线连接安卓设备
|
||||
- [x] 启动并连接 Scrcpy 服务端
|
||||
- [x] 实现 Scrcpy 控制协议
|
||||
- [x] 鼠标和键盘按键映射
|
||||
- [x] 可视化编辑按键映射配置
|
||||
- [x] 按键映射配置的导入与导出
|
||||
- [x] 更新检查
|
||||
- [x] 在按键映射和插入文本之间切换
|
||||
- [x] 国际化
|
||||
- [ ] 手柄按键映射
|
||||
- [ ] 更好的宏
|
||||
- [ ] 通过 WebSocket 提供外部接口
|
||||
- [ ] 帮助文档
|
||||
|
||||
## 视频演示
|
||||
|
||||
- [M 系列 Mac 电脑玩王者,暃排位实录,使用 Android Stuido 模拟器和开源 Scrcpy Mask 按键映射工具-哔哩哔哩](https://b23.tv/q6iDW1w)
|
||||
- [自制跨平台开源项目 Scrcpy Mask ,像模拟器一样用键鼠控制任意安卓设备!以 M 系列芯片 MacBook 打王者为例-哔哩哔哩](https://b23.tv/gqmriXr)
|
||||
- [如何用 PC 控制安卓手机打王者?只要思想不滑坡,办法总比困难多!-哔哩哔哩](https://b23.tv/dmUOpff)
|
||||
- [M 芯片 Mac 怎么用 Android Studio 模拟器打王者?这是 Up 耗时数个月给出的答案-哔哩哔哩](https://b23.tv/ckJgyK5)
|
||||
|
||||
## 截图
|
||||
|
||||
- 设备控制
|
||||
|
||||
![](https://pic.superbed.cc/item/6637190cf989f2fb975b6162.png)
|
||||
|
||||
- 可视化编辑按键映射配置
|
||||
|
||||
![](https://pic.superbed.cc/item/66371911f989f2fb975b62a3.png)
|
||||
|
||||
- 游戏控制
|
||||
|
||||
![](https://pic.superbed.cc/item/66373c8cf989f2fb97679dfd.png)
|
||||
|
||||
## 基本使用
|
||||
|
||||
1. 从 [releases](https://github.com/AkiChase/scrcpy-mask/releases) 中安装适合你系统平台的软件包
|
||||
2. 确认你的安卓设备类型
|
||||
1. 对于手机或平板电脑等物理设备
|
||||
1. 你需要自己解决投屏的问题。推荐使用设备品牌的官方投屏方式,这样一般延迟最小。
|
||||
2. 通过 USB 或无线方式在设备上启用 ADB 调试,然后将其连接到电脑。
|
||||
2. 对于模拟器,不仅不需要投屏,而且模拟器通常默认启用 ADB 有线调试。所以几乎不用操作就能获得最好的体验。
|
||||
3. 启动软件并导航到设备页面。
|
||||
1. 在可用的设备中查找你的设备(如果未找到,请自行搜索如何为安装设备启用 ADB 调试)。
|
||||
2. 右击你的设备并选择“获取屏幕大小”。根据获得的屏幕尺寸为参考,正确输入设备的宽度和高度。注意:如果宽度或高度不正确 (例如,在纵向和横向模式下这两个参数是颠倒的),所有触摸操作将被忽略,但是不会有任何错误消息。
|
||||
3. 再次右击设备并选择“控制此设备”。
|
||||
4. 导航到设置页面->蒙版设置,将蒙版的宽度和高度设置为设备大小的一定倍数,以确保蒙版大小合适。
|
||||
5. 导航到蒙版页面,你可以在其中看到一个完全透明的蒙版区域。接下来,调整并移动模拟器窗口或投屏窗口,让其内容区域与透明蒙版区域完全对齐。
|
||||
6. 导航到键映射页面,切换或编辑键映射配置。
|
||||
7. 返回到蒙版界面,开始使用吧!
|
||||
|
||||
## 贡献
|
||||
|
||||
如果你对这个项目感兴趣,欢迎提 PR 或 Issue。但我的时间和精力有限,所以可能无法全部及时处理。
|
||||
|
||||
[![Star History Chart](https://api.star-history.com/svg?repos=AkiChase/scrcpy-mask&type=Date)](https://star-history.com/#AkiChase/scrcpy-mask&Date)
|
22
README.md
22
README.md
@ -1,8 +1,8 @@
|
||||
# Scrcpy-mask
|
||||
|
||||
A Scrcpy client in Rust & Tarui aimed at providing mouse and key mapping to control Android device.
|
||||
To achieve computer control of Android devices, I developed a cross-platform desktop client using Tarui + Vue 3 + Rust. This client provides visual mouse and keyboard mapping configuration, enabling multi-touch operations similar to Android emulators through key mapping, with millisecond-level response time. This tool can be widely used for controlling Android devices from computers to play mobile games, providing a smooth touch experience.
|
||||
|
||||
Due to the delay and blurred image quality of the mirror screen. This project found another way, directly abandoned the mirror screen, and instead used a transparent mask to display the screen content behind the window, which fundamentally put an end to the delay in casting the screen.
|
||||
This project only implements the Scrcpy control protocol and **does not provide Screen mirroring**. Because screen mirroring may involve latency and blurriness issues, this project takes a different approach by directly abandoning screen mirroring and instead using a transparent mask to display the content behind the window (which can be AVD, low-latency screen mirroring provided by your phone manufacturers, etc.), Completely eliminates the problem of poor screen casting experience inherent in Scrcpy.
|
||||
|
||||
## Features
|
||||
|
||||
@ -14,7 +14,7 @@ Due to the delay and blurred image quality of the mirror screen. This project fo
|
||||
- [x] Key mapping config import and export
|
||||
- [x] Update check
|
||||
- [x] Switch between key mapping and input-text box
|
||||
- [ ] Internationalization (i18n)
|
||||
- [x] Internationalization (i18n)
|
||||
- [ ] Gamepad key mapping
|
||||
- [ ] Better macro support
|
||||
- [ ] Provide external interface through websocket
|
||||
@ -45,14 +45,14 @@ Due to the delay and blurred image quality of the mirror screen. This project fo
|
||||
|
||||
1. Install software suitable for your system platform from [releases](https://github.com/AkiChase/scrcpy-mask/releases)
|
||||
2. Identify your Android device type
|
||||
1. For physical devices like phones or tablets
|
||||
1. You need to solve the problem of screen casting on your own. Recommend using the official screen mirror method of your device brand to achieve the minimum delay
|
||||
2. Enable ADB debugging on your device via USB or wirelessly, then connect it to your computer.
|
||||
2. For emulator, you don't need screen mirror, and emulator generally default to enabling ADB wired debugging. So this is the best way for game, I think.
|
||||
1. For physical devices like phones or tablets
|
||||
1. You need to solve the problem of screen casting on your own. Recommend using the official screen mirror method of your device brand to achieve the minimum delay
|
||||
2. Enable ADB debugging on your device via USB or wirelessly, then connect it to your computer.
|
||||
2. For emulator, you don't need screen mirror, and emulator generally default to enabling ADB wired debugging. So this is the best way for game, I think.
|
||||
3. Launch the software and navigate to the Device page.
|
||||
1. Find your device among the available devices (if not found, please search for how to enable ADB debugging for your device).
|
||||
2. Right-click on your device and choose "Get Screen Size". Use the obtained screen size as a reference and enter the device's width and height correctly. Note: If the width or height is incorrect (for example, they are reversed in portrait and landscape modes), all touch operations will be ignored, but no error message will appear.
|
||||
3. Right-click on your device again and choose "Control this device".
|
||||
1. Find your device among the available devices (if not found, please search for how to enable ADB debugging for your device).
|
||||
2. Right-click on your device and choose "Get Screen Size". Use the obtained screen size as a reference and enter the device's width and height correctly. Note: If the width or height is incorrect (for example, they are reversed in portrait and landscape modes), all touch operations will be ignored, but no error message will appear.
|
||||
3. Right-click on your device again and choose "Control this device".
|
||||
4. Navigate to the Settings page -> Mask Settings, and set the width and height of the mask to a certain multiple of the device's size to ensure the mask size is appropriate.
|
||||
5. Navigate to the Mask page where you can see a transparent mask. Next, adjust and move your emulator window or screen mirroring window to align the displayed content area with the transparent mask area.
|
||||
6. Navigate to the Key mapping page and switch or edit the key mapping configs.
|
||||
@ -62,4 +62,4 @@ Due to the delay and blurred image quality of the mirror screen. This project fo
|
||||
|
||||
If you are interested in this project, you are welcome to submit pull request or issue. But my time and energy is limited, so I may not be able to deal with it all.
|
||||
|
||||
[![Star History Chart](https://api.star-history.com/svg?repos=AkiChase/scrcpy-mask&type=Date)](https://star-history.com/#AkiChase/scrcpy-mask&Date)
|
||||
[![Star History Chart](https://api.star-history.com/svg?repos=AkiChase/scrcpy-mask&type=Date)](https://star-history.com/#AkiChase/scrcpy-mask&Date)
|
||||
|
@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "scrcpy-mask",
|
||||
"private": true,
|
||||
"version": "0.1.8",
|
||||
"version": "0.1.9",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
@ -18,6 +18,7 @@
|
||||
"@tauri-apps/plugin-store": "2.0.0-beta.2",
|
||||
"pinia": "^2.1.7",
|
||||
"vue": "^3.3.4",
|
||||
"vue-i18n": "^9.13.1",
|
||||
"vue-router": "4"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "scrcpy-mask"
|
||||
version = "0.1.8"
|
||||
version = "0.1.9"
|
||||
description = "A Tauri App"
|
||||
authors = ["AkiChase"]
|
||||
edition = "2021"
|
||||
@ -11,12 +11,12 @@ edition = "2021"
|
||||
tauri-build = { version = "2.0.0-beta", features = [] }
|
||||
|
||||
[dependencies]
|
||||
tauri = { version = "2.0.0-beta.15", features = ["macos-private-api", "devtools"] }
|
||||
tauri-plugin-store = "2.0.0-beta.4"
|
||||
tauri = { version = "2.0.0-beta.18", features = ["macos-private-api", "devtools"] }
|
||||
tauri-plugin-store = "2.0.0-beta"
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
serde_json = "1"
|
||||
anyhow = "1.0"
|
||||
tokio = { version = "1.36.0", features = ["rt-multi-thread", "net", "macros", "io-util", "time", "sync"] }
|
||||
tauri-plugin-process = "2.0.0-beta.3"
|
||||
tauri-plugin-shell = "2.0.0-beta.4"
|
||||
tauri-plugin-http = "2.0.0-beta.7"
|
||||
tauri-plugin-process = "2.0.0-beta"
|
||||
tauri-plugin-shell = "2.0.0-beta"
|
||||
tauri-plugin-http = "2.0.0-beta"
|
||||
|
@ -161,7 +161,7 @@ async fn main() {
|
||||
let size_h = value["sizeH"].as_i64().unwrap_or(600);
|
||||
let main_window: tauri::WebviewWindow =
|
||||
app.get_webview_window("main").unwrap();
|
||||
main_window.set_zoom(1.).unwrap();
|
||||
main_window.set_zoom(1.).unwrap_or(());
|
||||
main_window
|
||||
.set_position(tauri::Position::Logical(tauri::LogicalPosition {
|
||||
x: (pos_x - 70) as f64,
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"productName": "scrcpy-mask",
|
||||
"version": "0.1.8",
|
||||
"version": "0.1.9",
|
||||
"identifier": "com.akichase.mask",
|
||||
"build": {
|
||||
"beforeDevCommand": "pnpm dev",
|
||||
|
71
src/App.vue
71
src/App.vue
@ -7,84 +7,13 @@ import {
|
||||
NMessageProvider,
|
||||
NDialogProvider,
|
||||
} from "naive-ui";
|
||||
import { Store } from "@tauri-apps/plugin-store";
|
||||
import { KeyMappingConfig } from "./keyMappingConfig";
|
||||
import { onMounted } from "vue";
|
||||
import { useGlobalStore } from "./store/global";
|
||||
import { useRouter } from "vue-router";
|
||||
|
||||
const store = useGlobalStore();
|
||||
const router = useRouter();
|
||||
|
||||
onMounted(async () => {
|
||||
router.replace({ name: "mask" });
|
||||
|
||||
const localStore = new Store("store.bin");
|
||||
// loading screenSize from local store
|
||||
const screenSize = await localStore.get<{ sizeW: number; sizeH: number }>(
|
||||
"screenSize"
|
||||
);
|
||||
if (screenSize !== null) {
|
||||
store.screenSizeW = screenSize.sizeW;
|
||||
store.screenSizeH = screenSize.sizeH;
|
||||
}
|
||||
|
||||
// loading keyMappingConfigList from local store
|
||||
let keyMappingConfigList = await localStore.get<KeyMappingConfig[]>(
|
||||
"keyMappingConfigList"
|
||||
);
|
||||
if (keyMappingConfigList === null || keyMappingConfigList.length === 0) {
|
||||
// add empty key mapping config
|
||||
// unable to get mask element when app is not ready
|
||||
// so we use the stored mask area to get relative size
|
||||
const maskArea = await localStore.get<{
|
||||
posX: number;
|
||||
posY: number;
|
||||
sizeW: number;
|
||||
sizeH: number;
|
||||
}>("maskArea");
|
||||
let relativeSize = { w: 800, h: 600 };
|
||||
if (maskArea !== null) {
|
||||
relativeSize = {
|
||||
w: maskArea.sizeW,
|
||||
h: maskArea.sizeH,
|
||||
};
|
||||
}
|
||||
keyMappingConfigList = [
|
||||
{
|
||||
relativeSize,
|
||||
title: "空白方案",
|
||||
list: [],
|
||||
},
|
||||
];
|
||||
await localStore.set("keyMappingConfigList", keyMappingConfigList);
|
||||
}
|
||||
store.keyMappingConfigList = keyMappingConfigList;
|
||||
|
||||
// loading curKeyMappingIndex from local store
|
||||
let curKeyMappingIndex = await localStore.get<number>("curKeyMappingIndex");
|
||||
if (
|
||||
curKeyMappingIndex === null ||
|
||||
curKeyMappingIndex >= keyMappingConfigList.length
|
||||
) {
|
||||
curKeyMappingIndex = 0;
|
||||
localStore.set("curKeyMappingIndex", curKeyMappingIndex);
|
||||
}
|
||||
store.curKeyMappingIndex = curKeyMappingIndex;
|
||||
|
||||
// loading maskButton from local store
|
||||
let maskButton = await localStore.get<{
|
||||
show: boolean;
|
||||
transparency: number;
|
||||
}>("maskButton");
|
||||
if (maskButton === null) {
|
||||
maskButton = {
|
||||
show: true,
|
||||
transparency: 0.5,
|
||||
};
|
||||
await localStore.set("maskButton", maskButton);
|
||||
}
|
||||
store.maskButton = maskButton;
|
||||
});
|
||||
</script>
|
||||
|
||||
|
@ -45,7 +45,9 @@ import { UnlistenFn, listen } from "@tauri-apps/api/event";
|
||||
import { Store } from "@tauri-apps/plugin-store";
|
||||
import { shutdown } from "../frontcommand/scrcpyMaskCmd";
|
||||
import { useGlobalStore } from "../store/global";
|
||||
import { useI18n } from "vue-i18n";
|
||||
|
||||
const { t } = useI18n();
|
||||
const dialog = useDialog();
|
||||
const store = useGlobalStore();
|
||||
const message = useMessage();
|
||||
@ -105,7 +107,7 @@ const tableCols: DataTableColumns = [
|
||||
key: "id",
|
||||
},
|
||||
{
|
||||
title: "Status",
|
||||
title: t("pages.Device.status"),
|
||||
key: "status",
|
||||
},
|
||||
];
|
||||
@ -144,10 +146,10 @@ const tableRowProps = (_: any, index: number) => {
|
||||
|
||||
async function shutdownSC() {
|
||||
dialog.warning({
|
||||
title: "Warning",
|
||||
content: "确定关闭Scrcpy控制服务?",
|
||||
positiveText: "确定",
|
||||
negativeText: "取消",
|
||||
title: t("pages.Device.shutdown.title"),
|
||||
content: t("pages.Device.shutdown.content"),
|
||||
positiveText: t("pages.Device.shutdown.positiveText"),
|
||||
negativeText: t("pages.Device.shutdown.negativeText"),
|
||||
onPositiveClick: async () => {
|
||||
await shutdown();
|
||||
store.controledDevice = null;
|
||||
@ -162,11 +164,11 @@ const menuY = ref(0);
|
||||
const showMenu = ref(false);
|
||||
const menuOptions: DropdownOption[] = [
|
||||
{
|
||||
label: () => h("span", "控制此设备"),
|
||||
label: () => h("span", t("pages.Device.menu.control")),
|
||||
key: "control",
|
||||
},
|
||||
{
|
||||
label: () => h("span", "获取屏幕尺寸"),
|
||||
label: () => h("span", t("pages.Device.menu.screen")),
|
||||
key: "screen",
|
||||
},
|
||||
];
|
||||
@ -181,7 +183,7 @@ async function deviceControl() {
|
||||
}
|
||||
|
||||
if (!(store.screenSizeW > 0) || !(store.screenSizeH > 0)) {
|
||||
message.error("请正确输入当前控制设备的屏幕尺寸");
|
||||
message.error(t("pages.Device.deviceControl.inputScreenSize"));
|
||||
store.screenSizeW = 0;
|
||||
store.screenSizeH = 0;
|
||||
store.hideLoading();
|
||||
@ -189,7 +191,7 @@ async function deviceControl() {
|
||||
}
|
||||
|
||||
if (store.controledDevice) {
|
||||
message.error("请先关闭当前控制设备");
|
||||
message.error(t("pages.Device.deviceControl.closeCurDevice"));
|
||||
store.hideLoading();
|
||||
return;
|
||||
}
|
||||
@ -198,7 +200,7 @@ async function deviceControl() {
|
||||
sizeW: store.screenSizeW,
|
||||
sizeH: store.screenSizeH,
|
||||
});
|
||||
message.info("屏幕尺寸已保存,正在启动控制服务,请保持设备亮屏");
|
||||
message.info(t("pages.Device.deviceControl.controlInfo"));
|
||||
|
||||
const device = devices.value[rowIndex];
|
||||
|
||||
@ -216,7 +218,7 @@ async function deviceControl() {
|
||||
await shutdown();
|
||||
store.controledDevice = null;
|
||||
store.hideLoading();
|
||||
message.error("设备连接超时");
|
||||
message.error(t("pages.Device.deviceControl.connectTimeout"));
|
||||
}
|
||||
}, 6000);
|
||||
|
||||
@ -239,7 +241,9 @@ async function deviceGetScreenSize() {
|
||||
let id = devices.value[rowIndex].id;
|
||||
const size = await getDeviceScreenSize(id);
|
||||
store.hideLoading();
|
||||
message.success(`设备屏幕尺寸为: ${size[0]} x ${size[1]}`);
|
||||
message.success(
|
||||
t("pages.Device.deviceGetScreenSize") + `${size[0]} x ${size[1]}`
|
||||
);
|
||||
}
|
||||
|
||||
async function onMenuSelect(key: string) {
|
||||
@ -264,7 +268,7 @@ async function refreshDevices() {
|
||||
|
||||
async function connectDevice() {
|
||||
if (!address.value) {
|
||||
message.error("请输入无线调试地址");
|
||||
message.error(t("pages.Device.inputWirelessAddress"));
|
||||
return;
|
||||
}
|
||||
|
||||
@ -278,51 +282,51 @@ async function connectDevice() {
|
||||
<NScrollbar>
|
||||
<div class="device">
|
||||
<NSpin :show="store.showLoadingRef">
|
||||
<NH4 prefix="bar">本地端口</NH4>
|
||||
<NH4 prefix="bar">{{ $t("pages.Device.localPort") }}</NH4>
|
||||
<NInputNumber
|
||||
v-model:value="port"
|
||||
:show-button="false"
|
||||
:min="16384"
|
||||
:max="49151"
|
||||
placeholder="Scrcpy 本地端口"
|
||||
:placeholder="$t('pages.Device.localPortPlaceholder')"
|
||||
style="max-width: 300px"
|
||||
/>
|
||||
<NH4 prefix="bar">无线连接</NH4>
|
||||
<NH4 prefix="bar">{{ $t("pages.Device.wireless") }}</NH4>
|
||||
<NInputGroup style="max-width: 300px">
|
||||
<NInput
|
||||
v-model:value="address"
|
||||
clearable
|
||||
placeholder="无线调试地址"
|
||||
:placeholder="$t('pages.Device.wirelessPlaceholder')"
|
||||
/>
|
||||
<NButton type="primary" @click="connectDevice">连接</NButton>
|
||||
<NButton type="primary" @click="connectDevice">{{
|
||||
$t("pages.Device.connect")
|
||||
}}</NButton>
|
||||
</NInputGroup>
|
||||
<NH4 prefix="bar">设备尺寸</NH4>
|
||||
<NH4 prefix="bar">{{ $t("pages.Device.deviceSize.title") }}</NH4>
|
||||
<NFlex justify="left" align="center">
|
||||
<NFormItem label="宽度">
|
||||
<NFormItem :label="$t('pages.Device.deviceSize.width')">
|
||||
<NInputNumber
|
||||
v-model:value="store.screenSizeW"
|
||||
placeholder="屏幕宽度"
|
||||
:placeholder="$t('pages.Device.deviceSize.widthPlaceholder')"
|
||||
:min="0"
|
||||
:disabled="store.controledDevice !== null"
|
||||
/>
|
||||
</NFormItem>
|
||||
<NFormItem label="高度">
|
||||
<NFormItem :label="$t('pages.Device.deviceSize.height')">
|
||||
<NInputNumber
|
||||
v-model:value="store.screenSizeH"
|
||||
placeholder="屏幕高度"
|
||||
:placeholder="$t('pages.Device.deviceSize.heightPlaceholder')"
|
||||
:min="0"
|
||||
:disabled="store.controledDevice !== null"
|
||||
/>
|
||||
</NFormItem>
|
||||
</NFlex>
|
||||
<NP
|
||||
>提示:请正确输入当前控制设备的屏幕尺寸,这是成功发送触摸事件的必要参数</NP
|
||||
>
|
||||
<NH4 prefix="bar">受控设备</NH4>
|
||||
<NP>{{ $t("pages.Device.deviceSize.tip") }}</NP>
|
||||
<NH4 prefix="bar">{{ $t("pages.Device.controledDevice") }}</NH4>
|
||||
<div class="controled-device-list">
|
||||
<NEmpty
|
||||
size="small"
|
||||
description="No Controled Device"
|
||||
:description="$t('pages.Device.noControledDevice')"
|
||||
v-if="!store.controledDevice"
|
||||
/>
|
||||
<div class="controled-device" v-if="store.controledDevice">
|
||||
@ -352,7 +356,9 @@ async function connectDevice() {
|
||||
</div>
|
||||
</div>
|
||||
<NFlex justify="space-between" align="center">
|
||||
<NH4 style="margin: 20px 0" prefix="bar">可用设备</NH4>
|
||||
<NH4 style="margin: 20px 0" prefix="bar">{{
|
||||
$t("pages.Device.availableDevice")
|
||||
}}</NH4>
|
||||
<NButton
|
||||
tertiary
|
||||
circle
|
||||
|
@ -10,7 +10,7 @@ import {
|
||||
unlistenToEvent,
|
||||
updateScreenSizeAndMaskArea,
|
||||
} from "../hotkey";
|
||||
import { KeySteeringWheel } from "../keyMappingConfig";
|
||||
import { KeyMappingConfig, KeySteeringWheel } from "../keyMappingConfig";
|
||||
import { getVersion } from "@tauri-apps/api/app";
|
||||
import { fetch } from "@tauri-apps/plugin-http";
|
||||
import { open } from "@tauri-apps/plugin-shell";
|
||||
@ -24,7 +24,10 @@ import {
|
||||
AndroidKeycode,
|
||||
AndroidMetastate,
|
||||
} from "../frontcommand/android";
|
||||
import { Store } from "@tauri-apps/plugin-store";
|
||||
import { useI18n } from "vue-i18n";
|
||||
|
||||
const { t } = useI18n();
|
||||
const store = useGlobalStore();
|
||||
const router = useRouter();
|
||||
const message = useMessage();
|
||||
@ -59,17 +62,87 @@ onActivated(async () => {
|
||||
) {
|
||||
listenToEvent();
|
||||
} else {
|
||||
message.error("按键方案异常,请删除此方案");
|
||||
message.error(t("pages.Mask.keyconfigException"));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
onMounted(() => {
|
||||
onMounted(async () => {
|
||||
await loadLocalStore();
|
||||
store.checkUpdate = checkUpdate;
|
||||
checkUpdate();
|
||||
store.showInputBox = showInputBox;
|
||||
if (store.checkUpdateAtStart) checkUpdate();
|
||||
});
|
||||
|
||||
async function loadLocalStore() {
|
||||
const localStore = new Store("store.bin");
|
||||
// loading screenSize from local store
|
||||
const screenSize = await localStore.get<{ sizeW: number; sizeH: number }>(
|
||||
"screenSize"
|
||||
);
|
||||
if (screenSize !== null) {
|
||||
store.screenSizeW = screenSize.sizeW;
|
||||
store.screenSizeH = screenSize.sizeH;
|
||||
}
|
||||
|
||||
// loading keyMappingConfigList from local store
|
||||
let keyMappingConfigList = await localStore.get<KeyMappingConfig[]>(
|
||||
"keyMappingConfigList"
|
||||
);
|
||||
if (keyMappingConfigList === null || keyMappingConfigList.length === 0) {
|
||||
// add empty key mapping config
|
||||
// unable to get mask element when app is not ready
|
||||
// so we use the stored mask area to get relative size
|
||||
const maskArea = await localStore.get<{
|
||||
posX: number;
|
||||
posY: number;
|
||||
sizeW: number;
|
||||
sizeH: number;
|
||||
}>("maskArea");
|
||||
let relativeSize = { w: 800, h: 600 };
|
||||
if (maskArea !== null) {
|
||||
relativeSize = {
|
||||
w: maskArea.sizeW,
|
||||
h: maskArea.sizeH,
|
||||
};
|
||||
}
|
||||
keyMappingConfigList = [
|
||||
{
|
||||
relativeSize,
|
||||
title: t("pages.Mask.blankConfig"),
|
||||
list: [],
|
||||
},
|
||||
];
|
||||
await localStore.set("keyMappingConfigList", keyMappingConfigList);
|
||||
}
|
||||
store.keyMappingConfigList = keyMappingConfigList;
|
||||
|
||||
// loading curKeyMappingIndex from local store
|
||||
let curKeyMappingIndex = await localStore.get<number>("curKeyMappingIndex");
|
||||
if (
|
||||
curKeyMappingIndex === null ||
|
||||
curKeyMappingIndex >= keyMappingConfigList.length
|
||||
) {
|
||||
curKeyMappingIndex = 0;
|
||||
localStore.set("curKeyMappingIndex", curKeyMappingIndex);
|
||||
}
|
||||
store.curKeyMappingIndex = curKeyMappingIndex;
|
||||
|
||||
// loading maskButton from local store
|
||||
let maskButton = await localStore.get<{
|
||||
show: boolean;
|
||||
transparency: number;
|
||||
}>("maskButton");
|
||||
store.maskButton = maskButton ?? {
|
||||
show: true,
|
||||
transparency: 0.5,
|
||||
};
|
||||
|
||||
// loading checkUpdateAtStart from local store
|
||||
let checkUpdateAtStart = await localStore.get<boolean>("checkUpdateAtStart");
|
||||
store.checkUpdateAtStart = checkUpdateAtStart ?? true;
|
||||
}
|
||||
|
||||
async function cleanAfterimage() {
|
||||
const appWindow = getCurrent();
|
||||
const oldSize = await appWindow.outerSize();
|
||||
@ -164,20 +237,20 @@ async function checkUpdate() {
|
||||
}
|
||||
);
|
||||
if (res.status !== 200) {
|
||||
message.error("检查更新失败");
|
||||
message.error(t("pages.Mask.checkUpdate.failed"));
|
||||
} else {
|
||||
const data = await res.json();
|
||||
const latestVersion = (data.tag_name as string).slice(1);
|
||||
if (latestVersion <= curVersion) {
|
||||
message.success(`最新版本: ${latestVersion},当前已是最新版本`);
|
||||
message.success(t("pages.Mask.checkUpdate.isLatest", [latestVersion]));
|
||||
return;
|
||||
}
|
||||
const body = data.body as string;
|
||||
dialog.info({
|
||||
title: `最新版本:${data.tag_name}`,
|
||||
title: t("pages.Mask.checkUpdate.notLatest.title", [latestVersion]),
|
||||
content: () => renderUpdateInfo(body),
|
||||
positiveText: "前往发布页",
|
||||
negativeText: "取消",
|
||||
positiveText: t("pages.Mask.checkUpdate.notLatest.positiveText"),
|
||||
negativeText: t("pages.Mask.checkUpdate.notLatest.negativeText"),
|
||||
onPositiveClick: () => {
|
||||
open(data.html_url);
|
||||
},
|
||||
@ -185,7 +258,7 @@ async function checkUpdate() {
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
message.error("检查更新失败");
|
||||
message.error(t("pages.Mask.checkUpdate.failed"));
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@ -195,9 +268,9 @@ async function checkUpdate() {
|
||||
<div class="content">
|
||||
<NDialog
|
||||
:closable="false"
|
||||
title="未找到受控设备"
|
||||
content="请启动服务端并控制任意设备"
|
||||
positive-text="去启动"
|
||||
:title="$t('pages.Mask.noControledDevice.title')"
|
||||
:content="$t('pages.Mask.noControledDevice.content')"
|
||||
:positive-text="$t('pages.Mask.noControledDevice.positiveText')"
|
||||
type="warning"
|
||||
@positive-click="toStartServer"
|
||||
/>
|
||||
@ -215,7 +288,7 @@ async function checkUpdate() {
|
||||
ref="inputInstRef"
|
||||
v-model:value="inputBoxVal"
|
||||
type="text"
|
||||
placeholder="Input text and then press enter/esc"
|
||||
:placeholder="$t('pages.Mask.inputBoxPlaceholder')"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
|
@ -25,7 +25,9 @@ import {
|
||||
AndroidMetastate,
|
||||
} from "../frontcommand/android";
|
||||
import { ref } from "vue";
|
||||
import { useI18n } from "vue-i18n";
|
||||
|
||||
const { t } = useI18n();
|
||||
const router = useRouter();
|
||||
const route = useRoute();
|
||||
const store = useGlobalStore();
|
||||
@ -61,7 +63,7 @@ async function sendKeyCodeToDevice(code: AndroidKeycode) {
|
||||
metastate: AndroidMetastate.AMETA_NONE,
|
||||
});
|
||||
} else {
|
||||
message.error("未连接设备");
|
||||
message.error(t("sidebar.noControledDevice"));
|
||||
}
|
||||
}
|
||||
|
||||
@ -70,7 +72,7 @@ async function changeScreenPowerMode() {
|
||||
sendSetScreenPowerMode({ mode: nextScreenPowerMode.value });
|
||||
nextScreenPowerMode.value = nextScreenPowerMode.value ? 0 : 2;
|
||||
} else {
|
||||
message.error("未连接设备");
|
||||
message.error(t("sidebar.noControledDevice"));
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
@ -18,7 +18,9 @@ import { useGlobalStore } from "../../store/global";
|
||||
import { DropdownOption, NDropdown, useDialog, useMessage } from "naive-ui";
|
||||
import { onBeforeRouteLeave } from "vue-router";
|
||||
import { useKeyboardStore } from "../../store/keyboard";
|
||||
import { useI18n } from "vue-i18n";
|
||||
|
||||
const { t } = useI18n();
|
||||
const store = useGlobalStore();
|
||||
const keyboardStore = useKeyboardStore();
|
||||
const dialog = useDialog();
|
||||
@ -27,27 +29,27 @@ const message = useMessage();
|
||||
const addButtonPos = ref({ x: 0, y: 0 });
|
||||
const addButtonOptions: DropdownOption[] = [
|
||||
{
|
||||
label: "普通点击",
|
||||
label: () => t("pages.KeyBoard.addButton.Tap"),
|
||||
key: "Tap",
|
||||
},
|
||||
{
|
||||
label: "键盘行走",
|
||||
label: () => t("pages.KeyBoard.addButton.SteeringWheel"),
|
||||
key: "SteeringWheel",
|
||||
},
|
||||
{
|
||||
label: "技能",
|
||||
label: () => t("pages.KeyBoard.addButton.Skill"),
|
||||
key: "DirectionalSkill",
|
||||
},
|
||||
{
|
||||
label: "技能取消",
|
||||
label: () => t("pages.KeyBoard.addButton.CancelSkill"),
|
||||
key: "CancelSkill",
|
||||
},
|
||||
{
|
||||
label: "观察视角",
|
||||
label: () => t("pages.KeyBoard.addButton.Observation"),
|
||||
key: "Observation",
|
||||
},
|
||||
{
|
||||
label: "宏",
|
||||
label: () => t("pages.KeyBoard.addButton.Macro"),
|
||||
key: "Macro",
|
||||
},
|
||||
];
|
||||
@ -83,7 +85,7 @@ function onAddButtonSelect(
|
||||
} else if (type === "DirectionalSkill") {
|
||||
(keyMapping as unknown as KeyDirectionalSkill).range = 30;
|
||||
} else if (type === "CancelSkill") {
|
||||
keyMapping.note = "取消技能";
|
||||
keyMapping.note = t("pages.KeyBoard.addButton.CancelSkill");
|
||||
} else if (type === "Observation") {
|
||||
(keyMapping as unknown as KeyMappingObservation).scale = 0.6;
|
||||
} else if (type === "Macro") {
|
||||
@ -141,7 +143,7 @@ function setCurButtonKey(curKey: string) {
|
||||
return;
|
||||
|
||||
if (!isKeyUnique(curKey)) {
|
||||
message.error("按键重复:" + curKey);
|
||||
message.error(t("pages.KeyBoard.buttonKeyRepeat", [curKey]));
|
||||
return;
|
||||
}
|
||||
|
||||
@ -250,16 +252,16 @@ onBeforeRouteLeave(() => {
|
||||
document.removeEventListener("wheel", handleMouseWheel);
|
||||
if (keyboardStore.edited) {
|
||||
dialog.warning({
|
||||
title: "Warning",
|
||||
content: "当前方案尚未保存,是否保存?",
|
||||
positiveText: "保存",
|
||||
negativeText: "取消",
|
||||
title: t("pages.KeyBoard.noSaveDialog.title"),
|
||||
content: t("pages.KeyBoard.noSaveDialog.content"),
|
||||
positiveText: t("pages.KeyBoard.noSaveDialog.positiveText"),
|
||||
negativeText: t("pages.KeyBoard.noSaveDialog.negativeText"),
|
||||
onPositiveClick: () => {
|
||||
if (store.applyEditKeyMappingList()) {
|
||||
keyboardStore.edited = false;
|
||||
resolve(true);
|
||||
} else {
|
||||
message.error("存在重复按键,无法保存");
|
||||
message.error(t("pages.KeyBoard.noSaveDialog.keyRepeat"));
|
||||
resolve(false);
|
||||
}
|
||||
},
|
||||
|
@ -16,6 +16,7 @@ import {
|
||||
import { CloseCircle, Settings } from "@vicons/ionicons5";
|
||||
import { KeyCommon, KeyMacro, KeyMacroList } from "../../keyMappingConfig";
|
||||
import { useKeyboardStore } from "../../store/keyboard";
|
||||
import { useI18n } from "vue-i18n";
|
||||
|
||||
const props = defineProps<{
|
||||
index: number;
|
||||
@ -23,6 +24,7 @@ const props = defineProps<{
|
||||
|
||||
const keyboardStore = useKeyboardStore();
|
||||
|
||||
const { t } = useI18n();
|
||||
const store = useGlobalStore();
|
||||
const message = useMessage();
|
||||
const elementRef = ref<HTMLElement | null>(null);
|
||||
@ -139,10 +141,10 @@ function saveMacro() {
|
||||
(keyMapping.value as KeyMacro).macro = macro;
|
||||
showMacroModal.value = false;
|
||||
keyboardStore.edited = true;
|
||||
message.success("宏代码解析成功,但不保证代码正确性,请自行测试");
|
||||
message.success(t("pages.KeyBoard.KeyCommon.macroParseSuccess"));
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
message.error("宏代码保存失败,请检查代码格式是否正确");
|
||||
message.error(t("pages.KeyBoard.KeyCommon.macroParseFailed"));
|
||||
}
|
||||
}
|
||||
|
||||
@ -213,29 +215,40 @@ function showSetting() {
|
||||
? "普通点击"
|
||||
: "宏"
|
||||
}}</NH4>
|
||||
<NFormItem v-if="keyMapping.type === 'Macro'" label="宏代码">
|
||||
<NButton type="success" @click="editMacro"> 编辑代码 </NButton>
|
||||
<NFormItem
|
||||
v-if="keyMapping.type === 'Macro'"
|
||||
:label="$t('pages.KeyBoard.KeyCommon.macro')"
|
||||
>
|
||||
<NButton type="success" @click="editMacro">
|
||||
{{ $t("pages.KeyBoard.KeyCommon.editMacro") }}
|
||||
</NButton>
|
||||
</NFormItem>
|
||||
<NFormItem v-if="keyMapping.type === 'Tap'" label="触摸时长">
|
||||
<NFormItem
|
||||
v-if="keyMapping.type === 'Tap'"
|
||||
:label="$t('pages.KeyBoard.setting.touchTime')"
|
||||
>
|
||||
<NInputNumber
|
||||
v-model:value="keyMapping.time"
|
||||
:min="0"
|
||||
placeholder="请输入触摸时长(ms)"
|
||||
:placeholder="$t('pages.KeyBoard.setting.touchTimePlaceholder')"
|
||||
@update:value="keyboardStore.edited = true"
|
||||
/>
|
||||
</NFormItem>
|
||||
<NFormItem v-if="keyMapping.type !== 'Macro'" label="触点ID">
|
||||
<NFormItem
|
||||
v-if="keyMapping.type !== 'Macro'"
|
||||
:label="$t('pages.KeyBoard.setting.pointerID')"
|
||||
>
|
||||
<NInputNumber
|
||||
v-model:value="keyMapping.pointerId"
|
||||
:min="0"
|
||||
placeholder="请输入触点ID"
|
||||
:placeholder="$t('pages.KeyBoard.setting.pointerIDPlaceholder')"
|
||||
@update:value="keyboardStore.edited = true"
|
||||
/>
|
||||
</NFormItem>
|
||||
<NFormItem label="备注">
|
||||
<NFormItem :label="$t('pages.KeyBoard.setting.note')">
|
||||
<NInput
|
||||
v-model:value="keyMapping.note"
|
||||
placeholder="请输入备注"
|
||||
:placeholder="$t('pages.KeyBoard.setting.notePlaceholder')"
|
||||
@update:value="keyboardStore.edited = true"
|
||||
/>
|
||||
</NFormItem>
|
||||
@ -245,33 +258,36 @@ function showSetting() {
|
||||
v-model:show="showMacroModal"
|
||||
@before-leave="saveMacro"
|
||||
>
|
||||
<NCard style="width: 50%; height: 80%" title="宏编辑">
|
||||
<NCard
|
||||
style="width: 50%; height: 80%"
|
||||
:title="$t('pages.KeyBoard.KeyCommon.macroModal.title')"
|
||||
>
|
||||
<NFlex vertical style="height: 100%">
|
||||
<div>按下按键执行</div>
|
||||
<div>{{ $t("pages.KeyBoard.KeyCommon.macroModal.down") }}</div>
|
||||
<NInput
|
||||
type="textarea"
|
||||
style="flex-grow: 1"
|
||||
placeholder="JSON宏代码, 可为空"
|
||||
placeholder="$t('pages.KeyBoard.KeyCommon.macroModal.placeholder')"
|
||||
v-model:value="editedMacroRaw.down"
|
||||
@update:value="macroEditedFlag = true"
|
||||
round
|
||||
clearable
|
||||
/>
|
||||
<div>按住执行</div>
|
||||
<div>{{ $t("pages.KeyBoard.KeyCommon.macroModal.loop") }}</div>
|
||||
<NInput
|
||||
type="textarea"
|
||||
style="flex-grow: 1"
|
||||
placeholder="JSON宏代码, 可为空"
|
||||
:placeholder="$t('pages.KeyBoard.KeyCommon.macroModal.placeholder')"
|
||||
v-model:value="editedMacroRaw.loop"
|
||||
@update:value="macroEditedFlag = true"
|
||||
round
|
||||
clearable
|
||||
/>
|
||||
<div>抬起执行</div>
|
||||
<div>{{ $t("pages.KeyBoard.KeyCommon.macroModal.up") }}</div>
|
||||
<NInput
|
||||
type="textarea"
|
||||
style="flex-grow: 1"
|
||||
placeholder="JSON宏代码, 可为空"
|
||||
:placeholder="$t('pages.KeyBoard.KeyCommon.macroModal.placeholder')"
|
||||
v-model:value="editedMacroRaw.up"
|
||||
@update:value="macroEditedFlag = true"
|
||||
round
|
||||
|
@ -109,7 +109,7 @@ function dragHandler(downEvent: MouseEvent) {
|
||||
<template>
|
||||
<div v-show="keyboardStore.showKeyInfoFlag" class="key-info" @contextmenu.prevent>
|
||||
<div class="key-info-header" @mousedown="dragHandler">
|
||||
Key Info
|
||||
{{ $t('pages.KeyBoard.KeyInfo.title') }}
|
||||
<div
|
||||
class="key-info-close"
|
||||
@click="keyboardStore.showKeyInfoFlag = false"
|
||||
@ -121,7 +121,7 @@ function dragHandler(downEvent: MouseEvent) {
|
||||
<div style="border-bottom: 1px solid var(--light-color)">
|
||||
{{ mouseX }}, {{ mouseY }}
|
||||
</div>
|
||||
<div v-if="keyboardCodeList.length === 0">Press any key</div>
|
||||
<div v-if="keyboardCodeList.length === 0">{{ $t('pages.KeyBoard.KeyInfo.note') }}</div>
|
||||
<div v-for="code in keyboardCodeList">
|
||||
{{ code }}
|
||||
</div>
|
||||
|
@ -124,27 +124,27 @@ function showSetting() {
|
||||
top: `${settingPosY}px`,
|
||||
}"
|
||||
>
|
||||
<NH4 prefix="bar">观察视角</NH4>
|
||||
<NFormItem label="灵敏度">
|
||||
<NH4 prefix="bar">{{ $t("pages.KeyBoard.Observation.observation") }}</NH4>
|
||||
<NFormItem :label="$t('pages.KeyBoard.Observation.scale')">
|
||||
<NInputNumber
|
||||
v-model:value="keyMapping.scale"
|
||||
placeholder="请输入灵敏度"
|
||||
:placeholder="$t('pages.KeyBoard.Observation.scalePlaceholder')"
|
||||
:step="0.1"
|
||||
@update:value="keyboardStore.edited = true"
|
||||
/>
|
||||
</NFormItem>
|
||||
<NFormItem label="触点ID">
|
||||
<NFormItem :label="$t('pages.KeyBoard.setting.pointerID')">
|
||||
<NInputNumber
|
||||
v-model:value="keyMapping.pointerId"
|
||||
:min="0"
|
||||
placeholder="请输入触点ID"
|
||||
:placeholder="$t('pages.KeyBoard.setting.pointerIDPlaceholder')"
|
||||
@update:value="keyboardStore.edited = true"
|
||||
/>
|
||||
</NFormItem>
|
||||
<NFormItem label="备注">
|
||||
<NFormItem :label="$t('pages.KeyBoard.setting.note')">
|
||||
<NInput
|
||||
v-model:value="keyMapping.note"
|
||||
placeholder="请输入备注"
|
||||
:placeholder="$t('pages.KeyBoard.setting.notePlaceholder')"
|
||||
@update:value="keyboardStore.edited = true"
|
||||
/>
|
||||
</NFormItem>
|
||||
|
@ -18,7 +18,9 @@ import { Store } from "@tauri-apps/plugin-store";
|
||||
import { loadDefaultKeyconfig } from "../../invoke";
|
||||
import { KeyMappingConfig } from "../../keyMappingConfig";
|
||||
import { useKeyboardStore } from "../../store/keyboard";
|
||||
import { useI18n } from "vue-i18n";
|
||||
|
||||
const { t } = useI18n();
|
||||
const store = useGlobalStore();
|
||||
const keyboardStore = useKeyboardStore();
|
||||
const localStore = new Store("store.bin");
|
||||
@ -126,7 +128,7 @@ function dragHandler(downEvent: MouseEvent) {
|
||||
keyboardStore.showSettingFlag &&
|
||||
store.keyMappingConfigList.length === 1
|
||||
) {
|
||||
message.info("当前仅有一个按键方案,点击导入默认,可导入预设方案");
|
||||
message.info(t("pages.KeyBoard.KeySetting.onlyOneConfig"));
|
||||
}
|
||||
}
|
||||
};
|
||||
@ -139,14 +141,14 @@ function importKeyMappingConfig() {
|
||||
keyMappingConfig = JSON.parse(importModalInputValue.value);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
message.error("导入失败");
|
||||
message.error(t("pages.KeyBoard.KeySetting.importFailed"));
|
||||
return;
|
||||
}
|
||||
store.keyMappingConfigList.push(keyMappingConfig);
|
||||
store.setKeyMappingIndex(store.keyMappingConfigList.length - 1);
|
||||
showImportModal.value = false;
|
||||
localStore.set("keyMappingConfigList", store.keyMappingConfigList);
|
||||
message.success("按键方案已导入");
|
||||
message.success(t("pages.KeyBoard.KeySetting.importSuccess"));
|
||||
}
|
||||
|
||||
async function importDefaultKeyMappingConfig() {
|
||||
@ -161,17 +163,17 @@ async function importDefaultKeyMappingConfig() {
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
message.error("默认按键方案导入失败");
|
||||
message.error(t("pages.KeyBoard.KeySetting.importDefaultFailed"));
|
||||
return;
|
||||
}
|
||||
|
||||
localStore.set("keyMappingConfigList", store.keyMappingConfigList);
|
||||
message.success(`已导入${count}个默认方案`);
|
||||
message.success(t("pages.KeyBoard.KeySetting.importDefaultSuccess", [count]));
|
||||
}
|
||||
|
||||
function createKeyMappingConfig() {
|
||||
if (keyboardStore.edited) {
|
||||
message.error("请先保存或还原当前方案");
|
||||
message.error(t("pages.KeyBoard.KeySetting.configEdited"));
|
||||
return;
|
||||
}
|
||||
|
||||
@ -179,7 +181,7 @@ function createKeyMappingConfig() {
|
||||
"keyboardElement"
|
||||
) as HTMLElement;
|
||||
const newConfig: KeyMappingConfig = {
|
||||
title: "新方案",
|
||||
title: t("pages.KeyBoard.KeySetting.newConfig"),
|
||||
relativeSize: {
|
||||
w: keyboardElement.clientWidth,
|
||||
h: keyboardElement.clientHeight,
|
||||
@ -189,18 +191,21 @@ function createKeyMappingConfig() {
|
||||
store.keyMappingConfigList.push(newConfig);
|
||||
store.setKeyMappingIndex(store.keyMappingConfigList.length - 1);
|
||||
localStore.set("keyMappingConfigList", store.keyMappingConfigList);
|
||||
message.success("新方案已创建");
|
||||
message.success(t("pages.KeyBoard.KeySetting.newConfigSuccess"));
|
||||
}
|
||||
|
||||
function copyCurKeyMappingConfig() {
|
||||
if (keyboardStore.edited) {
|
||||
message.error("请先保存或还原当前方案");
|
||||
message.error(t("pages.KeyBoard.KeySetting.configEdited"));
|
||||
return;
|
||||
}
|
||||
|
||||
const curConfig = store.keyMappingConfigList[store.curKeyMappingIndex];
|
||||
const newTitle = t("pages.KeyBoard.KeySetting.copyConfigTitle", [
|
||||
curConfig.title,
|
||||
]);
|
||||
const newConfig: KeyMappingConfig = {
|
||||
title: curConfig.title + "-副本",
|
||||
title: newTitle,
|
||||
relativeSize: curConfig.relativeSize,
|
||||
list: curConfig.list,
|
||||
};
|
||||
@ -209,12 +214,12 @@ function copyCurKeyMappingConfig() {
|
||||
keyboardStore.activeSteeringWheelButtonKeyIndex = -1;
|
||||
store.setKeyMappingIndex(store.keyMappingConfigList.length - 1);
|
||||
localStore.set("keyMappingConfigList", store.keyMappingConfigList);
|
||||
message.success("方案已复制为:" + curConfig.title + "-副本");
|
||||
message.success(t("pages.KeyBoard.KeySetting.copyConfigSuccess", [newTitle]));
|
||||
}
|
||||
|
||||
function delCurKeyMappingConfig() {
|
||||
if (store.keyMappingConfigList.length <= 1) {
|
||||
message.error("至少保留一个方案");
|
||||
message.error(t("pages.KeyBoard.KeySetting.delConfigLeast"));
|
||||
return;
|
||||
}
|
||||
const title = store.keyMappingConfigList[store.curKeyMappingIndex].title;
|
||||
@ -228,7 +233,7 @@ function delCurKeyMappingConfig() {
|
||||
store.curKeyMappingIndex > 0 ? store.curKeyMappingIndex - 1 : 0
|
||||
);
|
||||
localStore.set("keyMappingConfigList", store.keyMappingConfigList);
|
||||
message.success("方案已删除:" + title);
|
||||
message.success(t("pages.KeyBoard.KeySetting.delSuccess", [title]));
|
||||
}
|
||||
|
||||
function renameKeyMappingConfig() {
|
||||
@ -237,9 +242,9 @@ function renameKeyMappingConfig() {
|
||||
if (newTitle !== "") {
|
||||
store.keyMappingConfigList[store.curKeyMappingIndex].title = newTitle;
|
||||
localStore.set("keyMappingConfigList", store.keyMappingConfigList);
|
||||
message.success("方案已重命名为:" + newTitle);
|
||||
message.success(t("pages.KeyBoard.KeySetting.renameSuccess", [newTitle]));
|
||||
} else {
|
||||
message.error("方案名不能为空");
|
||||
message.error(t("pages.KeyBoard.KeySetting.renameEmpty"));
|
||||
}
|
||||
}
|
||||
|
||||
@ -249,11 +254,11 @@ function exportKeyMappingConfig() {
|
||||
navigator.clipboard
|
||||
.writeText(data)
|
||||
.then(() => {
|
||||
message.success("当前按键方案已导出到剪切板");
|
||||
message.success(t("pages.KeyBoard.KeySetting.exportSuccess"));
|
||||
})
|
||||
.catch((e) => {
|
||||
console.error(e);
|
||||
message.error("按键方案导出失败");
|
||||
message.error(t("pages.KeyBoard.KeySetting.exportFailed"));
|
||||
});
|
||||
}
|
||||
|
||||
@ -261,7 +266,7 @@ function saveKeyMappingConfig() {
|
||||
if (store.applyEditKeyMappingList()) {
|
||||
keyboardStore.edited = false;
|
||||
} else {
|
||||
message.error("存在重复按键,无法保存");
|
||||
message.error(t("pages.KeyBoard.KeySetting.saveKeyRepeat"));
|
||||
}
|
||||
}
|
||||
|
||||
@ -278,14 +283,16 @@ function checkConfigSize() {
|
||||
keyboardElement.clientHeight !== relativeSize.h
|
||||
) {
|
||||
message.warning(
|
||||
`请注意当前按键方案"${curKeyMappingConfig.title}"与蒙版尺寸不一致,若有需要可进行迁移`
|
||||
t("pages.KeyBoard.KeySetting.checkConfigSizeWarning", [
|
||||
curKeyMappingConfig.title,
|
||||
])
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function migrateKeyMappingConfig() {
|
||||
if (keyboardStore.edited) {
|
||||
message.error("请先保存或还原当前按键方案");
|
||||
message.error(t("pages.KeyBoard.KeySetting.configEdited"));
|
||||
return;
|
||||
}
|
||||
|
||||
@ -313,25 +320,29 @@ function migrateKeyMappingConfig() {
|
||||
keyMapping.posY = Math.round((keyMapping.posY / relativeSize.h) * sizeH);
|
||||
}
|
||||
// migrate title
|
||||
newConfig.title += "-迁移";
|
||||
newConfig.title = t("pages.KeyBoard.KeySetting.migrateConfigTitle", [
|
||||
newConfig.title,
|
||||
]);
|
||||
|
||||
store.keyMappingConfigList.splice(
|
||||
store.curKeyMappingIndex + 1,
|
||||
0,
|
||||
newConfig
|
||||
);
|
||||
message.success("已迁移到新方案:" + newConfig.title);
|
||||
message.success(
|
||||
t("pages.KeyBoard.KeySetting.migrateConfigSuccess", [newConfig.title])
|
||||
);
|
||||
keyboardStore.activeButtonIndex = -1;
|
||||
keyboardStore.activeSteeringWheelButtonKeyIndex = -1;
|
||||
store.setKeyMappingIndex(store.curKeyMappingIndex + 1);
|
||||
} else {
|
||||
message.info("当前方案符合蒙版尺寸,无需迁移");
|
||||
message.info(t("pages.KeyBoard.KeySetting.migrateConfigNeedless"));
|
||||
}
|
||||
}
|
||||
|
||||
function selectKeyMappingConfig(index: number) {
|
||||
if (keyboardStore.edited) {
|
||||
message.error("请先保存或还原当前按键方案");
|
||||
message.error(t("pages.KeyBoard.KeySetting.configEdited"));
|
||||
return;
|
||||
}
|
||||
|
||||
@ -355,7 +366,7 @@ function resetKeyMappingConfig() {
|
||||
size="large"
|
||||
class="key-setting-btn"
|
||||
id="keySettingBtn"
|
||||
title="长按可拖动"
|
||||
:title="$t('pages.KeyBoard.KeySetting.buttonDrag')"
|
||||
@mousedown="dragHandler"
|
||||
:style="{
|
||||
left: keySettingPos.x + 'px',
|
||||
@ -381,50 +392,73 @@ function resetKeyMappingConfig() {
|
||||
>
|
||||
<NIcon><CloseCircle></CloseCircle></NIcon>
|
||||
</NButton>
|
||||
<NH4 prefix="bar">按键方案</NH4>
|
||||
<NH4 prefix="bar">{{ $t("pages.KeyBoard.KeySetting.config") }}</NH4>
|
||||
<NSelect
|
||||
:value="store.curKeyMappingIndex"
|
||||
@update:value="selectKeyMappingConfig"
|
||||
:options="keyMappingNameOptions"
|
||||
/>
|
||||
<NP style="margin-top: 20px">
|
||||
Relative Size:{{ curRelativeSize.w }}x{{ curRelativeSize.h }}
|
||||
{{
|
||||
$t("pages.KeyBoard.KeySetting.configRelativeSize", [
|
||||
curRelativeSize.w,
|
||||
curRelativeSize.h,
|
||||
])
|
||||
}}
|
||||
</NP>
|
||||
<NFlex style="margin-top: 20px">
|
||||
<template v-if="keyboardStore.edited">
|
||||
<NButton type="success" @click="saveKeyMappingConfig">保存方案</NButton>
|
||||
<NButton type="error" @click="resetKeyMappingConfig">还原方案</NButton>
|
||||
<NButton type="success" @click="saveKeyMappingConfig">{{
|
||||
$t("pages.KeyBoard.KeySetting.saveConfig")
|
||||
}}</NButton>
|
||||
<NButton type="error" @click="resetKeyMappingConfig">{{
|
||||
$t("pages.KeyBoard.KeySetting.resetConfig")
|
||||
}}</NButton>
|
||||
</template>
|
||||
<NButton @click="createKeyMappingConfig">新建方案</NButton>
|
||||
<NButton @click="copyCurKeyMappingConfig">复制方案</NButton>
|
||||
<NButton @click="migrateKeyMappingConfig">迁移方案</NButton>
|
||||
<NButton @click="delCurKeyMappingConfig">删除方案</NButton>
|
||||
<NButton @click="createKeyMappingConfig">{{
|
||||
$t("pages.KeyBoard.KeySetting.createConfig")
|
||||
}}</NButton>
|
||||
<NButton @click="copyCurKeyMappingConfig">{{
|
||||
$t("pages.KeyBoard.KeySetting.copyConfig")
|
||||
}}</NButton>
|
||||
<NButton @click="migrateKeyMappingConfig">{{
|
||||
$t("pages.KeyBoard.KeySetting.migrateConfig")
|
||||
}}</NButton>
|
||||
<NButton @click="delCurKeyMappingConfig">{{
|
||||
$t("pages.KeyBoard.KeySetting.delConfig")
|
||||
}}</NButton>
|
||||
<NButton
|
||||
@click="
|
||||
showRenameModal = true;
|
||||
renameModalInputValue =
|
||||
store.keyMappingConfigList[store.curKeyMappingIndex].title;
|
||||
"
|
||||
>重命名</NButton
|
||||
>{{ $t("pages.KeyBoard.KeySetting.renameConfig") }}</NButton
|
||||
>
|
||||
</NFlex>
|
||||
<NH4 prefix="bar">其他</NH4>
|
||||
<NH4 prefix="bar">{{ $t("pages.KeyBoard.KeySetting.others") }}</NH4>
|
||||
<NFlex>
|
||||
<NButton
|
||||
@click="
|
||||
showImportModal = true;
|
||||
importModalInputValue = '';
|
||||
"
|
||||
>导入方案</NButton
|
||||
>{{ $t("pages.KeyBoard.KeySetting.importConfig") }}</NButton
|
||||
>
|
||||
<NButton @click="exportKeyMappingConfig">导出方案</NButton>
|
||||
<NButton @click="importDefaultKeyMappingConfig">导入默认</NButton>
|
||||
<NButton @click="exportKeyMappingConfig">{{
|
||||
$t("pages.KeyBoard.KeySetting.exportConfig")
|
||||
}}</NButton>
|
||||
<NButton @click="importDefaultKeyMappingConfig">{{
|
||||
$t("pages.KeyBoard.KeySetting.importDefaultConfig")
|
||||
}}</NButton>
|
||||
<NButton
|
||||
@click="keyboardStore.showKeyInfoFlag = !keyboardStore.showKeyInfoFlag"
|
||||
>按键信息</NButton
|
||||
>{{ $t("pages.KeyBoard.KeySetting.keyInfo") }}</NButton
|
||||
>
|
||||
</NFlex>
|
||||
<NP style="margin-top: 40px">提示:右键空白区域可添加按键</NP>
|
||||
<NP style="margin-top: 40px">{{
|
||||
$t("pages.KeyBoard.KeySetting.addButtonTip")
|
||||
}}</NP>
|
||||
</div>
|
||||
<NModal v-model:show="showImportModal">
|
||||
<NCard style="width: 40%; height: 50%">
|
||||
@ -432,24 +466,27 @@ function resetKeyMappingConfig() {
|
||||
<NInput
|
||||
type="textarea"
|
||||
style="flex-grow: 1"
|
||||
placeholder="粘贴单个按键方案的JSON文本 (此处无法对按键方案的合法性进行判断, 请确保JSON内容正确)"
|
||||
:placeholder="$t('pages.KeyBoard.KeySetting.importPlaceholder')"
|
||||
v-model:value="importModalInputValue"
|
||||
round
|
||||
clearable
|
||||
/>
|
||||
<NButton type="success" round @click="importKeyMappingConfig"
|
||||
>导入</NButton
|
||||
>
|
||||
<NButton type="success" round @click="importKeyMappingConfig">{{
|
||||
$t("pages.KeyBoard.KeySetting.import")
|
||||
}}</NButton>
|
||||
</NFlex>
|
||||
</NCard>
|
||||
</NModal>
|
||||
<NModal v-model:show="showRenameModal">
|
||||
<NCard style="width: 40%" title="重命名按键方案">
|
||||
<NCard
|
||||
style="width: 40%"
|
||||
:title="$t('pages.KeyBoard.KeySetting.renameTitle')"
|
||||
>
|
||||
<NFlex vertical>
|
||||
<NInput v-model:value="renameModalInputValue" clearable />
|
||||
<NButton type="success" round @click="renameKeyMappingConfig"
|
||||
>重命名</NButton
|
||||
>
|
||||
<NButton type="success" round @click="renameKeyMappingConfig">{{
|
||||
$t("pages.KeyBoard.KeySetting.renameConfig")
|
||||
}}</NButton>
|
||||
</NFlex>
|
||||
</NCard>
|
||||
</NModal>
|
||||
|
@ -260,27 +260,30 @@ function updateRangeIndicator(element?: HTMLElement) {
|
||||
top: `${settingPosY}px`,
|
||||
}"
|
||||
>
|
||||
<NH4 prefix="bar">技能</NH4>
|
||||
<NFormItem label="选项">
|
||||
<NH4 prefix="bar">{{ $t("pages.KeyBoard.KeySkill.skill") }}</NH4>
|
||||
<NFormItem :label="$t('pages.KeyBoard.KeySkill.options')">
|
||||
<NFlex vertical>
|
||||
<NCheckbox
|
||||
@click="changeSkillType('trigger-double')"
|
||||
:checked="isTriggerWhenDoublePressed"
|
||||
>双击施放</NCheckbox
|
||||
>{{ $t("pages.KeyBoard.KeySkill.double") }}</NCheckbox
|
||||
>
|
||||
<NCheckbox
|
||||
@click="changeSkillType('direction')"
|
||||
:checked="isDirectionless"
|
||||
>无方向技能</NCheckbox
|
||||
>{{ $t("pages.KeyBoard.KeySkill.directionless") }}</NCheckbox
|
||||
>
|
||||
<NCheckbox
|
||||
@click="changeSkillType('trigger')"
|
||||
:checked="isTriggerWhenPressed"
|
||||
>按下时触发</NCheckbox
|
||||
>{{ $t("pages.KeyBoard.KeySkill.triggerWhenPressed") }}</NCheckbox
|
||||
>
|
||||
</NFlex>
|
||||
</NFormItem>
|
||||
<NFormItem v-if="!isDirectionless" label="范围">
|
||||
<NFormItem
|
||||
v-if="!isDirectionless"
|
||||
:label="$t('pages.KeyBoard.KeySkill.range')"
|
||||
>
|
||||
<NInputNumber
|
||||
v-if="keyMapping.type === 'DirectionalSkill'"
|
||||
v-model:value="keyMapping.range"
|
||||
@ -317,27 +320,27 @@ function updateRangeIndicator(element?: HTMLElement) {
|
||||
</NFormItem>
|
||||
<NFormItem
|
||||
v-if="(keyMapping.type==='TriggerWhenPressedSkill'&&!(keyMapping as KeyTriggerWhenPressedSkill).directional)"
|
||||
label="触摸时长"
|
||||
:label="$t('pages.KeyBoard.setting.touchTime')"
|
||||
>
|
||||
<NInputNumber
|
||||
v-model:value="keyMapping.rangeOrTime"
|
||||
:min="0"
|
||||
placeholder="请输入触摸时长(ms)"
|
||||
:placeholder="$t('pages.KeyBoard.setting.touchTimePlaceholder')"
|
||||
@update:value="keyboardStore.edited = true"
|
||||
/>
|
||||
</NFormItem>
|
||||
<NFormItem label="触点ID">
|
||||
<NFormItem :label="$t('pages.KeyBoard.setting.pointerID')">
|
||||
<NInputNumber
|
||||
v-model:value="keyMapping.pointerId"
|
||||
:min="0"
|
||||
placeholder="请输入触点ID"
|
||||
:placeholder="$t('pages.KeyBoard.setting.pointerIDPlaceholder')"
|
||||
@update:value="keyboardStore.edited = true"
|
||||
/>
|
||||
</NFormItem>
|
||||
<NFormItem label="备注">
|
||||
<NFormItem :label="$t('pages.KeyBoard.setting.note')">
|
||||
<NInput
|
||||
v-model:value="keyMapping.note"
|
||||
placeholder="请输入备注"
|
||||
:placeholder="$t('pages.KeyBoard.setting.notePlaceholder')"
|
||||
@update:value="keyboardStore.edited = true"
|
||||
/>
|
||||
</NFormItem>
|
||||
|
@ -184,26 +184,28 @@ function showSetting() {
|
||||
top: `${settingPosY}px`,
|
||||
}"
|
||||
>
|
||||
<NH4 prefix="bar">键盘行走</NH4>
|
||||
<NFormItem label="偏移">
|
||||
<NH4 prefix="bar">{{
|
||||
$t("pages.KeyBoard.SteeringWheel.steeringWheel")
|
||||
}}</NH4>
|
||||
<NFormItem :label="$t('pages.KeyBoard.SteeringWheel.offset')">
|
||||
<NInputNumber
|
||||
v-model:value="keyMapping.offset"
|
||||
:min="1"
|
||||
@update:value="keyboardStore.edited = true"
|
||||
/>
|
||||
</NFormItem>
|
||||
<NFormItem label="触点ID">
|
||||
<NFormItem :label="$t('pages.KeyBoard.setting.pointerID')">
|
||||
<NInputNumber
|
||||
v-model:value="keyMapping.pointerId"
|
||||
:min="0"
|
||||
placeholder="请输入触点ID"
|
||||
:placeholder="$t('pages.KeyBoard.setting.pointerIDPlaceholder')"
|
||||
@update:value="keyboardStore.edited = true"
|
||||
/>
|
||||
</NFormItem>
|
||||
<NFormItem label="备注">
|
||||
<NFormItem :label="$t('pages.KeyBoard.setting.note')">
|
||||
<NInput
|
||||
v-model:value="keyMapping.note"
|
||||
placeholder="请输入备注"
|
||||
:placeholder="$t('pages.KeyBoard.setting.notePlaceholder')"
|
||||
@update:value="keyboardStore.edited = true"
|
||||
/>
|
||||
</NFormItem>
|
||||
|
@ -1,12 +1,14 @@
|
||||
<script setup lang="ts">
|
||||
import { NFlex, NH4, NButton, NIcon, NP } from "naive-ui";
|
||||
import { NFlex, NH4, NButton, NIcon, NP, NCheckbox } from "naive-ui";
|
||||
import { LogoGithub, Planet } from "@vicons/ionicons5";
|
||||
import { open } from "@tauri-apps/plugin-shell";
|
||||
import { getVersion } from "@tauri-apps/api/app";
|
||||
import { onMounted, ref } from "vue";
|
||||
import { useGlobalStore } from "../../store/global";
|
||||
import { Store } from "@tauri-apps/plugin-store";
|
||||
|
||||
const store = useGlobalStore();
|
||||
const localStore = new Store("store.bin");
|
||||
|
||||
const appVersion = ref("");
|
||||
onMounted(async () => {
|
||||
@ -26,11 +28,8 @@ async function checkUpdate() {
|
||||
|
||||
<template>
|
||||
<div class="setting-page">
|
||||
<NH4 prefix="bar">关于</NH4>
|
||||
<NP
|
||||
>A Scrcpy client in Rust & Tarui aimed at providing mouse and key mapping
|
||||
to control Android device.</NP
|
||||
>
|
||||
<NH4 prefix="bar">{{ $t("pages.Setting.About.about") }}</NH4>
|
||||
<NP>{{ $t("pages.Setting.About.introduction") }}</NP>
|
||||
<NFlex class="website">
|
||||
<NButton
|
||||
text
|
||||
@ -39,7 +38,7 @@ async function checkUpdate() {
|
||||
<template #icon>
|
||||
<NIcon><LogoGithub /> </NIcon>
|
||||
</template>
|
||||
Github repo
|
||||
{{ $t("pages.Setting.About.github") }}
|
||||
</NButton>
|
||||
<NButton
|
||||
text
|
||||
@ -67,12 +66,21 @@ async function checkUpdate() {
|
||||
<template #icon>
|
||||
<NIcon><Planet /> </NIcon>
|
||||
</template>
|
||||
AkiChase's Blog
|
||||
{{ $t("pages.Setting.About.blog") }}
|
||||
</NButton>
|
||||
</NFlex>
|
||||
<NH4 prefix="bar">更新</NH4>
|
||||
<NP>当前版本:{{ appVersion }}</NP>
|
||||
<NButton @click="checkUpdate">检查更新</NButton>
|
||||
<NH4 prefix="bar">{{ $t("pages.Setting.About.update") }}</NH4>
|
||||
<NCheckbox
|
||||
v-model:checked="store.checkUpdateAtStart"
|
||||
@update:checked="
|
||||
localStore.set('checkUpdateAtStart', store.checkUpdateAtStart)
|
||||
"
|
||||
>{{ $t("pages.Setting.About.checkUpdateOnStartup") }}</NCheckbox
|
||||
>
|
||||
<NP>{{ $t("pages.Setting.About.curVersion", [appVersion]) }}</NP>
|
||||
<NButton @click="checkUpdate">{{
|
||||
$t("pages.Setting.About.checkUpdate")
|
||||
}}</NButton>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
@ -13,10 +13,14 @@ import {
|
||||
useDialog,
|
||||
NCard,
|
||||
NIcon,
|
||||
NSelect,
|
||||
} from "naive-ui";
|
||||
import { relaunch } from "@tauri-apps/plugin-process";
|
||||
import { onMounted, ref } from "vue";
|
||||
import i18n from "../../i18n";
|
||||
import { useI18n } from "vue-i18n";
|
||||
|
||||
const { t } = useI18n();
|
||||
const localStore = new Store("store.bin");
|
||||
const dialog = useDialog();
|
||||
|
||||
@ -25,8 +29,16 @@ const showDataModal = ref(false);
|
||||
const dataModalInputVal = ref("");
|
||||
let curDataIndex = -1;
|
||||
|
||||
onMounted(() => {
|
||||
const languageOptions = [
|
||||
{ label: "简体中文", value: "zh-CN" },
|
||||
{ label: "English", value: "en-US" },
|
||||
];
|
||||
|
||||
const curLanguage = ref("en-US");
|
||||
|
||||
onMounted(async () => {
|
||||
refreshLocalData();
|
||||
curLanguage.value = (await localStore.get<string>("language")) ?? "en-US";
|
||||
});
|
||||
|
||||
async function refreshLocalData() {
|
||||
@ -46,10 +58,10 @@ function showLocalStore(index: number) {
|
||||
function delLocalStore(key?: string) {
|
||||
if (key) {
|
||||
dialog.warning({
|
||||
title: "Warning",
|
||||
content: `即将删除数据"${key}",删除操作不可撤回,是否继续?`,
|
||||
positiveText: "删除",
|
||||
negativeText: "取消",
|
||||
title: t("pages.Setting.Basic.delLocalStore.dialog.title"),
|
||||
content: t("pages.Setting.Basic.delLocalStore.dialog.delKey", [key]),
|
||||
positiveText: t("pages.Setting.Basic.delLocalStore.dialog.positiveText"),
|
||||
negativeText: t("pages.Setting.Basic.delLocalStore.dialog.negativeText"),
|
||||
onPositiveClick: () => {
|
||||
localStore.delete(key);
|
||||
localStoreEntries.value.splice(curDataIndex, 1);
|
||||
@ -58,23 +70,37 @@ function delLocalStore(key?: string) {
|
||||
});
|
||||
} else {
|
||||
dialog.warning({
|
||||
title: "Warning",
|
||||
content: "即将清空数据,操作不可撤回,且清空后将重启软件,是否继续?",
|
||||
positiveText: "删除",
|
||||
negativeText: "取消",
|
||||
title: t("pages.Setting.Basic.delLocalStore.dialog.title"),
|
||||
content: t("pages.Setting.Basic.delLocalStore.dialog.delAll"),
|
||||
positiveText: t("pages.Setting.Basic.delLocalStore.dialog.positiveText"),
|
||||
negativeText: t("pages.Setting.Basic.delLocalStore.dialog.negativeText"),
|
||||
onPositiveClick: () => {
|
||||
// localStore.clear();
|
||||
localStore.clear();
|
||||
relaunch();
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function changeLanguage(language: "zh-CN" | "en-US") {
|
||||
if (language === curLanguage.value) return;
|
||||
curLanguage.value = language;
|
||||
localStore.set("language", language);
|
||||
i18n.global.locale = language;
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="setting-page">
|
||||
<NH4 prefix="bar">{{ $t("pages.Setting.Basic.language") }}</NH4>
|
||||
<NSelect
|
||||
:value="curLanguage"
|
||||
@update:value="changeLanguage"
|
||||
:options="languageOptions"
|
||||
style="max-width: 300px; margin: 20px 0"
|
||||
/>
|
||||
<NFlex justify="space-between">
|
||||
<NH4 prefix="bar">本地数据</NH4>
|
||||
<NH4 prefix="bar">{{ $t("pages.Setting.Basic.localStore") }}</NH4>
|
||||
<NFlex>
|
||||
<NButton
|
||||
tertiary
|
||||
@ -100,9 +126,7 @@ function delLocalStore(key?: string) {
|
||||
</NButton>
|
||||
</NFlex>
|
||||
</NFlex>
|
||||
<NP
|
||||
>删除数据可能导致无法预料的后果,请慎重操作。若出现异常请尝试清空数据并重启软件。</NP
|
||||
>
|
||||
<NP>{{ $t("pages.Setting.Basic.delLocalStore.warning") }}</NP>
|
||||
<NList class="data-list" hoverable clickable>
|
||||
<NListItem v-for="(entrie, index) in localStoreEntries">
|
||||
<div @click="showLocalStore(index)">
|
||||
@ -112,7 +136,10 @@ function delLocalStore(key?: string) {
|
||||
</NList>
|
||||
</div>
|
||||
<NModal v-model:show="showDataModal">
|
||||
<NCard style="width: 50%; height: 80%" title="卡片">
|
||||
<NCard
|
||||
style="width: 50%; height: 80%"
|
||||
:title="localStoreEntries[curDataIndex][0]"
|
||||
>
|
||||
<NFlex vertical style="height: 100%">
|
||||
<NInput
|
||||
type="textarea"
|
||||
@ -125,7 +152,7 @@ function delLocalStore(key?: string) {
|
||||
type="success"
|
||||
round
|
||||
@click="delLocalStore(localStoreEntries[curDataIndex][0])"
|
||||
>删除当前数据</NButton
|
||||
>{{ $t("pages.Setting.Basic.delCurData") }}</NButton
|
||||
>
|
||||
</NFlex>
|
||||
</NCard>
|
||||
|
@ -28,11 +28,13 @@ import { Store } from "@tauri-apps/plugin-store";
|
||||
import { SettingsOutline } from "@vicons/ionicons5";
|
||||
import { UnlistenFn } from "@tauri-apps/api/event";
|
||||
import { useGlobalStore } from "../../store/global";
|
||||
import { useI18n } from "vue-i18n";
|
||||
|
||||
let unlistenResize: UnlistenFn = () => {};
|
||||
let unlistenMove: UnlistenFn = () => {};
|
||||
let factor = 1;
|
||||
|
||||
const { t } = useI18n();
|
||||
const localStore = new Store("store.bin");
|
||||
const store = useGlobalStore();
|
||||
const message = useMessage();
|
||||
@ -57,25 +59,25 @@ const areaFormRules: FormRules = {
|
||||
type: "number",
|
||||
required: true,
|
||||
trigger: ["blur", "input"],
|
||||
message: "请输入左上角X坐标",
|
||||
message: () => t("pages.Setting.Mask.areaFormMissing.x"),
|
||||
},
|
||||
posY: {
|
||||
type: "number",
|
||||
required: true,
|
||||
trigger: ["blur", "input"],
|
||||
message: "请输入左上角Y坐标",
|
||||
message: () => t("pages.Setting.Mask.areaFormMissing.y"),
|
||||
},
|
||||
sizeW: {
|
||||
type: "number",
|
||||
required: true,
|
||||
trigger: ["blur", "input"],
|
||||
message: "请输入蒙版宽度",
|
||||
message: () => t("pages.Setting.Mask.areaFormMissing.w"),
|
||||
},
|
||||
sizeH: {
|
||||
type: "number",
|
||||
required: true,
|
||||
trigger: ["blur", "input"],
|
||||
message: "请输入蒙版高度",
|
||||
message: () => t("pages.Setting.Mask.areaFormMissing.h"),
|
||||
},
|
||||
};
|
||||
|
||||
@ -104,10 +106,10 @@ function handleAdjustClick(e: MouseEvent) {
|
||||
if (!errors) {
|
||||
adjustMaskArea().then(() => {
|
||||
localStore.set("maskArea", areaModel.value);
|
||||
message.success("蒙版区域已保存");
|
||||
message.success(t("pages.Setting.Mask.areaSaved"));
|
||||
});
|
||||
} else {
|
||||
message.error("请正确输入蒙版的坐标和尺寸");
|
||||
message.error(t("pages.Setting.Mask.incorrectArea"));
|
||||
}
|
||||
});
|
||||
}
|
||||
@ -163,14 +165,17 @@ onUnmounted(() => {
|
||||
|
||||
<template>
|
||||
<div class="setting-page">
|
||||
<NH4 prefix="bar">按键提示</NH4>
|
||||
<NFormItem label="是否显示" label-placement="left">
|
||||
<NH4 prefix="bar">{{ $t("pages.Setting.Mask.buttonPrompts") }}</NH4>
|
||||
<NFormItem
|
||||
:label="$t('pages.Setting.Mask.ifButtonPrompts')"
|
||||
label-placement="left"
|
||||
>
|
||||
<NCheckbox
|
||||
v-model:checked="store.maskButton.show"
|
||||
@update:checked="localStore.set('maskButton', store.maskButton)"
|
||||
/>
|
||||
</NFormItem>
|
||||
<NFormItem label="不透明度" label-placement="left">
|
||||
<NFormItem :label="$t('pages.Setting.Mask.opacity')" label-placement="left">
|
||||
<NSlider
|
||||
v-model:value="store.maskButton.transparency"
|
||||
@update:checked="localStore.set('maskButton', store.maskButton)"
|
||||
@ -190,7 +195,7 @@ onUnmounted(() => {
|
||||
require-mark-placement="right-hanging"
|
||||
>
|
||||
<NFlex justify="space-between" align="center">
|
||||
<NH4 prefix="bar">蒙版调整</NH4>
|
||||
<NH4 prefix="bar">{{ $t("pages.Setting.Mask.areaAdjust") }}</NH4>
|
||||
<NButton
|
||||
tertiary
|
||||
circle
|
||||
@ -207,29 +212,29 @@ onUnmounted(() => {
|
||||
<NFormItemGi label="X" path="posX">
|
||||
<NInputNumber
|
||||
v-model:value="areaModel.posX"
|
||||
placeholder="左上角X坐标"
|
||||
:placeholder="$t('pages.Setting.Mask.areaPlaceholder.x')"
|
||||
/>
|
||||
</NFormItemGi>
|
||||
<NFormItemGi label="Y" path="posY">
|
||||
<NInputNumber
|
||||
v-model:value="areaModel.posY"
|
||||
placeholder="左上角Y坐标"
|
||||
:placeholder="$t('pages.Setting.Mask.areaFormPlaceholder.y')"
|
||||
/>
|
||||
</NFormItemGi>
|
||||
<NFormItemGi label="W" path="sizeW">
|
||||
<NInputNumber
|
||||
v-model:value="areaModel.sizeW"
|
||||
placeholder="蒙版宽度"
|
||||
:placeholder="$t('pages.Setting.Mask.areaFormPlaceholder.w')"
|
||||
/>
|
||||
</NFormItemGi>
|
||||
<NFormItemGi label="H" path="sizeH">
|
||||
<NInputNumber
|
||||
v-model:value="areaModel.sizeH"
|
||||
placeholder="蒙版高度"
|
||||
:placeholder="$t('pages.Setting.Mask.areaFormPlaceholder.h')"
|
||||
/>
|
||||
</NFormItemGi>
|
||||
</NGrid>
|
||||
<NP>提示:蒙版尺寸与设备尺寸将用于坐标转换,请保证尺寸的准确性</NP>
|
||||
<NP>{{ $t("pages.Setting.Mask.areaTip") }}</NP>
|
||||
</NForm>
|
||||
</div>
|
||||
</template>
|
||||
|
@ -1,15 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
import { NH4 } from "naive-ui";
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="setting-page">
|
||||
<NH4 prefix="bar">敬请期待</NH4>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.setting-page {
|
||||
padding: 10px 25px;
|
||||
}
|
||||
</style>
|
@ -1,6 +1,5 @@
|
||||
<script setup lang="ts">
|
||||
import Basic from "./Basic.vue";
|
||||
import Script from "./Script.vue";
|
||||
import Mask from "./Mask.vue";
|
||||
import About from "./About.vue";
|
||||
import { NTabs, NTabPane, NScrollbar, NSpin } from "naive-ui";
|
||||
@ -13,22 +12,17 @@ const store = useGlobalStore();
|
||||
<div class="setting">
|
||||
<NSpin :show="store.showLoadingRef">
|
||||
<NTabs type="line" animated placement="left" default-value="basic">
|
||||
<NTabPane tab="基本设置" name="basic">
|
||||
<NTabPane :tab="$t('pages.Setting.tabs.basic')" name="basic">
|
||||
<NScrollbar>
|
||||
<Basic />
|
||||
</NScrollbar>
|
||||
</NTabPane>
|
||||
<NTabPane tab="蒙版设置" name="mask">
|
||||
<NTabPane :tab="$t('pages.Setting.tabs.mask')" name="mask">
|
||||
<NScrollbar>
|
||||
<Mask />
|
||||
</NScrollbar>
|
||||
</NTabPane>
|
||||
<NTabPane tab="脚本设置" name="script">
|
||||
<NScrollbar>
|
||||
<Script />
|
||||
</NScrollbar>
|
||||
</NTabPane>
|
||||
<NTabPane tab="关于" name="about">
|
||||
<NTabPane :tab="$t('pages.Setting.tabs.about')" name="about">
|
||||
<NScrollbar>
|
||||
<About />
|
||||
</NScrollbar>
|
||||
@ -46,7 +40,7 @@ const store = useGlobalStore();
|
||||
overflow-y: auto;
|
||||
display: flex;
|
||||
|
||||
.n-tabs{
|
||||
.n-tabs {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
|
220
src/i18n/en-US.json
Normal file
220
src/i18n/en-US.json
Normal file
@ -0,0 +1,220 @@
|
||||
{
|
||||
"pages": {
|
||||
"Device": {
|
||||
"localPort": "Local port",
|
||||
"status": "Status",
|
||||
"shutdown": {
|
||||
"title": "Warning",
|
||||
"content": "Are you sure to turn off the Scrcpy control service?",
|
||||
"positiveText": "Confirm",
|
||||
"negativeText": "Cancel"
|
||||
},
|
||||
"menu": {
|
||||
"control": "Control this device",
|
||||
"screen": "Get screen size"
|
||||
},
|
||||
"deviceControl": {
|
||||
"inputScreenSize": "Please enter the screen size of the current control device correctly",
|
||||
"closeCurDevice": "Please close the current control device first",
|
||||
"controlInfo": "The screen size has been saved and the control service is starting. Please keep the device screen on.",
|
||||
"connectTimeout": "Device connection timeout"
|
||||
},
|
||||
"deviceGetScreenSize": "Device screen size: ",
|
||||
"inputWirelessAddress": "Please enter the wireless debugging address",
|
||||
"localPortPlaceholder": "Scrcpy local port",
|
||||
"wireless": "Wireless connection",
|
||||
"wirelessPlaceholder": "Wireless connection address",
|
||||
"connect": "Connect",
|
||||
"deviceSize": {
|
||||
"title": "Device screen size",
|
||||
"width": "Width",
|
||||
"widthPlaceholder": "Screen width",
|
||||
"height": "Height",
|
||||
"heightPlaceholder": "Screen height",
|
||||
"tip": "Tip: Please enter the screen size of the current control device correctly. This is a necessary parameter to successfully send touch events."
|
||||
},
|
||||
"controledDevice": "Controlled device",
|
||||
"availableDevice": "Available devices",
|
||||
"noControledDevice": "No Controled Device"
|
||||
},
|
||||
"Mask": {
|
||||
"inputBoxPlaceholder": "Input text and then press enter/esc",
|
||||
"keyconfigException": "The key mapping config is abnormal, please delete this config",
|
||||
"blankConfig": "Blank config",
|
||||
"checkUpdate": {
|
||||
"failed": "Check for updates failed",
|
||||
"isLatest": "Latest version: {0}, currently the latest version",
|
||||
"notLatest": {
|
||||
"title": "Latest version: {0}",
|
||||
"positiveText": "Release page",
|
||||
"negativeText": "Cancel"
|
||||
}
|
||||
},
|
||||
"noControledDevice": {
|
||||
"title": "Controlled device not found",
|
||||
"content": "Please go to the device page to control any device",
|
||||
"positiveText": "To control"
|
||||
}
|
||||
},
|
||||
"Setting": {
|
||||
"tabs": {
|
||||
"basic": "Basic settings",
|
||||
"mask": "Mask setting",
|
||||
"about": "About"
|
||||
},
|
||||
"Mask": {
|
||||
"areaFormMissing": {
|
||||
"x": "Enter the X coordinate of the mask upper left corner",
|
||||
"y": "Enter the Y coordinate of the mask upper left corner",
|
||||
"w": "Enter mask width",
|
||||
"h": "Enter the mask height"
|
||||
},
|
||||
"areaSaved": "Mask area saved",
|
||||
"incorrectArea": "Please enter the coordinates and size of the mask correctly",
|
||||
"buttonPrompts": "Button prompts",
|
||||
"ifButtonPrompts": "Whether to display",
|
||||
"opacity": "Opacity",
|
||||
"areaAdjust": "Mask adjustment",
|
||||
"areaPlaceholder": {
|
||||
"x": "X coordinate of upper left corner"
|
||||
},
|
||||
"areaFormPlaceholder": {
|
||||
"y": "Y coordinate of upper left corner",
|
||||
"w": "Mask width",
|
||||
"h": "Mask height"
|
||||
},
|
||||
"areaTip": "Tip: The mask size and device size will be used for coordinate conversion, please ensure the accuracy of the size"
|
||||
},
|
||||
"Basic": {
|
||||
"delLocalStore": {
|
||||
"dialog": {
|
||||
"title": "Warning",
|
||||
"delKey": "Data \"{0}\" is about to be deleted. The deletion operation is irreversible. Do you want to continue?",
|
||||
"positiveText": "Delete",
|
||||
"negativeText": "Cancel",
|
||||
"delAll": "The data is about to be cleared. The operation is irreversible and the software will be restarted after clearing. Do you want to continue?"
|
||||
},
|
||||
"warning": "Deleting data may lead to unpredictable consequences, so please operate with caution. \nIf an exception occurs, please try clearing the data and restarting the software."
|
||||
},
|
||||
"language": "Language",
|
||||
"localStore": "Local data",
|
||||
"delCurData": "Delete current data"
|
||||
},
|
||||
"About": {
|
||||
"introduction": "A Scrcpy client in Rust & Tarui aimed at providing mouse and key mapping to control Android device.",
|
||||
"github": "Github repo",
|
||||
"blog": "AkiChase's Blog",
|
||||
"about": "About",
|
||||
"update": "Update",
|
||||
"checkUpdateOnStartup": "Check for software updates on startup",
|
||||
"curVersion": "Current version: {0}",
|
||||
"checkUpdate": "Check for updates"
|
||||
}
|
||||
},
|
||||
"KeyBoard": {
|
||||
"noSaveDialog": {
|
||||
"title": "Warning",
|
||||
"content": "The current plan has not been saved. Do you want to save it?",
|
||||
"positiveText": "Save",
|
||||
"negativeText": "Cancel",
|
||||
"keyRepeat": "There are duplicate keystrokes and cannot be saved."
|
||||
},
|
||||
"addButton": {
|
||||
"SteeringWheel": "SteeringWheel",
|
||||
"Tap": "Tap",
|
||||
"Skill": "Skill",
|
||||
"CancelSkill": "CancelSkill",
|
||||
"Observation": "Observation",
|
||||
"Macro": "Macro"
|
||||
},
|
||||
"buttonKeyRepeat": "Key repeat: {0}",
|
||||
"KeyCommon": {
|
||||
"macroParseSuccess": "The macro code is parsed successfully, but the correctness of the code is not guaranteed. Please test by yourself.",
|
||||
"macroParseFailed": "Macro code failed to save, please check whether the code format is correct.",
|
||||
"macro": "Macro code",
|
||||
"editMacro": "Edit macro",
|
||||
"macroModal": {
|
||||
"title": "Macro editor",
|
||||
"down": "Macro executed on key press",
|
||||
"placeholder": "JSON macro code, can be empty",
|
||||
"loop": "Macro executed on key press and hold",
|
||||
"up": "Macro executed on key up"
|
||||
}
|
||||
},
|
||||
"setting": {
|
||||
"touchTime": "Touch duration",
|
||||
"touchTimePlaceholder": "Touch duration (ms)",
|
||||
"pointerID": "Pointer ID",
|
||||
"pointerIDPlaceholder": "Please enter Pointer ID",
|
||||
"note": "Note",
|
||||
"notePlaceholder": "Please enter note"
|
||||
},
|
||||
"KeyInfo": {
|
||||
"title": "按键信息",
|
||||
"note": "按下任意键"
|
||||
},
|
||||
"Observation": {
|
||||
"observation": "Observation",
|
||||
"scale": "Sensitivity",
|
||||
"scalePlaceholder": "Please enter sensitivity"
|
||||
},
|
||||
"KeySetting": {
|
||||
"onlyOneConfig": "There is currently only one config. Click Import Default to import the preset configs.",
|
||||
"importFailed": "Import failed",
|
||||
"importSuccess": "Key config has been imported",
|
||||
"importDefaultFailed": "Import of default key config failed",
|
||||
"importDefaultSuccess": "{0} default configs have been imported",
|
||||
"configEdited": "Please save or reset the current config first",
|
||||
"newConfig": "New Config",
|
||||
"newConfigSuccess": "New config has been created",
|
||||
"copyConfigTitle": "{0}-Copy",
|
||||
"copyConfigSuccess": "The config has been copied as: {0}",
|
||||
"delConfigLeast": "Keep at least one config",
|
||||
"delSuccess": "Config deleted: {0}",
|
||||
"renameSuccess": "Config has been renamed: {0}",
|
||||
"renameEmpty": "Config name cannot be empty",
|
||||
"exportSuccess": "The current key config has been exported to the clipboard",
|
||||
"exportFailed": "Key config export failed",
|
||||
"saveKeyRepeat": "There are duplicate key and cannot be saved.",
|
||||
"checkConfigSizeWarning": "Please note that the current key config \"{0}\" is inconsistent with the mask size. You can migrate it if necessary.",
|
||||
"migrateConfigTitle": "{0}-Migrate",
|
||||
"migrateConfigSuccess": "Migrated to new config: {0}",
|
||||
"migrateConfigNeedless": "The current config conforms to the mask size and does not need to be migrated",
|
||||
"buttonDrag": "Long press to drag",
|
||||
"config": "Key mapping config",
|
||||
"configRelativeSize": "Relative Mask Size: {0}x{1}",
|
||||
"saveConfig": "Save config",
|
||||
"resetConfig": "Reset config",
|
||||
"renameConfig": "Rename",
|
||||
"renameTitle": "Rename key config",
|
||||
"import": "import",
|
||||
"importPlaceholder": "Paste the JSON text of a key mapping config (the legality of the key mapping config cannot be judged here, please ensure that the JSON content is correct)",
|
||||
"addButtonTip": "Tip: Right-click on the blank area to add buttons",
|
||||
"keyInfo": "Key Info",
|
||||
"importDefaultConfig": "Import default",
|
||||
"exportConfig": "Export config",
|
||||
"importConfig": "Import config",
|
||||
"others": "Others",
|
||||
"delConfig": "Delete config",
|
||||
"migrateConfig": "Migration config",
|
||||
"copyConfig": "Copy config",
|
||||
"createConfig": "Create config"
|
||||
},
|
||||
"KeySkill": {
|
||||
"skill": "Skill",
|
||||
"options": "Options",
|
||||
"double": "Double click to cast",
|
||||
"directionless": "Directionless skills",
|
||||
"triggerWhenPressed": "Trigger when pressed",
|
||||
"range": "Range"
|
||||
},
|
||||
"SteeringWheel": {
|
||||
"steeringWheel": "SteeringWheel",
|
||||
"offset": "Offset"
|
||||
}
|
||||
}
|
||||
},
|
||||
"sidebar": {
|
||||
"noControledDevice": "No devices are controlled"
|
||||
}
|
||||
}
|
21
src/i18n/index.ts
Normal file
21
src/i18n/index.ts
Normal file
@ -0,0 +1,21 @@
|
||||
import { createI18n } from "vue-i18n";
|
||||
import { Store } from "@tauri-apps/plugin-store";
|
||||
|
||||
import enUS from "./en-US.json";
|
||||
import zhCN from "./zh-CN.json";
|
||||
|
||||
const localStore = new Store("store.bin");
|
||||
|
||||
const i18n = createI18n({
|
||||
allowComposition: true,
|
||||
messages: {
|
||||
"en-US": enUS,
|
||||
"zh-CN": zhCN,
|
||||
},
|
||||
});
|
||||
|
||||
localStore.get<"en-US" | "zh-CN">("language").then((language) => {
|
||||
i18n.global.locale = language ?? "en-US";
|
||||
});
|
||||
|
||||
export default i18n;
|
220
src/i18n/zh-CN.json
Normal file
220
src/i18n/zh-CN.json
Normal file
@ -0,0 +1,220 @@
|
||||
{
|
||||
"pages": {
|
||||
"Device": {
|
||||
"status": "状态",
|
||||
"shutdown": {
|
||||
"title": "警告",
|
||||
"content": "确定关闭Scrcpy控制服务?",
|
||||
"positiveText": "确定",
|
||||
"negativeText": "取消"
|
||||
},
|
||||
"menu": {
|
||||
"control": "控制此设备",
|
||||
"screen": "获取屏幕尺寸"
|
||||
},
|
||||
"deviceControl": {
|
||||
"inputScreenSize": "请正确输入当前控制设备的屏幕尺寸",
|
||||
"closeCurDevice": "请先关闭当前控制设备",
|
||||
"controlInfo": "屏幕尺寸已保存,正在启动控制服务,请保持设备亮屏",
|
||||
"connectTimeout": "设备连接超时"
|
||||
},
|
||||
"deviceGetScreenSize": "设备屏幕尺寸为:",
|
||||
"inputWirelessAddress": "请输入无线调试地址",
|
||||
"localPort": "本地端口",
|
||||
"localPortPlaceholder": "Scrcpy 本地端口",
|
||||
"wireless": "无线连接",
|
||||
"wirelessPlaceholder": "无线连接地址",
|
||||
"connect": "连接",
|
||||
"deviceSize": {
|
||||
"title": "设备屏幕尺寸",
|
||||
"width": "宽度",
|
||||
"widthPlaceholder": "屏幕宽度",
|
||||
"heightPlaceholder": "屏幕高度",
|
||||
"height": "高度",
|
||||
"tip": "提示:请正确输入当前控制设备的屏幕尺寸,这是成功发送触摸事件的必要参数"
|
||||
},
|
||||
"controledDevice": "受控设备",
|
||||
"noControledDevice": "无受控设备",
|
||||
"availableDevice": "可用设备"
|
||||
},
|
||||
"Mask": {
|
||||
"keyconfigException": "按键方案异常,请删除此方案",
|
||||
"blankConfig": "空白方案",
|
||||
"checkUpdate": {
|
||||
"failed": "检查更新失败",
|
||||
"isLatest": "最新版本: {0},当前已是最新版本",
|
||||
"notLatest": {
|
||||
"title": "最新版本:{0}",
|
||||
"positiveText": "前往发布页",
|
||||
"negativeText": "取消"
|
||||
}
|
||||
},
|
||||
"noControledDevice": {
|
||||
"title": "未找到受控设备",
|
||||
"content": "请前往设备页面,控制任意设备",
|
||||
"positiveText": "去控制"
|
||||
},
|
||||
"inputBoxPlaceholder": "输入文本后按Enter/Esc"
|
||||
},
|
||||
"Setting": {
|
||||
"tabs": {
|
||||
"basic": "基本设置",
|
||||
"mask": "蒙版设置",
|
||||
"about": "关于"
|
||||
},
|
||||
"Mask": {
|
||||
"incorrectArea": "请正确输入蒙版的坐标和尺寸",
|
||||
"areaSaved": "蒙版区域已保存",
|
||||
"buttonPrompts": "按键提示",
|
||||
"ifButtonPrompts": "是否显示",
|
||||
"opacity": "不透明度",
|
||||
"areaAdjust": "蒙版调整",
|
||||
"areaPlaceholder": {
|
||||
"x": "左上角X坐标"
|
||||
},
|
||||
"areaFormPlaceholder": {
|
||||
"y": "左上角Y坐标",
|
||||
"w": "蒙版宽度",
|
||||
"h": "蒙版高度"
|
||||
},
|
||||
"areaTip": "提示:蒙版尺寸与设备尺寸将用于坐标转换,请保证尺寸的准确性",
|
||||
"areaFormMissing": {
|
||||
"x": "请输入蒙版左上角X坐标",
|
||||
"y": "请输入蒙版左上角Y坐标",
|
||||
"w": "请输入蒙版宽度",
|
||||
"h": "请输入蒙版高度"
|
||||
}
|
||||
},
|
||||
"Basic": {
|
||||
"delLocalStore": {
|
||||
"dialog": {
|
||||
"title": "警告",
|
||||
"positiveText": "删除",
|
||||
"negativeText": "取消",
|
||||
"delKey": "即将删除数据\"{0}\",删除操作不可撤回,是否继续?",
|
||||
"delAll": "即将清空数据,操作不可撤回,且清空后将重启软件,是否继续?"
|
||||
},
|
||||
"warning": "删除数据可能导致无法预料的后果,请慎重操作。若出现异常请尝试清空数据并重启软件。"
|
||||
},
|
||||
"language": "语言",
|
||||
"localStore": "本地数据",
|
||||
"delCurData": "删除当前数据"
|
||||
},
|
||||
"About": {
|
||||
"about": "关于",
|
||||
"introduction": "一个基于 Rust & Tarui 的 Scrcpy 客户端,旨在提供鼠标键盘按键映射来控制安卓设备。",
|
||||
"github": "Github 仓库",
|
||||
"blog": "AkiChase 博客",
|
||||
"update": "更新",
|
||||
"curVersion": "当前版本:{0}",
|
||||
"checkUpdate": "检查更新",
|
||||
"checkUpdateOnStartup": "启动时检查软件更新"
|
||||
}
|
||||
},
|
||||
"KeyBoard": {
|
||||
"addButton": {
|
||||
"Tap": "普通点击",
|
||||
"SteeringWheel": "键盘行走",
|
||||
"Skill": "技能",
|
||||
"CancelSkill": "技能取消",
|
||||
"Observation": "观察视角",
|
||||
"Macro": "宏"
|
||||
},
|
||||
"buttonKeyRepeat": "按键重复: {0}",
|
||||
"noSaveDialog": {
|
||||
"title": "警告",
|
||||
"content": "当前方案尚未保存,是否保存?",
|
||||
"positiveText": "保存",
|
||||
"negativeText": "取消",
|
||||
"keyRepeat": "存在重复按键,无法保存"
|
||||
},
|
||||
"KeyCommon": {
|
||||
"macroParseSuccess": "宏代码解析成功,但不保证代码正确性,请自行测试",
|
||||
"macroParseFailed": "宏代码保存失败,请检查代码格式是否正确",
|
||||
"macro": "宏代码",
|
||||
"editMacro": "编辑宏代码",
|
||||
"macroModal": {
|
||||
"title": "宏编辑",
|
||||
"down": "按下按键执行的宏",
|
||||
"loop": "按住执行的宏",
|
||||
"placeholder": "JSON宏代码, 可为空",
|
||||
"up": "抬起执行的宏"
|
||||
}
|
||||
},
|
||||
"setting": {
|
||||
"touchTime": "触摸时长",
|
||||
"touchTimePlaceholder": "请输入触摸时长(ms)",
|
||||
"pointerID": "触点ID",
|
||||
"pointerIDPlaceholder": "请输入触点ID",
|
||||
"note": "备注",
|
||||
"notePlaceholder": "请输入备注"
|
||||
},
|
||||
"KeyInfo": {
|
||||
"title": "Key Info",
|
||||
"note": "Press any key"
|
||||
},
|
||||
"Observation": {
|
||||
"observation": "观察视角",
|
||||
"scale": "灵敏度",
|
||||
"scalePlaceholder": "请输入灵敏度"
|
||||
},
|
||||
"KeySetting": {
|
||||
"onlyOneConfig": "当前仅有一个按键方案,点击导入默认,可导入预设方案",
|
||||
"importFailed": "导入失败",
|
||||
"importSuccess": "按键方案已导入",
|
||||
"importDefaultFailed": "默认按键方案导入失败",
|
||||
"importDefaultSuccess": "已导入{0}个默认方案",
|
||||
"configEdited": "请先保存或还原当前方案",
|
||||
"newConfig": "新方案",
|
||||
"newConfigSuccess": "新方案已创建",
|
||||
"copyConfigTitle": "{0}-副本",
|
||||
"copyConfigSuccess": "方案已复制为:{0}",
|
||||
"exportSuccess": "当前按键方案已导出到剪切板",
|
||||
"exportFailed": "按键方案导出失败",
|
||||
"checkConfigSizeWarning": "请注意当前按键方案\"{0}\"与蒙版尺寸不一致,若有需要可进行迁移",
|
||||
"migrateConfigTitle": "{0}-迁移",
|
||||
"migrateConfigSuccess": "已迁移到新方案:{0}",
|
||||
"migrateConfigNeedless": "当前方案符合蒙版尺寸,无需迁移",
|
||||
"buttonDrag": "长按可拖动",
|
||||
"config": "按键映射方案",
|
||||
"configRelativeSize": "相对蒙版尺寸: {0}x{1}",
|
||||
"saveConfig": "保存方案",
|
||||
"resetConfig": "还原方案",
|
||||
"createConfig": "新建方案",
|
||||
"copyConfig": "复制方案",
|
||||
"migrateConfig": "迁移方案",
|
||||
"delConfig": "删除方案",
|
||||
"renameConfig": "重命名",
|
||||
"others": "其他",
|
||||
"importConfig": "导入方案",
|
||||
"exportConfig": "导出方案",
|
||||
"importDefaultConfig": "导入默认",
|
||||
"keyInfo": "按键信息",
|
||||
"addButtonTip": "提示:右键空白区域可添加按键",
|
||||
"importPlaceholder": "粘贴单个按键方案的JSON文本 (此处无法对按键方案的合法性进行判断, 请确保JSON内容正确)",
|
||||
"import": "导入",
|
||||
"renameTitle": "重命名按键方案",
|
||||
"delConfigLeast": "至少保留一个方案",
|
||||
"delSuccess": "方案已删除:{0}",
|
||||
"renameSuccess": "方案已重命名为:{0}",
|
||||
"renameEmpty": "方案名不能为空",
|
||||
"saveKeyRepeat": "存在重复按键,无法保存"
|
||||
},
|
||||
"KeySkill": {
|
||||
"skill": "技能",
|
||||
"options": "选项",
|
||||
"double": "双击施放",
|
||||
"directionless": "无方向技能",
|
||||
"triggerWhenPressed": "按下时触发",
|
||||
"range": "范围"
|
||||
},
|
||||
"SteeringWheel": {
|
||||
"steeringWheel": "键盘行走",
|
||||
"offset": "偏移"
|
||||
}
|
||||
}
|
||||
},
|
||||
"sidebar": {
|
||||
"noControledDevice": "未控制任何设备"
|
||||
}
|
||||
}
|
@ -3,10 +3,12 @@ import { createPinia } from "pinia";
|
||||
import "./styles.css";
|
||||
import App from "./App.vue";
|
||||
import router from "./router";
|
||||
import i18n from "./i18n";
|
||||
|
||||
const pinia = createPinia();
|
||||
|
||||
const app = createApp(App);
|
||||
app.use(router);
|
||||
app.use(pinia);
|
||||
app.use(i18n);
|
||||
app.mount("#app");
|
||||
|
@ -25,20 +25,9 @@ export const useGlobalStore = defineStore("global", () => {
|
||||
device: Device;
|
||||
}
|
||||
|
||||
const screenSizeW: Ref<number> = ref(0);
|
||||
const screenSizeH: Ref<number> = ref(0);
|
||||
|
||||
const controledDevice: Ref<ControledDevice | null> = ref(null);
|
||||
|
||||
const keyMappingConfigList: Ref<KeyMappingConfig[]> = ref([]);
|
||||
const curKeyMappingIndex = ref(0);
|
||||
const editKeyMappingList: Ref<KeyMapping[]> = ref([]);
|
||||
|
||||
const maskButton = ref({
|
||||
transparency: 0.5,
|
||||
show: true,
|
||||
});
|
||||
|
||||
const showInputBox: (_: boolean) => void = (_: boolean) => {};
|
||||
|
||||
let checkUpdate: () => Promise<void> = async () => {};
|
||||
@ -81,17 +70,31 @@ export const useGlobalStore = defineStore("global", () => {
|
||||
localStore.set("curKeyMappingIndex", index);
|
||||
}
|
||||
|
||||
// persistent storage
|
||||
const screenSizeW: Ref<number> = ref(0);
|
||||
const screenSizeH: Ref<number> = ref(0);
|
||||
const keyMappingConfigList: Ref<KeyMappingConfig[]> = ref([]);
|
||||
const curKeyMappingIndex = ref(0);
|
||||
const maskButton = ref({
|
||||
transparency: 0.5,
|
||||
show: true,
|
||||
});
|
||||
const checkUpdateAtStart = ref(true);
|
||||
|
||||
return {
|
||||
showLoading,
|
||||
hideLoading,
|
||||
showLoadingRef,
|
||||
controledDevice,
|
||||
// persistent storage
|
||||
screenSizeW,
|
||||
screenSizeH,
|
||||
keyMappingConfigList,
|
||||
curKeyMappingIndex,
|
||||
editKeyMappingList,
|
||||
maskButton,
|
||||
checkUpdateAtStart,
|
||||
// in-memory storage
|
||||
showLoading,
|
||||
hideLoading,
|
||||
showLoadingRef,
|
||||
controledDevice,
|
||||
editKeyMappingList,
|
||||
showInputBox,
|
||||
applyEditKeyMappingList,
|
||||
resetEditKeyMappingList,
|
||||
|
Loading…
Reference in New Issue
Block a user