mirror of
https://github.com/jetkvm/kvm.git
synced 2025-09-16 08:38:14 +00:00
* chore: Upgrade UI vite and tailwind packages Vite 5.2.0 -> 6.3.5 @vitejs/plugin-basic-ssl 1.2.0 -> 2.0.0 cva: 1.0.0-beta.1 -> 1.0.0-beta.3 focus-trap-react 10.2.3 -> 11.0.3 framer-motion 11.15.0 -> 12.11.0 @tailwindcss/postcss 4.1.6 @tailwindcss/vite 4.1.6 tailwind 3.4.17 -> 4.1.6 tailwind-merge 2.5.5 -> 3.3.0 Minor updates: @headlessui/react 2.2.2 -> 2.2.3 @types/react 19.1.3 -> 19.1.4 @types/react-dom 19.1.3 -> 19.1.5 @typescript-eslint/eslint-plugin 8.32.0 -> 8.32.1 @typescript-eslint/parser 8.32.0 -> 8.32.1 react-simple-keyboard 3.8.71 -> 3.8.72 The new version of vite required an Node 22.15 (since that's current LTS and node 21.x is EOL) The changes to css due to the tailwind 3 to 4 upgrade were done following [the upgrade guide](https://tailwindcss.com/docs/upgrade-guide#changes-from-v3) Done in this order (important): `shadow-sm` -> `shadow-xs` `shadow` -> `shadown-sm` `rounded` -> `rounded-sm` `outline-none` -> `outline-hidden` `32rem_32rem_at_center` -> `center_at_32rem_32rem` (revised order of gradient props) `ring-1 ring-black ring-opacity-5` -> `ring-1 ring-black/50` `flex-shrink-0` -> `shrink-0` `flex-grow-0` -> `grow-0` `outline outline-1` -> `outline-1` ALSO removed the **extra** `opacity-0` on the video element (trips up latest tailwind causing the video to be invisible) FocusTrap is now not exported as the default, so change those imports headlessui's Menu completely changed, so upgrade to the new syntax which necessitated a reorganization of the Header.tsx to enable the "menu" to still work * Update eslint config and fix errors
174 lines
5.4 KiB
TypeScript
174 lines
5.4 KiB
TypeScript
import { LuHardDrive, LuPower, LuRotateCcw } from "react-icons/lu";
|
|
import { useEffect, useState } from "react";
|
|
|
|
import { Button } from "@components/Button";
|
|
import Card from "@components/Card";
|
|
import { SettingsPageHeader } from "@components/SettingsPageheader";
|
|
import notifications from "@/notifications";
|
|
import LoadingSpinner from "@/components/LoadingSpinner";
|
|
|
|
import { useJsonRpc } from "../../hooks/useJsonRpc";
|
|
|
|
const LONG_PRESS_DURATION = 3000; // 3 seconds for long press
|
|
|
|
interface ATXState {
|
|
power: boolean;
|
|
hdd: boolean;
|
|
}
|
|
|
|
export function ATXPowerControl() {
|
|
const [isPowerPressed, setIsPowerPressed] = useState(false);
|
|
const [powerPressTimer, setPowerPressTimer] = useState<ReturnType<
|
|
typeof setTimeout
|
|
> | null>(null);
|
|
const [atxState, setAtxState] = useState<ATXState | null>(null);
|
|
|
|
const [send] = useJsonRpc(function onRequest(resp) {
|
|
if (resp.method === "atxState") {
|
|
setAtxState(resp.params as ATXState);
|
|
}
|
|
});
|
|
|
|
// Request initial state
|
|
useEffect(() => {
|
|
send("getATXState", {}, resp => {
|
|
if ("error" in resp) {
|
|
notifications.error(
|
|
`Failed to get ATX state: ${resp.error.data || "Unknown error"}`,
|
|
);
|
|
return;
|
|
}
|
|
setAtxState(resp.result as ATXState);
|
|
});
|
|
}, [send]);
|
|
|
|
const handlePowerPress = (pressed: boolean) => {
|
|
// Prevent phantom releases
|
|
if (!pressed && !isPowerPressed) return;
|
|
|
|
setIsPowerPressed(pressed);
|
|
|
|
// Handle button press
|
|
if (pressed) {
|
|
// Start long press timer
|
|
const timer = setTimeout(() => {
|
|
// Send long press action
|
|
console.log("Sending long press ATX power action");
|
|
send("setATXPowerAction", { action: "power-long" }, resp => {
|
|
if ("error" in resp) {
|
|
notifications.error(
|
|
`Failed to send ATX power action: ${resp.error.data || "Unknown error"}`,
|
|
);
|
|
}
|
|
setIsPowerPressed(false);
|
|
});
|
|
}, LONG_PRESS_DURATION);
|
|
|
|
setPowerPressTimer(timer);
|
|
}
|
|
// Handle button release
|
|
else {
|
|
// If timer exists, was a short press
|
|
if (powerPressTimer) {
|
|
clearTimeout(powerPressTimer);
|
|
setPowerPressTimer(null);
|
|
|
|
// Send short press action
|
|
console.log("Sending short press ATX power action");
|
|
send("setATXPowerAction", { action: "power-short" }, resp => {
|
|
if ("error" in resp) {
|
|
notifications.error(
|
|
`Failed to send ATX power action: ${resp.error.data || "Unknown error"}`,
|
|
);
|
|
}
|
|
});
|
|
}
|
|
}
|
|
};
|
|
|
|
// Cleanup timer on unmount
|
|
useEffect(() => {
|
|
return () => {
|
|
if (powerPressTimer) {
|
|
clearTimeout(powerPressTimer);
|
|
}
|
|
};
|
|
}, [powerPressTimer]);
|
|
|
|
return (
|
|
<div className="space-y-4">
|
|
<SettingsPageHeader
|
|
title="ATX Power Control"
|
|
description="Control your ATX power settings"
|
|
/>
|
|
|
|
{atxState === null ? (
|
|
<Card className="flex h-[120px] items-center justify-center p-3">
|
|
<LoadingSpinner className="h-6 w-6 text-blue-500 dark:text-blue-400" />
|
|
</Card>
|
|
) : (
|
|
<Card className="h-[120px] animate-fadeIn">
|
|
<div className="space-y-4 p-3">
|
|
{/* Control Buttons */}
|
|
<div className="flex items-center space-x-2">
|
|
<Button
|
|
size="SM"
|
|
theme="light"
|
|
LeadingIcon={LuPower}
|
|
text="Power"
|
|
onMouseDown={() => handlePowerPress(true)}
|
|
onMouseUp={() => handlePowerPress(false)}
|
|
onMouseLeave={() => handlePowerPress(false)}
|
|
className={isPowerPressed ? "opacity-75" : ""}
|
|
/>
|
|
<Button
|
|
size="SM"
|
|
theme="light"
|
|
LeadingIcon={LuRotateCcw}
|
|
text="Reset"
|
|
onClick={() => {
|
|
send("setATXPowerAction", { action: "reset" }, resp => {
|
|
if ("error" in resp) {
|
|
notifications.error(
|
|
`Failed to send ATX power action: ${resp.error.data || "Unknown error"}`,
|
|
);
|
|
return;
|
|
}
|
|
});
|
|
}}
|
|
/>
|
|
</div>
|
|
|
|
<hr className="border-slate-700/30 dark:border-slate-600/30" />
|
|
{/* Status Indicators */}
|
|
<div className="flex items-center space-x-4">
|
|
<div className="flex items-center space-x-2">
|
|
<span className="text-sm text-slate-600 dark:text-slate-400">
|
|
<LuPower
|
|
strokeWidth={3}
|
|
className={`mr-1 inline ${
|
|
atxState?.power ? "text-green-600" : "text-slate-300"
|
|
}`}
|
|
/>
|
|
Power LED
|
|
</span>
|
|
</div>
|
|
<div className="flex items-center space-x-2">
|
|
<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"
|
|
}`}
|
|
/>
|
|
HDD LED
|
|
</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</Card>
|
|
)}
|
|
</div>
|
|
);
|
|
}
|