refactor(desktop): enhance project tile interaction with suppress hover functionality (#15214)
This commit is contained in:
@@ -1,4 +1,5 @@
|
|||||||
import { createEffect, createMemo, createSignal, For, Show, type Accessor, type JSX } from "solid-js"
|
import { createEffect, createMemo, For, Show, type Accessor, type JSX } from "solid-js"
|
||||||
|
import { createStore } from "solid-js/store"
|
||||||
import { base64Encode } from "@opencode-ai/util/encode"
|
import { base64Encode } from "@opencode-ai/util/encode"
|
||||||
import { Button } from "@opencode-ai/ui/button"
|
import { Button } from "@opencode-ai/ui/button"
|
||||||
import { ContextMenu } from "@opencode-ai/ui/context-menu"
|
import { ContextMenu } from "@opencode-ai/ui/context-menu"
|
||||||
@@ -7,7 +8,7 @@ import { Icon } from "@opencode-ai/ui/icon"
|
|||||||
import { IconButton } from "@opencode-ai/ui/icon-button"
|
import { IconButton } from "@opencode-ai/ui/icon-button"
|
||||||
import { Tooltip } from "@opencode-ai/ui/tooltip"
|
import { Tooltip } from "@opencode-ai/ui/tooltip"
|
||||||
import { createSortable } from "@thisbeyond/solid-dnd"
|
import { createSortable } from "@thisbeyond/solid-dnd"
|
||||||
import { type LocalProject } from "@/context/layout"
|
import { useLayout, type LocalProject } from "@/context/layout"
|
||||||
import { useGlobalSync } from "@/context/global-sync"
|
import { useGlobalSync } from "@/context/global-sync"
|
||||||
import { useLanguage } from "@/context/language"
|
import { useLanguage } from "@/context/language"
|
||||||
import { useNotification } from "@/context/notification"
|
import { useNotification } from "@/context/notification"
|
||||||
@@ -60,6 +61,7 @@ const ProjectTile = (props: {
|
|||||||
selected: Accessor<boolean>
|
selected: Accessor<boolean>
|
||||||
active: Accessor<boolean>
|
active: Accessor<boolean>
|
||||||
overlay: Accessor<boolean>
|
overlay: Accessor<boolean>
|
||||||
|
suppressHover: Accessor<boolean>
|
||||||
dirs: Accessor<string[]>
|
dirs: Accessor<string[]>
|
||||||
onProjectMouseEnter: (worktree: string, event: MouseEvent) => void
|
onProjectMouseEnter: (worktree: string, event: MouseEvent) => void
|
||||||
onProjectMouseLeave: (worktree: string) => void
|
onProjectMouseLeave: (worktree: string) => void
|
||||||
@@ -71,9 +73,11 @@ const ProjectTile = (props: {
|
|||||||
closeProject: (directory: string) => void
|
closeProject: (directory: string) => void
|
||||||
setMenu: (value: boolean) => void
|
setMenu: (value: boolean) => void
|
||||||
setOpen: (value: boolean) => void
|
setOpen: (value: boolean) => void
|
||||||
|
setSuppressHover: (value: boolean) => void
|
||||||
language: ReturnType<typeof useLanguage>
|
language: ReturnType<typeof useLanguage>
|
||||||
}): JSX.Element => {
|
}): JSX.Element => {
|
||||||
const notification = useNotification()
|
const notification = useNotification()
|
||||||
|
const layout = useLayout()
|
||||||
const unseenCount = createMemo(() =>
|
const unseenCount = createMemo(() =>
|
||||||
props.dirs().reduce((total, directory) => total + notification.project.unseenCount(directory), 0),
|
props.dirs().reduce((total, directory) => total + notification.project.unseenCount(directory), 0),
|
||||||
)
|
)
|
||||||
@@ -107,17 +111,28 @@ const ProjectTile = (props: {
|
|||||||
}}
|
}}
|
||||||
onMouseEnter={(event: MouseEvent) => {
|
onMouseEnter={(event: MouseEvent) => {
|
||||||
if (!props.overlay()) return
|
if (!props.overlay()) return
|
||||||
|
if (props.suppressHover()) return
|
||||||
props.onProjectMouseEnter(props.project.worktree, event)
|
props.onProjectMouseEnter(props.project.worktree, event)
|
||||||
}}
|
}}
|
||||||
onMouseLeave={() => {
|
onMouseLeave={() => {
|
||||||
|
if (props.suppressHover()) props.setSuppressHover(false)
|
||||||
if (!props.overlay()) return
|
if (!props.overlay()) return
|
||||||
props.onProjectMouseLeave(props.project.worktree)
|
props.onProjectMouseLeave(props.project.worktree)
|
||||||
}}
|
}}
|
||||||
onFocus={() => {
|
onFocus={() => {
|
||||||
if (!props.overlay()) return
|
if (!props.overlay()) return
|
||||||
|
if (props.suppressHover()) return
|
||||||
props.onProjectFocus(props.project.worktree)
|
props.onProjectFocus(props.project.worktree)
|
||||||
}}
|
}}
|
||||||
onClick={() => props.navigateToProject(props.project.worktree)}
|
onClick={() => {
|
||||||
|
if (props.selected()) {
|
||||||
|
props.setSuppressHover(true)
|
||||||
|
layout.sidebar.toggle()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
props.setSuppressHover(false)
|
||||||
|
props.navigateToProject(props.project.worktree)
|
||||||
|
}}
|
||||||
onBlur={() => props.setOpen(false)}
|
onBlur={() => props.setOpen(false)}
|
||||||
>
|
>
|
||||||
<ProjectIcon project={props.project} notify />
|
<ProjectIcon project={props.project} notify />
|
||||||
@@ -278,16 +293,19 @@ export const SortableProject = (props: {
|
|||||||
const workspaces = createMemo(() => props.ctx.workspaceIds(props.project).slice(0, 2))
|
const workspaces = createMemo(() => props.ctx.workspaceIds(props.project).slice(0, 2))
|
||||||
const workspaceEnabled = createMemo(() => props.ctx.workspacesEnabled(props.project))
|
const workspaceEnabled = createMemo(() => props.ctx.workspacesEnabled(props.project))
|
||||||
const dirs = createMemo(() => props.ctx.workspaceIds(props.project))
|
const dirs = createMemo(() => props.ctx.workspaceIds(props.project))
|
||||||
const [open, setOpen] = createSignal(false)
|
const [state, setState] = createStore({
|
||||||
const [menu, setMenu] = createSignal(false)
|
open: false,
|
||||||
|
menu: false,
|
||||||
|
suppressHover: false,
|
||||||
|
})
|
||||||
|
|
||||||
const preview = createMemo(() => !props.mobile && props.ctx.sidebarOpened())
|
const preview = createMemo(() => !props.mobile && props.ctx.sidebarOpened())
|
||||||
const overlay = createMemo(() => !props.mobile && !props.ctx.sidebarOpened())
|
const overlay = createMemo(() => !props.mobile && !props.ctx.sidebarOpened())
|
||||||
const active = createMemo(() =>
|
const active = createMemo(() =>
|
||||||
projectTileActive({
|
projectTileActive({
|
||||||
menu: menu(),
|
menu: state.menu,
|
||||||
preview: preview(),
|
preview: preview(),
|
||||||
open: open(),
|
open: state.open,
|
||||||
overlay: overlay(),
|
overlay: overlay(),
|
||||||
hoverProject: props.ctx.hoverProject(),
|
hoverProject: props.ctx.hoverProject(),
|
||||||
worktree: props.project.worktree,
|
worktree: props.project.worktree,
|
||||||
@@ -296,8 +314,14 @@ export const SortableProject = (props: {
|
|||||||
|
|
||||||
createEffect(() => {
|
createEffect(() => {
|
||||||
if (preview()) return
|
if (preview()) return
|
||||||
if (!open()) return
|
if (!state.open) return
|
||||||
setOpen(false)
|
setState("open", false)
|
||||||
|
})
|
||||||
|
|
||||||
|
createEffect(() => {
|
||||||
|
if (!selected()) return
|
||||||
|
if (!state.open) return
|
||||||
|
setState("open", false)
|
||||||
})
|
})
|
||||||
|
|
||||||
const label = (directory: string) => {
|
const label = (directory: string) => {
|
||||||
@@ -328,6 +352,7 @@ export const SortableProject = (props: {
|
|||||||
selected={selected}
|
selected={selected}
|
||||||
active={active}
|
active={active}
|
||||||
overlay={overlay}
|
overlay={overlay}
|
||||||
|
suppressHover={() => state.suppressHover}
|
||||||
dirs={dirs}
|
dirs={dirs}
|
||||||
onProjectMouseEnter={props.ctx.onProjectMouseEnter}
|
onProjectMouseEnter={props.ctx.onProjectMouseEnter}
|
||||||
onProjectMouseLeave={props.ctx.onProjectMouseLeave}
|
onProjectMouseLeave={props.ctx.onProjectMouseLeave}
|
||||||
@@ -337,8 +362,9 @@ export const SortableProject = (props: {
|
|||||||
toggleProjectWorkspaces={props.ctx.toggleProjectWorkspaces}
|
toggleProjectWorkspaces={props.ctx.toggleProjectWorkspaces}
|
||||||
workspacesEnabled={props.ctx.workspacesEnabled}
|
workspacesEnabled={props.ctx.workspacesEnabled}
|
||||||
closeProject={props.ctx.closeProject}
|
closeProject={props.ctx.closeProject}
|
||||||
setMenu={setMenu}
|
setMenu={(value) => setState("menu", value)}
|
||||||
setOpen={setOpen}
|
setOpen={(value) => setState("open", value)}
|
||||||
|
setSuppressHover={(value) => setState("suppressHover", value)}
|
||||||
language={language}
|
language={language}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
@@ -346,17 +372,18 @@ export const SortableProject = (props: {
|
|||||||
return (
|
return (
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
<div use:sortable classList={{ "opacity-30": sortable.isActiveDraggable }}>
|
<div use:sortable classList={{ "opacity-30": sortable.isActiveDraggable }}>
|
||||||
<Show when={preview()} fallback={tile()}>
|
<Show when={preview() && !selected()} fallback={tile()}>
|
||||||
<HoverCard
|
<HoverCard
|
||||||
open={open() && !menu()}
|
open={!state.suppressHover && state.open && !state.menu}
|
||||||
openDelay={0}
|
openDelay={0}
|
||||||
closeDelay={0}
|
closeDelay={0}
|
||||||
placement="right-start"
|
placement="right-start"
|
||||||
gutter={6}
|
gutter={6}
|
||||||
trigger={tile()}
|
trigger={tile()}
|
||||||
onOpenChange={(value) => {
|
onOpenChange={(value) => {
|
||||||
if (menu()) return
|
if (state.menu) return
|
||||||
setOpen(value)
|
if (value && state.suppressHover) return
|
||||||
|
setState("open", value)
|
||||||
if (value) props.ctx.setHoverSession(undefined)
|
if (value) props.ctx.setHoverSession(undefined)
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
@@ -371,7 +398,7 @@ export const SortableProject = (props: {
|
|||||||
projectChildren={projectChildren}
|
projectChildren={projectChildren}
|
||||||
workspaceSessions={workspaceSessions}
|
workspaceSessions={workspaceSessions}
|
||||||
workspaceChildren={workspaceChildren}
|
workspaceChildren={workspaceChildren}
|
||||||
setOpen={setOpen}
|
setOpen={(value) => setState("open", value)}
|
||||||
ctx={props.ctx}
|
ctx={props.ctx}
|
||||||
language={language}
|
language={language}
|
||||||
/>
|
/>
|
||||||
|
|||||||
Reference in New Issue
Block a user