feat: add help tooltip

This commit is contained in:
AkiChase 2025-03-12 17:26:55 +08:00
parent 2573356b8c
commit b1f5960306
12 changed files with 119 additions and 52 deletions

View File

@ -18,6 +18,7 @@ import {
getDeviceScreenSize, getDeviceScreenSize,
adbConnect, adbConnect,
getCurClientInfo, getCurClientInfo,
adbRestartServer,
} from "../tools/invoke"; } from "../tools/invoke";
import { import {
NH4, NH4,
@ -34,6 +35,7 @@ import {
useDialog, useDialog,
useMessage, useMessage,
NInputGroup, NInputGroup,
NSpace,
} from "naive-ui"; } from "naive-ui";
import { CloseCircle, InformationCircle, Refresh } from "@vicons/ionicons5"; import { CloseCircle, InformationCircle, Refresh } from "@vicons/ionicons5";
import { UnlistenFn, listen } from "@tauri-apps/api/event"; import { UnlistenFn, listen } from "@tauri-apps/api/event";
@ -47,6 +49,7 @@ import {
import { LogicalSize, getCurrentWindow } from "@tauri-apps/api/window"; import { LogicalSize, getCurrentWindow } from "@tauri-apps/api/window";
import { writeText } from "@tauri-apps/plugin-clipboard-manager"; import { writeText } from "@tauri-apps/plugin-clipboard-manager";
import ButtonWithTip from "./common/ButtonWithTip.vue"; import ButtonWithTip from "./common/ButtonWithTip.vue";
import { NonReactiveStore } from "../store/noneReactiveStore";
const { t } = useI18n(); const { t } = useI18n();
const dialog = useDialog(); const dialog = useDialog();
@ -328,6 +331,21 @@ async function refreshDevices() {
store.hideLoading(); store.hideLoading();
} }
async function restartAdb() {
if (NonReactiveStore.mem.adbUnavailableMsgIns !== null) {
message.error(t("pages.Device.adbUnavailable"));
return;
}
store.showLoading();
try {
await adbRestartServer();
} catch (e) {
message.error(t("pages.Device.adbRestartError"));
console.error(e);
}
store.hideLoading();
}
async function connectDevice() { async function connectDevice() {
if (!wireless_address.value) { if (!wireless_address.value) {
message.error(t("pages.Device.inputWirelessAddress")); message.error(t("pages.Device.inputWirelessAddress"));
@ -437,15 +455,24 @@ function closeWS() {
<NH4 style="margin: 20px 0" prefix="bar">{{ <NH4 style="margin: 20px 0" prefix="bar">{{
$t("pages.Device.availableDevice") $t("pages.Device.availableDevice")
}}</NH4> }}</NH4>
<NSpace>
<ButtonWithTip <ButtonWithTip
tertiary tertiary
circle circle
type="primary" type="primary"
@click="refreshDevices" @click="refreshDevices"
style="margin-right: 20px"
:tip="$t('pages.Device.btnRefresh')" :tip="$t('pages.Device.btnRefresh')"
:icon="Refresh" :icon="Refresh"
/> />
<ButtonWithTip
tertiary
circle
type="error"
@click="restartAdb"
:tip="$t('pages.Device.btnReStart')"
:icon="Refresh"
/>
</NSpace>
</NFlex> </NFlex>
<NDataTable <NDataTable
max-height="120" max-height="120"

View File

@ -4,9 +4,11 @@ import { useGlobalStore } from "../store/global";
import { MessageReactive, useMessage } from "naive-ui"; import { MessageReactive, useMessage } from "naive-ui";
import { ScreenStream } from "../tools/screenStream"; import { ScreenStream } from "../tools/screenStream";
import { NonReactiveStore } from "../store/noneReactiveStore"; import { NonReactiveStore } from "../store/noneReactiveStore";
import { useI18n } from "vue-i18n";
const store = useGlobalStore(); const store = useGlobalStore();
const message = useMessage(); const message = useMessage();
const { t } = useI18n();
const streamImg = ref<HTMLImageElement | null>(null); const streamImg = ref<HTMLImageElement | null>(null);
@ -14,16 +16,22 @@ let msgReactive: MessageReactive | null = null;
function connectScreenStream() { function connectScreenStream() {
if (streamImg.value) { if (streamImg.value) {
const ss = new ScreenStream(streamImg.value, NonReactiveStore.mem.screenStreamClientId); const ss = new ScreenStream(
streamImg.value,
NonReactiveStore.mem.screenStreamClientId
);
ss.connect( ss.connect(
store.screenStream.address, store.screenStream.address,
() => {}, () => {},
() => { () => {
msgReactive = message.error("投屏连接失败。关闭此信息将尝试重新连接", { msgReactive = message.error(
t("pages.Setting.Mask.screenStream.connectError"),
{
duration: 0, duration: 0,
closable: true, closable: true,
onClose: () => connectScreenStream(), onClose: () => connectScreenStream(),
}); }
);
} }
); );
} }

View File

@ -1,12 +1,12 @@
<script setup lang="ts"> <script setup lang="ts">
import { NFlex, NH4, NButton, NIcon, NP, NCheckbox } from "naive-ui"; import { NFlex, NH4, NButton, NIcon, NP, NCheckbox } from "naive-ui";
import { LogoGithub, Planet } from "@vicons/ionicons5"; import { LogoGithub, Planet } from "@vicons/ionicons5";
import { open } from "@tauri-apps/plugin-shell";
import { getVersion } from "@tauri-apps/api/app"; import { getVersion } from "@tauri-apps/api/app";
import { onMounted, ref } from "vue"; import { onMounted, ref } from "vue";
import { useGlobalStore } from "../../store/global"; import { useGlobalStore } from "../../store/global";
import { LocalStore } from "../../store/localStore"; import { LocalStore } from "../../store/localStore";
import { useCheckUpdate } from "../../tools/hooks"; import { useCheckUpdate } from "../../tools/hooks";
import { openWebsite } from "../../tools/tools";
const store = useGlobalStore(); const store = useGlobalStore();
const checkUpdate = useCheckUpdate(); const checkUpdate = useCheckUpdate();
@ -16,10 +16,6 @@ onMounted(async () => {
appVersion.value = await getVersion(); appVersion.value = await getVersion();
}); });
function opendWebsite(url: string) {
open(url);
}
async function onClickCheckUpdate() { async function onClickCheckUpdate() {
store.showLoading(); store.showLoading();
await checkUpdate(); await checkUpdate();
@ -34,7 +30,7 @@ async function onClickCheckUpdate() {
<NFlex :size="30"> <NFlex :size="30">
<NButton <NButton
text text
@click="opendWebsite('https://github.com/AkiChase/scrcpy-mask')" @click="openWebsite('https://github.com/AkiChase/scrcpy-mask')"
> >
<template #icon> <template #icon>
<NIcon><LogoGithub /> </NIcon> <NIcon><LogoGithub /> </NIcon>
@ -43,7 +39,7 @@ async function onClickCheckUpdate() {
</NButton> </NButton>
<NButton <NButton
text text
@click="opendWebsite('https://space.bilibili.com/440760180')" @click="openWebsite('https://space.bilibili.com/440760180')"
> >
<template #icon> <template #icon>
<NIcon <NIcon
@ -63,7 +59,7 @@ async function onClickCheckUpdate() {
</template> </template>
BiliBili BiliBili
</NButton> </NButton>
<NButton text @click="opendWebsite('https://www.akichase.top/')"> <NButton text @click="openWebsite('https://www.akichase.top/')">
<template #icon> <template #icon>
<NIcon><Planet /> </NIcon> <NIcon><Planet /> </NIcon>
</template> </template>

View File

@ -7,9 +7,7 @@ import {
NFormItemGi, NFormItemGi,
NInputNumber, NInputNumber,
FormRules, FormRules,
NButton,
NFlex, NFlex,
NIcon,
FormInst, FormInst,
useMessage, useMessage,
NSlider, NSlider,
@ -22,11 +20,13 @@ import {
LogicalSize, LogicalSize,
getCurrentWindow, getCurrentWindow,
} from "@tauri-apps/api/window"; } from "@tauri-apps/api/window";
import { SettingsOutline } from "@vicons/ionicons5"; import { Help, SettingsOutline } from "@vicons/ionicons5";
import { useGlobalStore } from "../../store/global"; import { useGlobalStore } from "../../store/global";
import { useI18n } from "vue-i18n"; import { useI18n } from "vue-i18n";
import { LocalStore } from "../../store/localStore"; import { LocalStore } from "../../store/localStore";
import { NonReactiveStore } from "../../store/noneReactiveStore"; import { NonReactiveStore } from "../../store/noneReactiveStore";
import ButtonWithTip from "../common/ButtonWithTip.vue";
import { openWebsite } from "../../tools/tools";
const { t } = useI18n(); const { t } = useI18n();
const store = useGlobalStore(); const store = useGlobalStore();
@ -154,17 +154,14 @@ async function adjustWindowMaskArea() {
> >
<NFlex justify="space-between" align="center"> <NFlex justify="space-between" align="center">
<NH4 prefix="bar">{{ $t("pages.Setting.Mask.areaAdjust") }}</NH4> <NH4 prefix="bar">{{ $t("pages.Setting.Mask.areaAdjust") }}</NH4>
<NButton <ButtonWithTip
tertiary tertiary
circle circle
type="primary" type="primary"
@click="handleAdjustClick" @click="handleAdjustClick"
style="margin-right: 20px" :tip="$t('pages.Setting.Mask.btnAreaAdjustTip')"
> :icon="SettingsOutline"
<template #icon> />
<NIcon><SettingsOutline /></NIcon>
</template>
</NButton>
</NFlex> </NFlex>
<NGrid :cols="2" :x-gap="24"> <NGrid :cols="2" :x-gap="24">
<NFormItemGi label="X" path="posX"> <NFormItemGi label="X" path="posX">
@ -226,8 +223,17 @@ async function adjustWindowMaskArea() {
/> />
</NFormItemGi> </NFormItemGi>
</NGrid> </NGrid>
<NFlex justify="space-between" align="center">
<NH4 prefix="bar">ScreenStream</NH4> <NH4 prefix="bar">ScreenStream</NH4>
<ButtonWithTip
tertiary
circle
type="primary"
@click="openWebsite('https://github.com/dkrivoruchko/ScreenStream')"
:tip="$t('pages.Setting.Mask.screenStream.btnHelp')"
:icon="Help"
/>
</NFlex>
<NFormItem <NFormItem
:label="$t('pages.Setting.Mask.screenStream.enable')" :label="$t('pages.Setting.Mask.screenStream.enable')"
label-placement="left" label-placement="left"

View File

@ -7,6 +7,7 @@ import { NTabs, NTabPane, NSpin } from "naive-ui";
import { useGlobalStore } from "../../store/global"; import { useGlobalStore } from "../../store/global";
import SettingTab from "./SettingTab.vue"; import SettingTab from "./SettingTab.vue";
// TODO Switch back to landscape size when entering Settings and Devices screen
const store = useGlobalStore(); const store = useGlobalStore();
</script> </script>

View File

@ -30,6 +30,7 @@ div#app {
} }
.n-spin-container { .n-spin-container {
background-color: var(--bg-color);
@include common.flexFullHeight; @include common.flexFullHeight;
.n-spin-content { .n-spin-content {
@include common.flexFullHeight; @include common.flexFullHeight;

View File

@ -38,7 +38,10 @@
"adbConnectError": "Wireless connection failed", "adbConnectError": "Wireless connection failed",
"rotation": "Device rotation {0}°", "rotation": "Device rotation {0}°",
"btnShutdown": "Shutdown control", "btnShutdown": "Shutdown control",
"btnRefresh": "Refresh" "btnRefresh": "Refresh",
"btnReStart": "Restart ADB",
"adbRestartError": "ADB Restart Failed",
"adbUnavailable": "ADB Unavailable"
}, },
"Mask": { "Mask": {
"keyconfigException": "The key mapping config is abnormal, please delete this config", "keyconfigException": "The key mapping config is abnormal, please delete this config",
@ -97,8 +100,11 @@
"screenStream": { "screenStream": {
"enable": "Enable mirror", "enable": "Enable mirror",
"address": "Screen mirror address", "address": "Screen mirror address",
"addressPlaceholder": "Please enter the ScreenStream screen mirror address" "addressPlaceholder": "Please enter the ScreenStream screen mirror address",
} "connectError": "ScreenStream connection failed. Closing this message will attempt to reconnect",
"btnHelp": "ScreenStream is an Android screen mirror app with 0.5-1s screen delay, only as a complementary solution to Scrcpy Mask's screen mirror (click to open its project homepage)"
},
"btnAreaAdjustTip": "Setting the mask area"
}, },
"Basic": { "Basic": {
"language": "Language", "language": "Language",
@ -282,5 +288,6 @@
"open": "Connected to external control server", "open": "Connected to external control server",
"close": "External control connection disconnected", "close": "External control connection disconnected",
"error": "Something was wrong, the exter connection is closed" "error": "Something was wrong, the exter connection is closed"
} },
"screenStream": {}
} }

View File

@ -38,7 +38,10 @@
"adbConnectError": "无线连接失败", "adbConnectError": "无线连接失败",
"rotation": "设备旋转 {0}°", "rotation": "设备旋转 {0}°",
"btnShutdown": "关闭控制", "btnShutdown": "关闭控制",
"btnRefresh": "刷新" "btnRefresh": "刷新",
"btnReStart": "重启ABD",
"adbRestartError": "ADB 重启失败",
"adbUnavailable": "ADB 不可用"
}, },
"Mask": { "Mask": {
"keyconfigException": "按键方案异常,请删除此方案", "keyconfigException": "按键方案异常,请删除此方案",
@ -97,8 +100,11 @@
"screenStream": { "screenStream": {
"enable": "启用投屏", "enable": "启用投屏",
"address": "投屏地址", "address": "投屏地址",
"addressPlaceholder": "请输入 ScreenStream 投屏地址" "addressPlaceholder": "请输入 ScreenStream 投屏地址",
} "connectError": "ScreenStream 投屏连接失败。关闭此信息将尝试重新连接",
"btnHelp": "ScreenStream 是一款安卓投屏App存在0.5-1s的画面延迟仅作为 Scrcpy Mask 的投屏补充方案(点击打开其项目主页)"
},
"btnAreaAdjustTip": "设置蒙版区域"
}, },
"Basic": { "Basic": {
"language": "语言", "language": "语言",
@ -282,5 +288,6 @@
"open": "已连接到外部控制服务端", "open": "已连接到外部控制服务端",
"close": "外部控制连接断开", "close": "外部控制连接断开",
"error": "未知错误,外部控制连接断开" "error": "未知错误,外部控制连接断开"
} },
"screenStream": {}
} }

View File

@ -1,8 +1,10 @@
import { MessageReactive } from "naive-ui";
import { LocalStore } from "./localStore"; import { LocalStore } from "./localStore";
interface MemType { interface MemType {
screenStreamClientId: string; screenStreamClientId: string;
keyInputFlag: boolean; keyInputFlag: boolean;
adbUnavailableMsgIns: MessageReactive | null;
} }
interface LocalType { interface LocalType {
@ -19,6 +21,7 @@ export class NonReactiveStore {
static mem: MemType = { static mem: MemType = {
screenStreamClientId: "", screenStreamClientId: "",
keyInputFlag: false, keyInputFlag: false,
adbUnavailableMsgIns: null,
}; };
static local: LocalType = { static local: LocalType = {

View File

@ -1,13 +1,13 @@
import { getVersion } from "@tauri-apps/api/app"; import { getVersion } from "@tauri-apps/api/app";
import { MessageReactive, useDialog, useMessage } from "naive-ui"; import { useDialog, useMessage } from "naive-ui";
import { h } from "vue"; import { h } from "vue";
import { useI18n } from "vue-i18n"; import { useI18n } from "vue-i18n";
import { fetch } from "@tauri-apps/plugin-http"; import { fetch } from "@tauri-apps/plugin-http";
import { compareVersion } from "./tools"; import { compareVersion } from "./tools";
import { checkAdbAvailable } from "./invoke"; import { checkAdbAvailable } from "./invoke";
import { NonReactiveStore } from "../store/noneReactiveStore";
// TODO use markdown to render update info // TODO use markdown to render update info
// TODO check screen stream available
function renderUpdateInfo(content: string) { function renderUpdateInfo(content: string) {
const pList = content.split("\r\n").map((line: string) => h("p", line)); const pList = content.split("\r\n").map((line: string) => h("p", line));
@ -56,22 +56,24 @@ export function useCheckUpdate() {
}; };
} }
let checkAdbMessage: MessageReactive | null = null;
export function useCheckAdb() { export function useCheckAdb() {
const message = useMessage(); const message = useMessage();
const { t } = useI18n(); const { t } = useI18n();
return async function checkAdb() { return async function checkAdb() {
try { try {
if (checkAdbMessage) { if (NonReactiveStore.mem.adbUnavailableMsgIns) {
checkAdbMessage.destroy(); NonReactiveStore.mem.adbUnavailableMsgIns.destroy();
checkAdbMessage = null; NonReactiveStore.mem.adbUnavailableMsgIns = null;
} }
await checkAdbAvailable(); await checkAdbAvailable();
} catch (e) { } catch (e) {
checkAdbMessage = message.error(t("pages.Mask.checkAdb", [e]), { NonReactiveStore.mem.adbUnavailableMsgIns = message.error(
t("pages.Mask.checkAdb", [e]),
{
duration: 0, duration: 0,
}); }
);
} }
}; };
} }

View File

@ -9,6 +9,10 @@ export async function adbDevices(): Promise<Device[]> {
return await invoke("adb_devices"); return await invoke("adb_devices");
} }
export async function adbRestartServer(): Promise<void> {
return await invoke("adb_restart_server");
}
export async function forwardServerPort( export async function forwardServerPort(
id: string, id: string,
scid: string, scid: string,

View File

@ -1,4 +1,5 @@
import { getCurrentWindow, LogicalSize } from "@tauri-apps/api/window"; import { getCurrentWindow, LogicalSize } from "@tauri-apps/api/window";
import { open as shellOpen } from "@tauri-apps/plugin-shell";
export function compareVersion(v1: string, v2: string) { export function compareVersion(v1: string, v2: string) {
const [x1, y1, z1] = v1.split("."); const [x1, y1, z1] = v1.split(".");
@ -36,3 +37,7 @@ export async function cleanAfterimage() {
await appWindow.setSize(newSize); await appWindow.setSize(newSize);
await appWindow.setSize(oldSize); await appWindow.setSize(oldSize);
} }
export function openWebsite(url: string) {
shellOpen(url);
}