|
|
@@ -13,7 +13,7 @@ import {
|
|
|
type Accessor,
|
|
|
} from "solid-js"
|
|
|
import { makeEventListener } from "@solid-primitives/event-listener"
|
|
|
-import { useNavigate, useParams } from "@solidjs/router"
|
|
|
+import { useLocation, useNavigate, useParams } from "@solidjs/router"
|
|
|
import { useLayout, LocalProject } from "@/context/layout"
|
|
|
import { useGlobalSync } from "@/context/global-sync"
|
|
|
import { Persist, persisted } from "@/utils/persist"
|
|
|
@@ -127,6 +127,7 @@ export default function Layout(props: ParentProps) {
|
|
|
const theme = useTheme()
|
|
|
const language = useLanguage()
|
|
|
const initialDirectory = decode64(params.dir)
|
|
|
+ const location = useLocation()
|
|
|
const route = createMemo(() => {
|
|
|
const slug = params.dir
|
|
|
if (!slug) return { slug, dir: "" }
|
|
|
@@ -576,7 +577,7 @@ export default function Layout(props: ParentProps) {
|
|
|
|
|
|
return projects.find((p) => p.worktree === root)
|
|
|
})
|
|
|
-
|
|
|
+
|
|
|
const [autoselecting] = createResource(async () => {
|
|
|
await ready.promise
|
|
|
await layout.ready.promise
|
|
|
@@ -2102,196 +2103,198 @@ export default function Layout(props: ParentProps) {
|
|
|
</Show>
|
|
|
}
|
|
|
>
|
|
|
- <>
|
|
|
- <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="flex flex-col min-w-0">
|
|
|
- <InlineEditor
|
|
|
- id={`project:${projectId()}`}
|
|
|
- value={projectName}
|
|
|
- onSave={(next) => {
|
|
|
- const item = project()
|
|
|
- if (!item) return
|
|
|
- void renameProject(item, next)
|
|
|
- }}
|
|
|
- class="text-14-medium text-text-strong truncate"
|
|
|
- displayClass="text-14-medium text-text-strong truncate"
|
|
|
- stopPropagation
|
|
|
- />
|
|
|
-
|
|
|
- <Tooltip
|
|
|
- placement="bottom"
|
|
|
- gutter={2}
|
|
|
- value={worktree()}
|
|
|
- class="shrink-0"
|
|
|
- contentStyle={{
|
|
|
- "max-width": "640px",
|
|
|
- transform: "translate3d(52px, 0, 0)",
|
|
|
- }}
|
|
|
- >
|
|
|
- <span class="text-12-regular text-text-base truncate select-text">
|
|
|
- {worktree().replace(homedir(), "~")}
|
|
|
- </span>
|
|
|
- </Tooltip>
|
|
|
- </div>
|
|
|
+ {(project) => (
|
|
|
+ <>
|
|
|
+ <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="flex flex-col min-w-0">
|
|
|
+ <InlineEditor
|
|
|
+ id={`project:${projectId()}`}
|
|
|
+ value={projectName}
|
|
|
+ onSave={(next) => {
|
|
|
+ const item = project()
|
|
|
+ if (!item) return
|
|
|
+ void renameProject(item, next)
|
|
|
+ }}
|
|
|
+ class="text-14-medium text-text-strong truncate"
|
|
|
+ displayClass="text-14-medium text-text-strong truncate"
|
|
|
+ stopPropagation
|
|
|
+ />
|
|
|
+
|
|
|
+ <Tooltip
|
|
|
+ placement="bottom"
|
|
|
+ gutter={2}
|
|
|
+ value={worktree()}
|
|
|
+ class="shrink-0"
|
|
|
+ contentStyle={{
|
|
|
+ "max-width": "640px",
|
|
|
+ transform: "translate3d(52px, 0, 0)",
|
|
|
+ }}
|
|
|
+ >
|
|
|
+ <span class="text-12-regular text-text-base truncate select-text">
|
|
|
+ {worktree().replace(homedir(), "~")}
|
|
|
+ </span>
|
|
|
+ </Tooltip>
|
|
|
+ </div>
|
|
|
|
|
|
- <DropdownMenu modal={!sidebarHovering()}>
|
|
|
- <DropdownMenu.Trigger
|
|
|
- as={IconButton}
|
|
|
- icon="dot-grid"
|
|
|
- variant="ghost"
|
|
|
- data-action="project-menu"
|
|
|
- data-project={slug()}
|
|
|
- class="shrink-0 size-6 rounded-md transition-opacity data-[expanded]:bg-surface-base-active"
|
|
|
- classList={{
|
|
|
- "opacity-100": panelProps.mobile || merged(),
|
|
|
- "opacity-0 group-hover/project:opacity-100 group-focus-within/project:opacity-100 data-[expanded]:opacity-100":
|
|
|
- !panelProps.mobile && !merged(),
|
|
|
- }}
|
|
|
- aria-label={language.t("common.moreOptions")}
|
|
|
- />
|
|
|
- <DropdownMenu.Portal>
|
|
|
- <DropdownMenu.Content class="mt-1">
|
|
|
- <DropdownMenu.Item
|
|
|
- onSelect={() => {
|
|
|
- const item = project()
|
|
|
- if (!item) return
|
|
|
- showEditProjectDialog(item)
|
|
|
- }}
|
|
|
- >
|
|
|
- <DropdownMenu.ItemLabel>{language.t("common.edit")}</DropdownMenu.ItemLabel>
|
|
|
- </DropdownMenu.Item>
|
|
|
- <DropdownMenu.Item
|
|
|
- data-action="project-workspaces-toggle"
|
|
|
- data-project={slug()}
|
|
|
- disabled={!canToggle()}
|
|
|
- onSelect={() => {
|
|
|
- const item = project()
|
|
|
- if (!item) return
|
|
|
- toggleProjectWorkspaces(item)
|
|
|
- }}
|
|
|
- >
|
|
|
- <DropdownMenu.ItemLabel>
|
|
|
- {workspacesEnabled()
|
|
|
- ? language.t("sidebar.workspaces.disable")
|
|
|
- : language.t("sidebar.workspaces.enable")}
|
|
|
- </DropdownMenu.ItemLabel>
|
|
|
- </DropdownMenu.Item>
|
|
|
- <DropdownMenu.Item
|
|
|
- data-action="project-clear-notifications"
|
|
|
- data-project={slug()}
|
|
|
- disabled={unseenCount() === 0}
|
|
|
- onSelect={clearNotifications}
|
|
|
- >
|
|
|
- <DropdownMenu.ItemLabel>
|
|
|
- {language.t("sidebar.project.clearNotifications")}
|
|
|
- </DropdownMenu.ItemLabel>
|
|
|
- </DropdownMenu.Item>
|
|
|
- <DropdownMenu.Separator />
|
|
|
- <DropdownMenu.Item
|
|
|
- data-action="project-close-menu"
|
|
|
- data-project={slug()}
|
|
|
- onSelect={() => {
|
|
|
- const dir = worktree()
|
|
|
- if (!dir) return
|
|
|
- closeProject(dir)
|
|
|
- }}
|
|
|
- >
|
|
|
- <DropdownMenu.ItemLabel>{language.t("common.close")}</DropdownMenu.ItemLabel>
|
|
|
- </DropdownMenu.Item>
|
|
|
- </DropdownMenu.Content>
|
|
|
- </DropdownMenu.Portal>
|
|
|
- </DropdownMenu>
|
|
|
+ <DropdownMenu modal={!sidebarHovering()}>
|
|
|
+ <DropdownMenu.Trigger
|
|
|
+ as={IconButton}
|
|
|
+ icon="dot-grid"
|
|
|
+ variant="ghost"
|
|
|
+ data-action="project-menu"
|
|
|
+ data-project={slug()}
|
|
|
+ class="shrink-0 size-6 rounded-md transition-opacity data-[expanded]:bg-surface-base-active"
|
|
|
+ classList={{
|
|
|
+ "opacity-100": panelProps.mobile || merged(),
|
|
|
+ "opacity-0 group-hover/project:opacity-100 group-focus-within/project:opacity-100 data-[expanded]:opacity-100":
|
|
|
+ !panelProps.mobile && !merged(),
|
|
|
+ }}
|
|
|
+ aria-label={language.t("common.moreOptions")}
|
|
|
+ />
|
|
|
+ <DropdownMenu.Portal>
|
|
|
+ <DropdownMenu.Content class="mt-1">
|
|
|
+ <DropdownMenu.Item
|
|
|
+ onSelect={() => {
|
|
|
+ const item = project()
|
|
|
+ if (!item) return
|
|
|
+ showEditProjectDialog(item)
|
|
|
+ }}
|
|
|
+ >
|
|
|
+ <DropdownMenu.ItemLabel>{language.t("common.edit")}</DropdownMenu.ItemLabel>
|
|
|
+ </DropdownMenu.Item>
|
|
|
+ <DropdownMenu.Item
|
|
|
+ data-action="project-workspaces-toggle"
|
|
|
+ data-project={slug()}
|
|
|
+ disabled={!canToggle()}
|
|
|
+ onSelect={() => {
|
|
|
+ const item = project()
|
|
|
+ if (!item) return
|
|
|
+ toggleProjectWorkspaces(item)
|
|
|
+ }}
|
|
|
+ >
|
|
|
+ <DropdownMenu.ItemLabel>
|
|
|
+ {workspacesEnabled()
|
|
|
+ ? language.t("sidebar.workspaces.disable")
|
|
|
+ : language.t("sidebar.workspaces.enable")}
|
|
|
+ </DropdownMenu.ItemLabel>
|
|
|
+ </DropdownMenu.Item>
|
|
|
+ <DropdownMenu.Item
|
|
|
+ data-action="project-clear-notifications"
|
|
|
+ data-project={slug()}
|
|
|
+ disabled={unseenCount() === 0}
|
|
|
+ onSelect={clearNotifications}
|
|
|
+ >
|
|
|
+ <DropdownMenu.ItemLabel>
|
|
|
+ {language.t("sidebar.project.clearNotifications")}
|
|
|
+ </DropdownMenu.ItemLabel>
|
|
|
+ </DropdownMenu.Item>
|
|
|
+ <DropdownMenu.Separator />
|
|
|
+ <DropdownMenu.Item
|
|
|
+ data-action="project-close-menu"
|
|
|
+ data-project={slug()}
|
|
|
+ onSelect={() => {
|
|
|
+ const dir = worktree()
|
|
|
+ if (!dir) return
|
|
|
+ closeProject(dir)
|
|
|
+ }}
|
|
|
+ >
|
|
|
+ <DropdownMenu.ItemLabel>{language.t("common.close")}</DropdownMenu.ItemLabel>
|
|
|
+ </DropdownMenu.Item>
|
|
|
+ </DropdownMenu.Content>
|
|
|
+ </DropdownMenu.Portal>
|
|
|
+ </DropdownMenu>
|
|
|
+ </div>
|
|
|
</div>
|
|
|
- </div>
|
|
|
|
|
|
- <div class="flex-1 min-h-0 flex flex-col">
|
|
|
- <Show
|
|
|
- when={workspacesEnabled()}
|
|
|
- fallback={
|
|
|
+ <div class="flex-1 min-h-0 flex flex-col">
|
|
|
+ <Show
|
|
|
+ when={workspacesEnabled()}
|
|
|
+ fallback={
|
|
|
+ <>
|
|
|
+ <div class="shrink-0 py-4">
|
|
|
+ <Button
|
|
|
+ size="large"
|
|
|
+ icon="new-session"
|
|
|
+ class="w-full"
|
|
|
+ onClick={() => {
|
|
|
+ const dir = worktree()
|
|
|
+ if (!dir) return
|
|
|
+ navigateWithSidebarReset(`/${base64Encode(dir)}/session`)
|
|
|
+ }}
|
|
|
+ >
|
|
|
+ {language.t("command.session.new")}
|
|
|
+ </Button>
|
|
|
+ </div>
|
|
|
+ <div class="flex-1 min-h-0">
|
|
|
+ <LocalWorkspace
|
|
|
+ ctx={workspaceSidebarCtx}
|
|
|
+ project={project()}
|
|
|
+ sortNow={sortNow}
|
|
|
+ mobile={panelProps.mobile}
|
|
|
+ />
|
|
|
+ </div>
|
|
|
+ </>
|
|
|
+ }
|
|
|
+ >
|
|
|
<>
|
|
|
<div class="shrink-0 py-4">
|
|
|
<Button
|
|
|
size="large"
|
|
|
- icon="new-session"
|
|
|
+ icon="plus-small"
|
|
|
class="w-full"
|
|
|
onClick={() => {
|
|
|
- const dir = worktree()
|
|
|
- if (!dir) return
|
|
|
- navigateWithSidebarReset(`/${base64Encode(dir)}/session`)
|
|
|
+ const item = project()
|
|
|
+ if (!item) return
|
|
|
+ void createWorkspace(item)
|
|
|
}}
|
|
|
>
|
|
|
- {language.t("command.session.new")}
|
|
|
+ {language.t("workspace.new")}
|
|
|
</Button>
|
|
|
</div>
|
|
|
- <div class="flex-1 min-h-0">
|
|
|
- <LocalWorkspace
|
|
|
- ctx={workspaceSidebarCtx}
|
|
|
- project={project()!}
|
|
|
- sortNow={sortNow}
|
|
|
- mobile={panelProps.mobile}
|
|
|
- />
|
|
|
+ <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 [overflow-anchor:none]"
|
|
|
+ >
|
|
|
+ <SortableProvider ids={workspaces()}>
|
|
|
+ <For each={workspaces()}>
|
|
|
+ {(directory) => (
|
|
|
+ <SortableWorkspace
|
|
|
+ ctx={workspaceSidebarCtx}
|
|
|
+ directory={directory}
|
|
|
+ project={project()}
|
|
|
+ sortNow={sortNow}
|
|
|
+ mobile={panelProps.mobile}
|
|
|
+ />
|
|
|
+ )}
|
|
|
+ </For>
|
|
|
+ </SortableProvider>
|
|
|
+ </div>
|
|
|
+ <DragOverlay>
|
|
|
+ <WorkspaceDragOverlay
|
|
|
+ sidebarProject={sidebarProject}
|
|
|
+ activeWorkspace={() => store.activeWorkspace}
|
|
|
+ workspaceLabel={workspaceLabel}
|
|
|
+ />
|
|
|
+ </DragOverlay>
|
|
|
+ </DragDropProvider>
|
|
|
</div>
|
|
|
</>
|
|
|
- }
|
|
|
- >
|
|
|
- <>
|
|
|
- <div class="shrink-0 py-4">
|
|
|
- <Button
|
|
|
- size="large"
|
|
|
- icon="plus-small"
|
|
|
- class="w-full"
|
|
|
- onClick={() => {
|
|
|
- const item = project()
|
|
|
- if (!item) return
|
|
|
- void createWorkspace(item)
|
|
|
- }}
|
|
|
- >
|
|
|
- {language.t("workspace.new")}
|
|
|
- </Button>
|
|
|
- </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 [overflow-anchor:none]"
|
|
|
- >
|
|
|
- <SortableProvider ids={workspaces()}>
|
|
|
- <For each={workspaces()}>
|
|
|
- {(directory) => (
|
|
|
- <SortableWorkspace
|
|
|
- ctx={workspaceSidebarCtx}
|
|
|
- directory={directory}
|
|
|
- project={project()!}
|
|
|
- sortNow={sortNow}
|
|
|
- mobile={panelProps.mobile}
|
|
|
- />
|
|
|
- )}
|
|
|
- </For>
|
|
|
- </SortableProvider>
|
|
|
- </div>
|
|
|
- <DragOverlay>
|
|
|
- <WorkspaceDragOverlay
|
|
|
- sidebarProject={sidebarProject}
|
|
|
- activeWorkspace={() => store.activeWorkspace}
|
|
|
- workspaceLabel={workspaceLabel}
|
|
|
- />
|
|
|
- </DragOverlay>
|
|
|
- </DragDropProvider>
|
|
|
- </div>
|
|
|
- </>
|
|
|
- </Show>
|
|
|
- </div>
|
|
|
- </>
|
|
|
+ </Show>
|
|
|
+ </div>
|
|
|
+ </>
|
|
|
+ )}
|
|
|
</Show>
|
|
|
|
|
|
<div
|
|
|
@@ -2355,14 +2358,9 @@ export default function Layout(props: ParentProps) {
|
|
|
/>
|
|
|
)
|
|
|
|
|
|
- const [loading] = createResource(
|
|
|
- () => route()?.store?.[0]?.bootstrapPromise,
|
|
|
- (p) => p,
|
|
|
- )
|
|
|
-
|
|
|
return (
|
|
|
<div class="relative bg-background-base flex-1 min-h-0 min-w-0 flex flex-col select-none [&_input]:select-text [&_textarea]:select-text [&_[contenteditable]]:select-text">
|
|
|
- {(autoselecting(), loading()) ?? ""}
|
|
|
+ {autoselecting() ?? ""}
|
|
|
<Titlebar />
|
|
|
<div class="flex-1 min-h-0 min-w-0 flex">
|
|
|
<div class="flex-1 min-h-0 relative">
|