mirror of https://github.com/jetkvm/kvm.git
chore: Upgrade UI vite and tailwind packages (#443)
* 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
This commit is contained in:
parent
340babac24
commit
7ccb8e617c
|
@ -1,66 +0,0 @@
|
|||
module.exports = {
|
||||
root: true,
|
||||
env: { browser: true, es2020: true },
|
||||
extends: [
|
||||
"eslint:recommended",
|
||||
"plugin:@typescript-eslint/recommended",
|
||||
"plugin:@typescript-eslint/stylistic",
|
||||
"plugin:react-hooks/recommended",
|
||||
"plugin:react/recommended",
|
||||
"plugin:react/jsx-runtime",
|
||||
"plugin:import/recommended",
|
||||
"prettier",
|
||||
],
|
||||
ignorePatterns: ["dist", ".eslintrc.cjs", "tailwind.config.js", "postcss.config.js"],
|
||||
parser: "@typescript-eslint/parser",
|
||||
plugins: ["react-refresh"],
|
||||
parserOptions: {
|
||||
ecmaVersion: "latest",
|
||||
sourceType: "module",
|
||||
project: ["./tsconfig.json", "./tsconfig.node.json"],
|
||||
tsconfigRootDir: __dirname,
|
||||
},
|
||||
rules: {
|
||||
"react-refresh/only-export-components": ["warn", { allowConstantExport: true }],
|
||||
"import/order": [
|
||||
"error",
|
||||
{
|
||||
/**
|
||||
* @description
|
||||
*
|
||||
* This keeps imports separate from one another, ensuring that imports are separated
|
||||
* by their relative groups. As you move through the groups, imports become closer
|
||||
* to the current file.
|
||||
*
|
||||
* @example
|
||||
* ```
|
||||
* import fs from 'fs';
|
||||
*
|
||||
* import package from 'npm-package';
|
||||
*
|
||||
* import xyz from '~/project-file';
|
||||
*
|
||||
* import index from '../';
|
||||
*
|
||||
* import sibling from './foo';
|
||||
* ```
|
||||
*/
|
||||
groups: ["builtin", "external", "internal", "parent", "sibling"],
|
||||
"newlines-between": "always",
|
||||
},
|
||||
],
|
||||
},
|
||||
settings: {
|
||||
"import/resolver": {
|
||||
alias: {
|
||||
map: [
|
||||
["@components", "./src/components"],
|
||||
["@routes", "./src/routes"],
|
||||
["@assets", "./src/assets"],
|
||||
["@", "./src"],
|
||||
],
|
||||
extensions: [".ts", ".tsx", ".js", ".jsx", ".json"],
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
|
@ -0,0 +1,93 @@
|
|||
const {
|
||||
defineConfig,
|
||||
globalIgnores,
|
||||
} = require("eslint/config");
|
||||
|
||||
const globals = require("globals");
|
||||
|
||||
const {
|
||||
fixupConfigRules,
|
||||
} = require("@eslint/compat");
|
||||
|
||||
const tsParser = require("@typescript-eslint/parser");
|
||||
const reactRefresh = require("eslint-plugin-react-refresh");
|
||||
const js = require("@eslint/js");
|
||||
|
||||
const {
|
||||
FlatCompat,
|
||||
} = require("@eslint/eslintrc");
|
||||
|
||||
const compat = new FlatCompat({
|
||||
baseDirectory: __dirname,
|
||||
recommendedConfig: js.configs.recommended,
|
||||
allConfig: js.configs.all
|
||||
});
|
||||
|
||||
module.exports = defineConfig([{
|
||||
languageOptions: {
|
||||
globals: {
|
||||
...globals.browser,
|
||||
},
|
||||
|
||||
parser: tsParser,
|
||||
ecmaVersion: "latest",
|
||||
sourceType: "module",
|
||||
|
||||
parserOptions: {
|
||||
project: ["./tsconfig.json", "./tsconfig.node.json"],
|
||||
tsconfigRootDir: __dirname,
|
||||
ecmaFeatures: {
|
||||
jsx: true
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
extends: fixupConfigRules(compat.extends(
|
||||
"eslint:recommended",
|
||||
"plugin:@typescript-eslint/recommended",
|
||||
"plugin:@typescript-eslint/stylistic",
|
||||
"plugin:react-hooks/recommended",
|
||||
"plugin:react/recommended",
|
||||
"plugin:react/jsx-runtime",
|
||||
"plugin:import/recommended",
|
||||
"prettier",
|
||||
)),
|
||||
|
||||
plugins: {
|
||||
"react-refresh": reactRefresh,
|
||||
},
|
||||
|
||||
rules: {
|
||||
"react-refresh/only-export-components": ["warn", {
|
||||
allowConstantExport: true,
|
||||
}],
|
||||
|
||||
"import/order": ["error", {
|
||||
groups: ["builtin", "external", "internal", "parent", "sibling"],
|
||||
"newlines-between": "always",
|
||||
}],
|
||||
},
|
||||
|
||||
settings: {
|
||||
"react": {
|
||||
"version": "detect"
|
||||
},
|
||||
"import/resolver": {
|
||||
alias: {
|
||||
map: [
|
||||
["@components", "./src/components"],
|
||||
["@routes", "./src/routes"],
|
||||
["@assets", "./src/assets"],
|
||||
["@", "./src"],
|
||||
],
|
||||
|
||||
extensions: [".ts", ".tsx", ".js", ".jsx", ".json"],
|
||||
},
|
||||
},
|
||||
},
|
||||
}, globalIgnores([
|
||||
"**/dist",
|
||||
"**/.eslintrc.cjs",
|
||||
"**/tailwind.config.js",
|
||||
"**/postcss.config.js",
|
||||
])]);
|
File diff suppressed because it is too large
Load Diff
|
@ -4,7 +4,7 @@
|
|||
"version": "0.0.0",
|
||||
"type": "module",
|
||||
"engines": {
|
||||
"node": "21.1.0"
|
||||
"node": "22.15.0"
|
||||
},
|
||||
"scripts": {
|
||||
"dev": "./dev_device.sh",
|
||||
|
@ -19,21 +19,21 @@
|
|||
"preview": "vite preview"
|
||||
},
|
||||
"dependencies": {
|
||||
"@headlessui/react": "^2.2.2",
|
||||
"@headlessui/react": "^2.2.3",
|
||||
"@headlessui/tailwindcss": "^0.2.2",
|
||||
"@heroicons/react": "^2.2.0",
|
||||
"@vitejs/plugin-basic-ssl": "^1.2.0",
|
||||
"@vitejs/plugin-basic-ssl": "^2.0.0",
|
||||
"@xterm/addon-clipboard": "^0.1.0",
|
||||
"@xterm/addon-fit": "^0.10.0",
|
||||
"@xterm/addon-unicode11": "^0.8.0",
|
||||
"@xterm/addon-web-links": "^0.11.0",
|
||||
"@xterm/addon-webgl": "^0.18.0",
|
||||
"@xterm/xterm": "^5.5.0",
|
||||
"cva": "^1.0.0-beta.1",
|
||||
"cva": "^1.0.0-beta.3",
|
||||
"dayjs": "^1.11.13",
|
||||
"eslint-import-resolver-alias": "^1.1.2",
|
||||
"focus-trap-react": "^10.2.3",
|
||||
"framer-motion": "^11.15.0",
|
||||
"focus-trap-react": "^11.0.3",
|
||||
"framer-motion": "^12.11.0",
|
||||
"lodash.throttle": "^4.1.1",
|
||||
"mini-svg-data-uri": "^1.4.4",
|
||||
"react": "^19.1.0",
|
||||
|
@ -42,24 +42,29 @@
|
|||
"react-hot-toast": "^2.5.2",
|
||||
"react-icons": "^5.5.0",
|
||||
"react-router-dom": "^6.22.3",
|
||||
"react-simple-keyboard": "^3.8.71",
|
||||
"react-simple-keyboard": "^3.8.72",
|
||||
"react-use-websocket": "^4.13.0",
|
||||
"react-xtermjs": "^1.0.10",
|
||||
"recharts": "^2.15.3",
|
||||
"tailwind-merge": "^2.5.5",
|
||||
"tailwind-merge": "^3.3.0",
|
||||
"usehooks-ts": "^3.1.1",
|
||||
"validator": "^13.15.0",
|
||||
"zustand": "^4.5.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/compat": "^1.2.9",
|
||||
"@eslint/eslintrc": "^3.3.1",
|
||||
"@eslint/js": "^9.26.0",
|
||||
"@tailwindcss/forms": "^0.5.10",
|
||||
"@tailwindcss/postcss": "^4.1.6",
|
||||
"@tailwindcss/typography": "^0.5.16",
|
||||
"@types/react": "^19.1.3",
|
||||
"@types/react-dom": "^19.1.3",
|
||||
"@tailwindcss/vite": "^4.1.6",
|
||||
"@types/react": "^19.1.4",
|
||||
"@types/react-dom": "^19.1.5",
|
||||
"@types/semver": "^7.7.0",
|
||||
"@types/validator": "^13.15.0",
|
||||
"@typescript-eslint/eslint-plugin": "^8.32.0",
|
||||
"@typescript-eslint/parser": "^8.32.0",
|
||||
"@typescript-eslint/eslint-plugin": "^8.32.1",
|
||||
"@typescript-eslint/parser": "^8.32.1",
|
||||
"@vitejs/plugin-react-swc": "^3.9.0",
|
||||
"autoprefixer": "^10.4.21",
|
||||
"eslint": "^9.26.0",
|
||||
|
@ -68,12 +73,13 @@
|
|||
"eslint-plugin-react": "^7.37.5",
|
||||
"eslint-plugin-react-hooks": "^5.2.0",
|
||||
"eslint-plugin-react-refresh": "^0.4.20",
|
||||
"globals": "^16.1.0",
|
||||
"postcss": "^8.5.3",
|
||||
"prettier": "^3.5.3",
|
||||
"prettier-plugin-tailwindcss": "^0.6.11",
|
||||
"tailwindcss": "^3.4.17",
|
||||
"tailwindcss": "^4.1.6",
|
||||
"typescript": "^5.8.3",
|
||||
"vite": "^5.2.0",
|
||||
"vite": "^6.3.5",
|
||||
"vite-tsconfig-paths": "^5.1.4"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
export default {
|
||||
plugins: {
|
||||
tailwindcss: {},
|
||||
autoprefixer: {},
|
||||
},
|
||||
}
|
||||
|
|
|
@ -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",
|
||||
"bg-blue-700 dark:border-blue-600 border border-blue-900/60 text-white shadow-sm",
|
||||
// Hover states
|
||||
"group-hover:bg-blue-800",
|
||||
// Active states
|
||||
|
@ -24,7 +24,7 @@ const themes = {
|
|||
),
|
||||
danger: cx(
|
||||
// Base styles
|
||||
"bg-red-600 text-white border-red-700 shadow-sm shadow-red-200/80 dark:border-red-600 dark:shadow-red-900/20",
|
||||
"bg-red-600 text-white border-red-700 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",
|
||||
// Active states
|
||||
|
@ -34,7 +34,7 @@ const themes = {
|
|||
),
|
||||
light: cx(
|
||||
// Base styles
|
||||
"bg-white text-black border-slate-800/30 shadow dark:bg-slate-800 dark:border-slate-300/20 dark:text-white",
|
||||
"bg-white text-black border-slate-800/30 shadow-xs dark:bg-slate-800 dark:border-slate-300/20 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-sm",
|
||||
"bg-white text-black border-red-400/60 shadow-xs",
|
||||
// Hover states
|
||||
"group-hover:bg-red-50/80",
|
||||
// Active states
|
||||
|
@ -56,7 +56,7 @@ const themes = {
|
|||
// Base styles
|
||||
"bg-white/0 text-black border-transparent dark:text-white",
|
||||
// Hover states
|
||||
"group-hover:bg-white group-hover:border-slate-800/30 group-hover:shadow dark:group-hover:bg-slate-700 dark:group-hover:border-slate-600",
|
||||
"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",
|
||||
// Active states
|
||||
"group-active:bg-slate-100/80",
|
||||
),
|
||||
|
@ -65,15 +65,15 @@ const themes = {
|
|||
const btnVariants = cva({
|
||||
base: cx(
|
||||
// Base styles
|
||||
"border rounded select-none",
|
||||
"border rounded-sm select-none",
|
||||
// Size classes
|
||||
"justify-center items-center shrink-0",
|
||||
// Transition classes
|
||||
"outline-none transition-all duration-200",
|
||||
"outline-hidden transition-all duration-200",
|
||||
// Text classes
|
||||
"font-display text-center font-medium leading-tight",
|
||||
// States
|
||||
"group-focus:outline-none group-focus:ring-2 group-focus:ring-offset-2 group-focus:ring-blue-700",
|
||||
"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",
|
||||
),
|
||||
|
||||
|
@ -175,7 +175,7 @@ type ButtonPropsType = Pick<
|
|||
export const Button = React.forwardRef<HTMLButtonElement, ButtonPropsType>(
|
||||
({ type, disabled, onClick, formNoValidate, loading, fetcher, ...props }, ref) => {
|
||||
const classes = cx(
|
||||
"group outline-none",
|
||||
"group outline-hidden",
|
||||
props.fullWidth ? "w-full" : "",
|
||||
loading ? "pointer-events-none" : "",
|
||||
);
|
||||
|
@ -215,7 +215,7 @@ type LinkPropsType = Pick<LinkProps, "to"> &
|
|||
React.ComponentProps<typeof ButtonContent> & { disabled?: boolean };
|
||||
export const LinkButton = ({ to, ...props }: LinkPropsType) => {
|
||||
const classes = cx(
|
||||
"group outline-none",
|
||||
"group outline-hidden",
|
||||
props.disabled ? "pointer-events-none !opacity-70" : "",
|
||||
props.fullWidth ? "w-full" : "",
|
||||
props.loading ? "pointer-events-none" : "",
|
||||
|
@ -241,7 +241,7 @@ type LabelPropsType = Pick<HTMLLabelElement, "htmlFor"> &
|
|||
React.ComponentProps<typeof ButtonContent> & { disabled?: boolean };
|
||||
export const LabelButton = ({ htmlFor, ...props }: LabelPropsType) => {
|
||||
const classes = cx(
|
||||
"group outline-none block cursor-pointer",
|
||||
"group outline-hidden block cursor-pointer",
|
||||
props.disabled ? "pointer-events-none !opacity-70" : "",
|
||||
props.fullWidth ? "w-full" : "",
|
||||
props.loading ? "pointer-events-none" : "",
|
||||
|
|
|
@ -30,7 +30,7 @@ const Card = forwardRef<HTMLDivElement, CardPropsType>(({ children, className },
|
|||
<div
|
||||
ref={ref}
|
||||
className={cx(
|
||||
"w-full rounded border-none bg-white shadow outline outline-1 outline-slate-800/30 dark:bg-slate-800 dark:outline-slate-300/20",
|
||||
"w-full rounded-sm border-none bg-white shadow-xs outline-1 outline-slate-800/30 dark:bg-slate-800 dark:outline-slate-300/20",
|
||||
className,
|
||||
)}
|
||||
>
|
||||
|
|
|
@ -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-none 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 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",
|
||||
|
||||
// Disabled
|
||||
"disabled:pointer-events-none disabled:opacity-30",
|
||||
|
|
|
@ -58,7 +58,7 @@ export function Combobox({
|
|||
<HeadlessCombobox onChange={onChange} {...otherProps}>
|
||||
{() => (
|
||||
<>
|
||||
<Card className="w-auto !border border-solid !border-slate-800/30 shadow outline-0 dark:!border-slate-300/30">
|
||||
<Card className="w-auto !border border-solid !border-slate-800/30 shadow-xs outline-0 dark:!border-slate-300/30">
|
||||
<ComboboxInput
|
||||
ref={inputRef}
|
||||
className={clsx(
|
||||
|
|
|
@ -2,7 +2,7 @@ export default function GridBackground() {
|
|||
return (
|
||||
<div className="absolute w-screen h-screen overflow-hidden isolate opacity-60">
|
||||
<svg
|
||||
className="absolute inset-x-0 top-0 -z-10 h-[64rem] w-full stroke-gray-300 [mask-image:radial-gradient(32rem_32rem_at_center,white,transparent)] dark:stroke-slate-300/20"
|
||||
className="absolute inset-x-0 top-0 -z-10 h-[64rem] w-full stroke-gray-300 [mask-image:radial-gradient(center_at_32rem_32rem,white,transparent)] dark:stroke-slate-300/20"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<defs>
|
||||
|
|
|
@ -1,12 +1,11 @@
|
|||
import { Fragment, useCallback } from "react";
|
||||
import { useCallback } from "react";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { ArrowLeftEndOnRectangleIcon, ChevronDownIcon } from "@heroicons/react/16/solid";
|
||||
import { Menu, MenuButton } from "@headlessui/react";
|
||||
import { Button, Menu, MenuButton, MenuItem, MenuItems } from "@headlessui/react";
|
||||
import { LuMonitorSmartphone } from "react-icons/lu";
|
||||
|
||||
import Container from "@/components/Container";
|
||||
import Card from "@/components/Card";
|
||||
import { cx } from "@/cva.config";
|
||||
import { useHidStore, useRTCStore, useUserStore } from "@/hooks/stores";
|
||||
import LogoBlueIcon from "@/assets/logo-blue.svg";
|
||||
import LogoWhiteIcon from "@/assets/logo-white.svg";
|
||||
|
@ -17,7 +16,7 @@ import { CLOUD_API, DEVICE_API } from "@/ui.config";
|
|||
import api from "../api";
|
||||
import { isOnDevice } from "../main";
|
||||
|
||||
import { Button, LinkButton } from "./Button";
|
||||
import { LinkButton } from "./Button";
|
||||
|
||||
interface NavbarProps {
|
||||
isLoggedIn: boolean;
|
||||
|
@ -51,6 +50,10 @@ export default function DashboardNavbar({
|
|||
|
||||
const usbState = useHidStore(state => state.usbState);
|
||||
|
||||
// for testing
|
||||
//userEmail = "user@example.org";
|
||||
//picture = "https://placehold.co/32x32"
|
||||
|
||||
return (
|
||||
<div className="w-full select-none border-b border-b-slate-800/20 bg-white dark:border-b-slate-300/20 dark:bg-slate-900">
|
||||
<Container>
|
||||
|
@ -78,8 +81,9 @@ export default function DashboardNavbar({
|
|||
</div>
|
||||
<div className="flex w-full items-center justify-end gap-x-2">
|
||||
<div className="flex shrink-0 items-center space-x-4">
|
||||
{showConnectionStatus && (
|
||||
<div className="hidden items-center gap-x-2 md:flex">
|
||||
{showConnectionStatus && (
|
||||
<>
|
||||
<div className="w-[159px]">
|
||||
<PeerConnectionStatusCard
|
||||
state={peerConnectionState}
|
||||
|
@ -92,75 +96,67 @@ export default function DashboardNavbar({
|
|||
peerConnectionState={peerConnectionState}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
{isLoggedIn ? (
|
||||
<>
|
||||
<hr className="h-[20px] w-[1px] border-none bg-slate-800/20 dark:bg-slate-300/20" />
|
||||
<Menu as="div" className="relative inline-block text-left">
|
||||
<div>
|
||||
<MenuButton as={Fragment}>
|
||||
<Button
|
||||
theme="blank"
|
||||
size="SM"
|
||||
text={
|
||||
<>
|
||||
{picture ? <></> : userEmail}
|
||||
<ChevronDownIcon className="h-4 w-4 shrink-0 text-slate-900 dark:text-white" />
|
||||
</>
|
||||
}
|
||||
LeadingIcon={({ className }) =>
|
||||
picture && (
|
||||
<div className="relative inline-block text-left">
|
||||
<Menu>
|
||||
<MenuButton>
|
||||
<Button 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">
|
||||
{picture
|
||||
? (
|
||||
<img
|
||||
src={picture}
|
||||
alt="Avatar"
|
||||
className={cx(
|
||||
className,
|
||||
"h-8 w-8 rounded-full border-2 border-transparent transition-colors group-hover:border-blue-700",
|
||||
)}
|
||||
className="size-6 rounded-full border-2 border-transparent transition-colors group-hover:border-blue-700"
|
||||
/>
|
||||
)
|
||||
: (
|
||||
<span className="max-w-[200px] text-sm/6 font-display font-semibold truncate">
|
||||
{userEmail}
|
||||
</span>
|
||||
)
|
||||
}
|
||||
/>
|
||||
<ChevronDownIcon className="size-4 shrink-0 text-slate-900 dark:text-white" />
|
||||
</Button>
|
||||
</MenuButton>
|
||||
</div>
|
||||
|
||||
<Menu.Items className="absolute right-0 z-50 mt-2 w-56 origin-top-right focus:outline-none">
|
||||
<MenuItems
|
||||
transition
|
||||
anchor="bottom end"
|
||||
className="right-0 mt-1 w-56 origin-top-right data-closed:opacity-0 focus:outline-hidden">
|
||||
<MenuItem>
|
||||
<Card className="overflow-hidden">
|
||||
<div className="space-y-1 p-1 dark:text-white">
|
||||
{userEmail && (
|
||||
<div className="border-b border-b-slate-800/20 dark:border-slate-300/20">
|
||||
<Menu.Item>
|
||||
<div className="p-2">
|
||||
<div className="font-display text-xs">Logged in as</div>
|
||||
<div className="w-[200px] truncate font-display text-sm font-semibold">
|
||||
<div className="max-w-[200px] truncate font-display text-sm font-semibold">
|
||||
{userEmail}
|
||||
</div>
|
||||
</div>
|
||||
</Menu.Item>
|
||||
</div>
|
||||
)}
|
||||
<div>
|
||||
<Menu.Item>
|
||||
<div onClick={onLogout}>
|
||||
<button className="block w-full">
|
||||
<div className="flex items-center gap-x-2 rounded-md px-2 py-1.5 text-sm transition-colors hover:bg-slate-100 dark:hover:bg-slate-700">
|
||||
<ArrowLeftEndOnRectangleIcon className="h-4 w-4" />
|
||||
<div className="font-display">Log out</div>
|
||||
</div>
|
||||
<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">Log out</div>
|
||||
</button>
|
||||
</div>
|
||||
</Menu.Item>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
</Menu.Items>
|
||||
</MenuItem>
|
||||
</MenuItems>
|
||||
</Menu>
|
||||
</div>
|
||||
</>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Container>
|
||||
</div>
|
||||
);
|
||||
|
|
|
@ -44,7 +44,7 @@ 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-none focus-within:ring-2 focus-within:ring-blue-700 focus-within:ring-offset-2",
|
||||
"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",
|
||||
|
||||
// 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",
|
||||
|
|
|
@ -113,7 +113,7 @@ export default function KvmCard({
|
|||
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"
|
||||
>
|
||||
<Card className="absolute right-0 z-10 w-56 px-1 mt-2 transition origin-top-right ring-1 ring-black ring-opacity-5 focus:outline-none">
|
||||
<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">
|
||||
<div className="divide-y divide-slate-800/20 dark:divide-slate-300/20">
|
||||
<MenuItem>
|
||||
<div>
|
||||
|
|
|
@ -7,7 +7,7 @@ export default function LoadingSpinner({
|
|||
}) {
|
||||
return (
|
||||
<svg
|
||||
className={clsx(className, "flex-shrink-0 animate-spin p-[2px]")}
|
||||
className={clsx(className, "shrink-0 animate-spin p-[2px]")}
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
|
|
|
@ -63,7 +63,7 @@ export const SelectMenuBasic = React.forwardRef<HTMLSelectElement, SelectMenuPro
|
|||
)}
|
||||
>
|
||||
{label && <FieldLabel label={label} id={id} as="span" />}
|
||||
<Card className="w-auto !border border-solid !border-slate-800/30 shadow outline-0 dark:!border-slate-300/30">
|
||||
<Card className="w-auto !border border-solid !border-slate-800/30 shadow-xs outline-0 dark:!border-slate-300/30">
|
||||
<select
|
||||
ref={ref}
|
||||
name={name}
|
||||
|
@ -72,7 +72,7 @@ export const SelectMenuBasic = React.forwardRef<HTMLSelectElement, SelectMenuPro
|
|||
classes,
|
||||
|
||||
// General styling
|
||||
"block w-full cursor-pointer rounded border-none py-0 font-medium shadow-none outline-0 transition duration-300",
|
||||
"block w-full cursor-pointer rounded-sm border-none py-0 font-medium shadow-none outline-0 transition duration-300",
|
||||
|
||||
// Hover
|
||||
"hover:bg-blue-50/80 active:bg-blue-100/60 disabled:hover:bg-white",
|
||||
|
|
|
@ -44,7 +44,7 @@ export default function StepCounter({ nSteps, currStepIdx, size = "MD" }: Props)
|
|||
return (
|
||||
<div
|
||||
className={cx(
|
||||
"rounded-md border border-blue-800 bg-blue-700 px-2 py-1 font-medium text-white shadow-sm dark:border-blue-300",
|
||||
"rounded-md border border-blue-800 bg-blue-700 px-2 py-1 font-medium text-white shadow-xs dark:border-blue-300",
|
||||
textStyle,
|
||||
)}
|
||||
key={`${i}-${currStepIdx}`}
|
||||
|
|
|
@ -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-none focus-within:ring-1 focus-within:ring-blue-700 dark:focus-within:border-slate-600",
|
||||
"focus-within:border-slate-300 focus-within:outline-hidden focus-within:ring-1 focus-within:ring-blue-700 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 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: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",
|
||||
props.className,
|
||||
)}
|
||||
/>
|
||||
|
|
|
@ -14,7 +14,7 @@ interface OverlayContentProps {
|
|||
}
|
||||
function OverlayContent({ children }: OverlayContentProps) {
|
||||
return (
|
||||
<GridCard cardClassName="h-full pointer-events-auto !outline-none">
|
||||
<GridCard cardClassName="h-full pointer-events-auto !outline-hidden">
|
||||
<div className="flex h-full w-full flex-col items-center justify-center rounded-md border border-slate-800/30 dark:border-slate-300/20">
|
||||
{children}
|
||||
</div>
|
||||
|
@ -377,7 +377,7 @@ export function PointerLockBar({ show }: PointerLockBarProps) {
|
|||
>
|
||||
<div>
|
||||
<Card className="rounded-b-none shadow-none !outline-0">
|
||||
<div className="flex items-center justify-between border border-slate-800/50 px-4 py-2 outline-0 backdrop-blur-sm dark:border-slate-300/20 dark:bg-slate-800">
|
||||
<div className="flex items-center justify-between border border-slate-800/50 px-4 py-2 outline-0 backdrop-blur-xs dark:border-slate-300/20 dark:bg-slate-800">
|
||||
<div className="flex items-center space-x-2">
|
||||
<BsMouseFill className="h-4 w-4 text-blue-700 dark:text-blue-500" />
|
||||
<span className="text-sm text-black dark:text-white">
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
||||
import { useResizeObserver } from "usehooks-ts";
|
||||
|
||||
import {
|
||||
useDeviceSettingsStore,
|
||||
|
@ -10,7 +11,6 @@ import {
|
|||
useVideoStore,
|
||||
} from "@/hooks/stores";
|
||||
import { keys, modifiers } from "@/keyboardMappings";
|
||||
import { useResizeObserver } from "usehooks-ts";
|
||||
import { cx } from "@/cva.config";
|
||||
import VirtualKeyboard from "@components/VirtualKeyboard";
|
||||
import Actionbar from "@components/ActionBar";
|
||||
|
@ -724,7 +724,7 @@ export default function WebRTCVideo() {
|
|||
hdmiError ||
|
||||
peerConnectionState !== "connected",
|
||||
"!opacity-60": showPointerLockBar,
|
||||
"animate-slideUpFade border border-slate-800/30 opacity-0 shadow dark:border-slate-300/20":
|
||||
"animate-slideUpFade border border-slate-800/30 shadow-xs dark:border-slate-300/20":
|
||||
isPlaying,
|
||||
},
|
||||
)}
|
||||
|
@ -732,7 +732,7 @@ export default function WebRTCVideo() {
|
|||
{peerConnection?.connectionState == "connected" && (
|
||||
<div
|
||||
style={{ animationDuration: "500ms" }}
|
||||
className="pointer-events-none absolute inset-0 flex animate-slideUpFade items-center justify-center opacity-0"
|
||||
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} />
|
||||
|
|
|
@ -107,7 +107,7 @@ export function ATXPowerControl() {
|
|||
<LoadingSpinner className="h-6 w-6 text-blue-500 dark:text-blue-400" />
|
||||
</Card>
|
||||
) : (
|
||||
<Card className="h-[120px] animate-fadeIn opacity-0">
|
||||
<Card className="h-[120px] animate-fadeIn">
|
||||
<div className="space-y-4 p-3">
|
||||
{/* Control Buttons */}
|
||||
<div className="flex items-center space-x-2">
|
||||
|
|
|
@ -63,7 +63,7 @@ export function DCPowerControl() {
|
|||
<LoadingSpinner className="h-6 w-6 text-blue-500 dark:text-blue-400" />
|
||||
</Card>
|
||||
) : (
|
||||
<Card className="h-[160px] animate-fadeIn opacity-0">
|
||||
<Card className="h-[160px] animate-fadeIn">
|
||||
<div className="space-y-4 p-3">
|
||||
{/* Power Controls */}
|
||||
<div className="flex items-center space-x-2">
|
||||
|
|
|
@ -58,7 +58,7 @@ export function SerialConsole() {
|
|||
description="Configure your serial console settings"
|
||||
/>
|
||||
|
||||
<Card className="animate-fadeIn opacity-0">
|
||||
<Card className="animate-fadeIn">
|
||||
<div className="space-y-4 p-3">
|
||||
{/* Open Console Button */}
|
||||
<div className="flex items-center">
|
||||
|
|
|
@ -92,7 +92,7 @@ export default function ExtensionPopover() {
|
|||
{renderActiveExtension()}
|
||||
|
||||
<div
|
||||
className="flex animate-fadeIn items-center justify-end space-x-2 opacity-0"
|
||||
className="flex animate-fadeIn items-center justify-end space-x-2"
|
||||
style={{
|
||||
animationDuration: "0.7s",
|
||||
animationDelay: "0.2s",
|
||||
|
@ -113,7 +113,7 @@ export default function ExtensionPopover() {
|
|||
title="Extensions"
|
||||
description="Load and manage your extensions"
|
||||
/>
|
||||
<Card className="animate-fadeIn opacity-0">
|
||||
<Card className="animate-fadeIn">
|
||||
<div className="w-full divide-y divide-slate-700/30 dark:divide-slate-600/30">
|
||||
{AVAILABLE_EXTENSIONS.map(extension => (
|
||||
<div
|
||||
|
|
|
@ -214,7 +214,7 @@ const MountPopopover = forwardRef<HTMLDivElement, object>((_props, ref) => {
|
|||
) : null}
|
||||
|
||||
<div
|
||||
className="animate-fadeIn space-y-2 opacity-0"
|
||||
className="animate-fadeIn space-y-2"
|
||||
style={{
|
||||
animationDuration: "0.7s",
|
||||
animationDelay: "0.1s",
|
||||
|
@ -289,7 +289,7 @@ const MountPopopover = forwardRef<HTMLDivElement, object>((_props, ref) => {
|
|||
|
||||
{!remoteVirtualMediaState && (
|
||||
<div
|
||||
className="flex animate-fadeIn items-center justify-end space-x-2 opacity-0"
|
||||
className="flex animate-fadeIn items-center justify-end space-x-2"
|
||||
style={{
|
||||
animationDuration: "0.7s",
|
||||
animationDelay: "0.2s",
|
||||
|
|
|
@ -83,7 +83,7 @@ export default function PasteModal() {
|
|||
/>
|
||||
|
||||
<div
|
||||
className="animate-fadeIn space-y-2 opacity-0"
|
||||
className="animate-fadeIn space-y-2"
|
||||
style={{
|
||||
animationDuration: "0.7s",
|
||||
animationDelay: "0.1s",
|
||||
|
@ -137,7 +137,7 @@ export default function PasteModal() {
|
|||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="flex animate-fadeIn items-center justify-end gap-x-2 opacity-0"
|
||||
className="flex animate-fadeIn items-center justify-end gap-x-2"
|
||||
style={{
|
||||
animationDuration: "0.7s",
|
||||
animationDelay: "0.2s",
|
||||
|
|
|
@ -26,7 +26,7 @@ export default function AddDeviceForm({
|
|||
return (
|
||||
<div className="space-y-4">
|
||||
<div
|
||||
className="animate-fadeIn space-y-4 opacity-0"
|
||||
className="animate-fadeIn space-y-4"
|
||||
style={{
|
||||
animationDuration: "0.5s",
|
||||
animationFillMode: "forwards",
|
||||
|
@ -73,7 +73,7 @@ export default function AddDeviceForm({
|
|||
/>
|
||||
</div>
|
||||
<div
|
||||
className="flex animate-fadeIn items-center justify-end space-x-2 opacity-0"
|
||||
className="flex animate-fadeIn items-center justify-end space-x-2"
|
||||
style={{
|
||||
animationDuration: "0.7s",
|
||||
animationDelay: "0.2s",
|
||||
|
|
|
@ -28,7 +28,7 @@ export default function DeviceList({
|
|||
}: DeviceListProps) {
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
<Card className="animate-fadeIn opacity-0">
|
||||
<Card className="animate-fadeIn">
|
||||
<div className="w-full divide-y divide-slate-700/30 dark:divide-slate-600/30">
|
||||
{storedDevices.map((device, index) => (
|
||||
<div key={index} className="flex items-center justify-between gap-x-2 p-3">
|
||||
|
@ -63,7 +63,7 @@ export default function DeviceList({
|
|||
</div>
|
||||
</Card>
|
||||
<div
|
||||
className="flex animate-fadeIn items-center justify-end space-x-2 opacity-0"
|
||||
className="flex animate-fadeIn items-center justify-end space-x-2"
|
||||
style={{
|
||||
animationDuration: "0.7s",
|
||||
animationDelay: "0.2s",
|
||||
|
|
|
@ -13,7 +13,7 @@ export default function EmptyStateCard({
|
|||
}) {
|
||||
return (
|
||||
<div className="select-none space-y-4">
|
||||
<Card className="animate-fadeIn opacity-0">
|
||||
<Card className="animate-fadeIn">
|
||||
<div className="flex items-center justify-center py-8 text-center">
|
||||
<div className="space-y-3">
|
||||
<div className="space-y-1">
|
||||
|
@ -35,7 +35,7 @@ export default function EmptyStateCard({
|
|||
</div>
|
||||
</Card>
|
||||
<div
|
||||
className="flex animate-fadeIn items-center justify-end space-x-2 opacity-0"
|
||||
className="flex animate-fadeIn items-center justify-end space-x-2"
|
||||
style={{
|
||||
animationDuration: "0.7s",
|
||||
animationDelay: "0.2s",
|
||||
|
|
|
@ -99,7 +99,7 @@ export default function ConnectionStatsSidebar() {
|
|||
}, 500);
|
||||
|
||||
return (
|
||||
<div className="grid h-full grid-rows-headerBody shadow-sm">
|
||||
<div className="grid h-full grid-rows-headerBody shadow-xs">
|
||||
<SidebarHeader title="Connection Stats" setSidebarView={setSidebarView} />
|
||||
<div className="h-full space-y-4 overflow-y-scroll bg-white px-4 py-2 pb-8 dark:bg-slate-900">
|
||||
<div className="space-y-4">
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
@import "tailwindcss";
|
||||
@config "../tailwind.config.js";
|
||||
|
||||
html {
|
||||
@apply scroll-smooth;
|
||||
|
@ -50,7 +49,7 @@ video::-webkit-media-controls {
|
|||
}
|
||||
|
||||
.hg-theme-default .hg-button {
|
||||
@apply border !border-b border-slate-800/25 !border-b-slate-800/25 !shadow-sm;
|
||||
@apply border !border-b border-slate-800/25 !border-b-slate-800/25 !shadow-xs;
|
||||
}
|
||||
|
||||
.hg-theme-default .hg-button span {
|
||||
|
@ -174,7 +173,7 @@ video::-webkit-media-controls {
|
|||
}
|
||||
|
||||
.hg-theme-default .hg-row .combination-key {
|
||||
@apply inline-flex !h-auto !w-auto flex-grow-0 py-1 text-xs;
|
||||
@apply inline-flex !h-auto !w-auto grow-0 py-1 text-xs;
|
||||
}
|
||||
|
||||
.hg-theme-default .hg-row:has(.combination-key) {
|
||||
|
|
|
@ -17,7 +17,7 @@ import AdoptRoute from "@routes/adopt";
|
|||
import SignupRoute from "@routes/signup";
|
||||
import LoginRoute from "@routes/login";
|
||||
import SetupRoute from "@routes/devices.$id.setup";
|
||||
import DevicesRoute, { loader as DeviceListLoader } from "@routes/devices";
|
||||
import DevicesRoute from "@routes/devices";
|
||||
import DeviceRoute, { LocalDevice } from "@routes/devices.$id";
|
||||
import Card from "@components/Card";
|
||||
import DevicesAlreadyAdopted from "@routes/devices.already-adopted";
|
||||
|
@ -36,7 +36,7 @@ import SettingsKeyboardMouseRoute from "./routes/devices.$id.settings.mouse";
|
|||
import api from "./api";
|
||||
import * as SettingsIndexRoute from "./routes/devices.$id.settings._index";
|
||||
import SettingsAdvancedRoute from "./routes/devices.$id.settings.advanced";
|
||||
import * as SettingsAccessIndexRoute from "./routes/devices.$id.settings.access._index";
|
||||
import SettingsAccessIndexRoute from "./routes/devices.$id.settings.access._index";
|
||||
import SettingsHardwareRoute from "./routes/devices.$id.settings.hardware";
|
||||
import SettingsVideoRoute from "./routes/devices.$id.settings.video";
|
||||
import SettingsAppearanceRoute from "./routes/devices.$id.settings.appearance";
|
||||
|
@ -166,7 +166,7 @@ if (isOnDevice) {
|
|||
children: [
|
||||
{
|
||||
index: true,
|
||||
element: <SettingsAccessIndexRoute.default />,
|
||||
element: <SettingsAccessIndexRoute />,
|
||||
loader: SettingsAccessIndexRoute.loader,
|
||||
},
|
||||
{
|
||||
|
@ -291,7 +291,7 @@ if (isOnDevice) {
|
|||
children: [
|
||||
{
|
||||
index: true,
|
||||
element: <SettingsAccessIndexRoute.default />,
|
||||
element: <SettingsAccessIndexRoute />,
|
||||
loader: SettingsAccessIndexRoute.loader,
|
||||
},
|
||||
{
|
||||
|
@ -341,7 +341,10 @@ if (isOnDevice) {
|
|||
loader: DeviceIdRename.loader,
|
||||
action: DeviceIdRename.action,
|
||||
},
|
||||
{ path: "devices", element: <DevicesRoute />, loader: DeviceListLoader },
|
||||
{
|
||||
path: "devices",
|
||||
element: <DevicesRoute />,
|
||||
loader: DevicesRoute.loader },
|
||||
],
|
||||
},
|
||||
],
|
||||
|
@ -356,7 +359,7 @@ document.addEventListener("DOMContentLoaded", () => {
|
|||
<Notifications
|
||||
toastOptions={{
|
||||
className:
|
||||
"rounded border-none bg-white text-black shadow outline outline-1 outline-slate-800/30",
|
||||
"rounded-sm border-none bg-white text-black shadow-sm outline-1 outline-slate-800/30",
|
||||
}}
|
||||
max={2}
|
||||
/>
|
||||
|
|
|
@ -77,6 +77,7 @@ export default function DevicesIdDeregister() {
|
|||
primaryLinks={[{ title: "Cloud Devices", to: "/devices" }]}
|
||||
userEmail={user?.email}
|
||||
picture={user?.picture}
|
||||
kvmName={device?.name}
|
||||
/>
|
||||
|
||||
<div className="w-full h-full">
|
||||
|
|
|
@ -320,7 +320,7 @@ function ModeSelectionView({
|
|||
].map(({ label, description, value: mode, icon: Icon, tag, disabled }, index) => (
|
||||
<div
|
||||
key={label}
|
||||
className={cx("animate-fadeIn opacity-0")}
|
||||
className={cx("animate-fadeIn")}
|
||||
style={{
|
||||
animationDuration: "0.7s",
|
||||
animationDelay: `${25 * (index * 5)}ms`,
|
||||
|
@ -328,7 +328,7 @@ function ModeSelectionView({
|
|||
>
|
||||
<Card
|
||||
className={cx(
|
||||
"w-full min-w-[250px] cursor-pointer bg-white shadow-sm transition-all duration-100 hover:shadow-md dark:bg-slate-800",
|
||||
"w-full min-w-[250px] cursor-pointer bg-white shadow-xs transition-all duration-100 hover:shadow-md dark:bg-slate-800",
|
||||
{
|
||||
"ring-2 ring-blue-700": selectedMode === mode,
|
||||
"hover:ring-2 hover:ring-blue-500": selectedMode !== mode && !disabled,
|
||||
|
@ -373,7 +373,7 @@ function ModeSelectionView({
|
|||
))}
|
||||
</div>
|
||||
<div
|
||||
className="flex animate-fadeIn justify-end opacity-0"
|
||||
className="flex animate-fadeIn justify-end"
|
||||
style={{
|
||||
animationDuration: "0.7s",
|
||||
animationDelay: "0.2s",
|
||||
|
@ -437,7 +437,7 @@ function BrowserFileView({
|
|||
className="block cursor-pointer select-none"
|
||||
>
|
||||
<div
|
||||
className="group animate-fadeIn opacity-0"
|
||||
className="group animate-fadeIn"
|
||||
style={{
|
||||
animationDuration: "0.7s",
|
||||
}}
|
||||
|
@ -483,7 +483,7 @@ function BrowserFileView({
|
|||
</div>
|
||||
|
||||
<div
|
||||
className="flex w-full animate-fadeIn items-end justify-between opacity-0"
|
||||
className="flex w-full animate-fadeIn items-end justify-between"
|
||||
style={{
|
||||
animationDuration: "0.7s",
|
||||
animationDelay: "0.1s",
|
||||
|
@ -578,7 +578,7 @@ function UrlView({
|
|||
/>
|
||||
|
||||
<div
|
||||
className="animate-fadeIn opacity-0"
|
||||
className="animate-fadeIn"
|
||||
style={{
|
||||
animationDuration: "0.7s",
|
||||
}}
|
||||
|
@ -593,7 +593,7 @@ function UrlView({
|
|||
/>
|
||||
</div>
|
||||
<div
|
||||
className="flex w-full animate-fadeIn items-end justify-between opacity-0"
|
||||
className="flex w-full animate-fadeIn items-end justify-between"
|
||||
style={{
|
||||
animationDuration: "0.7s",
|
||||
animationDelay: "0.1s",
|
||||
|
@ -619,7 +619,7 @@ function UrlView({
|
|||
|
||||
<hr className="border-slate-800/30 dark:border-slate-300/20" />
|
||||
<div
|
||||
className="animate-fadeIn opacity-0"
|
||||
className="animate-fadeIn"
|
||||
style={{
|
||||
animationDuration: "0.7s",
|
||||
animationDelay: "0.2s",
|
||||
|
@ -797,7 +797,7 @@ function DeviceFileView({
|
|||
description="Select an image to mount from the JetKVM storage"
|
||||
/>
|
||||
<div
|
||||
className="w-full animate-fadeIn opacity-0"
|
||||
className="w-full animate-fadeIn"
|
||||
style={{
|
||||
animationDuration: "0.7s",
|
||||
animationDelay: "0.1s",
|
||||
|
@ -886,7 +886,7 @@ function DeviceFileView({
|
|||
|
||||
{onStorageFiles.length > 0 ? (
|
||||
<div
|
||||
className="flex animate-fadeIn items-end justify-between opacity-0"
|
||||
className="flex animate-fadeIn items-end justify-between"
|
||||
style={{
|
||||
animationDuration: "0.7s",
|
||||
animationDelay: "0.15s",
|
||||
|
@ -914,7 +914,7 @@ function DeviceFileView({
|
|||
</div>
|
||||
) : (
|
||||
<div
|
||||
className="flex animate-fadeIn items-end justify-end opacity-0"
|
||||
className="flex animate-fadeIn items-end justify-end"
|
||||
style={{
|
||||
animationDuration: "0.7s",
|
||||
animationDelay: "0.15s",
|
||||
|
@ -927,7 +927,7 @@ function DeviceFileView({
|
|||
)}
|
||||
<hr className="border-slate-800/20 dark:border-slate-300/20" />
|
||||
<div
|
||||
className="animate-fadeIn space-y-2 opacity-0"
|
||||
className="animate-fadeIn space-y-2"
|
||||
style={{
|
||||
animationDuration: "0.7s",
|
||||
animationDelay: "0.20s",
|
||||
|
@ -941,9 +941,9 @@ function DeviceFileView({
|
|||
{percentageUsed}% used
|
||||
</span>
|
||||
</div>
|
||||
<div className="h-3.5 w-full overflow-hidden rounded-sm bg-slate-200 dark:bg-slate-700">
|
||||
<div className="h-3.5 w-full overflow-hidden rounded-xs bg-slate-200 dark:bg-slate-700">
|
||||
<div
|
||||
className="h-full rounded-sm bg-blue-700 transition-all duration-300 ease-in-out dark:bg-blue-500"
|
||||
className="h-full rounded-xs bg-blue-700 transition-all duration-300 ease-in-out dark:bg-blue-500"
|
||||
style={{ width: `${percentageUsed}%` }}
|
||||
></div>
|
||||
</div>
|
||||
|
@ -959,7 +959,7 @@ function DeviceFileView({
|
|||
|
||||
{onStorageFiles.length > 0 && (
|
||||
<div
|
||||
className="w-full animate-fadeIn opacity-0"
|
||||
className="w-full animate-fadeIn"
|
||||
style={{
|
||||
animationDuration: "0.7s",
|
||||
animationDelay: "0.25s",
|
||||
|
@ -1251,7 +1251,7 @@ function UploadFileView({
|
|||
}
|
||||
/>
|
||||
<div
|
||||
className="animate-fadeIn space-y-2 opacity-0"
|
||||
className="animate-fadeIn space-y-2"
|
||||
style={{
|
||||
animationDuration: "0.7s",
|
||||
}}
|
||||
|
@ -1365,7 +1365,7 @@ function UploadFileView({
|
|||
{/* Display upload error if present */}
|
||||
{uploadError && (
|
||||
<div
|
||||
className="mt-2 animate-fadeIn truncate text-sm text-red-600 opacity-0 dark:text-red-400"
|
||||
className="mt-2 animate-fadeIn truncate text-sm text-red-600 dark:text-red-400"
|
||||
style={{ animationDuration: "0.7s" }}
|
||||
>
|
||||
Error: {uploadError}
|
||||
|
@ -1373,7 +1373,7 @@ function UploadFileView({
|
|||
)}
|
||||
|
||||
<div
|
||||
className="flex w-full animate-fadeIn items-end opacity-0"
|
||||
className="flex w-full animate-fadeIn items-end"
|
||||
style={{
|
||||
animationDuration: "0.7s",
|
||||
animationDelay: "0.1s",
|
||||
|
|
|
@ -81,6 +81,7 @@ export default function DeviceIdRename() {
|
|||
primaryLinks={[{ title: "Cloud Devices", to: "/devices" }]}
|
||||
userEmail={user?.email}
|
||||
picture={user?.picture}
|
||||
kvmName={device?.name}
|
||||
/>
|
||||
|
||||
<div className="h-full w-full">
|
||||
|
|
|
@ -26,7 +26,7 @@ export interface TLSState {
|
|||
privateKey?: string;
|
||||
}
|
||||
|
||||
export const loader = async () => {
|
||||
const loader = async () => {
|
||||
if (isOnDevice) {
|
||||
const status = await api
|
||||
.GET(`${DEVICE_API}/device`)
|
||||
|
@ -468,3 +468,5 @@ export default function SettingsAccessIndexRoute() {
|
|||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
SettingsAccessIndexRoute.loader = loader;
|
|
@ -175,8 +175,8 @@ export default function SettingsMacrosRoute() {
|
|||
|
||||
return (
|
||||
<span key={stepIndex} className="inline-flex items-center">
|
||||
<StepIcon className="mr-1 h-3 w-3 flex-shrink-0 text-slate-400 dark:text-slate-500" />
|
||||
<span className="rounded-md border border-slate-200/50 bg-slate-50 px-2 py-0.5 dark:border-slate-700/50 dark:bg-slate-800">
|
||||
<StepIcon className="mr-1 h-3 w-3 shrink-0 text-slate-400 dark:text-slate-500" />
|
||||
<span className="px-2 py-0.5 rounded-md border border-slate-200/50 dark:border-slate-700/50 bg-slate-50 dark:bg-slate-800">
|
||||
{(Array.isArray(step.modifiers) &&
|
||||
step.modifiers.length > 0) ||
|
||||
(Array.isArray(step.keys) && step.keys.length > 0) ? (
|
||||
|
|
|
@ -14,15 +14,14 @@ import {
|
|||
useNetworkStateStore,
|
||||
} from "@/hooks/stores";
|
||||
import { useJsonRpc } from "@/hooks/useJsonRpc";
|
||||
import notifications from "@/notifications";
|
||||
import { Button } from "@components/Button";
|
||||
import { GridCard } from "@components/Card";
|
||||
import InputField from "@components/InputField";
|
||||
|
||||
import { SettingsPageHeader } from "../components/SettingsPageheader";
|
||||
import { SelectMenuBasic } from "../components/SelectMenuBasic";
|
||||
import Fieldset from "../components/Fieldset";
|
||||
import { ConfirmDialog } from "../components/ConfirmDialog";
|
||||
import { SelectMenuBasic } from "@/components/SelectMenuBasic";
|
||||
import { SettingsPageHeader } from "@/components/SettingsPageheader";
|
||||
import Fieldset from "@/components/Fieldset";
|
||||
import { ConfirmDialog } from "@/components/ConfirmDialog";
|
||||
import notifications from "@/notifications";
|
||||
|
||||
import { SettingsItem } from "./devices.$id.settings";
|
||||
|
||||
|
@ -51,9 +50,13 @@ export function LifeTimeLabel({ lifetime }: { lifetime: string }) {
|
|||
return () => clearInterval(interval);
|
||||
}, [lifetime]);
|
||||
|
||||
if (lifetime == "") {
|
||||
return <strong>N/A</strong>;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<span>{dayjs(lifetime).format("YYYY-MM-DD HH:mm")}</span>
|
||||
<strong>{dayjs(lifetime).format("YYYY-MM-DD HH:mm")}</strong>
|
||||
{remaining && (
|
||||
<>
|
||||
{" "}
|
||||
|
|
|
@ -12,15 +12,16 @@ import {
|
|||
LuNetwork,
|
||||
} from "react-icons/lu";
|
||||
import React, { useEffect, useRef, useState } from "react";
|
||||
import { useResizeObserver } from "usehooks-ts";
|
||||
|
||||
import Card from "@/components/Card";
|
||||
import { LinkButton } from "@/components/Button";
|
||||
import LoadingSpinner from "@/components/LoadingSpinner";
|
||||
import { useUiStore } from "@/hooks/stores";
|
||||
import useKeyboard from "@/hooks/useKeyboard";
|
||||
|
||||
import { LinkButton } from "../components/Button";
|
||||
import { cx } from "../cva.config";
|
||||
import { useUiStore } from "../hooks/stores";
|
||||
import useKeyboard from "../hooks/useKeyboard";
|
||||
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. */
|
||||
export default function SettingsRoute() {
|
||||
|
|
|
@ -12,7 +12,7 @@ import {
|
|||
useSearchParams,
|
||||
} from "react-router-dom";
|
||||
import { useInterval } from "usehooks-ts";
|
||||
import FocusTrap from "focus-trap-react";
|
||||
import { FocusTrap } from "focus-trap-react";
|
||||
import { motion, AnimatePresence } from "framer-motion";
|
||||
import useWebSocket from "react-use-websocket";
|
||||
|
||||
|
@ -809,7 +809,7 @@ export default function KvmIdRoute() {
|
|||
<WebRTCVideo />
|
||||
<div
|
||||
style={{ animationDuration: "500ms" }}
|
||||
className="pointer-events-none absolute inset-0 flex animate-slideUpFade items-center justify-center p-4 opacity-0"
|
||||
className="pointer-events-none absolute inset-0 flex animate-slideUpFade items-center justify-center p-4"
|
||||
>
|
||||
<div className="relative h-full max-h-[720px] w-full max-w-[1280px] rounded-md">
|
||||
{!!ConnectionStatusElement && ConnectionStatusElement}
|
||||
|
|
|
@ -1,14 +1,14 @@
|
|||
import { useLoaderData, useRevalidator } from "react-router-dom";
|
||||
import { LuMonitorSmartphone } from "react-icons/lu";
|
||||
import { ArrowRightIcon } from "@heroicons/react/16/solid";
|
||||
import { useInterval } from "usehooks-ts";
|
||||
|
||||
import DashboardNavbar from "@components/Header";
|
||||
import { LinkButton } from "@components/Button";
|
||||
import KvmCard from "@components/KvmCard";
|
||||
import { useInterval } from "usehooks-ts";
|
||||
import { checkAuth } from "@/main";
|
||||
import { User } from "@/hooks/stores";
|
||||
import EmptyCard from "@components/EmptyCard";
|
||||
import KvmCard from "@components/KvmCard";
|
||||
import { LinkButton } from "@components/Button";
|
||||
import { User } from "@/hooks/stores";
|
||||
import { checkAuth } from "@/main";
|
||||
import { CLOUD_API } from "@/ui.config";
|
||||
|
||||
interface LoaderData {
|
||||
|
@ -16,7 +16,7 @@ interface LoaderData {
|
|||
user: User;
|
||||
}
|
||||
|
||||
export const loader = async () => {
|
||||
const loader = async () => {
|
||||
const user = await checkAuth();
|
||||
|
||||
try {
|
||||
|
@ -101,3 +101,5 @@ export default function DevicesRoute() {
|
|||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
DevicesRoute.loader = loader;
|
|
@ -61,13 +61,13 @@ export default function WelcomeLocalModeRoute() {
|
|||
<Container>
|
||||
<div className="flex items-center justify-center w-full h-full isolate">
|
||||
<div className="max-w-xl space-y-8">
|
||||
<div className="flex items-center justify-center opacity-0 animate-fadeIn">
|
||||
<div className="flex items-center justify-center animate-fadeIn">
|
||||
<img src={LogoWhiteIcon} alt="" className="-ml-4 h-[32px] hidden dark:block" />
|
||||
<img src={LogoBlueIcon} alt="" className="-ml-4 h-[32px] dark:hidden" />
|
||||
</div>
|
||||
|
||||
<div
|
||||
className="space-y-2 text-center opacity-0 animate-fadeIn"
|
||||
className="space-y-2 text-center animate-fadeIn"
|
||||
style={{ animationDelay: "200ms" }}
|
||||
>
|
||||
<h1 className="text-4xl font-semibold text-black dark:text-white">Local Authentication Method</h1>
|
||||
|
@ -78,7 +78,7 @@ export default function WelcomeLocalModeRoute() {
|
|||
|
||||
<Form method="POST" className="space-y-8">
|
||||
<div
|
||||
className="grid grid-cols-1 gap-6 opacity-0 animate-fadeIn sm:grid-cols-2"
|
||||
className="grid grid-cols-1 gap-6 animate-fadeIn sm:grid-cols-2"
|
||||
style={{ animationDelay: "400ms" }}
|
||||
>
|
||||
{["password", "noPassword"].map(mode => (
|
||||
|
@ -120,7 +120,7 @@ export default function WelcomeLocalModeRoute() {
|
|||
|
||||
{actionData?.error && (
|
||||
<p
|
||||
className="text-sm text-center text-red-600 opacity-0 dark:text-red-400 animate-fadeIn"
|
||||
className="text-sm text-center text-red-600 dark:text-red-400 animate-fadeIn"
|
||||
style={{ animationDelay: "500ms" }}
|
||||
>
|
||||
{actionData.error}
|
||||
|
@ -128,7 +128,7 @@ export default function WelcomeLocalModeRoute() {
|
|||
)}
|
||||
|
||||
<div
|
||||
className="max-w-sm mx-auto opacity-0 animate-fadeIn"
|
||||
className="max-w-sm mx-auto animate-fadeIn"
|
||||
style={{ animationDelay: "500ms" }}
|
||||
>
|
||||
<Button
|
||||
|
@ -144,7 +144,7 @@ export default function WelcomeLocalModeRoute() {
|
|||
</Form>
|
||||
|
||||
<p
|
||||
className="max-w-md mx-auto text-xs text-center opacity-0 animate-fadeIn text-slate-500 dark:text-slate-400"
|
||||
className="max-w-md mx-auto text-xs text-center animate-fadeIn text-slate-500 dark:text-slate-400"
|
||||
style={{ animationDelay: "600ms" }}
|
||||
>
|
||||
You can always change your authentication method later in the settings.
|
||||
|
|
|
@ -71,13 +71,13 @@ export default function WelcomeLocalPasswordRoute() {
|
|||
<Container>
|
||||
<div className="flex items-center justify-center w-full h-full isolate">
|
||||
<div className="max-w-2xl space-y-8">
|
||||
<div className="flex items-center justify-center opacity-0 animate-fadeIn">
|
||||
<div className="flex items-center justify-center animate-fadeIn">
|
||||
<img src={LogoWhiteIcon} alt="" className="-ml-4 h-[32px] hidden dark:block" />
|
||||
<img src={LogoBlueIcon} alt="" className="-ml-4 h-[32px] dark:hidden" />
|
||||
</div>
|
||||
|
||||
<div
|
||||
className="space-y-2 text-center opacity-0 animate-fadeIn"
|
||||
className="space-y-2 text-center animate-fadeIn"
|
||||
style={{ animationDelay: "200ms" }}
|
||||
>
|
||||
<h1 className="text-4xl font-semibold text-black dark:text-white">Set a Password</h1>
|
||||
|
@ -90,7 +90,7 @@ export default function WelcomeLocalPasswordRoute() {
|
|||
<Form method="POST" className="max-w-sm mx-auto space-y-4">
|
||||
<div className="space-y-4">
|
||||
<div
|
||||
className="opacity-0 animate-fadeIn"
|
||||
className="animate-fadeIn"
|
||||
style={{ animationDelay: "400ms" }}
|
||||
>
|
||||
<InputFieldWithLabel
|
||||
|
@ -120,7 +120,7 @@ export default function WelcomeLocalPasswordRoute() {
|
|||
/>
|
||||
</div>
|
||||
<div
|
||||
className="opacity-0 animate-fadeIn"
|
||||
className="animate-fadeIn"
|
||||
style={{ animationDelay: "400ms" }}
|
||||
>
|
||||
<InputFieldWithLabel
|
||||
|
@ -137,7 +137,7 @@ export default function WelcomeLocalPasswordRoute() {
|
|||
{actionData?.error && <p className="text-sm text-red-600">{}</p>}
|
||||
|
||||
<div
|
||||
className="opacity-0 animate-fadeIn"
|
||||
className="animate-fadeIn"
|
||||
style={{ animationDelay: "600ms" }}
|
||||
>
|
||||
<Button
|
||||
|
@ -153,7 +153,7 @@ export default function WelcomeLocalPasswordRoute() {
|
|||
</Fieldset>
|
||||
|
||||
<p
|
||||
className="max-w-md text-xs text-center opacity-0 animate-fadeIn text-slate-500 dark:text-slate-400"
|
||||
className="max-w-md text-xs text-center animate-fadeIn text-slate-500 dark:text-slate-400"
|
||||
style={{ animationDelay: "800ms" }}
|
||||
>
|
||||
This password will be used to secure your device data and protect against
|
||||
|
|
|
@ -47,13 +47,13 @@ export default function WelcomeRoute() {
|
|||
<div className="max-w-3xl text-center">
|
||||
<div className="space-y-8">
|
||||
<div className="space-y-4">
|
||||
<div className="flex items-center justify-center opacity-0 animate-fadeIn animation-delay-1000">
|
||||
<div className="flex items-center justify-center animate-fadeIn animation-delay-1000">
|
||||
<img src={LogoWhiteIcon} alt="JetKVM Logo" className="h-[32px] hidden dark:block" />
|
||||
<img src={LogoBlueIcon} alt="JetKVM Logo" className="h-[32px] dark:hidden" />
|
||||
</div>
|
||||
|
||||
<div
|
||||
className="space-y-1 opacity-0 animate-fadeIn"
|
||||
className="space-y-1 animate-fadeIn"
|
||||
style={{ animationDelay: "1500ms" }}
|
||||
>
|
||||
<h1 className="text-4xl font-semibold text-black dark:text-white">
|
||||
|
@ -69,21 +69,21 @@ export default function WelcomeRoute() {
|
|||
<img
|
||||
src={DeviceImage}
|
||||
alt="JetKVM Device"
|
||||
className="animation-delay-0 max-w-md scale-[0.98] animate-fadeInScaleFloat opacity-0 transition-all duration-1000 ease-out"
|
||||
className="animation-delay-0 max-w-md scale-[0.98] animate-fadeInScaleFloat transition-all duration-1000 ease-out"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="-mt-8 space-y-4">
|
||||
<p
|
||||
style={{ animationDelay: "2000ms" }}
|
||||
className="max-w-lg mx-auto text-lg opacity-0 animate-fadeIn text-slate-700 dark:text-slate-300"
|
||||
className="max-w-lg mx-auto text-lg animate-fadeIn text-slate-700 dark:text-slate-300"
|
||||
>
|
||||
JetKVM combines powerful hardware with intuitive software to provide a
|
||||
seamless remote control experience.
|
||||
</p>
|
||||
<div
|
||||
style={{ animationDelay: "2300ms" }}
|
||||
className="opacity-0 animate-fadeIn"
|
||||
className="animate-fadeIn"
|
||||
>
|
||||
<LinkButton
|
||||
size="LG"
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import { defineConfig } from "vite";
|
||||
import react from "@vitejs/plugin-react-swc";
|
||||
import tailwindcss from "@tailwindcss/vite";
|
||||
import tsconfigPaths from "vite-tsconfig-paths";
|
||||
import basicSsl from "@vitejs/plugin-basic-ssl";
|
||||
|
||||
|
@ -16,7 +17,11 @@ export default defineConfig(({ mode, command }) => {
|
|||
const { JETKVM_PROXY_URL, USE_SSL } = process.env;
|
||||
const useSSL = USE_SSL === "true";
|
||||
|
||||
const plugins = [tsconfigPaths(), react()];
|
||||
const plugins = [
|
||||
tailwindcss(),
|
||||
tsconfigPaths(),
|
||||
react()
|
||||
];
|
||||
if (useSSL) {
|
||||
plugins.push(basicSsl());
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue