|
@@ -135,6 +135,21 @@ export default function Layout(props: ParentProps) {
|
|
|
const editorRef = { current: undefined as HTMLInputElement | undefined }
|
|
const editorRef = { current: undefined as HTMLInputElement | undefined }
|
|
|
|
|
|
|
|
const [hoverSession, setHoverSession] = createSignal<string | undefined>()
|
|
const [hoverSession, setHoverSession] = createSignal<string | undefined>()
|
|
|
|
|
+ const [hoverProject, setHoverProject] = createSignal<string | undefined>()
|
|
|
|
|
+
|
|
|
|
|
+ const sidebarHovering = createMemo(() => !layout.sidebar.opened() && hoverProject() !== undefined)
|
|
|
|
|
+ const sidebarExpanded = createMemo(() => layout.sidebar.opened() || sidebarHovering())
|
|
|
|
|
+
|
|
|
|
|
+ const hoverProjectData = createMemo(() => {
|
|
|
|
|
+ const id = hoverProject()
|
|
|
|
|
+ if (!id) return
|
|
|
|
|
+ return layout.projects.list().find((project) => project.worktree === id)
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ createEffect(() => {
|
|
|
|
|
+ if (!layout.sidebar.opened()) return
|
|
|
|
|
+ setHoverProject(undefined)
|
|
|
|
|
+ })
|
|
|
|
|
|
|
|
const autoselecting = createMemo(() => {
|
|
const autoselecting = createMemo(() => {
|
|
|
if (params.dir) return false
|
|
if (params.dir) return false
|
|
@@ -1119,15 +1134,13 @@ export default function Layout(props: ParentProps) {
|
|
|
return language.t("common.requestFailed")
|
|
return language.t("common.requestFailed")
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- const deleteWorkspace = async (directory: string) => {
|
|
|
|
|
- const current = currentProject()
|
|
|
|
|
- if (!current) return
|
|
|
|
|
- if (directory === current.worktree) return
|
|
|
|
|
|
|
+ const deleteWorkspace = async (root: string, directory: string) => {
|
|
|
|
|
+ if (directory === root) return
|
|
|
|
|
|
|
|
setBusy(directory, true)
|
|
setBusy(directory, true)
|
|
|
|
|
|
|
|
const result = await globalSDK.client.worktree
|
|
const result = await globalSDK.client.worktree
|
|
|
- .remove({ directory: current.worktree, worktreeRemoveInput: { directory } })
|
|
|
|
|
|
|
+ .remove({ directory: root, worktreeRemoveInput: { directory } })
|
|
|
.then((x) => x.data)
|
|
.then((x) => x.data)
|
|
|
.catch((err) => {
|
|
.catch((err) => {
|
|
|
showToast({
|
|
showToast({
|
|
@@ -1142,17 +1155,15 @@ export default function Layout(props: ParentProps) {
|
|
|
if (!result) return
|
|
if (!result) return
|
|
|
|
|
|
|
|
layout.projects.close(directory)
|
|
layout.projects.close(directory)
|
|
|
- layout.projects.open(current.worktree)
|
|
|
|
|
|
|
+ layout.projects.open(root)
|
|
|
|
|
|
|
|
if (params.dir && base64Decode(params.dir) === directory) {
|
|
if (params.dir && base64Decode(params.dir) === directory) {
|
|
|
- navigateToProject(current.worktree)
|
|
|
|
|
|
|
+ navigateToProject(root)
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- const resetWorkspace = async (directory: string) => {
|
|
|
|
|
- const current = currentProject()
|
|
|
|
|
- if (!current) return
|
|
|
|
|
- if (directory === current.worktree) return
|
|
|
|
|
|
|
+ const resetWorkspace = async (root: string, directory: string) => {
|
|
|
|
|
+ if (directory === root) return
|
|
|
setBusy(directory, true)
|
|
setBusy(directory, true)
|
|
|
|
|
|
|
|
const progress = showToast({
|
|
const progress = showToast({
|
|
@@ -1168,7 +1179,7 @@ export default function Layout(props: ParentProps) {
|
|
|
.catch(() => [])
|
|
.catch(() => [])
|
|
|
|
|
|
|
|
const result = await globalSDK.client.worktree
|
|
const result = await globalSDK.client.worktree
|
|
|
- .reset({ directory: current.worktree, worktreeResetInput: { directory } })
|
|
|
|
|
|
|
+ .reset({ directory: root, worktreeResetInput: { directory } })
|
|
|
.then((x) => x.data)
|
|
.then((x) => x.data)
|
|
|
.catch((err) => {
|
|
.catch((err) => {
|
|
|
showToast({
|
|
showToast({
|
|
@@ -1251,7 +1262,7 @@ export default function Layout(props: ParentProps) {
|
|
|
)
|
|
)
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- function DialogDeleteWorkspace(props: { directory: string }) {
|
|
|
|
|
|
|
+ function DialogDeleteWorkspace(props: { root: string; directory: string }) {
|
|
|
const name = createMemo(() => getFilename(props.directory))
|
|
const name = createMemo(() => getFilename(props.directory))
|
|
|
const [data, setData] = createStore({
|
|
const [data, setData] = createStore({
|
|
|
status: "loading" as "loading" | "ready" | "error",
|
|
status: "loading" as "loading" | "ready" | "error",
|
|
@@ -1259,12 +1270,6 @@ export default function Layout(props: ParentProps) {
|
|
|
})
|
|
})
|
|
|
|
|
|
|
|
onMount(() => {
|
|
onMount(() => {
|
|
|
- const current = currentProject()
|
|
|
|
|
- if (!current) {
|
|
|
|
|
- setData({ status: "error", dirty: false })
|
|
|
|
|
- return
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
globalSDK.client.file
|
|
globalSDK.client.file
|
|
|
.status({ directory: props.directory })
|
|
.status({ directory: props.directory })
|
|
|
.then((x) => {
|
|
.then((x) => {
|
|
@@ -1279,7 +1284,7 @@ export default function Layout(props: ParentProps) {
|
|
|
|
|
|
|
|
const handleDelete = () => {
|
|
const handleDelete = () => {
|
|
|
dialog.close()
|
|
dialog.close()
|
|
|
- void deleteWorkspace(props.directory)
|
|
|
|
|
|
|
+ void deleteWorkspace(props.root, props.directory)
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
const description = () => {
|
|
const description = () => {
|
|
@@ -1311,7 +1316,7 @@ export default function Layout(props: ParentProps) {
|
|
|
)
|
|
)
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- function DialogResetWorkspace(props: { directory: string }) {
|
|
|
|
|
|
|
+ function DialogResetWorkspace(props: { root: string; directory: string }) {
|
|
|
const name = createMemo(() => getFilename(props.directory))
|
|
const name = createMemo(() => getFilename(props.directory))
|
|
|
const [state, setState] = createStore({
|
|
const [state, setState] = createStore({
|
|
|
status: "loading" as "loading" | "ready" | "error",
|
|
status: "loading" as "loading" | "ready" | "error",
|
|
@@ -1329,12 +1334,6 @@ export default function Layout(props: ParentProps) {
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
onMount(() => {
|
|
onMount(() => {
|
|
|
- const current = currentProject()
|
|
|
|
|
- if (!current) {
|
|
|
|
|
- setState({ status: "error", dirty: false })
|
|
|
|
|
- return
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
globalSDK.client.file
|
|
globalSDK.client.file
|
|
|
.status({ directory: props.directory })
|
|
.status({ directory: props.directory })
|
|
|
.then((x) => {
|
|
.then((x) => {
|
|
@@ -1350,7 +1349,7 @@ export default function Layout(props: ParentProps) {
|
|
|
|
|
|
|
|
const handleReset = () => {
|
|
const handleReset = () => {
|
|
|
dialog.close()
|
|
dialog.close()
|
|
|
- void resetWorkspace(props.directory)
|
|
|
|
|
|
|
+ void resetWorkspace(props.root, props.directory)
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
const archivedCount = () => state.sessions.length
|
|
const archivedCount = () => state.sessions.length
|
|
@@ -1444,6 +1443,7 @@ export default function Layout(props: ParentProps) {
|
|
|
function handleDragStart(event: unknown) {
|
|
function handleDragStart(event: unknown) {
|
|
|
const id = getDraggableId(event)
|
|
const id = getDraggableId(event)
|
|
|
if (!id) return
|
|
if (!id) return
|
|
|
|
|
+ setHoverProject(undefined)
|
|
|
setStore("activeProject", id)
|
|
setStore("activeProject", id)
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -1483,6 +1483,13 @@ export default function Layout(props: ParentProps) {
|
|
|
return [...merged, extra]
|
|
return [...merged, extra]
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ const sidebarProject = createMemo(() => {
|
|
|
|
|
+ if (layout.sidebar.opened()) return currentProject()
|
|
|
|
|
+ const hovered = hoverProjectData()
|
|
|
|
|
+ if (hovered) return hovered
|
|
|
|
|
+ return currentProject()
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
function handleWorkspaceDragStart(event: unknown) {
|
|
function handleWorkspaceDragStart(event: unknown) {
|
|
|
const id = getDraggableId(event)
|
|
const id = getDraggableId(event)
|
|
|
if (!id) return
|
|
if (!id) return
|
|
@@ -1493,7 +1500,7 @@ export default function Layout(props: ParentProps) {
|
|
|
const { draggable, droppable } = event
|
|
const { draggable, droppable } = event
|
|
|
if (!draggable || !droppable) return
|
|
if (!draggable || !droppable) return
|
|
|
|
|
|
|
|
- const project = currentProject()
|
|
|
|
|
|
|
+ const project = sidebarProject()
|
|
|
if (!project) return
|
|
if (!project) return
|
|
|
|
|
|
|
|
const ids = workspaceIds(project)
|
|
const ids = workspaceIds(project)
|
|
@@ -1593,7 +1600,7 @@ export default function Layout(props: ParentProps) {
|
|
|
sessionStore.message[props.session.id]?.filter((message) => message.role === "user"),
|
|
sessionStore.message[props.session.id]?.filter((message) => message.role === "user"),
|
|
|
)
|
|
)
|
|
|
const hoverReady = createMemo(() => sessionStore.message[props.session.id] !== undefined)
|
|
const hoverReady = createMemo(() => sessionStore.message[props.session.id] !== undefined)
|
|
|
- const hoverAllowed = createMemo(() => !props.mobile && layout.sidebar.opened())
|
|
|
|
|
|
|
+ const hoverAllowed = createMemo(() => !props.mobile && sidebarExpanded())
|
|
|
const hoverEnabled = createMemo(() => (props.popover ?? true) && hoverAllowed())
|
|
const hoverEnabled = createMemo(() => (props.popover ?? true) && hoverAllowed())
|
|
|
const isActive = createMemo(() => props.session.id === params.id)
|
|
const isActive = createMemo(() => props.session.id === params.id)
|
|
|
const [menuOpen, setMenuOpen] = createSignal(false)
|
|
const [menuOpen, setMenuOpen] = createSignal(false)
|
|
@@ -1611,7 +1618,11 @@ export default function Layout(props: ParentProps) {
|
|
|
class={`flex items-center justify-between gap-3 min-w-0 text-left w-full focus:outline-none transition-[padding] ${menuOpen() ? "pr-7" : ""} group-hover/session:pr-7 group-focus-within/session:pr-7 group-active/session:pr-7 ${props.dense ? "py-0.5" : "py-1"}`}
|
|
class={`flex items-center justify-between gap-3 min-w-0 text-left w-full focus:outline-none transition-[padding] ${menuOpen() ? "pr-7" : ""} group-hover/session:pr-7 group-focus-within/session:pr-7 group-active/session:pr-7 ${props.dense ? "py-0.5" : "py-1"}`}
|
|
|
onMouseEnter={() => prefetchSession(props.session, "high")}
|
|
onMouseEnter={() => prefetchSession(props.session, "high")}
|
|
|
onFocus={() => prefetchSession(props.session, "high")}
|
|
onFocus={() => prefetchSession(props.session, "high")}
|
|
|
- onClick={() => setHoverSession(undefined)}
|
|
|
|
|
|
|
+ onClick={() => {
|
|
|
|
|
+ setHoverSession(undefined)
|
|
|
|
|
+ if (layout.sidebar.opened()) return
|
|
|
|
|
+ queueMicrotask(() => setHoverProject(undefined))
|
|
|
|
|
+ }}
|
|
|
>
|
|
>
|
|
|
<div class="flex items-center gap-1 w-full">
|
|
<div class="flex items-center gap-1 w-full">
|
|
|
<div
|
|
<div
|
|
@@ -1753,13 +1764,17 @@ export default function Layout(props: ParentProps) {
|
|
|
|
|
|
|
|
const NewSessionItem = (props: { slug: string; mobile?: boolean; dense?: boolean }): JSX.Element => {
|
|
const NewSessionItem = (props: { slug: string; mobile?: boolean; dense?: boolean }): JSX.Element => {
|
|
|
const label = language.t("command.session.new")
|
|
const label = language.t("command.session.new")
|
|
|
- const tooltip = () => props.mobile || !layout.sidebar.opened()
|
|
|
|
|
|
|
+ const tooltip = () => props.mobile || !sidebarExpanded()
|
|
|
const item = (
|
|
const item = (
|
|
|
<A
|
|
<A
|
|
|
href={`${props.slug}/session`}
|
|
href={`${props.slug}/session`}
|
|
|
end
|
|
end
|
|
|
class={`flex items-center justify-between gap-3 min-w-0 text-left w-full focus:outline-none ${props.dense ? "py-0.5" : "py-1"}`}
|
|
class={`flex items-center justify-between gap-3 min-w-0 text-left w-full focus:outline-none ${props.dense ? "py-0.5" : "py-1"}`}
|
|
|
- onClick={() => setHoverSession(undefined)}
|
|
|
|
|
|
|
+ onClick={() => {
|
|
|
|
|
+ setHoverSession(undefined)
|
|
|
|
|
+ if (layout.sidebar.opened()) return
|
|
|
|
|
+ queueMicrotask(() => setHoverProject(undefined))
|
|
|
|
|
+ }}
|
|
|
>
|
|
>
|
|
|
<div class="flex items-center gap-1 w-full">
|
|
<div class="flex items-center gap-1 w-full">
|
|
|
<div class="shrink-0 size-6 flex items-center justify-center">
|
|
<div class="shrink-0 size-6 flex items-center justify-center">
|
|
@@ -1814,7 +1829,7 @@ export default function Layout(props: ParentProps) {
|
|
|
|
|
|
|
|
const WorkspaceDragOverlay = (): JSX.Element => {
|
|
const WorkspaceDragOverlay = (): JSX.Element => {
|
|
|
const label = createMemo(() => {
|
|
const label = createMemo(() => {
|
|
|
- const project = currentProject()
|
|
|
|
|
|
|
+ const project = sidebarProject()
|
|
|
if (!project) return
|
|
if (!project) return
|
|
|
const directory = store.activeWorkspace
|
|
const directory = store.activeWorkspace
|
|
|
if (!directory) return
|
|
if (!directory) return
|
|
@@ -1985,13 +2000,21 @@ export default function Layout(props: ParentProps) {
|
|
|
</DropdownMenu.Item>
|
|
</DropdownMenu.Item>
|
|
|
<DropdownMenu.Item
|
|
<DropdownMenu.Item
|
|
|
disabled={local() || busy()}
|
|
disabled={local() || busy()}
|
|
|
- onSelect={() => dialog.show(() => <DialogResetWorkspace directory={props.directory} />)}
|
|
|
|
|
|
|
+ onSelect={() =>
|
|
|
|
|
+ dialog.show(() => (
|
|
|
|
|
+ <DialogResetWorkspace root={props.project.worktree} directory={props.directory} />
|
|
|
|
|
+ ))
|
|
|
|
|
+ }
|
|
|
>
|
|
>
|
|
|
<DropdownMenu.ItemLabel>{language.t("common.reset")}</DropdownMenu.ItemLabel>
|
|
<DropdownMenu.ItemLabel>{language.t("common.reset")}</DropdownMenu.ItemLabel>
|
|
|
</DropdownMenu.Item>
|
|
</DropdownMenu.Item>
|
|
|
<DropdownMenu.Item
|
|
<DropdownMenu.Item
|
|
|
disabled={local() || busy()}
|
|
disabled={local() || busy()}
|
|
|
- onSelect={() => dialog.show(() => <DialogDeleteWorkspace directory={props.directory} />)}
|
|
|
|
|
|
|
+ onSelect={() =>
|
|
|
|
|
+ dialog.show(() => (
|
|
|
|
|
+ <DialogDeleteWorkspace root={props.project.worktree} directory={props.directory} />
|
|
|
|
|
+ ))
|
|
|
|
|
+ }
|
|
|
>
|
|
>
|
|
|
<DropdownMenu.ItemLabel>{language.t("common.delete")}</DropdownMenu.ItemLabel>
|
|
<DropdownMenu.ItemLabel>{language.t("common.delete")}</DropdownMenu.ItemLabel>
|
|
|
</DropdownMenu.Item>
|
|
</DropdownMenu.Item>
|
|
@@ -2005,9 +2028,7 @@ export default function Layout(props: ParentProps) {
|
|
|
|
|
|
|
|
<Collapsible.Content>
|
|
<Collapsible.Content>
|
|
|
<nav class="flex flex-col gap-1 px-2">
|
|
<nav class="flex flex-col gap-1 px-2">
|
|
|
- <Show when={workspaceSetting()}>
|
|
|
|
|
- <NewSessionItem slug={slug()} mobile={props.mobile} />
|
|
|
|
|
- </Show>
|
|
|
|
|
|
|
+ <NewSessionItem slug={slug()} mobile={props.mobile} />
|
|
|
<Show when={loading()}>
|
|
<Show when={loading()}>
|
|
|
<SessionSkeleton />
|
|
<SessionSkeleton />
|
|
|
</Show>
|
|
</Show>
|
|
@@ -2049,6 +2070,16 @@ export default function Layout(props: ParentProps) {
|
|
|
)
|
|
)
|
|
|
const [open, setOpen] = createSignal(false)
|
|
const [open, setOpen] = createSignal(false)
|
|
|
|
|
|
|
|
|
|
+ const preview = createMemo(() => !props.mobile && layout.sidebar.opened())
|
|
|
|
|
+ const overlay = createMemo(() => !props.mobile && !layout.sidebar.opened())
|
|
|
|
|
+ const active = createMemo(() => (preview() ? open() : overlay() && hoverProject() === props.project.worktree))
|
|
|
|
|
+
|
|
|
|
|
+ createEffect(() => {
|
|
|
|
|
+ if (preview()) return
|
|
|
|
|
+ if (!open()) return
|
|
|
|
|
+ setOpen(false)
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
const label = (directory: string) => {
|
|
const label = (directory: string) => {
|
|
|
const [data] = globalSync.child(directory, { bootstrap: false })
|
|
const [data] = globalSync.child(directory, { bootstrap: false })
|
|
|
const kind =
|
|
const kind =
|
|
@@ -2087,8 +2118,20 @@ export default function Layout(props: ParentProps) {
|
|
|
"flex items-center justify-center size-10 p-1 rounded-lg overflow-hidden transition-colors cursor-default": true,
|
|
"flex items-center justify-center size-10 p-1 rounded-lg overflow-hidden transition-colors cursor-default": true,
|
|
|
"bg-transparent border-2 border-icon-strong-base hover:bg-surface-base-hover": selected(),
|
|
"bg-transparent border-2 border-icon-strong-base hover:bg-surface-base-hover": selected(),
|
|
|
"bg-transparent border border-transparent hover:bg-surface-base-hover hover:border-border-weak-base":
|
|
"bg-transparent border border-transparent hover:bg-surface-base-hover hover:border-border-weak-base":
|
|
|
- !selected() && !open(),
|
|
|
|
|
- "bg-surface-base-hover border border-border-weak-base": !selected() && open(),
|
|
|
|
|
|
|
+ !selected() && !active(),
|
|
|
|
|
+ "bg-surface-base-hover border border-border-weak-base": !selected() && active(),
|
|
|
|
|
+ }}
|
|
|
|
|
+ onMouseEnter={() => {
|
|
|
|
|
+ if (!overlay()) return
|
|
|
|
|
+ globalSync.child(props.project.worktree)
|
|
|
|
|
+ setHoverProject(props.project.worktree)
|
|
|
|
|
+ setHoverSession(undefined)
|
|
|
|
|
+ }}
|
|
|
|
|
+ onFocus={() => {
|
|
|
|
|
+ if (!overlay()) return
|
|
|
|
|
+ globalSync.child(props.project.worktree)
|
|
|
|
|
+ setHoverProject(props.project.worktree)
|
|
|
|
|
+ setHoverSession(undefined)
|
|
|
}}
|
|
}}
|
|
|
onClick={() => navigateToProject(props.project.worktree)}
|
|
onClick={() => navigateToProject(props.project.worktree)}
|
|
|
onBlur={() => setOpen(false)}
|
|
onBlur={() => setOpen(false)}
|
|
@@ -2100,96 +2143,98 @@ export default function Layout(props: ParentProps) {
|
|
|
return (
|
|
return (
|
|
|
// @ts-ignore
|
|
// @ts-ignore
|
|
|
<div use:sortable classList={{ "opacity-30": sortable.isActiveDraggable }}>
|
|
<div use:sortable classList={{ "opacity-30": sortable.isActiveDraggable }}>
|
|
|
- <HoverCard
|
|
|
|
|
- open={open()}
|
|
|
|
|
- openDelay={0}
|
|
|
|
|
- closeDelay={0}
|
|
|
|
|
- placement="right-start"
|
|
|
|
|
- gutter={6}
|
|
|
|
|
- trigger={trigger}
|
|
|
|
|
- onOpenChange={(value) => {
|
|
|
|
|
- setOpen(value)
|
|
|
|
|
- if (value) setHoverSession(undefined)
|
|
|
|
|
- }}
|
|
|
|
|
- >
|
|
|
|
|
- <div class="-m-3 p-2 flex flex-col w-72">
|
|
|
|
|
- <div class="px-4 pt-2 pb-1 flex items-center gap-2">
|
|
|
|
|
- <div class="text-14-medium text-text-strong truncate grow">{displayName(props.project)}</div>
|
|
|
|
|
- <Tooltip value={language.t("common.close")} placement="top" gutter={6}>
|
|
|
|
|
- <IconButton
|
|
|
|
|
- icon="circle-x"
|
|
|
|
|
|
|
+ <Show when={preview()} fallback={trigger}>
|
|
|
|
|
+ <HoverCard
|
|
|
|
|
+ open={open()}
|
|
|
|
|
+ openDelay={0}
|
|
|
|
|
+ closeDelay={0}
|
|
|
|
|
+ placement="right-start"
|
|
|
|
|
+ gutter={6}
|
|
|
|
|
+ trigger={trigger}
|
|
|
|
|
+ onOpenChange={(value) => {
|
|
|
|
|
+ setOpen(value)
|
|
|
|
|
+ if (value) setHoverSession(undefined)
|
|
|
|
|
+ }}
|
|
|
|
|
+ >
|
|
|
|
|
+ <div class="-m-3 p-2 flex flex-col w-72">
|
|
|
|
|
+ <div class="px-4 pt-2 pb-1 flex items-center gap-2">
|
|
|
|
|
+ <div class="text-14-medium text-text-strong truncate grow">{displayName(props.project)}</div>
|
|
|
|
|
+ <Tooltip value={language.t("common.close")} placement="top" gutter={6}>
|
|
|
|
|
+ <IconButton
|
|
|
|
|
+ icon="circle-x"
|
|
|
|
|
+ variant="ghost"
|
|
|
|
|
+ class="shrink-0"
|
|
|
|
|
+ aria-label={language.t("common.close")}
|
|
|
|
|
+ onClick={(event) => {
|
|
|
|
|
+ event.stopPropagation()
|
|
|
|
|
+ setOpen(false)
|
|
|
|
|
+ closeProject(props.project.worktree)
|
|
|
|
|
+ }}
|
|
|
|
|
+ />
|
|
|
|
|
+ </Tooltip>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div class="px-4 pb-2 text-12-medium text-text-weak">{language.t("sidebar.project.recentSessions")}</div>
|
|
|
|
|
+ <div class="px-2 pb-2 flex flex-col gap-2">
|
|
|
|
|
+ <Show
|
|
|
|
|
+ when={workspaceEnabled()}
|
|
|
|
|
+ fallback={
|
|
|
|
|
+ <For each={projectSessions()}>
|
|
|
|
|
+ {(session) => (
|
|
|
|
|
+ <SessionItem
|
|
|
|
|
+ session={session}
|
|
|
|
|
+ slug={base64Encode(props.project.worktree)}
|
|
|
|
|
+ dense
|
|
|
|
|
+ mobile={props.mobile}
|
|
|
|
|
+ popover={false}
|
|
|
|
|
+ />
|
|
|
|
|
+ )}
|
|
|
|
|
+ </For>
|
|
|
|
|
+ }
|
|
|
|
|
+ >
|
|
|
|
|
+ <For each={workspaces()}>
|
|
|
|
|
+ {(directory) => (
|
|
|
|
|
+ <div class="flex flex-col gap-1">
|
|
|
|
|
+ <div class="px-2 py-0.5 flex items-center gap-1 min-w-0">
|
|
|
|
|
+ <div class="shrink-0 size-6 flex items-center justify-center">
|
|
|
|
|
+ <Icon name="branch" size="small" class="text-icon-base" />
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <span class="truncate text-14-medium text-text-base">{label(directory)}</span>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <For each={sessions(directory)}>
|
|
|
|
|
+ {(session) => (
|
|
|
|
|
+ <SessionItem
|
|
|
|
|
+ session={session}
|
|
|
|
|
+ slug={base64Encode(directory)}
|
|
|
|
|
+ dense
|
|
|
|
|
+ mobile={props.mobile}
|
|
|
|
|
+ popover={false}
|
|
|
|
|
+ />
|
|
|
|
|
+ )}
|
|
|
|
|
+ </For>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ )}
|
|
|
|
|
+ </For>
|
|
|
|
|
+ </Show>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div class="px-2 py-2 border-t border-border-weak-base">
|
|
|
|
|
+ <Button
|
|
|
variant="ghost"
|
|
variant="ghost"
|
|
|
- class="shrink-0"
|
|
|
|
|
- aria-label={language.t("common.close")}
|
|
|
|
|
- onClick={(event) => {
|
|
|
|
|
- event.stopPropagation()
|
|
|
|
|
|
|
+ class="flex w-full text-left justify-start text-text-base px-2 hover:bg-transparent active:bg-transparent"
|
|
|
|
|
+ onClick={() => {
|
|
|
|
|
+ layout.sidebar.open()
|
|
|
setOpen(false)
|
|
setOpen(false)
|
|
|
- closeProject(props.project.worktree)
|
|
|
|
|
|
|
+ if (selected()) {
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
+ navigateToProject(props.project.worktree)
|
|
|
}}
|
|
}}
|
|
|
- />
|
|
|
|
|
- </Tooltip>
|
|
|
|
|
- </div>
|
|
|
|
|
- <div class="px-4 pb-2 text-12-medium text-text-weak">{language.t("sidebar.project.recentSessions")}</div>
|
|
|
|
|
- <div class="px-2 pb-2 flex flex-col gap-2">
|
|
|
|
|
- <Show
|
|
|
|
|
- when={workspaceEnabled()}
|
|
|
|
|
- fallback={
|
|
|
|
|
- <For each={projectSessions()}>
|
|
|
|
|
- {(session) => (
|
|
|
|
|
- <SessionItem
|
|
|
|
|
- session={session}
|
|
|
|
|
- slug={base64Encode(props.project.worktree)}
|
|
|
|
|
- dense
|
|
|
|
|
- mobile={props.mobile}
|
|
|
|
|
- popover={false}
|
|
|
|
|
- />
|
|
|
|
|
- )}
|
|
|
|
|
- </For>
|
|
|
|
|
- }
|
|
|
|
|
- >
|
|
|
|
|
- <For each={workspaces()}>
|
|
|
|
|
- {(directory) => (
|
|
|
|
|
- <div class="flex flex-col gap-1">
|
|
|
|
|
- <div class="px-2 py-0.5 flex items-center gap-1 min-w-0">
|
|
|
|
|
- <div class="shrink-0 size-6 flex items-center justify-center">
|
|
|
|
|
- <Icon name="branch" size="small" class="text-icon-base" />
|
|
|
|
|
- </div>
|
|
|
|
|
- <span class="truncate text-14-medium text-text-base">{label(directory)}</span>
|
|
|
|
|
- </div>
|
|
|
|
|
- <For each={sessions(directory)}>
|
|
|
|
|
- {(session) => (
|
|
|
|
|
- <SessionItem
|
|
|
|
|
- session={session}
|
|
|
|
|
- slug={base64Encode(directory)}
|
|
|
|
|
- dense
|
|
|
|
|
- mobile={props.mobile}
|
|
|
|
|
- popover={false}
|
|
|
|
|
- />
|
|
|
|
|
- )}
|
|
|
|
|
- </For>
|
|
|
|
|
- </div>
|
|
|
|
|
- )}
|
|
|
|
|
- </For>
|
|
|
|
|
- </Show>
|
|
|
|
|
- </div>
|
|
|
|
|
- <div class="px-2 py-2 border-t border-border-weak-base">
|
|
|
|
|
- <Button
|
|
|
|
|
- variant="ghost"
|
|
|
|
|
- class="flex w-full text-left justify-start text-text-base px-2 hover:bg-transparent active:bg-transparent"
|
|
|
|
|
- onClick={() => {
|
|
|
|
|
- layout.sidebar.open()
|
|
|
|
|
- setOpen(false)
|
|
|
|
|
- if (selected()) {
|
|
|
|
|
- return
|
|
|
|
|
- }
|
|
|
|
|
- navigateToProject(props.project.worktree)
|
|
|
|
|
- }}
|
|
|
|
|
- >
|
|
|
|
|
- {language.t("sidebar.project.viewAllSessions")}
|
|
|
|
|
- </Button>
|
|
|
|
|
|
|
+ >
|
|
|
|
|
+ {language.t("sidebar.project.viewAllSessions")}
|
|
|
|
|
+ </Button>
|
|
|
|
|
+ </div>
|
|
|
</div>
|
|
</div>
|
|
|
- </div>
|
|
|
|
|
- </HoverCard>
|
|
|
|
|
|
|
+ </HoverCard>
|
|
|
|
|
+ </Show>
|
|
|
</div>
|
|
</div>
|
|
|
)
|
|
)
|
|
|
}
|
|
}
|
|
@@ -2219,9 +2264,6 @@ export default function Layout(props: ParentProps) {
|
|
|
style={{ "overflow-anchor": "none" }}
|
|
style={{ "overflow-anchor": "none" }}
|
|
|
>
|
|
>
|
|
|
<nav class="flex flex-col gap-1 px-2">
|
|
<nav class="flex flex-col gap-1 px-2">
|
|
|
- <Show when={workspaceSetting()}>
|
|
|
|
|
- <NewSessionItem slug={slug()} mobile={props.mobile} />
|
|
|
|
|
- </Show>
|
|
|
|
|
<Show when={loading()}>
|
|
<Show when={loading()}>
|
|
|
<SessionSkeleton />
|
|
<SessionSkeleton />
|
|
|
</Show>
|
|
</Show>
|
|
@@ -2248,60 +2290,241 @@ export default function Layout(props: ParentProps) {
|
|
|
)
|
|
)
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- const SidebarContent = (sidebarProps: { mobile?: boolean }) => {
|
|
|
|
|
- const expanded = () => sidebarProps.mobile || layout.sidebar.opened()
|
|
|
|
|
|
|
+ const createWorkspace = async (project: LocalProject) => {
|
|
|
|
|
+ const created = await globalSDK.client.worktree
|
|
|
|
|
+ .create({ directory: project.worktree })
|
|
|
|
|
+ .then((x) => x.data)
|
|
|
|
|
+ .catch((err) => {
|
|
|
|
|
+ showToast({
|
|
|
|
|
+ title: language.t("workspace.create.failed.title"),
|
|
|
|
|
+ description: errorMessage(err),
|
|
|
|
|
+ })
|
|
|
|
|
+ return undefined
|
|
|
|
|
+ })
|
|
|
|
|
|
|
|
- const sync = useGlobalSync()
|
|
|
|
|
- const project = createMemo(() => currentProject())
|
|
|
|
|
|
|
+ if (!created?.directory) return
|
|
|
|
|
+
|
|
|
|
|
+ const local = project.worktree
|
|
|
|
|
+ const key = workspaceKey(created.directory)
|
|
|
|
|
+ const root = workspaceKey(local)
|
|
|
|
|
+
|
|
|
|
|
+ setBusy(created.directory, true)
|
|
|
|
|
+ WorktreeState.pending(created.directory)
|
|
|
|
|
+ setStore("workspaceExpanded", key, true)
|
|
|
|
|
+ if (key !== created.directory) {
|
|
|
|
|
+ setStore("workspaceExpanded", created.directory, true)
|
|
|
|
|
+ }
|
|
|
|
|
+ setStore("workspaceOrder", project.worktree, (prev) => {
|
|
|
|
|
+ const existing = prev ?? []
|
|
|
|
|
+ const next = existing.filter((item) => {
|
|
|
|
|
+ const id = workspaceKey(item)
|
|
|
|
|
+ if (id === root) return false
|
|
|
|
|
+ return id !== key
|
|
|
|
|
+ })
|
|
|
|
|
+ return [local, created.directory, ...next]
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ globalSync.child(created.directory)
|
|
|
|
|
+ navigate(`/${base64Encode(created.directory)}/session`)
|
|
|
|
|
+ layout.mobileSidebar.hide()
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ const SidebarPanel = (panelProps: { project: LocalProject | undefined; mobile?: boolean }) => {
|
|
|
const projectName = createMemo(() => {
|
|
const projectName = createMemo(() => {
|
|
|
- const current = project()
|
|
|
|
|
- if (!current) return ""
|
|
|
|
|
- return current.name || getFilename(current.worktree)
|
|
|
|
|
|
|
+ const project = panelProps.project
|
|
|
|
|
+ if (!project) return ""
|
|
|
|
|
+ return project.name || getFilename(project.worktree)
|
|
|
})
|
|
})
|
|
|
- const projectId = createMemo(() => project()?.id ?? "")
|
|
|
|
|
- const workspaces = createMemo(() => workspaceIds(project()))
|
|
|
|
|
-
|
|
|
|
|
- const createWorkspace = async () => {
|
|
|
|
|
- const current = project()
|
|
|
|
|
- if (!current) return
|
|
|
|
|
-
|
|
|
|
|
- const created = await globalSDK.client.worktree
|
|
|
|
|
- .create({ directory: current.worktree })
|
|
|
|
|
- .then((x) => x.data)
|
|
|
|
|
- .catch((err) => {
|
|
|
|
|
- showToast({
|
|
|
|
|
- title: language.t("workspace.create.failed.title"),
|
|
|
|
|
- description: errorMessage(err),
|
|
|
|
|
- })
|
|
|
|
|
- return undefined
|
|
|
|
|
- })
|
|
|
|
|
|
|
+ const projectId = createMemo(() => panelProps.project?.id ?? "")
|
|
|
|
|
+ const workspaces = createMemo(() => workspaceIds(panelProps.project))
|
|
|
|
|
+ const workspacesEnabled = createMemo(() => {
|
|
|
|
|
+ const project = panelProps.project
|
|
|
|
|
+ if (!project) return false
|
|
|
|
|
+ if (project.vcs !== "git") return false
|
|
|
|
|
+ return layout.sidebar.workspaces(project.worktree)()
|
|
|
|
|
+ })
|
|
|
|
|
+ const homedir = createMemo(() => globalSync.data.path.home)
|
|
|
|
|
|
|
|
- if (!created?.directory) return
|
|
|
|
|
|
|
+ return (
|
|
|
|
|
+ <div
|
|
|
|
|
+ classList={{
|
|
|
|
|
+ "flex flex-col min-h-0 bg-background-stronger border border-b-0 border-border-weak-base rounded-tl-sm": true,
|
|
|
|
|
+ "flex-1 min-w-0": panelProps.mobile,
|
|
|
|
|
+ }}
|
|
|
|
|
+ style={{ width: panelProps.mobile ? undefined : `${Math.max(layout.sidebar.width() - 64, 0)}px` }}
|
|
|
|
|
+ >
|
|
|
|
|
+ <Show when={panelProps.project} keyed>
|
|
|
|
|
+ {(p) => (
|
|
|
|
|
+ <>
|
|
|
|
|
+ <div class="shrink-0 px-2 py-1">
|
|
|
|
|
+ <div class="group/project flex items-start justify-between gap-2 p-2 pr-1">
|
|
|
|
|
+ <div class="flex flex-col min-w-0">
|
|
|
|
|
+ <InlineEditor
|
|
|
|
|
+ id={`project:${projectId()}`}
|
|
|
|
|
+ value={projectName}
|
|
|
|
|
+ onSave={(next) => renameProject(p, next)}
|
|
|
|
|
+ class="text-16-medium text-text-strong truncate"
|
|
|
|
|
+ displayClass="text-16-medium text-text-strong truncate"
|
|
|
|
|
+ stopPropagation
|
|
|
|
|
+ />
|
|
|
|
|
+
|
|
|
|
|
+ <Tooltip
|
|
|
|
|
+ placement="bottom"
|
|
|
|
|
+ gutter={2}
|
|
|
|
|
+ value={p.worktree}
|
|
|
|
|
+ class="shrink-0"
|
|
|
|
|
+ contentStyle={{
|
|
|
|
|
+ "max-width": "640px",
|
|
|
|
|
+ transform: "translate3d(52px, 0, 0)",
|
|
|
|
|
+ }}
|
|
|
|
|
+ >
|
|
|
|
|
+ <span class="text-12-regular text-text-base truncate select-text">
|
|
|
|
|
+ {p.worktree.replace(homedir(), "~")}
|
|
|
|
|
+ </span>
|
|
|
|
|
+ </Tooltip>
|
|
|
|
|
+ </div>
|
|
|
|
|
|
|
|
- const local = current.worktree
|
|
|
|
|
- const key = workspaceKey(created.directory)
|
|
|
|
|
- const root = workspaceKey(local)
|
|
|
|
|
|
|
+ <DropdownMenu>
|
|
|
|
|
+ <DropdownMenu.Trigger
|
|
|
|
|
+ as={IconButton}
|
|
|
|
|
+ icon="dot-grid"
|
|
|
|
|
+ variant="ghost"
|
|
|
|
|
+ class="shrink-0 size-6 rounded-md opacity-0 group-hover/project:opacity-100 data-[expanded]:opacity-100 data-[expanded]:bg-surface-base-active"
|
|
|
|
|
+ aria-label={language.t("common.moreOptions")}
|
|
|
|
|
+ />
|
|
|
|
|
+ <DropdownMenu.Portal>
|
|
|
|
|
+ <DropdownMenu.Content class="mt-1">
|
|
|
|
|
+ <DropdownMenu.Item onSelect={() => dialog.show(() => <DialogEditProject project={p} />)}>
|
|
|
|
|
+ <DropdownMenu.ItemLabel>{language.t("common.edit")}</DropdownMenu.ItemLabel>
|
|
|
|
|
+ </DropdownMenu.Item>
|
|
|
|
|
+ <DropdownMenu.Item
|
|
|
|
|
+ disabled={p.vcs !== "git" && !layout.sidebar.workspaces(p.worktree)()}
|
|
|
|
|
+ onSelect={() => {
|
|
|
|
|
+ const enabled = layout.sidebar.workspaces(p.worktree)()
|
|
|
|
|
+ if (enabled) {
|
|
|
|
|
+ layout.sidebar.toggleWorkspaces(p.worktree)
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
+ if (p.vcs !== "git") return
|
|
|
|
|
+ layout.sidebar.toggleWorkspaces(p.worktree)
|
|
|
|
|
+ }}
|
|
|
|
|
+ >
|
|
|
|
|
+ <DropdownMenu.ItemLabel>
|
|
|
|
|
+ {layout.sidebar.workspaces(p.worktree)()
|
|
|
|
|
+ ? language.t("sidebar.workspaces.disable")
|
|
|
|
|
+ : language.t("sidebar.workspaces.enable")}
|
|
|
|
|
+ </DropdownMenu.ItemLabel>
|
|
|
|
|
+ </DropdownMenu.Item>
|
|
|
|
|
+ <DropdownMenu.Separator />
|
|
|
|
|
+ <DropdownMenu.Item onSelect={() => closeProject(p.worktree)}>
|
|
|
|
|
+ <DropdownMenu.ItemLabel>{language.t("common.close")}</DropdownMenu.ItemLabel>
|
|
|
|
|
+ </DropdownMenu.Item>
|
|
|
|
|
+ </DropdownMenu.Content>
|
|
|
|
|
+ </DropdownMenu.Portal>
|
|
|
|
|
+ </DropdownMenu>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
|
|
|
- setBusy(created.directory, true)
|
|
|
|
|
- WorktreeState.pending(created.directory)
|
|
|
|
|
- setStore("workspaceExpanded", key, true)
|
|
|
|
|
- if (key !== created.directory) {
|
|
|
|
|
- setStore("workspaceExpanded", created.directory, true)
|
|
|
|
|
- }
|
|
|
|
|
- setStore("workspaceOrder", current.worktree, (prev) => {
|
|
|
|
|
- const existing = prev ?? []
|
|
|
|
|
- const next = existing.filter((item) => {
|
|
|
|
|
- const id = workspaceKey(item)
|
|
|
|
|
- if (id === root) return false
|
|
|
|
|
- return id !== key
|
|
|
|
|
- })
|
|
|
|
|
- return [local, created.directory, ...next]
|
|
|
|
|
- })
|
|
|
|
|
|
|
+ <Show
|
|
|
|
|
+ when={workspacesEnabled()}
|
|
|
|
|
+ fallback={
|
|
|
|
|
+ <>
|
|
|
|
|
+ <div class="py-4 px-3">
|
|
|
|
|
+ <TooltipKeybind
|
|
|
|
|
+ title={language.t("command.session.new")}
|
|
|
|
|
+ keybind={command.keybind("session.new")}
|
|
|
|
|
+ placement="top"
|
|
|
|
|
+ >
|
|
|
|
|
+ <Button
|
|
|
|
|
+ size="large"
|
|
|
|
|
+ icon="plus-small"
|
|
|
|
|
+ class="w-full"
|
|
|
|
|
+ onClick={() => {
|
|
|
|
|
+ navigate(`/${base64Encode(p.worktree)}/session`)
|
|
|
|
|
+ layout.mobileSidebar.hide()
|
|
|
|
|
+ }}
|
|
|
|
|
+ >
|
|
|
|
|
+ {language.t("command.session.new")}
|
|
|
|
|
+ </Button>
|
|
|
|
|
+ </TooltipKeybind>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div class="flex-1 min-h-0">
|
|
|
|
|
+ <LocalWorkspace project={p} mobile={panelProps.mobile} />
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </>
|
|
|
|
|
+ }
|
|
|
|
|
+ >
|
|
|
|
|
+ <>
|
|
|
|
|
+ <div class="py-4 px-3">
|
|
|
|
|
+ <TooltipKeybind
|
|
|
|
|
+ title={language.t("workspace.new")}
|
|
|
|
|
+ keybind={command.keybind("workspace.new")}
|
|
|
|
|
+ placement="top"
|
|
|
|
|
+ >
|
|
|
|
|
+ <Button size="large" icon="plus-small" class="w-full" onClick={() => createWorkspace(p)}>
|
|
|
|
|
+ {language.t("workspace.new")}
|
|
|
|
|
+ </Button>
|
|
|
|
|
+ </TooltipKeybind>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div class="relative flex-1 min-h-0">
|
|
|
|
|
+ <DragDropProvider
|
|
|
|
|
+ onDragStart={handleWorkspaceDragStart}
|
|
|
|
|
+ onDragEnd={handleWorkspaceDragEnd}
|
|
|
|
|
+ onDragOver={handleWorkspaceDragOver}
|
|
|
|
|
+ collisionDetector={closestCenter}
|
|
|
|
|
+ >
|
|
|
|
|
+ <DragDropSensors />
|
|
|
|
|
+ <ConstrainDragXAxis />
|
|
|
|
|
+ <div
|
|
|
|
|
+ ref={(el) => {
|
|
|
|
|
+ if (!panelProps.mobile) scrollContainerRef = el
|
|
|
|
|
+ }}
|
|
|
|
|
+ class="size-full flex flex-col py-2 gap-4 overflow-y-auto no-scrollbar"
|
|
|
|
|
+ style={{ "overflow-anchor": "none" }}
|
|
|
|
|
+ >
|
|
|
|
|
+ <SortableProvider ids={workspaces()}>
|
|
|
|
|
+ <For each={workspaces()}>
|
|
|
|
|
+ {(directory) => (
|
|
|
|
|
+ <SortableWorkspace directory={directory} project={p} mobile={panelProps.mobile} />
|
|
|
|
|
+ )}
|
|
|
|
|
+ </For>
|
|
|
|
|
+ </SortableProvider>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <DragOverlay>
|
|
|
|
|
+ <WorkspaceDragOverlay />
|
|
|
|
|
+ </DragOverlay>
|
|
|
|
|
+ </DragDropProvider>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </>
|
|
|
|
|
+ </Show>
|
|
|
|
|
+ </>
|
|
|
|
|
+ )}
|
|
|
|
|
+ </Show>
|
|
|
|
|
+ <Show when={providers.all().length > 0 && providers.paid().length === 0}>
|
|
|
|
|
+ <div class="shrink-0 px-2 py-3 border-t border-border-weak-base">
|
|
|
|
|
+ <div class="rounded-md bg-background-base shadow-xs-border-base">
|
|
|
|
|
+ <div class="p-3 flex flex-col gap-2">
|
|
|
|
|
+ <div class="text-12-medium text-text-strong">{language.t("sidebar.gettingStarted.title")}</div>
|
|
|
|
|
+ <div class="text-text-base">{language.t("sidebar.gettingStarted.line1")}</div>
|
|
|
|
|
+ <div class="text-text-base">{language.t("sidebar.gettingStarted.line2")}</div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <Button
|
|
|
|
|
+ class="flex w-full text-left justify-start text-12-medium text-text-strong stroke-[1.5px] rounded-md rounded-t-none shadow-none border-t border-border-weak-base px-3"
|
|
|
|
|
+ size="large"
|
|
|
|
|
+ icon="plus"
|
|
|
|
|
+ onClick={connectProvider}
|
|
|
|
|
+ >
|
|
|
|
|
+ {language.t("command.provider.connect")}
|
|
|
|
|
+ </Button>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </Show>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ )
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
- globalSync.child(created.directory)
|
|
|
|
|
- navigate(`/${base64Encode(created.directory)}/session`)
|
|
|
|
|
- layout.mobileSidebar.hide()
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ const SidebarContent = (sidebarProps: { mobile?: boolean }) => {
|
|
|
|
|
+ const expanded = () => sidebarProps.mobile || layout.sidebar.opened()
|
|
|
|
|
|
|
|
command.register(() => [
|
|
command.register(() => [
|
|
|
{
|
|
{
|
|
@@ -2310,12 +2533,14 @@ export default function Layout(props: ParentProps) {
|
|
|
category: language.t("command.category.workspace"),
|
|
category: language.t("command.category.workspace"),
|
|
|
keybind: "mod+shift+w",
|
|
keybind: "mod+shift+w",
|
|
|
disabled: !workspaceSetting(),
|
|
disabled: !workspaceSetting(),
|
|
|
- onSelect: createWorkspace,
|
|
|
|
|
|
|
+ onSelect: () => {
|
|
|
|
|
+ const project = currentProject()
|
|
|
|
|
+ if (!project) return
|
|
|
|
|
+ return createWorkspace(project)
|
|
|
|
|
+ },
|
|
|
},
|
|
},
|
|
|
])
|
|
])
|
|
|
|
|
|
|
|
- const homedir = createMemo(() => sync.data.path.home)
|
|
|
|
|
-
|
|
|
|
|
return (
|
|
return (
|
|
|
<div class="flex h-full w-full overflow-hidden">
|
|
<div class="flex h-full w-full overflow-hidden">
|
|
|
<div class="w-16 shrink-0 bg-background-base flex flex-col items-center overflow-hidden">
|
|
<div class="w-16 shrink-0 bg-background-base flex flex-col items-center overflow-hidden">
|
|
@@ -2386,180 +2611,7 @@ export default function Layout(props: ParentProps) {
|
|
|
</div>
|
|
</div>
|
|
|
|
|
|
|
|
<Show when={expanded()}>
|
|
<Show when={expanded()}>
|
|
|
- <div
|
|
|
|
|
- classList={{
|
|
|
|
|
- "flex flex-col min-h-0 bg-background-stronger border border-b-0 border-border-weak-base rounded-tl-sm": true,
|
|
|
|
|
- "flex-1 min-w-0": sidebarProps.mobile,
|
|
|
|
|
- }}
|
|
|
|
|
- style={{ width: sidebarProps.mobile ? undefined : `${Math.max(layout.sidebar.width() - 64, 0)}px` }}
|
|
|
|
|
- >
|
|
|
|
|
- <Show when={project()} keyed>
|
|
|
|
|
- {(p) => (
|
|
|
|
|
- <>
|
|
|
|
|
- <div class="shrink-0 px-2 py-1">
|
|
|
|
|
- <div class="group/project flex items-start justify-between gap-2 p-2 pr-1">
|
|
|
|
|
- <div class="flex flex-col min-w-0">
|
|
|
|
|
- <InlineEditor
|
|
|
|
|
- id={`project:${projectId()}`}
|
|
|
|
|
- value={projectName}
|
|
|
|
|
- onSave={(next) => project() && renameProject(project()!, next)}
|
|
|
|
|
- class="text-16-medium text-text-strong truncate"
|
|
|
|
|
- displayClass="text-16-medium text-text-strong truncate"
|
|
|
|
|
- stopPropagation
|
|
|
|
|
- />
|
|
|
|
|
-
|
|
|
|
|
- <Tooltip
|
|
|
|
|
- placement="bottom"
|
|
|
|
|
- gutter={2}
|
|
|
|
|
- value={project()?.worktree}
|
|
|
|
|
- class="shrink-0"
|
|
|
|
|
- contentStyle={{
|
|
|
|
|
- "max-width": "640px",
|
|
|
|
|
- transform: "translate3d(52px, 0, 0)",
|
|
|
|
|
- }}
|
|
|
|
|
- >
|
|
|
|
|
- <span class="text-12-regular text-text-base truncate select-text">
|
|
|
|
|
- {project()?.worktree.replace(homedir(), "~")}
|
|
|
|
|
- </span>
|
|
|
|
|
- </Tooltip>
|
|
|
|
|
- </div>
|
|
|
|
|
-
|
|
|
|
|
- <DropdownMenu>
|
|
|
|
|
- <DropdownMenu.Trigger
|
|
|
|
|
- as={IconButton}
|
|
|
|
|
- icon="dot-grid"
|
|
|
|
|
- variant="ghost"
|
|
|
|
|
- class="shrink-0 size-6 rounded-md opacity-0 group-hover/project:opacity-100 data-[expanded]:opacity-100 data-[expanded]:bg-surface-base-active"
|
|
|
|
|
- aria-label={language.t("common.moreOptions")}
|
|
|
|
|
- />
|
|
|
|
|
- <DropdownMenu.Portal>
|
|
|
|
|
- <DropdownMenu.Content class="mt-1">
|
|
|
|
|
- <DropdownMenu.Item onSelect={() => dialog.show(() => <DialogEditProject project={p} />)}>
|
|
|
|
|
- <DropdownMenu.ItemLabel>{language.t("common.edit")}</DropdownMenu.ItemLabel>
|
|
|
|
|
- </DropdownMenu.Item>
|
|
|
|
|
- <DropdownMenu.Item
|
|
|
|
|
- disabled={p.vcs !== "git" && !layout.sidebar.workspaces(p.worktree)()}
|
|
|
|
|
- onSelect={() => {
|
|
|
|
|
- const enabled = layout.sidebar.workspaces(p.worktree)()
|
|
|
|
|
- if (enabled) {
|
|
|
|
|
- layout.sidebar.toggleWorkspaces(p.worktree)
|
|
|
|
|
- return
|
|
|
|
|
- }
|
|
|
|
|
- if (p.vcs !== "git") return
|
|
|
|
|
- layout.sidebar.toggleWorkspaces(p.worktree)
|
|
|
|
|
- }}
|
|
|
|
|
- >
|
|
|
|
|
- <DropdownMenu.ItemLabel>
|
|
|
|
|
- {layout.sidebar.workspaces(p.worktree)()
|
|
|
|
|
- ? language.t("sidebar.workspaces.disable")
|
|
|
|
|
- : language.t("sidebar.workspaces.enable")}
|
|
|
|
|
- </DropdownMenu.ItemLabel>
|
|
|
|
|
- </DropdownMenu.Item>
|
|
|
|
|
- <DropdownMenu.Separator />
|
|
|
|
|
- <DropdownMenu.Item onSelect={() => closeProject(p.worktree)}>
|
|
|
|
|
- <DropdownMenu.ItemLabel>{language.t("common.close")}</DropdownMenu.ItemLabel>
|
|
|
|
|
- </DropdownMenu.Item>
|
|
|
|
|
- </DropdownMenu.Content>
|
|
|
|
|
- </DropdownMenu.Portal>
|
|
|
|
|
- </DropdownMenu>
|
|
|
|
|
- </div>
|
|
|
|
|
- </div>
|
|
|
|
|
-
|
|
|
|
|
- <Show
|
|
|
|
|
- when={workspaceSetting()}
|
|
|
|
|
- fallback={
|
|
|
|
|
- <>
|
|
|
|
|
- <div class="py-4 px-3">
|
|
|
|
|
- <TooltipKeybind
|
|
|
|
|
- title={language.t("command.session.new")}
|
|
|
|
|
- keybind={command.keybind("session.new")}
|
|
|
|
|
- placement="top"
|
|
|
|
|
- >
|
|
|
|
|
- <Button
|
|
|
|
|
- size="large"
|
|
|
|
|
- icon="plus-small"
|
|
|
|
|
- class="w-full"
|
|
|
|
|
- onClick={() => {
|
|
|
|
|
- navigate(`/${base64Encode(p.worktree)}/session`)
|
|
|
|
|
- layout.mobileSidebar.hide()
|
|
|
|
|
- }}
|
|
|
|
|
- >
|
|
|
|
|
- {language.t("command.session.new")}
|
|
|
|
|
- </Button>
|
|
|
|
|
- </TooltipKeybind>
|
|
|
|
|
- </div>
|
|
|
|
|
- <div class="flex-1 min-h-0">
|
|
|
|
|
- <LocalWorkspace project={p} mobile={sidebarProps.mobile} />
|
|
|
|
|
- </div>
|
|
|
|
|
- </>
|
|
|
|
|
- }
|
|
|
|
|
- >
|
|
|
|
|
- <>
|
|
|
|
|
- <div class="py-4 px-3">
|
|
|
|
|
- <TooltipKeybind
|
|
|
|
|
- title={language.t("workspace.new")}
|
|
|
|
|
- keybind={command.keybind("workspace.new")}
|
|
|
|
|
- placement="top"
|
|
|
|
|
- >
|
|
|
|
|
- <Button size="large" icon="plus-small" class="w-full" onClick={createWorkspace}>
|
|
|
|
|
- {language.t("workspace.new")}
|
|
|
|
|
- </Button>
|
|
|
|
|
- </TooltipKeybind>
|
|
|
|
|
- </div>
|
|
|
|
|
- <div class="relative flex-1 min-h-0">
|
|
|
|
|
- <DragDropProvider
|
|
|
|
|
- onDragStart={handleWorkspaceDragStart}
|
|
|
|
|
- onDragEnd={handleWorkspaceDragEnd}
|
|
|
|
|
- onDragOver={handleWorkspaceDragOver}
|
|
|
|
|
- collisionDetector={closestCenter}
|
|
|
|
|
- >
|
|
|
|
|
- <DragDropSensors />
|
|
|
|
|
- <ConstrainDragXAxis />
|
|
|
|
|
- <div
|
|
|
|
|
- ref={(el) => {
|
|
|
|
|
- if (!sidebarProps.mobile) scrollContainerRef = el
|
|
|
|
|
- }}
|
|
|
|
|
- class="size-full flex flex-col py-2 gap-4 overflow-y-auto no-scrollbar"
|
|
|
|
|
- style={{ "overflow-anchor": "none" }}
|
|
|
|
|
- >
|
|
|
|
|
- <SortableProvider ids={workspaces()}>
|
|
|
|
|
- <For each={workspaces()}>
|
|
|
|
|
- {(directory) => (
|
|
|
|
|
- <SortableWorkspace directory={directory} project={p} mobile={sidebarProps.mobile} />
|
|
|
|
|
- )}
|
|
|
|
|
- </For>
|
|
|
|
|
- </SortableProvider>
|
|
|
|
|
- </div>
|
|
|
|
|
- <DragOverlay>
|
|
|
|
|
- <WorkspaceDragOverlay />
|
|
|
|
|
- </DragOverlay>
|
|
|
|
|
- </DragDropProvider>
|
|
|
|
|
- </div>
|
|
|
|
|
- </>
|
|
|
|
|
- </Show>
|
|
|
|
|
- </>
|
|
|
|
|
- )}
|
|
|
|
|
- </Show>
|
|
|
|
|
- <Show when={providers.all().length > 0 && providers.paid().length === 0}>
|
|
|
|
|
- <div class="shrink-0 px-2 py-3 border-t border-border-weak-base">
|
|
|
|
|
- <div class="rounded-md bg-background-base shadow-xs-border-base">
|
|
|
|
|
- <div class="p-3 flex flex-col gap-2">
|
|
|
|
|
- <div class="text-12-medium text-text-strong">{language.t("sidebar.gettingStarted.title")}</div>
|
|
|
|
|
- <div class="text-text-base">{language.t("sidebar.gettingStarted.line1")}</div>
|
|
|
|
|
- <div class="text-text-base">{language.t("sidebar.gettingStarted.line2")}</div>
|
|
|
|
|
- </div>
|
|
|
|
|
- <Button
|
|
|
|
|
- class="flex w-full text-left justify-start text-12-medium text-text-strong stroke-[1.5px] rounded-md rounded-t-none shadow-none border-t border-border-weak-base px-3"
|
|
|
|
|
- size="large"
|
|
|
|
|
- icon="plus"
|
|
|
|
|
- onClick={connectProvider}
|
|
|
|
|
- >
|
|
|
|
|
- {language.t("command.provider.connect")}
|
|
|
|
|
- </Button>
|
|
|
|
|
- </div>
|
|
|
|
|
- </div>
|
|
|
|
|
- </Show>
|
|
|
|
|
- </div>
|
|
|
|
|
|
|
+ <SidebarPanel project={currentProject()} mobile={sidebarProps.mobile} />
|
|
|
</Show>
|
|
</Show>
|
|
|
</div>
|
|
</div>
|
|
|
)
|
|
)
|
|
@@ -2576,10 +2628,18 @@ export default function Layout(props: ParentProps) {
|
|
|
"relative shrink-0": true,
|
|
"relative shrink-0": true,
|
|
|
}}
|
|
}}
|
|
|
style={{ width: layout.sidebar.opened() ? `${Math.max(layout.sidebar.width(), 244)}px` : "64px" }}
|
|
style={{ width: layout.sidebar.opened() ? `${Math.max(layout.sidebar.width(), 244)}px` : "64px" }}
|
|
|
|
|
+ onMouseLeave={() => setHoverProject(undefined)}
|
|
|
>
|
|
>
|
|
|
<div class="@container w-full h-full contain-strict">
|
|
<div class="@container w-full h-full contain-strict">
|
|
|
<SidebarContent />
|
|
<SidebarContent />
|
|
|
</div>
|
|
</div>
|
|
|
|
|
+ <Show when={!layout.sidebar.opened() ? hoverProjectData() : undefined} keyed>
|
|
|
|
|
+ {(project) => (
|
|
|
|
|
+ <div class="absolute inset-y-0 left-16 z-50 flex">
|
|
|
|
|
+ <SidebarPanel project={project} />
|
|
|
|
|
+ </div>
|
|
|
|
|
+ )}
|
|
|
|
|
+ </Show>
|
|
|
<Show when={layout.sidebar.opened()}>
|
|
<Show when={layout.sidebar.opened()}>
|
|
|
<ResizeHandle
|
|
<ResizeHandle
|
|
|
direction="horizontal"
|
|
direction="horizontal"
|