Merge pull request #338 from S33G/tooltips

Add tooltips to menu
This commit is contained in:
wenjie
2025-03-04 14:19:34 +08:00
committed by GitHub
13 changed files with 190 additions and 91 deletions

View File

@@ -47,6 +47,7 @@ const en = {
finishBtn: 'Finished'
},
screen: {
title: 'Screen',
video: 'Video Mode',
resolution: 'Resolution',
auto: 'Automatic',
@@ -65,6 +66,7 @@ const en = {
resetHdmi: 'Reset HDMI'
},
keyboard: {
title: 'Keyboard',
paste: 'Paste',
tips: 'Only standard keyboard letters and symbols are supported',
placeholder: 'Please input',
@@ -73,6 +75,7 @@ const en = {
ctrlaltdel: 'Ctrl+Alt+Del'
},
mouse: {
title: 'Mouse',
default: 'Default cursor',
pointer: 'Pointer cursor',
cell: 'Cell cursor',
@@ -86,7 +89,7 @@ const en = {
resetHid: 'Reset HID'
},
image: {
title: 'Image',
title: 'Images',
loading: 'Loading...',
empty: 'Nothing Found',
cdrom: 'Mount the image in CD-ROM mode',
@@ -110,7 +113,7 @@ const en = {
}
},
script: {
title: 'Script',
title: 'Scripts',
upload: 'Upload',
run: 'Run',
runBackground: 'Run Background',
@@ -139,7 +142,7 @@ const en = {
ok: 'Ok'
},
download: {
title: 'Download Image',
title: 'Image Downloader',
input: 'Please enter a remote image URL',
ok: 'Ok',
disabled: '/data partition is RO, so we cannot download the image'
@@ -257,8 +260,15 @@ const en = {
}
},
error: {
title: "We've ran into an issue",
refresh: 'Refresh'
title: 'We\'ve ran into an issue',
refresh: 'Refresh',
},
fullscreen: {
toggle: 'Toggle Fullscreen',
},
menu: {
collapse: 'Collapse Menu',
expand: 'Expand Menu',
}
}
};

View File

@@ -1,5 +1,5 @@
import { ChangeEvent, useEffect, useRef, useState } from 'react';
import { Button, Divider, Input, Popover } from 'antd';
import { Button, Divider, Input, Popover, Tooltip } from 'antd';
import type { InputRef } from 'antd';
import clsx from 'clsx';
import { useSetAtom } from 'jotai';
@@ -21,6 +21,9 @@ export const DownloadImage = () => {
const [diskEnabled, setDiskEnabled] = useState(false);
const [popoverKey, setPopoverKey] = useState(0);
const tooltip = t('download.title');
const [tooltipValue, setTooltipValue] = useState(tooltip);
const inputRef = useRef<InputRef>(null);
const intervalId = useRef<NodeJS.Timeout | undefined>(undefined);
@@ -165,11 +168,16 @@ export const DownloadImage = () => {
trigger="click"
arrow={false}
open={isPopoverOpen}
onOpenChange={handleOpenChange}
onOpenChange={(visible) => {
handleOpenChange(visible);
setTooltipValue(visible ? '' : tooltip);
}}
>
<div className="flex h-[30px] w-[30px] cursor-pointer items-center justify-center rounded text-neutral-300 hover:bg-neutral-700 hover:text-white">
<DownloadIcon size={18} />
</div>
<Tooltip title={tooltipValue} placement="bottom">
<div className="flex h-[30px] w-[30px] cursor-pointer items-center justify-center rounded text-neutral-300 hover:bg-neutral-700 hover:text-white">
<DownloadIcon size={18} />
</div>
</Tooltip>
</Popover>
);
};

View File

@@ -1,8 +1,12 @@
import { useEffect, useState } from 'react';
import { MaximizeIcon, MinimizeIcon } from 'lucide-react';
import { useTranslation } from 'react-i18next';
import { Tooltip } from 'antd';
export const Fullscreen = () => {
const { t } = useTranslation();
const [isFullscreen, setIsFullscreen] = useState(false);
const [tooltipValue, setTooltipValue] = useState(t('fullscreen.toggle'));
useEffect(() => {
function onFullscreenChange() {
@@ -27,11 +31,13 @@ export const Fullscreen = () => {
}
return (
<div
className="hidden h-[30px] w-[30px] cursor-pointer items-center justify-center rounded text-neutral-300 hover:bg-neutral-700 hover:text-white sm:flex"
onClick={handleFullscreen}
>
{isFullscreen ? <MinimizeIcon size={18} /> : <MaximizeIcon size={18} />}
</div>
<Tooltip title={tooltipValue} placement="bottom">
<div
className="hidden h-[30px] w-[30px] cursor-pointer items-center justify-center rounded text-neutral-300 hover:bg-neutral-700 hover:text-white sm:flex"
onClick={handleFullscreen}
>
{isFullscreen ? <MinimizeIcon size={18} /> : <MaximizeIcon size={18} />}
</div>
</Tooltip>
);
};

View File

@@ -18,6 +18,9 @@ export const Image = () => {
const [isMounted, setIsMounted] = useState(false);
const [cdrom, setCdrom] = useState(false);
const tooltip = t('image.title');
const [tooltipValue, setTooltipValue] = useState(tooltip);
useEffect(() => {
getMountedImage().then((rsp) => {
if (rsp.code !== 0) return;
@@ -60,16 +63,21 @@ export const Image = () => {
trigger="click"
arrow={false}
open={isPopoverOpen}
onOpenChange={setIsPopoverOpen}
onOpenChange={(visible) => {
setIsPopoverOpen(visible);
setTooltipValue(visible ? '' : tooltip);
}}
>
<div
className={clsx(
'flex h-[30px] w-[30px] cursor-pointer items-center justify-center rounded hover:bg-neutral-700',
isMounted ? 'text-blue-500' : 'text-neutral-300 hover:text-white'
)}
>
<DiscIcon size={18} />
</div>
<Tooltip title={tooltipValue} placement="bottom">
<div
className={clsx(
'flex h-[30px] w-[30px] cursor-pointer items-center justify-center rounded hover:bg-neutral-700',
isMounted ? 'text-blue-500' : 'text-neutral-300 hover:text-white'
)}
>
<DiscIcon size={18} />
</div>
</Tooltip>
</Popover>
);
};

View File

@@ -1,5 +1,5 @@
import { useEffect, useState } from 'react';
import { Divider } from 'antd';
import { Divider, Tooltip } from 'antd';
import clsx from 'clsx';
import { useAtom } from 'jotai';
import { MenuIcon, XIcon } from 'lucide-react';
@@ -18,6 +18,7 @@ import { Script } from './script';
import { Settings } from './settings';
import { Terminal } from './terminal';
import { Wol } from './wol';
import { t } from 'i18next';
export const Menu = () => {
const [menuDisabledItems, setMenuDisabledItems] = useAtom(menuDisabledItemsAtom);
@@ -67,21 +68,25 @@ export const Menu = () => {
<Settings />
<Fullscreen />
<div
className="mr-1 flex h-[30px] w-[30px] cursor-pointer items-center justify-center rounded text-neutral-300 hover:bg-neutral-700 hover:text-white"
onClick={() => setIsMenuOpen((o) => !o)}
>
<XIcon size={20} />
</div>
<Tooltip title={t('menu.collapse')} placement="bottom">
<div
className="mr-1 flex h-[30px] w-[30px] cursor-pointer items-center justify-center rounded text-neutral-300 hover:bg-neutral-700 hover:text-white"
onClick={() => setIsMenuOpen((o) => !o)}
>
<XIcon size={20} />
</div>
</Tooltip>
</div>
{!isMenuOpen && (
<div
className="flex h-[30px] w-[50px] items-center justify-center rounded bg-neutral-800/50 text-white/50 hover:bg-neutral-800 hover:text-white"
onClick={() => setIsMenuOpen((o) => !o)}
>
<MenuIcon />
</div>
<Tooltip title={t('menu.expand')} placement="bottom">
<div
className="flex h-[30px] w-[50px] items-center justify-center rounded bg-neutral-800/50 text-white/50 hover:bg-neutral-800 hover:text-white"
onClick={() => setIsMenuOpen((o) => !o)}
>
<MenuIcon />
</div>
</Tooltip>
)}
</div>
</div>

View File

@@ -1,14 +1,19 @@
import { useState } from 'react';
import { Popover } from 'antd';
import { Popover, Tooltip } from 'antd';
import { KeyboardIcon } from 'lucide-react';
import { t } from 'i18next';
import { Paste } from './paste.tsx';
import { VirtualKeyboard } from './virtual-keyboard.tsx';
import { CtrlAltDel } from './ctrl-alt-del.tsx';
export const Keyboard = () => {
const [isPopoverOpen, setIsPopoverOpen] = useState(false);
const tooltip = t('keyboard.title')
const [tooltipValue, setTooltipValue] = useState(tooltip);
const content = (
<>
<Paste setIsPopoverOpen={setIsPopoverOpen} />
@@ -24,11 +29,16 @@ export const Keyboard = () => {
trigger="click"
arrow={false}
open={isPopoverOpen}
onOpenChange={setIsPopoverOpen}
onOpenChange={(visible) => {
setIsPopoverOpen(visible);
setTooltipValue(visible ? "" : tooltip);
}}
>
<div className="flex h-[30px] w-[30px] cursor-pointer items-center justify-center rounded text-neutral-300 hover:bg-neutral-700 hover:text-white">
<KeyboardIcon size={18} />
</div>
<Tooltip title={tooltipValue} placement="bottom">
<div className="flex h-[30px] w-[30px] cursor-pointer items-center justify-center rounded text-neutral-300 hover:bg-neutral-700 hover:text-white">
<KeyboardIcon size={18} />
</div>
</Tooltip>
</Popover>
);
};

View File

@@ -1,5 +1,5 @@
import { useEffect, useState } from 'react';
import { Divider, Popover } from 'antd';
import { Divider, Popover, Tooltip } from 'antd';
import clsx from 'clsx';
import { useAtom } from 'jotai';
import {
@@ -27,6 +27,9 @@ export const Mouse = () => {
const [isPopoverOpen, setIsPopoverOpen] = useState(false);
const [isResetting, setIsResetting] = useState(false);
const tooltip = t('mouse.title');
const [tooltipValue, setTooltipValue] = useState(tooltip);
const mouseStyles = [
{ name: t('mouse.default'), icon: <MousePointerIcon size={14} />, value: 'cursor-default' },
{ name: t('mouse.grab'), icon: <HandIcon size={14} />, value: 'cursor-grab' },
@@ -154,11 +157,16 @@ export const Mouse = () => {
trigger="click"
arrow={false}
open={isPopoverOpen}
onOpenChange={setIsPopoverOpen}
onOpenChange={(visible) => {
setIsPopoverOpen(visible);
setTooltipValue(visible ? "" : tooltip);
}}
>
<div className="flex h-[30px] w-[30px] cursor-pointer items-center justify-center rounded text-neutral-300 hover:bg-neutral-700 hover:text-white">
<MouseIcon size={18} />
</div>
<Tooltip title={tooltipValue}>
<div className="flex h-[30px] w-[30px] cursor-pointer items-center justify-center rounded text-neutral-300 hover:bg-neutral-700 hover:text-white">
<MouseIcon size={18} />
</div>
</Tooltip>
</Popover>
);
};

View File

@@ -1,5 +1,5 @@
import { useEffect, useState } from 'react';
import { Popover, Slider } from 'antd';
import { Popover, Slider, Tooltip } from 'antd';
import clsx from 'clsx';
import { CirclePowerIcon, LoaderCircleIcon, PowerIcon, RotateCcwIcon } from 'lucide-react';
import { useTranslation } from 'react-i18next';
@@ -13,6 +13,9 @@ export const Power = () => {
const [isLoading, setIsLoading] = useState(false);
const [powerDuration, setPowerDuration] = useState(8);
const tooltip = t('power.title');
const [tooltipValue, setTooltipValue] = useState(tooltip);
useEffect(() => {
getLed();
const interval = setInterval(getLed, 5000);
@@ -95,19 +98,24 @@ export const Power = () => {
trigger="click"
arrow={false}
open={isPopoverOpen}
onOpenChange={setIsPopoverOpen}
onOpenChange={(visible) => {
setIsPopoverOpen(visible);
setTooltipValue(visible ? '' : tooltip);
}}
>
<div className="flex h-[30px] w-[30px] cursor-pointer items-center justify-center rounded hover:bg-neutral-700 hover:text-white">
<div
className={clsx('h-[18px] w-[18px]', isPowerOn ? 'text-green-600' : 'text-neutral-500')}
>
{isLoading ? (
<LoaderCircleIcon className="animate-spin" size={18} />
) : (
<PowerIcon size={18} />
)}
<Tooltip title={tooltipValue} placement="bottom">
<div className="flex h-[30px] w-[30px] cursor-pointer items-center justify-center rounded hover:bg-neutral-700 hover:text-white">
<div
className={clsx('h-[18px] w-[18px]', isPowerOn ? 'text-green-600' : 'text-neutral-500')}
>
{isLoading ? (
<LoaderCircleIcon className="animate-spin" size={18} />
) : (
<PowerIcon size={18} />
)}
</div>
</div>
</div>
</Tooltip>
</Popover>
);
};

View File

@@ -1,5 +1,5 @@
import { useEffect, useState } from 'react';
import { Popover } from 'antd';
import { Popover, Tooltip } from 'antd';
import { useAtomValue } from 'jotai';
import { MonitorIcon } from 'lucide-react';
@@ -14,6 +14,7 @@ import { Quality } from './quality';
import { Reset } from './reset.tsx';
import { Resolution } from './resolution';
import { VideoMode } from './video-mode.tsx';
import { t } from 'i18next';
export const Screen = () => {
const videoMode = useAtomValue(videoModeAtom);
@@ -21,10 +22,12 @@ export const Screen = () => {
const [fps, setFps] = useState(30);
const [quality, setQuality] = useState(2);
const tooltip = t('screen.title');
const [tooltipValue, setTooltipValue] = useState(tooltip);
useEffect(() => {
updateScreen('type', videoMode === 'mjpeg' ? 0 : 1);
updateScreen('resolution', resolution!.height);
updateQuality();
updateFps();
}, []);
@@ -69,10 +72,13 @@ export const Screen = () => {
placement="bottomLeft"
trigger="click"
arrow={false}
onOpenChange={(visible) => setTooltipValue(visible ? '' : tooltip)}
>
<div className="flex h-[30px] w-[30px] cursor-pointer items-center justify-center rounded text-neutral-300 hover:bg-neutral-700 hover:text-white">
<MonitorIcon size={18} />
</div>
<Tooltip title={tooltipValue}>
<div className="flex h-[30px] w-[30px] cursor-pointer items-center justify-center rounded text-neutral-300 hover:bg-neutral-700 hover:text-white">
<MonitorIcon size={18} />
</div>
</Tooltip>
</Popover>
);
};

View File

@@ -1,6 +1,6 @@
import { ChangeEvent, useRef, useState } from 'react';
import { UploadOutlined } from '@ant-design/icons';
import { Button, Divider, Popconfirm, Popover } from 'antd';
import { Button, Divider, Popconfirm, Popover, Tooltip } from 'antd';
import clsx from 'clsx';
import { ChevronRightIcon, FileJsonIcon } from 'lucide-react';
import { useTranslation } from 'react-i18next';
@@ -22,6 +22,9 @@ export const Script = () => {
const [isUploading, setIsUploading] = useState(false);
const inputRef = useRef<any>(null);
const tooltip = t('script.title');
const [tooltipValue, setTooltipValue] = useState(tooltip);
function handleOpenChange(open: boolean) {
if (open) {
getScripts();
@@ -194,11 +197,16 @@ export const Script = () => {
trigger="click"
arrow={false}
open={isPopoverOpen}
onOpenChange={handleOpenChange}
onOpenChange={(visible) => {
handleOpenChange(visible);
setTooltipValue(visible ? '' : tooltip);
}}
>
<div className="flex h-[30px] w-[30px] cursor-pointer items-center justify-center rounded text-neutral-300 hover:bg-neutral-700 hover:text-white">
<FileJsonIcon size={18} />
</div>
<Tooltip title={tooltipValue} placement="bottom">
<div className="flex h-[30px] w-[30px] cursor-pointer items-center justify-center rounded text-neutral-300 hover:bg-neutral-700 hover:text-white">
<FileJsonIcon size={18} />
</div>
</Tooltip>
</Popover>
{isRunning && <Run script={currentScript} setIsRunning={setIsRunning} />}

View File

@@ -1,5 +1,5 @@
import { useEffect, useState } from 'react';
import { Badge, Modal } from 'antd';
import { Badge, Modal, Tooltip } from 'antd';
import clsx from 'clsx';
import {
BadgeInfoIcon,
@@ -32,6 +32,9 @@ export const Settings = () => {
const [isUpdateAvailable, setIsUpdateAvailable] = useState(false);
const tooltip = t('settings.title');
const [tooltipValue, setTooltipValue] = useState(tooltip);
const tabs = [
{ id: 'about', icon: <BadgeInfoIcon size={16} />, component: <About /> },
{ id: 'appearance', icon: <PaletteIcon size={16} />, component: <Appearance /> },
@@ -92,16 +95,18 @@ export const Settings = () => {
return (
<>
<div
className="flex h-[30px] w-[30px] cursor-pointer items-center justify-center rounded text-white hover:bg-neutral-700"
onClick={() => setIsModalOpen(true)}
>
<Badge dot={isUpdateAvailable} color="blue" offset={[0, 2]}>
<div className="pt-[3px]">
<SettingsIcon size={18} />
</div>
</Badge>
</div>
<Tooltip title={tooltipValue} placement="bottom">
<div
className="flex h-[30px] w-[30px] cursor-pointer items-center justify-center rounded text-white hover:bg-neutral-700"
onClick={() => setIsModalOpen(true)}
>
<Badge dot={isUpdateAvailable} color="blue" offset={[0, 2]}>
<div className="pt-[3px]">
<SettingsIcon size={18} />
</div>
</Badge>
</div>
</Tooltip>
<Modal
open={isModalOpen}
@@ -110,6 +115,7 @@ export const Settings = () => {
destroyOnClose={true}
styles={{ content: { padding: 0 } }}
onCancel={closeModal}
afterOpenChange={(visible) => setTooltipValue(visible ? '' : tooltip)}
>
<div className="flex min-h-[500px] rounded-lg outline outline-1 outline-neutral-700">
<div className="flex flex-col space-y-0.5 rounded-l-lg bg-neutral-800 py-5 sm:w-1/5 sm:px-2">

View File

@@ -1,5 +1,5 @@
import { useState } from 'react';
import { Divider, Popover } from 'antd';
import { Divider, Popover, Tooltip } from 'antd';
import { SquareTerminalIcon } from 'lucide-react';
import { useTranslation } from 'react-i18next';
@@ -10,6 +10,9 @@ export const Terminal = () => {
const { t } = useTranslation();
const [isPopoverOpen, setIsPopoverOpen] = useState(false);
const tooltip = t('terminal.title');
const [tooltipValue, setTooltipValue] = useState(tooltip);
const content = (
<div className="min-w-[200px]">
<div className="flex items-center justify-between px-1">
@@ -30,11 +33,16 @@ export const Terminal = () => {
trigger="click"
arrow={false}
open={isPopoverOpen}
onOpenChange={setIsPopoverOpen}
onOpenChange={(visible) => {
setIsPopoverOpen(visible);
setTooltipValue(visible ? '' : tooltip);
}}
>
<div className="flex h-[30px] w-[30px] cursor-pointer items-center justify-center rounded text-neutral-300 hover:bg-neutral-700 hover:text-white">
<SquareTerminalIcon size={18} />
</div>
<Tooltip title={tooltipValue} placement="bottom">
<div className="flex h-[30px] w-[30px] cursor-pointer items-center justify-center rounded text-neutral-300 hover:bg-neutral-700 hover:text-white">
<SquareTerminalIcon size={18} />
</div>
</Tooltip>
</Popover>
);
};

View File

@@ -1,5 +1,5 @@
import { ChangeEvent, useRef, useState } from 'react';
import { Button, Divider, Input, List, Popover } from 'antd';
import { Button, Divider, Input, List, Popover, Tooltip } from 'antd';
import type { InputRef } from 'antd';
import clsx from 'clsx';
import { useSetAtom } from 'jotai';
@@ -22,6 +22,9 @@ export const Wol = () => {
const [status, setStatus] = useState('');
const [log, setLog] = useState('');
const tooltip = t('wol.title');
const [tooltipValue, setTooltipValue] = useState(tooltip);
const [macList, setMacList] = useState<string[]>([]);
const inputRef = useRef<InputRef>(null);
@@ -153,11 +156,16 @@ export const Wol = () => {
trigger="click"
arrow={false}
open={isPopoverOpen}
onOpenChange={handleOpenChange}
onOpenChange={(visible) => {
handleOpenChange(visible);
setTooltipValue(visible ? '' : tooltip);
}}
>
<div className="flex h-[30px] w-[30px] cursor-pointer items-center justify-center rounded text-neutral-300 hover:bg-neutral-700 hover:text-white">
<NetworkIcon size={16} />
</div>
<Tooltip title={tooltipValue} placement="bottom">
<div className="flex h-[30px] w-[30px] cursor-pointer items-center justify-center rounded text-neutral-300 hover:bg-neutral-700 hover:text-white">
<NetworkIcon size={16} />
</div>
</Tooltip>
</Popover>
);
};