Lint fix pass with all rules enforced.

Bumped devcontainer
Bump more packages
This commit is contained in:
Marc Brooks 2025-11-11 16:59:54 -06:00
parent e7afa61cc7
commit c56eb84325
No known key found for this signature in database
GPG Key ID: 583A6AF2D6AE1DC6
125 changed files with 3022 additions and 2985 deletions

View File

@ -4,7 +4,7 @@
"features": {
"ghcr.io/devcontainers/features/node:1": {
// Should match what is defined in ui/package.json
"version": "22.20.0"
"version": "22.21.1"
}
},
"mounts": [

View File

@ -4,7 +4,7 @@
"features": {
"ghcr.io/devcontainers/features/node:1": {
// Should match what is defined in ui/package.json
"version": "22.20.0"
"version": "22.21.1"
}
},
"runArgs": [

563
ui/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,7 +1,7 @@
{
"name": "kvm-ui",
"private": true,
"version": "2025.11.11.1900",
"version": "2025.11.20.0300",
"type": "module",
"engines": {
"node": "^22.21.1"
@ -47,11 +47,11 @@
"react": "^19.2.0",
"react-animate-height": "^3.2.3",
"react-dom": "^19.2.0",
"react-hook-form": "^7.66.0",
"react-hook-form": "^7.66.1",
"react-hot-toast": "^2.6.0",
"react-icons": "^5.5.0",
"react-router": "^7.9.5",
"react-simple-keyboard": "^3.8.133",
"react-router": "^7.9.6",
"react-simple-keyboard": "^3.8.136",
"react-use-websocket": "^4.13.0",
"react-xtermjs": "^1.0.10",
"recharts": "^3.4.1",
@ -61,11 +61,11 @@
"zustand": "^4.5.2"
},
"devDependencies": {
"@eslint/compat": "^1.4.1",
"@eslint/compat": "^2.0.0",
"@eslint/eslintrc": "^3.3.1",
"@eslint/js": "^9.39.1",
"@inlang/cli": "^3.0.12",
"@inlang/paraglide-js": "^2.4.0",
"@inlang/paraglide-js": "^2.5.0",
"@inlang/plugin-m-function-matcher": "^2.1.0",
"@inlang/plugin-message-format": "^4.0.0",
"@inlang/sdk": "^2.4.9",
@ -73,13 +73,13 @@
"@tailwindcss/postcss": "^4.1.17",
"@tailwindcss/typography": "^0.5.19",
"@tailwindcss/vite": "^4.1.17",
"@types/react": "^19.2.3",
"@types/react-dom": "^19.2.2",
"@types/react": "^19.2.6",
"@types/react-dom": "^19.2.3",
"@types/semver": "^7.7.1",
"@types/validator": "^13.15.5",
"@typescript-eslint/eslint-plugin": "^8.46.4",
"@typescript-eslint/parser": "^8.46.4",
"@vitejs/plugin-react-swc": "^4.2.1",
"@types/validator": "^13.15.10",
"@typescript-eslint/eslint-plugin": "^8.47.0",
"@typescript-eslint/parser": "^8.47.0",
"@vitejs/plugin-react-swc": "^4.2.2",
"autoprefixer": "^10.4.22",
"eslint": "^9.39.1",
"eslint-config-prettier": "^10.1.8",
@ -95,7 +95,7 @@
"prettier-plugin-tailwindcss": "^0.7.1",
"tailwindcss": "^4.1.17",
"typescript": "^5.9.3",
"typescript-eslint": "^8.46.4",
"typescript-eslint": "^8.47.0",
"vite": "^7.2.2",
"vite-tsconfig-paths": "^5.1.4"
}

View File

@ -6,12 +6,7 @@ import { Popover, PopoverButton, PopoverPanel } from "@headlessui/react";
import { CommandLineIcon } from "@heroicons/react/20/solid";
import { cx } from "@/cva.config";
import {
useHidStore,
useMountMediaStore,
useSettingsStore,
useUiStore,
} from "@hooks/stores";
import { useHidStore, useMountMediaStore, useSettingsStore, useUiStore } from "@hooks/stores";
import { useDeviceUiNavigation } from "@hooks/useAppNavigation";
import { Button } from "@components/Button";
import Container from "@components/Container";
@ -28,7 +23,8 @@ export default function Actionbar({
}) {
const { navigateTo } = useDeviceUiNavigation();
const { isVirtualKeyboardEnabled, setVirtualKeyboardEnabled } = useHidStore();
const { setDisableVideoFocusTrap, terminalType, setTerminalType, toggleSidebarView } = useUiStore();
const { setDisableVideoFocusTrap, terminalType, setTerminalType, toggleSidebarView } =
useUiStore();
const { remoteVirtualMediaState } = useMountMediaStore();
const { developerMode } = useSettingsStore();
@ -246,10 +242,7 @@ export default function Actionbar({
theme="light"
text={m.action_bar_connection_stats()}
LeadingIcon={({ className }) => (
<LuSignal
className={cx(className, "mb-0.5 text-green-500")}
strokeWidth={4}
/>
<LuSignal className={cx(className, "mb-0.5 text-green-500")} strokeWidth={4} />
)}
onClick={() => {
toggleSidebarView("connection-stats");
@ -264,7 +257,7 @@ export default function Actionbar({
LeadingIcon={LuSettings}
onClick={() => {
setDisableVideoFocusTrap(true);
navigateTo("/settings")
navigateTo("/settings");
}}
/>
</div>

View File

@ -55,9 +55,7 @@ export default function AuthLayout({
</div>
) : null}
<div className="space-y-2 text-center">
<h1 className="text-4xl font-semibold text-black dark:text-white">
{title}
</h1>
<h1 className="text-4xl font-semibold text-black dark:text-white">{title}</h1>
<p className="text-slate-600 dark:text-slate-400">{description}</p>
</div>
@ -65,12 +63,8 @@ export default function AuthLayout({
<div className="mx-auto max-w-sm space-y-4">
<form action={`${CLOUD_API}/oidc/google`} method="POST">
{/*This could be the KVM ID*/}
{deviceId ? (
<input type="hidden" name="deviceId" value={deviceId} />
) : null}
{returnTo ? (
<input type="hidden" name="returnTo" value={returnTo} />
) : null}
{deviceId ? <input type="hidden" name="deviceId" value={deviceId} /> : null}
{returnTo ? <input type="hidden" name="returnTo" value={returnTo} /> : null}
<Button
size="LG"
theme="light"
@ -80,8 +74,7 @@ export default function AuthLayout({
textAlign="center"
type="submit"
loading={
(navigation.state === "submitting" ||
navigation.state === "loading") &&
(navigation.state === "submitting" || navigation.state === "loading") &&
navigation.formMethod?.toLowerCase() === "post" &&
navigation.formAction?.includes("auth/google")
}

View File

@ -16,7 +16,7 @@ const sizes = {
const themes = {
primary: cx(
// Base styles
"bg-blue-700 dark:border-blue-600 border border-blue-900/60 text-white shadow-sm",
"border border-blue-900/60 bg-blue-700 text-white shadow-sm dark:border-blue-600",
// Hover states
"group-hover:bg-blue-800",
// Active states
@ -24,9 +24,9 @@ const themes = {
),
danger: cx(
// Base styles
"bg-red-600 text-white border-red-700 shadow-xs shadow-red-200/80 dark:border-red-600 dark:shadow-red-900/20",
"border-red-700 bg-red-600 text-white shadow-xs shadow-red-200/80 dark:border-red-600 dark:shadow-red-900/20",
// Hover states
"group-hover:bg-red-700 group-hover:border-red-800 dark:group-hover:bg-red-700 dark:group-hover:border-red-600",
"group-hover:border-red-800 group-hover:bg-red-700 dark:group-hover:border-red-600 dark:group-hover:bg-red-700",
// Active states
"group-active:bg-red-800 dark:group-active:bg-red-800",
// Focus states
@ -34,7 +34,7 @@ const themes = {
),
light: cx(
// Base styles
"bg-white text-black border-slate-800/30 shadow-xs dark:bg-slate-800 dark:border-slate-300/20 dark:text-white",
"border-slate-800/30 bg-white text-black shadow-xs dark:border-slate-300/20 dark:bg-slate-800 dark:text-white",
// Hover states
"group-hover:bg-blue-50/80 dark:group-hover:bg-slate-700",
// Active states
@ -44,7 +44,7 @@ const themes = {
),
lightDanger: cx(
// Base styles
"bg-white text-black border-red-400/60 shadow-xs",
"border-red-400/60 bg-white text-black shadow-xs",
// Hover states
"group-hover:bg-red-50/80",
// Active states
@ -54,9 +54,9 @@ const themes = {
),
blank: cx(
// Base styles
"bg-white/0 text-black border-transparent dark:text-white",
"border-transparent bg-white/0 text-black dark:text-white",
// Hover states
"group-hover:bg-white group-hover:border-slate-800/30 group-hover:shadow-sm dark:group-hover:bg-slate-700 dark:group-hover:border-slate-600",
"group-hover:border-slate-800/30 group-hover:bg-white group-hover:shadow-sm dark:group-hover:border-slate-600 dark:group-hover:bg-slate-700",
// Active states
"group-active:bg-slate-100/80",
),
@ -65,16 +65,16 @@ const themes = {
const btnVariants = cva({
base: cx(
// Base styles
"border rounded-sm select-none",
"rounded-sm border select-none",
// Size classes
"justify-center items-center shrink-0",
"shrink-0 items-center justify-center",
// Transition classes
"outline-hidden transition-all duration-200",
// Text classes
"font-display text-center font-medium leading-tight",
"text-center font-display leading-tight font-medium",
// States
"group-focus:outline-hidden group-focus:ring-2 group-focus:ring-offset-2 group-focus:ring-blue-700",
"group-disabled:opacity-50 group-disabled:pointer-events-none",
"group-focus:ring-2 group-focus:ring-blue-700 group-focus:ring-offset-2 group-focus:outline-hidden",
"group-disabled:pointer-events-none group-disabled:opacity-50",
),
variants: {
@ -115,8 +115,7 @@ interface ButtonContentPropsType {
}
function ButtonContent(props: ButtonContentPropsType) {
const { text, LeadingIcon, TrailingIcon, fullWidth, className, textAlign, loading } =
props;
const { text, LeadingIcon, TrailingIcon, fullWidth, className, textAlign, loading } = props;
// Based on the size prop, we'll use the corresponding variant classnames
const iconClassName = iconVariants(props);
@ -136,9 +135,7 @@ function ButtonContent(props: ButtonContentPropsType) {
<LoadingSpinner className={cx(iconClassName, "animate-spin")} />
</div>
) : (
LeadingIcon && (
<LeadingIcon className={cx(iconClassName, "shrink-0 justify-start")} />
)
LeadingIcon && <LeadingIcon className={cx(iconClassName, "shrink-0 justify-start")} />
)}
{text && typeof text === "string" ? (
@ -147,9 +144,7 @@ function ButtonContent(props: ButtonContentPropsType) {
text
)}
{TrailingIcon && (
<TrailingIcon className={cx(iconClassName, "shrink-0 justify-end")} />
)}
{TrailingIcon && <TrailingIcon className={cx(iconClassName, "shrink-0 justify-end")} />}
</div>
</div>
);
@ -175,7 +170,7 @@ type ButtonPropsType = Pick<
export const Button = React.forwardRef<HTMLButtonElement, ButtonPropsType>(
({ type, disabled, onClick, formNoValidate, loading, fetcher, ...props }, ref) => {
const classes = cx(
"group outline-hidden cursor-pointer",
"group cursor-pointer outline-hidden",
props.fullWidth ? "w-full" : "",
loading ? "pointer-events-none" : "",
);
@ -212,7 +207,7 @@ export const Button = React.forwardRef<HTMLButtonElement, ButtonPropsType>(
Button.displayName = "Button";
type LinkPropsType = Pick<LinkProps, "to"> &
React.ComponentProps<typeof ButtonContent> & { disabled?: boolean, reloadDocument?: boolean };
React.ComponentProps<typeof ButtonContent> & { disabled?: boolean; reloadDocument?: boolean };
export const LinkButton = ({ to, ...props }: LinkPropsType) => {
const classes = cx(
"group outline-hidden",
@ -241,7 +236,7 @@ type LabelPropsType = Pick<HTMLLabelElement, "htmlFor"> &
React.ComponentProps<typeof ButtonContent> & { disabled?: boolean };
export const LabelButton = ({ htmlFor, ...props }: LabelPropsType) => {
const classes = cx(
"group outline-hidden block cursor-pointer",
"group block cursor-pointer outline-hidden",
props.disabled ? "pointer-events-none opacity-70!" : "",
props.fullWidth ? "w-full" : "",
props.loading ? "pointer-events-none" : "",

View File

@ -8,10 +8,12 @@ interface Props {
export const CardHeader = ({ headline, description, Button }: Props) => {
return (
<div className="flex items-center justify-between pb-0 gap-x-4">
<div className="space-y-1 grow">
<h3 className="text-lg font-bold leading-none text-black dark:text-white">{headline}</h3>
{description && <div className="text-sm text-slate-700 dark:text-slate-300">{description}</div>}
<div className="flex items-center justify-between gap-x-4 pb-0">
<div className="grow space-y-1">
<h3 className="text-lg leading-none font-bold text-black dark:text-white">{headline}</h3>
{description && (
<div className="text-sm text-slate-700 dark:text-slate-300">{description}</div>
)}
</div>
{Button && <div>{Button}</div>}
</div>

View File

@ -15,7 +15,7 @@ const checkboxVariants = cva({
"form-checkbox block rounded",
// Colors
"border-slate-300 dark:border-slate-600 bg-slate-50 dark:bg-slate-800 checked:accent-blue-700 checked:dark:accent-blue-500 transition-colors",
"border-slate-300 bg-slate-50 transition-colors checked:accent-blue-700 dark:border-slate-600 dark:bg-slate-800 checked:dark:accent-blue-500",
// Hover
"hover:bg-slate-200/50 dark:hover:bg-slate-700/50",
@ -24,7 +24,7 @@ const checkboxVariants = cva({
"active:bg-slate-200 dark:active:bg-slate-700",
// Focus
"focus:border-slate-300 dark:focus:border-slate-600 focus:outline-hidden focus:ring-2 focus:ring-blue-700 dark:focus:ring-blue-500 focus:ring-offset-2 dark:focus:ring-offset-slate-900",
"focus:border-slate-300 focus:ring-2 focus:ring-blue-700 focus:ring-offset-2 focus:outline-hidden dark:focus:border-slate-600 dark:focus:ring-blue-500 dark:focus:ring-offset-slate-900",
// Disabled
"disabled:pointer-events-none disabled:opacity-30",
@ -41,9 +41,7 @@ const Checkbox = forwardRef<HTMLInputElement, CheckBoxProps>(function Checkbox(
ref,
) {
const classes = checkboxVariants({ size });
return (
<input ref={ref} {...props} type="checkbox" className={clsx(classes, className)} />
);
return <input ref={ref} {...props} type="checkbox" className={clsx(classes, className)} />;
});
Checkbox.displayName = "Checkbox";

View File

@ -74,11 +74,11 @@ export function Combobox({
"dark:bg-slate-800 dark:text-white dark:hover:bg-slate-700 dark:active:bg-slate-800/60",
// Focus
"focus:outline-blue-600 focus:ring-2 focus:ring-blue-700 focus:ring-offset-2 dark:focus:outline-blue-500 dark:focus:ring-blue-500",
"focus:ring-2 focus:ring-blue-700 focus:ring-offset-2 focus:outline-blue-600 dark:focus:ring-blue-500 dark:focus:outline-blue-500",
// Disabled
disabled &&
"pointer-events-none select-none bg-slate-50 text-slate-500/80 disabled:hover:bg-white dark:bg-slate-800 dark:text-slate-400/80 dark:disabled:hover:bg-slate-800",
"pointer-events-none bg-slate-50 text-slate-500/80 select-none disabled:hover:bg-white dark:bg-slate-800 dark:text-slate-400/80 dark:disabled:hover:bg-slate-800",
)}
placeholder={disabled ? disabledMessage : placeholder}
displayValue={displayValue}
@ -95,7 +95,7 @@ export function Combobox({
value={option}
className={clsx(
// General styling
"cursor-default select-none px-4 py-2",
"cursor-default px-4 py-2 select-none",
// Hover and active states
"hover:bg-blue-50/80 ui-active:bg-blue-50/80 ui-active:text-blue-900",

View File

@ -77,14 +77,13 @@ export function ConfirmDialog({
<div className="pointer-events-auto relative w-full overflow-hidden rounded-lg border border-slate-200 bg-white shadow-sm transition-all dark:border-slate-800 dark:bg-slate-900">
<div className="p-6">
<div className="flex items-start gap-3.5">
<Icon aria-hidden="true" className={cx("size-[18px] shrink-0 mt-[2px]", iconClass)} />
<div className="flex-1 min-w-0 space-y-2">
<h2 className="font-semibold text-slate-950 dark:text-white">
{title}
</h2>
<div className="text-sm text-slate-700 dark:text-slate-300">
{description}
</div>
<Icon
aria-hidden="true"
className={cx("mt-[2px] size-[18px] shrink-0", iconClass)}
/>
<div className="min-w-0 flex-1 space-y-2">
<h2 className="font-semibold text-slate-950 dark:text-white">{title}</h2>
<div className="text-sm text-slate-700 dark:text-slate-300">{description}</div>
</div>
</div>

View File

@ -4,7 +4,7 @@ import React, { ReactNode } from "react";
import { cx } from "@/cva.config";
function Container({ children, className }: { children: ReactNode; className?: string }) {
return <div className={cx("mx-auto h-full w-full px-8 ", className)}>{children}</div>;
return <div className={cx("mx-auto h-full w-full px-8", className)}>{children}</div>;
}
function Article({ children }: { children: React.ReactNode }) {

View File

@ -7,7 +7,6 @@ import { LifeTimeLabel } from "@routes/devices.$id.settings.network";
import { NetworkState } from "@hooks/stores";
import { m } from "@localizations/messages.js";
export default function DhcpLeaseCard({
networkState,
setShowRenewLeaseConfirm,
@ -54,10 +53,9 @@ export default function DhcpLeaseCard({
<div className="flex justify-between border-slate-800/10 pt-2 dark:border-slate-300/20">
<span className="text-sm text-slate-600 dark:text-slate-400">
{m.ip_address()}
</span>&nbsp;
<span className="text-sm font-medium">
{networkState?.dhcp_lease?.ip}
</span>
&nbsp;
<span className="text-sm font-medium">{networkState?.dhcp_lease?.ip}</span>
</div>
)}
@ -65,10 +63,9 @@ export default function DhcpLeaseCard({
<div className="flex justify-between border-t border-slate-800/10 pt-2 dark:border-slate-300/20">
<span className="text-sm text-slate-600 dark:text-slate-400">
{m.subnet_mask()}
</span>&nbsp;
<span className="text-sm font-medium">
{networkState?.dhcp_lease?.netmask}
</span>
&nbsp;
<span className="text-sm font-medium">{networkState?.dhcp_lease?.netmask}</span>
</div>
)}
@ -76,7 +73,8 @@ export default function DhcpLeaseCard({
<div className="flex justify-between border-t border-slate-800/10 pt-2 dark:border-slate-300/20">
<span className="text-sm text-slate-600 dark:text-slate-400">
{m.dns_servers()}
</span>&nbsp;
</span>
&nbsp;
<span className="text-right text-sm font-medium">
{networkState?.dhcp_lease?.dns_servers.map(dns => (
<div key={dns}>{dns}</div>
@ -89,10 +87,9 @@ export default function DhcpLeaseCard({
<div className="flex justify-between border-t border-slate-800/10 pt-2 dark:border-slate-300/20">
<span className="text-sm text-slate-600 dark:text-slate-400">
{m.dhcp_lease_broadcast()}
</span>&nbsp;
<span className="text-sm font-medium">
{networkState?.dhcp_lease?.broadcast}
</span>
&nbsp;
<span className="text-sm font-medium">{networkState?.dhcp_lease?.broadcast}</span>
</div>
)}
@ -100,10 +97,9 @@ export default function DhcpLeaseCard({
<div className="flex justify-between border-t border-slate-800/10 pt-2 dark:border-slate-300/20">
<span className="text-sm text-slate-600 dark:text-slate-400">
{m.dhcp_lease_domain()}
</span>&nbsp;
<span className="text-sm font-medium">
{networkState?.dhcp_lease?.domain}
</span>
&nbsp;
<span className="text-sm font-medium">{networkState?.dhcp_lease?.domain}</span>
</div>
)}
@ -112,7 +108,8 @@ export default function DhcpLeaseCard({
<div className="flex justify-between gap-x-8 border-t border-slate-800/10 pt-2 dark:border-slate-300/20">
<div className="w-full grow text-sm text-slate-600 dark:text-slate-400">
{m.ntp_servers()}
</div>&nbsp;
</div>
&nbsp;
<div className="shrink text-right text-sm font-medium">
{networkState?.dhcp_lease?.ntp_servers.map(server => (
<div key={server}>{server}</div>
@ -125,10 +122,9 @@ export default function DhcpLeaseCard({
<div className="flex justify-between border-t border-slate-800/10 pt-2 dark:border-slate-300/20">
<span className="text-sm text-slate-600 dark:text-slate-400">
{m.dhcp_lease_hostname()}
</span>&nbsp;
<span className="text-sm font-medium">
{networkState?.dhcp_lease?.hostname}
</span>
&nbsp;
<span className="text-sm font-medium">{networkState?.dhcp_lease?.hostname}</span>
</div>
)}
</div>
@ -139,7 +135,8 @@ export default function DhcpLeaseCard({
<div className="flex justify-between pt-2">
<span className="text-sm text-slate-600 dark:text-slate-400">
{m.dhcp_lease_gateway()}
</span>&nbsp;
</span>
&nbsp;
<span className="text-right text-sm font-medium">
{networkState?.dhcp_lease?.routers.map(router => (
<div key={router}>{router}</div>
@ -152,10 +149,9 @@ export default function DhcpLeaseCard({
<div className="flex justify-between border-t border-slate-800/10 pt-2 dark:border-slate-300/20">
<span className="text-sm text-slate-600 dark:text-slate-400">
{m.dhcp_server()}
</span>&nbsp;
<span className="text-sm font-medium">
{networkState?.dhcp_lease?.server_id}
</span>
&nbsp;
<span className="text-sm font-medium">{networkState?.dhcp_lease?.server_id}</span>
</div>
)}
@ -163,11 +159,10 @@ export default function DhcpLeaseCard({
<div className="flex justify-between border-t border-slate-800/10 pt-2 dark:border-slate-300/20">
<span className="text-sm text-slate-600 dark:text-slate-400">
{m.dhcp_lease_lease_expires()}
</span>&nbsp;
</span>
&nbsp;
<span className="text-sm font-medium">
<LifeTimeLabel
lifetime={`${networkState?.dhcp_lease?.lease_expiry}`}
/>
<LifeTimeLabel lifetime={`${networkState?.dhcp_lease?.lease_expiry}`} />
</span>
</div>
)}
@ -176,10 +171,9 @@ export default function DhcpLeaseCard({
<div className="flex justify-between border-t border-slate-800/10 pt-2 dark:border-slate-300/20">
<span className="text-sm text-slate-600 dark:text-slate-400">
{m.dhcp_lease_broadcast()}
</span>&nbsp;
<span className="text-sm font-medium">
{networkState?.dhcp_lease?.broadcast}
</span>
&nbsp;
<span className="text-sm font-medium">{networkState?.dhcp_lease?.broadcast}</span>
</div>
)}
@ -187,10 +181,9 @@ export default function DhcpLeaseCard({
<div className="flex justify-between border-t border-slate-800/10 pt-2 dark:border-slate-300/20">
<span className="text-sm text-slate-600 dark:text-slate-400">
{m.dhcp_lease_maximum_transfer_unit()}
</span>&nbsp;
<span className="text-sm font-medium">
{networkState?.dhcp_lease?.mtu}
</span>
&nbsp;
<span className="text-sm font-medium">{networkState?.dhcp_lease?.mtu}</span>
</div>
)}
@ -198,10 +191,9 @@ export default function DhcpLeaseCard({
<div className="flex justify-between border-t border-slate-800/10 pt-2 dark:border-slate-300/20">
<span className="text-sm text-slate-600 dark:text-slate-400">
{m.dhcp_lease_time_to_live()}
</span>&nbsp;
<span className="text-sm font-medium">
{networkState?.dhcp_lease?.ttl}
</span>
&nbsp;
<span className="text-sm font-medium">{networkState?.dhcp_lease?.ttl}</span>
</div>
)}
@ -209,7 +201,8 @@ export default function DhcpLeaseCard({
<div className="flex justify-between border-t border-slate-800/10 pt-2 dark:border-slate-300/20">
<span className="text-sm text-slate-600 dark:text-slate-400">
{m.dhcp_lease_boot_next_server()}
</span>&nbsp;
</span>
&nbsp;
<span className="text-sm font-medium">
{networkState?.dhcp_lease?.bootp_next_server}
</span>
@ -220,7 +213,8 @@ export default function DhcpLeaseCard({
<div className="flex justify-between border-t border-slate-800/10 pt-2 dark:border-slate-300/20">
<span className="text-sm text-slate-600 dark:text-slate-400">
{m.dhcp_lease_boot_server_name()}
</span>&nbsp;
</span>
&nbsp;
<span className="text-sm font-medium">
{networkState?.dhcp_lease?.bootp_server_name}
</span>
@ -231,7 +225,8 @@ export default function DhcpLeaseCard({
<div className="flex justify-between border-t border-slate-800/10 pt-2 dark:border-slate-300/20">
<span className="text-sm text-slate-600 dark:text-slate-400">
{m.dhcp_lease_boot_file()}
</span>&nbsp;
</span>
&nbsp;
<span className="text-sm font-medium">
{networkState?.dhcp_lease?.bootp_file}
</span>
@ -242,7 +237,8 @@ export default function DhcpLeaseCard({
<div className="flex justify-between border-t border-slate-800/10 pt-2 dark:border-slate-300/20">
<span className="text-sm text-slate-600 dark:text-slate-400">
{m.network_dhcp_client_title()}
</span>&nbsp;
</span>
&nbsp;
<span className="text-sm font-medium">
{networkState?.dhcp_lease?.dhcp_client}
</span>

View File

@ -11,13 +11,7 @@ interface Props {
className?: string;
}
export default function EmptyCard({
IconElm,
headline,
description,
BtnElm,
className,
}: Props) {
export default function EmptyCard({ IconElm, headline, description, BtnElm, className }: Props) {
return (
<GridCard>
<div
@ -28,16 +22,12 @@ export default function EmptyCard({
>
<div className="max-w-[90%] space-y-1.5 text-center md:max-w-[60%]">
<div className="space-y-2">
{IconElm && (
<IconElm className="mx-auto h-5 w-5 text-blue-600 dark:text-blue-600" />
)}
<h4 className="text-base font-bold leading-none text-black dark:text-white">
{IconElm && <IconElm className="mx-auto h-5 w-5 text-blue-600 dark:text-blue-600" />}
<h4 className="text-base leading-none font-bold text-black dark:text-white">
{headline}
</h4>
</div>
<p className="mx-auto text-sm text-slate-600 dark:text-slate-400">
{description}
</p>
<p className="mx-auto text-sm text-slate-600 dark:text-slate-400">{description}</p>
</div>
{BtnElm}
</div>

View File

@ -18,13 +18,10 @@ export function FailsafeModeBanner({ reason }: FailsafeModeBannerProps) {
return (
<Card>
<div className="diagonal-stripes flex items-center gap-3 p-4 rounded">
<LuTriangleAlert className="h-5 w-5 flex-shrink-0 text-red-600 dark:text-red-400" />
<p className="text-sm font-medium text-red-800 dark:text-white">
{getReasonMessage()}
</p>
<div className="diagonal-stripes flex items-center gap-3 rounded p-4">
<LuTriangleAlert className="h-5 w-5 flex-shrink-0 text-red-600 dark:text-red-400" />
<p className="text-sm font-medium text-red-800 dark:text-white">{getReasonMessage()}</p>
</div>
</Card>
);
}

View File

@ -14,8 +14,6 @@ import { DOWNGRADE_VERSION } from "@/ui.config";
import { GitHubIcon } from "./Icons";
interface FailSafeModeOverlayProps {
reason: string;
}
@ -45,13 +43,12 @@ function Tooltip({ children, text, show }: TooltipProps) {
return <>{children}</>;
}
return (
<div className="group/tooltip relative">
{children}
<div className="pointer-events-none absolute bottom-full left-1/2 mb-2 hidden -translate-x-1/2 opacity-0 transition-opacity group-hover/tooltip:block group-hover/tooltip:opacity-100">
<Card>
<div className="whitespace-nowrap px-2 py-1 text-xs flex items-center gap-1 justify-center">
<div className="flex items-center justify-center gap-1 px-2 py-1 text-xs whitespace-nowrap">
<LuInfo className="h-3 w-3 text-slate-700 dark:text-slate-300" />
{text}
</div>
@ -152,7 +149,7 @@ Please attach the recovery logs file that was downloaded to your computer:
return (
<AnimatePresence>
<motion.div
className="aspect-video h-full w-full isolate"
className="isolate aspect-video h-full w-full"
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0, transition: { duration: 0 } }}
@ -178,10 +175,12 @@ Please attach the recovery logs file that was downloaded to your computer:
size="SM"
disabled={isDownloadingLogs}
LeadingIcon={GitHubIcon}
text={isDownloadingLogs ? "Downloading Logs..." : "Download Logs & Report Issue"}
text={
isDownloadingLogs ? "Downloading Logs..." : "Download Logs & Report Issue"
}
/>
<div className="h-8 w-px bg-slate-200 dark:bg-slate-700 block" />
<div className="block h-8 w-px bg-slate-200 dark:bg-slate-700" />
<Tooltip text="Download logs first" show={!hasDownloadedLogs}>
<Button
onClick={() => navigateTo("/settings/general/reboot")}
@ -202,8 +201,6 @@ Please attach the recovery logs file that was downloaded to your computer:
/>
</Tooltip>
</div>
</div>
</div>
</div>
@ -213,4 +210,3 @@ Please attach the recovery logs file that was downloaded to your computer:
</AnimatePresence>
);
}

View File

@ -19,8 +19,8 @@ export function FeatureFlag({
if (!appVersion) return;
console.log(
`Feature '${name}' ${isEnabled ? "ENABLED" : "DISABLED"}: ` +
`Current version: ${appVersion}, ` +
`Required min version: ${minAppVersion || "N/A"}`,
`Current version: ${appVersion}, ` +
`Required min version: ${minAppVersion || "N/A"}`,
);
}, [isEnabled, name, minAppVersion, appVersion]);

View File

@ -9,19 +9,13 @@ interface Props {
description?: string | React.ReactNode | null;
disabled?: boolean;
}
export default function FieldLabel({
label,
id,
as = "label",
description,
disabled,
}: Props) {
export default function FieldLabel({ label, id, as = "label", description, disabled }: Props) {
if (as === "label") {
return (
<label
htmlFor={id}
className={cx(
"flex select-none flex-col text-left font-display text-[13px] font-semibold leading-snug text-black dark:text-white",
"flex flex-col text-left font-display text-[13px] leading-snug font-semibold text-black select-none dark:text-white",
disabled && "opacity-50",
)}
>
@ -35,8 +29,8 @@ export default function FieldLabel({
);
} else if (as === "span") {
return (
<div className="flex select-none flex-col">
<span className="font-display text-[13px] font-semibold leading-snug text-black dark:text-white">
<div className="flex flex-col select-none">
<span className="font-display text-[13px] leading-snug font-semibold text-black dark:text-white">
{label}
</span>
{description && (

View File

@ -18,11 +18,7 @@ export default function GridBackground() {
</pattern>
</defs>
<svg
x="50%"
y={-1}
className="overflow-visible fill-blue-100 dark:fill-blue-900/30"
>
<svg x="50%" y={-1} className="overflow-visible fill-blue-100 dark:fill-blue-900/30">
<path
d="M-200 0h201v201h-201Z M600 0h201v201h-201Z M-400 600h201v201h-201Z M200 800h201v201h-201Z"
strokeWidth={0}

View File

@ -59,8 +59,8 @@ export default function DashboardNavbar({
<div className="flex h-14 items-center justify-between">
<div className="flex shrink-0 items-center gap-x-8">
<div className="inline-block shrink-0">
<img src={LogoBlueIcon} alt="" className="h-[24px] dark:hidden" />
<img src={LogoWhiteIcon} alt="" className="hidden h-[24px] dark:block" />
<img src={LogoBlueIcon} alt="" className="h-6 dark:hidden" />
<img src={LogoWhiteIcon} alt="" className="hidden h-6 dark:block" />
</div>
<div className="flex gap-x-2">
@ -84,22 +84,16 @@ export default function DashboardNavbar({
{showConnectionStatus && (
<>
<div className="w-[159px]">
<PeerConnectionStatusCard
state={peerConnectionState}
title={kvmName}
/>
<PeerConnectionStatusCard state={peerConnectionState} title={kvmName} />
</div>
<div className="hidden w-[159px] md:block">
<USBStateStatus
state={usbState}
peerConnectionState={peerConnectionState}
/>
<USBStateStatus state={usbState} peerConnectionState={peerConnectionState} />
</div>
</>
)}
{isLoggedIn ? (
<>
<hr className="h-[20px] w-px self-center border-none bg-slate-800/20 dark:bg-slate-300/20" />
<hr className="h-5 w-px self-center border-none bg-slate-800/20 dark:bg-slate-300/20" />
<div className="relative inline-block text-left">
<Menu>
<MenuButton as="div" className="h-full">
@ -111,7 +105,7 @@ export default function DashboardNavbar({
className="size-6 rounded-full border-2 border-transparent transition-colors group-hover:border-blue-700"
/>
) : userEmail ? (
<span className="font-display max-w-[200px] truncate text-sm/6 font-semibold">
<span className="max-w-[200px] truncate font-display text-sm/6 font-semibold">
{userEmail}
</span>
) : null}
@ -129,20 +123,15 @@ export default function DashboardNavbar({
<div className="space-y-1 p-1 dark:text-white">
<div className="border-b border-b-slate-800/20 dark:border-slate-300/20">
<div className="p-2">
<div className="font-display text-xs">
{m.logged_in_as()}
</div>
<div className="font-display max-w-[200px] truncate text-sm font-semibold">
<div className="font-display text-xs">{m.logged_in_as()}</div>
<div className="max-w-[200px] truncate font-display text-sm font-semibold">
{userEmail}
</div>
</div>
</div>
</div>
)}
<div
className="space-y-1 p-1 dark:text-white"
onClick={onLogout}
>
<div className="space-y-1 p-1 dark:text-white" onClick={onLogout}>
<button className="group flex w-full items-center gap-2 rounded-md px-2 py-1.5 text-sm transition-colors hover:bg-slate-100 dark:hover:bg-slate-700">
<ArrowLeftEndOnRectangleIcon className="size-4" />
<div className="font-display">{m.log_out()}</div>

View File

@ -6,7 +6,7 @@ import {
useRTCStore,
useSettingsStore,
useVideoStore,
VideoState
VideoState,
} from "@hooks/stores";
import { useHidRpc } from "@hooks/useHidRpc";
import { keys, modifiers } from "@/keyboardMappings";
@ -48,10 +48,10 @@ export default function InfoBar() {
}, [keysDownState, showPressedKeys]);
return (
<div className="bg-white border-t border-t-slate-800/30 text-slate-800 dark:border-t-slate-300/20 dark:bg-slate-900 dark:text-slate-300">
<div className="border-t border-t-slate-800/30 bg-white text-slate-800 dark:border-t-slate-300/20 dark:bg-slate-900 dark:text-slate-300">
<div className="flex flex-wrap items-stretch justify-between gap-1">
<div className="flex items-center">
<div className="flex flex-wrap items-center pl-2 gap-x-4">
<div className="flex flex-wrap items-center gap-x-4 pl-2">
{debugMode ? (
<div className="flex">
<span className="text-xs font-semibold">{m.info_resolution()}</span>{" "}
@ -66,18 +66,22 @@ export default function InfoBar() {
</div>
) : null}
{(debugMode && mouseMode == "absolute") ? (
{debugMode && mouseMode == "absolute" ? (
<div className="flex w-[118px] items-center gap-x-1">
<span className="text-xs font-semibold">{m.info_pointer()}</span>
<span className="text-xs">{mouseX},{mouseY}</span>
<span className="text-xs">
{mouseX},{mouseY}
</span>
</div>
) : null}
{(debugMode && mouseMode == "relative") ? (
{debugMode && mouseMode == "relative" ? (
<div className="flex w-[118px] items-center gap-x-1">
<span className="text-xs font-semibold">{m.info_last_move()}</span>
<span className="text-xs">
{mouseMove ? `${mouseMove.x},${mouseMove.y} ${mouseMove.buttons ? `(${mouseMove.buttons})` : ""}` : "N/A"}
{mouseMove
? `${mouseMove.x},${mouseMove.y} ${mouseMove.buttons ? `(${mouseMove.buttons})` : ""}`
: "N/A"}
</span>
</div>
) : null}
@ -119,7 +123,7 @@ export default function InfoBar() {
</div>
</div>
<div className="flex items-center divide-x first:divide-l divide-slate-800/20 dark:divide-slate-300/20">
<div className="first:divide-l flex items-center divide-x divide-slate-800/20 dark:divide-slate-300/20">
{isTurnServerInUse && (
<div className="shrink-0 p-1 px-1.5 text-xs text-black dark:text-white">
{m.info_relayed_by_cloudflare()}

View File

@ -43,14 +43,18 @@ const InputField = forwardRef<HTMLInputElement, InputFieldProps>(function InputF
"[&:has(:user-invalid)]:ring-2 [&:has(:user-invalid)]:ring-red-600 [&:has(:user-invalid)]:ring-offset-2",
// Focus Within
"focus-within:border-slate-300 dark:focus-within:border-slate-600 focus-within:outline-hidden focus-within:ring-2 focus-within:ring-blue-700 focus-within:ring-offset-2",
"focus-within:border-slate-300 focus-within:ring-2 focus-within:ring-blue-700 focus-within:ring-offset-2 focus-within:outline-hidden dark:focus-within:border-slate-600",
// Disabled Within
"disabled-within:pointer-events-none disabled-within:select-none disabled-within:bg-slate-50 dark:disabled-within:bg-slate-800 disabled-within:text-slate-500/80",
"disabled-within:pointer-events-none disabled-within:bg-slate-50 disabled-within:text-slate-500/80 disabled-within:select-none dark:disabled-within:bg-slate-800",
)}
>
{LeadingElm && (
<div className={clsx("pointer-events-none border-r border-r-slate-300 dark:border-r-slate-600")}>
<div
className={clsx(
"pointer-events-none border-r border-r-slate-300 dark:border-r-slate-600",
)}
>
{LeadingElm}
</div>
)}
@ -60,12 +64,12 @@ const InputField = forwardRef<HTMLInputElement, InputFieldProps>(function InputF
sizeClasses,
TrailingElm ? "pr-2" : "",
className,
"block flex-1 border-0 bg-transparent leading-none placeholder:text-sm placeholder:text-slate-300 dark:placeholder:text-slate-500 focus:ring-0 text-black dark:text-white",
"block flex-1 border-0 bg-transparent leading-none text-black placeholder:text-sm placeholder:text-slate-300 focus:ring-0 dark:text-white dark:placeholder:text-slate-500",
)}
{...props}
/>
{TrailingElm && (
<div className="flex items-center pr-3 pointer-events-none">{TrailingElm}</div>
<div className="pointer-events-none flex items-center pr-3">{TrailingElm}</div>
)}
</Card>
{error && <FieldError error={error} />}
@ -75,15 +79,10 @@ const InputField = forwardRef<HTMLInputElement, InputFieldProps>(function InputF
InputField.displayName = "InputField";
const InputFieldWithLabel = forwardRef<HTMLInputElement, InputFieldWithLabelProps>(
function InputFieldWithLabel(
{ label, description, id, ...props },
ref: Ref<HTMLInputElement>,
) {
function InputFieldWithLabel({ label, description, id, ...props }, ref: Ref<HTMLInputElement>) {
return (
<div className="w-full space-y-1">
{(label || description) && (
<FieldLabel label={label} id={id} description={description} />
)}
{(label || description) && <FieldLabel label={label} id={id} description={description} />}
<InputField ref={ref as never} id={id} {...props} />
</div>
);

View File

@ -4,16 +4,14 @@ import { GridCard } from "@components/Card";
import { LifeTimeLabel } from "@routes/devices.$id.settings.network";
import { m } from "@localizations/messages.js";
export function FlagLabel({ flag, className }: { flag: string, className?: string }) {
export function FlagLabel({ flag, className }: { flag: string; className?: string }) {
const classes = cx(
"ml-2 rounded-sm bg-red-500 px-2 py-1 text-[10px] font-medium leading-none text-white dark:border",
"ml-2 rounded-sm bg-red-500 px-2 py-1 text-[10px] leading-none font-medium text-white dark:border",
"bg-red-500 text-white dark:border-red-700 dark:bg-red-800 dark:text-red-50",
className,
);
return <span className={classes}>
{flag}
</span>
return <span className={classes}>{flag}</span>;
}
export default function Ipv6NetworkCard({
@ -33,28 +31,22 @@ export default function Ipv6NetworkCard({
<div className="flex flex-col justify-between">
<span className="text-sm text-slate-600 dark:text-slate-400">
{m.ipv6_link_local()}
</span>&nbsp;
<span className="text-sm font-medium">
{networkState?.ipv6_link_local}
</span>
&nbsp;
<span className="text-sm font-medium">{networkState?.ipv6_link_local}</span>
</div>
<div className="flex flex-col justify-between">
<span className="text-sm text-slate-600 dark:text-slate-400">
{m.ipv6_gateway()}
</span>&nbsp;
<span className="text-sm font-medium">
{networkState?.ipv6_gateway}
</span>
<span className="text-sm text-slate-600 dark:text-slate-400">{m.ipv6_gateway()}</span>
&nbsp;
<span className="text-sm font-medium">{networkState?.ipv6_gateway}</span>
</div>
</div>
<div className="space-y-3 pt-2">
{networkState?.ipv6_addresses && networkState?.ipv6_addresses.length > 0 && (
<div className="space-y-3">
<h4 className="text-sm font-semibold">
{m.network_ipv6_addresses_header()}
</h4>
<h4 className="text-sm font-semibold">{m.network_ipv6_addresses_header()}</h4>
{networkState.ipv6_addresses.map(addr => (
<div
key={addr.address}
@ -64,12 +56,17 @@ export default function Ipv6NetworkCard({
<div className="col-span-2 flex flex-col justify-between">
<span className="text-sm text-slate-600 dark:text-slate-400">
{m.ipv6_address_label()}
</span>&nbsp;
<span className="text-sm font-medium flex">
</span>
&nbsp;
<span className="flex text-sm font-medium">
<span className="flex-1">{addr.address}</span>&nbsp;
<span className="text-sm font-medium flex gap-x-1">
{addr.flag_deprecated ? <FlagLabel flag={m.network_ipv6_flag_deprecated()} /> : null}
{addr.flag_dad_failed ? <FlagLabel flag={m.network_ipv6_flag_dad_failed()} /> : null}
<span className="flex gap-x-1 text-sm font-medium">
{addr.flag_deprecated ? (
<FlagLabel flag={m.network_ipv6_flag_deprecated()} />
) : null}
{addr.flag_dad_failed ? (
<FlagLabel flag={m.network_ipv6_flag_dad_failed()} />
) : null}
</span>
</span>
</div>
@ -78,7 +75,8 @@ export default function Ipv6NetworkCard({
<div className="flex flex-col justify-between">
<span className="text-sm text-slate-600 dark:text-slate-400">
{m.ipv6_valid_lifetime()}
</span>&nbsp;
</span>
&nbsp;
<span className="text-sm font-medium">
{addr.valid_lifetime === "" ? (
<span className="text-slate-400 dark:text-slate-600">
@ -95,7 +93,8 @@ export default function Ipv6NetworkCard({
<div className="flex flex-col justify-between">
<span className="text-sm text-slate-600 dark:text-slate-400">
{m.ipv6_preferred_lifetime()}
</span>&nbsp;
</span>
&nbsp;
<span className="text-sm font-medium">
{addr.preferred_lifetime === "" ? (
<span className="text-slate-400 dark:text-slate-600">

View File

@ -72,7 +72,9 @@ export function JigglerSetting({
return (
<div className="space-y-4">
<div className="space-y-2">
<h4 className="text-sm font-semibold text-gray-900 dark:text-gray-100">{m.jiggler_examples_label()}</h4>
<h4 className="text-sm font-semibold text-gray-900 dark:text-gray-100">
{m.jiggler_examples_label()}
</h4>
<div className="flex flex-wrap gap-2">
{exampleConfigs.map((example, index) => (
<Button

View File

@ -53,12 +53,10 @@ export default function KvmCard({
}) {
return (
<Card>
<div className="px-5 py-5 space-y-3">
<div className="flex justify-between items-center">
<div className="space-y-3 px-5 py-5">
<div className="flex items-center justify-between">
<div className="space-y-1.5">
<div className="text-lg font-bold leading-none text-black dark:text-white">
{title}
</div>
<div className="text-lg leading-none font-bold text-black dark:text-white">{title}</div>
{online ? (
<div className="flex items-center gap-x-1.5">
@ -67,7 +65,7 @@ export default function KvmCard({
</div>
) : (
<div className="flex items-center gap-x-1.5">
<div className="h-2.5 w-2.5 rounded-full border border-slate-400/60 dark:border-slate-500 bg-slate-200 dark:bg-slate-600" />
<div className="h-2.5 w-2.5 rounded-full border border-slate-400/60 bg-slate-200 dark:border-slate-500 dark:bg-slate-600" />
<div className="text-sm text-black dark:text-white">
{lastSeen ? (
<>{m.last_online({ time: getRelativeTimeString(lastSeen) })}</>
@ -109,14 +107,14 @@ export default function KvmCard({
></MenuButton>
<MenuItems
transition
className="data-closed:scale-95 data-closed:transform data-closed:opacity-0 data-enter:duration-100 data-leave:duration-75 data-enter:ease-out data-leave:ease-in"
className="data-closed:scale-95 data-closed:transform data-closed:opacity-0 data-enter:duration-100 data-enter:ease-out data-leave:duration-75 data-leave:ease-in"
>
<Card className="absolute right-0 z-10 w-56 px-1 mt-2 transition origin-top-right ring-1 ring-black/50 focus:outline-hidden">
<Card className="absolute right-0 z-10 mt-2 w-56 origin-top-right px-1 ring-1 ring-black/50 transition focus:outline-hidden">
<div className="divide-y divide-slate-800/20 dark:divide-slate-300/20">
<MenuItem>
<div>
<div className="block w-full">
<div className="flex items-center px-2 my-1 text-sm transition-colors rounded-md gap-x-2 hover:bg-slate-100 dark:hover:bg-slate-700">
<div className="my-1 flex items-center gap-x-2 rounded-md px-2 text-sm transition-colors hover:bg-slate-100 dark:hover:bg-slate-700">
<Link
className="block w-full py-1.5 text-black dark:text-white"
to={`./${id}/rename`}
@ -130,7 +128,7 @@ export default function KvmCard({
<MenuItem>
<div>
<div className="block w-full">
<div className="flex items-center px-2 my-1 text-sm transition-colors rounded-md gap-x-2 hover:bg-slate-100 dark:hover:bg-slate-700">
<div className="my-1 flex items-center gap-x-2 rounded-md px-2 text-sm transition-colors hover:bg-slate-100 dark:hover:bg-slate-700">
<Link
className="block w-full py-1.5 text-black dark:text-white"
to={`./${id}/deregister`}

View File

@ -1,25 +1,14 @@
import clsx from "clsx";
export default function LoadingSpinner({
className,
}: {
className: string | undefined;
}) {
export default function LoadingSpinner({ className }: { className: string | undefined }) {
return (
<svg
className={clsx(className, "shrink-0 animate-spin p-[2px]")}
className={clsx(className, "shrink-0 animate-spin p-0.5")}
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
>
<circle
className="opacity-25"
cx="12"
cy="12"
r="10"
stroke="currentColor"
strokeWidth="4"
/>
<circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4" />
<path
// className="opacity-75"
fill="currentColor"

View File

@ -25,7 +25,7 @@ export default function MacroBar() {
}
return (
<Container className="bg-white dark:bg-slate-900 border-b border-b-slate-800/20 dark:border-b-slate-300/20">
<Container className="border-b border-b-slate-800/20 bg-white dark:border-b-slate-300/20 dark:bg-slate-900">
<div className="flex items-center gap-x-2 py-1.5">
<div className="absolute -ml-5">
<LuCommand className="h-4 w-4 text-slate-500" />

View File

@ -8,11 +8,7 @@ import FieldLabel from "@components/FieldLabel";
import Fieldset from "@components/Fieldset";
import { InputFieldWithLabel, FieldError } from "@components/InputField";
import { MacroStepCard } from "@components/MacroStepCard";
import {
DEFAULT_DELAY,
MAX_STEPS_PER_MACRO,
MAX_KEYS_PER_STEP,
} from "@/constants/macros";
import { DEFAULT_DELAY, MAX_STEPS_PER_MACRO, MAX_KEYS_PER_STEP } from "@/constants/macros";
import { m } from "@localizations/messages.js";
interface ValidationErrors {
@ -61,7 +57,7 @@ export function MacroForm({
newErrors.name = m.macro_name_too_long();
}
const steps = (macro.steps || []);
const steps = macro.steps || [];
if (steps.length) {
const hasKeyOrModifier = steps.some(
@ -91,7 +87,9 @@ export function MacroForm({
await onSubmit(macro);
} catch (error) {
if (error instanceof Error) {
showTemporaryError(m.macro_save_failed_error({error: error.message || m.unknown_error()}));
showTemporaryError(
m.macro_save_failed_error({ error: error.message || m.unknown_error() }),
);
} else {
showTemporaryError(m.macro_save_failed());
}
@ -196,13 +194,13 @@ export function MacroForm({
<div>
<div className="flex items-center justify-between text-sm">
<div className="flex items-center gap-1">
<FieldLabel
label={m.macro_steps_label()}
description={m.macro_steps_description()}
/>
<FieldLabel label={m.macro_steps_label()} description={m.macro_steps_description()} />
</div>
<span className="text-slate-500 dark:text-slate-400">
{m.macro_step_count({ steps: macro.steps?.length || 0, max: MAX_STEPS_PER_MACRO })}
{m.macro_step_count({
steps: macro.steps?.length || 0,
max: MAX_STEPS_PER_MACRO,
})}
</span>
</div>
{errors.steps?.[0]?.keys && (
@ -220,10 +218,10 @@ export function MacroForm({
onDelete={
macro.steps && macro.steps.length > 1
? () => {
const newSteps = [...(macro.steps || [])];
newSteps.splice(stepIndex, 1);
setMacro(prev => ({ ...prev, steps: newSteps }));
}
const newSteps = [...(macro.steps || [])];
newSteps.splice(stepIndex, 1);
setMacro(prev => ({ ...prev, steps: newSteps }));
}
: undefined
}
onMoveUp={() => handleStepMove(stepIndex, "up")}
@ -231,9 +229,7 @@ export function MacroForm({
onKeySelect={option => handleKeySelect(stepIndex, option)}
onKeyQueryChange={query => handleKeyQueryChange(stepIndex, query)}
keyQuery={keyQueries[stepIndex] || ""}
onModifierChange={modifiers =>
handleModifierChange(stepIndex, modifiers)
}
onModifierChange={modifiers => handleModifierChange(stepIndex, modifiers)}
onDelayChange={delay => handleDelayChange(stepIndex, delay)}
isLastStep={stepIndex === (macro.steps?.length || 0) - 1}
keyboard={selectedKeyboard}
@ -248,7 +244,11 @@ export function MacroForm({
theme="light"
fullWidth
LeadingIcon={LuPlus}
text={m.macro_add_step({ maxed_out: isMaxStepsReached ? m.macro_max_steps_reached({ max: MAX_STEPS_PER_MACRO }) : "" })}
text={m.macro_add_step({
maxed_out: isMaxStepsReached
? m.macro_max_steps_reached({ max: MAX_STEPS_PER_MACRO })
: "",
})}
onClick={() => {
if (isMaxStepsReached) {
showTemporaryError(m.macro_max_steps_error({ max: MAX_STEPS_PER_MACRO }));
@ -257,10 +257,7 @@ export function MacroForm({
setMacro(prev => ({
...prev,
steps: [
...(prev.steps || []),
{ keys: [], modifiers: [], delay: DEFAULT_DELAY },
],
steps: [...(prev.steps || []), { keys: [], modifiers: [], delay: DEFAULT_DELAY }],
}));
setErrors({});
}}

View File

@ -72,7 +72,7 @@ const ensureArray = <T,>(arr: T[] | null | undefined): T[] => {
};
const keyDisplay = (keyDisplayMap: Record<string, string>, key: string): string => {
return keyDisplayMap[key] || key
return keyDisplayMap[key] || key;
};
export function MacroStepCard({
@ -113,9 +113,7 @@ export function MacroStepCard({
const filteredKeys = useMemo(() => {
const selectedKeys = ensureArray(step.keys);
const availableKeys = keyOptions.filter(
option => !selectedKeys.includes(option.value),
);
const availableKeys = keyOptions.filter(option => !selectedKeys.includes(option.value));
if (keyQuery === "") {
return availableKeys;
@ -186,9 +184,7 @@ export function MacroStepCard({
key={option.value}
size="XS"
theme={
ensureArray(step.modifiers).includes(option.value)
? "primary"
: "light"
ensureArray(step.modifiers).includes(option.value) ? "primary" : "light"
}
text={option.label.split(" ")[1] || option.label}
onClick={() => handleModifierToggle(option.value)}
@ -221,9 +217,7 @@ export function MacroStepCard({
className=""
theme="blank"
onClick={() => {
const newKeys = step.keys.filter(
(_, i) => i !== keyIndex,
);
const newKeys = step.keys.filter((_, i) => i !== keyIndex);
onKeySelect({ value: null, keys: newKeys });
}}
LeadingIcon={LuX}

View File

@ -35,10 +35,7 @@ interface MetricProps<T, K extends keyof T> {
* @param metrics - Expected to be ordered from oldest to newest.
* @param metricName - Name of the metric to create a chart array for.
*/
export function createChartArray<T, K extends keyof T>(
metrics: Map<number, T>,
metricName: K,
) {
export function createChartArray<T, K extends keyof T>(metrics: Map<number, T>, metricName: K) {
const result: { date: number; metric: number | null }[] = [];
const iter = metrics.entries();
let next = iter.next() as IteratorResult<[number, T]>;
@ -146,12 +143,7 @@ export function Metric<T, K extends keyof T>({
return (
<div className="space-y-2">
<MetricHeader
title={title}
description={description}
badge={badge}
badgeTheme={badgeTheme}
/>
<MetricHeader title={title} description={description} badge={badge} badgeTheme={badgeTheme} />
<GridCard>
<div

View File

@ -10,7 +10,7 @@ import {
YAxis,
} from "recharts";
import { getLocale } from '@localizations/runtime.js';
import { getLocale } from "@localizations/runtime.js";
import CustomTooltip, { CustomTooltipProps } from "@components/CustomTooltip";
export default function MetricsChart({

View File

@ -18,27 +18,25 @@ const Modal = React.memo(function Modal({
<Dialog open={open} onClose={onClose} className="relative z-20">
<DialogBackdrop
transition
className="fixed inset-0 bg-gray-500/75 transition-opacity data-closed:opacity-0 data-enter:duration-500 data-leave:duration-200 data-enter:ease-out data-leave:ease-in dark:bg-slate-900/90"
className="fixed inset-0 bg-gray-500/75 transition-opacity data-closed:opacity-0 data-enter:duration-500 data-enter:ease-out data-leave:duration-200 data-leave:ease-in dark:bg-slate-900/90"
/>
<div className="fixed inset-0 z-20 w-screen overflow-y-auto" style={{
scrollbarGutter: 'stable'
}}>
<div
className="fixed inset-0 z-20 w-screen overflow-y-auto"
style={{ scrollbarGutter: "stable" }}
>
{/* TODO: This doesn't work well with other-sessions */}
<div className="flex min-h-full items-end justify-center p-4 text-center md:items-baseline md:p-4">
<DialogPanel
transition
className={cx(
"pointer-events-none relative w-full md:my-8 md:mt-[10vh]!",
"transform transition-all data-closed:translate-y-8 data-closed:opacity-0 data-enter:duration-500 data-leave:duration-200 data-enter:ease-out data-leave:ease-in",
"transform transition-all data-closed:translate-y-8 data-closed:opacity-0 data-enter:duration-500 data-enter:ease-out data-leave:duration-200 data-leave:ease-in",
className,
)}
>
<div className="pointer-events-auto inline-block w-full text-left">
<div className="flex justify-center" onClick={onClose}>
<div
className="pointer-events-none w-full"
onClick={e => e.stopPropagation()}
>
<div className="pointer-events-none w-full" onClick={e => e.stopPropagation()}>
{children}
</div>
</div>

View File

@ -11,7 +11,7 @@ export function NestedSettingsGroup(props: NestedSettingsGroupProps) {
return (
<div
className={cx(
"space-y-4 border-l-2 border-slate-200 ml-2 pl-4 dark:border-slate-700",
"ml-2 space-y-4 border-l-2 border-slate-200 pl-4 dark:border-slate-700",
className,
)}
>
@ -19,4 +19,3 @@ export function NestedSettingsGroup(props: NestedSettingsGroupProps) {
</div>
);
}

View File

@ -9,7 +9,6 @@ import { JsonRpcResponse, useJsonRpc } from "@hooks/useJsonRpc";
import notifications from "@/notifications";
import { formatters } from "@/utils";
const TimeAgoLabel = ({ date }: { date: Date }) => {
const [timeAgo, setTimeAgo] = useState<string | undefined>(formatters.timeAgo(date));
useEffect(() => {
@ -19,11 +18,7 @@ const TimeAgoLabel = ({ date }: { date: Date }) => {
return () => clearInterval(interval);
}, [date]);
return (
<span className="text-sm text-slate-600 dark:text-slate-400 select-none">
{timeAgo}
</span>
);
return <span className="text-sm text-slate-600 select-none dark:text-slate-400">{timeAgo}</span>;
};
export default function PublicIPCard() {
@ -34,19 +29,23 @@ export default function PublicIPCard() {
send("getPublicIPAddresses", { refresh: true }, (resp: JsonRpcResponse) => {
setPublicIPs([]);
if ("error" in resp) {
notifications.error(m.public_ip_card_refresh_error({ error: resp.error.data || m.unknown_error() }));
notifications.error(
m.public_ip_card_refresh_error({ error: resp.error.data || m.unknown_error() }),
);
return;
}
const publicIPs = resp.result as PublicIP[];
// sort the public IPs by IP address
// IPv6 addresses are sorted after IPv4 addresses
setPublicIPs(publicIPs.sort(({ ip: aIp }, { ip: bIp }) => {
const aIsIPv6 = aIp.includes(":");
const bIsIPv6 = bIp.includes(":");
if (aIsIPv6 && !bIsIPv6) return 1;
if (!aIsIPv6 && bIsIPv6) return -1;
return aIp.localeCompare(bIp);
}));
setPublicIPs(
publicIPs.sort(({ ip: aIp }, { ip: bIp }) => {
const aIsIPv6 = aIp.includes(":");
const bIsIPv6 = bIp.includes(":");
if (aIsIPv6 && !bIsIPv6) return 1;
if (!aIsIPv6 && bIsIPv6) return -1;
return aIp.localeCompare(bIp);
}),
);
});
}, [send, setPublicIPs]);
@ -89,10 +88,11 @@ export default function PublicIPCard() {
<div className="flex gap-x-6 gap-y-2">
<div className="flex-1 space-y-2">
{publicIPs?.map(ip => (
<div key={ip.ip} className="flex justify-between border-slate-800/10 pt-2 dark:border-slate-300/20">
<span className="text-sm font-medium">
{ip.ip}
</span>
<div
key={ip.ip}
className="flex justify-between border-slate-800/10 pt-2 dark:border-slate-300/20"
>
<span className="text-sm font-medium">{ip.ip}</span>
{ip.last_updated && <TimeAgoLabel date={new Date(ip.last_updated)} />}
</div>
))}

View File

@ -82,10 +82,10 @@ export const SelectMenuBasic = React.forwardRef<HTMLSelectElement, SelectMenuPro
"invalid:ring-2 invalid:ring-red-600 invalid:ring-offset-2",
// Focus
"focus:outline-blue-600 focus:ring-2 focus:ring-blue-700 focus:ring-offset-2 dark:focus:outline-blue-500 dark:focus:ring-blue-500",
"focus:ring-2 focus:ring-blue-700 focus:ring-offset-2 focus:outline-blue-600 dark:focus:ring-blue-500 dark:focus:outline-blue-500",
// Disabled
"disabled:pointer-events-none disabled:select-none disabled:bg-slate-50 disabled:text-slate-500/80 dark:disabled:bg-slate-800 dark:disabled:text-slate-400/80",
"disabled:pointer-events-none disabled:bg-slate-50 disabled:text-slate-500/80 disabled:select-none dark:disabled:bg-slate-800 dark:disabled:text-slate-400/80",
)}
value={value}
id={id}

View File

@ -15,17 +15,14 @@ export function SettingsItem(props: SettingsItemProps) {
return (
<label
className={cx(
"flex select-none items-center justify-between gap-x-8 rounded",
className,
)}
className={cx("flex items-center justify-between gap-x-8 rounded select-none", className)}
>
<div className="space-y-0.5">
<div className="flex items-center gap-x-2">
<div className="flex items-center text-base font-semibold text-black dark:text-white">
{title}
{badge && (
<span className="ml-2 rounded-full bg-red-500 px-2 py-1 text-[10px] font-medium leading-none text-white dark:border dark:border-red-700 dark:bg-red-800 dark:text-red-50">
<span className="ml-2 rounded-full bg-red-500 px-2 py-1 text-[10px] leading-none font-medium text-white dark:border dark:border-red-700 dark:bg-red-800 dark:text-red-50">
{badge}
</span>
)}

View File

@ -1,8 +1,4 @@
export default function SettingsNestedSection({
children,
}: {
children: React.ReactNode;
}) {
export default function SettingsNestedSection({ children }: { children: React.ReactNode }) {
return (
<div className="ml-2 border-l border-slate-800/30 pl-4 dark:border-slate-300/30">
{children}

View File

@ -11,9 +11,9 @@ export default function SidebarHeader({
setSidebarView: (view: AvailableSidebarViews | null) => void;
}) {
return (
<div className="flex items-center justify-between border-b border-b-slate-800/20 bg-white px-4 py-1.5 font-semibold text-black dark:bg-slate-900 dark:border-b-slate-300/20">
<div className="flex items-center justify-between border-b border-b-slate-800/20 bg-white px-4 py-1.5 font-semibold text-black dark:border-b-slate-300/20 dark:bg-slate-900">
<div className="min-w-0" style={{ flex: 1 }}>
<h2 className="text-sm text-black truncate dark:text-white">{title}</h2>
<h2 className="truncate text-sm text-black dark:text-white">{title}</h2>
</div>
<Button
size="XS"

View File

@ -5,16 +5,19 @@ import LogoBlueIcon from "@assets/logo-blue.png";
import LogoWhiteIcon from "@assets/logo-white.svg";
import Container from "@components/Container";
interface Props { logoHref?: string; actionElement?: React.ReactNode }
interface Props {
logoHref?: string;
actionElement?: React.ReactNode;
}
export default function SimpleNavbar({ logoHref, actionElement }: Props) {
return (
<div>
<Container>
<div className="pb-4 my-4 border-b border-b-800/20 isolate dark:border-b-slate-300/20">
<div className="border-b-800/20 isolate my-4 border-b pb-4 dark:border-b-slate-300/20">
<div className="flex items-center justify-between">
<Link to={logoHref ?? "/"} className="hidden h-[26px] dark:inline-block">
<img src={LogoWhiteIcon} alt="" className="h-[26px] dark:block hidden" />
<img src={LogoWhiteIcon} alt="" className="hidden h-[26px] dark:block" />
<img src={LogoBlueIcon} alt="" className="h-[26px] dark:hidden" />
</Link>
<div>{actionElement}</div>

View File

@ -38,12 +38,13 @@ export default function StaticIpv4Card() {
}, [ipv4StaticAddress, setValue]);
const ipv4Validation = (value: string) => {
if (!validator.isIP(value, 4)) return m.network_ipv4_invalid()
if (!validator.isIP(value, 4)) return m.network_ipv4_invalid();
return true;
};
const validateIsIPOrCIDR4 = (value: string) => {
if (!validator.isIP(value) && !validator.isIPRange(value, 4)) return m.network_ipv4_invalid_cidr();
if (!validator.isIP(value) && !validator.isIPRange(value, 4))
return m.network_ipv4_invalid_cidr();
return true;
};
@ -55,27 +56,35 @@ export default function StaticIpv4Card() {
{m.network_static_ipv4_header()}
</h3>
<div className={cx("grid grid-cols-1 gap-4", hideSubnetMask ? "md:grid-cols-1" : "md:grid-cols-2")}>
<div
className={cx(
"grid grid-cols-1 gap-4",
hideSubnetMask ? "md:grid-cols-1" : "md:grid-cols-2",
)}
>
<InputFieldWithLabel
label={m.network_ipv4_address()}
type="text"
size="SM"
placeholder="192.168.1.100"
{
...register("ipv4_static.address", {
validate: (value: string | undefined) => validateIsIPOrCIDR4(value ?? "")
{...register("ipv4_static.address", {
validate: (value: string | undefined) => validateIsIPOrCIDR4(value ?? ""),
})}
error={formState.errors.ipv4_static?.address?.message}
/>
{!hideSubnetMask && <InputFieldWithLabel
label={m.network_ipv4_netmask()}
type="text"
size="SM"
placeholder="255.255.255.0"
{...register("ipv4_static.netmask", { validate: (value: string | undefined) => ipv4Validation(value ?? "") })}
error={formState.errors.ipv4_static?.netmask?.message}
/>}
{!hideSubnetMask && (
<InputFieldWithLabel
label={m.network_ipv4_netmask()}
type="text"
size="SM"
placeholder="255.255.255.0"
{...register("ipv4_static.netmask", {
validate: (value: string | undefined) => ipv4Validation(value ?? ""),
})}
error={formState.errors.ipv4_static?.netmask?.message}
/>
)}
</div>
<InputFieldWithLabel
@ -83,7 +92,9 @@ export default function StaticIpv4Card() {
type="text"
size="SM"
placeholder="192.168.1.1"
{...register("ipv4_static.gateway", { validate: (value: string | undefined) => ipv4Validation(value ?? "") })}
{...register("ipv4_static.gateway", {
validate: (value: string | undefined) => ipv4Validation(value ?? ""),
})}
error={formState.errors.ipv4_static?.gateway?.message}
/>
@ -99,10 +110,9 @@ export default function StaticIpv4Card() {
type="text"
size="SM"
placeholder="1.1.1.1"
{...register(
`ipv4_static.dns.${index}`,
{ validate: (value: string | undefined) => ipv4Validation(value ?? "") }
)}
{...register(`ipv4_static.dns.${index}`, {
validate: (value: string | undefined) => ipv4Validation(value ?? ""),
})}
error={formState.errors.ipv4_static?.dns?.[index]?.message}
/>
</div>

View File

@ -39,7 +39,7 @@ export default function StaticIpv6Card() {
};
const ipv6Validation = (value: string) => {
if (!validator.isIP(value, 6)) return m.network_ipv6_invalid()
if (!validator.isIP(value, 6)) return m.network_ipv6_invalid();
return true;
};
@ -56,7 +56,9 @@ export default function StaticIpv6Card() {
type="text"
size="SM"
placeholder="2001:db8::1/64"
{...register("ipv6_static.prefix", { validate: (value: string | undefined) => cidrValidation(value ?? "") })}
{...register("ipv6_static.prefix", {
validate: (value: string | undefined) => cidrValidation(value ?? ""),
})}
error={formState.errors.ipv6_static?.prefix?.message}
/>
@ -65,7 +67,9 @@ export default function StaticIpv6Card() {
type="text"
size="SM"
placeholder="2001:db8::1"
{...register("ipv6_static.gateway", { validate: (value: string | undefined) => ipv6Validation(value ?? "") })}
{...register("ipv6_static.gateway", {
validate: (value: string | undefined) => ipv6Validation(value ?? ""),
})}
error={formState.errors.ipv6_static?.gateway?.message}
/>
@ -81,7 +85,9 @@ export default function StaticIpv6Card() {
type="text"
size="SM"
placeholder="2001:4860:4860::8888"
{...register(`ipv6_static.dns.${index}`, { validate: (value: string | undefined) => ipv6Validation(value ?? "") })}
{...register(`ipv6_static.dns.${index}`, {
validate: (value: string | undefined) => ipv6Validation(value ?? ""),
})}
error={formState.errors.ipv6_static?.dns?.[index]?.message}
/>
</div>

View File

@ -18,7 +18,7 @@ export default function StatusCard({
statusIndicatorClassName,
}: Props) {
return (
<div className="flex items-center gap-x-3 rounded-md border bg-white dark:border-slate-600 dark:bg-slate-800 dark:text-white border-slate-800/20 px-2 py-1.5">
<div className="flex items-center gap-x-3 rounded-md border border-slate-800/20 bg-white px-2 py-1.5 dark:border-slate-600 dark:bg-slate-800 dark:text-white">
{Icon ? (
<span>
<Icon className={cx(iconClassName, "shrink-0")} />
@ -26,16 +26,11 @@ export default function StatusCard({
) : null}
<div className="space-y-1">
<div className="text-xs font-semibold leading-none transition text-ellipsis">
{title}
</div>
<div className="text-xs leading-none font-semibold text-ellipsis transition">{title}</div>
<div className="text-xs leading-none">
<div className="flex items-center gap-x-1">
<div
className={cx(
"h-2 w-2 rounded-full border transition",
statusIndicatorClassName,
)}
className={cx("h-2 w-2 rounded-full border transition", statusIndicatorClassName)}
/>
<span className={cx("transition")}>{status}</span>
</div>

View File

@ -24,7 +24,7 @@ const variants = cva({
export default function StepCounter({ nSteps, currStepIdx, size = "MD" }: Props) {
const textStyle = variants({ size });
return (
<Card className="inline-flex! w-auto select-none items-center justify-center gap-x-2 rounded-lg p-1">
<Card className="inline-flex! w-auto items-center justify-center gap-x-2 rounded-lg p-1 select-none">
{[...Array(nSteps).keys()].map(i => {
if (i < currStepIdx) {
return (

View File

@ -54,7 +54,7 @@ const TERMINAL_CONFIG = {
// Add these configurations:
cursorStyle: "block",
rendererType: "canvas", // Ensure we're using the canvas renderer
unicode: { activeVersion: "11" }
unicode: { activeVersion: "11" },
} as const;
function Terminal({
@ -162,10 +162,7 @@ function Terminal({
}, [instance]);
return (
<div
onKeyDown={e => e.stopPropagation()}
onKeyUp={e => e.stopPropagation()}
>
<div onKeyDown={e => e.stopPropagation()} onKeyUp={e => e.stopPropagation()}>
<div>
<div
className={cx(
@ -184,7 +181,7 @@ function Terminal({
>
<div className="h-[500px] w-full bg-[#0f172a]">
<div className="flex items-center justify-center border-y border-y-slate-800/30 bg-white px-2 py-1 dark:border-y-slate-300/20 dark:bg-slate-800">
<h2 className="select-none self-center font-sans text-[12px] text-slate-700 dark:text-slate-300">
<h2 className="self-center font-sans text-[12px] text-slate-700 select-none dark:text-slate-300">
{title}
</h2>
<div className="absolute right-2">

View File

@ -17,7 +17,7 @@ const TextArea = React.forwardRef<HTMLTextAreaElement, TextAreaProps>(
className={cx(
"relative w-full",
"invalid-within::ring-2 invalid-within::ring-red-600 invalid-within::ring-offset-2",
"focus-within:border-slate-300 focus-within:outline-hidden focus-within:ring-1 focus-within:ring-blue-700 dark:focus-within:border-slate-600",
"focus-within:border-slate-300 focus-within:ring-1 focus-within:ring-blue-700 focus-within:outline-hidden dark:focus-within:border-slate-600",
)}
>
<textarea
@ -25,7 +25,7 @@ const TextArea = React.forwardRef<HTMLTextAreaElement, TextAreaProps>(
{...props}
id="asd"
className={clsx(
"block w-full rounded-sm border-transparent bg-transparent text-black placeholder:text-slate-300 focus:ring-0 disabled:pointer-events-none disabled:select-none disabled:bg-slate-50 disabled:text-slate-300 dark:text-white dark:placeholder:text-slate-500 dark:disabled:bg-slate-800 sm:text-sm",
"block w-full rounded-sm border-transparent bg-transparent text-black placeholder:text-slate-300 focus:ring-0 disabled:pointer-events-none disabled:bg-slate-50 disabled:text-slate-300 disabled:select-none sm:text-sm dark:text-white dark:placeholder:text-slate-500 dark:disabled:bg-slate-800",
props.className,
)}
/>
@ -41,17 +41,16 @@ type TextAreaWithLabelProps = {
error?: string | null;
} & React.ComponentProps<typeof TextArea>;
export const TextAreaWithLabel = React.forwardRef<
HTMLTextAreaElement,
TextAreaWithLabelProps
>(function TextAreaWithLabel({ label, error, id, description, ...props }, ref) {
return (
<div className="space-y-1">
<FieldLabel label={label} id={id} description={description} />
<TextArea ref={ref} {...props} />
{error && <FieldError error={error} />}
</div>
);
});
export const TextAreaWithLabel = React.forwardRef<HTMLTextAreaElement, TextAreaWithLabelProps>(
function TextAreaWithLabel({ label, error, id, description, ...props }, ref) {
return (
<div className="space-y-1">
<FieldLabel label={label} id={id} description={description} />
<TextArea ref={ref} {...props} />
{error && <FieldError error={error} />}
</div>
);
},
);
export default TextArea;

View File

@ -64,7 +64,6 @@ export default function USBStateStatus({
state: USBStates;
peerConnectionState?: RTCPeerConnectionState | null;
}) {
const props = StatusCardProps[state];
if (!props) {
console.warn("Unsupported USB state: ", state);
@ -73,11 +72,7 @@ export default function USBStateStatus({
// If the peer connection is not connected, show the USB cable as disconnected
if (peerConnectionState !== "connected") {
const {
icon: Icon,
iconClassName,
statusIndicatorClassName,
} = StatusCardProps["not attached"];
const { icon: Icon, iconClassName, statusIndicatorClassName } = StatusCardProps["not attached"];
return (
<StatusCard
@ -90,7 +85,5 @@ export default function USBStateStatus({
);
}
return (
<StatusCard title={m.usb()} status={USBStateMap[state]} {...StatusCardProps[state]} />
);
return <StatusCard title={m.usb()} status={USBStateMap[state]} {...StatusCardProps[state]} />;
}

View File

@ -9,20 +9,18 @@ export default function UpdateInProgressStatusCard() {
const { navigateTo } = useDeviceUiNavigation();
return (
<div className="w-full select-none opacity-100 transition-all duration-300 ease-in-out">
<div className="w-full opacity-100 transition-all duration-300 ease-in-out select-none">
<GridCard cardClassName="shadow-xl!">
<div className="flex items-center justify-between gap-x-3 px-2.5 py-2.5 text-black dark:text-white">
<div className="flex items-center gap-x-3">
<LoadingSpinner className={cx("h-5 w-5", "shrink-0 text-blue-700")} />
<div className="space-y-1">
<div className="text-ellipsis text-sm font-semibold leading-none transition">
<div className="text-sm leading-none font-semibold text-ellipsis transition">
{m.update_in_progress()}
</div>
<div className="text-sm leading-none">
<div className="flex items-center gap-x-1">
<span className={cx("transition")}>
{m.updating_leave_device_on()}
</span>
<span className={cx("transition")}>{m.updating_leave_device_on()}</span>
</div>
</div>
</div>

View File

@ -1,4 +1,3 @@
import { CheckCircleIcon } from "@heroicons/react/24/solid"; // adjust import if you use a different icon set
import LoadingSpinner from "@components/LoadingSpinner"; // adjust import path if needed
@ -11,13 +10,7 @@ export interface UpdatePart {
complete: boolean;
}
export default function UpdatingStatusCard({
label,
part,
}: {
label: string;
part: UpdatePart;
}) {
export default function UpdatingStatusCard({ label, part }: { label: string; part: UpdatePart }) {
return (
<div className="space-y-2">
<div className="flex items-center justify-between">
@ -34,7 +27,7 @@ export default function UpdatingStatusCard({
aria-valuemin={0}
aria-valuemax={100}
aria-valuenow={Math.round(part.progress)}
aria-label={m.general_update_status_progress({part: label})}
aria-label={m.general_update_status_progress({ part: label })}
>
<div
className="h-2.5 rounded-full bg-blue-700 transition-all duration-500 ease-linear dark:bg-blue-500"

View File

@ -64,8 +64,7 @@ export function UsbDeviceSetting() {
const { send } = useJsonRpc();
const [loading, setLoading] = useState(false);
const [usbDeviceConfig, setUsbDeviceConfig] =
useState<UsbDeviceConfig>(defaultUsbDeviceConfig);
const [usbDeviceConfig, setUsbDeviceConfig] = useState<UsbDeviceConfig>(defaultUsbDeviceConfig);
const [selectedPreset, setSelectedPreset] = useState<string>("default");
const syncUsbDeviceConfig = useCallback(() => {
@ -134,9 +133,7 @@ export function UsbDeviceSetting() {
setSelectedPreset(newPreset);
if (newPreset !== "custom") {
const presetConfig = usbPresets.find(
preset => preset.value === newPreset,
)?.config;
const presetConfig = usbPresets.find(preset => preset.value === newPreset)?.config;
if (presetConfig) {
handleUsbConfigChange(presetConfig);
@ -176,10 +173,13 @@ export function UsbDeviceSetting() {
</SettingsItem>
{selectedPreset === "custom" && (
<div className="ml-2 border-l border-slate-800/10 pl-4 dark:border-slate-300/20 ">
<div className="ml-2 border-l border-slate-800/10 pl-4 dark:border-slate-300/20">
<div className="space-y-4">
<div className="space-y-4">
<SettingsItem title={m.usb_device_enable_keyboard_title()} description={m.usb_device_enable_keyboard_description()}>
<SettingsItem
title={m.usb_device_enable_keyboard_title()}
description={m.usb_device_enable_keyboard_description()}
>
<Checkbox
checked={usbDeviceConfig.keyboard}
onChange={onUsbConfigItemChange("keyboard")}

View File

@ -31,7 +31,6 @@ export interface USBConfig {
product: string;
}
const usbConfigs = [
{
label: m.usb_config_default(),
@ -127,7 +126,10 @@ export function UsbInfoSetting() {
await sleep(2000);
setLoading(false);
notifications.success(
m.usb_config_set_success({ manufacturer: usbConfig.manufacturer, product: usbConfig.product }),
m.usb_config_set_success({
manufacturer: usbConfig.manufacturer,
product: usbConfig.product,
}),
);
syncUsbConfigProduct();
@ -174,13 +176,11 @@ export function UsbInfoSetting() {
/>
</SettingsItem>
{usbConfigProduct === "custom" && (
<div className="ml-2 space-y-4 border-l border-slate-800/10 pl-4 dark:border-slate-300/20 ">
<div className="ml-2 space-y-4 border-l border-slate-800/10 pl-4 dark:border-slate-300/20">
<USBConfigDialog
loading={loading}
onSetUsbConfig={usbConfig => handleUsbConfigChange(usbConfig)}
onRestoreToDefault={() =>
handleUsbConfigChange(usbConfigData[usbConfigs[0].value])
}
onRestoreToDefault={() => handleUsbConfigChange(usbConfigData[usbConfigs[0].value])}
/>
</div>
)}

View File

@ -15,7 +15,6 @@ import LogoWhite from "@/assets/logo-white.svg";
import { isOnDevice } from "@/main";
import { sleep } from "@/utils";
interface OverlayContentProps {
readonly children: React.ReactNode;
}
@ -86,9 +85,7 @@ export function LoadingConnectionOverlay({ show, text }: LoadingConnectionOverla
<div className="animate flex h-12 w-12 items-center justify-center">
<LoadingSpinner className="h-8 w-8 text-blue-800 dark:text-blue-200" />
</div>
<p className="text-center text-sm text-slate-700 dark:text-slate-300">
{text}
</p>
<p className="text-center text-sm text-slate-700 dark:text-slate-300">{text}</p>
</div>
</OverlayContent>
</motion.div>
@ -125,7 +122,9 @@ export function ConnectionFailedOverlay({
<div className="text-left text-sm text-slate-700 dark:text-slate-300">
<div className="space-y-4">
<div className="space-y-2 text-black dark:text-white">
<h2 className="text-xl font-bold">{m.video_overlay_connection_issue_title()}</h2>
<h2 className="text-xl font-bold">
{m.video_overlay_connection_issue_title()}
</h2>
<ul className="list-disc space-y-2 pl-4 text-left">
<li>{m.video_overlay_conn_verify_power()}</li>
<li>{m.video_overlay_conn_check_cables()}</li>
@ -163,9 +162,7 @@ interface PeerConnectionDisconnectedOverlay {
readonly show: boolean;
}
export function PeerConnectionDisconnectedOverlay({
show,
}: PeerConnectionDisconnectedOverlay) {
export function PeerConnectionDisconnectedOverlay({ show }: PeerConnectionDisconnectedOverlay) {
return (
<AnimatePresence>
{show && (
@ -185,7 +182,9 @@ export function PeerConnectionDisconnectedOverlay({
<div className="text-left text-sm text-slate-700 dark:text-slate-300">
<div className="space-y-4">
<div className="space-y-2 text-black dark:text-white">
<h2 className="text-xl font-bold">{m.video_overlay_connection_issue_title()}</h2>
<h2 className="text-xl font-bold">
{m.video_overlay_connection_issue_title()}
</h2>
<ul className="list-disc space-y-2 pl-4 text-left">
<li>{m.video_overlay_conn_verify_power()}</li>
<li>{m.video_overlay_conn_check_cables()}</li>
@ -405,7 +404,7 @@ export function RebootingOverlay({ show, postRebootAction }: RebootingOverlayPro
// Check if we've already seen the connection drop (confirms reboot actually started)
const [hasSeenDisconnect, setHasSeenDisconnect] = useState(
['disconnected', 'closed', 'failed'].includes(peerConnectionState ?? '')
["disconnected", "closed", "failed"].includes(peerConnectionState ?? ""),
);
// Track if we've timed out
@ -416,8 +415,8 @@ export function RebootingOverlay({ show, postRebootAction }: RebootingOverlayPro
if (!show) return;
if (hasSeenDisconnect) return;
if (['disconnected', 'closed', 'failed'].includes(peerConnectionState ?? '')) {
console.log('hasSeenDisconnect', hasSeenDisconnect);
if (["disconnected", "closed", "failed"].includes(peerConnectionState ?? "")) {
console.log("hasSeenDisconnect", hasSeenDisconnect);
setHasSeenDisconnect(true);
}
}, [show, peerConnectionState, hasSeenDisconnect]);
@ -438,7 +437,6 @@ export function RebootingOverlay({ show, postRebootAction }: RebootingOverlayPro
};
}, [show]);
// Poll suggested IP in device mode to detect when it's available
const abortControllerRef = useRef<AbortController | null>(null);
const isFetchingRef = useRef(false);
@ -465,17 +463,16 @@ export function RebootingOverlay({ show, postRebootAction }: RebootingOverlayPro
abortControllerRef.current = abortController;
isFetchingRef.current = true;
console.log('Checking post-reboot health endpoint:', postRebootAction.healthCheck);
console.log("Checking post-reboot health endpoint:", postRebootAction.healthCheck);
const timeoutId = window.setTimeout(() => abortController.abort(), 2000);
try {
const response = await fetch(
postRebootAction.healthCheck,
{ signal: abortController.signal, }
);
const response = await fetch(postRebootAction.healthCheck, {
signal: abortController.signal,
});
if (response.ok) {
// Device is available, redirect to the specified URL
console.log('Device is available, redirecting to:', postRebootAction.redirectTo);
console.log("Device is available, redirecting to:", postRebootAction.redirectTo);
// URL constructor handles all cases elegantly:
// - Absolute paths: resolved against current origin
@ -492,8 +489,8 @@ export function RebootingOverlay({ show, postRebootAction }: RebootingOverlayPro
} catch (err) {
// Ignore errors - they're expected while device is rebooting
// Only log if it's not an abort error
if (err instanceof Error && err.name !== 'AbortError') {
console.debug('Error checking post-reboot health:', err);
if (err instanceof Error && err.name !== "AbortError") {
console.debug("Error checking post-reboot health:", err);
}
} finally {
clearTimeout(timeoutId);
@ -531,8 +528,7 @@ export function RebootingOverlay({ show, postRebootAction }: RebootingOverlayPro
}}
>
<OverlayContent>
<div className="flex flex-col items-start gap-y-4 w-full max-w-md">
<div className="flex w-full max-w-md flex-col items-start gap-y-4">
<div className="h-[24px]">
<img src={LogoBlue} alt="" className="h-full dark:hidden" />
<img src={LogoWhite} alt="" className="hidden h-full dark:block" />
@ -540,17 +536,16 @@ export function RebootingOverlay({ show, postRebootAction }: RebootingOverlayPro
<div className="text-left text-sm text-slate-700 dark:text-slate-300">
<div className="space-y-4">
<div className="space-y-2 text-black dark:text-white">
<h2 className="text-xl font-bold">{hasTimedOut ? m.video_overlay_reboot_unable_to_reconnect() : m.video_overlay_reboot_device_is_rebooting()}</h2>
<h2 className="text-xl font-bold">
{hasTimedOut
? m.video_overlay_reboot_unable_to_reconnect()
: m.video_overlay_reboot_device_is_rebooting()}
</h2>
<p className="text-sm text-slate-700 dark:text-slate-300">
{hasTimedOut ? (
<>
{m.video_overlay_reboot_different_ip_message()}
</>
<>{m.video_overlay_reboot_different_ip_message()}</>
) : (
<>
{m.video_overlay_reboot_please_wait_message()}
</>
<>{m.video_overlay_reboot_please_wait_message()}</>
)}
</p>
</div>

View File

@ -21,8 +21,7 @@ export const DetachIcon = ({ className }: { className?: string }) => {
function KeyboardWrapper() {
const keyboardRef = useRef<HTMLDivElement>(null);
const { isAttachedVirtualKeyboardVisible, setAttachedVirtualKeyboardVisibility } =
useUiStore();
const { isAttachedVirtualKeyboardVisible, setAttachedVirtualKeyboardVisibility } = useUiStore();
const { keyboardLedState, keysDownState, isVirtualKeyboardEnabled, setVirtualKeyboardEnabled } =
useHidStore();
const { handleKeyPress, executeMacro } = useKeyboard();
@ -159,9 +158,7 @@ function KeyboardWrapper() {
}
if (key === "AltMetaEscape") {
await executeMacro([
{ keys: ["Escape"], modifiers: ["AltLeft", "MetaLeft"], delay: 100 },
]);
await executeMacro([{ keys: ["Escape"], modifiers: ["AltLeft", "MetaLeft"], delay: 100 }]);
return;
}
@ -258,7 +255,7 @@ function KeyboardWrapper() {
{m.virtual_keyboard_header()}
</h2>
<div className="absolute right-2 flex items-center gap-x-2">
<div className="hidden md:flex gap-x-2 items-center">
<div className="hidden items-center gap-x-2 md:flex">
<LinkButton
size="XS"
to="settings/keyboard"

View File

@ -4,11 +4,7 @@ import { useResizeObserver } from "usehooks-ts";
import { cx } from "@/cva.config";
import useKeyboard from "@hooks/useKeyboard";
import useMouse from "@hooks/useMouse";
import {
useRTCStore,
useSettingsStore,
useVideoStore,
} from "@hooks/stores";
import { useRTCStore, useSettingsStore, useVideoStore } from "@hooks/stores";
import VirtualKeyboard from "@components/VirtualKeyboard";
import Actionbar from "@components/ActionBar";
import MacroBar from "@components/MacroBar";
@ -32,7 +28,8 @@ export default function WebRTCVideo({ hasConnectionIssues }: { hasConnectionIssu
const [isPointerLockActive, setIsPointerLockActive] = useState(false);
const [isKeyboardLockActive, setIsKeyboardLockActive] = useState(false);
const isPointerLockPossible = window.location.protocol === "https:" || window.location.hostname === "localhost";
const isPointerLockPossible =
window.location.protocol === "https:" || window.location.hostname === "localhost";
// Store hooks
const settings = useSettingsStore();
@ -71,7 +68,7 @@ export default function WebRTCVideo({ hasConnectionIssues }: { hasConnectionIssu
setVideoClientSize(width || 0, height || 0);
setVideoSize(videoElm.current.videoWidth, videoElm.current.videoHeight);
},
[setVideoClientSize, setVideoSize]
[setVideoClientSize, setVideoSize],
);
useResizeObserver({
@ -119,9 +116,7 @@ export default function WebRTCVideo({ hasConnectionIssues }: { hasConnectionIssu
}, []);
const requestPointerLock = useCallback(async () => {
if (!isPointerLockPossible
|| videoElm.current === null
|| document.pointerLockElement) return;
if (!isPointerLockPossible || videoElm.current === null || document.pointerLockElement) return;
const isPointerLockGranted = await checkNavigatorPermissions("pointer-lock");
@ -151,7 +146,11 @@ export default function WebRTCVideo({ hasConnectionIssues }: { hasConnectionIssu
}, [checkNavigatorPermissions, setIsKeyboardLockActive]);
const releaseKeyboardLock = useCallback(async () => {
if (fullscreenContainerRef.current === null || document.fullscreenElement !== fullscreenContainerRef.current) return;
if (
fullscreenContainerRef.current === null ||
document.fullscreenElement !== fullscreenContainerRef.current
)
return;
if (navigator && "keyboard" in navigator) {
try {
@ -216,24 +215,19 @@ export default function WebRTCVideo({ hasConnectionIssues }: { hasConnectionIssu
}, [releaseKeyboardLock]);
const absMouseMoveHandler = useMemo(
() => getAbsMouseMoveHandler({
videoClientWidth,
videoClientHeight,
videoWidth,
videoHeight,
}),
() =>
getAbsMouseMoveHandler({
videoClientWidth,
videoClientHeight,
videoWidth,
videoHeight,
}),
[getAbsMouseMoveHandler, videoClientWidth, videoClientHeight, videoWidth, videoHeight],
);
const relMouseMoveHandler = useMemo(
() => getRelMouseMoveHandler(),
[getRelMouseMoveHandler],
);
const relMouseMoveHandler = useMemo(() => getRelMouseMoveHandler(), [getRelMouseMoveHandler]);
const mouseWheelHandler = useMemo(
() => getMouseWheelHandler(),
[getMouseWheelHandler],
);
const mouseWheelHandler = useMemo(() => getMouseWheelHandler(), [getMouseWheelHandler]);
function getAdjustedKeyCode(e: KeyboardEvent) {
const key = e.key;
@ -263,7 +257,7 @@ export default function WebRTCVideo({ hasConnectionIssues }: { hasConnectionIssu
// event, so we need to clear the keys after a short delay
// https://bugs.chromium.org/p/chromium/issues/detail?id=28089
// https://bugzilla.mozilla.org/show_bug.cgi?id=1299553
if (e.metaKey && hidKey < 0xE0) {
if (e.metaKey && hidKey < 0xe0) {
setTimeout(() => {
console.debug(`Forcing the meta key release of associated key: ${hidKey}`);
handleKeyPress(hidKey, false);
@ -403,7 +397,7 @@ export default function WebRTCVideo({ hasConnectionIssues }: { hasConnectionIssu
const videoElmRefValue = videoElm.current;
if (!videoElmRefValue) return;
const isRelativeMouseMode = (settings.mouseMode === "relative");
const isRelativeMouseMode = settings.mouseMode === "relative";
const mouseHandler = isRelativeMouseMode ? relMouseMoveHandler : absMouseMoveHandler;
const abortController = new AbortController();
@ -418,7 +412,8 @@ export default function WebRTCVideo({ hasConnectionIssues }: { hasConnectionIssu
});
if (isRelativeMouseMode) {
videoElmRefValue.addEventListener("click",
videoElmRefValue.addEventListener(
"click",
() => {
if (isPointerLockPossible && !isPointerLockActive && !document.pointerLockElement) {
requestPointerLock();
@ -469,7 +464,15 @@ export default function WebRTCVideo({ hasConnectionIssues }: { hasConnectionIssu
if (!isPlaying) return false;
if (videoHeight === 0 || videoWidth === 0) return false;
return true;
}, [isPlaying, isPointerLockActive, isPointerLockPossible, isVideoLoading, settings.mouseMode, videoHeight, videoWidth]);
}, [
isPlaying,
isPointerLockActive,
isPointerLockPossible,
isVideoLoading,
settings.mouseMode,
videoHeight,
videoWidth,
]);
// Conditionally set the filter style so we don't fallback to software rendering if these values are default of 1.0
const videoStyle = useMemo(() => {
@ -477,19 +480,15 @@ export default function WebRTCVideo({ hasConnectionIssues }: { hasConnectionIssu
return isDefault
? {} // No filter if all settings are default (1.0)
: {
filter: `saturate(${videoSaturation}) brightness(${videoBrightness}) contrast(${videoContrast})`,
};
filter: `saturate(${videoSaturation}) brightness(${videoBrightness}) contrast(${videoContrast})`,
};
}, [videoSaturation, videoBrightness, videoContrast]);
return (
<div className="grid h-full w-full grid-rows-(--grid-layout)">
<div className="flex min-h-[39.5px] flex-col">
<div className="flex flex-col">
<fieldset
disabled={peerConnection?.connectionState !== "connected"}
className="contents"
>
<fieldset disabled={peerConnection?.connectionState !== "connected"} className="contents">
<Actionbar requestFullscreen={requestFullscreen} />
<MacroBar />
</fieldset>
@ -546,7 +545,7 @@ export default function WebRTCVideo({ hasConnectionIssues }: { hasConnectionIssu
{peerConnection?.connectionState == "connected" && !hasConnectionIssues && (
<div
style={{ animationDuration: "500ms" }}
className="animate-slideUpFade pointer-events-none absolute inset-0 flex items-center justify-center"
className="pointer-events-none absolute inset-0 flex animate-slideUpFade items-center justify-center"
>
<div className="relative h-full w-full rounded-md">
<LoadingVideoOverlay show={isVideoLoading} />

View File

@ -18,12 +18,12 @@ interface ATXState {
export function ATXPowerControl() {
const [isPowerPressed, setIsPowerPressed] = useState(false);
const [powerPressTimer, setPowerPressTimer] = useState<ReturnType<
typeof setTimeout
> | null>(null);
const [powerPressTimer, setPowerPressTimer] = useState<ReturnType<typeof setTimeout> | null>(
null,
);
const [atxState, setAtxState] = useState<ATXState | null>(null);
const { send } = useJsonRpc(function onRequest(resp) {
const { send } = useJsonRpc(function onRequest(resp) {
if (resp.method === "atxState") {
setAtxState(resp.params as ATXState);
}
@ -33,7 +33,9 @@ export function ATXPowerControl() {
useEffect(() => {
send("getATXState", {}, (resp: JsonRpcResponse) => {
if ("error" in resp) {
notifications.error(m.atx_power_control_get_state_error({ error: resp.error.data || m.unknown_error() }));
notifications.error(
m.atx_power_control_get_state_error({ error: resp.error.data || m.unknown_error() }),
);
return;
}
setAtxState(resp.result as ATXState);
@ -54,7 +56,12 @@ export function ATXPowerControl() {
console.log("Sending long press ATX power action");
send("setATXPowerAction", { action: "power-long" }, (resp: JsonRpcResponse) => {
if ("error" in resp) {
notifications.error(m.atx_power_control_send_action_error({ action: m.atx_power_control_long_power_button(), error: resp.error.data || m.unknown_error() }));
notifications.error(
m.atx_power_control_send_action_error({
action: m.atx_power_control_long_power_button(),
error: resp.error.data || m.unknown_error(),
}),
);
}
setIsPowerPressed(false);
});
@ -73,7 +80,12 @@ export function ATXPowerControl() {
console.log("Sending short press ATX power action");
send("setATXPowerAction", { action: "power-short" }, (resp: JsonRpcResponse) => {
if ("error" in resp) {
notifications.error(m.atx_power_control_send_action_error({ action: m.atx_power_control_short_power_button(), error: resp.error.data || m.unknown_error() }));
notifications.error(
m.atx_power_control_send_action_error({
action: m.atx_power_control_short_power_button(),
error: resp.error.data || m.unknown_error(),
}),
);
}
});
}
@ -123,7 +135,12 @@ export function ATXPowerControl() {
onClick={() => {
send("setATXPowerAction", { action: "reset" }, (resp: JsonRpcResponse) => {
if ("error" in resp) {
notifications.error(m.atx_power_control_send_action_error({ action: m.atx_power_control_reset_button(), error: resp.error.data || m.unknown_error() }));
notifications.error(
m.atx_power_control_send_action_error({
action: m.atx_power_control_reset_button(),
error: resp.error.data || m.unknown_error(),
}),
);
return;
}
});
@ -149,9 +166,7 @@ export function ATXPowerControl() {
<span className="text-sm text-slate-600 dark:text-slate-400">
<LuHardDrive
strokeWidth={3}
className={`mr-1 inline ${
atxState?.hdd ? "text-blue-400" : "text-slate-300"
}`}
className={`mr-1 inline ${atxState?.hdd ? "text-blue-400" : "text-slate-300"}`}
/>
{m.atx_power_control_hdd_led()}
</span>

View File

@ -8,7 +8,7 @@ import Card from "@components/Card";
import { SettingsPageHeader } from "@components/SettingsPageheader";
import FieldLabel from "@components/FieldLabel";
import LoadingSpinner from "@components/LoadingSpinner";
import {SelectMenuBasic} from "@components/SelectMenuBasic";
import { SelectMenuBasic } from "@components/SelectMenuBasic";
import notifications from "@/notifications";
interface DCPowerState {
@ -26,7 +26,9 @@ export function DCPowerControl() {
const getDCPowerState = useCallback(() => {
send("getDCPowerState", {}, (resp: JsonRpcResponse) => {
if ("error" in resp) {
notifications.error(m.dc_power_control_get_state_error({ error: resp.error.data || m.unknown_error() }));
notifications.error(
m.dc_power_control_get_state_error({ error: resp.error.data || m.unknown_error() }),
);
return;
}
setPowerState(resp.result as DCPowerState);
@ -36,7 +38,12 @@ export function DCPowerControl() {
const handlePowerToggle = (enabled: boolean) => {
send("setDCPowerState", { enabled }, (resp: JsonRpcResponse) => {
if ("error" in resp) {
notifications.error(m.dc_power_control_set_power_state_error({ enabled: enabled, error: resp.error.data || m.unknown_error() }));
notifications.error(
m.dc_power_control_set_power_state_error({
enabled: enabled,
error: resp.error.data || m.unknown_error(),
}),
);
return;
}
getDCPowerState(); // Refresh state after change
@ -46,7 +53,12 @@ export function DCPowerControl() {
// const state = powerState?.restoreState === 0 ? 1 : powerState?.restoreState === 1 ? 2 : 0;
send("setDCRestoreState", { state }, (resp: JsonRpcResponse) => {
if ("error" in resp) {
notifications.error(m.dc_power_control_set_restore_state_error({ state: state, error: resp.error.data || m.unknown_error() }));
notifications.error(
m.dc_power_control_set_restore_state_error({
state: state,
error: resp.error.data || m.unknown_error(),
}),
);
return;
}
getDCPowerState(); // Refresh state after change
@ -96,15 +108,15 @@ export function DCPowerControl() {
{powerState.restoreState > -1 ? (
<div className="flex items-center">
<SelectMenuBasic
size="SM"
label={m.dc_power_control_restore_power_state()}
value={powerState.restoreState}
onChange={e => handleRestoreChange(parseInt(e.target.value))}
options={[
{ value: '0', label: m.dc_power_control_power_off_state()},
{ value: '1', label: m.dc_power_control_power_on_state()},
{ value: '2', label: m.dc_power_control_restore_last_state()},
]}
size="SM"
label={m.dc_power_control_restore_power_state()}
value={powerState.restoreState}
onChange={e => handleRestoreChange(parseInt(e.target.value))}
options={[
{ value: "0", label: m.dc_power_control_power_off_state() },
{ value: "1", label: m.dc_power_control_power_on_state() },
{ value: "2", label: m.dc_power_control_restore_last_state() },
]}
/>
</div>
) : null}
@ -125,7 +137,7 @@ export function DCPowerControl() {
</p>
</div>
<div className="space-y-1">
<FieldLabel label={m.dc_power_control_power()}/>
<FieldLabel label={m.dc_power_control_power()} />
<p className="text-sm font-medium text-slate-900 dark:text-slate-100">
{powerState.power.toFixed(1)}&nbsp;{m.dc_power_control_power_unit()}
</p>

View File

@ -29,7 +29,9 @@ export function SerialConsole() {
useEffect(() => {
send("getSerialSettings", {}, (resp: JsonRpcResponse) => {
if ("error" in resp) {
notifications.error(m.serial_console_get_settings_error({ error: resp.error.data || m.unknown_error() }));
notifications.error(
m.serial_console_get_settings_error({ error: resp.error.data || m.unknown_error() }),
);
return;
}
setSettings(resp.result as SerialSettings);
@ -40,7 +42,12 @@ export function SerialConsole() {
const newSettings = { ...settings, [setting]: value };
send("setSerialSettings", { settings: newSettings }, (resp: JsonRpcResponse) => {
if ("error" in resp) {
notifications.error(m.serial_console_set_settings_error({ settings: setting, error: resp.error.data || m.unknown_error() }));
notifications.error(
m.serial_console_set_settings_error({
settings: setting,
error: resp.error.data || m.unknown_error(),
}),
);
return;
}
setSettings(newSettings);

View File

@ -61,7 +61,9 @@ export default function ExtensionPopover() {
send("setActiveExtension", { extensionId: extension?.id || "" }, (resp: JsonRpcResponse) => {
if ("error" in resp) {
notifications.error(
m.extension_popover_set_error_notification({ error: resp.error.data || m.unknown_error() }),
m.extension_popover_set_error_notification({
error: resp.error.data || m.unknown_error(),
}),
);
return;
}
@ -93,7 +95,7 @@ export default function ExtensionPopover() {
{renderActiveExtension()}
<div
className="flex animate-fadeIn opacity-0 items-center justify-end space-x-2"
className="flex animate-fadeIn items-center justify-end space-x-2 opacity-0"
style={{
animationDuration: "0.7s",
animationDelay: "0.2s",
@ -114,15 +116,12 @@ export default function ExtensionPopover() {
title={m.extensions_popover_extensions()}
description={m.extension_popover_load_and_manage_extensions()}
/>
<Card className="animate-fadeIn opacity-0" >
<Card className="animate-fadeIn opacity-0">
<div className="w-full divide-y divide-slate-700/30 dark:divide-slate-600/30">
{AVAILABLE_EXTENSIONS.map(extension => (
<div
key={extension.id}
className="flex items-center justify-between p-3"
>
<div key={extension.id} className="flex items-center justify-between p-3">
<div className="space-y-0.5">
<p className="text-sm font-semibold leading-none text-slate-900 dark:text-slate-100">
<p className="text-sm leading-none font-semibold text-slate-900 dark:text-slate-100">
{extension.name}
</p>
<p className="text-sm text-slate-600 dark:text-slate-400">

View File

@ -51,7 +51,7 @@ const MountPopopover = forwardRef<HTMLDivElement, object>((_props, ref) => {
</Card>
</div>
<div className="space-y-1">
<h3 className="text-sm font-semibold leading-none text-black dark:text-white">
<h3 className="text-sm leading-none font-semibold text-black dark:text-white">
{m.mount_no_mounted_media()}
</h3>
<p className="text-xs leading-none text-slate-700 dark:text-slate-300">
@ -138,7 +138,7 @@ const MountPopopover = forwardRef<HTMLDivElement, object>((_props, ref) => {
/>
<div
className="animate-fadeIn opacity-0 space-y-2"
className="animate-fadeIn space-y-2 opacity-0"
style={{
animationDuration: "0.7s",
animationDelay: "0.1s",
@ -156,11 +156,13 @@ const MountPopopover = forwardRef<HTMLDivElement, object>((_props, ref) => {
</div>
</div>
{remoteVirtualMediaState ? (
<div className="flex select-none items-center justify-between text-xs">
<div className="select-none text-white dark:text-slate-300">
<div className="flex items-center justify-between text-xs select-none">
<div className="text-white select-none dark:text-slate-300">
<span>{m.mount_mounted_as()}</span>{" "}
<span className="font-semibold">
{remoteVirtualMediaState.mode === "Disk" ? m.mount_mode_disk() : m.mount_mode_cdrom()}
{remoteVirtualMediaState.mode === "Disk"
? m.mount_mode_disk()
: m.mount_mode_cdrom()}
</span>
</div>
@ -189,10 +191,7 @@ const MountPopopover = forwardRef<HTMLDivElement, object>((_props, ref) => {
d="M4.99933 0.775635L0 5.77546H10L4.99933 0.775635Z"
fill="currentColor"
/>
<path
d="M10 7.49976H0V9.22453H10V7.49976Z"
fill="currentColor"
/>
<path d="M10 7.49976H0V9.22453H10V7.49976Z" fill="currentColor" />
</g>
<defs>
<clipPath id="clip0_3137_1186">
@ -213,7 +212,7 @@ const MountPopopover = forwardRef<HTMLDivElement, object>((_props, ref) => {
{!remoteVirtualMediaState && (
<div
className="flex animate-fadeIn opacity-0 items-center justify-end space-x-2"
className="flex animate-fadeIn items-center justify-end space-x-2 opacity-0"
style={{
animationDuration: "0.7s",
animationDelay: "0.2s",

View File

@ -39,7 +39,7 @@ export default function PasteModal() {
const close = useClose();
const debugMode = useSettingsStore(state => state.debugMode);
const delayClassName = useMemo(() => debugMode ? "" : "hidden", [debugMode]);
const delayClassName = useMemo(() => (debugMode ? "" : "hidden"), [debugMode]);
const { setKeyboardLayout } = useSettingsStore();
const { selectedKeyboard } = useKeyboardLayout();
@ -66,7 +66,7 @@ export default function PasteModal() {
const macroSteps: MacroStep[] = [];
for (const char of text) {
const normalizedChar = char.normalize('NFC');
const normalizedChar = char.normalize("NFC");
const keyprops = selectedKeyboard.chars[normalizedChar];
if (!keyprops) continue;
@ -94,7 +94,7 @@ export default function PasteModal() {
macroSteps.push({
keys: [String(key)],
modifiers: modifiers.length > 0 ? modifiers : null,
delay
delay,
});
// if what was requested was a dead key, we need to send an unmodified space to emit
@ -123,10 +123,7 @@ export default function PasteModal() {
<div className="grid h-full grid-rows-(--grid-headerBody)">
<div className="h-full space-y-4">
<div className="space-y-4">
<SettingsPageHeader
title={m.paste_text()}
description={m.paste_text_description()}
/>
<SettingsPageHeader title={m.paste_text()} description={m.paste_text_description()} />
<div
className="animate-fadeIn space-y-2 opacity-0"
@ -165,7 +162,7 @@ export default function PasteModal() {
...new Set(
// @ts-expect-error TS doesn't recognize Intl.Segmenter in some environments
[...new Intl.Segmenter().segment(value)]
.map(x => x.segment.normalize('NFC'))
.map(x => x.segment.normalize("NFC"))
.filter(char => !selectedKeyboard.chars[char]),
),
];
@ -178,8 +175,7 @@ export default function PasteModal() {
<div className="mt-2 flex items-center gap-x-2">
<ExclamationCircleIcon className="h-4 w-4 text-red-500 dark:text-red-400" />
<span className="text-xs text-red-500 dark:text-red-400">
{m.paste_modal_invalid_chars_intro()}{" "}
{invalidChars.join(", ")}
{m.paste_modal_invalid_chars_intro()} {invalidChars.join(", ")}
</span>
</div>
)}
@ -197,18 +193,22 @@ export default function PasteModal() {
setDelayValue(parseInt(e.target.value, 10));
}}
/>
{delayValue < 50 || delayValue > 65534 && (
<div className="mt-2 flex items-center gap-x-2">
<ExclamationCircleIcon className="h-4 w-4 text-red-500 dark:text-red-400" />
<span className="text-xs text-red-500 dark:text-red-400">
{m.paste_modal_delay_out_of_range({ min: 50, max: 65534 })}
</span>
</div>
)}
{delayValue < 50 ||
(delayValue > 65534 && (
<div className="mt-2 flex items-center gap-x-2">
<ExclamationCircleIcon className="h-4 w-4 text-red-500 dark:text-red-400" />
<span className="text-xs text-red-500 dark:text-red-400">
{m.paste_modal_delay_out_of_range({ min: 50, max: 65534 })}
</span>
</div>
))}
</div>
<div className="space-y-4">
<p className="text-xs text-slate-600 dark:text-slate-400">
{m.paste_modal_sending_using_layout({ iso: selectedKeyboard.isoCode, name: selectedKeyboard.name })}
{m.paste_modal_sending_using_layout({
iso: selectedKeyboard.isoCode,
name: selectedKeyboard.name,
})}
</p>
</div>
</div>

View File

@ -27,7 +27,7 @@ export default function AddDeviceForm({
return (
<div className="space-y-4">
<div
className="animate-fadeIn opacity-0 space-y-4"
className="animate-fadeIn space-y-4 opacity-0"
style={{
animationDuration: "0.5s",
animationFillMode: "forwards",
@ -74,7 +74,7 @@ export default function AddDeviceForm({
/>
</div>
<div
className="flex animate-fadeIn opacity-0 items-center justify-end space-x-2"
className="flex animate-fadeIn items-center justify-end space-x-2 opacity-0"
style={{
animationDuration: "0.7s",
animationDelay: "0.2s",

View File

@ -34,7 +34,7 @@ export default function DeviceList({
{storedDevices.map((device, index) => (
<div key={index} className="flex items-center justify-between gap-x-2 p-3">
<div className="space-y-0.5">
<p className="text-sm font-semibold leading-none text-slate-900 dark:text-slate-100">
<p className="text-sm leading-none font-semibold text-slate-900 dark:text-slate-100">
{device?.name}
</p>
<p className="text-sm text-slate-600 dark:text-slate-400">
@ -64,7 +64,7 @@ export default function DeviceList({
</div>
</Card>
<div
className="flex animate-fadeIn opacity-0 items-center justify-end space-x-2"
className="flex animate-fadeIn items-center justify-end space-x-2 opacity-0"
style={{
animationDuration: "0.7s",
animationDelay: "0.2s",

View File

@ -13,7 +13,7 @@ export default function EmptyStateCard({
setShowAddForm: (show: boolean) => void;
}) {
return (
<div className="select-none space-y-4">
<div className="space-y-4 select-none">
<Card className="animate-fadeIn opacity-0">
<div className="flex items-center justify-center py-8 text-center">
<div className="space-y-3">
@ -25,7 +25,7 @@ export default function EmptyStateCard({
</div>
</Card>
</div>
<h3 className="text-sm font-semibold leading-none text-black dark:text-white">
<h3 className="text-sm leading-none font-semibold text-black dark:text-white">
{m.wake_on_lan_empty_no_devices_added()}
</h3>
<p className="text-xs leading-none text-slate-700 dark:text-slate-300">
@ -36,7 +36,7 @@ export default function EmptyStateCard({
</div>
</Card>
<div
className="flex animate-fadeIn opacity-0 items-center justify-end space-x-2"
className="flex animate-fadeIn items-center justify-end space-x-2 opacity-0"
style={{
animationDuration: "0.7s",
animationDelay: "0.2s",

View File

@ -69,13 +69,17 @@ export default function WakeOnLanModal() {
(index: number) => {
const updatedDevices = storedDevices.filter((_, i) => i !== index);
send("setWakeOnLanDevices", { params: { devices: updatedDevices } }, (resp: JsonRpcResponse) => {
if ("error" in resp) {
console.error("Failed to update Wake-on-LAN devices:", resp.error);
} else {
syncStoredDevices();
}
});
send(
"setWakeOnLanDevices",
{ params: { devices: updatedDevices } },
(resp: JsonRpcResponse) => {
if ("error" in resp) {
console.error("Failed to update Wake-on-LAN devices:", resp.error);
} else {
syncStoredDevices();
}
},
);
},
[send, storedDevices, syncStoredDevices],
);
@ -85,15 +89,19 @@ export default function WakeOnLanModal() {
if (!name || !macAddress) return;
const updatedDevices = [...storedDevices, { name, macAddress }];
console.log("updatedDevices", updatedDevices);
send("setWakeOnLanDevices", { params: { devices: updatedDevices } }, (resp: JsonRpcResponse) => {
if ("error" in resp) {
console.error("Failed to add Wake-on-LAN device:", resp.error);
setAddDeviceErrorMessage(m.wake_on_lan_failed_add_device());
} else {
setShowAddForm(false);
syncStoredDevices();
}
});
send(
"setWakeOnLanDevices",
{ params: { devices: updatedDevices } },
(resp: JsonRpcResponse) => {
if ("error" in resp) {
console.error("Failed to add Wake-on-LAN device:", resp.error);
setAddDeviceErrorMessage(m.wake_on_lan_failed_add_device());
} else {
setShowAddForm(false);
syncStoredDevices();
}
},
);
},
[send, storedDevices, syncStoredDevices],
);
@ -103,10 +111,7 @@ export default function WakeOnLanModal() {
<div className="space-y-4 p-4 py-3">
<div className="grid h-full grid-rows-(--grid-headerBody)">
<div className="space-y-4">
<SettingsPageHeader
title={m.wake_on_lan()}
description={m.wake_on_lan_description()}
/>
<SettingsPageHeader title={m.wake_on_lan()} description={m.wake_on_lan_description()} />
{showAddForm ? (
<AddDeviceForm

View File

@ -124,17 +124,28 @@ export default function ConnectionStatsSidebar() {
</div>
<div className="flex items-center">
<GridCard cardClassName="rounded-r-none">
<div className="h-[34px] flex items-center text-xs select-all text-black font-mono dark:text-white px-3 ">
<div className="flex h-[34px] items-center px-3 font-mono text-xs text-black select-all dark:text-white">
{remoteIPAddress}
</div>
</GridCard>
<Button className="rounded-l-none border-l-slate-800/30 dark:border-slate-300/20" size="SM" type="button" theme="light" LeadingIcon={LuCopy} onClick={async () => {
if (await copy(remoteIPAddress)) {
notifications.success((m.connection_stats_remote_ip_address_copy_success({ ip: remoteIPAddress })));
} else {
notifications.error(m.connection_stats_remote_ip_address_copy_error());
}
}} />
<Button
className="rounded-l-none border-l-slate-800/30 dark:border-slate-300/20"
size="SM"
type="button"
theme="light"
LeadingIcon={LuCopy}
onClick={async () => {
if (await copy(remoteIPAddress)) {
notifications.success(
m.connection_stats_remote_ip_address_copy_success({
ip: remoteIPAddress,
}),
);
} else {
notifications.error(m.connection_stats_remote_ip_address_copy_error());
}
}}
/>
</div>
</div>
)}
@ -184,10 +195,7 @@ export default function ConnectionStatsSidebar() {
data={jitterBufferAvgDelayData}
gate={inboundVideoRtpStats}
supported={
someIterable(
inboundVideoRtpStats,
([, x]) => x.jitterBufferDelay != null,
) &&
someIterable(inboundVideoRtpStats, ([, x]) => x.jitterBufferDelay != null) &&
someIterable(
inboundVideoRtpStats,
([, x]) => x.jitterBufferEmittedCount != null,

View File

@ -3,47 +3,50 @@ import { useCallback, useState } from "react";
export function useCopyToClipboard(resetInterval = 2000) {
const [isCopied, setIsCopied] = useState(false);
const copy = useCallback(async (text: string) => {
if (!text) return false;
const copy = useCallback(
async (text: string) => {
if (!text) return false;
let success = false;
let success = false;
if (navigator.clipboard && window.isSecureContext) {
try {
await navigator.clipboard.writeText(text);
success = true;
} catch (err) {
console.warn("Clipboard API failed:", err);
if (navigator.clipboard && window.isSecureContext) {
try {
await navigator.clipboard.writeText(text);
success = true;
} catch (err) {
console.warn("Clipboard API failed:", err);
}
}
}
// Fallback for insecure contexts
if (!success) {
const textarea = document.createElement("textarea");
textarea.value = text;
textarea.style.position = "fixed";
textarea.style.opacity = "0";
document.body.appendChild(textarea);
textarea.focus();
textarea.select();
// Fallback for insecure contexts
if (!success) {
const textarea = document.createElement("textarea");
textarea.value = text;
textarea.style.position = "fixed";
textarea.style.opacity = "0";
document.body.appendChild(textarea);
textarea.focus();
textarea.select();
try {
success = document.execCommand("copy");
} catch (err) {
console.error("Fallback copy failed:", err);
success = false;
} finally {
document.body.removeChild(textarea);
try {
success = document.execCommand("copy");
} catch (err) {
console.error("Fallback copy failed:", err);
success = false;
} finally {
document.body.removeChild(textarea);
}
}
}
setIsCopied(success);
if (success && resetInterval > 0) {
setTimeout(() => setIsCopied(false), resetInterval);
}
setIsCopied(success);
if (success && resetInterval > 0) {
setTimeout(() => setIsCopied(false), resetInterval);
}
return success;
}, [resetInterval]);
return success;
},
[resetInterval],
);
return { copy, isCopied };
}

View File

@ -1,449 +1,428 @@
import { hidKeyBufferSize, KeyboardLedState, KeysDownState } from "./stores";
export const HID_RPC_MESSAGE_TYPES = {
Handshake: 0x01,
KeyboardReport: 0x02,
PointerReport: 0x03,
WheelReport: 0x04,
KeypressReport: 0x05,
KeypressKeepAliveReport: 0x09,
MouseReport: 0x06,
KeyboardMacroReport: 0x07,
CancelKeyboardMacroReport: 0x08,
KeyboardLedState: 0x32,
KeysDownState: 0x33,
KeyboardMacroState: 0x34,
}
Handshake: 0x01,
KeyboardReport: 0x02,
PointerReport: 0x03,
WheelReport: 0x04,
KeypressReport: 0x05,
KeypressKeepAliveReport: 0x09,
MouseReport: 0x06,
KeyboardMacroReport: 0x07,
CancelKeyboardMacroReport: 0x08,
KeyboardLedState: 0x32,
KeysDownState: 0x33,
KeyboardMacroState: 0x34,
};
export type HidRpcMessageType = typeof HID_RPC_MESSAGE_TYPES[keyof typeof HID_RPC_MESSAGE_TYPES];
export type HidRpcMessageType = (typeof HID_RPC_MESSAGE_TYPES)[keyof typeof HID_RPC_MESSAGE_TYPES];
export const HID_RPC_VERSION = 0x01;
const withinUint8Range = (value: number) => {
return value >= 0 && value <= 255;
return value >= 0 && value <= 255;
};
const fromInt32toUint8 = (n: number) => {
if (n !== n >> 0) {
throw new Error(`Number ${n} is not within the int32 range`);
}
if (n !== n >> 0) {
throw new Error(`Number ${n} is not within the int32 range`);
}
return new Uint8Array([
(n >> 24) & 0xFF,
(n >> 16) & 0xFF,
(n >> 8) & 0xFF,
n & 0xFF,
]);
return new Uint8Array([(n >> 24) & 0xff, (n >> 16) & 0xff, (n >> 8) & 0xff, n & 0xff]);
};
const fromUint16toUint8 = (n: number) => {
if (n > 65535 || n < 0) {
throw new Error(`Number ${n} is not within the uint16 range`);
}
if (n > 65535 || n < 0) {
throw new Error(`Number ${n} is not within the uint16 range`);
}
return new Uint8Array([
(n >> 8) & 0xFF,
n & 0xFF,
]);
return new Uint8Array([(n >> 8) & 0xff, n & 0xff]);
};
const fromUint32toUint8 = (n: number) => {
if (n > 4294967295 || n < 0) {
throw new Error(`Number ${n} is not within the uint32 range`);
}
if (n > 4294967295 || n < 0) {
throw new Error(`Number ${n} is not within the uint32 range`);
}
return new Uint8Array([
(n >> 24) & 0xFF,
(n >> 16) & 0xFF,
(n >> 8) & 0xFF,
n & 0xFF,
]);
return new Uint8Array([(n >> 24) & 0xff, (n >> 16) & 0xff, (n >> 8) & 0xff, n & 0xff]);
};
const fromInt8ToUint8 = (n: number) => {
if (n < -128 || n > 127) {
throw new Error(`Number ${n} is not within the int8 range`);
}
if (n < -128 || n > 127) {
throw new Error(`Number ${n} is not within the int8 range`);
}
return n & 0xFF;
return n & 0xff;
};
const keyboardLedStateMasks = {
num_lock: 1 << 0,
caps_lock: 1 << 1,
scroll_lock: 1 << 2,
compose: 1 << 3,
kana: 1 << 4,
shift: 1 << 6,
}
num_lock: 1 << 0,
caps_lock: 1 << 1,
scroll_lock: 1 << 2,
compose: 1 << 3,
kana: 1 << 4,
shift: 1 << 6,
};
export class RpcMessage {
messageType: HidRpcMessageType;
messageType: HidRpcMessageType;
constructor(messageType: HidRpcMessageType) {
this.messageType = messageType;
}
constructor(messageType: HidRpcMessageType) {
this.messageType = messageType;
}
marshal(): Uint8Array {
throw new Error("Not implemented");
}
marshal(): Uint8Array {
throw new Error("Not implemented");
}
public static unmarshal(_data: Uint8Array): RpcMessage | undefined {
throw new Error("Not implemented");
}
public static unmarshal(_data: Uint8Array): RpcMessage | undefined {
throw new Error("Not implemented");
}
}
export class HandshakeMessage extends RpcMessage {
version: number;
version: number;
constructor(version: number) {
super(HID_RPC_MESSAGE_TYPES.Handshake);
this.version = version;
constructor(version: number) {
super(HID_RPC_MESSAGE_TYPES.Handshake);
this.version = version;
}
marshal(): Uint8Array {
return new Uint8Array([this.messageType, this.version]);
}
public static unmarshal(data: Uint8Array): HandshakeMessage | undefined {
if (data.length < 1) {
throw new Error(`Invalid handshake message length: ${data.length}`);
}
marshal(): Uint8Array {
return new Uint8Array([this.messageType, this.version]);
}
public static unmarshal(data: Uint8Array): HandshakeMessage | undefined {
if (data.length < 1) {
throw new Error(`Invalid handshake message length: ${data.length}`);
}
return new HandshakeMessage(data[0]);
}
return new HandshakeMessage(data[0]);
}
}
export class KeypressReportMessage extends RpcMessage {
private _key = 0;
private _press = false;
private _key = 0;
private _press = false;
get key(): number {
return this._key;
get key(): number {
return this._key;
}
set key(value: number) {
if (!withinUint8Range(value)) {
throw new Error(`Key ${value} is not within the uint8 range`);
}
set key(value: number) {
if (!withinUint8Range(value)) {
throw new Error(`Key ${value} is not within the uint8 range`);
}
this._key = value;
}
this._key = value;
get press(): boolean {
return this._press;
}
set press(value: boolean) {
this._press = value;
}
constructor(key: number, press: boolean) {
super(HID_RPC_MESSAGE_TYPES.KeypressReport);
this.key = key;
this.press = press;
}
marshal(): Uint8Array {
return new Uint8Array([this.messageType, this.key, this.press ? 1 : 0]);
}
public static unmarshal(data: Uint8Array): KeypressReportMessage | undefined {
if (data.length < 1) {
throw new Error(`Invalid keypress report message length: ${data.length}`);
}
get press(): boolean {
return this._press;
}
set press(value: boolean) {
this._press = value;
}
constructor(key: number, press: boolean) {
super(HID_RPC_MESSAGE_TYPES.KeypressReport);
this.key = key;
this.press = press;
}
marshal(): Uint8Array {
return new Uint8Array([
this.messageType,
this.key,
this.press ? 1 : 0,
]);
}
public static unmarshal(data: Uint8Array): KeypressReportMessage | undefined {
if (data.length < 1) {
throw new Error(`Invalid keypress report message length: ${data.length}`);
}
return new KeypressReportMessage(data[0], data[1] === 1);
}
return new KeypressReportMessage(data[0], data[1] === 1);
}
}
export class KeyboardReportMessage extends RpcMessage {
private _keys: number[] = [];
private _modifier = 0;
private _keys: number[] = [];
private _modifier = 0;
get keys(): number[] {
return this._keys;
get keys(): number[] {
return this._keys;
}
set keys(value: number[]) {
value.forEach(k => {
if (!withinUint8Range(k)) {
throw new Error(`Key ${k} is not within the uint8 range`);
}
});
this._keys = value;
}
get modifier(): number {
return this._modifier;
}
set modifier(value: number) {
if (!withinUint8Range(value)) {
throw new Error(`Modifier ${value} is not within the uint8 range`);
}
set keys(value: number[]) {
value.forEach((k) => {
if (!withinUint8Range(k)) {
throw new Error(`Key ${k} is not within the uint8 range`);
}
});
this._modifier = value;
}
this._keys = value;
constructor(keys: number[], modifier: number) {
super(HID_RPC_MESSAGE_TYPES.KeyboardReport);
this.keys = keys;
this.modifier = modifier;
}
marshal(): Uint8Array {
return new Uint8Array([this.messageType, this.modifier, ...this.keys]);
}
public static unmarshal(data: Uint8Array): KeyboardReportMessage | undefined {
if (data.length < 1) {
throw new Error(`Invalid keyboard report message length: ${data.length}`);
}
get modifier(): number {
return this._modifier;
}
set modifier(value: number) {
if (!withinUint8Range(value)) {
throw new Error(`Modifier ${value} is not within the uint8 range`);
}
this._modifier = value;
}
constructor(keys: number[], modifier: number) {
super(HID_RPC_MESSAGE_TYPES.KeyboardReport);
this.keys = keys;
this.modifier = modifier;
}
marshal(): Uint8Array {
return new Uint8Array([
this.messageType,
this.modifier,
...this.keys,
]);
}
public static unmarshal(data: Uint8Array): KeyboardReportMessage | undefined {
if (data.length < 1) {
throw new Error(`Invalid keyboard report message length: ${data.length}`);
}
return new KeyboardReportMessage(Array.from(data.slice(1)), data[0]);
}
return new KeyboardReportMessage(Array.from(data.slice(1)), data[0]);
}
}
export interface KeyboardMacroStep extends KeysDownState {
delay: number;
delay: number;
}
export class KeyboardMacroReportMessage extends RpcMessage {
isPaste: boolean;
stepCount: number;
steps: KeyboardMacroStep[];
isPaste: boolean;
stepCount: number;
steps: KeyboardMacroStep[];
KEYS_LENGTH = hidKeyBufferSize;
KEYS_LENGTH = hidKeyBufferSize;
constructor(isPaste: boolean, stepCount: number, steps: KeyboardMacroStep[]) {
super(HID_RPC_MESSAGE_TYPES.KeyboardMacroReport);
this.isPaste = isPaste;
this.stepCount = stepCount;
this.steps = steps;
constructor(isPaste: boolean, stepCount: number, steps: KeyboardMacroStep[]) {
super(HID_RPC_MESSAGE_TYPES.KeyboardMacroReport);
this.isPaste = isPaste;
this.stepCount = stepCount;
this.steps = steps;
}
marshal(): Uint8Array {
// validate if length is correct
if (this.stepCount !== this.steps.length) {
throw new Error(
`Length ${this.stepCount} is not equal to the number of steps ${this.steps.length}`,
);
}
marshal(): Uint8Array {
// validate if length is correct
if (this.stepCount !== this.steps.length) {
throw new Error(`Length ${this.stepCount} is not equal to the number of steps ${this.steps.length}`);
const data = new Uint8Array(this.stepCount * 9 + 6);
data.set(
new Uint8Array([
this.messageType,
this.isPaste ? 1 : 0,
...fromUint32toUint8(this.stepCount),
]),
0,
);
let offset = 6;
for (let i = 0; i < this.stepCount; i++) {
const step = this.steps[i];
if (!withinUint8Range(step.modifier)) {
throw new Error(`Modifier ${step.modifier} is not within the uint8 range`);
}
// Ensure the keys are within the KEYS_LENGTH range
const keys = step.keys;
if (keys.length > this.KEYS_LENGTH) {
throw new Error(`Keys ${keys} is not within the hidKeyBufferSize range`);
} else if (keys.length < this.KEYS_LENGTH) {
keys.push(...Array(this.KEYS_LENGTH - keys.length).fill(0));
}
for (const key of keys) {
if (!withinUint8Range(key)) {
throw new Error(`Key ${key} is not within the uint8 range`);
}
}
const data = new Uint8Array(this.stepCount * 9 + 6);
data.set(new Uint8Array([
this.messageType,
this.isPaste ? 1 : 0,
...fromUint32toUint8(this.stepCount),
]), 0);
const macroBinary = new Uint8Array([
step.modifier,
...keys,
...fromUint16toUint8(step.delay),
]);
let offset = 6;
for (let i = 0; i < this.stepCount; i++) {
const step = this.steps[i];
if (!withinUint8Range(step.modifier)) {
throw new Error(`Modifier ${step.modifier} is not within the uint8 range`);
}
// Ensure the keys are within the KEYS_LENGTH range
const keys = step.keys;
if (keys.length > this.KEYS_LENGTH) {
throw new Error(`Keys ${keys} is not within the hidKeyBufferSize range`);
} else if (keys.length < this.KEYS_LENGTH) {
keys.push(...Array(this.KEYS_LENGTH - keys.length).fill(0));
}
for (const key of keys) {
if (!withinUint8Range(key)) {
throw new Error(`Key ${key} is not within the uint8 range`);
}
}
const macroBinary = new Uint8Array([
step.modifier,
...keys,
...fromUint16toUint8(step.delay),
]);
data.set(macroBinary, offset);
offset += 9;
}
return data;
data.set(macroBinary, offset);
offset += 9;
}
return data;
}
}
export class KeyboardMacroStateMessage extends RpcMessage {
state: boolean;
isPaste: boolean;
state: boolean;
isPaste: boolean;
constructor(state: boolean, isPaste: boolean) {
super(HID_RPC_MESSAGE_TYPES.KeyboardMacroState);
this.state = state;
this.isPaste = isPaste;
constructor(state: boolean, isPaste: boolean) {
super(HID_RPC_MESSAGE_TYPES.KeyboardMacroState);
this.state = state;
this.isPaste = isPaste;
}
marshal(): Uint8Array {
return new Uint8Array([this.messageType, this.state ? 1 : 0, this.isPaste ? 1 : 0]);
}
public static unmarshal(data: Uint8Array): KeyboardMacroStateMessage | undefined {
if (data.length < 1) {
throw new Error(`Invalid keyboard macro state report message length: ${data.length}`);
}
marshal(): Uint8Array {
return new Uint8Array([
this.messageType,
this.state ? 1 : 0,
this.isPaste ? 1 : 0,
]);
}
public static unmarshal(data: Uint8Array): KeyboardMacroStateMessage | undefined {
if (data.length < 1) {
throw new Error(`Invalid keyboard macro state report message length: ${data.length}`);
}
return new KeyboardMacroStateMessage(data[0] === 1, data[1] === 1);
}
return new KeyboardMacroStateMessage(data[0] === 1, data[1] === 1);
}
}
export class KeyboardLedStateMessage extends RpcMessage {
keyboardLedState: KeyboardLedState;
keyboardLedState: KeyboardLedState;
constructor(keyboardLedState: KeyboardLedState) {
super(HID_RPC_MESSAGE_TYPES.KeyboardLedState);
this.keyboardLedState = keyboardLedState;
constructor(keyboardLedState: KeyboardLedState) {
super(HID_RPC_MESSAGE_TYPES.KeyboardLedState);
this.keyboardLedState = keyboardLedState;
}
public static unmarshal(data: Uint8Array): KeyboardLedStateMessage | undefined {
if (data.length < 1) {
throw new Error(`Invalid keyboard led state message length: ${data.length}`);
}
public static unmarshal(data: Uint8Array): KeyboardLedStateMessage | undefined {
if (data.length < 1) {
throw new Error(`Invalid keyboard led state message length: ${data.length}`);
}
const s = data[0];
const s = data[0];
const state = {
num_lock: (s & keyboardLedStateMasks.num_lock) !== 0,
caps_lock: (s & keyboardLedStateMasks.caps_lock) !== 0,
scroll_lock: (s & keyboardLedStateMasks.scroll_lock) !== 0,
compose: (s & keyboardLedStateMasks.compose) !== 0,
kana: (s & keyboardLedStateMasks.kana) !== 0,
shift: (s & keyboardLedStateMasks.shift) !== 0,
} as KeyboardLedState;
const state = {
num_lock: (s & keyboardLedStateMasks.num_lock) !== 0,
caps_lock: (s & keyboardLedStateMasks.caps_lock) !== 0,
scroll_lock: (s & keyboardLedStateMasks.scroll_lock) !== 0,
compose: (s & keyboardLedStateMasks.compose) !== 0,
kana: (s & keyboardLedStateMasks.kana) !== 0,
shift: (s & keyboardLedStateMasks.shift) !== 0,
} as KeyboardLedState;
return new KeyboardLedStateMessage(state);
}
return new KeyboardLedStateMessage(state);
}
}
export class KeysDownStateMessage extends RpcMessage {
keysDownState: KeysDownState;
keysDownState: KeysDownState;
constructor(keysDownState: KeysDownState) {
super(HID_RPC_MESSAGE_TYPES.KeysDownState);
this.keysDownState = keysDownState;
constructor(keysDownState: KeysDownState) {
super(HID_RPC_MESSAGE_TYPES.KeysDownState);
this.keysDownState = keysDownState;
}
public static unmarshal(data: Uint8Array): KeysDownStateMessage | undefined {
if (data.length < 1) {
throw new Error(`Invalid keys down state message length: ${data.length}`);
}
public static unmarshal(data: Uint8Array): KeysDownStateMessage | undefined {
if (data.length < 1) {
throw new Error(`Invalid keys down state message length: ${data.length}`);
}
return new KeysDownStateMessage({
modifier: data[0],
keys: Array.from(data.slice(1))
});
}
return new KeysDownStateMessage({
modifier: data[0],
keys: Array.from(data.slice(1)),
});
}
}
export class PointerReportMessage extends RpcMessage {
x: number;
y: number;
buttons: number;
x: number;
y: number;
buttons: number;
constructor(x: number, y: number, buttons: number) {
super(HID_RPC_MESSAGE_TYPES.PointerReport);
this.x = x;
this.y = y;
this.buttons = buttons;
}
constructor(x: number, y: number, buttons: number) {
super(HID_RPC_MESSAGE_TYPES.PointerReport);
this.x = x;
this.y = y;
this.buttons = buttons;
}
marshal(): Uint8Array {
return new Uint8Array([
this.messageType,
...fromInt32toUint8(this.x),
...fromInt32toUint8(this.y),
this.buttons,
]);
}
marshal(): Uint8Array {
return new Uint8Array([
this.messageType,
...fromInt32toUint8(this.x),
...fromInt32toUint8(this.y),
this.buttons,
]);
}
}
export class CancelKeyboardMacroReportMessage extends RpcMessage {
constructor() {
super(HID_RPC_MESSAGE_TYPES.CancelKeyboardMacroReport);
}
constructor() {
super(HID_RPC_MESSAGE_TYPES.CancelKeyboardMacroReport);
}
marshal(): Uint8Array {
return new Uint8Array([this.messageType]);
}
marshal(): Uint8Array {
return new Uint8Array([this.messageType]);
}
}
export class MouseReportMessage extends RpcMessage {
dx: number;
dy: number;
buttons: number;
dx: number;
dy: number;
buttons: number;
constructor(dx: number, dy: number, buttons: number) {
super(HID_RPC_MESSAGE_TYPES.MouseReport);
this.dx = dx;
this.dy = dy;
this.buttons = buttons;
}
constructor(dx: number, dy: number, buttons: number) {
super(HID_RPC_MESSAGE_TYPES.MouseReport);
this.dx = dx;
this.dy = dy;
this.buttons = buttons;
}
marshal(): Uint8Array {
return new Uint8Array([
this.messageType,
fromInt8ToUint8(this.dx),
fromInt8ToUint8(this.dy),
this.buttons,
]);
}
marshal(): Uint8Array {
return new Uint8Array([
this.messageType,
fromInt8ToUint8(this.dx),
fromInt8ToUint8(this.dy),
this.buttons,
]);
}
}
export class KeypressKeepAliveMessage extends RpcMessage {
constructor() {
super(HID_RPC_MESSAGE_TYPES.KeypressKeepAliveReport);
}
constructor() {
super(HID_RPC_MESSAGE_TYPES.KeypressKeepAliveReport);
}
marshal(): Uint8Array {
return new Uint8Array([this.messageType]);
}
marshal(): Uint8Array {
return new Uint8Array([this.messageType]);
}
}
export const messageRegistry = {
[HID_RPC_MESSAGE_TYPES.Handshake]: HandshakeMessage,
[HID_RPC_MESSAGE_TYPES.KeysDownState]: KeysDownStateMessage,
[HID_RPC_MESSAGE_TYPES.KeyboardLedState]: KeyboardLedStateMessage,
[HID_RPC_MESSAGE_TYPES.KeyboardReport]: KeyboardReportMessage,
[HID_RPC_MESSAGE_TYPES.KeypressReport]: KeypressReportMessage,
[HID_RPC_MESSAGE_TYPES.KeyboardMacroReport]: KeyboardMacroReportMessage,
[HID_RPC_MESSAGE_TYPES.CancelKeyboardMacroReport]: CancelKeyboardMacroReportMessage,
[HID_RPC_MESSAGE_TYPES.KeyboardMacroState]: KeyboardMacroStateMessage,
[HID_RPC_MESSAGE_TYPES.KeypressKeepAliveReport]: KeypressKeepAliveMessage,
}
[HID_RPC_MESSAGE_TYPES.Handshake]: HandshakeMessage,
[HID_RPC_MESSAGE_TYPES.KeysDownState]: KeysDownStateMessage,
[HID_RPC_MESSAGE_TYPES.KeyboardLedState]: KeyboardLedStateMessage,
[HID_RPC_MESSAGE_TYPES.KeyboardReport]: KeyboardReportMessage,
[HID_RPC_MESSAGE_TYPES.KeypressReport]: KeypressReportMessage,
[HID_RPC_MESSAGE_TYPES.KeyboardMacroReport]: KeyboardMacroReportMessage,
[HID_RPC_MESSAGE_TYPES.CancelKeyboardMacroReport]: CancelKeyboardMacroReportMessage,
[HID_RPC_MESSAGE_TYPES.KeyboardMacroState]: KeyboardMacroStateMessage,
[HID_RPC_MESSAGE_TYPES.KeypressKeepAliveReport]: KeypressKeepAliveMessage,
};
export const unmarshalHidRpcMessage = (data: Uint8Array): RpcMessage | undefined => {
if (data.length < 1) {
throw new Error(`Invalid HID RPC message length: ${data.length}`);
}
if (data.length < 1) {
throw new Error(`Invalid HID RPC message length: ${data.length}`);
}
const payload = data.slice(1);
const payload = data.slice(1);
const messageType = data[0];
if (!(messageType in messageRegistry)) {
throw new Error(`Unknown HID RPC message type: ${messageType}`);
}
const messageType = data[0];
if (!(messageType in messageRegistry)) {
throw new Error(`Unknown HID RPC message type: ${messageType}`);
}
return messageRegistry[messageType].unmarshal(payload);
return messageRegistry[messageType].unmarshal(payload);
};

View File

@ -1,11 +1,7 @@
import { create } from "zustand";
import { createJSONStorage, persist } from "zustand/middleware";
import {
MAX_STEPS_PER_MACRO,
MAX_TOTAL_MACROS,
MAX_KEYS_PER_STEP,
} from "@/constants/macros";
import { MAX_STEPS_PER_MACRO, MAX_TOTAL_MACROS, MAX_KEYS_PER_STEP } from "@/constants/macros";
// Define the JsonRpc types for better type checking
interface JsonRpcResponse {
@ -92,8 +88,7 @@ export const useUiStore = create<UIState>(set => ({
setDisableVideoFocusTrap: (enabled: boolean) => set({ disableVideoFocusTrap: enabled }),
isWakeOnLanModalVisible: false,
setWakeOnLanModalVisibility: (enabled: boolean) =>
set({ isWakeOnLanModalVisible: enabled }),
setWakeOnLanModalVisibility: (enabled: boolean) => set({ isWakeOnLanModalVisible: enabled }),
toggleSidebarView: view =>
set(state => {
@ -275,12 +270,7 @@ export const useMouseStore = create<MouseState>(set => ({
setMousePosition: (x, y) => set({ mouseX: x, mouseY: y }),
}));
export type HdmiStates =
| "ready"
| "no_signal"
| "no_lock"
| "out_of_range"
| "connecting";
export type HdmiStates = "ready" | "no_signal" | "no_lock" | "out_of_range" | "connecting";
export type HdmiErrorStates = Extract<
VideoState["hdmiState"],
"no_signal" | "no_lock" | "out_of_range"
@ -310,8 +300,7 @@ export const useVideoStore = create<VideoState>(set => ({
clientHeight: 0,
// The video element's client size
setClientSize: (clientWidth: number, clientHeight: number) =>
set({ clientWidth, clientHeight }),
setClientSize: (clientWidth: number, clientHeight: number) => set({ clientWidth, clientHeight }),
// Resolution
setSize: (width: number, height: number) => set({ width, height }),
@ -401,8 +390,7 @@ export const useSettingsStore = create(
dim_after: 10000,
off_after: 50000,
},
setBacklightSettings: (settings: BacklightSettings) =>
set({ backlightSettings: settings }),
setBacklightSettings: (settings: BacklightSettings) => set({ backlightSettings: settings }),
keyboardLayout: "en-US",
setKeyboardLayout: (layout: string) => set({ keyboardLayout: layout }),
@ -493,12 +481,7 @@ export interface KeysDownState {
keys: number[];
}
export type USBStates =
| "configured"
| "attached"
| "not attached"
| "suspended"
| "addressed";
export type USBStates = "configured" | "attached" | "not attached" | "suspended" | "addressed";
export interface HidState {
keyboardLedState: KeyboardLedState;
@ -526,15 +509,13 @@ export const useHidStore = create<HidState>(set => ({
kana: false,
shift: false,
} as KeyboardLedState,
setKeyboardLedState: (ledState: KeyboardLedState): void =>
set({ keyboardLedState: ledState }),
setKeyboardLedState: (ledState: KeyboardLedState): void => set({ keyboardLedState: ledState }),
keysDownState: { modifier: 0, keys: [0, 0, 0, 0, 0, 0] } as KeysDownState,
setKeysDownState: (state: KeysDownState): void => set({ keysDownState: state }),
isVirtualKeyboardEnabled: false,
setVirtualKeyboardEnabled: (enabled: boolean): void =>
set({ isVirtualKeyboardEnabled: enabled }),
setVirtualKeyboardEnabled: (enabled: boolean): void => set({ isVirtualKeyboardEnabled: enabled }),
isPasteInProgress: false,
setPasteModeEnabled: (enabled: boolean): void => set({ isPasteInProgress: enabled }),
@ -641,8 +622,7 @@ export const useUpdateStore = create<UpdateState>(set => ({
setModalView: (view: UpdateModalViews) => set({ modalView: view }),
updateErrorMessage: null,
setUpdateErrorMessage: (errorMessage: string) =>
set({ updateErrorMessage: errorMessage }),
setUpdateErrorMessage: (errorMessage: string) => set({ updateErrorMessage: errorMessage }),
shouldReload: false,
setShouldReload: (reloadRequired: boolean) => set({ shouldReload: reloadRequired }),
@ -791,12 +771,7 @@ export type IPv6Mode =
export type IPv4Mode = "disabled" | "static" | "dhcp" | "unknown";
export type LLDPMode = "disabled" | "basic" | "all" | "unknown";
export type mDNSMode = "disabled" | "auto" | "ipv4_only" | "ipv6_only" | "unknown";
export type TimeSyncMode =
| "ntp_only"
| "ntp_and_http"
| "http_only"
| "custom"
| "unknown";
export type TimeSyncMode = "ntp_only" | "ntp_and_http" | "http_only" | "custom" | "unknown";
export interface IPv4StaticConfig {
address: string;
@ -861,12 +836,12 @@ export interface MacrosState {
loadMacros: () => Promise<void>;
saveMacros: (macros: KeySequence[]) => Promise<void>;
sendFn:
| ((
method: string,
params: unknown,
callback?: ((resp: JsonRpcResponse) => void) | undefined,
) => void)
| null;
| ((
method: string,
params: unknown,
callback?: ((resp: JsonRpcResponse) => void) | undefined,
) => void)
| null;
setSendFn: (
sendFn: (
method: string,

View File

@ -31,7 +31,8 @@ export function useHidRpc(onHidRpcMessage?: (payload: RpcMessage) => void) {
rpcHidUnreliableChannel,
rpcHidUnreliableNonOrderedChannel,
setRpcHidProtocolVersion,
rpcHidProtocolVersion, hidRpcDisabled,
rpcHidProtocolVersion,
hidRpcDisabled,
} = useRTCStore();
const rpcHidReady = useMemo(() => {
@ -40,15 +41,12 @@ export function useHidRpc(onHidRpcMessage?: (payload: RpcMessage) => void) {
}, [rpcHidChannel, rpcHidProtocolVersion, hidRpcDisabled]);
const rpcHidUnreliableReady = useMemo(() => {
return (
rpcHidUnreliableChannel?.readyState === "open" && rpcHidProtocolVersion !== null
);
return rpcHidUnreliableChannel?.readyState === "open" && rpcHidProtocolVersion !== null;
}, [rpcHidProtocolVersion, rpcHidUnreliableChannel?.readyState]);
const rpcHidUnreliableNonOrderedReady = useMemo(() => {
return (
rpcHidUnreliableNonOrderedChannel?.readyState === "open" &&
rpcHidProtocolVersion !== null
rpcHidUnreliableNonOrderedChannel?.readyState === "open" && rpcHidProtocolVersion !== null
);
}, [rpcHidProtocolVersion, rpcHidUnreliableNonOrderedChannel?.readyState]);
@ -64,11 +62,7 @@ export function useHidRpc(onHidRpcMessage?: (payload: RpcMessage) => void) {
const sendMessage = useCallback(
(
message: RpcMessage,
{
ignoreHandshakeState,
useUnreliableChannel,
requireOrdered = true,
}: sendMessageParams = {},
{ ignoreHandshakeState, useUnreliableChannel, requireOrdered = true }: sendMessageParams = {},
) => {
if (hidRpcDisabled) return;
if (rpcHidChannel?.readyState !== "open") return;
@ -96,7 +90,8 @@ export function useHidRpc(onHidRpcMessage?: (payload: RpcMessage) => void) {
[
rpcHidChannel,
rpcHidUnreliableChannel,
hidRpcDisabled, rpcHidUnreliableNonOrderedChannel,
hidRpcDisabled,
rpcHidUnreliableNonOrderedChannel,
rpcHidReady,
rpcHidUnreliableReady,
rpcHidUnreliableNonOrderedReady,
@ -140,12 +135,9 @@ export function useHidRpc(onHidRpcMessage?: (payload: RpcMessage) => void) {
[sendMessage],
);
const cancelOngoingKeyboardMacro = useCallback(
() => {
sendMessage(new CancelKeyboardMacroReportMessage());
},
[sendMessage],
);
const cancelOngoingKeyboardMacro = useCallback(() => {
sendMessage(new CancelKeyboardMacroReportMessage());
}, [sendMessage]);
const reportKeypressKeepAlive = useCallback(() => {
sendMessage(KEEPALIVE_MESSAGE);
@ -224,7 +216,7 @@ export function useHidRpc(onHidRpcMessage?: (payload: RpcMessage) => void) {
};
const errorHandler = (e: Event) => {
console.error(`Error on rpcHidChannel '${rpcHidChannel.label}': ${e}`)
console.error(`Error on rpcHidChannel '${rpcHidChannel.label}': ${e}`);
};
rpcHidChannel.addEventListener("message", messageHandler);

View File

@ -37,14 +37,14 @@ let requestCounter = 0;
// Map of blocked RPC methods by failsafe reason
const blockedMethodsByReason: Record<string, string[]> = {
video: [
'setStreamQualityFactor',
'getEDID',
'setEDID',
'getVideoLogStatus',
'setDisplayRotation',
'getVideoSleepMode',
'setVideoSleepMode',
'getVideoState',
"setStreamQualityFactor",
"getEDID",
"setEDID",
"getVideoLogStatus",
"setDisplayRotation",
"getVideoSleepMode",
"setVideoSleepMode",
"getVideoState",
],
};
@ -86,7 +86,7 @@ export function useJsonRpc(onRequest?: (payload: JsonRpcRequest) => void) {
rpcDataChannel.send(JSON.stringify(payload));
},
[rpcDataChannel, isFailsafeMode, reason]
[rpcDataChannel, isFailsafeMode, reason],
);
useEffect(() => {
@ -117,8 +117,7 @@ export function useJsonRpc(onRequest?: (payload: JsonRpcRequest) => void) {
return () => {
rpcDataChannel.removeEventListener("message", messageHandler);
};
},
[rpcDataChannel, onRequest]);
}, [rpcDataChannel, onRequest]);
return { send };
}

View File

@ -197,9 +197,7 @@ export default function useKeyboard() {
// If we reach here it means we didn't find an empty slot or the key in the buffer
if (overrun) {
if (press) {
console.warn(
`keyboard buffer overflow current keys ${keys}, key: ${key} not added`,
);
console.warn(`keyboard buffer overflow current keys ${keys}, key: ${key} not added`);
// Fill all key slots with ErrorRollOver (0x01) to indicate overflow
keys.length = hidKeyBufferSize;
keys.fill(hidErrorRollOver);
@ -248,11 +246,7 @@ export default function useKeyboard() {
// Older backends don't support the hidRpc API, so we need:
// 1. Calculate the state
// 2. Send the newly calculated state to the device
const downState = simulateDeviceSideKeyHandlingForLegacyDevices(
keysDownState,
key,
press,
);
const downState = simulateDeviceSideKeyHandlingForLegacyDevices(keysDownState, key, press);
handleLegacyKeyboardReport(downState.keys, downState.modifier);

View File

@ -7,8 +7,8 @@ export default function useKeyboardLayout() {
const { keyboardLayout } = useSettingsStore();
const keyboardOptions = useMemo(() => {
return keyboards.map((keyboard) => {
return { label: keyboard.name, value: keyboard.isoCode }
return keyboards.map(keyboard => {
return { label: keyboard.name, value: keyboard.isoCode };
});
}, []);
@ -27,8 +27,10 @@ export default function useKeyboardLayout() {
const selectedKeyboard = useMemo(() => {
// fallback to original behaviour of en-US if no isoCode given or matching layout not found
return keyboards.find(keyboard => keyboard.isoCode === isoCode)
?? keyboards.find(keyboard => keyboard.isoCode === "en-US")!;
return (
keyboards.find(keyboard => keyboard.isoCode === isoCode) ??
keyboards.find(keyboard => keyboard.isoCode === "en-US")!
);
}, [isoCode]);
return { keyboardOptions, isoCode, selectedKeyboard };

View File

@ -40,13 +40,7 @@ export default function useMouse() {
}
setMouseMove({ x, y, buttons });
},
[
send,
reportRelMouseEvent,
setMouseMove,
mouseMode,
rpcHidReady,
],
[send, reportRelMouseEvent, setMouseMove, mouseMode, rpcHidReady],
);
const getRelMouseMoveHandler = useCallback(
@ -72,56 +66,52 @@ export default function useMouse() {
// We set that for the debug info bar
setMousePosition(x, y);
},
[
send,
reportAbsMouseEvent,
setMousePosition,
mouseMode,
rpcHidReady,
],
[send, reportAbsMouseEvent, setMousePosition, mouseMode, rpcHidReady],
);
const getAbsMouseMoveHandler = useCallback(
({ videoClientWidth, videoClientHeight, videoWidth, videoHeight }: AbsMouseMoveHandlerProps) => (e: MouseEvent) => {
if (!videoClientWidth || !videoClientHeight) return;
if (mouseMode !== "absolute") return;
({ videoClientWidth, videoClientHeight, videoWidth, videoHeight }: AbsMouseMoveHandlerProps) =>
(e: MouseEvent) => {
if (!videoClientWidth || !videoClientHeight) return;
if (mouseMode !== "absolute") return;
// Get the aspect ratios of the video element and the video stream
const videoElementAspectRatio = videoClientWidth / videoClientHeight;
const videoStreamAspectRatio = videoWidth / videoHeight;
// Get the aspect ratios of the video element and the video stream
const videoElementAspectRatio = videoClientWidth / videoClientHeight;
const videoStreamAspectRatio = videoWidth / videoHeight;
// Calculate the effective video display area
let effectiveWidth = videoClientWidth;
let effectiveHeight = videoClientHeight;
let offsetX = 0;
let offsetY = 0;
// Calculate the effective video display area
let effectiveWidth = videoClientWidth;
let effectiveHeight = videoClientHeight;
let offsetX = 0;
let offsetY = 0;
if (videoElementAspectRatio > videoStreamAspectRatio) {
// Pillarboxing: black bars on the left and right
effectiveWidth = videoClientHeight * videoStreamAspectRatio;
offsetX = (videoClientWidth - effectiveWidth) / 2;
} else if (videoElementAspectRatio < videoStreamAspectRatio) {
// Letterboxing: black bars on the top and bottom
effectiveHeight = videoClientWidth / videoStreamAspectRatio;
offsetY = (videoClientHeight - effectiveHeight) / 2;
}
if (videoElementAspectRatio > videoStreamAspectRatio) {
// Pillarboxing: black bars on the left and right
effectiveWidth = videoClientHeight * videoStreamAspectRatio;
offsetX = (videoClientWidth - effectiveWidth) / 2;
} else if (videoElementAspectRatio < videoStreamAspectRatio) {
// Letterboxing: black bars on the top and bottom
effectiveHeight = videoClientWidth / videoStreamAspectRatio;
offsetY = (videoClientHeight - effectiveHeight) / 2;
}
// Clamp mouse position within the effective video boundaries
const clampedX = Math.min(Math.max(offsetX, e.offsetX), offsetX + effectiveWidth);
const clampedY = Math.min(Math.max(offsetY, e.offsetY), offsetY + effectiveHeight);
// Clamp mouse position within the effective video boundaries
const clampedX = Math.min(Math.max(offsetX, e.offsetX), offsetX + effectiveWidth);
const clampedY = Math.min(Math.max(offsetY, e.offsetY), offsetY + effectiveHeight);
// Map clamped mouse position to the video stream's coordinate system
const relativeX = (clampedX - offsetX) / effectiveWidth;
const relativeY = (clampedY - offsetY) / effectiveHeight;
// Map clamped mouse position to the video stream's coordinate system
const relativeX = (clampedX - offsetX) / effectiveWidth;
const relativeY = (clampedY - offsetY) / effectiveHeight;
// Convert to HID absolute coordinate system (0-32767 range)
const x = Math.round(relativeX * 32767);
const y = Math.round(relativeY * 32767);
// Convert to HID absolute coordinate system (0-32767 range)
const x = Math.round(relativeX * 32767);
const y = Math.round(relativeY * 32767);
// Send mouse movement
const { buttons } = e;
sendAbsMouseMovement(x, y, buttons);
}, [mouseMode, sendAbsMouseMovement],
// Send mouse movement
const { buttons } = e;
sendAbsMouseMovement(x, y, buttons);
},
[mouseMode, sendAbsMouseMovement],
);
const getMouseWheelHandler = useCallback(

View File

@ -20,12 +20,7 @@ export interface SystemVersionInfo {
}
export function useVersion() {
const {
appVersion,
systemVersion,
setAppVersion,
setSystemVersion,
} = useDeviceStore();
const { appVersion, systemVersion, setAppVersion, setSystemVersion } = useDeviceStore();
const getVersionInfo = useCallback(async () => {
try {

View File

@ -1,6 +1,16 @@
export interface KeyStroke { modifier: number; keys: number[]; }
export interface KeyInfo { key: string | number; shift?: boolean, altRight?: boolean }
export interface KeyCombo extends KeyInfo { deadKey?: boolean, accentKey?: KeyInfo }
export interface KeyStroke {
modifier: number;
keys: number[];
}
export interface KeyInfo {
key: string | number;
shift?: boolean;
altRight?: boolean;
}
export interface KeyCombo extends KeyInfo {
deadKey?: boolean;
accentKey?: KeyInfo;
}
export interface KeyboardLayout {
isoCode: string;
name: string;
@ -8,25 +18,39 @@ export interface KeyboardLayout {
modifierDisplayMap: Record<string, string>;
keyDisplayMap: Record<string, string>;
virtualKeyboard: {
main: { default: string[], shift: string[] },
control?: { default: string[], shift?: string[] },
arrows?: { default: string[] }
main: { default: string[]; shift: string[] };
control?: { default: string[]; shift?: string[] };
arrows?: { default: string[] };
};
}
// To add a new layout, create a file like the above and add it to the list
import { cs_CZ } from "@/keyboardLayouts/cs_CZ"
import { de_CH } from "@/keyboardLayouts/de_CH"
import { de_DE } from "@/keyboardLayouts/de_DE"
import { en_US } from "@/keyboardLayouts/en_US"
import { en_UK } from "@/keyboardLayouts/en_UK"
import { es_ES } from "@/keyboardLayouts/es_ES"
import { fr_BE } from "@/keyboardLayouts/fr_BE"
import { fr_CH } from "@/keyboardLayouts/fr_CH"
import { fr_FR } from "@/keyboardLayouts/fr_FR"
import { it_IT } from "@/keyboardLayouts/it_IT"
import { nb_NO } from "@/keyboardLayouts/nb_NO"
import { sv_SE } from "@/keyboardLayouts/sv_SE"
import { da_DK } from "@/keyboardLayouts/da_DK"
import { cs_CZ } from "@/keyboardLayouts/cs_CZ";
import { de_CH } from "@/keyboardLayouts/de_CH";
import { de_DE } from "@/keyboardLayouts/de_DE";
import { en_US } from "@/keyboardLayouts/en_US";
import { en_UK } from "@/keyboardLayouts/en_UK";
import { es_ES } from "@/keyboardLayouts/es_ES";
import { fr_BE } from "@/keyboardLayouts/fr_BE";
import { fr_CH } from "@/keyboardLayouts/fr_CH";
import { fr_FR } from "@/keyboardLayouts/fr_FR";
import { it_IT } from "@/keyboardLayouts/it_IT";
import { nb_NO } from "@/keyboardLayouts/nb_NO";
import { sv_SE } from "@/keyboardLayouts/sv_SE";
import { da_DK } from "@/keyboardLayouts/da_DK";
export const keyboards: KeyboardLayout[] = [cs_CZ, de_CH, de_DE, en_UK, en_US, es_ES, fr_BE, fr_CH, fr_FR, it_IT, nb_NO, sv_SE, da_DK];
export const keyboards: KeyboardLayout[] = [
cs_CZ,
de_CH,
de_DE,
en_UK,
en_US,
es_ES,
fr_BE,
fr_CH,
fr_FR,
it_IT,
nb_NO,
sv_SE,
da_DK,
];

View File

@ -1,231 +1,231 @@
import { KeyboardLayout, KeyCombo } from "../keyboardLayouts"
import { KeyboardLayout, KeyCombo } from "../keyboardLayouts";
import { en_US } from "./en_US" // for fallback of keyDisplayMap, modifierDisplayMap, and virtualKeyboard
import { en_US } from "./en_US"; // for fallback of keyDisplayMap, modifierDisplayMap, and virtualKeyboard
const name = "Čeština";
const isoCode = "cs-CZ";
const keyTrema: KeyCombo = { key: "Backslash" } // tréma (umlaut), two dots placed above a vowel
const keyAcute: KeyCombo = { key: "Equal" } // accent aigu (acute accent), mark ´ placed above the letter
const keyHat: KeyCombo = { key: "Digit3", shift: true, altRight: true } // accent circonflexe (accent hat), mark ^ placed above the letter
const keyCaron: KeyCombo = { key: "Equal", shift: true } // caron or haček (inverted hat), mark ˇ placed above the letter
const keyGrave: KeyCombo = { key: "Digit7", shift: true, altRight: true } // accent grave, mark ` placed above the letter
const keyTilde: KeyCombo = { key: "Digit1", shift: true, altRight: true } // tilde, mark ~ placed above the letter
const keyRing: KeyCombo = { key: "Backquote", shift: true } // kroužek (little ring), mark ° placed above the letter
const keyOverdot: KeyCombo = { key: "Digit8", shift: true, altRight: true } // overdot (dot above), mark ˙ placed above the letter
const keyHook: KeyCombo = { key: "Digit6", shift: true, altRight: true } // ogonoek (little hook), mark ˛ placed beneath a letter
const keyCedille: KeyCombo = { key: "Equal", shift: true, altRight: true } // accent cedille (cedilla), mark ¸ placed beneath a letter
const keyTrema: KeyCombo = { key: "Backslash" }; // tréma (umlaut), two dots placed above a vowel
const keyAcute: KeyCombo = { key: "Equal" }; // accent aigu (acute accent), mark ´ placed above the letter
const keyHat: KeyCombo = { key: "Digit3", shift: true, altRight: true }; // accent circonflexe (accent hat), mark ^ placed above the letter
const keyCaron: KeyCombo = { key: "Equal", shift: true }; // caron or haček (inverted hat), mark ˇ placed above the letter
const keyGrave: KeyCombo = { key: "Digit7", shift: true, altRight: true }; // accent grave, mark ` placed above the letter
const keyTilde: KeyCombo = { key: "Digit1", shift: true, altRight: true }; // tilde, mark ~ placed above the letter
const keyRing: KeyCombo = { key: "Backquote", shift: true }; // kroužek (little ring), mark ° placed above the letter
const keyOverdot: KeyCombo = { key: "Digit8", shift: true, altRight: true }; // overdot (dot above), mark ˙ placed above the letter
const keyHook: KeyCombo = { key: "Digit6", shift: true, altRight: true }; // ogonoek (little hook), mark ˛ placed beneath a letter
const keyCedille: KeyCombo = { key: "Equal", shift: true, altRight: true }; // accent cedille (cedilla), mark ¸ placed beneath a letter
const chars = {
A: { key: "KeyA", shift: true },
"Ä": { key: "KeyA", shift: true, accentKey: keyTrema },
"Á": { key: "KeyA", shift: true, accentKey: keyAcute },
"Â": { key: "KeyA", shift: true, accentKey: keyHat },
"À": { key: "KeyA", shift: true, accentKey: keyGrave },
"Ã": { key: "KeyA", shift: true, accentKey: keyTilde },
"Ȧ": { key: "KeyA", shift: true, accentKey: keyOverdot },
"Ą": { key: "KeyA", shift: true, accentKey: keyHook },
Ä: { key: "KeyA", shift: true, accentKey: keyTrema },
Á: { key: "KeyA", shift: true, accentKey: keyAcute },
Â: { key: "KeyA", shift: true, accentKey: keyHat },
À: { key: "KeyA", shift: true, accentKey: keyGrave },
Ã: { key: "KeyA", shift: true, accentKey: keyTilde },
Ȧ: { key: "KeyA", shift: true, accentKey: keyOverdot },
Ą: { key: "KeyA", shift: true, accentKey: keyHook },
B: { key: "KeyB", shift: true },
"Ḃ": { key: "KeyB", shift: true, accentKEy: keyOverdot },
: { key: "KeyB", shift: true, accentKEy: keyOverdot },
C: { key: "KeyC", shift: true },
"Č": { key: "KeyC", shift: true, accentKey: keyCaron },
"Ċ": { key: "KeyC", shift: true, accentKey: keyOverdot },
"Ç": { key: "KeyC", shift: true, accentKey: keyCedille },
Č: { key: "KeyC", shift: true, accentKey: keyCaron },
Ċ: { key: "KeyC", shift: true, accentKey: keyOverdot },
Ç: { key: "KeyC", shift: true, accentKey: keyCedille },
D: { key: "KeyD", shift: true },
"Ď": { key: "KeyD", shift: true, accentKey: keyCaron },
"Ḋ": { key: "KeyD", shift: true, accentKey: keyOverdot },
Ď: { key: "KeyD", shift: true, accentKey: keyCaron },
: { key: "KeyD", shift: true, accentKey: keyOverdot },
E: { key: "KeyE", shift: true },
"Ë": { key: "KeyE", shift: true, accentKey: keyTrema },
"É": { key: "KeyE", shift: true, accentKey: keyAcute },
"Ê": { key: "KeyE", shift: true, accentKey: keyHat },
"Ě": { key: "KeyE", shift: true, accentKey: keyCaron },
"È": { key: "KeyE", shift: true, accentKey: keyGrave },
"Ẽ": { key: "KeyE", shift: true, accentKey: keyTilde },
"Ė": { key: "KeyE", shift: true, accentKEy: keyOverdot },
"Ę": { key: "KeyE", shift: true, accentKey: keyHook },
Ë: { key: "KeyE", shift: true, accentKey: keyTrema },
É: { key: "KeyE", shift: true, accentKey: keyAcute },
Ê: { key: "KeyE", shift: true, accentKey: keyHat },
Ě: { key: "KeyE", shift: true, accentKey: keyCaron },
È: { key: "KeyE", shift: true, accentKey: keyGrave },
: { key: "KeyE", shift: true, accentKey: keyTilde },
Ė: { key: "KeyE", shift: true, accentKEy: keyOverdot },
Ę: { key: "KeyE", shift: true, accentKey: keyHook },
F: { key: "KeyF", shift: true },
"Ḟ": { key: "KeyF", shift: true, accentKey: keyOverdot },
: { key: "KeyF", shift: true, accentKey: keyOverdot },
G: { key: "KeyG", shift: true },
"Ġ": { key: "KeyG", shift: true, accentKey: keyOverdot },
Ġ: { key: "KeyG", shift: true, accentKey: keyOverdot },
H: { key: "KeyH", shift: true },
"Ḣ": { key: "KeyH", shift: true, accentKey: keyOverdot },
: { key: "KeyH", shift: true, accentKey: keyOverdot },
I: { key: "KeyI", shift: true },
"Ï": { key: "KeyI", shift: true, accentKey: keyTrema },
"Í": { key: "KeyI", shift: true, accentKey: keyAcute },
"Î": { key: "KeyI", shift: true, accentKey: keyHat },
"Ì": { key: "KeyI", shift: true, accentKey: keyGrave },
"Ĩ": { key: "KeyI", shift: true, accentKey: keyTilde },
"İ": { key: "KeyI", shift: true, accentKey: keyOverdot },
"Į": { key: "KeyI", shift: true, accentKey: keyHook },
Ï: { key: "KeyI", shift: true, accentKey: keyTrema },
Í: { key: "KeyI", shift: true, accentKey: keyAcute },
Î: { key: "KeyI", shift: true, accentKey: keyHat },
Ì: { key: "KeyI", shift: true, accentKey: keyGrave },
Ĩ: { key: "KeyI", shift: true, accentKey: keyTilde },
İ: { key: "KeyI", shift: true, accentKey: keyOverdot },
Į: { key: "KeyI", shift: true, accentKey: keyHook },
J: { key: "KeyJ", shift: true },
K: { key: "KeyK", shift: true },
L: { key: "KeyL", shift: true },
"Ŀ": { key: "KeyL", shift: true },
Ŀ: { key: "KeyL", shift: true },
M: { key: "KeyM", shift: true },
"Ṁ": { key: "KeyM", shift: true },
: { key: "KeyM", shift: true },
N: { key: "KeyN", shift: true },
"Ň": { key: "KeyN", shift: true, accentKey: keyCaron },
"Ñ": { key: "KeyN", shift: true, accentKey: keyTilde },
"Ṅ": { key: "KeyN", shift: true, accentKEy: keyOverdot },
Ň: { key: "KeyN", shift: true, accentKey: keyCaron },
Ñ: { key: "KeyN", shift: true, accentKey: keyTilde },
: { key: "KeyN", shift: true, accentKEy: keyOverdot },
O: { key: "KeyO", shift: true },
"Ö": { key: "KeyO", shift: true, accentKey: keyTrema },
"Ó": { key: "KeyO", shift: true, accentKey: keyAcute },
"Ô": { key: "KeyO", shift: true, accentKey: keyHat },
"Ò": { key: "KeyO", shift: true, accentKey: keyGrave },
"Õ": { key: "KeyO", shift: true, accentKey: keyTilde },
"Ȯ": { key: "KeyO", shift: true, accentKey: keyOverdot },
"Ǫ": { key: "KeyO", shift: true, accentKey: keyHook },
Ö: { key: "KeyO", shift: true, accentKey: keyTrema },
Ó: { key: "KeyO", shift: true, accentKey: keyAcute },
Ô: { key: "KeyO", shift: true, accentKey: keyHat },
Ò: { key: "KeyO", shift: true, accentKey: keyGrave },
Õ: { key: "KeyO", shift: true, accentKey: keyTilde },
Ȯ: { key: "KeyO", shift: true, accentKey: keyOverdot },
Ǫ: { key: "KeyO", shift: true, accentKey: keyHook },
P: { key: "KeyP", shift: true },
"Ṗ": { key: "KeyP", shift: true, accentKey: keyOverdot },
: { key: "KeyP", shift: true, accentKey: keyOverdot },
Q: { key: "KeyQ", shift: true },
R: { key: "KeyR", shift: true },
"Ř": { key: "KeyR", shift: true, accentKey: keyCaron },
"Ṙ": { key: "KeyR", shift: true, accentKey: keyOverdot },
Ř: { key: "KeyR", shift: true, accentKey: keyCaron },
: { key: "KeyR", shift: true, accentKey: keyOverdot },
S: { key: "KeyS", shift: true },
"Š": { key: "KeyS", shift: true, accentKey: keyCaron },
"Ṡ": { key: "KeyS", shift: true, accentKey: keyOverdot },
Š: { key: "KeyS", shift: true, accentKey: keyCaron },
: { key: "KeyS", shift: true, accentKey: keyOverdot },
T: { key: "KeyT", shift: true },
"Ť": { key: "KeyT", shift: true, accentKey: keyCaron },
"Ṫ": { key: "KeyT", shift: true, accentKey: keyOverdot },
Ť: { key: "KeyT", shift: true, accentKey: keyCaron },
: { key: "KeyT", shift: true, accentKey: keyOverdot },
U: { key: "KeyU", shift: true },
"Ü": { key: "KeyU", shift: true, accentKey: keyTrema },
"Ú": { key: "KeyU", shift: true, accentKey: keyAcute },
"Û": { key: "KeyU", shift: true, accentKey: keyHat },
"Ù": { key: "KeyU", shift: true, accentKey: keyGrave },
"Ũ": { key: "KeyU", shift: true, accentKey: keyTilde },
"Ů": { key: "KeyU", shift: true, accentKey: keyRing },
"Ų": { key: "KeyU", shift: true, accentKey: keyHook },
Ü: { key: "KeyU", shift: true, accentKey: keyTrema },
Ú: { key: "KeyU", shift: true, accentKey: keyAcute },
Û: { key: "KeyU", shift: true, accentKey: keyHat },
Ù: { key: "KeyU", shift: true, accentKey: keyGrave },
Ũ: { key: "KeyU", shift: true, accentKey: keyTilde },
Ů: { key: "KeyU", shift: true, accentKey: keyRing },
Ų: { key: "KeyU", shift: true, accentKey: keyHook },
V: { key: "KeyV", shift: true },
W: { key: "KeyW", shift: true },
"Ẇ": { key: "KeyW", shift: true, accentKey: keyOverdot },
: { key: "KeyW", shift: true, accentKey: keyOverdot },
X: { key: "KeyX", shift: true },
"Ẋ": { key: "KeyX", shift: true, accentKey: keyOverdot },
: { key: "KeyX", shift: true, accentKey: keyOverdot },
Y: { key: "KeyY", shift: true },
"Ý": { key: "KeyY", shift: true, accentKey: keyAcute },
"Ẏ": { key: "KeyY", shift: true, accentKey: keyOverdot },
Ý: { key: "KeyY", shift: true, accentKey: keyAcute },
: { key: "KeyY", shift: true, accentKey: keyOverdot },
Z: { key: "KeyZ", shift: true },
"Ż": { key: "KeyZ", shift: true, accentKey: keyOverdot },
Ż: { key: "KeyZ", shift: true, accentKey: keyOverdot },
a: { key: "KeyA" },
"ä": { key: "KeyA", accentKey: keyTrema },
"â": { key: "KeyA", accentKey: keyHat },
"à": { key: "KeyA", accentKey: keyGrave },
"ã": { key: "KeyA", accentKey: keyTilde },
"ȧ": { key: "KeyA", accentKey: keyOverdot },
"ą": { key: "KeyA", accentKey: keyHook },
ä: { key: "KeyA", accentKey: keyTrema },
â: { key: "KeyA", accentKey: keyHat },
à: { key: "KeyA", accentKey: keyGrave },
ã: { key: "KeyA", accentKey: keyTilde },
ȧ: { key: "KeyA", accentKey: keyOverdot },
ą: { key: "KeyA", accentKey: keyHook },
b: { key: "KeyB" },
"{": { key: "KeyB", altRight: true },
"ḃ": { key: "KeyB", accentKey: keyOverdot },
: { key: "KeyB", accentKey: keyOverdot },
c: { key: "KeyC" },
"&": { key: "KeyC", altRight: true },
"ç": { key: "KeyC", accentKey: keyCedille },
"ċ": { key: "KeyC", accentKey: keyOverdot },
ç: { key: "KeyC", accentKey: keyCedille },
ċ: { key: "KeyC", accentKey: keyOverdot },
d: { key: "KeyD" },
"ď": { key: "KeyD", accentKey: keyCaron },
"ḋ": { key: "KeyD", accentKey: keyOverdot },
"Đ": { key: "KeyD", altRight: true },
ď: { key: "KeyD", accentKey: keyCaron },
: { key: "KeyD", accentKey: keyOverdot },
Đ: { key: "KeyD", altRight: true },
e: { key: "KeyE" },
"ë": { key: "KeyE", accentKey: keyTrema },
"ê": { key: "KeyE", accentKey: keyHat },
"ẽ": { key: "KeyE", accentKey: keyTilde },
"è": { key: "KeyE", accentKey: keyGrave },
"ė": { key: "KeyE", accentKey: keyOverdot },
"ę": { key: "KeyE", accentKey: keyHook },
ë: { key: "KeyE", accentKey: keyTrema },
ê: { key: "KeyE", accentKey: keyHat },
: { key: "KeyE", accentKey: keyTilde },
è: { key: "KeyE", accentKey: keyGrave },
ė: { key: "KeyE", accentKey: keyOverdot },
ę: { key: "KeyE", accentKey: keyHook },
"€": { key: "KeyE", altRight: true },
f: { key: "KeyF" },
"ḟ": { key: "KeyF", accentKey: keyOverdot },
: { key: "KeyF", accentKey: keyOverdot },
"[": { key: "KeyF", altRight: true },
g: { key: "KeyG" },
"ġ": { key: "KeyG", accentKey: keyOverdot },
ġ: { key: "KeyG", accentKey: keyOverdot },
"]": { key: "KeyF", altRight: true },
h: { key: "KeyH" },
"ḣ": { key: "KeyH", accentKey: keyOverdot },
: { key: "KeyH", accentKey: keyOverdot },
i: { key: "KeyI" },
"ï": { key: "KeyI", accentKey: keyTrema },
"î": { key: "KeyI", accentKey: keyHat },
"ì": { key: "KeyI", accentKey: keyGrave },
"ĩ": { key: "KeyI", accentKey: keyTilde },
"ı": { key: "KeyI", accentKey: keyOverdot },
"į": { key: "KeyI", accentKey: keyHook },
ï: { key: "KeyI", accentKey: keyTrema },
î: { key: "KeyI", accentKey: keyHat },
ì: { key: "KeyI", accentKey: keyGrave },
ĩ: { key: "KeyI", accentKey: keyTilde },
ı: { key: "KeyI", accentKey: keyOverdot },
į: { key: "KeyI", accentKey: keyHook },
j: { key: "KeyJ" },
"ȷ": { key: "KeyJ", accentKey: keyOverdot },
ȷ: { key: "KeyJ", accentKey: keyOverdot },
k: { key: "KeyK" },
"ł": { key: "KeyK", altRight: true },
ł: { key: "KeyK", altRight: true },
l: { key: "KeyL" },
"ŀ": { key: "KeyL", accentKey: keyOverdot },
"Ł": { key: "KeyL", altRight: true },
ŀ: { key: "KeyL", accentKey: keyOverdot },
Ł: { key: "KeyL", altRight: true },
m: { key: "KeyM" },
"ṁ": { key: "KeyM", accentKey: keyOverdot },
: { key: "KeyM", accentKey: keyOverdot },
n: { key: "KeyN" },
"}": { key: "KeyN", altRight: true },
"ň": { key: "KeyN", accentKey: keyCaron },
"ñ": { key: "KeyN", accentKey: keyTilde },
"ṅ": { key: "KeyN", accentKey: keyOverdot },
ň: { key: "KeyN", accentKey: keyCaron },
ñ: { key: "KeyN", accentKey: keyTilde },
: { key: "KeyN", accentKey: keyOverdot },
o: { key: "KeyO" },
"ö": { key: "Key0", accentKey: keyTrema },
"ó": { key: "KeyO", accentKey: keyAcute },
"ô": { key: "KeyO", accentKey: keyHat },
"ò": { key: "KeyO", accentKey: keyGrave },
"õ": { key: "KeyO", accentKey: keyTilde },
"ȯ": { key: "KeyO", accentKey: keyOverdot },
"ǫ": { key: "KeyO", accentKey: keyHook },
ö: { key: "Key0", accentKey: keyTrema },
ó: { key: "KeyO", accentKey: keyAcute },
ô: { key: "KeyO", accentKey: keyHat },
ò: { key: "KeyO", accentKey: keyGrave },
õ: { key: "KeyO", accentKey: keyTilde },
ȯ: { key: "KeyO", accentKey: keyOverdot },
ǫ: { key: "KeyO", accentKey: keyHook },
p: { key: "KeyP" },
"ṗ": { key: "KeyP", accentKey: keyOverdot },
: { key: "KeyP", accentKey: keyOverdot },
q: { key: "KeyQ" },
r: { key: "KeyR" },
"ṙ": { key: "KeyR", accentKey: keyOverdot },
: { key: "KeyR", accentKey: keyOverdot },
s: { key: "KeyS" },
"ṡ": { key: "KeyS", accentKey: keyOverdot },
"đ": { key: "KeyS", altRight: true },
: { key: "KeyS", accentKey: keyOverdot },
đ: { key: "KeyS", altRight: true },
t: { key: "KeyT" },
"ť": { key: "KeyT", accentKey: keyCaron },
"ṫ": { key: "KeyT", accentKey: keyOverdot },
ť: { key: "KeyT", accentKey: keyCaron },
: { key: "KeyT", accentKey: keyOverdot },
u: { key: "KeyU" },
"ü": { key: "KeyU", accentKey: keyTrema },
"û": { key: "KeyU", accentKey: keyHat },
"ù": { key: "KeyU", accentKey: keyGrave },
"ũ": { key: "KeyU", accentKey: keyTilde },
"ų": { key: "KeyU", accentKey: keyHook },
ü: { key: "KeyU", accentKey: keyTrema },
û: { key: "KeyU", accentKey: keyHat },
ù: { key: "KeyU", accentKey: keyGrave },
ũ: { key: "KeyU", accentKey: keyTilde },
ų: { key: "KeyU", accentKey: keyHook },
v: { key: "KeyV" },
"@": { key: "KeyV", altRight: true },
w: { key: "KeyW" },
"ẇ": { key: "KeyW", accentKey: keyOverdot },
: { key: "KeyW", accentKey: keyOverdot },
x: { key: "KeyX" },
"#": { key: "KeyX", altRight: true },
"ẋ": { key: "KeyX", accentKey: keyOverdot },
: { key: "KeyX", accentKey: keyOverdot },
y: { key: "KeyY" },
"ẏ": { key: "KeyY", accentKey: keyOverdot },
: { key: "KeyY", accentKey: keyOverdot },
z: { key: "KeyZ" },
"ż": { key: "KeyZ", accentKey: keyOverdot },
ż: { key: "KeyZ", accentKey: keyOverdot },
";": { key: "Backquote" },
"°": { key: "Backquote", shift: true, deadKey: true },
"+": { key: "Digit1" },
1: { key: "Digit1", shift: true },
"ě": { key: "Digit2" },
ě: { key: "Digit2" },
2: { key: "Digit2", shift: true },
"š": { key: "Digit3" },
š: { key: "Digit3" },
3: { key: "Digit3", shift: true },
"č": { key: "Digit4" },
č: { key: "Digit4" },
4: { key: "Digit4", shift: true },
"ř": { key: "Digit5" },
ř: { key: "Digit5" },
5: { key: "Digit5", shift: true },
"ž": { key: "Digit6" },
ž: { key: "Digit6" },
6: { key: "Digit6", shift: true },
"ý": { key: "Digit7" },
ý: { key: "Digit7" },
7: { key: "Digit7", shift: true },
"á": { key: "Digit8" },
á: { key: "Digit8" },
8: { key: "Digit8", shift: true },
"í": { key: "Digit9" },
í: { key: "Digit9" },
9: { key: "Digit9", shift: true },
"é": { key: "Digit0" },
é: { key: "Digit0" },
0: { key: "Digit0", shift: true },
"=": { key: "Minus" },
"%": { key: "Minus", shift: true },
"ú": { key: "BracketLeft" },
ú: { key: "BracketLeft" },
"/": { key: "BracketLeft", shift: true },
")": { key: "BracketRight" },
"(": { key: "BracketRight", shift: true },
"ů": { key: "Semicolon" },
"\"": { key: "Semicolon", shift: true },
ů: { key: "Semicolon" },
'"': { key: "Semicolon", shift: true },
"§": { key: "Quote" },
"!": { key: "Quote", shift: true },
"'": { key: "Backslash", shift: true },
@ -236,7 +236,7 @@ const chars = {
":": { key: "Period", shift: true },
">": { key: "Period", altRight: true },
"-": { key: "Slash" },
"_": { key: "Slash", shift: true },
_: { key: "Slash", shift: true },
"*": { key: "Slash", altRight: true },
"\\": { key: "IntlBackslash" },
"|": { key: "IntlBackslash", shift: true },
@ -253,5 +253,5 @@ export const cs_CZ: KeyboardLayout = {
// TODO need to localize these maps and layouts
keyDisplayMap: en_US.keyDisplayMap,
modifierDisplayMap: en_US.modifierDisplayMap,
virtualKeyboard: en_US.virtualKeyboard
virtualKeyboard: en_US.virtualKeyboard,
};

View File

@ -1,133 +1,133 @@
import { KeyboardLayout, KeyCombo } from "../keyboardLayouts"
import { KeyboardLayout, KeyCombo } from "../keyboardLayouts";
import { en_US } from "./en_US" // for fallback of keyDisplayMap, modifierDisplayMap, and virtualKeyboard
import { en_US } from "./en_US"; // for fallback of keyDisplayMap, modifierDisplayMap, and virtualKeyboard
export const name = "Dansk";
const isoCode = "da-DK";
const keyTrema = { key: "BracketRight" }
const keyAcute = { key: "Equal", altRight: true }
const keyHat = { key: "BracketRight", shift: true }
const keyGrave = { key: "Equal", shift: true }
const keyTilde = { key: "BracketRight", altRight: true }
const keyTrema = { key: "BracketRight" };
const keyAcute = { key: "Equal", altRight: true };
const keyHat = { key: "BracketRight", shift: true };
const keyGrave = { key: "Equal", shift: true };
const keyTilde = { key: "BracketRight", altRight: true };
export const chars = {
A: { key: "KeyA", shift: true },
"Ä": { key: "KeyA", shift: true, accentKey: keyTrema },
"Á": { key: "KeyA", shift: true, accentKey: keyAcute },
"Â": { key: "KeyA", shift: true, accentKey: keyHat },
"À": { key: "KeyA", shift: true, accentKey: keyGrave },
"Ã": { key: "KeyA", shift: true, accentKey: keyTilde },
Ä: { key: "KeyA", shift: true, accentKey: keyTrema },
Á: { key: "KeyA", shift: true, accentKey: keyAcute },
Â: { key: "KeyA", shift: true, accentKey: keyHat },
À: { key: "KeyA", shift: true, accentKey: keyGrave },
Ã: { key: "KeyA", shift: true, accentKey: keyTilde },
B: { key: "KeyB", shift: true },
C: { key: "KeyC", shift: true },
D: { key: "KeyD", shift: true },
E: { key: "KeyE", shift: true },
"Ë": { key: "KeyE", shift: true, accentKey: keyTrema },
"É": { key: "KeyE", shift: true, accentKey: keyAcute },
"Ê": { key: "KeyE", shift: true, accentKey: keyHat },
"È": { key: "KeyE", shift: true, accentKey: keyGrave },
"Ẽ": { key: "KeyE", shift: true, accentKey: keyTilde },
Ë: { key: "KeyE", shift: true, accentKey: keyTrema },
É: { key: "KeyE", shift: true, accentKey: keyAcute },
Ê: { key: "KeyE", shift: true, accentKey: keyHat },
È: { key: "KeyE", shift: true, accentKey: keyGrave },
: { key: "KeyE", shift: true, accentKey: keyTilde },
F: { key: "KeyF", shift: true },
G: { key: "KeyG", shift: true },
H: { key: "KeyH", shift: true },
I: { key: "KeyI", shift: true },
"Ï": { key: "KeyI", shift: true, accentKey: keyTrema },
"Í": { key: "KeyI", shift: true, accentKey: keyAcute },
"Î": { key: "KeyI", shift: true, accentKey: keyHat },
"Ì": { key: "KeyI", shift: true, accentKey: keyGrave },
"Ĩ": { key: "KeyI", shift: true, accentKey: keyTilde },
Ï: { key: "KeyI", shift: true, accentKey: keyTrema },
Í: { key: "KeyI", shift: true, accentKey: keyAcute },
Î: { key: "KeyI", shift: true, accentKey: keyHat },
Ì: { key: "KeyI", shift: true, accentKey: keyGrave },
Ĩ: { key: "KeyI", shift: true, accentKey: keyTilde },
J: { key: "KeyJ", shift: true },
K: { key: "KeyK", shift: true },
L: { key: "KeyL", shift: true },
M: { key: "KeyM", shift: true },
N: { key: "KeyN", shift: true },
O: { key: "KeyO", shift: true },
"Ö": { key: "KeyO", shift: true, accentKey: keyTrema },
"Ó": { key: "KeyO", shift: true, accentKey: keyAcute },
"Ô": { key: "KeyO", shift: true, accentKey: keyHat },
"Ò": { key: "KeyO", shift: true, accentKey: keyGrave },
"Õ": { key: "KeyO", shift: true, accentKey: keyTilde },
Ö: { key: "KeyO", shift: true, accentKey: keyTrema },
Ó: { key: "KeyO", shift: true, accentKey: keyAcute },
Ô: { key: "KeyO", shift: true, accentKey: keyHat },
Ò: { key: "KeyO", shift: true, accentKey: keyGrave },
Õ: { key: "KeyO", shift: true, accentKey: keyTilde },
P: { key: "KeyP", shift: true },
Q: { key: "KeyQ", shift: true },
R: { key: "KeyR", shift: true },
S: { key: "KeyS", shift: true },
T: { key: "KeyT", shift: true },
U: { key: "KeyU", shift: true },
"Ü": { key: "KeyU", shift: true, accentKey: keyTrema },
"Ú": { key: "KeyU", shift: true, accentKey: keyAcute },
"Û": { key: "KeyU", shift: true, accentKey: keyHat },
"Ù": { key: "KeyU", shift: true, accentKey: keyGrave },
"Ũ": { key: "KeyU", shift: true, accentKey: keyTilde },
Ü: { key: "KeyU", shift: true, accentKey: keyTrema },
Ú: { key: "KeyU", shift: true, accentKey: keyAcute },
Û: { key: "KeyU", shift: true, accentKey: keyHat },
Ù: { key: "KeyU", shift: true, accentKey: keyGrave },
Ũ: { key: "KeyU", shift: true, accentKey: keyTilde },
V: { key: "KeyV", shift: true },
W: { key: "KeyW", shift: true },
X: { key: "KeyX", shift: true },
Y: { key: "KeyY", shift: true },
Z: { key: "KeyZ", shift: true },
a: { key: "KeyA" },
"ä": { key: "KeyA", accentKey: keyTrema },
"á": { key: "KeyA", accentKey: keyAcute },
"â": { key: "KeyA", accentKey: keyHat },
"à": { key: "KeyA", accentKey: keyGrave },
"ã": { key: "KeyA", accentKey: keyTilde },
ä: { key: "KeyA", accentKey: keyTrema },
á: { key: "KeyA", accentKey: keyAcute },
â: { key: "KeyA", accentKey: keyHat },
à: { key: "KeyA", accentKey: keyGrave },
ã: { key: "KeyA", accentKey: keyTilde },
b: { key: "KeyB" },
c: { key: "KeyC" },
d: { key: "KeyD" },
e: { key: "KeyE" },
"ë": { key: "KeyE", accentKey: keyTrema },
"é": { key: "KeyE", accentKey: keyAcute },
"ê": { key: "KeyE", accentKey: keyHat },
"è": { key: "KeyE", accentKey: keyGrave },
"ẽ": { key: "KeyE", accentKey: keyTilde },
ë: { key: "KeyE", accentKey: keyTrema },
é: { key: "KeyE", accentKey: keyAcute },
ê: { key: "KeyE", accentKey: keyHat },
è: { key: "KeyE", accentKey: keyGrave },
: { key: "KeyE", accentKey: keyTilde },
"€": { key: "KeyE", altRight: true },
f: { key: "KeyF" },
g: { key: "KeyG" },
h: { key: "KeyH" },
i: { key: "KeyI" },
"ï": { key: "KeyI", accentKey: keyTrema },
"í": { key: "KeyI", accentKey: keyAcute },
"î": { key: "KeyI", accentKey: keyHat },
"ì": { key: "KeyI", accentKey: keyGrave },
"ĩ": { key: "KeyI", accentKey: keyTilde },
ï: { key: "KeyI", accentKey: keyTrema },
í: { key: "KeyI", accentKey: keyAcute },
î: { key: "KeyI", accentKey: keyHat },
ì: { key: "KeyI", accentKey: keyGrave },
ĩ: { key: "KeyI", accentKey: keyTilde },
j: { key: "KeyJ" },
k: { key: "KeyK" },
l: { key: "KeyL" },
m: { key: "KeyM" },
n: { key: "KeyN" },
o: { key: "KeyO" },
"ö": { key: "KeyO", accentKey: keyTrema },
"ó": { key: "KeyO", accentKey: keyAcute },
"ô": { key: "KeyO", accentKey: keyHat },
"ò": { key: "KeyO", accentKey: keyGrave },
"õ": { key: "KeyO", accentKey: keyTilde },
ö: { key: "KeyO", accentKey: keyTrema },
ó: { key: "KeyO", accentKey: keyAcute },
ô: { key: "KeyO", accentKey: keyHat },
ò: { key: "KeyO", accentKey: keyGrave },
õ: { key: "KeyO", accentKey: keyTilde },
p: { key: "KeyP" },
q: { key: "KeyQ" },
r: { key: "KeyR" },
s: { key: "KeyS" },
t: { key: "KeyT" },
u: { key: "KeyU" },
"ü": { key: "KeyU", accentKey: keyTrema },
"ú": { key: "KeyU", accentKey: keyAcute },
"û": { key: "KeyU", accentKey: keyHat },
"ù": { key: "KeyU", accentKey: keyGrave },
"ũ": { key: "KeyU", accentKey: keyTilde },
ü: { key: "KeyU", accentKey: keyTrema },
ú: { key: "KeyU", accentKey: keyAcute },
û: { key: "KeyU", accentKey: keyHat },
ù: { key: "KeyU", accentKey: keyGrave },
ũ: { key: "KeyU", accentKey: keyTilde },
v: { key: "KeyV" },
w: { key: "KeyW" },
x: { key: "KeyX" },
y: { key: "KeyY" }, // <-- corrected
z: { key: "KeyZ" }, // <-- corrected
y: { key: "KeyY" }, // <-- corrected
z: { key: "KeyZ" }, // <-- corrected
"½": { key: "Backquote" },
"§": { key: "Backquote", shift: true },
1: { key: "Digit1" },
"!": { key: "Digit1", shift: true },
2: { key: "Digit2" },
"\"": { key: "Digit2", shift: true },
'"': { key: "Digit2", shift: true },
"@": { key: "Digit2", altRight: true },
3: { key: "Digit3" },
"#": { key: "Digit3", shift: true },
"£": { key: "Digit3", altRight: true },
4: { key: "Digit4" },
"¤": { key: "Digit4", shift: true },
"$": { key: "Digit4", altRight: true },
$: { key: "Digit4", altRight: true },
5: { key: "Digit5" },
"%": { key: "Digit5", shift: true },
6: { key: "Digit6" },
@ -147,12 +147,12 @@ export const chars = {
"+": { key: "Minus" },
"?": { key: "Minus", shift: true },
"\\": { key: "Equal" },
"å": { key: "BracketLeft" },
"Å": { key: "BracketLeft", shift: true },
"ø": { key: "Semicolon" },
"Ø": { key: "Semicolon", shift: true },
"æ": { key: "Quote" },
"Æ": { key: "Quote", shift: true },
å: { key: "BracketLeft" },
Å: { key: "BracketLeft", shift: true },
ø: { key: "Semicolon" },
Ø: { key: "Semicolon", shift: true },
æ: { key: "Quote" },
Æ: { key: "Quote", shift: true },
"'": { key: "Backslash" },
"*": { key: "Backslash", shift: true },
",": { key: "Comma" },
@ -160,20 +160,19 @@ export const chars = {
".": { key: "Period" },
":": { key: "Period", shift: true },
"-": { key: "Slash" },
"_": { key: "Slash", shift: true },
_: { key: "Slash", shift: true },
"<": { key: "IntlBackslash" },
">": { key: "IntlBackslash", shift: true },
"~": { key: "BracketRight", deadKey: true, altRight: true },
"^": { key: "BracketRight", deadKey: true, shift: true },
"¨": { key: "BracketRight", deadKey: true, },
"¨": { key: "BracketRight", deadKey: true },
"|": { key: "Equal", deadKey: true, altRight: true },
"`": { key: "Equal", deadKey: true, shift: true, },
"´": { key: "Equal", deadKey: true, },
"`": { key: "Equal", deadKey: true, shift: true },
"´": { key: "Equal", deadKey: true },
" ": { key: "Space" },
"\n": { key: "Enter" },
Enter: { key: "Enter" },
Tab: { key: "Tab" },
} as Record<string, KeyCombo>;
export const da_DK: KeyboardLayout = {
@ -183,5 +182,5 @@ export const da_DK: KeyboardLayout = {
// TODO need to localize these maps and layouts
keyDisplayMap: en_US.keyDisplayMap,
modifierDisplayMap: en_US.modifierDisplayMap,
virtualKeyboard: en_US.virtualKeyboard
virtualKeyboard: en_US.virtualKeyboard,
};

View File

@ -1,109 +1,109 @@
import { KeyboardLayout, KeyCombo } from "../keyboardLayouts"
import { KeyboardLayout, KeyCombo } from "../keyboardLayouts";
import { en_US } from "./en_US" // for fallback of keyDisplayMap, modifierDisplayMap, and virtualKeyboard
import { en_US } from "./en_US"; // for fallback of keyDisplayMap, modifierDisplayMap, and virtualKeyboard
const name = "Schwiizerdütsch";
const isoCode = "de-CH";
const keyTrema: KeyCombo = { key: "BracketRight" } // tréma (umlaut), two dots placed above a vowel
const keyAcute: KeyCombo = { key: "Minus", altRight: true } // accent aigu (acute accent), mark ´ placed above the letter
const keyHat: KeyCombo = { key: "Equal" } // accent circonflexe (accent hat), mark ^ placed above the letter
const keyGrave: KeyCombo = { key: "Equal", shift: true } // accent grave, mark ` placed above the letter
const keyTilde: KeyCombo = { key: "Equal", altRight: true } // tilde, mark ~ placed above the letter
const keyTrema: KeyCombo = { key: "BracketRight" }; // tréma (umlaut), two dots placed above a vowel
const keyAcute: KeyCombo = { key: "Minus", altRight: true }; // accent aigu (acute accent), mark ´ placed above the letter
const keyHat: KeyCombo = { key: "Equal" }; // accent circonflexe (accent hat), mark ^ placed above the letter
const keyGrave: KeyCombo = { key: "Equal", shift: true }; // accent grave, mark ` placed above the letter
const keyTilde: KeyCombo = { key: "Equal", altRight: true }; // tilde, mark ~ placed above the letter
const chars = {
A: { key: "KeyA", shift: true },
"Ä": { key: "KeyA", shift: true, accentKey: keyTrema },
"Á": { key: "KeyA", shift: true, accentKey: keyAcute },
"Â": { key: "KeyA", shift: true, accentKey: keyHat },
"À": { key: "KeyA", shift: true, accentKey: keyGrave },
"Ã": { key: "KeyA", shift: true, accentKey: keyTilde },
Ä: { key: "KeyA", shift: true, accentKey: keyTrema },
Á: { key: "KeyA", shift: true, accentKey: keyAcute },
Â: { key: "KeyA", shift: true, accentKey: keyHat },
À: { key: "KeyA", shift: true, accentKey: keyGrave },
Ã: { key: "KeyA", shift: true, accentKey: keyTilde },
B: { key: "KeyB", shift: true },
C: { key: "KeyC", shift: true },
D: { key: "KeyD", shift: true },
E: { key: "KeyE", shift: true },
"Ë": { key: "KeyE", shift: true, accentKey: keyTrema },
"É": { key: "KeyE", shift: true, accentKey: keyAcute },
"Ê": { key: "KeyE", shift: true, accentKey: keyHat },
"È": { key: "KeyE", shift: true, accentKey: keyGrave },
"Ẽ": { key: "KeyE", shift: true, accentKey: keyTilde },
Ë: { key: "KeyE", shift: true, accentKey: keyTrema },
É: { key: "KeyE", shift: true, accentKey: keyAcute },
Ê: { key: "KeyE", shift: true, accentKey: keyHat },
È: { key: "KeyE", shift: true, accentKey: keyGrave },
: { key: "KeyE", shift: true, accentKey: keyTilde },
F: { key: "KeyF", shift: true },
G: { key: "KeyG", shift: true },
H: { key: "KeyH", shift: true },
I: { key: "KeyI", shift: true },
"Ï": { key: "KeyI", shift: true, accentKey: keyTrema },
"Í": { key: "KeyI", shift: true, accentKey: keyAcute },
"Î": { key: "KeyI", shift: true, accentKey: keyHat },
"Ì": { key: "KeyI", shift: true, accentKey: keyGrave },
"Ĩ": { key: "KeyI", shift: true, accentKey: keyTilde },
Ï: { key: "KeyI", shift: true, accentKey: keyTrema },
Í: { key: "KeyI", shift: true, accentKey: keyAcute },
Î: { key: "KeyI", shift: true, accentKey: keyHat },
Ì: { key: "KeyI", shift: true, accentKey: keyGrave },
Ĩ: { key: "KeyI", shift: true, accentKey: keyTilde },
J: { key: "KeyJ", shift: true },
K: { key: "KeyK", shift: true },
L: { key: "KeyL", shift: true },
M: { key: "KeyM", shift: true },
N: { key: "KeyN", shift: true },
O: { key: "KeyO", shift: true },
"Ö": { key: "KeyO", shift: true, accentKey: keyTrema },
"Ó": { key: "KeyO", shift: true, accentKey: keyAcute },
"Ô": { key: "KeyO", shift: true, accentKey: keyHat },
"Ò": { key: "KeyO", shift: true, accentKey: keyGrave },
"Õ": { key: "KeyO", shift: true, accentKey: keyTilde },
Ö: { key: "KeyO", shift: true, accentKey: keyTrema },
Ó: { key: "KeyO", shift: true, accentKey: keyAcute },
Ô: { key: "KeyO", shift: true, accentKey: keyHat },
Ò: { key: "KeyO", shift: true, accentKey: keyGrave },
Õ: { key: "KeyO", shift: true, accentKey: keyTilde },
P: { key: "KeyP", shift: true },
Q: { key: "KeyQ", shift: true },
R: { key: "KeyR", shift: true },
S: { key: "KeyS", shift: true },
T: { key: "KeyT", shift: true },
U: { key: "KeyU", shift: true },
"Ü": { key: "KeyU", shift: true, accentKey: keyTrema },
"Ú": { key: "KeyU", shift: true, accentKey: keyAcute },
"Û": { key: "KeyU", shift: true, accentKey: keyHat },
"Ù": { key: "KeyU", shift: true, accentKey: keyGrave },
"Ũ": { key: "KeyU", shift: true, accentKey: keyTilde },
Ü: { key: "KeyU", shift: true, accentKey: keyTrema },
Ú: { key: "KeyU", shift: true, accentKey: keyAcute },
Û: { key: "KeyU", shift: true, accentKey: keyHat },
Ù: { key: "KeyU", shift: true, accentKey: keyGrave },
Ũ: { key: "KeyU", shift: true, accentKey: keyTilde },
V: { key: "KeyV", shift: true },
W: { key: "KeyW", shift: true },
X: { key: "KeyX", shift: true },
Y: { key: "KeyZ", shift: true },
Z: { key: "KeyY", shift: true },
a: { key: "KeyA" },
"á": { key: "KeyA", accentKey: keyAcute },
"â": { key: "KeyA", accentKey: keyHat },
"ã": { key: "KeyA", accentKey: keyTilde },
á: { key: "KeyA", accentKey: keyAcute },
â: { key: "KeyA", accentKey: keyHat },
ã: { key: "KeyA", accentKey: keyTilde },
b: { key: "KeyB" },
c: { key: "KeyC" },
d: { key: "KeyD" },
e: { key: "KeyE" },
"ë": { key: "KeyE", accentKey: keyTrema },
"ê": { key: "KeyE", accentKey: keyHat },
"ẽ": { key: "KeyE", accentKey: keyTilde },
ë: { key: "KeyE", accentKey: keyTrema },
ê: { key: "KeyE", accentKey: keyHat },
: { key: "KeyE", accentKey: keyTilde },
"€": { key: "KeyE", altRight: true },
f: { key: "KeyF" },
g: { key: "KeyG" },
h: { key: "KeyH" },
i: { key: "KeyI" },
"ï": { key: "KeyI", accentKey: keyTrema },
"í": { key: "KeyI", accentKey: keyAcute },
"î": { key: "KeyI", accentKey: keyHat },
"ì": { key: "KeyI", accentKey: keyGrave },
"ĩ": { key: "KeyI", accentKey: keyTilde },
ï: { key: "KeyI", accentKey: keyTrema },
í: { key: "KeyI", accentKey: keyAcute },
î: { key: "KeyI", accentKey: keyHat },
ì: { key: "KeyI", accentKey: keyGrave },
ĩ: { key: "KeyI", accentKey: keyTilde },
j: { key: "KeyJ" },
k: { key: "KeyK" },
l: { key: "KeyL" },
m: { key: "KeyM" },
n: { key: "KeyN" },
o: { key: "KeyO" },
"ó": { key: "KeyO", accentKey: keyAcute },
"ô": { key: "KeyO", accentKey: keyHat },
"ò": { key: "KeyO", accentKey: keyGrave },
"õ": { key: "KeyO", accentKey: keyTilde },
ó: { key: "KeyO", accentKey: keyAcute },
ô: { key: "KeyO", accentKey: keyHat },
ò: { key: "KeyO", accentKey: keyGrave },
õ: { key: "KeyO", accentKey: keyTilde },
p: { key: "KeyP" },
q: { key: "KeyQ" },
r: { key: "KeyR" },
s: { key: "KeyS" },
t: { key: "KeyT" },
u: { key: "KeyU" },
"ú": { key: "KeyU", accentKey: keyAcute },
"û": { key: "KeyU", accentKey: keyHat },
"ù": { key: "KeyU", accentKey: keyGrave },
"ũ": { key: "KeyU", accentKey: keyTilde },
ú: { key: "KeyU", accentKey: keyAcute },
û: { key: "KeyU", accentKey: keyHat },
ù: { key: "KeyU", accentKey: keyGrave },
ũ: { key: "KeyU", accentKey: keyTilde },
v: { key: "KeyV" },
w: { key: "KeyW" },
x: { key: "KeyX" },
@ -115,13 +115,13 @@ const chars = {
"+": { key: "Digit1", shift: true },
"|": { key: "Digit1", altRight: true },
2: { key: "Digit2" },
"\"": { key: "Digit2", shift: true },
'"': { key: "Digit2", shift: true },
"@": { key: "Digit2", altRight: true },
3: { key: "Digit3" },
"*": { key: "Digit3", shift: true },
"#": { key: "Digit3", altRight: true },
4: { key: "Digit4" },
"ç": { key: "Digit4", shift: true },
ç: { key: "Digit4", shift: true },
5: { key: "Digit5" },
"%": { key: "Digit5", shift: true },
6: { key: "Digit6" },
@ -139,17 +139,17 @@ const chars = {
"^": { key: "Equal", deadKey: true },
"`": { key: "Equal", shift: true },
"~": { key: "Equal", altRight: true, deadKey: true },
"ü": { key: "BracketLeft" },
"è": { key: "BracketLeft", shift: true },
ü: { key: "BracketLeft" },
è: { key: "BracketLeft", shift: true },
"[": { key: "BracketLeft", altRight: true },
"!": { key: "BracketRight", shift: true },
"]": { key: "BracketRight", altRight: true },
"ö": { key: "Semicolon" },
"é": { key: "Semicolon", shift: true },
"ä": { key: "Quote" },
"à": { key: "Quote", shift: true },
ö: { key: "Semicolon" },
é: { key: "Semicolon", shift: true },
ä: { key: "Quote" },
à: { key: "Quote", shift: true },
"{": { key: "Quote", altRight: true },
"$": { key: "Backslash" },
$: { key: "Backslash" },
"£": { key: "Backslash", shift: true },
"}": { key: "Backslash", altRight: true },
",": { key: "Comma" },
@ -157,7 +157,7 @@ const chars = {
".": { key: "Period" },
":": { key: "Period", shift: true },
"-": { key: "Slash" },
"_": { key: "Slash", shift: true },
_: { key: "Slash", shift: true },
"<": { key: "IntlBackslash" },
">": { key: "IntlBackslash", shift: true },
"\\": { key: "IntlBackslash", altRight: true },
@ -184,5 +184,5 @@ export const de_CH: KeyboardLayout = {
keyDisplayMap: keyDisplayMap,
// TODO need to localize these maps and layouts
modifierDisplayMap: en_US.modifierDisplayMap,
virtualKeyboard: en_US.virtualKeyboard
virtualKeyboard: en_US.virtualKeyboard,
};

View File

@ -1,23 +1,23 @@
import { KeyboardLayout, KeyCombo } from "../keyboardLayouts"
import { KeyboardLayout, KeyCombo } from "../keyboardLayouts";
import { en_US } from "./en_US" // for fallback of keyDisplayMap, modifierDisplayMap, and virtualKeyboard
import { en_US } from "./en_US"; // for fallback of keyDisplayMap, modifierDisplayMap, and virtualKeyboard
const name = "Deutsch";
const isoCode = "de-DE";
const keyAcute: KeyCombo = { key: "Equal" } // accent aigu (acute accent), mark ´ placed above the letter
const keyHat: KeyCombo = { key: "Backquote" } // accent circonflexe (accent hat), mark ^ placed above the letter
const keyGrave: KeyCombo = { key: "Equal", shift: true } // accent grave, mark ` placed above the letter
const keyAcute: KeyCombo = { key: "Equal" }; // accent aigu (acute accent), mark ´ placed above the letter
const keyHat: KeyCombo = { key: "Backquote" }; // accent circonflexe (accent hat), mark ^ placed above the letter
const keyGrave: KeyCombo = { key: "Equal", shift: true }; // accent grave, mark ` placed above the letter
const chars = {
a: { key: "KeyA" },
"á": { key: "KeyA", accentKey: keyAcute },
"â": { key: "KeyA", accentKey: keyHat },
"à": { key: "KeyA", accentKey: keyGrave },
á: { key: "KeyA", accentKey: keyAcute },
â: { key: "KeyA", accentKey: keyHat },
à: { key: "KeyA", accentKey: keyGrave },
A: { key: "KeyA", shift: true },
"Á": { key: "KeyA", shift: true, accentKey: keyAcute },
"Â": { key: "KeyA", shift: true, accentKey: keyHat },
"À": { key: "KeyA", shift: true, accentKey: keyGrave },
Á: { key: "KeyA", shift: true, accentKey: keyAcute },
Â: { key: "KeyA", shift: true, accentKey: keyHat },
À: { key: "KeyA", shift: true, accentKey: keyGrave },
"☺": { key: "KeyA", altRight: true }, // white smiling face ☺
b: { key: "KeyB" },
B: { key: "KeyB", shift: true },
@ -29,31 +29,31 @@ const chars = {
D: { key: "KeyD", shift: true },
"": { key: "KeyD", altRight: true }, // prime, mark placed above the letter
e: { key: "KeyE" },
"é": { key: "KeyE", accentKey: keyAcute },
"ê": { key: "KeyE", accentKey: keyHat },
"è": { key: "KeyE", accentKey: keyGrave },
é: { key: "KeyE", accentKey: keyAcute },
ê: { key: "KeyE", accentKey: keyHat },
è: { key: "KeyE", accentKey: keyGrave },
"€": { key: "KeyE", altRight: true },
E: { key: "KeyE", shift: true },
"É": { key: "KeyE", shift: true, accentKey: keyAcute },
"Ê": { key: "KeyE", shift: true, accentKey: keyHat },
"È": { key: "KeyE", shift: true, accentKey: keyGrave },
É: { key: "KeyE", shift: true, accentKey: keyAcute },
Ê: { key: "KeyE", shift: true, accentKey: keyHat },
È: { key: "KeyE", shift: true, accentKey: keyGrave },
f: { key: "KeyF" },
F: { key: "KeyF", shift: true },
"˟": { key: "KeyF", deadKey: true, altRight: true }, // modifier letter cross accent, ˟
G: { key: "KeyG", shift: true },
g: { key: "KeyG" },
"ẞ": { key: "KeyG", altRight: true }, // capital sharp S, ẞ
: { key: "KeyG", altRight: true }, // capital sharp S, ẞ
h: { key: "KeyH" },
H: { key: "KeyH", shift: true },
"ˍ": { key: "KeyH", deadKey: true, altRight: true }, // modifier letter low macron, ˍ
ˍ: { key: "KeyH", deadKey: true, altRight: true }, // modifier letter low macron, ˍ
i: { key: "KeyI" },
"í": { key: "KeyI", accentKey: keyAcute },
"î": { key: "KeyI", accentKey: keyHat },
"ì": { key: "KeyI", accentKey: keyGrave },
í: { key: "KeyI", accentKey: keyAcute },
î: { key: "KeyI", accentKey: keyHat },
ì: { key: "KeyI", accentKey: keyGrave },
I: { key: "KeyI", shift: true },
"Í": { key: "KeyI", shift: true, accentKey: keyAcute },
"Î": { key: "KeyI", shift: true, accentKey: keyHat },
"Ì": { key: "KeyI", shift: true, accentKey: keyGrave },
Í: { key: "KeyI", shift: true, accentKey: keyAcute },
Î: { key: "KeyI", shift: true, accentKey: keyHat },
Ì: { key: "KeyI", shift: true, accentKey: keyGrave },
"˜": { key: "KeyI", deadKey: true, altRight: true }, // tilde accent, mark ˜ placed above the letter
j: { key: "KeyJ" },
J: { key: "KeyJ", shift: true },
@ -62,25 +62,25 @@ const chars = {
K: { key: "KeyK", shift: true },
l: { key: "KeyL" },
L: { key: "KeyL", shift: true },
"ˏ": { key: "KeyL", deadKey: true, altRight: true }, // modifier letter reversed comma, ˏ
ˏ: { key: "KeyL", deadKey: true, altRight: true }, // modifier letter reversed comma, ˏ
m: { key: "KeyM" },
M: { key: "KeyM", shift: true },
"µ": { key: "KeyM", altRight: true },
µ: { key: "KeyM", altRight: true },
n: { key: "KeyN" },
N: { key: "KeyN", shift: true },
"": { key: "KeyN", altRight: true }, // en dash,
o: { key: "KeyO" },
"ó": { key: "KeyO", accentKey: keyAcute },
"ô": { key: "KeyO", accentKey: keyHat },
"ò": { key: "KeyO", accentKey: keyGrave },
ó: { key: "KeyO", accentKey: keyAcute },
ô: { key: "KeyO", accentKey: keyHat },
ò: { key: "KeyO", accentKey: keyGrave },
O: { key: "KeyO", shift: true },
"Ó": { key: "KeyO", shift: true, accentKey: keyAcute },
"Ô": { key: "KeyO", shift: true, accentKey: keyHat },
"Ò": { key: "KeyO", shift: true, accentKey: keyGrave },
Ó: { key: "KeyO", shift: true, accentKey: keyAcute },
Ô: { key: "KeyO", shift: true, accentKey: keyHat },
Ò: { key: "KeyO", shift: true, accentKey: keyGrave },
"˚": { key: "KeyO", deadKey: true, altRight: true }, // ring above, ˚
p: { key: "KeyP" },
P: { key: "KeyP", shift: true },
"ˀ": { key: "KeyP", deadKey: true, altRight: true }, // modifier letter apostrophe, ʾ
ˀ: { key: "KeyP", deadKey: true, altRight: true }, // modifier letter apostrophe, ʾ
q: { key: "KeyQ" },
Q: { key: "KeyQ", shift: true },
"@": { key: "KeyQ", altRight: true },
@ -92,15 +92,15 @@ const chars = {
"″": { key: "KeyS", altRight: true }, // double prime, mark ″ placed above the letter
T: { key: "KeyT", shift: true },
t: { key: "KeyT" },
"ˇ": { key: "KeyT", deadKey: true, altRight: true }, // caron/hacek accent, mark ˇ placed above the letter
ˇ: { key: "KeyT", deadKey: true, altRight: true }, // caron/hacek accent, mark ˇ placed above the letter
u: { key: "KeyU" },
"ú": { key: "KeyU", accentKey: keyAcute },
"û": { key: "KeyU", accentKey: keyHat },
"ù": { key: "KeyU", accentKey: keyGrave },
ú: { key: "KeyU", accentKey: keyAcute },
û: { key: "KeyU", accentKey: keyHat },
ù: { key: "KeyU", accentKey: keyGrave },
U: { key: "KeyU", shift: true },
"Ú": { key: "KeyU", shift: true, accentKey: keyAcute },
"Û": { key: "KeyU", shift: true, accentKey: keyHat },
"Ù": { key: "KeyU", shift: true, accentKey: keyGrave },
Ú: { key: "KeyU", shift: true, accentKey: keyAcute },
Û: { key: "KeyU", shift: true, accentKey: keyHat },
Ù: { key: "KeyU", shift: true, accentKey: keyGrave },
"˘": { key: "KeyU", deadKey: true, altRight: true }, // breve accent, ˘ placed above the letter
v: { key: "KeyV" },
V: { key: "KeyV", shift: true },
@ -125,7 +125,7 @@ const chars = {
"!": { key: "Digit1", shift: true },
"": { key: "Digit1", altRight: true }, // single quote, mark placed above the letter
2: { key: "Digit2" },
"\"": { key: "Digit2", shift: true },
'"': { key: "Digit2", shift: true },
"²": { key: "Digit2", altRight: true },
"<": { key: "Digit2", altRight: true }, // non-US < and >
3: { key: "Digit3" },
@ -133,7 +133,7 @@ const chars = {
"³": { key: "Digit3", altRight: true },
">": { key: "Digit3", altRight: true }, // non-US < and >
4: { key: "Digit4" },
"$": { key: "Digit4", shift: true },
$: { key: "Digit4", shift: true },
"—": { key: "Digit4", altRight: true }, // em dash, —
5: { key: "Digit5" },
"%": { key: "Digit5", shift: true },
@ -153,25 +153,25 @@ const chars = {
0: { key: "Digit0" },
"=": { key: "Digit0", shift: true },
"}": { key: "Digit0", altRight: true },
"ß": { key: "Minus" },
ß: { key: "Minus" },
"?": { key: "Minus", shift: true },
"\\": { key: "Minus", altRight: true },
"´": { key: "Equal", deadKey: true }, // accent acute, mark ´ placed above the letter
"`": { key: "Equal", shift: true, deadKey: true }, // accent grave, mark ` placed above the letter
"`": { key: "Equal", shift: true, deadKey: true }, // accent grave, mark ` placed above the letter
"˙": { key: "Equal", control: true, altRight: true, deadKey: true }, // acute accent, mark ˙ placed above the letter
"ü": { key: "BracketLeft" },
"Ü": { key: "BracketLeft", shift: true },
ü: { key: "BracketLeft" },
Ü: { key: "BracketLeft", shift: true },
Escape: { key: "BracketLeft", control: true },
"ʼ": { key: "BracketLeft", altRight: true }, // modifier letter apostrophe, ʼ
ʼ: { key: "BracketLeft", altRight: true }, // modifier letter apostrophe, ʼ
"+": { key: "BracketRight" },
"*": { key: "BracketRight", shift: true },
Control: { key: "BracketRight", control: true },
"~": { key: "BracketRight", altRight: true },
"ö": { key: "Semicolon" },
"Ö": { key: "Semicolon", shift: true },
"ˌ": { key: "Semicolon", deadkey: true, altRight: true }, // modifier letter low vertical line, ˌ
"ä": { key: "Quote" },
"Ä": { key: "Quote", shift: true },
ö: { key: "Semicolon" },
Ö: { key: "Semicolon", shift: true },
ˌ: { key: "Semicolon", deadkey: true, altRight: true }, // modifier letter low vertical line, ˌ
ä: { key: "Quote" },
Ä: { key: "Quote", shift: true },
"˗": { key: "Quote", deadKey: true, altRight: true }, // modifier letter minus sign, ˗
"#": { key: "Backslash" },
"'": { key: "Backslash", shift: true },
@ -183,7 +183,7 @@ const chars = {
":": { key: "Period", shift: true },
"·": { key: "Period", altRight: true }, // middle dot, ·
"-": { key: "Slash" },
"_": { key: "Slash", shift: true },
_: { key: "Slash", shift: true },
"\u00ad": { key: "Slash", altRight: true }, // soft hyphen, ­
" ": { key: "Space" },
"\n": { key: "Enter" },
@ -237,7 +237,7 @@ export const keyDisplayMap: Record<string, string> = {
"(Backslash)": "'",
// Shifted Numbers
"(Digit2)": "\"",
"(Digit2)": '"',
"(Digit3)": "§",
"(Digit6)": "&",
"(Digit7)": "/",
@ -265,7 +265,7 @@ export const keyDisplayMap: Record<string, string> = {
PrintScreen: "Druck",
ScrollLock: "Rollen",
"(Pause)": "Unterbr",
}
};
export const modifierDisplayMap: Record<string, string> = {
ShiftLeft: "Umschalt (links)",
@ -298,25 +298,15 @@ export const virtualKeyboard = {
"CapsLock (KeyA) (KeyS) (KeyD) (KeyF) (KeyG) (KeyH) (KeyJ) (KeyK) (KeyL) (Semicolon) (Quote) Enter",
"ShiftLeft (KeyZ) (KeyX) (KeyC) (KeyV) (KeyB) (KeyN) (KeyM) (Comma) (Period) (Slash) ShiftRight",
"ControlLeft MetaLeft AltLeft Space AltGr MetaRight Menu ControlRight",
]
],
},
control: {
default: [
"PrintScreen ScrollLock Pause",
"Insert Home PageUp",
"Delete End PageDown"
],
shift: [
"(PrintScreen) ScrollLock (Pause)",
"Insert Home PageUp",
"Delete End PageDown"
],
default: ["PrintScreen ScrollLock Pause", "Insert Home PageUp", "Delete End PageDown"],
shift: ["(PrintScreen) ScrollLock (Pause)", "Insert Home PageUp", "Delete End PageDown"],
},
arrows: {
default: [
" ArrowUp ",
"ArrowLeft ArrowDown ArrowRight"],
default: [" ArrowUp ", "ArrowLeft ArrowDown ArrowRight"],
},
numpad: {
@ -334,8 +324,8 @@ export const virtualKeyboard = {
"End ArrowDown PageDown NumpadEnter",
"NumpadInsert NumpadDelete",
],
}
}
},
};
export const de_DE: KeyboardLayout = {
isoCode: isoCode,
@ -343,5 +333,5 @@ export const de_DE: KeyboardLayout = {
chars: chars,
keyDisplayMap: keyDisplayMap,
modifierDisplayMap: modifierDisplayMap,
virtualKeyboard: virtualKeyboard
virtualKeyboard: virtualKeyboard,
};

View File

@ -1,6 +1,6 @@
import { KeyboardLayout, KeyCombo } from "../keyboardLayouts"
import { KeyboardLayout, KeyCombo } from "../keyboardLayouts";
import { en_US } from "./en_US" // for fallback of keyDisplayMap, modifierDisplayMap, and virtualKeyboard
import { en_US } from "./en_US"; // for fallback of keyDisplayMap, modifierDisplayMap, and virtualKeyboard
const name = "English (UK)";
const isoCode = "en-UK";
@ -61,7 +61,7 @@ const chars = {
1: { key: "Digit1" },
"!": { key: "Digit1", shift: true },
2: { key: "Digit2" },
"\"": { key: "Digit2", shift: true },
'"': { key: "Digit2", shift: true },
3: { key: "Digit3" },
"£": { key: "Digit3", shift: true },
4: { key: "Digit4" },
@ -84,7 +84,7 @@ const chars = {
"=": { key: "Equal" },
"+": { key: "Equal", shift: true },
"'": { key: "Quote" },
'@': { key: "Quote", shift: true },
"@": { key: "Quote", shift: true },
",": { key: "Comma" },
"<": { key: "Comma", shift: true },
"/": { key: "Slash" },
@ -107,7 +107,7 @@ const chars = {
"\n": { key: "Enter" },
Enter: { key: "Enter" },
Tab: { key: "Tab" },
} as Record<string, KeyCombo>
} as Record<string, KeyCombo>;
export const en_UK: KeyboardLayout = {
isoCode: isoCode,
@ -116,5 +116,5 @@ export const en_UK: KeyboardLayout = {
// TODO need to localize these maps and layouts
keyDisplayMap: en_US.keyDisplayMap,
modifierDisplayMap: en_US.modifierDisplayMap,
virtualKeyboard: en_US.virtualKeyboard
virtualKeyboard: en_US.virtualKeyboard,
};

View File

@ -1,4 +1,4 @@
import { KeyboardLayout, KeyCombo } from "../keyboardLayouts"
import { KeyboardLayout, KeyCombo } from "../keyboardLayouts";
const name = "English (US)";
const isoCode = "en-US";
@ -125,7 +125,7 @@ export const chars = {
Break: { key: "Pause", shift: true },
Insert: { key: "Insert" },
Delete: { key: "Delete" },
} as Record<string, KeyCombo>
} as Record<string, KeyCombo>;
export const modifierDisplayMap: Record<string, string> = {
ControlLeft: "Left Ctrl",
@ -173,28 +173,84 @@ export const keyDisplayMap: Record<string, string> = {
Tab: "Tab ⇥",
// Letters
KeyA: "a", KeyB: "b", KeyC: "c", KeyD: "d", KeyE: "e",
KeyF: "f", KeyG: "g", KeyH: "h", KeyI: "i", KeyJ: "j",
KeyK: "k", KeyL: "l", KeyM: "m", KeyN: "n", KeyO: "o",
KeyP: "p", KeyQ: "q", KeyR: "r", KeyS: "s", KeyT: "t",
KeyU: "u", KeyV: "v", KeyW: "w", KeyX: "x", KeyY: "y",
KeyA: "a",
KeyB: "b",
KeyC: "c",
KeyD: "d",
KeyE: "e",
KeyF: "f",
KeyG: "g",
KeyH: "h",
KeyI: "i",
KeyJ: "j",
KeyK: "k",
KeyL: "l",
KeyM: "m",
KeyN: "n",
KeyO: "o",
KeyP: "p",
KeyQ: "q",
KeyR: "r",
KeyS: "s",
KeyT: "t",
KeyU: "u",
KeyV: "v",
KeyW: "w",
KeyX: "x",
KeyY: "y",
KeyZ: "z",
// Capital letters
"(KeyA)": "A", "(KeyB)": "B", "(KeyC)": "C", "(KeyD)": "D", "(KeyE)": "E",
"(KeyF)": "F", "(KeyG)": "G", "(KeyH)": "H", "(KeyI)": "I", "(KeyJ)": "J",
"(KeyK)": "K", "(KeyL)": "L", "(KeyM)": "M", "(KeyN)": "N", "(KeyO)": "O",
"(KeyP)": "P", "(KeyQ)": "Q", "(KeyR)": "R", "(KeyS)": "S", "(KeyT)": "T",
"(KeyU)": "U", "(KeyV)": "V", "(KeyW)": "W", "(KeyX)": "X", "(KeyY)": "Y",
"(KeyA)": "A",
"(KeyB)": "B",
"(KeyC)": "C",
"(KeyD)": "D",
"(KeyE)": "E",
"(KeyF)": "F",
"(KeyG)": "G",
"(KeyH)": "H",
"(KeyI)": "I",
"(KeyJ)": "J",
"(KeyK)": "K",
"(KeyL)": "L",
"(KeyM)": "M",
"(KeyN)": "N",
"(KeyO)": "O",
"(KeyP)": "P",
"(KeyQ)": "Q",
"(KeyR)": "R",
"(KeyS)": "S",
"(KeyT)": "T",
"(KeyU)": "U",
"(KeyV)": "V",
"(KeyW)": "W",
"(KeyX)": "X",
"(KeyY)": "Y",
"(KeyZ)": "Z",
// Numbers
Digit1: "1", Digit2: "2", Digit3: "3", Digit4: "4", Digit5: "5",
Digit6: "6", Digit7: "7", Digit8: "8", Digit9: "9", Digit0: "0",
Digit1: "1",
Digit2: "2",
Digit3: "3",
Digit4: "4",
Digit5: "5",
Digit6: "6",
Digit7: "7",
Digit8: "8",
Digit9: "9",
Digit0: "0",
// Shifted Numbers
"(Digit1)": "!", "(Digit2)": "@", "(Digit3)": "#", "(Digit4)": "$", "(Digit5)": "%",
"(Digit6)": "^", "(Digit7)": "&", "(Digit8)": "*", "(Digit9)": "(", "(Digit0)": ")",
"(Digit1)": "!",
"(Digit2)": "@",
"(Digit3)": "#",
"(Digit4)": "$",
"(Digit5)": "%",
"(Digit6)": "^",
"(Digit7)": "&",
"(Digit8)": "*",
"(Digit9)": "(",
"(Digit0)": ")",
// Symbols
Minus: "-",
@ -210,7 +266,7 @@ export const keyDisplayMap: Record<string, string> = {
Semicolon: ";",
"(Semicolon)": ":",
Quote: "'",
"(Quote)": "\"",
"(Quote)": '"',
Comma: ",",
"(Comma)": "<",
Period: ".",
@ -222,23 +278,49 @@ export const keyDisplayMap: Record<string, string> = {
IntlBackslash: "\\",
// Function keys
F1: "F1", F2: "F2", F3: "F3", F4: "F4",
F5: "F5", F6: "F6", F7: "F7", F8: "F8",
F9: "F9", F10: "F10", F11: "F11", F12: "F12",
F1: "F1",
F2: "F2",
F3: "F3",
F4: "F4",
F5: "F5",
F6: "F6",
F7: "F7",
F8: "F8",
F9: "F9",
F10: "F10",
F11: "F11",
F12: "F12",
// Numpad
Numpad0: "Num 0", Numpad1: "Num 1", Numpad2: "Num 2",
Numpad3: "Num 3", Numpad4: "Num 4", Numpad5: "Num 5",
Numpad6: "Num 6", Numpad7: "Num 7", Numpad8: "Num 8",
Numpad9: "Num 9", NumpadAdd: "Num +", NumpadSubtract: "Num -",
NumpadMultiply: "Num *", NumpadDivide: "Num /", NumpadDecimal: "Num .",
NumpadEqual: "Num =", NumpadEnter: "Num Enter", NumpadInsert: "Ins",
NumpadDelete: "Del", NumLock: "Num Lock",
Numpad0: "Num 0",
Numpad1: "Num 1",
Numpad2: "Num 2",
Numpad3: "Num 3",
Numpad4: "Num 4",
Numpad5: "Num 5",
Numpad6: "Num 6",
Numpad7: "Num 7",
Numpad8: "Num 8",
Numpad9: "Num 9",
NumpadAdd: "Num +",
NumpadSubtract: "Num -",
NumpadMultiply: "Num *",
NumpadDivide: "Num /",
NumpadDecimal: "Num .",
NumpadEqual: "Num =",
NumpadEnter: "Num Enter",
NumpadInsert: "Ins",
NumpadDelete: "Del",
NumLock: "Num Lock",
// Modals
PrintScreen: "Prt Sc", ScrollLock: "Scr Lk", Pause: "Pause",
"(PrintScreen)": "Sys Rq", "(Pause)": "Break",
SystemRequest: "Sys Rq", Break: "Break"
PrintScreen: "Prt Sc",
ScrollLock: "Scr Lk",
Pause: "Pause",
"(PrintScreen)": "Sys Rq",
"(Pause)": "Break",
SystemRequest: "Sys Rq",
Break: "Break",
};
export const virtualKeyboard = {
@ -260,25 +342,15 @@ export const virtualKeyboard = {
"CapsLock (KeyA) (KeyS) (KeyD) (KeyF) (KeyG) (KeyH) (KeyJ) (KeyK) (KeyL) (Semicolon) (Quote) Enter",
"ShiftLeft (KeyZ) (KeyX) (KeyC) (KeyV) (KeyB) (KeyN) (KeyM) (Comma) (Period) (Slash) ShiftRight",
"ControlLeft MetaLeft AltLeft Space AltGr MetaRight Menu ControlRight",
]
],
},
control: {
default: [
"PrintScreen ScrollLock Pause",
"Insert Home PageUp",
"Delete End PageDown"
],
shift: [
"(PrintScreen) ScrollLock (Pause)",
"Insert Home PageUp",
"Delete End PageDown"
],
default: ["PrintScreen ScrollLock Pause", "Insert Home PageUp", "Delete End PageDown"],
shift: ["(PrintScreen) ScrollLock (Pause)", "Insert Home PageUp", "Delete End PageDown"],
},
arrows: {
default: [
"ArrowUp",
"ArrowLeft ArrowDown ArrowRight"],
default: ["ArrowUp", "ArrowLeft ArrowDown ArrowRight"],
},
numpad: {
@ -296,8 +368,8 @@ export const virtualKeyboard = {
"End ArrowDown PageDown NumpadEnter",
"NumpadInsert NumpadDelete",
],
}
}
},
};
export const en_US: KeyboardLayout = {
isoCode,
@ -305,7 +377,5 @@ export const en_US: KeyboardLayout = {
chars,
keyDisplayMap,
modifierDisplayMap,
virtualKeyboard
virtualKeyboard,
};

View File

@ -1,134 +1,134 @@
import { KeyboardLayout, KeyCombo } from "../keyboardLayouts"
import { KeyboardLayout, KeyCombo } from "../keyboardLayouts";
import { en_US } from "./en_US" // for fallback of keyDisplayMap, modifierDisplayMap, and virtualKeyboard
import { en_US } from "./en_US"; // for fallback of keyDisplayMap, modifierDisplayMap, and virtualKeyboard
const name = "Español";
const isoCode = "es-ES";
const keyTrema: KeyCombo = { key: "Quote", shift: true } // tréma (umlaut), two dots placed above a vowel
const keyAcute: KeyCombo = { key: "Quote" } // accent aigu (acute accent), mark ´ placed above the letter
const keyHat: KeyCombo = { key: "BracketRight", shift: true } // accent circonflexe (accent hat), mark ^ placed above the letter
const keyGrave: KeyCombo = { key: "BracketRight" } // accent grave, mark ` placed above the letter
const keyTilde: KeyCombo = { key: "Key4", altRight: true } // tilde, mark ~ placed above the letter
const keyTrema: KeyCombo = { key: "Quote", shift: true }; // tréma (umlaut), two dots placed above a vowel
const keyAcute: KeyCombo = { key: "Quote" }; // accent aigu (acute accent), mark ´ placed above the letter
const keyHat: KeyCombo = { key: "BracketRight", shift: true }; // accent circonflexe (accent hat), mark ^ placed above the letter
const keyGrave: KeyCombo = { key: "BracketRight" }; // accent grave, mark ` placed above the letter
const keyTilde: KeyCombo = { key: "Key4", altRight: true }; // tilde, mark ~ placed above the letter
const chars = {
A: { key: "KeyA", shift: true },
"Ä": { key: "KeyA", shift: true, accentKey: keyTrema },
"Á": { key: "KeyA", shift: true, accentKey: keyAcute },
"Â": { key: "KeyA", shift: true, accentKey: keyHat },
"À": { key: "KeyA", shift: true, accentKey: keyGrave },
"Ã": { key: "KeyA", shift: true, accentKey: keyTilde },
Ä: { key: "KeyA", shift: true, accentKey: keyTrema },
Á: { key: "KeyA", shift: true, accentKey: keyAcute },
Â: { key: "KeyA", shift: true, accentKey: keyHat },
À: { key: "KeyA", shift: true, accentKey: keyGrave },
Ã: { key: "KeyA", shift: true, accentKey: keyTilde },
B: { key: "KeyB", shift: true },
C: { key: "KeyC", shift: true },
D: { key: "KeyD", shift: true },
E: { key: "KeyE", shift: true },
"Ë": { key: "KeyE", shift: true, accentKey: keyTrema },
"É": { key: "KeyE", shift: true, accentKey: keyAcute },
"Ê": { key: "KeyE", shift: true, accentKey: keyHat },
"È": { key: "KeyE", shift: true, accentKey: keyGrave },
"Ẽ": { key: "KeyE", shift: true, accentKey: keyTilde },
Ë: { key: "KeyE", shift: true, accentKey: keyTrema },
É: { key: "KeyE", shift: true, accentKey: keyAcute },
Ê: { key: "KeyE", shift: true, accentKey: keyHat },
È: { key: "KeyE", shift: true, accentKey: keyGrave },
: { key: "KeyE", shift: true, accentKey: keyTilde },
F: { key: "KeyF", shift: true },
G: { key: "KeyG", shift: true },
H: { key: "KeyH", shift: true },
I: { key: "KeyI", shift: true },
"Ï": { key: "KeyI", shift: true, accentKey: keyTrema },
"Í": { key: "KeyI", shift: true, accentKey: keyAcute },
"Î": { key: "KeyI", shift: true, accentKey: keyHat },
"Ì": { key: "KeyI", shift: true, accentKey: keyGrave },
"Ĩ": { key: "KeyI", shift: true, accentKey: keyTilde },
Ï: { key: "KeyI", shift: true, accentKey: keyTrema },
Í: { key: "KeyI", shift: true, accentKey: keyAcute },
Î: { key: "KeyI", shift: true, accentKey: keyHat },
Ì: { key: "KeyI", shift: true, accentKey: keyGrave },
Ĩ: { key: "KeyI", shift: true, accentKey: keyTilde },
J: { key: "KeyJ", shift: true },
K: { key: "KeyK", shift: true },
L: { key: "KeyL", shift: true },
M: { key: "KeyM", shift: true },
N: { key: "KeyN", shift: true },
O: { key: "KeyO", shift: true },
"Ö": { key: "KeyO", shift: true, accentKey: keyTrema },
"Ó": { key: "KeyO", shift: true, accentKey: keyAcute },
"Ô": { key: "KeyO", shift: true, accentKey: keyHat },
"Ò": { key: "KeyO", shift: true, accentKey: keyGrave },
"Õ": { key: "KeyO", shift: true, accentKey: keyTilde },
Ö: { key: "KeyO", shift: true, accentKey: keyTrema },
Ó: { key: "KeyO", shift: true, accentKey: keyAcute },
Ô: { key: "KeyO", shift: true, accentKey: keyHat },
Ò: { key: "KeyO", shift: true, accentKey: keyGrave },
Õ: { key: "KeyO", shift: true, accentKey: keyTilde },
P: { key: "KeyP", shift: true },
Q: { key: "KeyQ", shift: true },
R: { key: "KeyR", shift: true },
S: { key: "KeyS", shift: true },
T: { key: "KeyT", shift: true },
U: { key: "KeyU", shift: true },
"Ü": { key: "KeyU", shift: true, accentKey: keyTrema },
"Ú": { key: "KeyU", shift: true, accentKey: keyAcute },
"Û": { key: "KeyU", shift: true, accentKey: keyHat },
"Ù": { key: "KeyU", shift: true, accentKey: keyGrave },
"Ũ": { key: "KeyU", shift: true, accentKey: keyTilde },
Ü: { key: "KeyU", shift: true, accentKey: keyTrema },
Ú: { key: "KeyU", shift: true, accentKey: keyAcute },
Û: { key: "KeyU", shift: true, accentKey: keyHat },
Ù: { key: "KeyU", shift: true, accentKey: keyGrave },
Ũ: { key: "KeyU", shift: true, accentKey: keyTilde },
V: { key: "KeyV", shift: true },
W: { key: "KeyW", shift: true },
X: { key: "KeyX", shift: true },
Y: { key: "KeyY", shift: true },
Z: { key: "KeyZ", shift: true },
a: { key: "KeyA" },
"ä": { key: "KeyA", accentKey: keyTrema },
"á": { key: "KeyA", accentKey: keyAcute },
"â": { key: "KeyA", accentKey: keyHat },
"à": { key: "KeyA", accentKey: keyGrave },
"ã": { key: "KeyA", accentKey: keyTilde },
ä: { key: "KeyA", accentKey: keyTrema },
á: { key: "KeyA", accentKey: keyAcute },
â: { key: "KeyA", accentKey: keyHat },
à: { key: "KeyA", accentKey: keyGrave },
ã: { key: "KeyA", accentKey: keyTilde },
b: { key: "KeyB" },
c: { key: "KeyC" },
d: { key: "KeyD" },
e: { key: "KeyE" },
"ë": { key: "KeyE", accentKey: keyTrema },
"é": { key: "KeyE", accentKey: keyAcute },
"ê": { key: "KeyE", accentKey: keyHat },
"è": { key: "KeyE", accentKey: keyGrave },
"ẽ": { key: "KeyE", accentKey: keyTilde },
ë: { key: "KeyE", accentKey: keyTrema },
é: { key: "KeyE", accentKey: keyAcute },
ê: { key: "KeyE", accentKey: keyHat },
è: { key: "KeyE", accentKey: keyGrave },
: { key: "KeyE", accentKey: keyTilde },
"€": { key: "KeyE", altRight: true },
f: { key: "KeyF" },
g: { key: "KeyG" },
h: { key: "KeyH" },
i: { key: "KeyI" },
"ï": { key: "KeyI", accentKey: keyTrema },
"í": { key: "KeyI", accentKey: keyAcute },
"î": { key: "KeyI", accentKey: keyHat },
"ì": { key: "KeyI", accentKey: keyGrave },
"ĩ": { key: "KeyI", accentKey: keyTilde },
ï: { key: "KeyI", accentKey: keyTrema },
í: { key: "KeyI", accentKey: keyAcute },
î: { key: "KeyI", accentKey: keyHat },
ì: { key: "KeyI", accentKey: keyGrave },
ĩ: { key: "KeyI", accentKey: keyTilde },
j: { key: "KeyJ" },
k: { key: "KeyK" },
l: { key: "KeyL" },
m: { key: "KeyM" },
n: { key: "KeyN" },
o: { key: "KeyO" },
"ö": { key: "KeyO", accentKey: keyTrema },
"ó": { key: "KeyO", accentKey: keyAcute },
"ô": { key: "KeyO", accentKey: keyHat },
"ò": { key: "KeyO", accentKey: keyGrave },
"õ": { key: "KeyO", accentKey: keyTilde },
ö: { key: "KeyO", accentKey: keyTrema },
ó: { key: "KeyO", accentKey: keyAcute },
ô: { key: "KeyO", accentKey: keyHat },
ò: { key: "KeyO", accentKey: keyGrave },
õ: { key: "KeyO", accentKey: keyTilde },
p: { key: "KeyP" },
q: { key: "KeyQ" },
r: { key: "KeyR" },
s: { key: "KeyS" },
t: { key: "KeyT" },
u: { key: "KeyU" },
"ü": { key: "KeyU", accentKey: keyTrema },
"ú": { key: "KeyU", accentKey: keyAcute },
"û": { key: "KeyU", accentKey: keyHat },
"ù": { key: "KeyU", accentKey: keyGrave },
"ũ": { key: "KeyU", accentKey: keyTilde },
ü: { key: "KeyU", accentKey: keyTrema },
ú: { key: "KeyU", accentKey: keyAcute },
û: { key: "KeyU", accentKey: keyHat },
ù: { key: "KeyU", accentKey: keyGrave },
ũ: { key: "KeyU", accentKey: keyTilde },
v: { key: "KeyV" },
w: { key: "KeyW" },
x: { key: "KeyX" },
y: { key: "KeyY" },
z: { key: "KeyZ" },
"º": { key: "Backquote" },
"ª": { key: "Backquote", shift: true },
º: { key: "Backquote" },
ª: { key: "Backquote", shift: true },
"\\": { key: "Backquote", altRight: true },
1: { key: "Digit1" },
"!": { key: "Digit1", shift: true },
"|": { key: "Digit1", altRight: true },
2: { key: "Digit2" },
"\"": { key: "Digit2", shift: true },
'"': { key: "Digit2", shift: true },
"@": { key: "Digit2", altRight: true },
3: { key: "Digit3" },
"·": { key: "Digit3", shift: true },
"#": { key: "Digit3", altRight: true },
4: { key: "Digit4" },
"$": { key: "Digit4", shift: true },
$: { key: "Digit4", shift: true },
5: { key: "Digit5" },
"%": { key: "Digit5", shift: true },
6: { key: "Digit6" },
@ -150,18 +150,18 @@ const chars = {
"+": { key: "BracketRight" },
"*": { key: "BracketRight", shift: true },
"]": { key: "BracketRight", altRight: true },
"ñ": { key: "Semicolon" },
"Ñ": { key: "Semicolon", shift: true },
ñ: { key: "Semicolon" },
Ñ: { key: "Semicolon", shift: true },
"{": { key: "Quote", altRight: true },
"ç": { key: "Backslash" },
"Ç": { key: "Backslash", shift: true },
ç: { key: "Backslash" },
Ç: { key: "Backslash", shift: true },
"}": { key: "Backslash", altRight: true },
",": { key: "Comma" },
";": { key: "Comma", shift: true },
".": { key: "Period" },
":": { key: "Period", shift: true },
"-": { key: "Slash" },
"_": { key: "Slash", shift: true },
_: { key: "Slash", shift: true },
"<": { key: "IntlBackslash" },
">": { key: "IntlBackslash", shift: true },
" ": { key: "Space" },
@ -177,5 +177,5 @@ export const es_ES: KeyboardLayout = {
// TODO need to localize these maps and layouts
keyDisplayMap: en_US.keyDisplayMap,
modifierDisplayMap: en_US.modifierDisplayMap,
virtualKeyboard: en_US.virtualKeyboard
virtualKeyboard: en_US.virtualKeyboard,
};

View File

@ -1,111 +1,111 @@
import { KeyboardLayout, KeyCombo } from "../keyboardLayouts"
import { KeyboardLayout, KeyCombo } from "../keyboardLayouts";
import { en_US } from "./en_US" // for fallback of keyDisplayMap, modifierDisplayMap, and virtualKeyboard
import { en_US } from "./en_US"; // for fallback of keyDisplayMap, modifierDisplayMap, and virtualKeyboard
const name = "Belgisch Nederlands";
const isoCode = "nl-BE";
const keyTrema: KeyCombo = { key: "BracketLeft", shift: true } // tréma (umlaut), two dots placed above a vowel
const keyHat: KeyCombo = { key: "BracketLeft" } // accent circonflexe (accent hat), mark ^ placed above the letter
const keyAcute: KeyCombo = { key: "Semicolon", altRight: true } // accent aigu (acute accent), mark ´ placed above the letter
const keyGrave: KeyCombo = { key: "Quote", shift: true } // accent grave, mark ` placed above the letter
const keyTilde: KeyCombo = { key: "Slash", altRight: true } // tilde, mark ~ placed above the letter
const keyTrema: KeyCombo = { key: "BracketLeft", shift: true }; // tréma (umlaut), two dots placed above a vowel
const keyHat: KeyCombo = { key: "BracketLeft" }; // accent circonflexe (accent hat), mark ^ placed above the letter
const keyAcute: KeyCombo = { key: "Semicolon", altRight: true }; // accent aigu (acute accent), mark ´ placed above the letter
const keyGrave: KeyCombo = { key: "Quote", shift: true }; // accent grave, mark ` placed above the letter
const keyTilde: KeyCombo = { key: "Slash", altRight: true }; // tilde, mark ~ placed above the letter
const chars = {
A: { key: "KeyQ", shift: true },
"Ä": { key: "KeyQ", shift: true, accentKey: keyTrema },
"Â": { key: "KeyQ", shift: true, accentKey: keyHat },
"Á": { key: "KeyQ", shift: true, accentKey: keyAcute },
"À": { key: "KeyQ", shift: true, accentKey: keyGrave },
"Ã": { key: "KeyQ", shift: true, accentKey: keyTilde },
Ä: { key: "KeyQ", shift: true, accentKey: keyTrema },
Â: { key: "KeyQ", shift: true, accentKey: keyHat },
Á: { key: "KeyQ", shift: true, accentKey: keyAcute },
À: { key: "KeyQ", shift: true, accentKey: keyGrave },
Ã: { key: "KeyQ", shift: true, accentKey: keyTilde },
B: { key: "KeyB", shift: true },
C: { key: "KeyC", shift: true },
D: { key: "KeyD", shift: true },
E: { key: "KeyE", shift: true },
"Ë": { key: "KeyE", shift: true, accentKey: keyTrema },
"Ê": { key: "KeyE", shift: true, accentKey: keyHat },
"É": { key: "KeyE", shift: true, accentKey: keyAcute },
"È": { key: "KeyE", shift: true, accentKey: keyGrave },
"Ẽ": { key: "KeyE", shift: true, accentKey: keyTilde },
Ë: { key: "KeyE", shift: true, accentKey: keyTrema },
Ê: { key: "KeyE", shift: true, accentKey: keyHat },
É: { key: "KeyE", shift: true, accentKey: keyAcute },
È: { key: "KeyE", shift: true, accentKey: keyGrave },
: { key: "KeyE", shift: true, accentKey: keyTilde },
F: { key: "KeyF", shift: true },
G: { key: "KeyG", shift: true },
H: { key: "KeyH", shift: true },
I: { key: "KeyI", shift: true },
"Ï": { key: "KeyI", shift: true, accentKey: keyTrema },
"Î": { key: "KeyI", shift: true, accentKey: keyHat },
"Í": { key: "KeyI", shift: true, accentKey: keyAcute },
"Ì": { key: "KeyI", shift: true, accentKey: keyGrave },
"Ĩ": { key: "KeyI", shift: true, accentKey: keyTilde },
Ï: { key: "KeyI", shift: true, accentKey: keyTrema },
Î: { key: "KeyI", shift: true, accentKey: keyHat },
Í: { key: "KeyI", shift: true, accentKey: keyAcute },
Ì: { key: "KeyI", shift: true, accentKey: keyGrave },
Ĩ: { key: "KeyI", shift: true, accentKey: keyTilde },
J: { key: "KeyJ", shift: true },
K: { key: "KeyK", shift: true },
L: { key: "KeyL", shift: true },
M: { key: "Semicolon", shift: true },
N: { key: "KeyN", shift: true },
O: { key: "KeyO", shift: true },
"Ö": { key: "KeyO", shift: true, accentKey: keyTrema },
"Ô": { key: "KeyO", shift: true, accentKey: keyHat },
"Ó": { key: "KeyO", shift: true, accentKey: keyAcute },
"Ò": { key: "KeyO", shift: true, accentKey: keyGrave },
"Õ": { key: "KeyO", shift: true, accentKey: keyTilde },
Ö: { key: "KeyO", shift: true, accentKey: keyTrema },
Ô: { key: "KeyO", shift: true, accentKey: keyHat },
Ó: { key: "KeyO", shift: true, accentKey: keyAcute },
Ò: { key: "KeyO", shift: true, accentKey: keyGrave },
Õ: { key: "KeyO", shift: true, accentKey: keyTilde },
P: { key: "KeyP", shift: true },
Q: { key: "KeyA", shift: true },
R: { key: "KeyR", shift: true },
S: { key: "KeyS", shift: true },
T: { key: "KeyT", shift: true },
U: { key: "KeyU", shift: true },
"Ü": { key: "KeyU", shift: true, accentKey: keyTrema },
"Û": { key: "KeyU", shift: true, accentKey: keyHat },
"Ú": { key: "KeyU", shift: true, accentKey: keyAcute },
"Ù": { key: "KeyU", shift: true, accentKey: keyGrave },
"Ũ": { key: "KeyU", shift: true, accentKey: keyTilde },
Ü: { key: "KeyU", shift: true, accentKey: keyTrema },
Û: { key: "KeyU", shift: true, accentKey: keyHat },
Ú: { key: "KeyU", shift: true, accentKey: keyAcute },
Ù: { key: "KeyU", shift: true, accentKey: keyGrave },
Ũ: { key: "KeyU", shift: true, accentKey: keyTilde },
V: { key: "KeyV", shift: true },
W: { key: "KeyW", shift: true },
X: { key: "KeyX", shift: true },
Y: { key: "KeyZ", shift: true },
Z: { key: "KeyY", shift: true },
a: { key: "KeyQ" },
"ä": { key: "KeyQ", accentKey: keyTrema },
"â": { key: "KeyQ", accentKey: keyHat },
"á": { key: "KeyQ", accentKey: keyAcute },
"ã": { key: "KeyQ", accentKey: keyTilde },
ä: { key: "KeyQ", accentKey: keyTrema },
â: { key: "KeyQ", accentKey: keyHat },
á: { key: "KeyQ", accentKey: keyAcute },
ã: { key: "KeyQ", accentKey: keyTilde },
b: { key: "KeyB" },
c: { key: "KeyC" },
d: { key: "KeyD" },
e: { key: "KeyE" },
"ë": { key: "KeyE", accentKey: keyTrema },
"ê": { key: "KeyE", accentKey: keyHat },
"ẽ": { key: "KeyE", accentKey: keyTilde },
ë: { key: "KeyE", accentKey: keyTrema },
ê: { key: "KeyE", accentKey: keyHat },
: { key: "KeyE", accentKey: keyTilde },
"€": { key: "KeyE", altRight: true },
f: { key: "KeyF" },
g: { key: "KeyG" },
h: { key: "KeyH" },
i: { key: "KeyI" },
"ï": { key: "KeyI", accentKey: keyTrema },
"î": { key: "KeyI", accentKey: keyHat },
"í": { key: "KeyI", accentKey: keyAcute },
"ì": { key: "KeyI", accentKey: keyGrave },
"ĩ": { key: "KeyI", accentKey: keyTilde },
ï: { key: "KeyI", accentKey: keyTrema },
î: { key: "KeyI", accentKey: keyHat },
í: { key: "KeyI", accentKey: keyAcute },
ì: { key: "KeyI", accentKey: keyGrave },
ĩ: { key: "KeyI", accentKey: keyTilde },
j: { key: "KeyJ" },
k: { key: "KeyK" },
l: { key: "KeyL" },
m: { key: "Semicolon" },
n: { key: "KeyN" },
o: { key: "KeyO" },
"ö": { key: "KeyO", accentKey: keyTrema },
"ó": { key: "KeyO", accentKey: keyAcute },
"ô": { key: "KeyO", accentKey: keyHat },
"ò": { key: "KeyO", accentKey: keyGrave },
"õ": { key: "KeyO", accentKey: keyTilde },
ö: { key: "KeyO", accentKey: keyTrema },
ó: { key: "KeyO", accentKey: keyAcute },
ô: { key: "KeyO", accentKey: keyHat },
ò: { key: "KeyO", accentKey: keyGrave },
õ: { key: "KeyO", accentKey: keyTilde },
p: { key: "KeyP" },
q: { key: "KeyA" },
r: { key: "KeyR" },
s: { key: "KeyS" },
t: { key: "KeyT" },
u: { key: "KeyU" },
"ü": { key: "KeyU", accentKey: keyTrema },
"û": { key: "KeyU", accentKey: keyHat },
"ú": { key: "KeyU", accentKey: keyAcute },
"ũ": { key: "KeyU", accentKey: keyTilde },
ü: { key: "KeyU", accentKey: keyTrema },
û: { key: "KeyU", accentKey: keyHat },
ú: { key: "KeyU", accentKey: keyAcute },
ũ: { key: "KeyU", accentKey: keyTilde },
v: { key: "KeyV" },
w: { key: "KeyW" },
x: { key: "KeyX" },
@ -116,10 +116,10 @@ const chars = {
"&": { key: "Digit1" },
1: { key: "Digit1", shift: true },
"|": { key: "Digit1", altRight: true },
"é": { key: "Digit2" },
é: { key: "Digit2" },
2: { key: "Digit2", shift: true },
"@": { key: "Digit2", altRight: true },
"\"": { key: "Digit3" },
'"': { key: "Digit3" },
3: { key: "Digit3", shift: true },
"#": { key: "Digit3", altRight: true },
"'": { key: "Digit4" },
@ -129,27 +129,27 @@ const chars = {
"§": { key: "Digit6" },
6: { key: "Digit6", shift: true },
"^": { key: "Digit6", altRight: true },
"è": { key: "Digit7" },
è: { key: "Digit7" },
7: { key: "Digit7", shift: true },
"!": { key: "Digit8" },
8: { key: "Digit8", shift: true },
"ç": { key: "Digit9" },
ç: { key: "Digit9" },
9: { key: "Digit9", shift: true },
"{": { key: "Digit9", altRight: true },
"à": { key: "Digit0" },
à: { key: "Digit0" },
0: { key: "Digit0", shift: true },
"}": { key: "Digit0", altRight: true },
")": { key: "Minus" },
"°": { key: "Minus", shift: true },
"-": { key: "Equal", deadKey: true },
"_": { key: "Equal", shift: true },
_: { key: "Equal", shift: true },
"[": { key: "BracketLeft", altRight: true },
"$": { key: "BracketRight" },
$: { key: "BracketRight" },
"*": { key: "BracketRight", altRight: true },
"]": { key: "BracketRight", altRight: true },
"ù": { key: "Quote" },
ù: { key: "Quote" },
"%": { key: "Quote", shift: true },
"µ": { key: "Backslash" },
µ: { key: "Backslash" },
"£": { key: "Backslash", shift: true },
",": { key: "KeyM" },
"?": { key: "KeyM", shift: true },
@ -176,5 +176,5 @@ export const fr_BE: KeyboardLayout = {
// TODO need to localize these maps and layouts
keyDisplayMap: en_US.keyDisplayMap,
modifierDisplayMap: en_US.modifierDisplayMap,
virtualKeyboard: en_US.virtualKeyboard
virtualKeyboard: en_US.virtualKeyboard,
};

View File

@ -1,28 +1,28 @@
import { KeyboardLayout, KeyCombo } from "../keyboardLayouts"
import { KeyboardLayout, KeyCombo } from "../keyboardLayouts";
import { de_CH } from "./de_CH"
import { de_CH } from "./de_CH";
const name = "Français de Suisse";
const isoCode = "fr-CH";
const chars = {
...de_CH.chars,
"è": { key: "BracketLeft" },
"ü": { key: "BracketLeft", shift: true },
"é": { key: "Semicolon" },
"ö": { key: "Semicolon", shift: true },
"à": { key: "Quote" },
"ä": { key: "Quote", shift: true },
è: { key: "BracketLeft" },
ü: { key: "BracketLeft", shift: true },
é: { key: "Semicolon" },
ö: { key: "Semicolon", shift: true },
à: { key: "Quote" },
ä: { key: "Quote", shift: true },
} as Record<string, KeyCombo>;
const keyDisplayMap = {
...de_CH.keyDisplayMap,
"BracketLeft": "è",
"BracketLeftShift": "ü",
"Semicolon": "é",
"SemicolonShift": "ö",
"Quote": "à",
"QuoteShift": "ä",
BracketLeft: "è",
BracketLeftShift: "ü",
Semicolon: "é",
SemicolonShift: "ö",
Quote: "à",
QuoteShift: "ä",
} as Record<string, string>;
export const fr_CH: KeyboardLayout = {
@ -32,5 +32,5 @@ export const fr_CH: KeyboardLayout = {
keyDisplayMap: keyDisplayMap,
// TODO need to localize these maps and layouts
modifierDisplayMap: de_CH.modifierDisplayMap,
virtualKeyboard: de_CH.virtualKeyboard
virtualKeyboard: de_CH.virtualKeyboard,
};

View File

@ -1,82 +1,82 @@
import { KeyboardLayout, KeyCombo } from "../keyboardLayouts"
import { KeyboardLayout, KeyCombo } from "../keyboardLayouts";
import { en_US } from "./en_US" // for fallback of keyDisplayMap, modifierDisplayMap, and virtualKeyboard
import { en_US } from "./en_US"; // for fallback of keyDisplayMap, modifierDisplayMap, and virtualKeyboard
const name = "Français";
const isoCode = "fr-FR";
const keyTrema: KeyCombo = { key: "BracketLeft", shift: true } // tréma (umlaut), two dots placed above a vowel
const keyHat: KeyCombo = { key: "BracketLeft" } // accent circonflexe (accent hat), mark ^ placed above the letter
const keyTrema: KeyCombo = { key: "BracketLeft", shift: true }; // tréma (umlaut), two dots placed above a vowel
const keyHat: KeyCombo = { key: "BracketLeft" }; // accent circonflexe (accent hat), mark ^ placed above the letter
const chars = {
A: { key: "KeyQ", shift: true },
"Ä": { key: "KeyQ", shift: true, accentKey: keyTrema },
"Â": { key: "KeyQ", shift: true, accentKey: keyHat },
Ä: { key: "KeyQ", shift: true, accentKey: keyTrema },
Â: { key: "KeyQ", shift: true, accentKey: keyHat },
B: { key: "KeyB", shift: true },
C: { key: "KeyC", shift: true },
D: { key: "KeyD", shift: true },
E: { key: "KeyE", shift: true },
"Ë": { key: "KeyE", shift: true, accentKey: keyTrema },
"Ê": { key: "KeyE", shift: true, accentKey: keyHat },
Ë: { key: "KeyE", shift: true, accentKey: keyTrema },
Ê: { key: "KeyE", shift: true, accentKey: keyHat },
F: { key: "KeyF", shift: true },
G: { key: "KeyG", shift: true },
H: { key: "KeyH", shift: true },
I: { key: "KeyI", shift: true },
"Ï": { key: "KeyI", shift: true, accentKey: keyTrema },
"Î": { key: "KeyI", shift: true, accentKey: keyHat },
Ï: { key: "KeyI", shift: true, accentKey: keyTrema },
Î: { key: "KeyI", shift: true, accentKey: keyHat },
J: { key: "KeyJ", shift: true },
K: { key: "KeyK", shift: true },
L: { key: "KeyL", shift: true },
M: { key: "Semicolon", shift: true },
N: { key: "KeyN", shift: true },
O: { key: "KeyO", shift: true },
"Ö": { key: "KeyO", shift: true, accentKey: keyTrema },
"Ô": { key: "KeyO", shift: true, accentKey: keyHat },
Ö: { key: "KeyO", shift: true, accentKey: keyTrema },
Ô: { key: "KeyO", shift: true, accentKey: keyHat },
P: { key: "KeyP", shift: true },
Q: { key: "KeyA", shift: true },
R: { key: "KeyR", shift: true },
S: { key: "KeyS", shift: true },
T: { key: "KeyT", shift: true },
U: { key: "KeyU", shift: true },
"Ü": { key: "KeyU", shift: true, accentKey: keyTrema },
"Û": { key: "KeyU", shift: true, accentKey: keyHat },
Ü: { key: "KeyU", shift: true, accentKey: keyTrema },
Û: { key: "KeyU", shift: true, accentKey: keyHat },
V: { key: "KeyV", shift: true },
W: { key: "KeyZ", shift: true },
X: { key: "KeyX", shift: true },
Y: { key: "KeyY", shift: true },
Z: { key: "KeyW", shift: true },
a: { key: "KeyQ" },
"ä": { key: "KeyQ", accentKey: keyTrema },
"â": { key: "KeyQ", accentKey: keyHat },
ä: { key: "KeyQ", accentKey: keyTrema },
â: { key: "KeyQ", accentKey: keyHat },
b: { key: "KeyB" },
c: { key: "KeyC" },
d: { key: "KeyD" },
e: { key: "KeyE" },
"ë": { key: "KeyE", accentKey: keyTrema },
"ê": { key: "KeyE", accentKey: keyHat },
ë: { key: "KeyE", accentKey: keyTrema },
ê: { key: "KeyE", accentKey: keyHat },
"€": { key: "KeyE", altRight: true },
f: { key: "KeyF" },
g: { key: "KeyG" },
h: { key: "KeyH" },
i: { key: "KeyI" },
"ï": { key: "KeyI", accentKey: keyTrema },
"î": { key: "KeyI", accentKey: keyHat },
ï: { key: "KeyI", accentKey: keyTrema },
î: { key: "KeyI", accentKey: keyHat },
j: { key: "KeyJ" },
k: { key: "KeyK" },
l: { key: "KeyL" },
m: { key: "Semicolon" },
n: { key: "KeyN" },
o: { key: "KeyO" },
"ö": { key: "KeyO", accentKey: keyTrema },
"ô": { key: "KeyO", accentKey: keyHat },
ö: { key: "KeyO", accentKey: keyTrema },
ô: { key: "KeyO", accentKey: keyHat },
p: { key: "KeyP" },
q: { key: "KeyA" },
r: { key: "KeyR" },
s: { key: "KeyS" },
t: { key: "KeyT" },
u: { key: "KeyU" },
"ü": { key: "KeyU", accentKey: keyTrema },
"û": { key: "KeyU", accentKey: keyHat },
ü: { key: "KeyU", accentKey: keyTrema },
û: { key: "KeyU", accentKey: keyHat },
v: { key: "KeyV" },
w: { key: "KeyZ" },
x: { key: "KeyX" },
@ -85,10 +85,10 @@ const chars = {
"²": { key: "Backquote" },
"&": { key: "Digit1" },
1: { key: "Digit1", shift: true },
"é": { key: "Digit2" },
é: { key: "Digit2" },
2: { key: "Digit2", shift: true },
"~": { key: "Digit2", altRight: true },
"\"": { key: "Digit3" },
'"': { key: "Digit3" },
3: { key: "Digit3", shift: true },
"#": { key: "Digit3", altRight: true },
"'": { key: "Digit4" },
@ -100,16 +100,16 @@ const chars = {
"-": { key: "Digit6" },
6: { key: "Digit6", shift: true },
"|": { key: "Digit6", altRight: true },
"è": { key: "Digit7" },
è: { key: "Digit7" },
7: { key: "Digit7", shift: true },
"`": { key: "Digit7", altRight: true },
"_": { key: "Digit8" },
_: { key: "Digit8" },
8: { key: "Digit8", shift: true },
"\\": { key: "Digit8", altRight: true },
"ç": { key: "Digit9" },
ç: { key: "Digit9" },
9: { key: "Digit9", shift: true },
"^": { key: "Digit9", altRight: true },
"à": { key: "Digit0" },
à: { key: "Digit0" },
0: { key: "Digit0", shift: true },
"@": { key: "Digit0", altRight: true },
")": { key: "Minus" },
@ -118,13 +118,13 @@ const chars = {
"=": { key: "Equal" },
"+": { key: "Equal", shift: true },
"}": { key: "Equal", altRight: true },
"$": { key: "BracketRight" },
$: { key: "BracketRight" },
"£": { key: "BracketRight", shift: true },
"¤": { key: "BracketRight", altRight: true },
"ù": { key: "Quote" },
ù: { key: "Quote" },
"%": { key: "Quote", shift: true },
"*": { key: "Backslash" },
"µ": { key: "Backslash", shift: true },
µ: { key: "Backslash", shift: true },
",": { key: "KeyM" },
"?": { key: "KeyM", shift: true },
";": { key: "Comma" },
@ -148,5 +148,5 @@ export const fr_FR: KeyboardLayout = {
// TODO need to localize these maps and layouts
keyDisplayMap: en_US.keyDisplayMap,
modifierDisplayMap: en_US.modifierDisplayMap,
virtualKeyboard: en_US.virtualKeyboard
virtualKeyboard: en_US.virtualKeyboard,
};

View File

@ -1,6 +1,6 @@
import { KeyboardLayout, KeyCombo } from "../keyboardLayouts"
import { KeyboardLayout, KeyCombo } from "../keyboardLayouts";
import { en_US } from "./en_US" // for fallback of keyDisplayMap, modifierDisplayMap, and virtualKeyboard
import { en_US } from "./en_US"; // for fallback of keyDisplayMap, modifierDisplayMap, and virtualKeyboard
const name = "Italiano";
const isoCode = "it-IT";
@ -64,11 +64,11 @@ const chars = {
1: { key: "Digit1" },
"!": { key: "Digit1", shift: true },
2: { key: "Digit2" },
"\"": { key: "Digit2", shift: true },
'"': { key: "Digit2", shift: true },
3: { key: "Digit3" },
"£": { key: "Digit3", shift: true },
4: { key: "Digit4" },
"$": { key: "Digit4", shift: true },
$: { key: "Digit4", shift: true },
5: { key: "Digit5" },
"%": { key: "Digit5", shift: true },
6: { key: "Digit6" },
@ -83,30 +83,30 @@ const chars = {
"=": { key: "Digit0", shift: true },
"'": { key: "Minus" },
"?": { key: "Minus", shift: true },
"ì": { key: "Equal" },
ì: { key: "Equal" },
"^": { key: "Equal", shift: true },
"è": { key: "BracketLeft" },
"é": { key: "BracketLeft", shift: true },
è: { key: "BracketLeft" },
é: { key: "BracketLeft", shift: true },
"[": { key: "BracketLeft", altRight: true },
"{": { key: "BracketLeft", shift: true, altRight: true },
"+": { key: "BracketRight" },
"*": { key: "BracketRight", shift: true },
"]": { key: "BracketRight", altRight: true },
"}": { key: "BracketRight", shift: true, altRight: true },
"ò": { key: "Semicolon" },
"ç": { key: "Semicolon", shift: true },
ò: { key: "Semicolon" },
ç: { key: "Semicolon", shift: true },
"@": { key: "Semicolon", altRight: true },
"à": { key: "Quote" },
à: { key: "Quote" },
"°": { key: "Quote", shift: true },
"#": { key: "Quote", altRight: true },
"ù": { key: "Backslash" },
ù: { key: "Backslash" },
"§": { key: "Backslash", shift: true },
",": { key: "Comma" },
";": { key: "Comma", shift: true },
".": { key: "Period" },
":": { key: "Period", shift: true },
"-": { key: "Slash" },
"_": { key: "Slash", shift: true },
_: { key: "Slash", shift: true },
"<": { key: "IntlBackslash" },
">": { key: "IntlBackslash", shift: true },
" ": { key: "Space" },
@ -122,5 +122,5 @@ export const it_IT: KeyboardLayout = {
// TODO need to localize these maps and layouts
keyDisplayMap: en_US.keyDisplayMap,
modifierDisplayMap: en_US.modifierDisplayMap,
virtualKeyboard: en_US.virtualKeyboard
virtualKeyboard: en_US.virtualKeyboard,
};

View File

@ -1,115 +1,115 @@
import { KeyboardLayout, KeyCombo } from "../keyboardLayouts"
import { KeyboardLayout, KeyCombo } from "../keyboardLayouts";
import { en_US } from "./en_US" // for fallback of keyDisplayMap, modifierDisplayMap, and virtualKeyboard
import { en_US } from "./en_US"; // for fallback of keyDisplayMap, modifierDisplayMap, and virtualKeyboard
const name = "Norsk bokmål";
const isoCode = "nb-NO";
const keyTrema: KeyCombo = { key: "BracketRight" } // tréma (umlaut), two dots placed above a vowel
const keyAcute: KeyCombo = { key: "Equal", altRight: true } // accent aigu (acute accent), mark ´ placed above the letter
const keyHat: KeyCombo = { key: "BracketRight", shift: true } // accent circonflexe (accent hat), mark ^ placed above the letter
const keyGrave: KeyCombo = { key: "Equal", shift: true } // accent grave, mark ` placed above the letter
const keyTilde: KeyCombo = { key: "BracketRight", altRight: true } // tilde, mark ~ placed above the letter
const keyTrema: KeyCombo = { key: "BracketRight" }; // tréma (umlaut), two dots placed above a vowel
const keyAcute: KeyCombo = { key: "Equal", altRight: true }; // accent aigu (acute accent), mark ´ placed above the letter
const keyHat: KeyCombo = { key: "BracketRight", shift: true }; // accent circonflexe (accent hat), mark ^ placed above the letter
const keyGrave: KeyCombo = { key: "Equal", shift: true }; // accent grave, mark ` placed above the letter
const keyTilde: KeyCombo = { key: "BracketRight", altRight: true }; // tilde, mark ~ placed above the letter
const chars = {
A: { key: "KeyA", shift: true },
"Ä": { key: "KeyA", shift: true, accentKey: keyTrema },
"Á": { key: "KeyA", shift: true, accentKey: keyAcute },
"Â": { key: "KeyA", shift: true, accentKey: keyHat },
"À": { key: "KeyA", shift: true, accentKey: keyGrave },
"Ã": { key: "KeyA", shift: true, accentKey: keyTilde },
Ä: { key: "KeyA", shift: true, accentKey: keyTrema },
Á: { key: "KeyA", shift: true, accentKey: keyAcute },
Â: { key: "KeyA", shift: true, accentKey: keyHat },
À: { key: "KeyA", shift: true, accentKey: keyGrave },
Ã: { key: "KeyA", shift: true, accentKey: keyTilde },
B: { key: "KeyB", shift: true },
C: { key: "KeyC", shift: true },
D: { key: "KeyD", shift: true },
E: { key: "KeyE", shift: true },
"Ë": { key: "KeyE", shift: true, accentKey: keyTrema },
"É": { key: "KeyE", shift: true, accentKey: keyAcute },
"Ê": { key: "KeyE", shift: true, accentKey: keyHat },
"È": { key: "KeyE", shift: true, accentKey: keyGrave },
"Ẽ": { key: "KeyE", shift: true, accentKey: keyTilde },
Ë: { key: "KeyE", shift: true, accentKey: keyTrema },
É: { key: "KeyE", shift: true, accentKey: keyAcute },
Ê: { key: "KeyE", shift: true, accentKey: keyHat },
È: { key: "KeyE", shift: true, accentKey: keyGrave },
: { key: "KeyE", shift: true, accentKey: keyTilde },
F: { key: "KeyF", shift: true },
G: { key: "KeyG", shift: true },
H: { key: "KeyH", shift: true },
I: { key: "KeyI", shift: true },
"Ï": { key: "KeyI", shift: true, accentKey: keyTrema },
"Í": { key: "KeyI", shift: true, accentKey: keyAcute },
"Î": { key: "KeyI", shift: true, accentKey: keyHat },
"Ì": { key: "KeyI", shift: true, accentKey: keyGrave },
"Ĩ": { key: "KeyI", shift: true, accentKey: keyTilde },
Ï: { key: "KeyI", shift: true, accentKey: keyTrema },
Í: { key: "KeyI", shift: true, accentKey: keyAcute },
Î: { key: "KeyI", shift: true, accentKey: keyHat },
Ì: { key: "KeyI", shift: true, accentKey: keyGrave },
Ĩ: { key: "KeyI", shift: true, accentKey: keyTilde },
J: { key: "KeyJ", shift: true },
K: { key: "KeyK", shift: true },
L: { key: "KeyL", shift: true },
M: { key: "KeyM", shift: true },
N: { key: "KeyN", shift: true },
O: { key: "KeyO", shift: true },
"Ö": { key: "KeyO", shift: true, accentKey: keyTrema },
"Ó": { key: "KeyO", shift: true, accentKey: keyAcute },
"Ô": { key: "KeyO", shift: true, accentKey: keyHat },
"Ò": { key: "KeyO", shift: true, accentKey: keyGrave },
"Õ": { key: "KeyO", shift: true, accentKey: keyTilde },
Ö: { key: "KeyO", shift: true, accentKey: keyTrema },
Ó: { key: "KeyO", shift: true, accentKey: keyAcute },
Ô: { key: "KeyO", shift: true, accentKey: keyHat },
Ò: { key: "KeyO", shift: true, accentKey: keyGrave },
Õ: { key: "KeyO", shift: true, accentKey: keyTilde },
P: { key: "KeyP", shift: true },
Q: { key: "KeyQ", shift: true },
R: { key: "KeyR", shift: true },
S: { key: "KeyS", shift: true },
T: { key: "KeyT", shift: true },
U: { key: "KeyU", shift: true },
"Ü": { key: "KeyU", shift: true, accentKey: keyTrema },
"Ú": { key: "KeyU", shift: true, accentKey: keyAcute },
"Û": { key: "KeyU", shift: true, accentKey: keyHat },
"Ù": { key: "KeyU", shift: true, accentKey: keyGrave },
"Ũ": { key: "KeyU", shift: true, accentKey: keyTilde },
Ü: { key: "KeyU", shift: true, accentKey: keyTrema },
Ú: { key: "KeyU", shift: true, accentKey: keyAcute },
Û: { key: "KeyU", shift: true, accentKey: keyHat },
Ù: { key: "KeyU", shift: true, accentKey: keyGrave },
Ũ: { key: "KeyU", shift: true, accentKey: keyTilde },
V: { key: "KeyV", shift: true },
W: { key: "KeyW", shift: true },
X: { key: "KeyX", shift: true },
Y: { key: "KeyZ", shift: true },
Z: { key: "KeyY", shift: true },
a: { key: "KeyA" },
"ä": { key: "KeyA", accentKey: keyTrema },
"á": { key: "KeyA", accentKey: keyAcute },
"â": { key: "KeyA", accentKey: keyHat },
"à": { key: "KeyA", accentKey: keyGrave },
"ã": { key: "KeyA", accentKey: keyTilde },
ä: { key: "KeyA", accentKey: keyTrema },
á: { key: "KeyA", accentKey: keyAcute },
â: { key: "KeyA", accentKey: keyHat },
à: { key: "KeyA", accentKey: keyGrave },
ã: { key: "KeyA", accentKey: keyTilde },
b: { key: "KeyB" },
c: { key: "KeyC" },
d: { key: "KeyD" },
e: { key: "KeyE" },
"ë": { key: "KeyE", accentKey: keyTrema },
"é": { key: "KeyE", accentKey: keyAcute },
"ê": { key: "KeyE", accentKey: keyHat },
"è": { key: "KeyE", accentKey: keyGrave },
"ẽ": { key: "KeyE", accentKey: keyTilde },
ë: { key: "KeyE", accentKey: keyTrema },
é: { key: "KeyE", accentKey: keyAcute },
ê: { key: "KeyE", accentKey: keyHat },
è: { key: "KeyE", accentKey: keyGrave },
: { key: "KeyE", accentKey: keyTilde },
"€": { key: "KeyE", altRight: true },
f: { key: "KeyF" },
g: { key: "KeyG" },
h: { key: "KeyH" },
i: { key: "KeyI" },
"ï": { key: "KeyI", accentKey: keyTrema },
"í": { key: "KeyI", accentKey: keyAcute },
"î": { key: "KeyI", accentKey: keyHat },
"ì": { key: "KeyI", accentKey: keyGrave },
"ĩ": { key: "KeyI", accentKey: keyTilde },
ï: { key: "KeyI", accentKey: keyTrema },
í: { key: "KeyI", accentKey: keyAcute },
î: { key: "KeyI", accentKey: keyHat },
ì: { key: "KeyI", accentKey: keyGrave },
ĩ: { key: "KeyI", accentKey: keyTilde },
j: { key: "KeyJ" },
k: { key: "KeyK" },
l: { key: "KeyL" },
m: { key: "KeyM" },
n: { key: "KeyN" },
o: { key: "KeyO" },
"ö": { key: "KeyO", accentKey: keyTrema },
"ó": { key: "KeyO", accentKey: keyAcute },
"ô": { key: "KeyO", accentKey: keyHat },
"ò": { key: "KeyO", accentKey: keyGrave },
"õ": { key: "KeyO", accentKey: keyTilde },
ö: { key: "KeyO", accentKey: keyTrema },
ó: { key: "KeyO", accentKey: keyAcute },
ô: { key: "KeyO", accentKey: keyHat },
ò: { key: "KeyO", accentKey: keyGrave },
õ: { key: "KeyO", accentKey: keyTilde },
p: { key: "KeyP" },
q: { key: "KeyQ" },
r: { key: "KeyR" },
s: { key: "KeyS" },
t: { key: "KeyT" },
u: { key: "KeyU" },
"ü": { key: "KeyU", accentKey: keyTrema },
"ú": { key: "KeyU", accentKey: keyAcute },
"û": { key: "KeyU", accentKey: keyHat },
"ù": { key: "KeyU", accentKey: keyGrave },
"ũ": { key: "KeyU", accentKey: keyTilde },
ü: { key: "KeyU", accentKey: keyTrema },
ú: { key: "KeyU", accentKey: keyAcute },
û: { key: "KeyU", accentKey: keyHat },
ù: { key: "KeyU", accentKey: keyGrave },
ũ: { key: "KeyU", accentKey: keyTilde },
v: { key: "KeyV" },
w: { key: "KeyW" },
x: { key: "KeyX" },
@ -120,14 +120,14 @@ const chars = {
1: { key: "Digit1" },
"!": { key: "Digit1", shift: true },
2: { key: "Digit2" },
"\"": { key: "Digit2", shift: true },
'"': { key: "Digit2", shift: true },
"@": { key: "Digit2", altRight: true },
3: { key: "Digit3" },
"#": { key: "Digit3", shift: true },
"£": { key: "Digit3", altRight: true },
4: { key: "Digit4" },
"¤": { key: "Digit4", shift: true },
"$": { key: "Digit4", altRight: true },
$: { key: "Digit4", altRight: true },
5: { key: "Digit5" },
"%": { key: "Digit5", shift: true },
6: { key: "Digit6" },
@ -147,12 +147,12 @@ const chars = {
"+": { key: "Minus" },
"?": { key: "Minus", shift: true },
"\\": { key: "Equal" },
"å": { key: "BracketLeft" },
"Å": { key: "BracketLeft", shift: true },
"ø": { key: "Semicolon" },
"Ø": { key: "Semicolon", shift: true },
"æ": { key: "Quote" },
"Æ": { key: "Quote", shift: true },
å: { key: "BracketLeft" },
Å: { key: "BracketLeft", shift: true },
ø: { key: "Semicolon" },
Ø: { key: "Semicolon", shift: true },
æ: { key: "Quote" },
Æ: { key: "Quote", shift: true },
"'": { key: "Backslash" },
"*": { key: "Backslash", shift: true },
",": { key: "Comma" },
@ -160,7 +160,7 @@ const chars = {
".": { key: "Period" },
":": { key: "Period", shift: true },
"-": { key: "Slash" },
"_": { key: "Slash", shift: true },
_: { key: "Slash", shift: true },
"<": { key: "IntlBackslash" },
">": { key: "IntlBackslash", shift: true },
" ": { key: "Space" },
@ -176,5 +176,5 @@ export const nb_NO: KeyboardLayout = {
// TODO need to localize these maps and layouts
keyDisplayMap: en_US.keyDisplayMap,
modifierDisplayMap: en_US.modifierDisplayMap,
virtualKeyboard: en_US.virtualKeyboard
virtualKeyboard: en_US.virtualKeyboard,
};

View File

@ -1,111 +1,111 @@
import { KeyboardLayout, KeyCombo } from "../keyboardLayouts"
import { KeyboardLayout, KeyCombo } from "../keyboardLayouts";
import { en_US } from "./en_US" // for fallback of keyDisplayMap, modifierDisplayMap, and virtualKeyboard
import { en_US } from "./en_US"; // for fallback of keyDisplayMap, modifierDisplayMap, and virtualKeyboard
const name = "Svenska";
const isoCode = "sv-SE";
const keyTrema: KeyCombo = { key: "BracketRight" } // tréma (umlaut), two dots placed above a vowel
const keyAcute: KeyCombo = { key: "Equal" } // accent aigu (acute accent), mark ´ placed above the letter
const keyHat: KeyCombo = { key: "BracketRight", shift: true } // accent circonflexe (accent hat), mark ^ placed above the letter
const keyGrave: KeyCombo = { key: "Equal", shift: true } // accent grave, mark ` placed above the letter
const keyTilde: KeyCombo = { key: "BracketRight", altRight: true } // tilde, mark ~ placed above the letter
const keyTrema: KeyCombo = { key: "BracketRight" }; // tréma (umlaut), two dots placed above a vowel
const keyAcute: KeyCombo = { key: "Equal" }; // accent aigu (acute accent), mark ´ placed above the letter
const keyHat: KeyCombo = { key: "BracketRight", shift: true }; // accent circonflexe (accent hat), mark ^ placed above the letter
const keyGrave: KeyCombo = { key: "Equal", shift: true }; // accent grave, mark ` placed above the letter
const keyTilde: KeyCombo = { key: "BracketRight", altRight: true }; // tilde, mark ~ placed above the letter
const chars = {
A: { key: "KeyA", shift: true },
"Á": { key: "KeyA", shift: true, accentKey: keyAcute },
"Â": { key: "KeyA", shift: true, accentKey: keyHat },
"À": { key: "KeyA", shift: true, accentKey: keyGrave },
"Ã": { key: "KeyA", shift: true, accentKey: keyTilde },
Á: { key: "KeyA", shift: true, accentKey: keyAcute },
Â: { key: "KeyA", shift: true, accentKey: keyHat },
À: { key: "KeyA", shift: true, accentKey: keyGrave },
Ã: { key: "KeyA", shift: true, accentKey: keyTilde },
B: { key: "KeyB", shift: true },
C: { key: "KeyC", shift: true },
D: { key: "KeyD", shift: true },
E: { key: "KeyE", shift: true },
"Ë": { key: "KeyE", shift: true, accentKey: keyTrema },
"É": { key: "KeyE", shift: true, accentKey: keyAcute },
"Ê": { key: "KeyE", shift: true, accentKey: keyHat },
"È": { key: "KeyE", shift: true, accentKey: keyGrave },
"Ẽ": { key: "KeyE", shift: true, accentKey: keyTilde },
Ë: { key: "KeyE", shift: true, accentKey: keyTrema },
É: { key: "KeyE", shift: true, accentKey: keyAcute },
Ê: { key: "KeyE", shift: true, accentKey: keyHat },
È: { key: "KeyE", shift: true, accentKey: keyGrave },
: { key: "KeyE", shift: true, accentKey: keyTilde },
F: { key: "KeyF", shift: true },
G: { key: "KeyG", shift: true },
H: { key: "KeyH", shift: true },
I: { key: "KeyI", shift: true },
"Ï": { key: "KeyI", shift: true, accentKey: keyTrema },
"Í": { key: "KeyI", shift: true, accentKey: keyAcute },
"Î": { key: "KeyI", shift: true, accentKey: keyHat },
"Ì": { key: "KeyI", shift: true, accentKey: keyGrave },
"Ĩ": { key: "KeyI", shift: true, accentKey: keyTilde },
Ï: { key: "KeyI", shift: true, accentKey: keyTrema },
Í: { key: "KeyI", shift: true, accentKey: keyAcute },
Î: { key: "KeyI", shift: true, accentKey: keyHat },
Ì: { key: "KeyI", shift: true, accentKey: keyGrave },
Ĩ: { key: "KeyI", shift: true, accentKey: keyTilde },
J: { key: "KeyJ", shift: true },
K: { key: "KeyK", shift: true },
L: { key: "KeyL", shift: true },
M: { key: "KeyM", shift: true },
N: { key: "KeyN", shift: true },
O: { key: "KeyO", shift: true },
"Ó": { key: "KeyO", shift: true, accentKey: keyAcute },
"Ô": { key: "KeyO", shift: true, accentKey: keyHat },
"Ò": { key: "KeyO", shift: true, accentKey: keyGrave },
"Õ": { key: "KeyO", shift: true, accentKey: keyTilde },
Ó: { key: "KeyO", shift: true, accentKey: keyAcute },
Ô: { key: "KeyO", shift: true, accentKey: keyHat },
Ò: { key: "KeyO", shift: true, accentKey: keyGrave },
Õ: { key: "KeyO", shift: true, accentKey: keyTilde },
P: { key: "KeyP", shift: true },
Q: { key: "KeyQ", shift: true },
R: { key: "KeyR", shift: true },
S: { key: "KeyS", shift: true },
T: { key: "KeyT", shift: true },
U: { key: "KeyU", shift: true },
"Ü": { key: "KeyU", shift: true, accentKey: keyTrema },
"Ú": { key: "KeyU", shift: true, accentKey: keyAcute },
"Û": { key: "KeyU", shift: true, accentKey: keyHat },
"Ù": { key: "KeyU", shift: true, accentKey: keyGrave },
"Ũ": { key: "KeyU", shift: true, accentKey: keyTilde },
Ü: { key: "KeyU", shift: true, accentKey: keyTrema },
Ú: { key: "KeyU", shift: true, accentKey: keyAcute },
Û: { key: "KeyU", shift: true, accentKey: keyHat },
Ù: { key: "KeyU", shift: true, accentKey: keyGrave },
Ũ: { key: "KeyU", shift: true, accentKey: keyTilde },
V: { key: "KeyV", shift: true },
W: { key: "KeyW", shift: true },
X: { key: "KeyX", shift: true },
Y: { key: "KeyY", shift: true },
Z: { key: "KeyZ", shift: true },
a: { key: "KeyA" },
"á": { key: "KeyA", accentKey: keyAcute },
"â": { key: "KeyA", accentKey: keyHat },
"à": { key: "KeyA", accentKey: keyGrave },
"ã": { key: "KeyA", accentKey: keyTilde },
á: { key: "KeyA", accentKey: keyAcute },
â: { key: "KeyA", accentKey: keyHat },
à: { key: "KeyA", accentKey: keyGrave },
ã: { key: "KeyA", accentKey: keyTilde },
b: { key: "KeyB" },
c: { key: "KeyC" },
d: { key: "KeyD" },
e: { key: "KeyE" },
"ë": { key: "KeyE", accentKey: keyTrema },
"é": { key: "KeyE", accentKey: keyAcute },
"ê": { key: "KeyE", accentKey: keyHat },
"è": { key: "KeyE", accentKey: keyGrave },
"ẽ": { key: "KeyE", accentKey: keyTilde },
ë: { key: "KeyE", accentKey: keyTrema },
é: { key: "KeyE", accentKey: keyAcute },
ê: { key: "KeyE", accentKey: keyHat },
è: { key: "KeyE", accentKey: keyGrave },
: { key: "KeyE", accentKey: keyTilde },
"€": { key: "KeyE", altRight: true },
f: { key: "KeyF" },
g: { key: "KeyG" },
h: { key: "KeyH" },
i: { key: "KeyI" },
"ï": { key: "KeyI", accentKey: keyTrema },
"í": { key: "KeyI", accentKey: keyAcute },
"î": { key: "KeyI", accentKey: keyHat },
"ì": { key: "KeyI", accentKey: keyGrave },
"ĩ": { key: "KeyI", accentKey: keyTilde },
ï: { key: "KeyI", accentKey: keyTrema },
í: { key: "KeyI", accentKey: keyAcute },
î: { key: "KeyI", accentKey: keyHat },
ì: { key: "KeyI", accentKey: keyGrave },
ĩ: { key: "KeyI", accentKey: keyTilde },
j: { key: "KeyJ" },
k: { key: "KeyK" },
l: { key: "KeyL" },
m: { key: "KeyM" },
n: { key: "KeyN" },
o: { key: "KeyO" },
"ó": { key: "KeyO", accentKey: keyAcute },
"ô": { key: "KeyO", accentKey: keyHat },
"ò": { key: "KeyO", accentKey: keyGrave },
"õ": { key: "KeyO", accentKey: keyTilde },
ó: { key: "KeyO", accentKey: keyAcute },
ô: { key: "KeyO", accentKey: keyHat },
ò: { key: "KeyO", accentKey: keyGrave },
õ: { key: "KeyO", accentKey: keyTilde },
p: { key: "KeyP" },
q: { key: "KeyQ" },
r: { key: "KeyR" },
s: { key: "KeyS" },
t: { key: "KeyT" },
u: { key: "KeyU" },
"ü": { key: "KeyU", accentKey: keyTrema },
"ú": { key: "KeyU", accentKey: keyAcute },
"û": { key: "KeyU", accentKey: keyHat },
"ù": { key: "KeyU", accentKey: keyGrave },
"ũ": { key: "KeyU", accentKey: keyTilde },
ü: { key: "KeyU", accentKey: keyTrema },
ú: { key: "KeyU", accentKey: keyAcute },
û: { key: "KeyU", accentKey: keyHat },
ù: { key: "KeyU", accentKey: keyGrave },
ũ: { key: "KeyU", accentKey: keyTilde },
v: { key: "KeyV" },
w: { key: "KeyW" },
x: { key: "KeyX" },
@ -116,14 +116,14 @@ const chars = {
1: { key: "Digit1" },
"!": { key: "Digit1", shift: true },
2: { key: "Digit2" },
"\"": { key: "Digit2", shift: true },
'"': { key: "Digit2", shift: true },
"@": { key: "Digit2", altRight: true },
3: { key: "Digit3" },
"#": { key: "Digit3", shift: true },
"£": { key: "Digit3", altRight: true },
4: { key: "Digit4" },
"¤": { key: "Digit4", shift: true },
"$": { key: "Digit4", altRight: true },
$: { key: "Digit4", altRight: true },
5: { key: "Digit5" },
"%": { key: "Digit5", shift: true },
6: { key: "Digit6" },
@ -143,12 +143,12 @@ const chars = {
"+": { key: "Minus" },
"?": { key: "Minus", shift: true },
"\\": { key: "Minus", altRight: true },
"å": { key: "BracketLeft" },
"Å": { key: "BracketLeft", shift: true },
"ö": { key: "Semicolon" },
"Ö": { key: "Semicolon", shift: true },
"ä": { key: "Quote" },
"Ä": { key: "Quote", shift: true },
å: { key: "BracketLeft" },
Å: { key: "BracketLeft", shift: true },
ö: { key: "Semicolon" },
Ö: { key: "Semicolon", shift: true },
ä: { key: "Quote" },
Ä: { key: "Quote", shift: true },
"'": { key: "Backslash" },
"*": { key: "Backslash", shift: true },
",": { key: "Comma" },
@ -156,7 +156,7 @@ const chars = {
".": { key: "Period" },
":": { key: "Period", shift: true },
"-": { key: "Slash" },
"_": { key: "Slash", shift: true },
_: { key: "Slash", shift: true },
"<": { key: "IntlBackslash" },
">": { key: "IntlBackslash", shift: true },
"|": { key: "IntlBackslash", altRight: true },
@ -173,5 +173,5 @@ export const sv_SE: KeyboardLayout = {
// TODO need to localize these maps and layouts
keyDisplayMap: en_US.keyDisplayMap,
modifierDisplayMap: en_US.modifierDisplayMap,
virtualKeyboard: en_US.virtualKeyboard
virtualKeyboard: en_US.virtualKeyboard,
};

View File

@ -5,7 +5,7 @@
export const keys = {
Again: 0x79,
AlternateErase: 0x9d,
AltGr: 0xe6, // aka AltRight
AltGr: 0xe6, // aka AltRight
AltLeft: 0xe2,
AltRight: 0xe6,
Application: 0x65,
@ -14,10 +14,10 @@ export const keys = {
ArrowRight: 0x4f,
ArrowUp: 0x52,
Attention: 0x9a,
Backquote: 0x35, // aka Grave
Backquote: 0x35, // aka Grave
Backslash: 0x31,
Backspace: 0x2a,
BracketLeft: 0x2f, // aka LeftBrace
BracketLeft: 0x2f, // aka LeftBrace
BracketRight: 0x30, // aka RightBrace
Cancel: 0x9b,
CapsLock: 0x39,
@ -77,7 +77,7 @@ export const keys = {
F24: 0x73,
Find: 0x7e,
Grave: 0x35,
HashTilde: 0x32, // non-US # and ~
HashTilde: 0x32, // non-US # and ~
Help: 0x75,
Home: 0x4a,
Insert: 0x49,
@ -134,19 +134,19 @@ export const keys = {
MetaRight: 0xe7,
Minus: 0x2d,
Mute: 0x7f,
NumLock: 0x53, // and Clear
Numpad0: 0x62, // and Insert
NumLock: 0x53, // and Clear
Numpad0: 0x62, // and Insert
Numpad00: 0xb0,
Numpad000: 0xb1,
Numpad1: 0x59, // and End
Numpad2: 0x5a, // and Down Arrow
Numpad3: 0x5b, // and Page Down
Numpad4: 0x5c, // and Left Arrow
Numpad1: 0x59, // and End
Numpad2: 0x5a, // and Down Arrow
Numpad3: 0x5b, // and Page Down
Numpad4: 0x5c, // and Left Arrow
Numpad5: 0x5d,
Numpad6: 0x5e, // and Right Arrow
Numpad7: 0x5f, // and Home
Numpad8: 0x60, // and Up Arrow
Numpad9: 0x61, // and Page Up
Numpad6: 0x5e, // and Right Arrow
Numpad7: 0x5f, // and Home
Numpad8: 0x60, // and Up Arrow
Numpad9: 0x61, // and Page Up
NumpadAdd: 0x57,
NumpadAnd: 0xc7,
NumpadAt: 0xce,
@ -226,7 +226,7 @@ export const keys = {
Slash: 0x38,
Space: 0x2c,
Stop: 0x78,
SystemRequest: 0x9a, // aka Attention
SystemRequest: 0x9a, // aka Attention
Tab: 0x2b,
ThousandsSeparator: 0xb2,
Tilde: 0x35,
@ -251,7 +251,7 @@ export const deadKeys = {
Slash: 0x02f8,
Tilde: 0x007e,
Umlaut: 0x00a8,
} as Record<string, number>
} as Record<string, number>;
export const modifiers = {
ControlLeft: 0x01,

View File

@ -43,10 +43,16 @@ const SettingsHardwareRoute = lazy(() => import("@routes/devices.$id.settings.ha
const SettingsVideoRoute = lazy(() => import("@routes/devices.$id.settings.video"));
const SettingsAppearanceRoute = lazy(() => import("@routes/devices.$id.settings.appearance"));
const SettingsGeneralIndexRoute = lazy(() => import("@routes/devices.$id.settings.general._index"));
const SettingsGeneralRebootRoute = lazy(() => import("@routes/devices.$id.settings.general.reboot"));
const SettingsGeneralUpdateRoute = lazy(() => import("@routes/devices.$id.settings.general.update"));
const SettingsGeneralRebootRoute = lazy(
() => import("@routes/devices.$id.settings.general.reboot"),
);
const SettingsGeneralUpdateRoute = lazy(
() => import("@routes/devices.$id.settings.general.update"),
);
const SettingsNetworkRoute = lazy(() => import("@routes/devices.$id.settings.network"));
const SecurityAccessLocalAuthRoute = lazy(() => import("@routes/devices.$id.settings.access.local-auth"));
const SecurityAccessLocalAuthRoute = lazy(
() => import("@routes/devices.$id.settings.access.local-auth"),
);
const SettingsMacrosRoute = lazy(() => import("@routes/devices.$id.settings.macros"));
const SettingsMacrosAddRoute = lazy(() => import("@routes/devices.$id.settings.macros.add"));
const SettingsMacrosEditRoute = lazy(() => import("@routes/devices.$id.settings.macros.edit"));
@ -364,7 +370,7 @@ if (isOnDevice) {
{
path: "devices",
element: <DevicesRoute />,
loader: DevicesRoute.loader
loader: DevicesRoute.loader,
},
],
},

View File

@ -19,8 +19,9 @@ const ToastContent = ({
t: Toast;
}) => (
<Card
className={`${t.visible ? "animate-enter" : "animate-leave"
} pointer-events-auto z-30 w-full max-w-sm shadow-xl!`}
className={`${
t.visible ? "animate-enter" : "animate-leave"
} pointer-events-auto z-30 w-full max-w-sm shadow-xl!`}
>
<div className="flex items-center gap-x-2 p-2.5 px-2">
{icon}
@ -34,7 +35,7 @@ const notifications = {
return toast.custom(
(t: Toast) => (
<ToastContent
icon={<CheckCircleIcon className="w-5 h-5 text-green-500 dark:text-green-400" />}
icon={<CheckCircleIcon className="h-5 w-5 text-green-500 dark:text-green-400" />}
message={message}
t={t}
/>
@ -47,7 +48,7 @@ const notifications = {
return toast.custom(
(t: Toast) => (
<ToastContent
icon={<XCircleIcon className="w-5 h-5 text-red-500 dark:text-red-400" />}
icon={<XCircleIcon className="h-5 w-5 text-red-500 dark:text-red-400" />}
message={message}
t={t}
/>

View File

@ -31,7 +31,5 @@ export const FeatureFlagProvider = ({
const value = { appVersion, isFeatureEnabled };
return (
<FeatureFlagContext.Provider value={value}>{children}</FeatureFlagContext.Provider>
);
return <FeatureFlagContext.Provider value={value}>{children}</FeatureFlagContext.Provider>;
};

View File

@ -37,7 +37,7 @@ const loader: LoaderFunction = async ({ request }: LoaderFunctionArgs) => {
};
export default function AdoptRoute() {
return (<></>);
return <></>;
}
AdoptRoute.loader = loader;

View File

@ -1,5 +1,10 @@
import { Form, redirect, useActionData, useLoaderData } from "react-router";
import type { ActionFunction, ActionFunctionArgs, LoaderFunction, LoaderFunctionArgs } from "react-router";
import type {
ActionFunction,
ActionFunctionArgs,
LoaderFunction,
LoaderFunctionArgs,
} from "react-router";
import { ChevronLeftIcon } from "@heroicons/react/16/solid";
import { User } from "@hooks/stores";
@ -76,9 +81,9 @@ export default function DevicesIdDeregister() {
kvmName={device?.name}
/>
<div className="w-full h-full">
<div className="h-full w-full">
<div className="mt-4">
<div className="w-full h-full px-4 mx-auto space-y-6 sm:max-w-6xl sm:px-8 md:max-w-7xl md:px-12 lg:max-w-8xl">
<div className="mx-auto h-full w-full space-y-6 px-4 sm:max-w-6xl sm:px-8 md:max-w-7xl md:px-12 lg:max-w-8xl">
<div className="space-y-4">
<LinkButton
size="SM"

View File

@ -1,11 +1,6 @@
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
import { useNavigate } from "react-router";
import {
LuLink,
LuRadioReceiver,
LuCheck,
LuUpload,
} from "react-icons/lu";
import { LuLink, LuRadioReceiver, LuCheck, LuUpload } from "react-icons/lu";
import { PlusCircleIcon, ExclamationTriangleIcon } from "@heroicons/react/20/solid";
import { TrashIcon } from "@heroicons/react/16/solid";
@ -43,13 +38,8 @@ export default function MountRoute() {
}
export function Dialog({ onClose }: Readonly<{ onClose: () => void }>) {
const {
modalView,
setModalView,
setRemoteVirtualMediaState,
errorMessage,
setErrorMessage,
} = useMountMediaStore();
const { modalView, setModalView, setRemoteVirtualMediaState, errorMessage, setErrorMessage } =
useMountMediaStore();
const navigate = useNavigate();
const [incompleteFileName, setIncompleteFileName] = useState<string | null>(null);
@ -65,9 +55,7 @@ export function Dialog({ onClose }: Readonly<{ onClose: () => void }>) {
if ("error" in resp) {
reject(new Error(resp.error.message));
} else {
setRemoteVirtualMediaState(
resp as unknown as MountMediaState["remoteVirtualMediaState"],
);
setRemoteVirtualMediaState(resp as unknown as MountMediaState["remoteVirtualMediaState"]);
resolve(null);
}
});
@ -132,10 +120,7 @@ export function Dialog({ onClose }: Readonly<{ onClose: () => void }>) {
className={cx("mx-auto max-w-4xl px-4 transition-all duration-300 ease-in-out", {
"max-w-4xl": modalView === "mode",
"max-w-2xl": modalView === "device",
"max-w-xl":
modalView === "url" ||
modalView === "upload" ||
modalView === "error",
"max-w-xl": modalView === "url" || modalView === "upload" || modalView === "error",
})}
>
<GridCard cardClassName="relative w-full text-left pointer-events-auto">
@ -281,9 +266,7 @@ function ModeSelectionView({
>
<div
className="relative z-50 flex flex-col items-start p-4 select-none"
onClick={() =>
disabled ? null : setSelectedMode(mode as "url" | "device")
}
onClick={() => (disabled ? null : setSelectedMode(mode as "url" | "device"))}
>
<div>
<Card>
@ -293,14 +276,10 @@ function ModeSelectionView({
</Card>
</div>
<div className="mt-2 space-y-0">
<p className="block pt-1 text-xs text-red-500">
{tag ? tag : <>&nbsp;</>}
</p>
<p className="block pt-1 text-xs text-red-500">{tag ? tag : <>&nbsp;</>}</p>
<h3 className="text-sm font-medium dark:text-white">{label}</h3>
<p className="text-sm text-gray-700 dark:text-slate-400">
{description}
</p>
<p className="text-sm text-gray-700 dark:text-slate-400">{description}</p>
</div>
<input
type="radio"
@ -414,10 +393,7 @@ function UrlView({
return (
<div className="w-full space-y-4">
<ViewHeader
title={m.mount_view_url_title()}
description={m.mount_view_url_description()}
/>
<ViewHeader title={m.mount_view_url_title()} description={m.mount_view_url_description()} />
<div
className="animate-fadeIn opacity-0"
@ -452,9 +428,7 @@ function UrlView({
loading={mountInProgress}
text={m.mount_button_mount_url()}
onClick={() => onMount(url, usbMode)}
disabled={
mountInProgress || !isUrlValid || url.length === 0
}
disabled={mountInProgress || !isUrlValid || url.length === 0}
/>
</div>
</div>
@ -538,10 +512,9 @@ function DeviceFileView({
const percentageUsed = useMemo(() => {
if (!storageSpace) return 0;
return Number(
(
(storageSpace.bytesUsed / (storageSpace.bytesUsed + storageSpace.bytesFree)) *
100
).toFixed(1),
((storageSpace.bytesUsed / (storageSpace.bytesUsed + storageSpace.bytesFree)) * 100).toFixed(
1,
),
);
}, [storageSpace]);
@ -681,9 +654,7 @@ function DeviceFileView({
onDelete={() => {
const selectedFile = onStorageFiles.find(f => f.name === file.name);
if (!selectedFile) return;
if (
window.confirm(m.mount_confirm_delete({ name: selectedFile.name }))
) {
if (window.confirm(m.mount_confirm_delete({ name: selectedFile.name }))) {
handleDeleteFile(selectedFile);
}
}}
@ -698,7 +669,7 @@ function DeviceFileView({
{m.mount_button_showing_results({
from: indexOfFirstFile + 1,
to: Math.min(indexOfLastFile, onStorageFiles.length),
total: onStorageFiles.length
total: onStorageFiles.length,
})}
</p>
<div className="flex items-center gap-x-2">
@ -827,9 +798,7 @@ function UploadFileView({
onCancelUpload: () => void;
incompleteFileName?: string;
}) {
const [uploadState, setUploadState] = useState<"idle" | "uploading" | "success">(
"idle",
);
const [uploadState, setUploadState] = useState<"idle" | "uploading" | "success">("idle");
const [uploadProgress, setUploadProgress] = useState(0);
const [uploadedFileName, setUploadedFileName] = useState<string | null>(null);
const [uploadedFileSize, setUploadedFileSize] = useState<number | null>(null);
@ -854,14 +823,8 @@ function UploadFileView({
};
}, []);
function handleWebRTCUpload(
file: File,
alreadyUploadedBytes: number,
dataChannel: string,
) {
const rtcDataChannel = useRTCStore
.getState()
.peerConnection?.createDataChannel(dataChannel);
function handleWebRTCUpload(file: File, alreadyUploadedBytes: number, dataChannel: string) {
const rtcDataChannel = useRTCStore.getState().peerConnection?.createDataChannel(dataChannel);
if (!rtcDataChannel) {
console.error("Failed to create data channel for file upload");
@ -903,8 +866,7 @@ function UploadFileView({
}
// Calculate average speed
const averageSpeed =
speedHistory.reduce((a, b) => a + b, 0) / speedHistory.length;
const averageSpeed = speedHistory.reduce((a, b) => a + b, 0) / speedHistory.length;
setUploadSpeed(averageSpeed);
setUploadProgress((AlreadyUploadedBytes / Size) * 100);
@ -961,11 +923,7 @@ function UploadFileView({
};
}
async function handleHttpUpload(
file: File,
alreadyUploadedBytes: number,
dataChannel: string,
) {
async function handleHttpUpload(file: File, alreadyUploadedBytes: number, dataChannel: string) {
const uploadUrl = `${DEVICE_API}/storage/upload?uploadId=${dataChannel}`;
const xhr = new XMLHttpRequest();
@ -994,8 +952,7 @@ function UploadFileView({
}
// Calculate average speed
const averageSpeed =
speedHistory.reduce((a, b) => a + b, 0) / speedHistory.length;
const averageSpeed = speedHistory.reduce((a, b) => a + b, 0) / speedHistory.length;
setUploadSpeed(averageSpeed);
setUploadProgress((totalUploaded / totalSize) * 100);
@ -1035,10 +992,7 @@ function UploadFileView({
// Reset the upload error when a new file is selected
setUploadError(null);
if (
incompleteFileName &&
file.name !== incompleteFileName.replace(".incomplete", "")
) {
if (incompleteFileName && file.name !== incompleteFileName.replace(".incomplete", "")) {
setFileError(
m.mount_please_select_file({ name: incompleteFileName.replace(".incomplete", "") }),
);
@ -1052,31 +1006,35 @@ function UploadFileView({
setUploadState("uploading");
console.log("Upload state set to 'uploading'");
send("startStorageFileUpload", { filename: file.name, size: file.size }, (resp: JsonRpcResponse) => {
console.log("startStorageFileUpload response:", resp);
if ("error" in resp) {
console.error("Upload error:", resp.error.message);
setUploadError(resp.error.data || resp.error.message);
setUploadState("idle");
console.log("Upload state set to 'idle'");
return;
}
send(
"startStorageFileUpload",
{ filename: file.name, size: file.size },
(resp: JsonRpcResponse) => {
console.log("startStorageFileUpload response:", resp);
if ("error" in resp) {
console.error("Upload error:", resp.error.message);
setUploadError(resp.error.data || resp.error.message);
setUploadState("idle");
console.log("Upload state set to 'idle'");
return;
}
const { alreadyUploadedBytes, dataChannel } = resp.result as {
alreadyUploadedBytes: number;
dataChannel: string;
};
const { alreadyUploadedBytes, dataChannel } = resp.result as {
alreadyUploadedBytes: number;
dataChannel: string;
};
console.log(
`Already uploaded bytes: ${alreadyUploadedBytes}, Data channel: ${dataChannel}`,
);
console.log(
`Already uploaded bytes: ${alreadyUploadedBytes}, Data channel: ${dataChannel}`,
);
if (isOnDevice) {
handleHttpUpload(file, alreadyUploadedBytes, dataChannel);
} else {
handleWebRTCUpload(file, alreadyUploadedBytes, dataChannel);
}
});
if (isOnDevice) {
handleHttpUpload(file, alreadyUploadedBytes, dataChannel);
} else {
handleWebRTCUpload(file, alreadyUploadedBytes, dataChannel);
}
},
);
}
};
@ -1086,7 +1044,9 @@ function UploadFileView({
title={m.mount_upload_title()}
description={
incompleteFileName
? m.mount_continue_uploading_with_name({ name: incompleteFileName.replace(".incomplete", "") })
? m.mount_continue_uploading_with_name({
name: incompleteFileName.replace(".incomplete", ""),
})
: m.mount_upload_description()
}
/>
@ -1124,7 +1084,9 @@ function UploadFileView({
</div>
<h3 className="text-sm leading-none font-semibold text-black dark:text-white">
{incompleteFileName
? m.mount_click_to_select_incomplete({ name: incompleteFileName.replace(".incomplete", "") })
? m.mount_click_to_select_incomplete({
name: incompleteFileName.replace(".incomplete", ""),
})
: m.mount_click_to_select_file()}
</h3>
<p className="text-xs leading-none text-slate-700 dark:text-slate-300">
@ -1143,7 +1105,9 @@ function UploadFileView({
</Card>
</div>
<h3 className="leading-non text-lg font-semibold text-black dark:text-white">
{m.mount_uploading_with_name({ name: formatters.truncateMiddle(uploadedFileName, 30) })}
{m.mount_uploading_with_name({
name: formatters.truncateMiddle(uploadedFileName, 30),
})}
</h3>
<p className="text-xs leading-none text-slate-700 dark:text-slate-300">
{formatters.bytes(uploadedFileSize || 0)}
@ -1180,7 +1144,9 @@ function UploadFileView({
{m.mount_upload_successful()}
</h3>
<p className="text-xs leading-none text-slate-700 dark:text-slate-300">
{m.mount_uploaded_has_been_uploaded({ name: formatters.truncateMiddle(uploadedFileName, 40) })}
{m.mount_uploaded_has_been_uploaded({
name: formatters.truncateMiddle(uploadedFileName, 40),
})}
</p>
</div>
)}
@ -1196,15 +1162,13 @@ function UploadFileView({
className="hidden"
accept=".iso, .img"
/>
{fileError && (
<p className="mt-2 text-sm text-red-600 dark:text-red-400">{fileError}</p>
)}
{fileError && <p className="mt-2 text-sm text-red-600 dark:text-red-400">{fileError}</p>}
</div>
{/* Display upload error if present */}
{uploadError && (
<div
className="mt-2 animate-fadeIn truncate text-sm text-red-600 dark:text-red-400 opacity-0"
className="mt-2 animate-fadeIn truncate text-sm text-red-600 opacity-0 dark:text-red-400"
style={{ animationDuration: "0.7s" }}
>
{m.mount_upload_error({ error: String(uploadError) })}
@ -1263,9 +1227,7 @@ function ErrorView({
<ExclamationTriangleIcon className="h-6 w-6" />
<h2 className="text-lg leading-tight font-bold">{m.mount_error_title()}</h2>
</div>
<p className="text-sm leading-snug text-slate-600">
{m.mount_error_description()}
</p>
<p className="text-sm leading-snug text-slate-600">{m.mount_error_description()}</p>
</div>
{errorMessage && (
<Card className="border border-red-200 bg-red-50 p-4">
@ -1274,7 +1236,12 @@ function ErrorView({
)}
<div className="flex justify-end space-x-2">
<Button size="SM" theme="light" text={m.close()} onClick={onClose} />
<Button size="SM" theme="primary" text={m.mount_button_back_to_overview()} onClick={onRetry} />
<Button
size="SM"
theme="primary"
text={m.mount_button_back_to_overview()}
onClick={onRetry}
/>
</div>
</div>
);
@ -1379,12 +1346,8 @@ function PreUploadedImageItem({
function ViewHeader({ title, description }: { title: string; description: string }) {
return (
<div className="space-y-0">
<h2 className="text-lg leading-tight font-bold text-black dark:text-white">
{title}
</h2>
<div className="text-sm leading-snug text-slate-600 dark:text-slate-400">
{description}
</div>
<h2 className="text-lg leading-tight font-bold text-black dark:text-white">{title}</h2>
<div className="text-sm leading-snug text-slate-600 dark:text-slate-400">{description}</div>
</div>
);
}

View File

@ -30,14 +30,17 @@ export default function OtherSessionRoute() {
</div>
<div className="text-left">
<p className="text-base font-semibold dark:text-white">
{m.other_session_detected()}
</p>
<p className="text-base font-semibold dark:text-white">{m.other_session_detected()}</p>
<p className="mb-4 text-sm text-slate-600 dark:text-slate-400">
{m.other_session_take_over()}
</p>
<div className="flex items-center justify-start space-x-4">
<Button size="SM" theme="primary" text={m.other_session_use_here_button()} onClick={handleClose} />
<Button
size="SM"
theme="primary"
text={m.other_session_use_here_button()}
onClick={handleClose}
/>
</div>
</div>
</div>

View File

@ -1,5 +1,10 @@
import { Form, redirect, useActionData, useLoaderData } from "react-router";
import type { ActionFunction, ActionFunctionArgs, LoaderFunction, LoaderFunctionArgs } from "react-router";
import type {
ActionFunction,
ActionFunctionArgs,
LoaderFunction,
LoaderFunctionArgs,
} from "react-router";
import { ChevronLeftIcon } from "@heroicons/react/16/solid";
import { Button, LinkButton } from "@components/Button";

View File

@ -5,10 +5,10 @@ import { getDeviceUiPath } from "@hooks/useAppNavigation";
const loader: LoaderFunction = ({ params }: LoaderFunctionArgs) => {
return redirect(getDeviceUiPath("/settings/general", params.id));
}
};
export default function SettingIndexRoute() {
return (<></>);
return <></>;
}
SettingIndexRoute.loader = loader;

View File

@ -125,10 +125,7 @@ export default function SettingsAccessIndexRoute() {
returnTo.search = "";
returnTo.hash = "";
window.location.href =
cloudAppUrl +
"/signup?deviceId=" +
deviceId +
`&returnTo=${returnTo.toString()}`;
cloudAppUrl + "/signup?deviceId=" + deviceId + `&returnTo=${returnTo.toString()}`;
});
},
[deviceId, send],
@ -168,7 +165,9 @@ export default function SettingsAccessIndexRoute() {
notifications.success(m.access_tls_updated());
});
}, [send]);
},
[send],
);
// Handle TLS mode change
const handleTlsModeChange = (value: string) => {
@ -206,10 +205,7 @@ export default function SettingsAccessIndexRoute() {
return (
<div className="space-y-4">
<SettingsPageHeader
title={m.access_title()}
description={m.access_description()}
/>
<SettingsPageHeader title={m.access_title()} description={m.access_description()} />
{loaderData?.authMode && (
<>
@ -246,9 +242,7 @@ export default function SettingsAccessIndexRoute() {
<TextAreaWithLabel
label={m.access_certificate_label()}
rows={3}
placeholder={
"-----BEGIN CERTIFICATE-----\n...\n-----END CERTIFICATE-----"
}
placeholder={"-----BEGIN CERTIFICATE-----\n...\n-----END CERTIFICATE-----"}
value={tlsCert}
onChange={e => handleTlsCertChange(e.target.value)}
/>
@ -256,9 +250,7 @@ export default function SettingsAccessIndexRoute() {
label={m.access_private_key_label()}
description={m.access_private_key_description()}
rows={3}
placeholder={
"-----BEGIN PRIVATE KEY-----\n...\n-----END PRIVATE KEY-----"
}
placeholder={"-----BEGIN PRIVATE KEY-----\n...\n-----END PRIVATE KEY-----"}
value={tlsKey}
onChange={e => handleTlsKeyChange(e.target.value)}
/>
@ -275,7 +267,11 @@ export default function SettingsAccessIndexRoute() {
<SettingsItem
title={m.access_authentication_mode_title()}
description={loaderData.authMode === "password" ? m.access_auth_mode_password() : m.access_auth_mode_no_password()}
description={
loaderData.authMode === "password"
? m.access_auth_mode_password()
: m.access_auth_mode_no_password()
}
>
{loaderData.authMode === "password" ? (
<Button
@ -320,10 +316,7 @@ export default function SettingsAccessIndexRoute() {
)}
<div className="space-y-4">
<SettingsSectionHeader
title="Remote"
description={m.access_remote_description()}
/>
<SettingsSectionHeader title="Remote" description={m.access_remote_description()} />
<div className="space-y-4">
{!isAdopted && (
@ -438,11 +431,7 @@ export default function SettingsAccessIndexRoute() {
className="text-red-600"
onClick={() => {
if (deviceId) {
if (
window.confirm(
m.access_confirm_deregister(),
)
) {
if (window.confirm(m.access_confirm_deregister())) {
deregisterDevice();
}
} else {

View File

@ -195,9 +195,7 @@ function CreatePasswordModal({
}}
>
<div>
<h2 className="text-lg font-semibold dark:text-white">
{m.local_auth_create_title()}
</h2>
<h2 className="text-lg font-semibold dark:text-white">{m.local_auth_create_title()}</h2>
<p className="text-sm text-slate-600 dark:text-slate-400">
{m.local_auth_create_description()}
</p>
@ -225,7 +223,12 @@ function CreatePasswordModal({
text={m.local_auth_create_secure_button()}
onClick={() => onSetPassword(password, confirmPassword)}
/>
<Button size="SM" theme="light" text={m.local_auth_create_not_now_button()} onClick={onCancel} />
<Button
size="SM"
theme="light"
text={m.local_auth_create_not_now_button()}
onClick={onCancel}
/>
</div>
{error && <p className="text-sm text-red-500">{error}</p>}
</form>
@ -282,11 +285,7 @@ function UpdatePasswordModal({
onCancel,
error,
}: {
onUpdatePassword: (
oldPassword: string,
newPassword: string,
confirmNewPassword: string,
) => void;
onUpdatePassword: (oldPassword: string, newPassword: string, confirmNewPassword: string) => void;
onCancel: () => void;
error: string | null;
}) {

View File

@ -81,8 +81,12 @@ export default function SettingsAdvancedRoute() {
if ("error" in resp) {
notifications.error(
enabled
? m.advanced_error_usb_emulation_enable({ error: resp.error.data || m.unknown_error() })
: m.advanced_error_usb_emulation_disable({ error: resp.error.data || m.unknown_error() })
? m.advanced_error_usb_emulation_enable({
error: resp.error.data || m.unknown_error(),
})
: m.advanced_error_usb_emulation_disable({
error: resp.error.data || m.unknown_error(),
}),
);
return;
}
@ -97,7 +101,7 @@ export default function SettingsAdvancedRoute() {
send("resetConfig", {}, (resp: JsonRpcResponse) => {
if ("error" in resp) {
notifications.error(
m.advanced_error_reset_config({ error: resp.error.data || m.unknown_error() })
m.advanced_error_reset_config({ error: resp.error.data || m.unknown_error() }),
);
return;
}
@ -109,7 +113,7 @@ export default function SettingsAdvancedRoute() {
send("setSSHKeyState", { sshKey }, (resp: JsonRpcResponse) => {
if ("error" in resp) {
notifications.error(
m.advanced_error_update_ssh_key({ error: resp.error.data || m.unknown_error() })
m.advanced_error_update_ssh_key({ error: resp.error.data || m.unknown_error() }),
);
return;
}
@ -122,7 +126,7 @@ export default function SettingsAdvancedRoute() {
send("setDevModeState", { enabled: developerMode }, (resp: JsonRpcResponse) => {
if ("error" in resp) {
notifications.error(
m.advanced_error_set_dev_mode({ error: resp.error.data || m.unknown_error() })
m.advanced_error_set_dev_mode({ error: resp.error.data || m.unknown_error() }),
);
return;
}
@ -137,7 +141,7 @@ export default function SettingsAdvancedRoute() {
send("setDevChannelState", { enabled }, (resp: JsonRpcResponse) => {
if ("error" in resp) {
notifications.error(
m.advanced_error_set_dev_channel({ error: resp.error.data || m.unknown_error() })
m.advanced_error_set_dev_channel({ error: resp.error.data || m.unknown_error() }),
);
return;
}
@ -154,7 +158,7 @@ export default function SettingsAdvancedRoute() {
notifications.error(
enabled
? m.advanced_error_loopback_enable({ error: resp.error.data || m.unknown_error() })
: m.advanced_error_loopback_disable({ error: resp.error.data || m.unknown_error() })
: m.advanced_error_loopback_disable({ error: resp.error.data || m.unknown_error() }),
);
return;
}
@ -190,9 +194,10 @@ export default function SettingsAdvancedRoute() {
const handleVersionUpdateError = useCallback((error?: JsonRpcError | string) => {
notifications.error(
m.advanced_error_version_update({
error: typeof error === "string" ? error : (error?.data ?? error?.message ?? m.unknown_error())
error:
typeof error === "string" ? error : (error?.data ?? error?.message ?? m.unknown_error()),
}),
{ duration: 1000 * 15 } // 15 seconds
{ duration: 1000 * 15 }, // 15 seconds
);
setCustomVersionUpdateLoading(false);
}, []);
@ -200,16 +205,15 @@ export default function SettingsAdvancedRoute() {
const handleCustomVersionUpdate = useCallback(async () => {
const components: UpdateComponents = {};
if (["app", "both"].includes(updateTarget) && appVersion) components.app = appVersion;
if (["system", "both"].includes(updateTarget) && systemVersion) components.system = systemVersion;
if (["system", "both"].includes(updateTarget) && systemVersion)
components.system = systemVersion;
let versionInfo: SystemVersionInfo | undefined;
try {
// we do not need to set it to false if check succeeds,
// because it will be redirected to the update page later
setCustomVersionUpdateLoading(true);
versionInfo = await checkUpdateComponents({
components,
}, devChannel);
versionInfo = await checkUpdateComponents({ components }, devChannel);
} catch (error: unknown) {
const jsonRpcError = error as JsonRpcError;
handleVersionUpdateError(jsonRpcError);
@ -223,7 +227,11 @@ export default function SettingsAdvancedRoute() {
hasUpdate = true;
pageParams.set("custom_app_version", versionInfo.remote?.appVersion);
}
if (components.system && versionInfo?.remote?.systemVersion && versionInfo?.systemUpdateAvailable) {
if (
components.system &&
versionInfo?.remote?.systemVersion &&
versionInfo?.systemUpdateAvailable
) {
hasUpdate = true;
pageParams.set("custom_system_version", versionInfo.remote?.systemVersion);
}
@ -237,17 +245,18 @@ export default function SettingsAdvancedRoute() {
// Navigate to update page
navigateTo(`/settings/general/update?${pageParams.toString()}`);
}, [
updateTarget, appVersion, systemVersion, devChannel,
navigateTo, resetConfig, handleVersionUpdateError,
setCustomVersionUpdateLoading
appVersion,
devChannel,
handleVersionUpdateError,
navigateTo,
resetConfig,
systemVersion,
updateTarget,
]);
return (
<div className="space-y-4">
<SettingsPageHeader
title={m.advanced_title()}
description={m.advanced_description()}
/>
<SettingsPageHeader title={m.advanced_title()} description={m.advanced_description()} />
<div className="space-y-4">
<SettingsItem
@ -319,7 +328,8 @@ export default function SettingsAdvancedRoute() {
placeholder={m.advanced_ssh_public_key_placeholder()}
/>
<p className="text-xs text-slate-600 dark:text-slate-400">
{m.advanced_ssh_default_user()}<strong>root</strong>.
{m.advanced_ssh_default_user()}
<strong>root</strong>.
</p>
<div className="flex items-center gap-x-2">
<Button
@ -426,8 +436,6 @@ export default function SettingsAdvancedRoute() {
/>
</SettingsItem>
<SettingsItem
title={m.advanced_troubleshooting_mode_title()}
description={m.advanced_troubleshooting_mode_description()}
@ -450,7 +458,9 @@ export default function SettingsAdvancedRoute() {
size="SM"
theme="light"
text={
usbEmulationEnabled ? m.advanced_disable_usb_emulation() : m.advanced_enable_usb_emulation()
usbEmulationEnabled
? m.advanced_disable_usb_emulation()
: m.advanced_enable_usb_emulation()
}
onClick={() => handleUsbEmulationToggle(!usbEmulationEnabled)}
/>
@ -484,12 +494,8 @@ export default function SettingsAdvancedRoute() {
title={m.advanced_loopback_warning_title()}
description={
<>
<p>
{m.advanced_loopback_warning_description()}
</p>
<p>
{m.advanced_loopback_warning_before()}
</p>
<p>{m.advanced_loopback_warning_description()}</p>
<p>{m.advanced_loopback_warning_before()}</p>
<ul className="list-disc space-y-1 pl-5 text-xs text-slate-700 dark:text-slate-300">
<li>{m.advanced_loopback_warning_ssh()}</li>
<li>{m.advanced_loopback_warning_cloud()}</li>

Some files were not shown because too many files have changed in this diff Show More