2025-11-05 10:36:16 -06:00
|
|
|
/* eslint-disable react-refresh/only-export-components */
|
2026-05-06 15:12:07 -05:00
|
|
|
import { prepareClientCacheVersion } from "@/lib/client-cache-version";
|
|
|
|
|
import { StrictMode, Suspense, lazy, useEffect, useState, useRef } from "react";
|
2025-09-12 14:42:00 -05:00
|
|
|
import { createRoot } from "react-dom/client";
|
|
|
|
|
import "./index.css";
|
|
|
|
|
import { ThemeProvider } from "@/components/theme-provider";
|
|
|
|
|
import "./i18n/i18n";
|
2026-05-06 15:12:07 -05:00
|
|
|
import { isElectron } from "@/lib/electron";
|
|
|
|
|
|
|
|
|
|
const DesktopApp = lazy(() => import("@/ui/desktop/DesktopApp.tsx"));
|
|
|
|
|
const MobileApp = lazy(() =>
|
|
|
|
|
import("@/ui/mobile/MobileApp.tsx").then((module) => ({
|
|
|
|
|
default: module.MobileApp,
|
|
|
|
|
})),
|
|
|
|
|
);
|
|
|
|
|
const HostManagerApp = lazy(
|
|
|
|
|
() => import("./ui/desktop/apps/host-manager/HostManagerApp.tsx"),
|
|
|
|
|
);
|
|
|
|
|
const TerminalApp = lazy(
|
|
|
|
|
() => import("./ui/desktop/apps/features/terminal/TerminalApp.tsx"),
|
|
|
|
|
);
|
|
|
|
|
const FileManagerApp = lazy(
|
|
|
|
|
() => import("./ui/desktop/apps/features/file-manager/FileManagerApp.tsx"),
|
|
|
|
|
);
|
|
|
|
|
const TunnelApp = lazy(
|
|
|
|
|
() => import("./ui/desktop/apps/features/tunnel/TunnelApp.tsx"),
|
|
|
|
|
);
|
|
|
|
|
const ServerStatsApp = lazy(
|
|
|
|
|
() => import("./ui/desktop/apps/features/server-stats/ServerStatsApp.tsx"),
|
|
|
|
|
);
|
|
|
|
|
const DockerApp = lazy(
|
|
|
|
|
() => import("./ui/desktop/apps/features/docker/DockerApp.tsx"),
|
|
|
|
|
);
|
|
|
|
|
const GuacamoleApp = lazy(
|
|
|
|
|
() => import("@/ui/desktop/apps/features/guacamole/GuacamoleApp.tsx"),
|
|
|
|
|
);
|
|
|
|
|
const ElectronVersionCheck = lazy(() =>
|
|
|
|
|
import("@/ui/desktop/user/ElectronVersionCheck.tsx").then((module) => ({
|
|
|
|
|
default: module.ElectronVersionCheck,
|
|
|
|
|
})),
|
|
|
|
|
);
|
2026-01-24 19:49:42 -06:00
|
|
|
|
|
|
|
|
const FullscreenApp: React.FC = () => {
|
|
|
|
|
const searchParams = new URLSearchParams(window.location.search);
|
|
|
|
|
const view = searchParams.get("view");
|
|
|
|
|
const hostId = searchParams.get("hostId");
|
|
|
|
|
|
|
|
|
|
switch (view) {
|
|
|
|
|
case "host-manager":
|
|
|
|
|
return <HostManagerApp />;
|
|
|
|
|
case "terminal":
|
|
|
|
|
return <TerminalApp hostId={hostId || undefined} />;
|
|
|
|
|
case "file-manager":
|
|
|
|
|
return <FileManagerApp hostId={hostId || undefined} />;
|
|
|
|
|
case "tunnel":
|
|
|
|
|
return <TunnelApp hostId={hostId || undefined} />;
|
|
|
|
|
case "server-stats":
|
|
|
|
|
return <ServerStatsApp hostId={hostId || undefined} />;
|
|
|
|
|
case "docker":
|
|
|
|
|
return <DockerApp hostId={hostId || undefined} />;
|
2026-03-14 20:05:05 -05:00
|
|
|
case "rdp":
|
|
|
|
|
case "vnc":
|
|
|
|
|
case "telnet":
|
|
|
|
|
return <GuacamoleApp hostId={hostId || undefined} />;
|
2026-01-24 19:49:42 -06:00
|
|
|
default:
|
|
|
|
|
return <DesktopApp />;
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
import { useServiceWorker } from "@/hooks/use-service-worker";
|
2025-08-07 02:20:27 -05:00
|
|
|
|
2025-09-12 14:42:00 -05:00
|
|
|
function useWindowWidth() {
|
|
|
|
|
const [width, setWidth] = useState(window.innerWidth);
|
|
|
|
|
const lastSwitchTime = useRef(0);
|
|
|
|
|
const isCurrentlyMobile = useRef(window.innerWidth < 768);
|
|
|
|
|
const hasSwitchedOnce = useRef(false);
|
|
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
let timeoutId: NodeJS.Timeout;
|
|
|
|
|
const handleResize = () => {
|
|
|
|
|
clearTimeout(timeoutId);
|
|
|
|
|
timeoutId = setTimeout(() => {
|
|
|
|
|
const newWidth = window.innerWidth;
|
|
|
|
|
const newIsMobile = newWidth < 768;
|
|
|
|
|
const now = Date.now();
|
|
|
|
|
|
|
|
|
|
if (hasSwitchedOnce.current && now - lastSwitchTime.current < 10000) {
|
|
|
|
|
setWidth(newWidth);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (
|
|
|
|
|
newIsMobile !== isCurrentlyMobile.current &&
|
|
|
|
|
now - lastSwitchTime.current > 5000
|
|
|
|
|
) {
|
|
|
|
|
lastSwitchTime.current = now;
|
|
|
|
|
isCurrentlyMobile.current = newIsMobile;
|
|
|
|
|
hasSwitchedOnce.current = true;
|
|
|
|
|
setWidth(newWidth);
|
|
|
|
|
} else {
|
|
|
|
|
setWidth(newWidth);
|
|
|
|
|
}
|
|
|
|
|
}, 2000);
|
|
|
|
|
};
|
|
|
|
|
window.addEventListener("resize", handleResize);
|
|
|
|
|
|
|
|
|
|
return () => {
|
|
|
|
|
clearTimeout(timeoutId);
|
|
|
|
|
window.removeEventListener("resize", handleResize);
|
|
|
|
|
};
|
|
|
|
|
}, []);
|
|
|
|
|
|
|
|
|
|
return width;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function RootApp() {
|
|
|
|
|
const width = useWindowWidth();
|
|
|
|
|
const isMobile = width < 768;
|
2025-11-05 10:36:16 -06:00
|
|
|
const [showVersionCheck, setShowVersionCheck] = useState(true);
|
2025-09-12 14:42:00 -05:00
|
|
|
|
2026-01-24 19:49:42 -06:00
|
|
|
useServiceWorker();
|
|
|
|
|
|
2025-11-05 10:36:16 -06:00
|
|
|
const userAgent =
|
2026-05-06 15:12:07 -05:00
|
|
|
navigator.userAgent ||
|
|
|
|
|
navigator.vendor ||
|
|
|
|
|
(window as Window & { opera?: string }).opera ||
|
|
|
|
|
"";
|
2025-11-05 10:36:16 -06:00
|
|
|
const isTermixMobile = /Termix-Mobile/.test(userAgent);
|
|
|
|
|
|
2026-01-24 19:49:42 -06:00
|
|
|
const searchParams = new URLSearchParams(window.location.search);
|
|
|
|
|
const isFullscreen = searchParams.has("view");
|
|
|
|
|
|
2025-11-05 10:36:16 -06:00
|
|
|
const renderApp = () => {
|
2026-01-24 19:49:42 -06:00
|
|
|
if (isFullscreen) {
|
|
|
|
|
return <FullscreenApp />;
|
|
|
|
|
}
|
|
|
|
|
|
2025-11-05 10:36:16 -06:00
|
|
|
if (isElectron()) {
|
|
|
|
|
return <DesktopApp />;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (isTermixMobile) {
|
|
|
|
|
return <MobileApp key="mobile" />;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return isMobile ? <MobileApp key="mobile" /> : <DesktopApp key="desktop" />;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<>
|
2026-01-24 19:49:42 -06:00
|
|
|
{!isFullscreen && (
|
|
|
|
|
<div
|
|
|
|
|
className="fixed inset-0 pointer-events-none"
|
|
|
|
|
style={{
|
|
|
|
|
backgroundColor: "var(--bg-base)",
|
|
|
|
|
backgroundImage: `linear-gradient(
|
|
|
|
|
135deg,
|
|
|
|
|
transparent 0%,
|
|
|
|
|
transparent 49%,
|
|
|
|
|
rgba(128, 128, 128, 0.03) 49%,
|
|
|
|
|
rgba(128, 128, 128, 0.03) 51%,
|
|
|
|
|
transparent 51%,
|
|
|
|
|
transparent 100%
|
|
|
|
|
)`,
|
|
|
|
|
backgroundSize: "80px 80px",
|
|
|
|
|
zIndex: 0,
|
|
|
|
|
}}
|
|
|
|
|
/>
|
|
|
|
|
)}
|
2025-11-05 10:36:16 -06:00
|
|
|
<div className="relative min-h-screen" style={{ zIndex: 1 }}>
|
2026-01-24 19:49:42 -06:00
|
|
|
{isElectron() && showVersionCheck && !isFullscreen ? (
|
2026-05-06 15:12:07 -05:00
|
|
|
<Suspense fallback={null}>
|
|
|
|
|
<ElectronVersionCheck
|
|
|
|
|
onContinue={() => setShowVersionCheck(false)}
|
|
|
|
|
isAuthenticated={false}
|
|
|
|
|
/>
|
|
|
|
|
</Suspense>
|
2025-11-05 10:36:16 -06:00
|
|
|
) : (
|
2026-05-06 15:12:07 -05:00
|
|
|
<Suspense fallback={null}>{renderApp()}</Suspense>
|
2025-11-05 10:36:16 -06:00
|
|
|
)}
|
|
|
|
|
</div>
|
|
|
|
|
</>
|
|
|
|
|
);
|
2025-09-12 14:42:00 -05:00
|
|
|
}
|
|
|
|
|
|
2026-05-06 15:12:07 -05:00
|
|
|
prepareClientCacheVersion().finally(() => {
|
|
|
|
|
createRoot(document.getElementById("root")!).render(
|
|
|
|
|
<StrictMode>
|
|
|
|
|
<ThemeProvider defaultTheme="dark" storageKey="vite-ui-theme">
|
|
|
|
|
<RootApp />
|
|
|
|
|
</ThemeProvider>
|
|
|
|
|
</StrictMode>,
|
|
|
|
|
);
|
|
|
|
|
});
|