Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 25 additions & 3 deletions src/components/layout/ExplorerSidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,8 @@ import { QueryHistorySection } from "./sidebar/QueryHistorySection";
import { NotebooksSection } from "./sidebar/NotebooksSection";
import { renameNotebook, deleteNotebook, listNotebooks, NOTEBOOKS_CHANGED_EVENT } from "../../utils/notebookStore";
import { useConnectionLayoutContext } from "../../hooks/useConnectionLayoutContext";
import { useDrivers } from "../../hooks/useDrivers";
import { getConnectionAccent } from "../../utils/driverUI";
import type { TableColumn } from "../../types/schema";
import type { ContextMenuData } from "../../types/sidebar";
import type { RoutineInfo, TriggerInfo } from "../../contexts/DatabaseContext";
Expand Down Expand Up @@ -134,10 +136,20 @@ export const ExplorerSidebar = ({ sidebarWidth, startResize, onCollapse, sidebar
loadDatabaseData,
refreshDatabaseData,
connectionDataMap,
connections,
connect,
} = useDatabase();
const { allDrivers } = useDrivers();
const { tabs, openNotebook, updateTab, closeTab } = useEditor();

// Accent color for a connection, matching the tinted editor tab bar / split
// panel headers. Falls back to the driver manifest color.
const accentForConnection = (connId: string) => {
const conn = connections.find((c) => c.id === connId);
const driverId = conn?.params.driver ?? connectionDataMap[connId]?.driver;
return getConnectionAccent(conn, allDrivers.find((d) => d.id === driverId));
};

const schemaLoadError =
activeCapabilities?.schemas === true && schemas.length === 0 && activeConnectionId
? connectionDataMap[activeConnectionId]?.error
Expand Down Expand Up @@ -487,13 +499,23 @@ export const ExplorerSidebar = ({ sidebarWidth, startResize, onCollapse, sidebar
{splitView.connectionIds.map(connId => {
const name = connectionDataMap[connId]?.connectionName ?? connId;
const isActive = explorerConnectionId === connId;
const accent = accentForConnection(connId);
return (
<button
key={connId}
onClick={() => setExplorerConnectionId(connId)}
className={isActive
? 'text-xs px-2 py-0.5 rounded bg-blue-500/20 text-blue-400 border border-blue-500/40'
: 'text-xs px-2 py-0.5 rounded text-muted hover:text-primary hover:bg-surface-secondary'}
className="text-xs px-2 py-0.5 rounded border transition-colors"
style={isActive
? {
backgroundColor: `${accent}33`,
borderColor: `${accent}66`,
color: accent,
}
: {
backgroundColor: `${accent}14`,
borderColor: 'transparent',
color: `${accent}80`,
}}
>
{name}
</button>
Expand Down
48 changes: 34 additions & 14 deletions src/components/layout/SplitPaneLayout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,28 @@ import { Editor } from '../../pages/Editor';
import { useSplitPaneResize } from '../../hooks/useSplitPaneResize';
import { useConnectionLayoutContext } from '../../hooks/useConnectionLayoutContext';
import { useDatabase } from '../../hooks/useDatabase';
import { useDrivers } from '../../hooks/useDrivers';
import { getConnectionAccent } from '../../utils/driverUI';
import type { SplitView } from '../../utils/connectionLayout';

export const SplitPaneLayout = ({ connectionIds, mode }: SplitView) => {
const containerRef = useRef<HTMLDivElement>(null);
const { splitRatio, startResize } = useSplitPaneResize(mode, containerRef);
const isVertical = mode === 'vertical';
const { deactivateSplit, removeConnectionFromSplit, explorerConnectionId, setExplorerConnectionId } = useConnectionLayoutContext();
const { switchConnection, connectionDataMap } = useDatabase();
const { switchConnection, connectionDataMap, connections } = useDatabase();
const { allDrivers } = useDrivers();
const { t } = useTranslation();

// Each panel header carries its own connection's accent color (matching the
// tinted editor tab bar inside the panel), with the active panel rendered
// more strongly so it still stands out from the others.
const accentFor = (connId: string) => {
const conn = connections.find((c) => c.id === connId);
const driverId = conn?.params.driver ?? connectionDataMap[connId]?.driver;
return getConnectionAccent(conn, allDrivers.find((d) => d.id === driverId));
};

const handleClosePanel = (connId: string) => {
const remaining = connectionIds.filter(id => id !== connId);
if (remaining.length < 2) {
Expand All @@ -36,7 +48,10 @@ export const SplitPaneLayout = ({ connectionIds, mode }: SplitView) => {
ref={containerRef}
className={clsx('flex h-full w-full', isVertical ? 'flex-row' : 'flex-col')}
>
{connectionIds.map((connId, i) => (
{connectionIds.map((connId, i) => {
const accent = accentFor(connId);
const isActivePanel = explorerConnectionId === connId;
return (
<Fragment key={connId}>
<div
className="flex flex-col min-w-0 min-h-0"
Expand All @@ -52,17 +67,21 @@ export const SplitPaneLayout = ({ connectionIds, mode }: SplitView) => {
: { flex: 1 }
}
>
{/* Panel header */}
<div className={clsx(
'flex items-center justify-between h-7 px-3 border-b shrink-0 transition-colors',
explorerConnectionId === connId
? 'bg-blue-500/10 border-blue-500/30'
: 'bg-elevated border-default',
)}>
<span className={clsx(
'text-xs truncate transition-colors',
explorerConnectionId === connId ? 'text-blue-400' : 'text-muted',
)}>
{/* Panel header — same accent wash as the editor tab bar below,
with the connection's accent color for the title text. */}
<div
className="flex items-center justify-between h-7 px-3 border-b shrink-0 transition-colors"
style={{
backgroundImage: isActivePanel
? `linear-gradient(${accent}30, ${accent}20)`
: `linear-gradient(${accent}18, ${accent}10)`,
borderBottomColor: `${accent}${isActivePanel ? '50' : '26'}`,
}}
>
<span
className="text-xs truncate transition-colors"
style={{ color: `${accent}${isActivePanel ? 'ff' : 'b3'}` }}
>
{connectionDataMap[connId]?.connectionName ?? connId}
</span>
<button
Expand Down Expand Up @@ -94,7 +113,8 @@ export const SplitPaneLayout = ({ connectionIds, mode }: SplitView) => {
/>
)}
</Fragment>
))}
);
})}
</div>
);
};
77 changes: 64 additions & 13 deletions src/pages/Editor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,8 @@ import { type OnMount, type Monaco } from "@monaco-editor/react";
import { save } from "@tauri-apps/plugin-dialog";
import { useAlert } from "../hooks/useAlert";
import { useDatabase } from "../hooks/useDatabase";
import { useDrivers } from "../hooks/useDrivers";
import { getConnectionAccent } from "../utils/driverUI";
import { useSavedQueries } from "../hooks/useSavedQueries";
import { useQueryHistory } from "../hooks/useQueryHistory";
import { useSettings } from "../hooks/useSettings";
Expand Down Expand Up @@ -137,6 +139,7 @@ export const Editor = () => {
const { t } = useTranslation();
const {
activeConnectionId,
connections,
tables,
views,
activeDriver,
Expand All @@ -148,6 +151,7 @@ export const Editor = () => {
schemaDataMap,
databaseDataMap,
} = useDatabase();
const { allDrivers } = useDrivers();
const { explorerConnectionId } = useConnectionLayoutContext();
const { settings } = useSettings();
const { saveQuery } = useSavedQueries();
Expand Down Expand Up @@ -2486,21 +2490,44 @@ export const Editor = () => {
);
}

const activeConnection = connections.find((c) => c.id === activeConnectionId);
const tabBarAccent = activeConnectionId
? getConnectionAccent(
activeConnection,
allDrivers.find((d) => d.id === activeDriver),
)
: null;
// Active-tab accents (indicator line, loading bar, rename border) follow the
// connection color when present, falling back to the default blue otherwise.
const tabAccentColor = tabBarAccent ?? "#3b82f6";

return (
<div className="flex flex-col h-full bg-base">
{/* Tab Bar */}
<div className="flex items-center bg-elevated border-b border-default h-9 shrink-0">
{/* Tab Bar — tinted with the active connection's accent color */}
<div
className="flex items-center bg-elevated border-b border-default h-9 shrink-0"
style={
tabBarAccent
? {
// Vertical accent wash (stronger at top) + accent-tinted bottom
// border so the bar reads as part of the active connection.
backgroundImage: `linear-gradient(${tabBarAccent}30, ${tabBarAccent}20)`,
borderBottomColor: `${tabBarAccent}50`,
}
: undefined
}
>
<button
onClick={() => scrollTabs("left")}
disabled={!canScrollLeft}
className="flex items-center justify-center w-7 h-full text-muted border-r border-default shrink-0 transition-colors disabled:opacity-30 disabled:cursor-not-allowed hover:enabled:text-white hover:enabled:bg-surface-secondary"
className="flex items-center justify-center w-7 h-full text-muted border-r border-default shrink-0 transition-colors disabled:opacity-30 disabled:cursor-not-allowed hover:enabled:text-primary hover:enabled:bg-surface-secondary"
>
<ChevronLeft size={14} />
</button>
<button
onClick={() => scrollTabs("right")}
disabled={!canScrollRight}
className="flex items-center justify-center w-7 h-full text-muted border-r border-default shrink-0 transition-colors disabled:opacity-30 disabled:cursor-not-allowed hover:enabled:text-white hover:enabled:bg-surface-secondary"
className="flex items-center justify-center w-7 h-full text-muted border-r border-default shrink-0 transition-colors disabled:opacity-30 disabled:cursor-not-allowed hover:enabled:text-primary hover:enabled:bg-surface-secondary"
>
<ChevronRight size={14} />
</button>
Expand All @@ -2521,14 +2548,32 @@ export const Editor = () => {
}
}}
className={clsx(
"flex items-center gap-2 px-3 h-full border-r border-default cursor-pointer min-w-[140px] max-w-[220px] text-xs transition-all group relative select-none",
"flex items-center gap-2 px-3 h-full border-r border-default cursor-pointer min-w-[140px] max-w-[220px] text-xs transition-all duration-150 group relative select-none",
activeTabId === tab.id
? "bg-base text-primary font-medium"
: "text-muted hover:bg-surface-secondary hover:text-secondary",
: "text-muted hover:bg-[var(--tab-hover)] hover:text-secondary",
)}
style={
activeTabId === tab.id
? {
// Active tab keeps the content background (so it reads as
// connected to the pane below) but carries a soft accent
// body, stronger at the top, tinted by the connection.
backgroundImage: `linear-gradient(${tabAccentColor}30, ${tabAccentColor}20)`,
}
: // Inactive tabs pick up a soft accent wash on hover instead of
// a flat neutral grey, keeping the strip tied to the connection.
({ "--tab-hover": `${tabAccentColor}33` } as React.CSSProperties)
}
>
{activeTabId === tab.id && (
<div className="absolute top-0 left-0 right-0 h-[2px] bg-(--accent-primary)" />
<div
className="absolute top-0 left-0 right-0 h-[2px] rounded-b-sm"
style={{
backgroundColor: `${tabAccentColor}cc`,
boxShadow: `0 0 5px ${tabAccentColor}59`,
}}
/>
)}
{tab.type === "table" ? (
<TableIcon size={12} className="text-accent shrink-0" />
Expand All @@ -2552,7 +2597,8 @@ export const Editor = () => {
if (e.key === "Enter") commitTabRename();
if (e.key === "Escape") setEditingTabId(null);
}}
className="flex-1 min-w-0 bg-surface-secondary border border-blue-500/50 rounded px-1 py-0.5 text-xs text-primary focus:outline-none"
className="flex-1 min-w-0 bg-surface-secondary border rounded px-1 py-0.5 text-xs text-primary focus:outline-none"
style={{ borderColor: `${tabAccentColor}80` }}
/>
) : (
<span
Expand Down Expand Up @@ -2580,7 +2626,7 @@ export const Editor = () => {
handleCloseTab(tab.id);
}}
className={clsx(
"p-0.5 rounded-sm hover:bg-surface-secondary transition-opacity shrink-0",
"p-0.5 rounded hover:bg-surface-secondary hover:text-primary hover:scale-110 transition-all duration-150 shrink-0",
activeTabId === tab.id
? "opacity-100"
: "opacity-0 group-hover:opacity-100",
Expand All @@ -2589,7 +2635,12 @@ export const Editor = () => {
<X size={12} />
</button>
{tab.isLoading && (
<div className="absolute bottom-0 left-0 h-0.5 bg-blue-500 animate-pulse w-full" />
<div
className="absolute bottom-0 left-0 h-0.5 w-full animate-pulse"
style={{
backgroundImage: `linear-gradient(90deg, transparent, ${tabAccentColor}, transparent)`,
}}
/>
)}
</div>
))}
Expand All @@ -2601,14 +2652,14 @@ export const Editor = () => {
...(isMultiDb ? { schema: selectedDatabases[0] } : {}),
})
}
className="flex items-center justify-center w-9 h-full text-muted hover:text-white hover:bg-surface-secondary border-l border-default transition-colors shrink-0"
className="flex items-center justify-center w-9 h-full text-muted hover:text-primary hover:bg-surface-secondary border-l border-default transition-colors shrink-0"
title={t("editor.newConsole")}
>
<Plus size={16} />
</button>
<button
onClick={() => addTab({ type: "query_builder" })}
className="flex items-center justify-center w-9 h-full text-purple-500 hover:text-white hover:bg-surface-secondary border-l border-default transition-colors shrink-0"
className="flex items-center justify-center w-9 h-full text-purple-500 hover:text-primary hover:bg-surface-secondary border-l border-default transition-colors shrink-0"
title={t("editor.newVisualQuery")}
>
<Network size={16} />
Expand All @@ -2624,7 +2675,7 @@ export const Editor = () => {
...(isMultiDb ? { schema: selectedDatabases[0] } : {}),
});
}}
className="flex items-center justify-center w-9 h-full text-orange-400 hover:text-white hover:bg-surface-secondary border-l border-default transition-colors shrink-0"
className="flex items-center justify-center w-9 h-full text-orange-400 hover:text-primary hover:bg-surface-secondary border-l border-default transition-colors shrink-0"
title={t("editor.newNotebook")}
>
<BookOpen size={16} />
Expand Down