mirror of https://github.com/jetkvm/kvm.git
Update npm packages for the UI
Upgraded most packages to current as of 2025-05-09 for almost everything. Remove the erroneous extra dependency to old xterm package since the correct @xterm/xterm package was already included (suspect a bad merge) and it was causing issues with react 19.1. Switched to using the hooks exposed in the usehooks-ts package (this package was already referenced, suspect a bad merge) removing our private copies of useInterval, useIsMounted, useResizeObserver which are identical. Added import of JSX from react now needed because NPX is not in global scope in react 19.x. Explicitly cast the ref of included elements due to change in react 19.x
This commit is contained in:
parent
d79f359c43
commit
cedad5f516
File diff suppressed because it is too large
Load Diff
|
@ -19,8 +19,8 @@
|
|||
"preview": "vite preview"
|
||||
},
|
||||
"dependencies": {
|
||||
"@headlessui/react": "^2.2.0",
|
||||
"@headlessui/tailwindcss": "^0.2.1",
|
||||
"@headlessui/react": "^2.2.2",
|
||||
"@headlessui/tailwindcss": "^0.2.2",
|
||||
"@heroicons/react": "^2.2.0",
|
||||
"@vitejs/plugin-basic-ssl": "^1.2.0",
|
||||
"@xterm/addon-clipboard": "^0.1.0",
|
||||
|
@ -36,44 +36,43 @@
|
|||
"framer-motion": "^11.15.0",
|
||||
"lodash.throttle": "^4.1.1",
|
||||
"mini-svg-data-uri": "^1.4.4",
|
||||
"react": "^18.2.0",
|
||||
"react": "^19.1.0",
|
||||
"react-animate-height": "^3.2.3",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-hot-toast": "^2.4.1",
|
||||
"react-icons": "^5.4.0",
|
||||
"react-dom": "^19.1.0",
|
||||
"react-hot-toast": "^2.5.2",
|
||||
"react-icons": "^5.5.0",
|
||||
"react-router-dom": "^6.22.3",
|
||||
"react-simple-keyboard": "^3.7.112",
|
||||
"react-simple-keyboard": "^3.8.71",
|
||||
"react-use-websocket": "^4.13.0",
|
||||
"react-xtermjs": "^1.0.9",
|
||||
"recharts": "^2.15.0",
|
||||
"react-xtermjs": "^1.0.10",
|
||||
"recharts": "^2.15.3",
|
||||
"tailwind-merge": "^2.5.5",
|
||||
"usehooks-ts": "^3.1.0",
|
||||
"validator": "^13.12.0",
|
||||
"xterm": "^5.3.0",
|
||||
"usehooks-ts": "^3.1.1",
|
||||
"validator": "^13.15.0",
|
||||
"zustand": "^4.5.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@tailwindcss/forms": "^0.5.9",
|
||||
"@tailwindcss/typography": "^0.5.15",
|
||||
"@types/react": "^18.2.66",
|
||||
"@types/react-dom": "^18.3.0",
|
||||
"@types/semver": "^7.5.8",
|
||||
"@types/validator": "^13.12.2",
|
||||
"@typescript-eslint/eslint-plugin": "^8.25.0",
|
||||
"@typescript-eslint/parser": "^8.25.0",
|
||||
"@vitejs/plugin-react-swc": "^3.7.2",
|
||||
"autoprefixer": "^10.4.20",
|
||||
"eslint": "^8.20.0",
|
||||
"eslint-config-prettier": "^10.0.1",
|
||||
"@tailwindcss/forms": "^0.5.10",
|
||||
"@tailwindcss/typography": "^0.5.16",
|
||||
"@types/react": "^19.1.3",
|
||||
"@types/react-dom": "^19.1.3",
|
||||
"@types/semver": "^7.7.0",
|
||||
"@types/validator": "^13.15.0",
|
||||
"@typescript-eslint/eslint-plugin": "^8.32.0",
|
||||
"@typescript-eslint/parser": "^8.32.0",
|
||||
"@vitejs/plugin-react-swc": "^3.9.0",
|
||||
"autoprefixer": "^10.4.21",
|
||||
"eslint": "^9.26.0",
|
||||
"eslint-config-prettier": "^10.1.5",
|
||||
"eslint-plugin-import": "^2.31.0",
|
||||
"eslint-plugin-react": "^7.37.4",
|
||||
"eslint-plugin-react-hooks": "^5.1.0",
|
||||
"eslint-plugin-react-refresh": "^0.4.19",
|
||||
"postcss": "^8.4.49",
|
||||
"prettier": "^3.4.2",
|
||||
"eslint-plugin-react": "^7.37.5",
|
||||
"eslint-plugin-react-hooks": "^5.2.0",
|
||||
"eslint-plugin-react-refresh": "^0.4.20",
|
||||
"postcss": "^8.5.3",
|
||||
"prettier": "^3.5.3",
|
||||
"prettier-plugin-tailwindcss": "^0.6.11",
|
||||
"tailwindcss": "^3.4.17",
|
||||
"typescript": "^5.7.2",
|
||||
"typescript": "^5.8.3",
|
||||
"vite": "^5.2.0",
|
||||
"vite-tsconfig-paths": "^5.1.4"
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import React from "react";
|
||||
import React, { JSX } from "react";
|
||||
import { FetcherWithComponents, Link, LinkProps, useNavigation } from "react-router-dom";
|
||||
|
||||
import ExtLink from "@/components/ExtLink";
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import type { Ref } from "react";
|
||||
import React, { forwardRef } from "react";
|
||||
import React, { forwardRef, JSX } from "react";
|
||||
import clsx from "clsx";
|
||||
|
||||
import FieldLabel from "@/components/FieldLabel";
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import type { Ref } from "react";
|
||||
import React, { forwardRef } from "react";
|
||||
import React, { forwardRef, JSX } from "react";
|
||||
import clsx from "clsx";
|
||||
|
||||
import FieldLabel from "@/components/FieldLabel";
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import React from "react";
|
||||
import React, { JSX } from "react";
|
||||
import clsx from "clsx";
|
||||
|
||||
import FieldLabel from "@/components/FieldLabel";
|
||||
|
|
|
@ -79,10 +79,11 @@ function Terminal({
|
|||
return () => {
|
||||
setDisableKeyboardFocusTrap(false);
|
||||
};
|
||||
}, [enableTerminal, instance, ref, setDisableKeyboardFocusTrap, type]);
|
||||
}, [ref, instance, enableTerminal, setDisableKeyboardFocusTrap, type]);
|
||||
|
||||
const readyState = dataChannel.readyState;
|
||||
useEffect(() => {
|
||||
if (!instance) return;
|
||||
if (readyState !== "open") return;
|
||||
|
||||
const abortController = new AbortController();
|
||||
|
@ -93,11 +94,10 @@ function Terminal({
|
|||
// Handle binary data differently based on browser implementation
|
||||
// Firefox sends data as blobs, chrome sends data as arraybuffer
|
||||
if (binaryType === "arraybuffer") {
|
||||
instance?.write(new Uint8Array(e.data));
|
||||
instance.write(new Uint8Array(e.data));
|
||||
} else if (binaryType === "blob") {
|
||||
const reader = new FileReader();
|
||||
reader.onload = () => {
|
||||
if (!instance) return;
|
||||
if (!reader.result) return;
|
||||
instance.write(new Uint8Array(reader.result as ArrayBuffer));
|
||||
};
|
||||
|
@ -107,12 +107,12 @@ function Terminal({
|
|||
{ signal: abortController.signal },
|
||||
);
|
||||
|
||||
const onDataHandler = instance?.onData(data => {
|
||||
const onDataHandler = instance.onData(data => {
|
||||
dataChannel.send(data);
|
||||
});
|
||||
|
||||
// Setup escape key handler
|
||||
const onKeyHandler = instance?.onKey(e => {
|
||||
const onKeyHandler = instance.onKey(e => {
|
||||
const { domEvent } = e;
|
||||
if (domEvent.key === "Escape") {
|
||||
setTerminalType("none");
|
||||
|
@ -123,32 +123,32 @@ function Terminal({
|
|||
|
||||
// Send initial terminal size
|
||||
if (dataChannel.readyState === "open") {
|
||||
dataChannel.send(JSON.stringify({ rows: instance?.rows, cols: instance?.cols }));
|
||||
dataChannel.send(JSON.stringify({ rows: instance.rows, cols: instance.cols }));
|
||||
}
|
||||
|
||||
return () => {
|
||||
abortController.abort();
|
||||
onDataHandler?.dispose();
|
||||
onKeyHandler?.dispose();
|
||||
onDataHandler.dispose();
|
||||
onKeyHandler.dispose();
|
||||
};
|
||||
}, [dataChannel, instance, readyState, setDisableKeyboardFocusTrap, setTerminalType]);
|
||||
}, [instance, dataChannel, readyState, setDisableKeyboardFocusTrap, setTerminalType]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!instance) return;
|
||||
|
||||
// Load the fit addon
|
||||
const fitAddon = new FitAddon();
|
||||
instance?.loadAddon(fitAddon);
|
||||
instance.loadAddon(fitAddon);
|
||||
|
||||
instance?.loadAddon(new ClipboardAddon());
|
||||
instance?.loadAddon(new Unicode11Addon());
|
||||
instance?.loadAddon(new WebLinksAddon());
|
||||
instance.loadAddon(new ClipboardAddon());
|
||||
instance.loadAddon(new Unicode11Addon());
|
||||
instance.loadAddon(new WebLinksAddon());
|
||||
instance.unicode.activeVersion = "11";
|
||||
|
||||
if (isWebGl2Supported) {
|
||||
const webGl2Addon = new WebglAddon();
|
||||
webGl2Addon.onContextLoss(() => webGl2Addon.dispose());
|
||||
instance?.loadAddon(webGl2Addon);
|
||||
instance.loadAddon(webGl2Addon);
|
||||
}
|
||||
|
||||
const handleResize = () => fitAddon.fit();
|
||||
|
@ -158,13 +158,11 @@ function Terminal({
|
|||
return () => {
|
||||
window.removeEventListener("resize", handleResize);
|
||||
};
|
||||
}, [ref, instance, dataChannel]);
|
||||
}, [ref, instance]);
|
||||
|
||||
return (
|
||||
<div
|
||||
onKeyDown={e => {
|
||||
e.stopPropagation();
|
||||
}}
|
||||
onKeyDown={e => e.stopPropagation()}
|
||||
onKeyUp={e => e.stopPropagation()}
|
||||
>
|
||||
<div>
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import React from "react";
|
||||
import React, { JSX } from "react";
|
||||
import clsx from "clsx";
|
||||
|
||||
import FieldLabel from "@/components/FieldLabel";
|
||||
|
|
|
@ -10,7 +10,7 @@ import {
|
|||
useVideoStore,
|
||||
} from "@/hooks/stores";
|
||||
import { keys, modifiers } from "@/keyboardMappings";
|
||||
import { useResizeObserver } from "@/hooks/useResizeObserver";
|
||||
import { useResizeObserver } from "usehooks-ts";
|
||||
import { cx } from "@/cva.config";
|
||||
import VirtualKeyboard from "@components/VirtualKeyboard";
|
||||
import Actionbar from "@components/ActionBar";
|
||||
|
@ -67,7 +67,7 @@ export default function WebRTCVideo() {
|
|||
|
||||
// Video-related
|
||||
useResizeObserver({
|
||||
ref: videoElm,
|
||||
ref: videoElm as React.RefObject<HTMLElement>,
|
||||
onResize: ({ width, height }) => {
|
||||
// This is actually client size, not videoSize
|
||||
if (width && height) {
|
||||
|
|
|
@ -1,21 +0,0 @@
|
|||
import { useEffect, useRef } from "react";
|
||||
|
||||
export default function useInterval(callback: () => void, delay: number) {
|
||||
const savedCallback = useRef<typeof callback>();
|
||||
|
||||
// Save the callback directly in the useRef object
|
||||
savedCallback.current = callback;
|
||||
|
||||
// Set up the interval.
|
||||
useEffect(() => {
|
||||
function tick() {
|
||||
if (!savedCallback.current) return;
|
||||
savedCallback.current();
|
||||
}
|
||||
|
||||
if (delay !== null) {
|
||||
const id = setInterval(tick, delay);
|
||||
return () => clearInterval(id);
|
||||
}
|
||||
}, [delay]);
|
||||
}
|
|
@ -1,26 +0,0 @@
|
|||
import { useCallback, useEffect, useRef } from "react";
|
||||
|
||||
/**
|
||||
* Custom hook that determines if the component is currently mounted.
|
||||
* @returns {() => boolean} A function that returns a boolean value indicating whether the component is mounted.
|
||||
* @public
|
||||
* @see [Documentation](https://usehooks-ts.com/react-hook/use-is-mounted)
|
||||
* @example
|
||||
* ```tsx
|
||||
* const isComponentMounted = useIsMounted();
|
||||
* // Use isComponentMounted() to check if the component is currently mounted before performing certain actions.
|
||||
* ```
|
||||
*/
|
||||
export function useIsMounted(): () => boolean {
|
||||
const isMounted = useRef(false);
|
||||
|
||||
useEffect(() => {
|
||||
isMounted.current = true;
|
||||
|
||||
return () => {
|
||||
isMounted.current = false;
|
||||
};
|
||||
}, []);
|
||||
|
||||
return useCallback(() => isMounted.current, []);
|
||||
}
|
|
@ -1,131 +0,0 @@
|
|||
import { useEffect, useRef, useState } from "react";
|
||||
import type { RefObject } from "react";
|
||||
|
||||
import { useIsMounted } from "./useIsMounted";
|
||||
|
||||
/** The size of the observed element. */
|
||||
interface Size {
|
||||
/** The width of the observed element. */
|
||||
width: number | undefined;
|
||||
/** The height of the observed element. */
|
||||
height: number | undefined;
|
||||
}
|
||||
|
||||
/** The options for the ResizeObserver. */
|
||||
interface UseResizeObserverOptions<T extends HTMLElement = HTMLElement> {
|
||||
/** The ref of the element to observe. */
|
||||
ref: RefObject<T>;
|
||||
/**
|
||||
* When using `onResize`, the hook doesn't re-render on element size changes; it delegates handling to the provided callback.
|
||||
* @default undefined
|
||||
*/
|
||||
onResize?: (size: Size) => void;
|
||||
/**
|
||||
* The box model to use for the ResizeObserver.
|
||||
* @default 'content-box'
|
||||
*/
|
||||
box?: "border-box" | "content-box" | "device-pixel-content-box";
|
||||
}
|
||||
|
||||
const initialSize: Size = {
|
||||
width: undefined,
|
||||
height: undefined,
|
||||
};
|
||||
|
||||
/**
|
||||
* Custom hook that observes the size of an element using the ResizeObserver API.
|
||||
* @template T - The type of the element to observe.
|
||||
* @param {UseResizeObserverOptions<T>} options - The options for the ResizeObserver.
|
||||
* @returns {Size} - The size of the observed element.
|
||||
* @public
|
||||
* @see [Documentation](https://usehooks-ts.com/react-hook/use-resize-observer)
|
||||
* @see [MDN ResizeObserver API](https://developer.mozilla.org/en-US/docs/Web/API/ResizeObserver)
|
||||
* @example
|
||||
* ```tsx
|
||||
* const myRef = useRef(null);
|
||||
* const { width = 0, height = 0 } = useResizeObserver({
|
||||
* ref: myRef,
|
||||
* box: 'content-box',
|
||||
* });
|
||||
*
|
||||
* <div ref={myRef}>Hello, world!</div>
|
||||
* ```
|
||||
*/
|
||||
export function useResizeObserver<T extends HTMLElement = HTMLElement>(
|
||||
options: UseResizeObserverOptions<T>,
|
||||
): Size {
|
||||
const { ref, box = "content-box" } = options;
|
||||
const [{ width, height }, setSize] = useState<Size>(initialSize);
|
||||
const isMounted = useIsMounted();
|
||||
const previousSize = useRef<Size>({ ...initialSize });
|
||||
const onResize = useRef<((size: Size) => void) | undefined>(undefined);
|
||||
onResize.current = options.onResize;
|
||||
|
||||
useEffect(() => {
|
||||
if (!ref.current) return;
|
||||
|
||||
if (typeof window === "undefined" || !("ResizeObserver" in window)) return;
|
||||
|
||||
const observer = new ResizeObserver(([entry]) => {
|
||||
const boxProp =
|
||||
box === "border-box"
|
||||
? "borderBoxSize"
|
||||
: box === "device-pixel-content-box"
|
||||
? "devicePixelContentBoxSize"
|
||||
: "contentBoxSize";
|
||||
|
||||
const newWidth = extractSize(entry, boxProp, "inlineSize");
|
||||
const newHeight = extractSize(entry, boxProp, "blockSize");
|
||||
|
||||
const hasChanged =
|
||||
previousSize.current.width !== newWidth ||
|
||||
previousSize.current.height !== newHeight;
|
||||
|
||||
if (hasChanged) {
|
||||
const newSize: Size = { width: newWidth, height: newHeight };
|
||||
previousSize.current.width = newWidth;
|
||||
previousSize.current.height = newHeight;
|
||||
|
||||
if (onResize.current) {
|
||||
onResize.current(newSize);
|
||||
} else {
|
||||
if (isMounted()) {
|
||||
setSize(newSize);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
observer.observe(ref.current, { box });
|
||||
|
||||
return () => {
|
||||
observer.disconnect();
|
||||
};
|
||||
}, [box, isMounted, ref]);
|
||||
|
||||
return { width, height };
|
||||
}
|
||||
|
||||
/** @private */
|
||||
type BoxSizesKey = keyof Pick<
|
||||
ResizeObserverEntry,
|
||||
"borderBoxSize" | "contentBoxSize" | "devicePixelContentBoxSize"
|
||||
>;
|
||||
|
||||
function extractSize(
|
||||
entry: ResizeObserverEntry,
|
||||
box: BoxSizesKey,
|
||||
sizeType: keyof ResizeObserverSize,
|
||||
): number | undefined {
|
||||
if (!entry[box]) {
|
||||
if (box === "contentBoxSize") {
|
||||
return entry.contentRect[sizeType === "inlineSize" ? "width" : "height"];
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return Array.isArray(entry[box])
|
||||
? entry[box][0][sizeType]
|
||||
: // @ts-expect-error Support Firefox's non-standard behavior
|
||||
(entry[box][sizeType] as number);
|
||||
}
|
|
@ -19,7 +19,7 @@ import { LinkButton } from "../components/Button";
|
|||
import { cx } from "../cva.config";
|
||||
import { useUiStore } from "../hooks/stores";
|
||||
import useKeyboard from "../hooks/useKeyboard";
|
||||
import { useResizeObserver } from "../hooks/useResizeObserver";
|
||||
import { useResizeObserver } from "usehooks-ts";
|
||||
import LoadingSpinner from "../components/LoadingSpinner";
|
||||
|
||||
/* TODO: Migrate to using URLs instead of the global state. To simplify the refactoring, we'll keep the global state for now. */
|
||||
|
@ -30,7 +30,7 @@ export default function SettingsRoute() {
|
|||
const scrollContainerRef = useRef<HTMLDivElement>(null);
|
||||
const [showLeftGradient, setShowLeftGradient] = useState(false);
|
||||
const [showRightGradient, setShowRightGradient] = useState(false);
|
||||
const { width } = useResizeObserver({ ref: scrollContainerRef });
|
||||
const { width = 0 } = useResizeObserver({ ref: scrollContainerRef as React.RefObject<HTMLDivElement> });
|
||||
|
||||
// Handle scroll position to show/hide gradients
|
||||
const handleScroll = () => {
|
||||
|
|
|
@ -5,7 +5,7 @@ import { ArrowRightIcon } from "@heroicons/react/16/solid";
|
|||
import DashboardNavbar from "@components/Header";
|
||||
import { LinkButton } from "@components/Button";
|
||||
import KvmCard from "@components/KvmCard";
|
||||
import useInterval from "@/hooks/useInterval";
|
||||
import { useInterval } from "usehooks-ts";
|
||||
import { checkAuth } from "@/main";
|
||||
import { User } from "@/hooks/stores";
|
||||
import EmptyCard from "@components/EmptyCard";
|
||||
|
|
Loading…
Reference in New Issue