196 lines
7.1 KiB
TypeScript
196 lines
7.1 KiB
TypeScript
import React, {useRef, FC, useState, useEffect} from "react";
|
|
import {Terminal} from "@/ui/Mobile/Apps/Terminal/Terminal.tsx";
|
|
import {TerminalKeyboard} from "@/ui/Mobile/Apps/Terminal/TerminalKeyboard.tsx";
|
|
import {BottomNavbar} from "@/ui/Mobile/Navigation/BottomNavbar.tsx";
|
|
import {LeftSidebar} from "@/ui/Mobile/Navigation/LeftSidebar.tsx";
|
|
import {TabProvider, useTabs} from "@/ui/Mobile/Navigation/Tabs/TabContext.tsx";
|
|
import {getUserInfo, getCookie} from "@/ui/main-axios.ts";
|
|
import {HomepageAuth} from "@/ui/Mobile/Homepage/HomepageAuth.tsx";
|
|
import {useTranslation} from "react-i18next";
|
|
|
|
const AppContent: FC = () => {
|
|
const {t} = useTranslation();
|
|
const {tabs, currentTab, getTab, removeTab} = useTabs();
|
|
const [isSidebarOpen, setIsSidebarOpen] = React.useState(true);
|
|
const [ready, setReady] = React.useState(true);
|
|
|
|
const [isAuthenticated, setIsAuthenticated] = useState(false);
|
|
const [username, setUsername] = useState<string | null>(null);
|
|
const [isAdmin, setIsAdmin] = useState(false);
|
|
const [authLoading, setAuthLoading] = useState(true);
|
|
|
|
useEffect(() => {
|
|
const checkAuth = () => {
|
|
const jwt = getCookie("jwt");
|
|
if (jwt) {
|
|
setAuthLoading(true);
|
|
getUserInfo()
|
|
.then((meRes) => {
|
|
setIsAuthenticated(true);
|
|
setIsAdmin(!!meRes.is_admin);
|
|
setUsername(meRes.username || null);
|
|
})
|
|
.catch((err) => {
|
|
setIsAuthenticated(false);
|
|
setIsAdmin(false);
|
|
setUsername(null);
|
|
document.cookie = 'jwt=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;';
|
|
})
|
|
.finally(() => setAuthLoading(false));
|
|
} else {
|
|
setIsAuthenticated(false);
|
|
setIsAdmin(false);
|
|
setUsername(null);
|
|
setAuthLoading(false);
|
|
}
|
|
}
|
|
|
|
checkAuth()
|
|
|
|
const handleStorageChange = () => checkAuth()
|
|
window.addEventListener('storage', handleStorageChange)
|
|
|
|
return () => window.removeEventListener('storage', handleStorageChange)
|
|
}, [])
|
|
|
|
useEffect(() => {
|
|
const interval = setInterval(() => {
|
|
fitCurrentTerminal()
|
|
}, 2000);
|
|
|
|
return () => clearInterval(interval);
|
|
}, []);
|
|
|
|
const handleAuthSuccess = (authData: { isAdmin: boolean; username: string | null; userId: string | null }) => {
|
|
setIsAuthenticated(true)
|
|
setIsAdmin(authData.isAdmin)
|
|
setUsername(authData.username)
|
|
}
|
|
|
|
const fitCurrentTerminal = () => {
|
|
const tab = getTab(currentTab as number);
|
|
if (tab && tab.terminalRef?.current?.fit) {
|
|
tab.terminalRef.current.fit();
|
|
}
|
|
};
|
|
|
|
React.useEffect(() => {
|
|
if (tabs.length > 0) {
|
|
setReady(false);
|
|
requestAnimationFrame(() => {
|
|
requestAnimationFrame(() => {
|
|
fitCurrentTerminal();
|
|
setReady(true);
|
|
});
|
|
});
|
|
}
|
|
}, [currentTab]);
|
|
|
|
const closeSidebar = () => setIsSidebarOpen(false);
|
|
|
|
const handleKeyboardLayoutChange = () => {
|
|
fitCurrentTerminal();
|
|
}
|
|
|
|
function handleKeyboardInput(input: string) {
|
|
const currentTerminalTab = getTab(currentTab as number);
|
|
if (currentTerminalTab && currentTerminalTab.terminalRef?.current?.sendInput) {
|
|
currentTerminalTab.terminalRef.current.sendInput(input);
|
|
}
|
|
}
|
|
|
|
if (authLoading) {
|
|
return (
|
|
<div className="h-screen w-screen flex items-center justify-center bg-dark-bg-darkest">
|
|
<p className="text-white">{t('common.loading')}</p>
|
|
</div>
|
|
)
|
|
}
|
|
|
|
if (!isAuthenticated) {
|
|
return (
|
|
<div className="h-screen w-screen flex items-center justify-center bg-dark-bg p-4">
|
|
<HomepageAuth
|
|
setLoggedIn={setIsAuthenticated}
|
|
setIsAdmin={setIsAdmin}
|
|
setUsername={setUsername}
|
|
setUserId={(id) => {
|
|
}}
|
|
loggedIn={isAuthenticated}
|
|
authLoading={authLoading}
|
|
dbError={null}
|
|
setDbError={(err) => {
|
|
}}
|
|
onAuthSuccess={handleAuthSuccess}
|
|
/>
|
|
</div>
|
|
)
|
|
}
|
|
|
|
return (
|
|
<div className="h-screen w-screen flex flex-col bg-dark-bg-darkest overflow-y-hidden overflow-x-hidden relative">
|
|
<div className="flex-1 min-h-0 relative">
|
|
{tabs.map(tab => (
|
|
<div
|
|
key={tab.id}
|
|
className={`absolute inset-0 mb-2 ${tab.id === currentTab ? 'visible' : 'invisible'} ${ready ? 'opacity-100' : 'opacity-0'}`}
|
|
>
|
|
<Terminal
|
|
ref={tab.terminalRef}
|
|
hostConfig={tab.hostConfig}
|
|
isVisible={tab.id === currentTab}
|
|
onClose={() => removeTab(tab.id)}
|
|
/>
|
|
</div>
|
|
))}
|
|
{tabs.length === 0 && (
|
|
<div className="flex flex-col items-center justify-center h-full text-white gap-3 px-4 text-center">
|
|
<h1 className="text-lg font-semibold">
|
|
{t('mobile.selectHostToStart')}
|
|
</h1>
|
|
<p className="text-sm text-gray-300 max-w-xs">
|
|
{t('mobile.limitedSupportMessage')}
|
|
</p>
|
|
</div>
|
|
)}
|
|
</div>
|
|
{currentTab &&
|
|
<div className="mb-1 z-10">
|
|
<TerminalKeyboard onSendInput={handleKeyboardInput} onLayoutChange={handleKeyboardLayoutChange}/>
|
|
</div>
|
|
}
|
|
<BottomNavbar
|
|
onSidebarOpenClick={() => setIsSidebarOpen(true)}
|
|
/>
|
|
|
|
{isSidebarOpen && (
|
|
<div
|
|
className="absolute inset-0 bg-black/30 backdrop-blur-sm z-10"
|
|
onClick={() => setIsSidebarOpen(false)}
|
|
/>
|
|
)}
|
|
|
|
<div className="absolute top-0 left-0 h-full z-20 pointer-events-none">
|
|
<div onClick={(e) => {
|
|
e.stopPropagation();
|
|
}} className="pointer-events-auto">
|
|
<LeftSidebar
|
|
isSidebarOpen={isSidebarOpen}
|
|
setIsSidebarOpen={setIsSidebarOpen}
|
|
onHostConnect={closeSidebar}
|
|
disabled={!isAuthenticated || authLoading}
|
|
username={username}
|
|
/>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
export const MobileApp: FC = () => {
|
|
return (
|
|
<TabProvider>
|
|
<AppContent/>
|
|
</TabProvider>
|
|
);
|
|
} |