diff options
| -rw-r--r-- | packages/app/src/context/global-sync.tsx | 1 | ||||
| -rw-r--r-- | packages/app/src/context/global-sync/child-store.ts | 1 | ||||
| -rw-r--r-- | packages/app/src/context/global-sync/types.ts | 1 | ||||
| -rw-r--r-- | packages/app/src/pages/layout.tsx | 364 | ||||
| -rw-r--r-- | packages/app/src/pages/layout/sidebar-workspace.tsx | 12 | ||||
| -rw-r--r-- | packages/app/src/utils/persist.ts | 2 |
6 files changed, 187 insertions, 194 deletions
diff --git a/packages/app/src/context/global-sync.tsx b/packages/app/src/context/global-sync.tsx index 1359b07b4..1a672639b 100644 --- a/packages/app/src/context/global-sync.tsx +++ b/packages/app/src/context/global-sync.tsx @@ -264,7 +264,6 @@ function createGlobalSync() { children.pin(directory) const promise = Promise.resolve().then(async () => { const child = children.ensureChild(directory) - child[1]("bootstrapPromise", promise!) const cache = children.vcsCache.get(directory) if (!cache) return const sdk = sdkFor(directory) diff --git a/packages/app/src/context/global-sync/child-store.ts b/packages/app/src/context/global-sync/child-store.ts index 6788e8cc5..3fe67e4fb 100644 --- a/packages/app/src/context/global-sync/child-store.ts +++ b/packages/app/src/context/global-sync/child-store.ts @@ -182,7 +182,6 @@ export function createChildStoreManager(input: { limit: 5, message: {}, part: {}, - bootstrapPromise: Promise.resolve(), }) children[directory] = child disposers.set(directory, dispose) diff --git a/packages/app/src/context/global-sync/types.ts b/packages/app/src/context/global-sync/types.ts index 28b3705d1..e3ec83c5e 100644 --- a/packages/app/src/context/global-sync/types.ts +++ b/packages/app/src/context/global-sync/types.ts @@ -72,7 +72,6 @@ export type State = { part: { [messageID: string]: Part[] } - bootstrapPromise: Promise<void> } export type VcsCache = { diff --git a/packages/app/src/pages/layout.tsx b/packages/app/src/pages/layout.tsx index 12a2bf763..6f6b3c555 100644 --- a/packages/app/src/pages/layout.tsx +++ b/packages/app/src/pages/layout.tsx @@ -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"> diff --git a/packages/app/src/pages/layout/sidebar-workspace.tsx b/packages/app/src/pages/layout/sidebar-workspace.tsx index c1836fa8a..0202cfc3b 100644 --- a/packages/app/src/pages/layout/sidebar-workspace.tsx +++ b/packages/app/src/pages/layout/sidebar-workspace.tsx @@ -317,12 +317,11 @@ export const SortableWorkspace = (props: { }) const open = createMemo(() => props.ctx.workspaceExpanded(props.directory, local())) const boot = createMemo(() => open() || active()) - const booted = createMemo((prev) => prev || workspaceStore.status === "complete", false) const count = createMemo(() => sessions()?.length ?? 0) const hasMore = createMemo(() => workspaceStore.sessionTotal > count()) + const query = useQuery(() => ({ ...loadSessionsQuery(props.project.worktree) })) const busy = createMemo(() => props.ctx.isBusy(props.directory)) - const wasBusy = createMemo((prev) => prev || busy(), false) - const loading = createMemo(() => open() && !booted() && count() === 0 && !wasBusy()) + const loading = () => query.isLoading const touch = createMediaQuery("(hover: none)") const showNew = createMemo(() => !loading() && (touch() || count() === 0 || (active() && !params.id))) const loadMore = async () => { @@ -427,7 +426,7 @@ export const SortableWorkspace = (props: { mobile={props.mobile} ctx={props.ctx} showNew={showNew} - loading={loading} + loading={() => query.isLoading && count() === 0} sessions={sessions} hasMore={hasMore} loadMore={loadMore} @@ -453,11 +452,10 @@ export const LocalWorkspace = (props: { }) const slug = createMemo(() => base64Encode(props.project.worktree)) const sessions = createMemo(() => sortedRootSessions(workspace().store, props.sortNow())) - const booted = createMemo((prev) => prev || workspace().store.status === "complete", false) const count = createMemo(() => sessions()?.length ?? 0) const query = useQuery(() => ({ ...loadSessionsQuery(props.project.worktree) })) - const loading = createMemo(() => query.isPending && count() === 0) const hasMore = createMemo(() => workspace().store.sessionTotal > count()) + const loading = () => query.isLoading && count() === 0 const loadMore = async () => { workspace().setStore("limit", (limit) => (limit ?? 0) + 5) await globalSync.project.loadSessions(props.project.worktree) @@ -473,7 +471,7 @@ export const LocalWorkspace = (props: { mobile={props.mobile} ctx={props.ctx} showNew={() => false} - loading={() => query.isLoading} + loading={loading} sessions={sessions} hasMore={hasMore} loadMore={loadMore} diff --git a/packages/app/src/utils/persist.ts b/packages/app/src/utils/persist.ts index dce0e94c3..0cac30cb1 100644 --- a/packages/app/src/utils/persist.ts +++ b/packages/app/src/utils/persist.ts @@ -469,7 +469,7 @@ export function persisted<T>( state, setState, init, - Object.assign(() => ready() === true, { + Object.assign(() => (ready.loading ? false : ready.latest === true), { promise: init instanceof Promise ? init : undefined, }), ] |
