Workspaces in Workspaces all around
This commit is contained in:
@@ -1,96 +1,48 @@
|
||||
import { useState, useEffect, useRef, useMemo } from 'react';
|
||||
import { Search, Terminal, Shield } from 'lucide-react';
|
||||
import { Input } from '@/components/ui/input';
|
||||
import { Accordion, AccordionItem, AccordionTrigger, AccordionContent } from '@/components/ui/accordion';
|
||||
import { Card } from '@/components/Card';
|
||||
import { useMemo } from 'react';
|
||||
import { Terminal, Shield, Server } from 'lucide-react';
|
||||
import type { LayoutNode, PanelComponents } from '@/components/Workspace';
|
||||
import { WorkspaceLayout } from '@/components/Workspace';
|
||||
import { appRegistry } from '../../Workspaces/app-registry';
|
||||
import { createSettingsPanelComponents, type SettingsSection } from '../SettingsPanel';
|
||||
import { AIHarnessesSection } from './AIHarnessesSection';
|
||||
// import { PluginsSection } from './PluginsSection';
|
||||
import { TerminalSection } from './TerminalSection';
|
||||
|
||||
const sections = [
|
||||
{
|
||||
key: 'ai-harnesses',
|
||||
icon: Terminal,
|
||||
title: 'AI Harnesses',
|
||||
description: 'Which AI coding tools do you use?',
|
||||
content: <AIHarnessesSection />,
|
||||
},
|
||||
// {
|
||||
// key: 'plugins',
|
||||
// icon: Puzzle,
|
||||
// title: 'Plugins',
|
||||
// description: 'Enable or disable installed plugins.',
|
||||
// content: <PluginsSection />,
|
||||
// },
|
||||
{
|
||||
key: 'terminal',
|
||||
icon: Shield,
|
||||
title: 'Terminal',
|
||||
description: 'Sandbox and access controls for the Terminal plugin.',
|
||||
content: <TerminalSection />,
|
||||
},
|
||||
const GLOBAL_KEY = 'SERVER_SETTINGS_SELECTED';
|
||||
|
||||
const sections: SettingsSection[] = [
|
||||
{ key: 'ai-harnesses', icon: Terminal, title: 'AI Harnesses', description: 'AI coding tools setup', content: <AIHarnessesSection /> },
|
||||
{ key: 'terminal', icon: Shield, title: 'Terminal', description: 'Sandbox and access controls', content: <TerminalSection /> },
|
||||
];
|
||||
|
||||
const allKeys = sections.map((s) => s.key);
|
||||
const { Sidebar, Content } = createSettingsPanelComponents({
|
||||
globalKey: GLOBAL_KEY,
|
||||
sidebarIcon: Server,
|
||||
sidebarLabel: 'Server',
|
||||
sections,
|
||||
});
|
||||
|
||||
const layout: LayoutNode = {
|
||||
type: 'group',
|
||||
id: 'server-root',
|
||||
direction: 'horizontal',
|
||||
children: [
|
||||
{ node: { type: 'panel', id: 'server-left', appType: null }, size: 20 },
|
||||
{ node: { type: 'panel', id: 'server-right', appType: null }, size: 80 },
|
||||
],
|
||||
};
|
||||
|
||||
export const ServerSettings = () => {
|
||||
const [search, setSearch] = useState('');
|
||||
const [expanded, setExpanded] = useState<string[]>(allKeys);
|
||||
const sectionRefs = useRef<Record<string, HTMLDivElement | null>>({});
|
||||
|
||||
const matchingKeys = useMemo(() => {
|
||||
if (!search) return allKeys;
|
||||
const query = search.toLowerCase();
|
||||
return sections
|
||||
.filter((s) => {
|
||||
const el = sectionRefs.current[s.key];
|
||||
return (el?.textContent?.toLowerCase() ?? '').includes(query);
|
||||
})
|
||||
.map((s) => s.key);
|
||||
}, [search]);
|
||||
|
||||
useEffect(() => {
|
||||
setExpanded(search ? matchingKeys : allKeys);
|
||||
}, [search, matchingKeys]);
|
||||
const panelComponents: PanelComponents = useMemo(
|
||||
() => ({
|
||||
'server-left': Sidebar,
|
||||
'server-right': Content,
|
||||
}),
|
||||
[],
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="flex justify-center h-full px-4 py-8 overflow-y-auto">
|
||||
<Card className="w-full max-w-2xl h-fit p-6">
|
||||
<div className="relative mb-6">
|
||||
<Search className="absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-duck-dark/40" />
|
||||
<Input
|
||||
placeholder="Search settings..."
|
||||
value={search}
|
||||
onChange={(ev) => setSearch(ev.target.value)}
|
||||
className="pl-9"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<Accordion type="multiple" value={expanded} onValueChange={setExpanded}>
|
||||
{sections.map((section) => (
|
||||
<div
|
||||
key={section.key}
|
||||
ref={(el) => {
|
||||
sectionRefs.current[section.key] = el;
|
||||
}}
|
||||
className={search && !matchingKeys.includes(section.key) ? 'hidden' : ''}
|
||||
>
|
||||
<AccordionItem value={section.key}>
|
||||
<AccordionTrigger className="hover:no-underline">
|
||||
<div className="flex items-center gap-3">
|
||||
<section.icon className="h-5 w-5 text-duck-forest shrink-0" />
|
||||
<div className="text-left">
|
||||
<div className="text-base font-bold text-duck-dark">{section.title}</div>
|
||||
<div className="text-sm font-normal text-duck-dark/70">{section.description}</div>
|
||||
</div>
|
||||
</div>
|
||||
</AccordionTrigger>
|
||||
<AccordionContent forceMount>{section.content}</AccordionContent>
|
||||
</AccordionItem>
|
||||
</div>
|
||||
))}
|
||||
</Accordion>
|
||||
</Card>
|
||||
<div className="h-full w-full pt-2">
|
||||
<WorkspaceLayout layout={layout} onLayoutChange={() => {}} registry={appRegistry} components={panelComponents} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user