header.tsx 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113
  1. import { useGlobalSync } from "@/context/global-sync"
  2. import { useLayout } from "@/context/layout"
  3. import { Session } from "@opencode-ai/sdk/v2/client"
  4. import { Button } from "@opencode-ai/ui/button"
  5. import { Icon } from "@opencode-ai/ui/icon"
  6. import { Mark } from "@opencode-ai/ui/logo"
  7. import { Select } from "@opencode-ai/ui/select"
  8. import { Tooltip } from "@opencode-ai/ui/tooltip"
  9. import { base64Decode } from "@opencode-ai/util/encode"
  10. import { getFilename } from "@opencode-ai/util/path"
  11. import { A, useParams } from "@solidjs/router"
  12. import { createMemo, Show } from "solid-js"
  13. export function Header(props: {
  14. navigateToProject: (directory: string) => void
  15. navigateToSession: (session: Session | undefined) => void
  16. }) {
  17. const globalSync = useGlobalSync()
  18. const layout = useLayout()
  19. const params = useParams()
  20. const currentDirectory = createMemo(() => base64Decode(params.dir ?? ""))
  21. const store = createMemo(() => globalSync.child(currentDirectory())[0])
  22. const sessions = createMemo(() => store().session ?? [])
  23. const currentSession = createMemo(() => sessions().find((s) => s.id === params.id))
  24. return (
  25. <header class="h-12 shrink-0 bg-background-base border-b border-border-weak-base flex" data-tauri-drag-region>
  26. <A
  27. href="/"
  28. classList={{
  29. "w-12 shrink-0 px-4 py-3.5": true,
  30. "flex items-center justify-start self-stretch": true,
  31. "border-r border-border-weak-base": true,
  32. }}
  33. style={{ width: layout.sidebar.opened() ? `${layout.sidebar.width()}px` : undefined }}
  34. data-tauri-drag-region
  35. >
  36. <Mark class="shrink-0" />
  37. </A>
  38. <div class="pl-4 px-6 flex items-center justify-between gap-4 w-full">
  39. <Show when={params.dir && layout.projects.list().length > 0}>
  40. <div class="flex items-center gap-3">
  41. <div class="flex items-center gap-2">
  42. <Select
  43. options={layout.projects.list().map((project) => project.worktree)}
  44. current={currentDirectory()}
  45. label={(x) => getFilename(x)}
  46. onSelect={(x) => (x ? props.navigateToProject(x) : undefined)}
  47. class="text-14-regular text-text-base"
  48. variant="ghost"
  49. >
  50. {/* @ts-ignore */}
  51. {(i) => (
  52. <div class="flex items-center gap-2">
  53. <Icon name="folder" size="small" />
  54. <div class="text-text-strong">{getFilename(i)}</div>
  55. </div>
  56. )}
  57. </Select>
  58. <div class="text-text-weaker">/</div>
  59. <Select
  60. options={sessions()}
  61. current={currentSession()}
  62. placeholder="New session"
  63. label={(x) => x.title}
  64. value={(x) => x.id}
  65. onSelect={props.navigateToSession}
  66. class="text-14-regular text-text-base max-w-md"
  67. variant="ghost"
  68. />
  69. </div>
  70. <Show when={currentSession()}>
  71. <Button as={A} href={`/${params.dir}/session`} icon="plus-small">
  72. New session
  73. </Button>
  74. </Show>
  75. </div>
  76. <div class="flex items-center gap-4">
  77. <Tooltip
  78. class="shrink-0"
  79. value={
  80. <div class="flex items-center gap-2">
  81. <span>Toggle terminal</span>
  82. <span class="text-icon-base text-12-medium">Ctrl `</span>
  83. </div>
  84. }
  85. >
  86. <Button variant="ghost" class="group/terminal-toggle size-6 p-0" onClick={layout.terminal.toggle}>
  87. <div class="relative flex items-center justify-center size-4 [&>*]:absolute [&>*]:inset-0">
  88. <Icon
  89. size="small"
  90. name={layout.terminal.opened() ? "layout-bottom-full" : "layout-bottom"}
  91. class="group-hover/terminal-toggle:hidden"
  92. />
  93. <Icon
  94. size="small"
  95. name="layout-bottom-partial"
  96. class="hidden group-hover/terminal-toggle:inline-block"
  97. />
  98. <Icon
  99. size="small"
  100. name={layout.terminal.opened() ? "layout-bottom" : "layout-bottom-full"}
  101. class="hidden group-active/terminal-toggle:inline-block"
  102. />
  103. </div>
  104. </Button>
  105. </Tooltip>
  106. </div>
  107. </Show>
  108. </div>
  109. </header>
  110. )
  111. }