fix(app): sidebar sync
This commit is contained in:
@@ -1,4 +1,16 @@
|
|||||||
import { batch, createEffect, createMemo, For, on, onCleanup, onMount, ParentProps, Show, untrack } from "solid-js"
|
import {
|
||||||
|
batch,
|
||||||
|
createEffect,
|
||||||
|
createMemo,
|
||||||
|
For,
|
||||||
|
on,
|
||||||
|
onCleanup,
|
||||||
|
onMount,
|
||||||
|
ParentProps,
|
||||||
|
Show,
|
||||||
|
untrack,
|
||||||
|
type Accessor,
|
||||||
|
} from "solid-js"
|
||||||
import { useNavigate, useParams } from "@solidjs/router"
|
import { useNavigate, useParams } from "@solidjs/router"
|
||||||
import { useLayout, LocalProject } from "@/context/layout"
|
import { useLayout, LocalProject } from "@/context/layout"
|
||||||
import { useGlobalSync } from "@/context/global-sync"
|
import { useGlobalSync } from "@/context/global-sync"
|
||||||
@@ -135,7 +147,7 @@ export default function Layout(props: ParentProps) {
|
|||||||
nav: undefined as HTMLElement | undefined,
|
nav: undefined as HTMLElement | undefined,
|
||||||
sortNow: Date.now(),
|
sortNow: Date.now(),
|
||||||
sizing: false,
|
sizing: false,
|
||||||
peek: undefined as LocalProject | undefined,
|
peek: undefined as string | undefined,
|
||||||
peeked: false,
|
peeked: false,
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -233,6 +245,12 @@ export default function Layout(props: ParentProps) {
|
|||||||
return layout.projects.list().find((project) => project.worktree === id)
|
return layout.projects.list().find((project) => project.worktree === id)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const peekProject = createMemo(() => {
|
||||||
|
const id = state.peek
|
||||||
|
if (!id) return
|
||||||
|
return layout.projects.list().find((project) => project.worktree === id)
|
||||||
|
})
|
||||||
|
|
||||||
createEffect(() => {
|
createEffect(() => {
|
||||||
const p = hoverProjectData()
|
const p = hoverProjectData()
|
||||||
if (p) {
|
if (p) {
|
||||||
@@ -240,7 +258,7 @@ export default function Layout(props: ParentProps) {
|
|||||||
clearTimeout(peekt)
|
clearTimeout(peekt)
|
||||||
peekt = undefined
|
peekt = undefined
|
||||||
}
|
}
|
||||||
setState("peek", p)
|
setState("peek", p.worktree)
|
||||||
setState("peeked", true)
|
setState("peeked", true)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -1932,17 +1950,32 @@ export default function Layout(props: ParentProps) {
|
|||||||
setHoverSession,
|
setHoverSession,
|
||||||
}
|
}
|
||||||
|
|
||||||
const SidebarPanel = (panelProps: { project: LocalProject | undefined; mobile?: boolean; merged?: boolean }) => {
|
const SidebarPanel = (panelProps: {
|
||||||
|
project: Accessor<LocalProject | undefined>
|
||||||
|
mobile?: boolean
|
||||||
|
merged?: boolean
|
||||||
|
}) => {
|
||||||
|
const project = panelProps.project
|
||||||
const merged = createMemo(() => panelProps.mobile || (panelProps.merged ?? layout.sidebar.opened()))
|
const merged = createMemo(() => panelProps.mobile || (panelProps.merged ?? layout.sidebar.opened()))
|
||||||
const hover = createMemo(() => !panelProps.mobile && panelProps.merged === false && !layout.sidebar.opened())
|
const hover = createMemo(() => !panelProps.mobile && panelProps.merged === false && !layout.sidebar.opened())
|
||||||
const popover = createMemo(() => !!panelProps.mobile || panelProps.merged === false || layout.sidebar.opened())
|
const popover = createMemo(() => !!panelProps.mobile || panelProps.merged === false || layout.sidebar.opened())
|
||||||
const projectName = createMemo(() => {
|
const projectName = createMemo(() => {
|
||||||
const project = panelProps.project
|
const item = project()
|
||||||
if (!project) return ""
|
if (!item) return ""
|
||||||
return project.name || getFilename(project.worktree)
|
return item.name || getFilename(item.worktree)
|
||||||
|
})
|
||||||
|
const projectId = createMemo(() => project()?.id ?? "")
|
||||||
|
const worktree = createMemo(() => project()?.worktree ?? "")
|
||||||
|
const slug = createMemo(() => {
|
||||||
|
const dir = worktree()
|
||||||
|
if (!dir) return ""
|
||||||
|
return base64Encode(dir)
|
||||||
|
})
|
||||||
|
const workspaces = createMemo(() => {
|
||||||
|
const item = project()
|
||||||
|
if (!item) return [] as string[]
|
||||||
|
return workspaceIds(item)
|
||||||
})
|
})
|
||||||
const projectId = createMemo(() => panelProps.project?.id ?? "")
|
|
||||||
const workspaces = createMemo(() => workspaceIds(panelProps.project))
|
|
||||||
const unseenCount = createMemo(() =>
|
const unseenCount = createMemo(() =>
|
||||||
workspaces().reduce((total, directory) => total + notification.project.unseenCount(directory), 0),
|
workspaces().reduce((total, directory) => total + notification.project.unseenCount(directory), 0),
|
||||||
)
|
)
|
||||||
@@ -1951,10 +1984,15 @@ export default function Layout(props: ParentProps) {
|
|||||||
.filter((directory) => notification.project.unseenCount(directory) > 0)
|
.filter((directory) => notification.project.unseenCount(directory) > 0)
|
||||||
.forEach((directory) => notification.project.markViewed(directory))
|
.forEach((directory) => notification.project.markViewed(directory))
|
||||||
const workspacesEnabled = createMemo(() => {
|
const workspacesEnabled = createMemo(() => {
|
||||||
const project = panelProps.project
|
const item = project()
|
||||||
if (!project) return false
|
if (!item) return false
|
||||||
if (project.vcs !== "git") return false
|
if (item.vcs !== "git") return false
|
||||||
return layout.sidebar.workspaces(project.worktree)()
|
return layout.sidebar.workspaces(item.worktree)()
|
||||||
|
})
|
||||||
|
const canToggle = createMemo(() => {
|
||||||
|
const item = project()
|
||||||
|
if (!item) return false
|
||||||
|
return item.vcs === "git" || layout.sidebar.workspaces(item.worktree)()
|
||||||
})
|
})
|
||||||
const homedir = createMemo(() => globalSync.data.path.home)
|
const homedir = createMemo(() => globalSync.data.path.home)
|
||||||
|
|
||||||
@@ -1973,8 +2011,7 @@ export default function Layout(props: ParentProps) {
|
|||||||
width: panelProps.mobile ? undefined : `${Math.max(Math.max(layout.sidebar.width(), 244) - 64, 0)}px`,
|
width: panelProps.mobile ? undefined : `${Math.max(Math.max(layout.sidebar.width(), 244) - 64, 0)}px`,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Show when={panelProps.project}>
|
<Show when={project()}>
|
||||||
{(p) => (
|
|
||||||
<>
|
<>
|
||||||
<div class="shrink-0 pl-1 py-1">
|
<div class="shrink-0 pl-1 py-1">
|
||||||
<div class="group/project flex items-start justify-between gap-2 py-2 pl-2 pr-0">
|
<div class="group/project flex items-start justify-between gap-2 py-2 pl-2 pr-0">
|
||||||
@@ -1982,7 +2019,11 @@ export default function Layout(props: ParentProps) {
|
|||||||
<InlineEditor
|
<InlineEditor
|
||||||
id={`project:${projectId()}`}
|
id={`project:${projectId()}`}
|
||||||
value={projectName}
|
value={projectName}
|
||||||
onSave={(next) => renameProject(p(), next)}
|
onSave={(next) => {
|
||||||
|
const item = project()
|
||||||
|
if (!item) return
|
||||||
|
renameProject(item, next)
|
||||||
|
}}
|
||||||
class="text-14-medium text-text-strong truncate"
|
class="text-14-medium text-text-strong truncate"
|
||||||
displayClass="text-14-medium text-text-strong truncate"
|
displayClass="text-14-medium text-text-strong truncate"
|
||||||
stopPropagation
|
stopPropagation
|
||||||
@@ -1991,7 +2032,7 @@ export default function Layout(props: ParentProps) {
|
|||||||
<Tooltip
|
<Tooltip
|
||||||
placement="bottom"
|
placement="bottom"
|
||||||
gutter={2}
|
gutter={2}
|
||||||
value={p().worktree}
|
value={worktree()}
|
||||||
class="shrink-0"
|
class="shrink-0"
|
||||||
contentStyle={{
|
contentStyle={{
|
||||||
"max-width": "640px",
|
"max-width": "640px",
|
||||||
@@ -1999,7 +2040,7 @@ export default function Layout(props: ParentProps) {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<span class="text-12-regular text-text-base truncate select-text">
|
<span class="text-12-regular text-text-base truncate select-text">
|
||||||
{p().worktree.replace(homedir(), "~")}
|
{worktree().replace(homedir(), "~")}
|
||||||
</span>
|
</span>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</div>
|
</div>
|
||||||
@@ -2010,7 +2051,7 @@ export default function Layout(props: ParentProps) {
|
|||||||
icon="dot-grid"
|
icon="dot-grid"
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
data-action="project-menu"
|
data-action="project-menu"
|
||||||
data-project={base64Encode(p().worktree)}
|
data-project={slug()}
|
||||||
class="shrink-0 size-6 rounded-md data-[expanded]:bg-surface-base-active"
|
class="shrink-0 size-6 rounded-md data-[expanded]:bg-surface-base-active"
|
||||||
classList={{
|
classList={{
|
||||||
"opacity-0 group-hover/project:opacity-100 data-[expanded]:opacity-100": !panelProps.mobile,
|
"opacity-0 group-hover/project:opacity-100 data-[expanded]:opacity-100": !panelProps.mobile,
|
||||||
@@ -2019,24 +2060,34 @@ export default function Layout(props: ParentProps) {
|
|||||||
/>
|
/>
|
||||||
<DropdownMenu.Portal>
|
<DropdownMenu.Portal>
|
||||||
<DropdownMenu.Content class="mt-1">
|
<DropdownMenu.Content class="mt-1">
|
||||||
<DropdownMenu.Item onSelect={() => showEditProjectDialog(p())}>
|
<DropdownMenu.Item
|
||||||
|
onSelect={() => {
|
||||||
|
const item = project()
|
||||||
|
if (!item) return
|
||||||
|
showEditProjectDialog(item)
|
||||||
|
}}
|
||||||
|
>
|
||||||
<DropdownMenu.ItemLabel>{language.t("common.edit")}</DropdownMenu.ItemLabel>
|
<DropdownMenu.ItemLabel>{language.t("common.edit")}</DropdownMenu.ItemLabel>
|
||||||
</DropdownMenu.Item>
|
</DropdownMenu.Item>
|
||||||
<DropdownMenu.Item
|
<DropdownMenu.Item
|
||||||
data-action="project-workspaces-toggle"
|
data-action="project-workspaces-toggle"
|
||||||
data-project={base64Encode(p().worktree)}
|
data-project={slug()}
|
||||||
disabled={p().vcs !== "git" && !layout.sidebar.workspaces(p().worktree)()}
|
disabled={!canToggle()}
|
||||||
onSelect={() => toggleProjectWorkspaces(p())}
|
onSelect={() => {
|
||||||
|
const item = project()
|
||||||
|
if (!item) return
|
||||||
|
toggleProjectWorkspaces(item)
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<DropdownMenu.ItemLabel>
|
<DropdownMenu.ItemLabel>
|
||||||
{layout.sidebar.workspaces(p().worktree)()
|
{workspacesEnabled()
|
||||||
? language.t("sidebar.workspaces.disable")
|
? language.t("sidebar.workspaces.disable")
|
||||||
: language.t("sidebar.workspaces.enable")}
|
: language.t("sidebar.workspaces.enable")}
|
||||||
</DropdownMenu.ItemLabel>
|
</DropdownMenu.ItemLabel>
|
||||||
</DropdownMenu.Item>
|
</DropdownMenu.Item>
|
||||||
<DropdownMenu.Item
|
<DropdownMenu.Item
|
||||||
data-action="project-clear-notifications"
|
data-action="project-clear-notifications"
|
||||||
data-project={base64Encode(p().worktree)}
|
data-project={slug()}
|
||||||
disabled={unseenCount() === 0}
|
disabled={unseenCount() === 0}
|
||||||
onSelect={clearNotifications}
|
onSelect={clearNotifications}
|
||||||
>
|
>
|
||||||
@@ -2047,8 +2098,12 @@ export default function Layout(props: ParentProps) {
|
|||||||
<DropdownMenu.Separator />
|
<DropdownMenu.Separator />
|
||||||
<DropdownMenu.Item
|
<DropdownMenu.Item
|
||||||
data-action="project-close-menu"
|
data-action="project-close-menu"
|
||||||
data-project={base64Encode(p().worktree)}
|
data-project={slug()}
|
||||||
onSelect={() => closeProject(p().worktree)}
|
onSelect={() => {
|
||||||
|
const dir = worktree()
|
||||||
|
if (!dir) return
|
||||||
|
closeProject(dir)
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<DropdownMenu.ItemLabel>{language.t("common.close")}</DropdownMenu.ItemLabel>
|
<DropdownMenu.ItemLabel>{language.t("common.close")}</DropdownMenu.ItemLabel>
|
||||||
</DropdownMenu.Item>
|
</DropdownMenu.Item>
|
||||||
@@ -2068,7 +2123,11 @@ export default function Layout(props: ParentProps) {
|
|||||||
size="large"
|
size="large"
|
||||||
icon="new-session"
|
icon="new-session"
|
||||||
class="w-full"
|
class="w-full"
|
||||||
onClick={() => navigateWithSidebarReset(`/${base64Encode(p().worktree)}/session`)}
|
onClick={() => {
|
||||||
|
const dir = worktree()
|
||||||
|
if (!dir) return
|
||||||
|
navigateWithSidebarReset(`/${base64Encode(dir)}/session`)
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
{language.t("command.session.new")}
|
{language.t("command.session.new")}
|
||||||
</Button>
|
</Button>
|
||||||
@@ -2076,7 +2135,7 @@ export default function Layout(props: ParentProps) {
|
|||||||
<div class="flex-1 min-h-0">
|
<div class="flex-1 min-h-0">
|
||||||
<LocalWorkspace
|
<LocalWorkspace
|
||||||
ctx={workspaceSidebarCtx}
|
ctx={workspaceSidebarCtx}
|
||||||
project={p()}
|
project={project()!}
|
||||||
sortNow={sortNow}
|
sortNow={sortNow}
|
||||||
mobile={panelProps.mobile}
|
mobile={panelProps.mobile}
|
||||||
popover={popover()}
|
popover={popover()}
|
||||||
@@ -2087,7 +2146,16 @@ export default function Layout(props: ParentProps) {
|
|||||||
>
|
>
|
||||||
<>
|
<>
|
||||||
<div class="shrink-0 py-4">
|
<div class="shrink-0 py-4">
|
||||||
<Button size="large" icon="plus-small" class="w-full" onClick={() => createWorkspace(p())}>
|
<Button
|
||||||
|
size="large"
|
||||||
|
icon="plus-small"
|
||||||
|
class="w-full"
|
||||||
|
onClick={() => {
|
||||||
|
const item = project()
|
||||||
|
if (!item) return
|
||||||
|
createWorkspace(item)
|
||||||
|
}}
|
||||||
|
>
|
||||||
{language.t("workspace.new")}
|
{language.t("workspace.new")}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
@@ -2112,7 +2180,7 @@ export default function Layout(props: ParentProps) {
|
|||||||
<SortableWorkspace
|
<SortableWorkspace
|
||||||
ctx={workspaceSidebarCtx}
|
ctx={workspaceSidebarCtx}
|
||||||
directory={directory}
|
directory={directory}
|
||||||
project={p()}
|
project={project()!}
|
||||||
sortNow={sortNow}
|
sortNow={sortNow}
|
||||||
mobile={panelProps.mobile}
|
mobile={panelProps.mobile}
|
||||||
popover={popover()}
|
popover={popover()}
|
||||||
@@ -2134,7 +2202,6 @@ export default function Layout(props: ParentProps) {
|
|||||||
</Show>
|
</Show>
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
)}
|
|
||||||
</Show>
|
</Show>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
@@ -2194,10 +2261,10 @@ export default function Layout(props: ParentProps) {
|
|||||||
onOpenHelp={() => platform.openLink("https://opencode.ai/desktop-feedback")}
|
onOpenHelp={() => platform.openLink("https://opencode.ai/desktop-feedback")}
|
||||||
renderPanel={() =>
|
renderPanel={() =>
|
||||||
mobile ? (
|
mobile ? (
|
||||||
<SidebarPanel project={currentProject()} mobile />
|
<SidebarPanel project={currentProject} mobile />
|
||||||
) : (
|
) : (
|
||||||
<Show when={currentProject()}>
|
<Show when={currentProject()}>
|
||||||
<SidebarPanel project={currentProject()} merged />
|
<SidebarPanel project={currentProject} merged />
|
||||||
</Show>
|
</Show>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -2325,8 +2392,8 @@ export default function Layout(props: ParentProps) {
|
|||||||
arm()
|
arm()
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Show when={state.peek}>
|
<Show when={peekProject()}>
|
||||||
<SidebarPanel project={state.peek} merged={false} />
|
<SidebarPanel project={peekProject} merged={false} />
|
||||||
</Show>
|
</Show>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user