Skip to content

Commit dd1dcc2

Browse files
committed
feat: Allow starting with no open tabs, add dedicated buttons for new query tabs, and enable closing all tab types.
1 parent 147d7f2 commit dd1dcc2

File tree

2 files changed

+72
-54
lines changed

2 files changed

+72
-54
lines changed

frontend/src/App.tsx

Lines changed: 57 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -71,13 +71,10 @@ function App() {
7171
// Tab State
7272
const [tabs, setTabs] = useState<Tab[]>(() => {
7373
const saved = localStorage.getItem('opendb_tabs');
74-
// We use a constant for the title here because it's initial state, but we should potentially localize it dynamically if re-rendered.
75-
// However, tabs state is persisted. We might want to store 'type' and resolve title visually if generic.
76-
// For now, let's keep it simple.
77-
return saved ? JSON.parse(saved) : [{ id: 'query-main', type: 'query', title: t('app.queryEditor') }];
74+
return saved ? JSON.parse(saved) : [];
7875
});
7976
const [activeTabId, setActiveTabId] = useState<string>(() => {
80-
return localStorage.getItem('opendb_active_tab') || 'query-main';
77+
return localStorage.getItem('opendb_active_tab') || '';
8178
});
8279
// Schema for autocomplete
8380
const [dbSchema, setDbSchema] = useState<Record<string, string[]> | null>(null);
@@ -219,6 +216,16 @@ function App() {
219216
}, [query, executeQueries, tabs, activeTab]);
220217

221218

219+
const handleNewQueryTab = useCallback(() => {
220+
const newTab: Tab = {
221+
id: `query-${Date.now()}`,
222+
type: 'query',
223+
title: t('app.newQuery')
224+
};
225+
setTabs(prev => [...prev, newTab]);
226+
setActiveTabId(newTab.id);
227+
}, [t]);
228+
222229
const handleOpenModal = (config?: ConnectionConfig, name?: string) => {
223230
setModalData({ config, name });
224231
setModalOpen(true);
@@ -315,9 +322,14 @@ function App() {
315322

316323
{connected && (
317324
<div className="flex items-center h-full gap-1">
318-
{/* Replaced by TabBar in main area */}
325+
<button
326+
onClick={handleNewQueryTab}
327+
className="flex items-center gap-2 px-3 py-1.5 rounded-md bg-primary/10 text-primary hover:bg-primary/20 transition-all font-bold text-[10px] uppercase tracking-wider border border-primary/20"
328+
>
329+
<Code2 size={12} strokeWidth={2.5} />
330+
{t('app.newQuery')}
331+
</button>
319332
</div>
320-
321333
)}
322334
</div>
323335

@@ -391,6 +403,7 @@ function App() {
391403
activeTabId={activeTabId}
392404
onTabSelect={setActiveTabId}
393405
onTabClose={handleTabClose}
406+
onAddTab={handleNewQueryTab}
394407
/>
395408
)}
396409

@@ -432,8 +445,18 @@ function App() {
432445
}}
433446
/>
434447
) : (
435-
<div className="p-10 flex items-center justify-center h-full text-muted-foreground opacity-50 text-sm font-bold uppercase tracking-widest">
436-
{t('app.noTabSelected')}
448+
<div className="p-10 flex flex-col items-center justify-center h-full text-muted-foreground opacity-50 gap-4">
449+
<span className="text-sm font-bold uppercase tracking-widest">{t('app.noTabSelected')}</span>
450+
<div className="flex gap-2">
451+
<button
452+
onClick={handleNewQueryTab}
453+
className="flex items-center gap-2 px-4 py-2 rounded-lg bg-primary/10 text-primary hover:bg-primary/20 transition-colors font-bold text-xs uppercase tracking-wider"
454+
>
455+
<Code2 size={16} />
456+
{t('app.newQuery')}
457+
</button>
458+
</div>
459+
<p className="text-[10px] font-mono opacity-50">Press Cmd+K for commands</p>
437460
</div>
438461
)}
439462
</div>
@@ -461,29 +484,33 @@ function App() {
461484
)}
462485
</ResizablePanel>
463486

464-
</ResizablePanelGroup>
465-
</div>
487+
</ResizablePanelGroup >
488+
</div >
466489

467490
{/* Premium Modal */}
468-
{modalOpen && (
469-
<ConnectionModal
470-
title={modalData.name ? t('app.editConnection') : t('app.newConnection')}
471-
initialConfig={modalData.config}
472-
initialName={modalData.name}
473-
onSave={handleSaveModal}
474-
onClose={() => setModalOpen(false)}
475-
onTest={testConnection}
476-
loading={loading}
477-
/>
478-
)}
491+
{
492+
modalOpen && (
493+
<ConnectionModal
494+
title={modalData.name ? t('app.editConnection') : t('app.newConnection')}
495+
initialConfig={modalData.config}
496+
initialName={modalData.name}
497+
onSave={handleSaveModal}
498+
onClose={() => setModalOpen(false)}
499+
onTest={testConnection}
500+
loading={loading}
501+
/>
502+
)
503+
}
479504

480505
{/* Update Modal */}
481-
{updateInfo && (
482-
<UpdateModal
483-
updateInfo={updateInfo}
484-
onClose={() => setUpdateInfo(null)}
485-
/>
486-
)}
506+
{
507+
updateInfo && (
508+
<UpdateModal
509+
updateInfo={updateInfo}
510+
onClose={() => setUpdateInfo(null)}
511+
/>
512+
)
513+
}
487514

488515
{/* Slim Status Footer */}
489516
<footer className="h-8 border-t bg-card/80 flex items-center px-4 justify-between shrink-0 shadow-inner">
@@ -511,15 +538,7 @@ function App() {
511538
databases={databases}
512539
currentDb={currentDb}
513540
connected={connected}
514-
onNewQueryTab={() => {
515-
const newTab: Tab = {
516-
id: `query-${Date.now()}`,
517-
type: 'query',
518-
title: t('app.newQuery')
519-
};
520-
setTabs(prev => [...prev, newTab]);
521-
setActiveTabId(newTab.id);
522-
}}
541+
onNewQueryTab={handleNewQueryTab}
523542
onSwitchDb={handleSelectDatabase}
524543
onOpenHistory={() => {
525544
// Focus query editor and open history popover
@@ -535,7 +554,7 @@ function App() {
535554
}}
536555
onOpenSettings={() => setModalOpen(true)}
537556
/>
538-
</div>
557+
</div >
539558
);
540559
}
541560

frontend/src/components/TabBar.tsx

Lines changed: 15 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { X, Database, Code2 } from 'lucide-react';
1+
import { X, Database, Code2, Plus } from 'lucide-react';
22
import { cn } from '@/lib/utils';
33
import { Tab } from '../types/tabs';
44

@@ -7,9 +7,10 @@ interface TabBarProps {
77
activeTabId: string;
88
onTabSelect: (id: string) => void;
99
onTabClose: (id: string, e: React.MouseEvent) => void;
10+
onAddTab?: () => void;
1011
}
1112

12-
export function TabBar({ tabs, activeTabId, onTabSelect, onTabClose }: TabBarProps) {
13+
export function TabBar({ tabs, activeTabId, onTabSelect, onTabClose, onAddTab }: TabBarProps) {
1314
return (
1415
<div className="flex items-center h-9 bg-muted/40 border-b overflow-x-auto no-scrollbar px-1 gap-1">
1516
{tabs.map((tab) => (
@@ -36,20 +37,18 @@ export function TabBar({ tabs, activeTabId, onTabSelect, onTabClose }: TabBarPro
3637
Let's make Query Editor permanent for now or at least one tab must exist?
3738
For now, allow closing tables. Query Logic might be special.
3839
*/}
39-
{tab.type === 'table' && (
40-
<div
41-
onClick={(e) => {
42-
e.stopPropagation();
43-
onTabClose(tab.id, e);
44-
}}
45-
className={cn(
46-
"w-4 h-4 rounded hover:bg-destructive/10 hover:text-destructive flex items-center justify-center transition-colors opacity-0 group-hover:opacity-100",
47-
activeTabId === tab.id && "opacity-100"
48-
)}
49-
>
50-
<X size={10} strokeWidth={3} />
51-
</div>
52-
)}
40+
<div
41+
onClick={(e) => {
42+
e.stopPropagation();
43+
onTabClose(tab.id, e);
44+
}}
45+
className={cn(
46+
"w-4 h-4 rounded hover:bg-destructive/10 hover:text-destructive flex items-center justify-center transition-colors opacity-0 group-hover:opacity-100",
47+
activeTabId === tab.id && "opacity-100"
48+
)}
49+
>
50+
<X size={10} strokeWidth={3} />
51+
</div>
5352
</div>
5453
))}
5554
</div>

0 commit comments

Comments
 (0)