2026-02-27 04:29:51 +00:00
|
|
|
import { useEffect } from "react";
|
2026-02-28 20:42:40 +00:00
|
|
|
import { useShallow } from "zustand/react/shallow";
|
2026-02-27 04:29:51 +00:00
|
|
|
import Sidebar from "./components/layout/Sidebar";
|
|
|
|
|
import TopBar from "./components/layout/TopBar";
|
|
|
|
|
import StatusBar from "./components/layout/StatusBar";
|
|
|
|
|
import TerminalView from "./components/terminal/TerminalView";
|
|
|
|
|
import { useDocker } from "./hooks/useDocker";
|
|
|
|
|
import { useSettings } from "./hooks/useSettings";
|
|
|
|
|
import { useProjects } from "./hooks/useProjects";
|
2026-02-28 21:18:33 +00:00
|
|
|
import { useUpdates } from "./hooks/useUpdates";
|
2026-02-27 04:29:51 +00:00
|
|
|
import { useAppState } from "./store/appState";
|
|
|
|
|
|
|
|
|
|
export default function App() {
|
2026-03-03 14:46:59 -08:00
|
|
|
const { checkDocker, checkImage, startDockerPolling } = useDocker();
|
2026-03-01 03:59:58 +00:00
|
|
|
const { loadSettings } = useSettings();
|
2026-02-27 04:29:51 +00:00
|
|
|
const { refresh } = useProjects();
|
2026-02-28 21:18:33 +00:00
|
|
|
const { loadVersion, checkForUpdates, startPeriodicCheck } = useUpdates();
|
2026-02-28 20:42:40 +00:00
|
|
|
const { sessions, activeSessionId } = useAppState(
|
|
|
|
|
useShallow(s => ({ sessions: s.sessions, activeSessionId: s.activeSessionId }))
|
|
|
|
|
);
|
2026-02-27 04:29:51 +00:00
|
|
|
|
|
|
|
|
// Initialize on mount
|
|
|
|
|
useEffect(() => {
|
2026-02-27 15:22:49 +00:00
|
|
|
loadSettings();
|
2026-03-03 14:46:59 -08:00
|
|
|
let stopPolling: (() => void) | undefined;
|
2026-02-28 20:42:40 +00:00
|
|
|
checkDocker().then((available) => {
|
2026-03-03 14:46:59 -08:00
|
|
|
if (available) {
|
|
|
|
|
checkImage();
|
|
|
|
|
} else {
|
|
|
|
|
stopPolling = startDockerPolling();
|
|
|
|
|
}
|
2026-02-28 20:42:40 +00:00
|
|
|
});
|
2026-02-27 04:29:51 +00:00
|
|
|
refresh();
|
2026-02-28 21:18:33 +00:00
|
|
|
|
|
|
|
|
// Update detection
|
|
|
|
|
loadVersion();
|
|
|
|
|
const updateTimer = setTimeout(() => checkForUpdates(), 3000);
|
|
|
|
|
const cleanup = startPeriodicCheck();
|
|
|
|
|
return () => {
|
|
|
|
|
clearTimeout(updateTimer);
|
|
|
|
|
cleanup?.();
|
2026-03-03 14:46:59 -08:00
|
|
|
stopPolling?.();
|
2026-02-28 21:18:33 +00:00
|
|
|
};
|
2026-02-27 04:29:51 +00:00
|
|
|
}, []); // eslint-disable-line react-hooks/exhaustive-deps
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<div className="flex flex-col h-screen p-6 gap-4 bg-[var(--bg-primary)]">
|
|
|
|
|
<TopBar />
|
|
|
|
|
<div className="flex flex-1 min-h-0 gap-4">
|
|
|
|
|
<Sidebar />
|
|
|
|
|
<main className="flex-1 bg-[var(--bg-secondary)] border border-[var(--border-color)] rounded-lg min-w-0 overflow-hidden">
|
|
|
|
|
{sessions.length === 0 ? (
|
|
|
|
|
<WelcomeScreen />
|
|
|
|
|
) : (
|
|
|
|
|
<div className="w-full h-full">
|
|
|
|
|
{sessions.map((session) => (
|
|
|
|
|
<TerminalView
|
|
|
|
|
key={session.id}
|
|
|
|
|
sessionId={session.id}
|
|
|
|
|
active={session.id === activeSessionId}
|
|
|
|
|
/>
|
|
|
|
|
))}
|
|
|
|
|
</div>
|
|
|
|
|
)}
|
|
|
|
|
</main>
|
|
|
|
|
</div>
|
|
|
|
|
<StatusBar />
|
|
|
|
|
</div>
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function WelcomeScreen() {
|
|
|
|
|
return (
|
|
|
|
|
<div className="flex items-center justify-center h-full text-[var(--text-secondary)]">
|
|
|
|
|
<div className="text-center">
|
|
|
|
|
<h1 className="text-3xl font-bold mb-2 text-[var(--text-primary)]">
|
|
|
|
|
Triple-C
|
|
|
|
|
</h1>
|
|
|
|
|
<p className="text-sm mb-4">Claude Code Container</p>
|
|
|
|
|
<p className="text-xs max-w-md">
|
|
|
|
|
Add a project from the sidebar, start its container, then open a
|
|
|
|
|
terminal to begin using Claude Code in a sandboxed environment.
|
|
|
|
|
</p>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
);
|
|
|
|
|
}
|