diff --git a/src/renderer/src/App.tsx b/src/renderer/src/App.tsx index 6f34b34b..a0f8f467 100644 --- a/src/renderer/src/App.tsx +++ b/src/renderer/src/App.tsx @@ -1,4 +1,6 @@ import { useEffect } from 'react' +import { Minimize2, PanelLeft } from 'lucide-react' +import { TOGGLE_TERMINAL_PANE_EXPAND_EVENT } from '@/constants/terminal' import { useAppStore } from './store' import { useIpcEvents } from './hooks/useIpcEvents' import Sidebar from './components/Sidebar' @@ -10,6 +12,10 @@ function App(): React.JSX.Element { const toggleSidebar = useAppStore((s) => s.toggleSidebar) const activeView = useAppStore((s) => s.activeView) const activeWorktreeId = useAppStore((s) => s.activeWorktreeId) + const tabsByWorktree = useAppStore((s) => s.tabsByWorktree) + const activeTabId = useAppStore((s) => s.activeTabId) + const expandedPaneByTabId = useAppStore((s) => s.expandedPaneByTabId) + const canExpandPaneByTabId = useAppStore((s) => s.canExpandPaneByTabId) const fetchRepos = useAppStore((s) => s.fetchRepos) const fetchSettings = useAppStore((s) => s.fetchSettings) const initGitHubCache = useAppStore((s) => s.initGitHubCache) @@ -50,27 +56,50 @@ function App(): React.JSX.Element { } }, [settings?.theme]) + const tabs = activeWorktreeId ? (tabsByWorktree[activeWorktreeId] ?? []) : [] + const hasTabBar = tabs.length >= 2 + const effectiveActiveTabId = activeTabId ?? tabs[0]?.id ?? null + const activeTabCanExpand = effectiveActiveTabId + ? (canExpandPaneByTabId[effectiveActiveTabId] ?? false) + : false + const effectiveActiveTabExpanded = effectiveActiveTabId + ? (expandedPaneByTabId[effectiveActiveTabId] ?? false) + : false + const showTitlebarExpandButton = + activeView !== 'settings' && + activeWorktreeId !== null && + !hasTabBar && + effectiveActiveTabExpanded + + const handleToggleExpand = (): void => { + if (!effectiveActiveTabId) return + window.dispatchEvent( + new CustomEvent(TOGGLE_TERMINAL_PANE_EXPAND_EVENT, { + detail: { tabId: effectiveActiveTabId } + }) + ) + } + return (
Orca
+ {showTitlebarExpandButton && ( + + )}
diff --git a/src/renderer/src/assets/main.css b/src/renderer/src/assets/main.css index 87396c06..2c364fa2 100644 --- a/src/renderer/src/assets/main.css +++ b/src/renderer/src/assets/main.css @@ -176,6 +176,17 @@ scrollbar-color: var(--muted-foreground, #737373) transparent; } +/* Hide tab-strip scrollbars to prevent drag-time scrollbar flashes */ +.terminal-tab-strip { + -ms-overflow-style: none; + scrollbar-width: none; +} + +.terminal-tab-strip::-webkit-scrollbar { + width: 0; + height: 0; +} + /* ── Layout ──────────────────────────────────────────── */ #root { @@ -247,6 +258,36 @@ flex-shrink: 0; } +.titlebar-icon-button { + -webkit-app-region: no-drag; + background: none; + border: none; + color: var(--muted-foreground); + cursor: pointer; + padding: 4px; + margin-right: 8px; + display: flex; + align-items: center; + justify-content: center; + border-radius: 4px; + transition: background 150ms; +} + +.titlebar-icon-button:hover { + background: var(--accent); + color: var(--foreground); +} + +.titlebar-icon-button:disabled { + cursor: default; + opacity: 0.5; +} + +.titlebar-icon-button:disabled:hover { + background: none; + color: var(--muted-foreground); +} + /* ── Content ─────────────────────────────────────────── */ .content-area { diff --git a/src/renderer/src/components/Landing.tsx b/src/renderer/src/components/Landing.tsx index 0ea07b42..8d425063 100644 --- a/src/renderer/src/components/Landing.tsx +++ b/src/renderer/src/components/Landing.tsx @@ -1,26 +1,91 @@ +import { useMemo } from 'react' +import { FolderPlus, GitBranchPlus } from 'lucide-react' import { useAppStore } from '../store' +import logo from '../../../../resources/icon.png' + +type ShortcutItem = { + id: string + keys: string[] + action: string +} + +function KeyCap({ label }: { label: string }): React.JSX.Element { + return ( + + {label} + + ) +} export default function Landing(): React.JSX.Element { const repos = useAppStore((s) => s.repos) const addRepo = useAppStore((s) => s.addRepo) + const openModal = useAppStore((s) => s.openModal) + + const canCreateWorktree = repos.length > 0 + + const shortcuts = useMemo(() => { + const items: ShortcutItem[] = [] + if (canCreateWorktree) { + items.push({ id: 'create', keys: ['⌘', 'N'], action: 'Create worktree' }) + items.push({ id: 'up', keys: ['⌘', '⇧', '↑'], action: 'Move up worktree' }) + items.push({ id: 'down', keys: ['⌘', '⇧', '↓'], action: 'Move down worktree' }) + } + return items + }, [canCreateWorktree]) return (
-
-

Orca

- {repos.length === 0 ? ( - <> -

Get started by adding a repository

+
+
+ Orca logo +

ORCA

+ +

+ {canCreateWorktree + ? 'Select a worktree from the sidebar to begin.' + : 'Add a repository to get started.'} +

+ +
- - ) : ( -

Select a worktree from the sidebar

- )} + + {canCreateWorktree && ( + + )} +
+ + {shortcuts.length > 0 && ( +
+ {shortcuts.map((shortcut) => ( +
+ {shortcut.action} +
+ {shortcut.keys.map((key) => ( + + ))} +
+
+ ))} +
+ )} +
) diff --git a/src/renderer/src/components/TabBar.tsx b/src/renderer/src/components/TabBar.tsx index c57d4e72..f6a35bad 100644 --- a/src/renderer/src/components/TabBar.tsx +++ b/src/renderer/src/components/TabBar.tsx @@ -14,7 +14,7 @@ import { arrayMove } from '@dnd-kit/sortable' import { CSS } from '@dnd-kit/utilities' -import { X, Plus, Terminal as TerminalIcon } from 'lucide-react' +import { X, Plus, Terminal as TerminalIcon, Minimize2 } from 'lucide-react' import { DropdownMenu, DropdownMenuContent, @@ -29,12 +29,14 @@ interface SortableTabProps { tabCount: number hasTabsToRight: boolean isActive: boolean + isExpanded: boolean onActivate: (tabId: string) => void onClose: (tabId: string) => void onCloseOthers: (tabId: string) => void onCloseToRight: (tabId: string) => void onSetCustomTitle: (tabId: string, title: string | null) => void onSetTabColor: (tabId: string, color: string | null) => void + onToggleExpand: (tabId: string) => void } const TAB_COLORS = [ @@ -57,12 +59,14 @@ function SortableTab({ tabCount, hasTabsToRight, isActive, + isExpanded, onActivate, onClose, onCloseOthers, onCloseToRight, onSetCustomTitle, - onSetTabColor + onSetTabColor, + onToggleExpand }: SortableTabProps): React.JSX.Element { const { attributes, listeners, setNodeRef, transform, transition, isDragging } = useSortable({ id: tab.id @@ -124,6 +128,24 @@ function SortableTab({ style={{ backgroundColor: tab.color }} /> )} + {isExpanded && ( + + )}