Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 10 additions & 6 deletions browser/src/components/menu/audio/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import { videoDeviceIdAtom, videoStateAtom } from '@/jotai/device.ts';
import { camera } from '@/libs/media/camera.ts';
import { checkPermission, requestMicrophonePermission } from '@/libs/media/permission.ts';

import { MenuTooltip } from '../menu-tooltip';

export const Audio = () => {
const { t } = useTranslation();

Expand Down Expand Up @@ -51,12 +53,14 @@ export const Audio = () => {

return (
<>
<div
className="flex h-[28px] w-[28px] cursor-pointer items-center justify-center rounded text-neutral-300 hover:bg-neutral-700/70 hover:text-white"
onClick={requestPermission}
>
<VolumeOffIcon size={18} />
</div>
<MenuTooltip title={t('menu.audio', 'Enable Audio')}>
<div
className="flex h-[28px] w-[28px] cursor-pointer items-center justify-center rounded text-neutral-300 hover:bg-neutral-700/70 hover:text-white"
onClick={requestPermission}
>
<VolumeOffIcon size={18} />
</div>
</MenuTooltip>

<Modal open={isModalOpen} title={t('audio.tip')} footer={null} onCancel={closeModal}>
<div className="whitespace-pre-line py-5">{t('audio.permission')}</div>
Expand Down
18 changes: 12 additions & 6 deletions browser/src/components/menu/fullscreen/index.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
import { useEffect, useState } from 'react';
import { MaximizeIcon, MinimizeIcon } from 'lucide-react';
import { useTranslation } from 'react-i18next';

import { MenuTooltip } from '../menu-tooltip';

export const Fullscreen = () => {
const { t } = useTranslation();
const [isFullscreen, setIsFullscreen] = useState(false);

useEffect(() => {
Expand Down Expand Up @@ -34,11 +38,13 @@ export const Fullscreen = () => {
}

return (
<div
className="flex h-[28px] w-[28px] cursor-pointer items-center justify-center rounded text-neutral-300 hover:bg-neutral-700/70 hover:text-white"
onClick={handleFullscreen}
>
{isFullscreen ? <MinimizeIcon size={18} /> : <MaximizeIcon size={18} />}
</div>
<MenuTooltip title={isFullscreen ? t('menu.exitFullscreen', 'Exit Fullscreen') : t('menu.fullscreen', 'Fullscreen')}>
<div
className="flex h-[28px] w-[28px] cursor-pointer items-center justify-center rounded text-neutral-300 hover:bg-neutral-700/70 hover:text-white"
onClick={handleFullscreen}
>
{isFullscreen ? <MinimizeIcon size={18} /> : <MaximizeIcon size={18} />}
</div>
</MenuTooltip>
);
};
30 changes: 18 additions & 12 deletions browser/src/components/menu/keyboard/index.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
import { useState } from 'react';
import { Popover } from 'antd';
import { KeyboardIcon } from 'lucide-react';
import { useTranslation } from 'react-i18next';

import { MenuTooltip } from '../menu-tooltip';

import { Paste } from './paste.tsx';
import { Shortcuts } from './shortcuts';
import { VirtualKeyboard } from './virtual-keyboard.tsx';

export const Keyboard = () => {
const { t } = useTranslation();
const [isPopoverOpen, setIsPopoverOpen] = useState(false);

const content = (
Expand All @@ -18,17 +22,19 @@ export const Keyboard = () => {
);

return (
<Popover
content={content}
placement="bottomLeft"
trigger="click"
arrow={false}
open={isPopoverOpen}
onOpenChange={setIsPopoverOpen}
>
<div className="flex h-[28px] w-[28px] cursor-pointer items-center justify-center rounded text-neutral-300 hover:bg-neutral-700/70 hover:text-white">
<KeyboardIcon size={18} />
</div>
</Popover>
<MenuTooltip title={t('menu.keyboard', 'Keyboard')}>
<Popover
content={content}
placement="bottomLeft"
trigger="click"
arrow={false}
open={isPopoverOpen}
onOpenChange={setIsPopoverOpen}
>
<div className="flex h-[28px] w-[28px] cursor-pointer items-center justify-center rounded text-neutral-300 hover:bg-neutral-700/70 hover:text-white">
<KeyboardIcon size={18} />
</div>
</Popover>
</MenuTooltip>
);
};
24 changes: 24 additions & 0 deletions browser/src/components/menu/menu-tooltip.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { ReactNode } from 'react';
import { Tooltip } from 'antd';
import { useAtomValue } from 'jotai';

import { showTooltipsAtom } from './settings/tooltips';

interface MenuTooltipProps {
title: string;
children: ReactNode;
}

export const MenuTooltip = ({ title, children }: MenuTooltipProps) => {
const showTooltips = useAtomValue(showTooltipsAtom);

if (!showTooltips) {
return <>{children}</>;
}

return (
<Tooltip title={title} placement="bottom" mouseEnterDelay={0.5}>
{children}
</Tooltip>
);
};
30 changes: 18 additions & 12 deletions browser/src/components/menu/mouse/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@ import { useEffect, useState } from 'react';
import { Divider, Popover } from 'antd';
import { useAtom, useSetAtom } from 'jotai';
import { MouseIcon } from 'lucide-react';
import { useTranslation } from 'react-i18next';

import { MenuTooltip } from '../menu-tooltip';

import {
mouseJigglerModeAtom,
Expand All @@ -20,6 +23,7 @@ import { Speed } from './speed.tsx';
import { Style } from './style.tsx';

export const Mouse = () => {
const { t } = useTranslation();
const [mouseStyle, setMouseStyle] = useAtom(mouseStyleAtom);
const [mouseMode, setMouseMode] = useAtom(mouseModeAtom);
const setScrollDirection = useSetAtom(scrollDirectionAtom);
Expand Down Expand Up @@ -71,17 +75,19 @@ export const Mouse = () => {
);

return (
<Popover
content={content}
placement="bottomLeft"
trigger="click"
arrow={false}
open={isPopoverOpen}
onOpenChange={setIsPopoverOpen}
>
<div className="flex h-[28px] w-[28px] cursor-pointer items-center justify-center rounded text-neutral-300 hover:bg-neutral-700/70 hover:text-white">
<MouseIcon size={18} />
</div>
</Popover>
<MenuTooltip title={t('menu.mouse', 'Mouse')}>
<Popover
content={content}
placement="bottomLeft"
trigger="click"
arrow={false}
open={isPopoverOpen}
onOpenChange={setIsPopoverOpen}
>
<div className="flex h-[28px] w-[28px] cursor-pointer items-center justify-center rounded text-neutral-300 hover:bg-neutral-700/70 hover:text-white">
<MouseIcon size={18} />
</div>
</Popover>
</MenuTooltip>
);
};
17 changes: 11 additions & 6 deletions browser/src/components/menu/recorder/index.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
import { useEffect, useRef, useState } from 'react';
import { Video } from 'lucide-react';
import { useTranslation } from 'react-i18next';

import { MenuTooltip } from '../menu-tooltip';
import { camera } from '@/libs/media/camera';

export const Recorder = () => {
const { t } = useTranslation();
const [isRecording, setIsRecording] = useState(false);
const [elapsedMs, setElapsedMs] = useState(0);

Expand Down Expand Up @@ -116,11 +119,13 @@ export const Recorder = () => {
}

return (
<div
className="flex h-[28px] w-[28px] cursor-pointer items-center justify-center rounded text-neutral-300 hover:bg-neutral-700/70 hover:text-white"
onClick={handleStartRecording}
>
<Video size={18} />
</div>
<MenuTooltip title={t('menu.recorder', 'Record')}>
<div
className="flex h-[28px] w-[28px] cursor-pointer items-center justify-center rounded text-neutral-300 hover:bg-neutral-700/70 hover:text-white"
onClick={handleStartRecording}
>
<Video size={18} />
</div>
</MenuTooltip>
);
};
13 changes: 9 additions & 4 deletions browser/src/components/menu/serial-port/index.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
import { useState } from 'react';
import { CpuIcon, Loader2Icon } from 'lucide-react';
import { useTranslation } from 'react-i18next';

import { MenuTooltip } from '../menu-tooltip';
import { device } from '@/libs/device';

export const SerialPort = () => {
const { t } = useTranslation();
const [isLoading, setIsLoading] = useState(false);

async function selectSerial() {
Expand All @@ -19,10 +22,12 @@ export const SerialPort = () => {
}

return (
<div className="flex items-center justify-center text-neutral-300" onClick={selectSerial}>
<div className="flex h-[28px] w-[28px] cursor-pointer items-center justify-center rounded text-neutral-300 hover:bg-neutral-700/70 hover:text-white">
{isLoading ? <Loader2Icon className="animate-spin" size={18} /> : <CpuIcon size={18} />}
<MenuTooltip title={t('menu.serialPort', 'Serial Port')}>
<div className="flex items-center justify-center text-neutral-300" onClick={selectSerial}>
<div className="flex h-[28px] w-[28px] cursor-pointer items-center justify-center rounded text-neutral-300 hover:bg-neutral-700/70 hover:text-white">
{isLoading ? <Loader2Icon className="animate-spin" size={18} /> : <CpuIcon size={18} />}
</div>
</div>
</div>
</MenuTooltip>
);
};
19 changes: 13 additions & 6 deletions browser/src/components/menu/settings/index.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import { Popover } from 'antd';
import { Divider, Popover } from 'antd';
import { BookIcon, DownloadIcon, SettingsIcon } from 'lucide-react';
import { useTranslation } from 'react-i18next';

import { MenuTooltip } from '../menu-tooltip';
import { Language } from './language.tsx';
import { TooltipsSetting } from './tooltips';

export const Settings = () => {
const { t } = useTranslation();
Expand All @@ -14,6 +16,9 @@ export const Settings = () => {
const content = (
<div className="flex flex-col space-y-0.5">
<Language />
<TooltipsSetting />

<Divider style={{ margin: '5px 0 5px 0' }} />

<div
className="flex h-[32px] cursor-pointer items-center space-x-2 rounded px-3 text-neutral-300 hover:bg-neutral-700/50"
Expand All @@ -34,10 +39,12 @@ export const Settings = () => {
);

return (
<Popover content={content} placement="bottomLeft" trigger="click" arrow={false}>
<div className="flex h-[28px] w-[28px] cursor-pointer items-center justify-center rounded text-neutral-300 hover:bg-neutral-700/50 hover:text-white">
<SettingsIcon size={18} />
</div>
</Popover>
<MenuTooltip title={t('menu.settings', 'Settings')}>
<Popover content={content} placement="bottomLeft" trigger="click" arrow={false}>
<div className="flex h-[28px] w-[28px] cursor-pointer items-center justify-center rounded text-neutral-300 hover:bg-neutral-700/50 hover:text-white">
<SettingsIcon size={18} />
</div>
</Popover>
</MenuTooltip>
);
};
31 changes: 31 additions & 0 deletions browser/src/components/menu/settings/tooltips.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { Checkbox } from 'antd';
import { useAtom } from 'jotai';
import { atom } from 'jotai';
import { MessageSquareIcon } from 'lucide-react';
import { useTranslation } from 'react-i18next';

import * as storage from '@/libs/storage';

// Atom for tooltip visibility
export const showTooltipsAtom = atom(storage.getShowTooltips());

export const TooltipsSetting = () => {
const { t } = useTranslation();
const [showTooltips, setShowTooltips] = useAtom(showTooltipsAtom);

function handleChange(checked: boolean) {
setShowTooltips(checked);
storage.setShowTooltips(checked);
}

return (
<div className="flex h-[32px] items-center space-x-2 rounded px-3 text-neutral-300">
<MessageSquareIcon size={16} />
<span className="text-sm">{t('settings.tooltips.title', 'Show Tooltips')}:</span>
<Checkbox
checked={showTooltips}
onChange={(e) => handleChange(e.target.checked)}
/>
</div>
);
};
17 changes: 12 additions & 5 deletions browser/src/components/menu/video/index.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,17 @@
import { Popover } from 'antd';
import { MonitorIcon } from 'lucide-react';
import { useTranslation } from 'react-i18next';

import { MenuTooltip } from '../menu-tooltip';

import { Device } from './device.tsx';
import { Resolution } from './resolution.tsx';
import { Rotation } from './rotation.tsx';
import { Scale } from './scale.tsx';

export const Video = () => {
const { t } = useTranslation();

const content = (
<div className="flex flex-col space-y-0.5">
<Resolution />
Expand All @@ -17,10 +22,12 @@ export const Video = () => {
);

return (
<Popover content={content} placement="bottomLeft" trigger="click" arrow={false}>
<div className="flex h-[28px] w-[28px] cursor-pointer items-center justify-center rounded text-neutral-300 hover:bg-neutral-700/70 hover:text-white">
<MonitorIcon size={18} />
</div>
</Popover>
<MenuTooltip title={t('menu.video', 'Video')}>
<Popover content={content} placement="bottomLeft" trigger="click" arrow={false}>
<div className="flex h-[28px] w-[28px] cursor-pointer items-center justify-center rounded text-neutral-300 hover:bg-neutral-700/70 hover:text-white">
<MonitorIcon size={18} />
</div>
</Popover>
</MenuTooltip>
);
};
10 changes: 10 additions & 0 deletions browser/src/libs/storage/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ const MOUSE_SCROLL_DIRECTION_KEY = 'nanokvm-usb-mouse-scroll-direction';
const MOUSE_SCROLL_INTERVAL_KEY = 'nanokvm-usb-mouse-scroll-interval';
const MOUSE_JIGGLER_MODE_KEY = 'nanokvm-usb-mouse-jiggler-mode';
const KEYBOARD_SHORTCUT_KEY = 'nanokvm-usb-keyboard-shortcut';
const SHOW_TOOLTIPS_KEY = 'nanokvm-usb-show-tooltips';

export function getLanguage() {
return localStorage.getItem(LANGUAGE_KEY);
Expand Down Expand Up @@ -155,3 +156,12 @@ export function getMouseJigglerMode(): 'enable' | 'disable' {
export function setMouseJigglerMode(jiggler: 'enable' | 'disable'): void {
localStorage.setItem(MOUSE_JIGGLER_MODE_KEY, jiggler);
}

export function getShowTooltips(): boolean {
const value = localStorage.getItem(SHOW_TOOLTIPS_KEY);
return value !== 'false'; // Default to true
}

export function setShowTooltips(show: boolean): void {
localStorage.setItem(SHOW_TOOLTIPS_KEY, show ? 'true' : 'false');
}