Skip to content
Open
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
24 changes: 23 additions & 1 deletion packages/app/src/components/settings-general.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { useTheme, type ColorScheme } from "@opencode-ai/ui/theme"
import { showToast } from "@opencode-ai/ui/toast"
import { useLanguage } from "@/context/language"
import { usePlatform } from "@/context/platform"
import { useSettings, monoFontFamily } from "@/context/settings"
import { useSettings, monoFontFamily, type SidebarStyle } from "@/context/settings"
import { playSound, SOUND_OPTIONS } from "@/utils/sound"
import { Link } from "./link"

Expand Down Expand Up @@ -137,6 +137,11 @@ export const SettingsGeneral: Component = () => {
] as const
const fontOptionsList = [...fontOptions]

const sidebarStyleOptions = [
{ value: "classic" as SidebarStyle, label: "sidebar.style.classic" as const },
{ value: "list" as SidebarStyle, label: "sidebar.style.list" as const },
]

const noneSound = { id: "none", label: "sound.option.none", src: undefined } as const
const soundOptions = [noneSound, ...SOUND_OPTIONS]

Expand Down Expand Up @@ -267,6 +272,23 @@ export const SettingsGeneral: Component = () => {
)}
</Select>
</SettingsRow>

<SettingsRow
title={language.t("settings.general.row.sidebarStyle.title")}
description={language.t("settings.general.row.sidebarStyle.description")}
>
<Select
data-action="settings-sidebar-style"
options={sidebarStyleOptions}
current={sidebarStyleOptions.find((o) => o.value === settings.general.sidebarStyle())}
value={(o) => o.value}
label={(o) => language.t(o.label)}
onSelect={(option) => option && settings.general.setSidebarStyle(option.value)}
variant="secondary"
size="small"
triggerVariant="settings"
/>
</SettingsRow>
</div>
</div>
)
Expand Down
8 changes: 8 additions & 0 deletions packages/app/src/context/settings.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,16 @@ export interface SoundSettings {
errors: string
}

export type SidebarStyle = "classic" | "list"

export interface Settings {
general: {
autoSave: boolean
releaseNotes: boolean
showReasoningSummaries: boolean
shellToolPartsExpanded: boolean
editToolPartsExpanded: boolean
sidebarStyle: SidebarStyle
}
updates: {
startup: boolean
Expand All @@ -48,6 +51,7 @@ const defaultSettings: Settings = {
showReasoningSummaries: false,
shellToolPartsExpanded: true,
editToolPartsExpanded: false,
sidebarStyle: "classic" as SidebarStyle,
},
updates: {
startup: true,
Expand Down Expand Up @@ -147,6 +151,10 @@ export const { use: useSettings, provider: SettingsProvider } = createSimpleCont
setEditToolPartsExpanded(value: boolean) {
setStore("general", "editToolPartsExpanded", value)
},
sidebarStyle: withFallback(() => store.general?.sidebarStyle, defaultSettings.general.sidebarStyle),
setSidebarStyle(value: SidebarStyle) {
setStore("general", "sidebarStyle", value)
},
},
updates: {
startup: withFallback(() => store.updates?.startup, defaultSettings.updates.startup),
Expand Down
5 changes: 5 additions & 0 deletions packages/app/src/i18n/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -608,6 +608,7 @@ export const dict = {
"sidebar.project.recentSessions": "Recent sessions",
"sidebar.project.viewAllSessions": "View all sessions",
"sidebar.project.clearNotifications": "Clear notifications",
"sidebar.threads": "Projects",

"app.name.desktop": "OpenCode Desktop",

Expand All @@ -634,6 +635,10 @@ export const dict = {
"settings.general.row.theme.description": "Customise how OpenCode is themed.",
"settings.general.row.font.title": "Font",
"settings.general.row.font.description": "Customise the mono font used in code blocks",
"settings.general.row.sidebarStyle.title": "Sidebar style",
"settings.general.row.sidebarStyle.description": "Choose between classic icon strip or list layout",
"sidebar.style.classic": "Classic",
"sidebar.style.list": "List",
"settings.general.row.reasoningSummaries.title": "Show reasoning summaries",
"settings.general.row.reasoningSummaries.description": "Display model reasoning summaries in the timeline",
"settings.general.row.shellToolPartsExpanded.title": "Expand shell tool parts",
Expand Down
224 changes: 154 additions & 70 deletions packages/app/src/pages/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ import {
import { workspaceOpenState } from "./layout/sidebar-workspace-helpers"
import { ProjectDragOverlay, SortableProject, type ProjectSidebarContext } from "./layout/sidebar-project"
import { SidebarContent } from "./layout/sidebar-shell"
import { SidebarListContent } from "./layout/sidebar-list"

export default function Layout(props: ParentProps) {
const [store, setStore, , ready] = persisted(
Expand Down Expand Up @@ -176,7 +177,8 @@ export default function Layout(props: ParentProps) {
aim.reset()
})

const sidebarHovering = createMemo(() => !layout.sidebar.opened() && state.hoverProject !== undefined)
const listMode = createMemo(() => settings.general.sidebarStyle() === "list")
const sidebarHovering = createMemo(() => !listMode() && !layout.sidebar.opened() && state.hoverProject !== undefined)
const sidebarExpanded = createMemo(() => layout.sidebar.opened() || sidebarHovering())
const setHoverProject = (value: string | undefined) => {
setState("hoverProject", value)
Expand Down Expand Up @@ -1510,7 +1512,13 @@ export default function Layout(props: ParentProps) {
)

createEffect(() => {
const sidebarWidth = layout.sidebar.opened() ? layout.sidebar.width() : 48
const sidebarWidth = listMode()
? layout.sidebar.opened()
? layout.sidebar.width()
: 0
: layout.sidebar.opened()
? layout.sidebar.width()
: 48
document.documentElement.style.setProperty("--dialog-left-margin", `${sidebarWidth}px`)
})

Expand Down Expand Up @@ -1969,10 +1977,19 @@ export default function Layout(props: ParentProps) {
aria-label={language.t("sidebar.nav.projectsAndSessions")}
data-component="sidebar-nav-desktop"
classList={{
"hidden xl:block": true,
"hidden xl:block": !listMode() || layout.sidebar.opened(),
hidden: listMode() && !layout.sidebar.opened(),
"relative shrink-0": true,
}}
style={{ width: layout.sidebar.opened() ? `${Math.max(layout.sidebar.width(), 244)}px` : "64px" }}
style={{
width: listMode()
? layout.sidebar.opened()
? `${Math.max(layout.sidebar.width(), 280)}px`
: "0px"
: layout.sidebar.opened()
? `${Math.max(layout.sidebar.width(), 244)}px`
: "64px",
}}
ref={(el) => {
setState("nav", el)
}}
Expand All @@ -1993,48 +2010,89 @@ export default function Layout(props: ParentProps) {
}, 300)
}}
>
<div class="@container w-full h-full contain-strict">
<SidebarContent
opened={() => layout.sidebar.opened()}
aimMove={aim.move}
projects={() => layout.projects.list()}
renderProject={(project) => (
<SortableProject ctx={projectSidebarCtx} project={project} sortNow={sortNow} />
)}
handleDragStart={handleDragStart}
handleDragEnd={handleDragEnd}
handleDragOver={handleDragOver}
openProjectLabel={language.t("command.project.open")}
openProjectKeybind={() => command.keybind("project.open")}
onOpenProject={chooseProject}
renderProjectOverlay={() => (
<ProjectDragOverlay projects={() => layout.projects.list()} activeProject={() => store.activeProject} />
)}
settingsLabel={() => language.t("sidebar.settings")}
settingsKeybind={() => command.keybind("settings.open")}
onOpenSettings={openSettings}
helpLabel={() => language.t("sidebar.help")}
onOpenHelp={() => platform.openLink("https://opencode.ai/desktop-feedback")}
renderPanel={() => <SidebarPanel project={currentProject()} />}
/>
</div>
<Show when={!layout.sidebar.opened() ? hoverProjectData()?.worktree : undefined} keyed>
{(worktree) => (
<div class="absolute inset-y-0 left-16 z-50 flex" onMouseEnter={aim.reset}>
<SidebarPanel project={hoverProjectData()} />
<Show
when={listMode()}
fallback={
<>
<div class="@container w-full h-full contain-strict">
<SidebarContent
opened={() => layout.sidebar.opened()}
aimMove={aim.move}
projects={() => layout.projects.list()}
renderProject={(project) => (
<SortableProject ctx={projectSidebarCtx} project={project} sortNow={sortNow} />
)}
handleDragStart={handleDragStart}
handleDragEnd={handleDragEnd}
handleDragOver={handleDragOver}
openProjectLabel={language.t("command.project.open")}
openProjectKeybind={() => command.keybind("project.open")}
onOpenProject={chooseProject}
renderProjectOverlay={() => (
<ProjectDragOverlay
projects={() => layout.projects.list()}
activeProject={() => store.activeProject}
/>
)}
settingsLabel={() => language.t("sidebar.settings")}
settingsKeybind={() => command.keybind("settings.open")}
onOpenSettings={openSettings}
helpLabel={() => language.t("sidebar.help")}
onOpenHelp={() => platform.openLink("https://opencode.ai/desktop-feedback")}
renderPanel={() => <SidebarPanel project={currentProject()} />}
/>
</div>
<Show when={!layout.sidebar.opened() ? hoverProjectData()?.worktree : undefined} keyed>
{(_worktree) => (
<div class="absolute inset-y-0 left-16 z-50 flex" onMouseEnter={aim.reset}>
<SidebarPanel project={hoverProjectData()} />
</div>
)}
</Show>
<Show when={layout.sidebar.opened()}>
<ResizeHandle
direction="horizontal"
size={layout.sidebar.width()}
min={244}
max={typeof window === "undefined" ? 1000 : window.innerWidth * 0.3 + 64}
collapseThreshold={244}
onResize={layout.sidebar.resize}
onCollapse={layout.sidebar.close}
/>
</Show>
</>
}
>
<Show when={layout.sidebar.opened()}>
<div class="@container w-full h-full contain-strict border-t border-r border-border-weak-base">
<SidebarListContent
projects={() => layout.projects.list()}
sortNow={sortNow}
onNewSession={(directory) => navigateWithSidebarReset(`/${base64Encode(directory)}/session`)}
onOpenSettings={openSettings}
onOpenProject={chooseProject}
archiveSession={archiveSession}
openProjectLabel={() => language.t("command.project.open")}
settingsLabel={() => language.t("sidebar.settings")}
settingsKeybind={() => command.keybind("settings.open")}
newSessionLabel={() => language.t("command.session.new")}
newSessionKeybind={() => command.keybind("session.new")}
currentSessionId={() => params.id}
threadsLabel={() => language.t("sidebar.threads")}
helpLabel={() => language.t("sidebar.help")}
onOpenHelp={() => platform.openLink("https://opencode.ai/desktop-feedback")}
/>
</div>
)}
</Show>
<Show when={layout.sidebar.opened()}>
<ResizeHandle
direction="horizontal"
size={layout.sidebar.width()}
min={244}
max={typeof window === "undefined" ? 1000 : window.innerWidth * 0.3 + 64}
collapseThreshold={244}
onResize={layout.sidebar.resize}
onCollapse={layout.sidebar.close}
/>
<ResizeHandle
direction="horizontal"
size={layout.sidebar.width()}
min={280}
max={typeof window === "undefined" ? 1000 : window.innerWidth * 0.3}
collapseThreshold={280}
onResize={layout.sidebar.resize}
onCollapse={layout.sidebar.close}
/>
</Show>
</Show>
</nav>
<div class="xl:hidden">
Expand All @@ -2058,37 +2116,63 @@ export default function Layout(props: ParentProps) {
}}
onClick={(e) => e.stopPropagation()}
>
<SidebarContent
mobile
opened={() => layout.sidebar.opened()}
aimMove={aim.move}
projects={() => layout.projects.list()}
renderProject={(project) => (
<SortableProject ctx={projectSidebarCtx} project={project} sortNow={sortNow} mobile />
)}
handleDragStart={handleDragStart}
handleDragEnd={handleDragEnd}
handleDragOver={handleDragOver}
openProjectLabel={language.t("command.project.open")}
openProjectKeybind={() => command.keybind("project.open")}
onOpenProject={chooseProject}
renderProjectOverlay={() => (
<ProjectDragOverlay projects={() => layout.projects.list()} activeProject={() => store.activeProject} />
)}
settingsLabel={() => language.t("sidebar.settings")}
settingsKeybind={() => command.keybind("settings.open")}
onOpenSettings={openSettings}
helpLabel={() => language.t("sidebar.help")}
onOpenHelp={() => platform.openLink("https://opencode.ai/desktop-feedback")}
renderPanel={() => <SidebarPanel project={currentProject()} mobile />}
/>
<Show
when={listMode()}
fallback={
<SidebarContent
mobile
opened={() => layout.sidebar.opened()}
aimMove={aim.move}
projects={() => layout.projects.list()}
renderProject={(project) => (
<SortableProject ctx={projectSidebarCtx} project={project} sortNow={sortNow} mobile />
)}
handleDragStart={handleDragStart}
handleDragEnd={handleDragEnd}
handleDragOver={handleDragOver}
openProjectLabel={language.t("command.project.open")}
openProjectKeybind={() => command.keybind("project.open")}
onOpenProject={chooseProject}
renderProjectOverlay={() => (
<ProjectDragOverlay
projects={() => layout.projects.list()}
activeProject={() => store.activeProject}
/>
)}
settingsLabel={() => language.t("sidebar.settings")}
settingsKeybind={() => command.keybind("settings.open")}
onOpenSettings={openSettings}
helpLabel={() => language.t("sidebar.help")}
onOpenHelp={() => platform.openLink("https://opencode.ai/desktop-feedback")}
renderPanel={() => <SidebarPanel project={currentProject()} mobile />}
/>
}
>
<SidebarListContent
projects={() => layout.projects.list()}
sortNow={sortNow}
onNewSession={(directory) => navigateWithSidebarReset(`/${base64Encode(directory)}/session`)}
onOpenSettings={openSettings}
onOpenProject={chooseProject}
archiveSession={archiveSession}
openProjectLabel={() => language.t("command.project.open")}
settingsLabel={() => language.t("sidebar.settings")}
settingsKeybind={() => command.keybind("settings.open")}
newSessionLabel={() => language.t("command.session.new")}
newSessionKeybind={() => command.keybind("session.new")}
currentSessionId={() => params.id}
threadsLabel={() => language.t("sidebar.threads")}
helpLabel={() => language.t("sidebar.help")}
onOpenHelp={() => platform.openLink("https://opencode.ai/desktop-feedback")}
/>
</Show>
</nav>
</div>

<main
classList={{
"size-full overflow-x-hidden flex flex-col items-start contain-strict border-t border-border-weak-base": true,
"xl:border-l xl:rounded-tl-[12px]": !layout.sidebar.opened(),
"xl:border-l xl:rounded-tl-[12px]": !listMode() && !layout.sidebar.opened(),
}}
>
<Show when={!autoselecting()} fallback={<div class="size-full" />}>
Expand Down
Loading
Loading