hololake-system-architecture/product-source/hololake-platform/src/components/StatusBar.tsx

276 lines
7.7 KiB
TypeScript

import { useEffect, useState } from 'react'
import type { McpStatus } from '../hooks/useMcpStatus'
import type { ThemeMode } from '../lib/themeMode'
import type { AppLocale } from '../lib/i18n'
import type { GitRemoteStatus, SyncStatus } from '../types'
import { TooltipProvider } from '@/components/ui/tooltip'
import type { GitRepositoryOption } from '../utils/gitRepositories'
import {
StatusBarPrimarySection,
StatusBarSecondarySection,
} from './status-bar/StatusBarSections'
import type { VaultOption } from './status-bar/types'
export type { VaultOption } from './status-bar/types'
const COMPACT_STATUS_BAR_MAX_WIDTH = 1000
const STACKED_STATUS_BAR_MAX_WIDTH = 900
const STATUS_BAR_STACKING_Z_INDEX = 30
function getWindowWidth() {
return typeof window === 'undefined' ? Number.POSITIVE_INFINITY : window.innerWidth
}
function getStatusBarLayout(windowWidth: number) {
const compact = windowWidth <= COMPACT_STATUS_BAR_MAX_WIDTH
const stacked = windowWidth <= STACKED_STATUS_BAR_MAX_WIDTH
return {
compact,
stacked,
}
}
function useStatusBarTicker() {
const [, setTick] = useState(0)
useEffect(() => {
const id = setInterval(() => setTick((tick) => tick + 1), 30_000)
return () => clearInterval(id)
}, [])
}
function useStatusBarLayout() {
const [windowWidth, setWindowWidth] = useState(() => getWindowWidth())
useEffect(() => {
if (typeof window === 'undefined') return
const handleResize = () => setWindowWidth(getWindowWidth())
window.addEventListener('resize', handleResize)
return () => window.removeEventListener('resize', handleResize)
}, [])
return getStatusBarLayout(windowWidth)
}
interface StatusBarProps {
galaxyMode?: boolean
noteCount: number
modifiedCount?: number
vaultPath: string
defaultWorkspacePath?: string | null
vaults: VaultOption[]
multiWorkspaceEnabled?: boolean
onSwitchVault: (path: string) => void
onSetDefaultWorkspace?: (path: string) => void
onOpenSettings?: () => void
onOpenVaultSettings?: () => void
onOpenLocalFolder?: () => void
onCreateEmptyVault?: () => void
onCloneVault?: () => void
onCloneGettingStarted?: () => void
onClickPending?: () => void
onClickPulse?: () => void
onCommitPush?: () => void
commitActionPending?: boolean
gitFeaturesEnabled?: boolean
onInitializeGit?: () => void
isOffline?: boolean
isVaultReloading?: boolean
isGitVault?: boolean
syncStatus?: SyncStatus
lastSyncTime?: number | null
conflictCount?: number
remoteStatus?: GitRemoteStatus | null
repositories?: GitRepositoryOption[]
selectedRepositoryPath?: string
onRepositoryChange?: (path: string) => void
onTriggerSync?: () => void
onPullAndPush?: () => void
onOpenConflictResolver?: () => void
zoomLevel?: number
themeMode?: ThemeMode
onZoomReset?: () => void
onToggleThemeMode?: () => void
onOpenFeedback?: () => void
onOpenDocs?: () => void
buildNumber?: string
onCheckForUpdates?: () => void
onRemoveVault?: (path: string) => void
onReorderVaults?: (orderedPaths: string[]) => void
onUpdateWorkspaceIdentity?: (path: string, patch: Partial<VaultOption>) => void
aiFeaturesEnabled?: boolean
mcpStatus?: McpStatus
onInstallMcp?: () => void
locale?: AppLocale
}
interface StatusBarFooterProps extends StatusBarProps {
compact: boolean
stacked: boolean
}
function StatusBarPrimaryFromFooter({
modifiedCount = 0,
vaultPath,
defaultWorkspacePath,
vaults,
multiWorkspaceEnabled,
onSwitchVault,
onSetDefaultWorkspace,
onOpenVaultSettings,
onOpenLocalFolder,
onCreateEmptyVault,
onCloneVault,
onCloneGettingStarted,
onClickPending,
onClickPulse,
onCommitPush,
commitActionPending = false,
gitFeaturesEnabled = true,
onInitializeGit,
isOffline = false,
isVaultReloading = false,
isGitVault = true,
syncStatus = 'idle',
lastSyncTime = null,
conflictCount = 0,
remoteStatus,
repositories,
selectedRepositoryPath,
onRepositoryChange,
onTriggerSync,
onPullAndPush,
onOpenConflictResolver,
buildNumber,
onCheckForUpdates,
onRemoveVault,
onReorderVaults,
onUpdateWorkspaceIdentity,
aiFeaturesEnabled = true,
mcpStatus,
onInstallMcp,
locale = 'en',
compact,
stacked,
}: StatusBarFooterProps) {
return (
<StatusBarPrimarySection
modifiedCount={modifiedCount}
vaultPath={vaultPath}
defaultWorkspacePath={defaultWorkspacePath}
vaults={vaults}
multiWorkspaceEnabled={multiWorkspaceEnabled}
onSwitchVault={onSwitchVault}
onSetDefaultWorkspace={onSetDefaultWorkspace}
onOpenVaultSettings={onOpenVaultSettings}
onOpenLocalFolder={onOpenLocalFolder}
onCreateEmptyVault={onCreateEmptyVault}
onCloneVault={onCloneVault}
onCloneGettingStarted={onCloneGettingStarted}
onClickPending={onClickPending}
onClickPulse={onClickPulse}
onCommitPush={onCommitPush}
commitActionPending={commitActionPending}
gitFeaturesEnabled={gitFeaturesEnabled}
onInitializeGit={onInitializeGit}
isOffline={isOffline}
isVaultReloading={isVaultReloading}
isGitVault={isGitVault}
syncStatus={syncStatus}
lastSyncTime={lastSyncTime}
conflictCount={conflictCount}
remoteStatus={remoteStatus}
repositories={repositories}
selectedRepositoryPath={selectedRepositoryPath}
onRepositoryChange={onRepositoryChange}
onTriggerSync={onTriggerSync}
onPullAndPush={onPullAndPush}
onOpenConflictResolver={onOpenConflictResolver}
buildNumber={buildNumber}
onCheckForUpdates={onCheckForUpdates}
onRemoveVault={onRemoveVault}
onReorderVaults={onReorderVaults}
onUpdateWorkspaceIdentity={onUpdateWorkspaceIdentity}
mcpStatus={aiFeaturesEnabled ? mcpStatus : undefined}
onInstallMcp={onInstallMcp}
locale={locale}
stacked={stacked}
compact={compact}
/>
)
}
function StatusBarSecondaryFromFooter({
noteCount,
zoomLevel = 100,
themeMode = 'light',
onZoomReset,
onToggleThemeMode,
onOpenFeedback,
onOpenDocs,
onOpenSettings,
locale = 'en',
compact,
stacked,
}: StatusBarFooterProps) {
return (
<StatusBarSecondarySection
noteCount={noteCount}
zoomLevel={zoomLevel}
themeMode={themeMode}
onZoomReset={onZoomReset}
onToggleThemeMode={onToggleThemeMode}
onOpenFeedback={onOpenFeedback}
onOpenDocs={onOpenDocs}
onOpenSettings={onOpenSettings}
locale={locale}
stacked={stacked}
compact={compact}
/>
)
}
function StatusBarFooter(props: StatusBarFooterProps) {
const { compact, galaxyMode = false, stacked } = props
return (
<footer
data-testid="status-bar"
style={{
minHeight: 36,
height: stacked ? 'auto' : 36,
flexShrink: 0,
display: 'flex',
flexWrap: stacked ? 'wrap' : 'nowrap',
alignItems: stacked ? 'flex-start' : 'center',
justifyContent: stacked ? 'flex-start' : 'space-between',
rowGap: stacked ? 4 : 0,
columnGap: compact ? 8 : 12,
background: galaxyMode ? '#050b1a' : 'var(--sidebar)',
borderTop: galaxyMode ? '1px solid rgba(132, 178, 222, .16)' : '1px solid var(--border)',
padding: stacked ? '4px 8px' : '0 8px',
fontSize: 14,
color: galaxyMode ? '#68809a' : 'var(--muted-foreground)',
position: 'relative',
zIndex: STATUS_BAR_STACKING_Z_INDEX,
}}
>
<StatusBarPrimaryFromFooter {...props} />
<StatusBarSecondaryFromFooter {...props} />
</footer>
)
}
export function StatusBar(props: StatusBarProps) {
useStatusBarTicker()
const { compact, stacked } = useStatusBarLayout()
return (
<TooltipProvider>
<StatusBarFooter {...props} compact={compact} stacked={stacked} />
</TooltipProvider>
)
}