chore(app): createStore over signals
This commit is contained in:
@@ -91,7 +91,6 @@ export default function Layout(props: ParentProps) {
|
||||
let scrollContainerRef: HTMLDivElement | undefined
|
||||
|
||||
const params = useParams()
|
||||
const [autoselect, setAutoselect] = createSignal(!params.dir)
|
||||
const globalSDK = useGlobalSDK()
|
||||
const globalSync = useGlobalSync()
|
||||
const layout = useLayout()
|
||||
@@ -117,27 +116,31 @@ export default function Layout(props: ParentProps) {
|
||||
}
|
||||
const colorSchemeLabel = (scheme: ColorScheme) => language.t(colorSchemeKey[scheme])
|
||||
|
||||
const [state, setState] = createStore({
|
||||
autoselect: !params.dir,
|
||||
busyWorkspaces: new Set<string>(),
|
||||
hoverSession: undefined as string | undefined,
|
||||
hoverProject: undefined as string | undefined,
|
||||
scrollSessionKey: undefined as string | undefined,
|
||||
nav: undefined as HTMLElement | undefined,
|
||||
})
|
||||
|
||||
const [editor, setEditor] = createStore({
|
||||
active: "" as string,
|
||||
value: "",
|
||||
})
|
||||
const [busyWorkspaces, setBusyWorkspaces] = createSignal<Set<string>>(new Set())
|
||||
const setBusy = (directory: string, value: boolean) => {
|
||||
const key = workspaceKey(directory)
|
||||
setBusyWorkspaces((prev) => {
|
||||
setState("busyWorkspaces", (prev) => {
|
||||
const next = new Set(prev)
|
||||
if (value) next.add(key)
|
||||
else next.delete(key)
|
||||
return next
|
||||
})
|
||||
}
|
||||
const isBusy = (directory: string) => busyWorkspaces().has(workspaceKey(directory))
|
||||
const isBusy = (directory: string) => state.busyWorkspaces.has(workspaceKey(directory))
|
||||
const editorRef = { current: undefined as HTMLInputElement | undefined }
|
||||
|
||||
const [hoverSession, setHoverSession] = createSignal<string | undefined>()
|
||||
const [hoverProject, setHoverProject] = createSignal<string | undefined>()
|
||||
|
||||
const [nav, setNav] = createSignal<HTMLElement | undefined>(undefined)
|
||||
const navLeave = { current: undefined as number | undefined }
|
||||
|
||||
onCleanup(() => {
|
||||
@@ -145,18 +148,18 @@ export default function Layout(props: ParentProps) {
|
||||
clearTimeout(navLeave.current)
|
||||
})
|
||||
|
||||
const sidebarHovering = createMemo(() => !layout.sidebar.opened() && hoverProject() !== undefined)
|
||||
const sidebarHovering = createMemo(() => !layout.sidebar.opened() && state.hoverProject !== undefined)
|
||||
const sidebarExpanded = createMemo(() => layout.sidebar.opened() || sidebarHovering())
|
||||
|
||||
const hoverProjectData = createMemo(() => {
|
||||
const id = hoverProject()
|
||||
const id = state.hoverProject
|
||||
if (!id) return
|
||||
return layout.projects.list().find((project) => project.worktree === id)
|
||||
})
|
||||
|
||||
createEffect(() => {
|
||||
if (!layout.sidebar.opened()) return
|
||||
setHoverProject(undefined)
|
||||
setState("hoverProject", undefined)
|
||||
})
|
||||
|
||||
createEffect(
|
||||
@@ -164,9 +167,9 @@ export default function Layout(props: ParentProps) {
|
||||
() => ({ dir: params.dir, id: params.id }),
|
||||
() => {
|
||||
if (layout.sidebar.opened()) return
|
||||
if (!hoverProject()) return
|
||||
setHoverSession(undefined)
|
||||
setHoverProject(undefined)
|
||||
if (!state.hoverProject) return
|
||||
setState("hoverSession", undefined)
|
||||
setState("hoverProject", undefined)
|
||||
},
|
||||
{ defer: true },
|
||||
),
|
||||
@@ -175,7 +178,7 @@ export default function Layout(props: ParentProps) {
|
||||
const autoselecting = createMemo(() => {
|
||||
if (params.dir) return false
|
||||
if (initialDir) return false
|
||||
if (!autoselect()) return false
|
||||
if (!state.autoselect) return false
|
||||
if (!pageReady()) return true
|
||||
if (!layoutReady()) return true
|
||||
const list = layout.projects.list()
|
||||
@@ -483,20 +486,18 @@ export default function Layout(props: ParentProps) {
|
||||
}
|
||||
}
|
||||
|
||||
const [scrollSessionKey, setScrollSessionKey] = createSignal<string | undefined>(undefined)
|
||||
|
||||
function scrollToSession(sessionId: string, sessionKey: string) {
|
||||
if (!scrollContainerRef) return
|
||||
if (scrollSessionKey() === sessionKey) return
|
||||
if (state.scrollSessionKey === sessionKey) return
|
||||
const element = scrollContainerRef.querySelector(`[data-session-id="${sessionId}"]`)
|
||||
if (!element) return
|
||||
const containerRect = scrollContainerRef.getBoundingClientRect()
|
||||
const elementRect = element.getBoundingClientRect()
|
||||
if (elementRect.top >= containerRect.top && elementRect.bottom <= containerRect.bottom) {
|
||||
setScrollSessionKey(sessionKey)
|
||||
setState("scrollSessionKey", sessionKey)
|
||||
return
|
||||
}
|
||||
setScrollSessionKey(sessionKey)
|
||||
setState("scrollSessionKey", sessionKey)
|
||||
element.scrollIntoView({ block: "nearest", behavior: "smooth" })
|
||||
}
|
||||
|
||||
@@ -544,7 +545,7 @@ export default function Layout(props: ParentProps) {
|
||||
(value) => {
|
||||
if (!value.ready) return
|
||||
if (!value.layoutReady) return
|
||||
if (!autoselect()) return
|
||||
if (!state.autoselect) return
|
||||
if (initialDir) return
|
||||
if (value.dir) return
|
||||
if (value.list.length === 0) return
|
||||
@@ -552,7 +553,7 @@ export default function Layout(props: ParentProps) {
|
||||
const last = server.projects.last()
|
||||
const next = value.list.find((project) => project.worktree === last) ?? value.list[0]
|
||||
if (!next) return
|
||||
setAutoselect(false)
|
||||
setState("autoselect", false)
|
||||
openProject(next.worktree, false)
|
||||
navigateToProject(next.worktree)
|
||||
},
|
||||
@@ -1066,8 +1067,8 @@ export default function Layout(props: ParentProps) {
|
||||
function navigateToProject(directory: string | undefined) {
|
||||
if (!directory) return
|
||||
if (!layout.sidebar.opened()) {
|
||||
setHoverSession(undefined)
|
||||
setHoverProject(undefined)
|
||||
setState("hoverSession", undefined)
|
||||
setState("hoverProject", undefined)
|
||||
}
|
||||
server.projects.touch(directory)
|
||||
const lastSession = store.lastSession[directory]
|
||||
@@ -1078,8 +1079,8 @@ export default function Layout(props: ParentProps) {
|
||||
function navigateToSession(session: Session | undefined) {
|
||||
if (!session) return
|
||||
if (!layout.sidebar.opened()) {
|
||||
setHoverSession(undefined)
|
||||
setHoverProject(undefined)
|
||||
setState("hoverSession", undefined)
|
||||
setState("hoverProject", undefined)
|
||||
}
|
||||
navigate(`/${base64Encode(session.directory)}/session/${session.id}`)
|
||||
layout.mobileSidebar.hide()
|
||||
@@ -1472,7 +1473,7 @@ export default function Layout(props: ParentProps) {
|
||||
function handleDragStart(event: unknown) {
|
||||
const id = getDraggableId(event)
|
||||
if (!id) return
|
||||
setHoverProject(undefined)
|
||||
setState("hoverProject", undefined)
|
||||
setStore("activeProject", id)
|
||||
}
|
||||
|
||||
@@ -1632,8 +1633,10 @@ export default function Layout(props: ParentProps) {
|
||||
const hoverAllowed = createMemo(() => !props.mobile && sidebarExpanded())
|
||||
const hoverEnabled = createMemo(() => (props.popover ?? true) && hoverAllowed())
|
||||
const isActive = createMemo(() => props.session.id === params.id)
|
||||
const [menuOpen, setMenuOpen] = createSignal(false)
|
||||
const [pendingRename, setPendingRename] = createSignal(false)
|
||||
const [menu, setMenu] = createStore({
|
||||
open: false,
|
||||
pendingRename: false,
|
||||
})
|
||||
|
||||
const messageLabel = (message: Message) => {
|
||||
const parts = sessionStore.part[message.id] ?? []
|
||||
@@ -1644,13 +1647,13 @@ export default function Layout(props: ParentProps) {
|
||||
const item = (
|
||||
<A
|
||||
href={`${props.slug}/session/${props.session.id}`}
|
||||
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] ${menu.open ? "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")}
|
||||
onFocus={() => prefetchSession(props.session, "high")}
|
||||
onClick={() => {
|
||||
setHoverSession(undefined)
|
||||
setState("hoverSession", undefined)
|
||||
if (layout.sidebar.opened()) return
|
||||
queueMicrotask(() => setHoverProject(undefined))
|
||||
queueMicrotask(() => setState("hoverProject", undefined))
|
||||
}}
|
||||
>
|
||||
<div class="flex items-center gap-1 w-full">
|
||||
@@ -1713,9 +1716,9 @@ export default function Layout(props: ParentProps) {
|
||||
gutter={16}
|
||||
shift={-2}
|
||||
trigger={item}
|
||||
mount={!props.mobile ? nav() : undefined}
|
||||
open={hoverSession() === props.session.id}
|
||||
onOpenChange={(open) => setHoverSession(open ? props.session.id : undefined)}
|
||||
mount={!props.mobile ? state.nav : undefined}
|
||||
open={state.hoverSession === props.session.id}
|
||||
onOpenChange={(open) => setState("hoverSession", open ? props.session.id : undefined)}
|
||||
>
|
||||
<Show
|
||||
when={hoverReady()}
|
||||
@@ -1745,13 +1748,13 @@ export default function Layout(props: ParentProps) {
|
||||
<div
|
||||
class={`absolute ${props.dense ? "top-0.5 right-0.5" : "top-1 right-1"} flex items-center gap-0.5 transition-opacity`}
|
||||
classList={{
|
||||
"opacity-100 pointer-events-auto": menuOpen(),
|
||||
"opacity-0 pointer-events-none": !menuOpen(),
|
||||
"opacity-100 pointer-events-auto": menu.open,
|
||||
"opacity-0 pointer-events-none": !menu.open,
|
||||
"group-hover/session:opacity-100 group-hover/session:pointer-events-auto": true,
|
||||
"group-focus-within/session:opacity-100 group-focus-within/session:pointer-events-auto": true,
|
||||
}}
|
||||
>
|
||||
<DropdownMenu modal={!sidebarHovering()} open={menuOpen()} onOpenChange={setMenuOpen}>
|
||||
<DropdownMenu modal={!sidebarHovering()} open={menu.open} onOpenChange={(open) => setMenu("open", open)}>
|
||||
<Tooltip value={language.t("common.moreOptions")} placement="top">
|
||||
<DropdownMenu.Trigger
|
||||
as={IconButton}
|
||||
@@ -1761,19 +1764,19 @@ export default function Layout(props: ParentProps) {
|
||||
aria-label={language.t("common.moreOptions")}
|
||||
/>
|
||||
</Tooltip>
|
||||
<DropdownMenu.Portal mount={!props.mobile ? nav() : undefined}>
|
||||
<DropdownMenu.Portal mount={!props.mobile ? state.nav : undefined}>
|
||||
<DropdownMenu.Content
|
||||
onCloseAutoFocus={(event) => {
|
||||
if (!pendingRename()) return
|
||||
if (!menu.pendingRename) return
|
||||
event.preventDefault()
|
||||
setPendingRename(false)
|
||||
setMenu("pendingRename", false)
|
||||
openEditor(`session:${props.session.id}`, props.session.title)
|
||||
}}
|
||||
>
|
||||
<DropdownMenu.Item
|
||||
onSelect={() => {
|
||||
setPendingRename(true)
|
||||
setMenuOpen(false)
|
||||
setMenu("pendingRename", true)
|
||||
setMenu("open", false)
|
||||
}}
|
||||
>
|
||||
<DropdownMenu.ItemLabel>{language.t("common.rename")}</DropdownMenu.ItemLabel>
|
||||
@@ -1802,9 +1805,9 @@ export default function Layout(props: ParentProps) {
|
||||
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"}`}
|
||||
onClick={() => {
|
||||
setHoverSession(undefined)
|
||||
setState("hoverSession", undefined)
|
||||
if (layout.sidebar.opened()) return
|
||||
queueMicrotask(() => setHoverProject(undefined))
|
||||
queueMicrotask(() => setState("hoverProject", undefined))
|
||||
}}
|
||||
>
|
||||
<div class="flex items-center gap-1 w-full">
|
||||
@@ -1884,8 +1887,10 @@ export default function Layout(props: ParentProps) {
|
||||
const SortableWorkspace = (props: { directory: string; project: LocalProject; mobile?: boolean }): JSX.Element => {
|
||||
const sortable = createSortable(props.directory)
|
||||
const [workspaceStore, setWorkspaceStore] = globalSync.child(props.directory, { bootstrap: false })
|
||||
const [menuOpen, setMenuOpen] = createSignal(false)
|
||||
const [pendingRename, setPendingRename] = createSignal(false)
|
||||
const [menu, setMenu] = createStore({
|
||||
open: false,
|
||||
pendingRename: false,
|
||||
})
|
||||
const slug = createMemo(() => base64Encode(props.directory))
|
||||
const sessions = createMemo(() =>
|
||||
workspaceStore.session
|
||||
@@ -1995,13 +2000,17 @@ export default function Layout(props: ParentProps) {
|
||||
<div
|
||||
class="absolute right-1 top-1/2 -translate-y-1/2 flex items-center gap-0.5 transition-opacity"
|
||||
classList={{
|
||||
"opacity-100 pointer-events-auto": menuOpen(),
|
||||
"opacity-0 pointer-events-none": !menuOpen(),
|
||||
"opacity-100 pointer-events-auto": menu.open,
|
||||
"opacity-0 pointer-events-none": !menu.open,
|
||||
"group-hover/workspace:opacity-100 group-hover/workspace:pointer-events-auto": true,
|
||||
"group-focus-within/workspace:opacity-100 group-focus-within/workspace:pointer-events-auto": true,
|
||||
}}
|
||||
>
|
||||
<DropdownMenu modal={!sidebarHovering()} open={menuOpen()} onOpenChange={setMenuOpen}>
|
||||
<DropdownMenu
|
||||
modal={!sidebarHovering()}
|
||||
open={menu.open}
|
||||
onOpenChange={(open) => setMenu("open", open)}
|
||||
>
|
||||
<Tooltip value={language.t("common.moreOptions")} placement="top">
|
||||
<DropdownMenu.Trigger
|
||||
as={IconButton}
|
||||
@@ -2011,20 +2020,20 @@ export default function Layout(props: ParentProps) {
|
||||
aria-label={language.t("common.moreOptions")}
|
||||
/>
|
||||
</Tooltip>
|
||||
<DropdownMenu.Portal mount={!props.mobile ? nav() : undefined}>
|
||||
<DropdownMenu.Portal mount={!props.mobile ? state.nav : undefined}>
|
||||
<DropdownMenu.Content
|
||||
onCloseAutoFocus={(event) => {
|
||||
if (!pendingRename()) return
|
||||
if (!menu.pendingRename) return
|
||||
event.preventDefault()
|
||||
setPendingRename(false)
|
||||
setMenu("pendingRename", false)
|
||||
openEditor(`workspace:${props.directory}`, workspaceValue())
|
||||
}}
|
||||
>
|
||||
<DropdownMenu.Item
|
||||
disabled={local()}
|
||||
onSelect={() => {
|
||||
setPendingRename(true)
|
||||
setMenuOpen(false)
|
||||
setMenu("pendingRename", true)
|
||||
setMenu("open", false)
|
||||
}}
|
||||
>
|
||||
<DropdownMenu.ItemLabel>{language.t("common.rename")}</DropdownMenu.ItemLabel>
|
||||
@@ -2103,7 +2112,7 @@ export default function Layout(props: ParentProps) {
|
||||
|
||||
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))
|
||||
const active = createMemo(() => (preview() ? open() : overlay() && state.hoverProject === props.project.worktree))
|
||||
|
||||
createEffect(() => {
|
||||
if (preview()) return
|
||||
@@ -2155,14 +2164,14 @@ export default function Layout(props: ParentProps) {
|
||||
onMouseEnter={() => {
|
||||
if (!overlay()) return
|
||||
globalSync.child(props.project.worktree)
|
||||
setHoverProject(props.project.worktree)
|
||||
setHoverSession(undefined)
|
||||
setState("hoverProject", props.project.worktree)
|
||||
setState("hoverSession", undefined)
|
||||
}}
|
||||
onFocus={() => {
|
||||
if (!overlay()) return
|
||||
globalSync.child(props.project.worktree)
|
||||
setHoverProject(props.project.worktree)
|
||||
setHoverSession(undefined)
|
||||
setState("hoverProject", props.project.worktree)
|
||||
setState("hoverSession", undefined)
|
||||
}}
|
||||
onClick={() => navigateToProject(props.project.worktree)}
|
||||
onBlur={() => setOpen(false)}
|
||||
@@ -2184,7 +2193,7 @@ export default function Layout(props: ParentProps) {
|
||||
trigger={trigger}
|
||||
onOpenChange={(value) => {
|
||||
setOpen(value)
|
||||
if (value) setHoverSession(undefined)
|
||||
if (value) setState("hoverSession", undefined)
|
||||
}}
|
||||
>
|
||||
<div class="-m-3 p-2 flex flex-col w-72">
|
||||
@@ -2323,8 +2332,8 @@ export default function Layout(props: ParentProps) {
|
||||
|
||||
const createWorkspace = async (project: LocalProject) => {
|
||||
if (!layout.sidebar.opened()) {
|
||||
setHoverSession(undefined)
|
||||
setHoverProject(undefined)
|
||||
setState("hoverSession", undefined)
|
||||
setState("hoverProject", undefined)
|
||||
}
|
||||
const created = await globalSDK.client.worktree
|
||||
.create({ directory: project.worktree })
|
||||
@@ -2427,7 +2436,7 @@ export default function Layout(props: ParentProps) {
|
||||
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 mount={!panelProps.mobile ? nav() : undefined}>
|
||||
<DropdownMenu.Portal mount={!panelProps.mobile ? state.nav : undefined}>
|
||||
<DropdownMenu.Content class="mt-1">
|
||||
<DropdownMenu.Item onSelect={() => dialog.show(() => <DialogEditProject project={p} />)}>
|
||||
<DropdownMenu.ItemLabel>{language.t("common.edit")}</DropdownMenu.ItemLabel>
|
||||
@@ -2476,8 +2485,8 @@ export default function Layout(props: ParentProps) {
|
||||
class="w-full"
|
||||
onClick={() => {
|
||||
if (!layout.sidebar.opened()) {
|
||||
setHoverSession(undefined)
|
||||
setHoverProject(undefined)
|
||||
setState("hoverSession", undefined)
|
||||
setState("hoverProject", undefined)
|
||||
}
|
||||
navigate(`/${base64Encode(p.worktree)}/session`)
|
||||
layout.mobileSidebar.hide()
|
||||
@@ -2668,7 +2677,7 @@ export default function Layout(props: ParentProps) {
|
||||
}}
|
||||
style={{ width: layout.sidebar.opened() ? `${Math.max(layout.sidebar.width(), 244)}px` : "64px" }}
|
||||
ref={(el) => {
|
||||
setNav(el)
|
||||
setState("nav", el)
|
||||
}}
|
||||
onMouseEnter={() => {
|
||||
if (navLeave.current === undefined) return
|
||||
@@ -2681,8 +2690,8 @@ export default function Layout(props: ParentProps) {
|
||||
if (navLeave.current !== undefined) clearTimeout(navLeave.current)
|
||||
navLeave.current = window.setTimeout(() => {
|
||||
navLeave.current = undefined
|
||||
setHoverProject(undefined)
|
||||
setHoverSession(undefined)
|
||||
setState("hoverProject", undefined)
|
||||
setState("hoverSession", undefined)
|
||||
}, 300)
|
||||
}}
|
||||
>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { For, onCleanup, onMount, Show, Match, Switch, createMemo, createEffect, on, createSignal } from "solid-js"
|
||||
import { For, onCleanup, onMount, Show, Match, Switch, createMemo, createEffect, on } from "solid-js"
|
||||
import { createMediaQuery } from "@solid-primitives/media"
|
||||
import { createResizeObserver } from "@solid-primitives/resize-observer"
|
||||
import { Dynamic } from "solid-js/web"
|
||||
@@ -198,12 +198,17 @@ export default function Page() {
|
||||
return next
|
||||
})
|
||||
|
||||
const [responding, setResponding] = createSignal(false)
|
||||
const [ui, setUi] = createStore({
|
||||
responding: false,
|
||||
pendingMessage: undefined as string | undefined,
|
||||
scrollGesture: 0,
|
||||
autoCreated: false,
|
||||
})
|
||||
|
||||
createEffect(
|
||||
on(
|
||||
() => request()?.id,
|
||||
() => setResponding(false),
|
||||
() => setUi("responding", false),
|
||||
{ defer: true },
|
||||
),
|
||||
)
|
||||
@@ -211,18 +216,17 @@ export default function Page() {
|
||||
const decide = (response: "once" | "always" | "reject") => {
|
||||
const perm = request()
|
||||
if (!perm) return
|
||||
if (responding()) return
|
||||
if (ui.responding) return
|
||||
|
||||
setResponding(true)
|
||||
setUi("responding", true)
|
||||
sdk.client.permission
|
||||
.respond({ sessionID: perm.sessionID, permissionID: perm.id, response })
|
||||
.catch((err: unknown) => {
|
||||
const message = err instanceof Error ? err.message : String(err)
|
||||
showToast({ title: language.t("common.requestFailed"), description: message })
|
||||
})
|
||||
.finally(() => setResponding(false))
|
||||
.finally(() => setUi("responding", false))
|
||||
}
|
||||
const [pendingMessage, setPendingMessage] = createSignal<string | undefined>(undefined)
|
||||
const sessionKey = createMemo(() => `${params.dir}${params.id ? "/" + params.id : ""}`)
|
||||
const tabs = createMemo(() => layout.tabs(sessionKey))
|
||||
const view = createMemo(() => layout.view(sessionKey))
|
||||
@@ -439,7 +443,6 @@ export default function Page() {
|
||||
let promptDock: HTMLDivElement | undefined
|
||||
let scroller: HTMLDivElement | undefined
|
||||
|
||||
const [scrollGesture, setScrollGesture] = createSignal(0)
|
||||
const scrollGestureWindowMs = 250
|
||||
|
||||
const markScrollGesture = (target?: EventTarget | null) => {
|
||||
@@ -450,26 +453,24 @@ export default function Page() {
|
||||
const nested = el?.closest("[data-scrollable]")
|
||||
if (nested && nested !== root) return
|
||||
|
||||
setScrollGesture(Date.now())
|
||||
setUi("scrollGesture", Date.now())
|
||||
}
|
||||
|
||||
const hasScrollGesture = () => Date.now() - scrollGesture() < scrollGestureWindowMs
|
||||
const hasScrollGesture = () => Date.now() - ui.scrollGesture < scrollGestureWindowMs
|
||||
|
||||
createEffect(() => {
|
||||
if (!params.id) return
|
||||
sync.session.sync(params.id)
|
||||
})
|
||||
|
||||
const [autoCreated, setAutoCreated] = createSignal(false)
|
||||
|
||||
createEffect(() => {
|
||||
if (!view().terminal.opened()) {
|
||||
setAutoCreated(false)
|
||||
setUi("autoCreated", false)
|
||||
return
|
||||
}
|
||||
if (!terminal.ready() || terminal.all().length !== 0 || autoCreated()) return
|
||||
if (!terminal.ready() || terminal.all().length !== 0 || ui.autoCreated) return
|
||||
terminal.new()
|
||||
setAutoCreated(true)
|
||||
setUi("autoCreated", true)
|
||||
})
|
||||
|
||||
createEffect(
|
||||
@@ -1019,9 +1020,18 @@ export default function Page() {
|
||||
|
||||
const showTabs = createMemo(() => view().reviewPanel.opened())
|
||||
|
||||
const [fileTreeTab, setFileTreeTab] = createSignal<"changes" | "all">("changes")
|
||||
const [reviewScroll, setReviewScroll] = createSignal<HTMLDivElement | undefined>(undefined)
|
||||
const [pendingDiff, setPendingDiff] = createSignal<string | undefined>(undefined)
|
||||
const [tree, setTree] = createStore({
|
||||
fileTreeTab: "changes" as "changes" | "all",
|
||||
reviewScroll: undefined as HTMLDivElement | undefined,
|
||||
pendingDiff: undefined as string | undefined,
|
||||
})
|
||||
|
||||
const fileTreeTab = () => tree.fileTreeTab
|
||||
const setFileTreeTab = (value: "changes" | "all") => setTree("fileTreeTab", value)
|
||||
const reviewScroll = () => tree.reviewScroll
|
||||
const setReviewScroll = (value: HTMLDivElement | undefined) => setTree("reviewScroll", value)
|
||||
const pendingDiff = () => tree.pendingDiff
|
||||
const setPendingDiff = (value: string | undefined) => setTree("pendingDiff", value)
|
||||
|
||||
createEffect(() => {
|
||||
if (!layout.fileTree.opened()) return
|
||||
@@ -1316,7 +1326,7 @@ export default function Page() {
|
||||
if (pendingSessionID !== sessionID) return
|
||||
|
||||
sessionStorage.removeItem("opencode.pendingMessage")
|
||||
setPendingMessage(messageID)
|
||||
setUi("pendingMessage", messageID)
|
||||
})
|
||||
|
||||
const scrollToElement = (el: HTMLElement, behavior: ScrollBehavior) => {
|
||||
@@ -1484,7 +1494,7 @@ export default function Page() {
|
||||
store.turnStart
|
||||
|
||||
const targetId =
|
||||
pendingMessage() ??
|
||||
ui.pendingMessage ??
|
||||
(() => {
|
||||
const hash = window.location.hash.slice(1)
|
||||
const match = hash.match(/^message-(.+)$/)
|
||||
@@ -1496,7 +1506,7 @@ export default function Page() {
|
||||
|
||||
const msg = visibleUserMessages().find((m) => m.id === targetId)
|
||||
if (!msg) return
|
||||
if (pendingMessage() === targetId) setPendingMessage(undefined)
|
||||
if (ui.pendingMessage === targetId) setUi("pendingMessage", undefined)
|
||||
requestAnimationFrame(() => scrollToMessage(msg, "auto"))
|
||||
})
|
||||
|
||||
@@ -1877,18 +1887,18 @@ export default function Page() {
|
||||
</BasicTool>
|
||||
<div data-component="permission-prompt">
|
||||
<div data-slot="permission-actions">
|
||||
<Button variant="ghost" size="small" onClick={() => decide("reject")} disabled={responding()}>
|
||||
<Button variant="ghost" size="small" onClick={() => decide("reject")} disabled={ui.responding}>
|
||||
{language.t("ui.permission.deny")}
|
||||
</Button>
|
||||
<Button
|
||||
variant="secondary"
|
||||
size="small"
|
||||
onClick={() => decide("always")}
|
||||
disabled={responding()}
|
||||
disabled={ui.responding}
|
||||
>
|
||||
{language.t("ui.permission.allowAlways")}
|
||||
</Button>
|
||||
<Button variant="primary" size="small" onClick={() => decide("once")} disabled={responding()}>
|
||||
<Button variant="primary" size="small" onClick={() => decide("once")} disabled={ui.responding}>
|
||||
{language.t("ui.permission.allowOnce")}
|
||||
</Button>
|
||||
</div>
|
||||
@@ -2144,11 +2154,40 @@ export default function Page() {
|
||||
|
||||
const commentedLines = createMemo(() => fileComments().map((comment) => comment.selection))
|
||||
|
||||
const [openedComment, setOpenedComment] = createSignal<string | null>(null)
|
||||
const [commenting, setCommenting] = createSignal<SelectedLineRange | null>(null)
|
||||
const [draft, setDraft] = createSignal("")
|
||||
const [positions, setPositions] = createSignal<Record<string, number>>({})
|
||||
const [draftTop, setDraftTop] = createSignal<number | undefined>(undefined)
|
||||
const [note, setNote] = createStore({
|
||||
openedComment: null as string | null,
|
||||
commenting: null as SelectedLineRange | null,
|
||||
draft: "",
|
||||
positions: {} as Record<string, number>,
|
||||
draftTop: undefined as number | undefined,
|
||||
})
|
||||
|
||||
const openedComment = () => note.openedComment
|
||||
const setOpenedComment = (
|
||||
value:
|
||||
| typeof note.openedComment
|
||||
| ((value: typeof note.openedComment) => typeof note.openedComment),
|
||||
) => setNote("openedComment", value)
|
||||
|
||||
const commenting = () => note.commenting
|
||||
const setCommenting = (
|
||||
value: typeof note.commenting | ((value: typeof note.commenting) => typeof note.commenting),
|
||||
) => setNote("commenting", value)
|
||||
|
||||
const draft = () => note.draft
|
||||
const setDraft = (
|
||||
value: typeof note.draft | ((value: typeof note.draft) => typeof note.draft),
|
||||
) => setNote("draft", value)
|
||||
|
||||
const positions = () => note.positions
|
||||
const setPositions = (
|
||||
value: typeof note.positions | ((value: typeof note.positions) => typeof note.positions),
|
||||
) => setNote("positions", value)
|
||||
|
||||
const draftTop = () => note.draftTop
|
||||
const setDraftTop = (
|
||||
value: typeof note.draftTop | ((value: typeof note.draftTop) => typeof note.draftTop),
|
||||
) => setNote("draftTop", value)
|
||||
|
||||
const commentLabel = (range: SelectedLineRange) => {
|
||||
const start = Math.min(range.start, range.end)
|
||||
@@ -2695,7 +2734,7 @@ export default function Page() {
|
||||
terminal={pty}
|
||||
onClose={() => {
|
||||
view().terminal.close()
|
||||
setAutoCreated(false)
|
||||
setUi("autoCreated", false)
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
|
||||
Reference in New Issue
Block a user