fix(tui): show current location in working copies; order by created; change shortcut; tab to cycle actions in dialog select (#30989)
This commit is contained in:
parent
3151e2246a
commit
ecdfcd91ca
8 changed files with 151 additions and 51 deletions
|
|
@ -2,7 +2,7 @@ export * as ProjectV2 from "./project"
|
||||||
export * as Project from "./project"
|
export * as Project from "./project"
|
||||||
|
|
||||||
import { Context, Effect, Layer, Schema } from "effect"
|
import { Context, Effect, Layer, Schema } from "effect"
|
||||||
import { eq } from "drizzle-orm"
|
import { asc, desc, eq } from "drizzle-orm"
|
||||||
import path from "path"
|
import path from "path"
|
||||||
import { AbsolutePath, withStatics } from "./schema"
|
import { AbsolutePath, withStatics } from "./schema"
|
||||||
import { FSUtil } from "./fs-util"
|
import { FSUtil } from "./fs-util"
|
||||||
|
|
@ -76,11 +76,10 @@ export const layer = Layer.effect(
|
||||||
.select({ directory: ProjectDirectoryTable.directory })
|
.select({ directory: ProjectDirectoryTable.directory })
|
||||||
.from(ProjectDirectoryTable)
|
.from(ProjectDirectoryTable)
|
||||||
.where(eq(ProjectDirectoryTable.project_id, input.projectID))
|
.where(eq(ProjectDirectoryTable.project_id, input.projectID))
|
||||||
|
.orderBy(desc(ProjectDirectoryTable.time_created), asc(ProjectDirectoryTable.directory))
|
||||||
.all()
|
.all()
|
||||||
.pipe(Effect.orDie)
|
.pipe(Effect.orDie)
|
||||||
return rows
|
return rows.map((row) => AbsolutePath.make(row.directory))
|
||||||
.toSorted((a, b) => a.directory.localeCompare(b.directory))
|
|
||||||
.map((row) => AbsolutePath.make(row.directory))
|
|
||||||
})
|
})
|
||||||
|
|
||||||
const cached = Effect.fnUntraced(function* (dir: string) {
|
const cached = Effect.fnUntraced(function* (dir: string) {
|
||||||
|
|
|
||||||
|
|
@ -65,7 +65,7 @@ describe("Project directories schemas", () => {
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|
||||||
it.effect("lists stored project directories only for the requested project", () =>
|
it.effect("lists stored project directories newest first for the requested project", () =>
|
||||||
Effect.gen(function* () {
|
Effect.gen(function* () {
|
||||||
const project = yield* ProjectV2.Service
|
const project = yield* ProjectV2.Service
|
||||||
const { db } = yield* Database.Service
|
const { db } = yield* Database.Service
|
||||||
|
|
@ -82,16 +82,16 @@ describe("Project directories schemas", () => {
|
||||||
yield* db
|
yield* db
|
||||||
.insert(ProjectDirectoryTable)
|
.insert(ProjectDirectoryTable)
|
||||||
.values([
|
.values([
|
||||||
{ project_id: projectID, directory: AbsolutePath.make("/repo/z"), type: "root" },
|
{ project_id: projectID, directory: AbsolutePath.make("/repo/z"), type: "root", time_created: 2 },
|
||||||
{ project_id: projectID, directory: AbsolutePath.make("/repo/a"), type: "main" },
|
{ project_id: projectID, directory: AbsolutePath.make("/repo/a"), type: "main", time_created: 1 },
|
||||||
{ project_id: otherID, directory: AbsolutePath.make("/other"), type: "main" },
|
{ project_id: otherID, directory: AbsolutePath.make("/other"), type: "main", time_created: 3 },
|
||||||
])
|
])
|
||||||
.run()
|
.run()
|
||||||
.pipe(Effect.orDie)
|
.pipe(Effect.orDie)
|
||||||
|
|
||||||
expect(yield* project.directories({ projectID })).toEqual([
|
expect(yield* project.directories({ projectID })).toEqual([
|
||||||
AbsolutePath.make("/repo/a"),
|
|
||||||
AbsolutePath.make("/repo/z"),
|
AbsolutePath.make("/repo/z"),
|
||||||
|
AbsolutePath.make("/repo/a"),
|
||||||
])
|
])
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,11 @@ const REFRESH_FRAMES = ["■", "⬝"]
|
||||||
|
|
||||||
export type MoveSessionSelection = { type: "directory"; directory: string } | { type: "new" }
|
export type MoveSessionSelection = { type: "directory"; directory: string } | { type: "new" }
|
||||||
|
|
||||||
export function DialogMoveSession(props: { projectID: string; onSelect: (selection: MoveSessionSelection) => void }) {
|
export function DialogMoveSession(props: {
|
||||||
|
projectID: string
|
||||||
|
current?: MoveSessionSelection
|
||||||
|
onSelect: (selection: MoveSessionSelection) => void
|
||||||
|
}) {
|
||||||
const dialog = useDialog()
|
const dialog = useDialog()
|
||||||
const sdk = useSDK()
|
const sdk = useSDK()
|
||||||
const dimensions = useTerminalDimensions()
|
const dimensions = useTerminalDimensions()
|
||||||
|
|
@ -42,7 +46,7 @@ export function DialogMoveSession(props: { projectID: string; onSelect: (selecti
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
const options = createMemo<DialogSelectOption<string | undefined>[]>(() => {
|
const options = createMemo<DialogSelectOption<MoveSessionSelection | undefined>[]>(() => {
|
||||||
if (directories.loading) return [{ title: "Loading project directories...", value: undefined }]
|
if (directories.loading) return [{ title: "Loading project directories...", value: undefined }]
|
||||||
if (directories.error) return [{ title: "Failed to load project directories", value: undefined }]
|
if (directories.error) return [{ title: "Failed to load project directories", value: undefined }]
|
||||||
const data = directories()
|
const data = directories()
|
||||||
|
|
@ -88,7 +92,7 @@ export function DialogMoveSession(props: { projectID: string; onSelect: (selecti
|
||||||
<span style={{ fg: theme.textMuted }}>{visible.slice(split)}</span>
|
<span style={{ fg: theme.textMuted }}>{visible.slice(split)}</span>
|
||||||
</>
|
</>
|
||||||
) : undefined,
|
) : undefined,
|
||||||
value: item.location,
|
value: { type: "directory", directory: item.location } as const,
|
||||||
category: item.root === data?.main ? "Project" : "Working copies",
|
category: item.root === data?.main ? "Project" : "Working copies",
|
||||||
titleWidth,
|
titleWidth,
|
||||||
truncateTitle: "left" as const,
|
truncateTitle: "left" as const,
|
||||||
|
|
@ -103,8 +107,9 @@ export function DialogMoveSession(props: { projectID: string; onSelect: (selecti
|
||||||
<DialogSelect
|
<DialogSelect
|
||||||
title="Move session"
|
title="Move session"
|
||||||
options={options()}
|
options={options()}
|
||||||
|
current={props.current}
|
||||||
onSelect={(option) => {
|
onSelect={(option) => {
|
||||||
if (option.value) props.onSelect({ type: "directory", directory: option.value })
|
if (option.value) props.onSelect(option.value)
|
||||||
}}
|
}}
|
||||||
actions={[
|
actions={[
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -51,9 +51,12 @@ export function usePromptMove(input: { projectID: () => string | undefined; sess
|
||||||
function open() {
|
function open() {
|
||||||
const projectID = input.projectID()
|
const projectID = input.projectID()
|
||||||
if (!projectID) return
|
if (!projectID) return
|
||||||
|
const sessionID = input.sessionID()
|
||||||
|
const session = sessionID ? sync.session.get(sessionID) : undefined
|
||||||
dialog.replace(() => (
|
dialog.replace(() => (
|
||||||
<DialogMoveSession
|
<DialogMoveSession
|
||||||
projectID={projectID}
|
projectID={projectID}
|
||||||
|
current={homeDestination?.destination() ?? (session ? { type: "directory", directory: session.directory } : undefined)}
|
||||||
onSelect={(selection) => {
|
onSelect={(selection) => {
|
||||||
const sessionID = input.sessionID()
|
const sessionID = input.sessionID()
|
||||||
if (!sessionID) {
|
if (!sessionID) {
|
||||||
|
|
|
||||||
|
|
@ -207,7 +207,7 @@ export const Definitions = {
|
||||||
"dialog.select.submit": keybind("return", "Submit selected dialog item"),
|
"dialog.select.submit": keybind("return", "Submit selected dialog item"),
|
||||||
"dialog.prompt.submit": keybind("return", "Submit dialog prompt"),
|
"dialog.prompt.submit": keybind("return", "Submit dialog prompt"),
|
||||||
"dialog.mcp.toggle": keybind("space", "Toggle MCP in MCP dialog"),
|
"dialog.mcp.toggle": keybind("space", "Toggle MCP in MCP dialog"),
|
||||||
"dialog.move_session.new": keybind("ctrl+w", "New project copy"),
|
"dialog.move_session.new": keybind("ctrl+m", "New project copy"),
|
||||||
"prompt.autocomplete.prev": keybind("up,ctrl+p", "Move to previous autocomplete item"),
|
"prompt.autocomplete.prev": keybind("up,ctrl+p", "Move to previous autocomplete item"),
|
||||||
"prompt.autocomplete.next": keybind("down,ctrl+n", "Move to next autocomplete item"),
|
"prompt.autocomplete.next": keybind("down,ctrl+n", "Move to next autocomplete item"),
|
||||||
"prompt.autocomplete.hide": keybind("escape", "Hide autocomplete"),
|
"prompt.autocomplete.hide": keybind("escape", "Hide autocomplete"),
|
||||||
|
|
|
||||||
|
|
@ -11,11 +11,10 @@ function Directory(props: { api: TuiPluginApi }) {
|
||||||
const destination = useHomeSessionDestination()
|
const destination = useHomeSessionDestination()
|
||||||
const dir = createMemo(() => {
|
const dir = createMemo(() => {
|
||||||
const selected = destination?.destination()
|
const selected = destination?.destination()
|
||||||
if (selected?.type === "new") return
|
if (!selected || selected.type === "new") return
|
||||||
if (selected?.type === "directory") return selected.directory.replace(Global.Path.home, "~")
|
const out = selected.directory.replace(Global.Path.home, "~")
|
||||||
const dir = props.api.state.path.directory || process.cwd()
|
const branch =
|
||||||
const out = dir.replace(Global.Path.home, "~")
|
selected.directory === (props.api.state.path.directory || process.cwd()) ? props.api.state.vcs?.branch : undefined
|
||||||
const branch = props.api.state.vcs?.branch
|
|
||||||
if (branch) return out + ":" + branch
|
if (branch) return out + ":" + branch
|
||||||
return out
|
return out
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
import { createContext, createSignal, useContext, type Accessor, type ParentProps, type Setter } from "solid-js"
|
import { createContext, createMemo, createSignal, useContext, type Accessor, type ParentProps, type Setter } from "solid-js"
|
||||||
|
import { useSync } from "../../context/sync"
|
||||||
|
|
||||||
export type HomeSessionDestination = { type: "directory"; directory: string } | { type: "new" }
|
export type HomeSessionDestination = { type: "directory"; directory: string } | { type: "new" }
|
||||||
|
|
||||||
|
|
@ -11,7 +12,11 @@ type Context = {
|
||||||
const HomeSessionDestinationContext = createContext<Context>()
|
const HomeSessionDestinationContext = createContext<Context>()
|
||||||
|
|
||||||
export function HomeSessionDestinationProvider(props: ParentProps) {
|
export function HomeSessionDestinationProvider(props: ParentProps) {
|
||||||
const [destination, setDestination] = createSignal<HomeSessionDestination>()
|
const sync = useSync()
|
||||||
|
const [selected, setDestination] = createSignal<HomeSessionDestination>()
|
||||||
|
const destination = createMemo<HomeSessionDestination>(() =>
|
||||||
|
selected() ?? { type: "directory", directory: sync.path.directory || process.cwd() },
|
||||||
|
)
|
||||||
return (
|
return (
|
||||||
<HomeSessionDestinationContext.Provider
|
<HomeSessionDestinationContext.Provider
|
||||||
value={{ destination, setDestination, clear: () => setDestination(undefined) }}
|
value={{ destination, setDestination, clear: () => setDestination(undefined) }}
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@ import {
|
||||||
import type { Binding } from "@opentui/keymap"
|
import type { Binding } from "@opentui/keymap"
|
||||||
import { useTheme, selectedForeground } from "@tui/context/theme"
|
import { useTheme, selectedForeground } from "@tui/context/theme"
|
||||||
import { entries, filter, flatMap, groupBy, pipe } from "remeda"
|
import { entries, filter, flatMap, groupBy, pipe } from "remeda"
|
||||||
import { batch, createEffect, createMemo, For, Show, type JSX, on } from "solid-js"
|
import { batch, createEffect, createMemo, createSignal, For, Show, type JSX, on } from "solid-js"
|
||||||
import { createStore } from "solid-js/store"
|
import { createStore } from "solid-js/store"
|
||||||
import { useTerminalDimensions } from "@opentui/solid"
|
import { useTerminalDimensions } from "@opentui/solid"
|
||||||
import * as fuzzysort from "fuzzysort"
|
import * as fuzzysort from "fuzzysort"
|
||||||
|
|
@ -74,6 +74,10 @@ export type DialogSelectRef<T> = {
|
||||||
}
|
}
|
||||||
|
|
||||||
export function DialogSelect<T>(props: DialogSelectProps<T>) {
|
export function DialogSelect<T>(props: DialogSelectProps<T>) {
|
||||||
|
type Action = NonNullable<DialogSelectProps<T>["actions"]>[number]
|
||||||
|
type FooterHint = NonNullable<DialogSelectProps<T>["footerHints"]>[number]
|
||||||
|
type VisibleAction = (Action & { label: string }) | FooterHint
|
||||||
|
|
||||||
const dialog = useDialog()
|
const dialog = useDialog()
|
||||||
const { theme } = useTheme()
|
const { theme } = useTheme()
|
||||||
const tuiConfig = useTuiConfig()
|
const tuiConfig = useTuiConfig()
|
||||||
|
|
@ -84,6 +88,8 @@ export function DialogSelect<T>(props: DialogSelectProps<T>) {
|
||||||
filter: "",
|
filter: "",
|
||||||
input: "keyboard" as "keyboard" | "mouse",
|
input: "keyboard" as "keyboard" | "mouse",
|
||||||
})
|
})
|
||||||
|
const [focusedAction, setFocusedAction] = createSignal<number>()
|
||||||
|
const actionFocused = createMemo(() => focusedAction() !== undefined)
|
||||||
|
|
||||||
createEffect(
|
createEffect(
|
||||||
on(
|
on(
|
||||||
|
|
@ -119,6 +125,18 @@ export function DialogSelect<T>(props: DialogSelectProps<T>) {
|
||||||
|
|
||||||
return labels
|
return labels
|
||||||
})
|
})
|
||||||
|
const visibleActions = createMemo(() => [
|
||||||
|
...actions()
|
||||||
|
.map((item) => ({ ...item, label: actionLabels().get(item.command) ?? "" }))
|
||||||
|
.filter((item) => !item.disabled && item.label),
|
||||||
|
...(props.footerHints ?? []),
|
||||||
|
])
|
||||||
|
const actionItems = createMemo(() => visibleActions().filter(isActionItem))
|
||||||
|
|
||||||
|
createEffect(() => {
|
||||||
|
const index = focusedAction()
|
||||||
|
if (index !== undefined && index >= actionItems().length) setFocusedAction(undefined)
|
||||||
|
})
|
||||||
|
|
||||||
const filtered = createMemo(() => {
|
const filtered = createMemo(() => {
|
||||||
if (props.skipFilter || props.renderFilter === false) return props.options.filter((x) => x.disabled !== true)
|
if (props.skipFilter || props.renderFilter === false) return props.options.filter((x) => x.disabled !== true)
|
||||||
|
|
@ -147,6 +165,7 @@ export function DialogSelect<T>(props: DialogSelectProps<T>) {
|
||||||
createEffect(() => {
|
createEffect(() => {
|
||||||
filtered()
|
filtered()
|
||||||
setStore("input", "keyboard")
|
setStore("input", "keyboard")
|
||||||
|
setFocusedAction(undefined)
|
||||||
})
|
})
|
||||||
|
|
||||||
const flatten = createMemo(() => props.flat && store.filter.length > 0)
|
const flatten = createMemo(() => props.flat && store.filter.length > 0)
|
||||||
|
|
@ -206,6 +225,7 @@ export function DialogSelect<T>(props: DialogSelectProps<T>) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function moveTo(next: number, center = false) {
|
function moveTo(next: number, center = false) {
|
||||||
|
setFocusedAction(undefined)
|
||||||
setStore("selected", next)
|
setStore("selected", next)
|
||||||
const option = selected()
|
const option = selected()
|
||||||
if (option) props.onMove?.(option)
|
if (option) props.onMove?.(option)
|
||||||
|
|
@ -233,12 +253,27 @@ export function DialogSelect<T>(props: DialogSelectProps<T>) {
|
||||||
|
|
||||||
function submit() {
|
function submit() {
|
||||||
setStore("input", "keyboard")
|
setStore("input", "keyboard")
|
||||||
|
const index = focusedAction()
|
||||||
|
if (index !== undefined) {
|
||||||
|
triggerAction(actionItems()[index])
|
||||||
|
return
|
||||||
|
}
|
||||||
const option = selected()
|
const option = selected()
|
||||||
if (!option) return
|
if (!option) return
|
||||||
option.onSelect?.(dialog)
|
option.onSelect?.(dialog)
|
||||||
props.onSelect?.(option)
|
props.onSelect?.(option)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function moveAction(direction: 1 | -1) {
|
||||||
|
const total = actionItems().length
|
||||||
|
if (total === 0) return
|
||||||
|
setFocusedAction((index) => {
|
||||||
|
if (index === undefined) return direction === 1 ? 0 : total - 1
|
||||||
|
const next = index + direction
|
||||||
|
return next < 0 || next >= total ? undefined : next
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
useBindings(() => {
|
useBindings(() => {
|
||||||
const enabledActions = actions().filter((item) => !item.disabled)
|
const enabledActions = actions().filter((item) => !item.disabled)
|
||||||
|
|
||||||
|
|
@ -327,6 +362,22 @@ export function DialogSelect<T>(props: DialogSelectProps<T>) {
|
||||||
"dialog.select.submit",
|
"dialog.select.submit",
|
||||||
]),
|
]),
|
||||||
...enabledActions.flatMap((item) => tuiConfig.keybinds.get(item.command)),
|
...enabledActions.flatMap((item) => tuiConfig.keybinds.get(item.command)),
|
||||||
|
...(enabledActions.length
|
||||||
|
? [
|
||||||
|
{
|
||||||
|
key: "tab",
|
||||||
|
desc: "Next dialog action",
|
||||||
|
group: "Dialog",
|
||||||
|
cmd: () => moveAction(1),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "shift+tab",
|
||||||
|
desc: "Previous dialog action",
|
||||||
|
group: "Dialog",
|
||||||
|
cmd: () => moveAction(-1),
|
||||||
|
},
|
||||||
|
]
|
||||||
|
: []),
|
||||||
...(props.bindings ?? []).filter((binding) => {
|
...(props.bindings ?? []).filter((binding) => {
|
||||||
if (typeof binding.cmd !== "string") return true
|
if (typeof binding.cmd !== "string") return true
|
||||||
return enabledActions.some((item) => item.command === binding.cmd)
|
return enabledActions.some((item) => item.command === binding.cmd)
|
||||||
|
|
@ -353,15 +404,53 @@ export function DialogSelect<T>(props: DialogSelectProps<T>) {
|
||||||
}
|
}
|
||||||
props.ref?.(ref)
|
props.ref?.(ref)
|
||||||
|
|
||||||
const visibleActions = createMemo(() => [
|
|
||||||
...actions()
|
|
||||||
.map((item) => ({ ...item, label: actionLabels().get(item.command) ?? "" }))
|
|
||||||
.filter((item) => !item.disabled && item.label),
|
|
||||||
...(props.footerHints ?? []),
|
|
||||||
])
|
|
||||||
const left = createMemo(() => visibleActions().filter((item) => item.side !== "right"))
|
const left = createMemo(() => visibleActions().filter((item) => item.side !== "right"))
|
||||||
const right = createMemo(() => visibleActions().filter((item) => item.side === "right"))
|
const right = createMemo(() => visibleActions().filter((item) => item.side === "right"))
|
||||||
|
|
||||||
|
function triggerAction(item: VisibleAction | undefined) {
|
||||||
|
if (!item || !isActionItem(item)) return
|
||||||
|
setStore("input", "keyboard")
|
||||||
|
const option = selected()
|
||||||
|
if (!option) return
|
||||||
|
item.onTrigger(option)
|
||||||
|
}
|
||||||
|
|
||||||
|
function isActionItem(item: VisibleAction): item is Action & { label: string } {
|
||||||
|
return "onTrigger" in item
|
||||||
|
}
|
||||||
|
|
||||||
|
function isActionFocused(item: VisibleAction) {
|
||||||
|
if (!isActionItem(item)) return false
|
||||||
|
return actionItems().indexOf(item) === focusedAction()
|
||||||
|
}
|
||||||
|
|
||||||
|
function FooterAction(action: { item: VisibleAction }) {
|
||||||
|
if (!isActionItem(action.item))
|
||||||
|
return (
|
||||||
|
<text>
|
||||||
|
<span style={{ fg: theme.text }}>
|
||||||
|
<b>{action.item.title}</b>{" "}
|
||||||
|
</span>
|
||||||
|
<span style={{ fg: theme.textMuted }}>{action.item.label}</span>
|
||||||
|
</text>
|
||||||
|
)
|
||||||
|
const active = createMemo(() => isActionFocused(action.item))
|
||||||
|
const fg = selectedForeground(theme)
|
||||||
|
return (
|
||||||
|
<box
|
||||||
|
flexDirection="row"
|
||||||
|
paddingRight={1}
|
||||||
|
backgroundColor={active() ? theme.primary : RGBA.fromInts(0, 0, 0, 0)}
|
||||||
|
onMouseUp={() => triggerAction(action.item)}
|
||||||
|
>
|
||||||
|
<text fg={active() ? fg : theme.text} attributes={active() ? TextAttributes.BOLD : undefined}>
|
||||||
|
{action.item.title}
|
||||||
|
</text>
|
||||||
|
<text fg={active() ? fg : theme.textMuted}> {action.item.label}</text>
|
||||||
|
</box>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<box gap={1} paddingBottom={1} flexGrow={1}>
|
<box gap={1} paddingBottom={1} flexGrow={1}>
|
||||||
<box paddingLeft={4} paddingRight={4}>
|
<box paddingLeft={4} paddingRight={4}>
|
||||||
|
|
@ -445,6 +534,7 @@ export function DialogSelect<T>(props: DialogSelectProps<T>) {
|
||||||
position="relative"
|
position="relative"
|
||||||
onMouseMove={() => {
|
onMouseMove={() => {
|
||||||
setStore("input", "mouse")
|
setStore("input", "mouse")
|
||||||
|
setFocusedAction(undefined)
|
||||||
}}
|
}}
|
||||||
onMouseUp={() => {
|
onMouseUp={() => {
|
||||||
option.onSelect?.(dialog)
|
option.onSelect?.(dialog)
|
||||||
|
|
@ -467,7 +557,13 @@ export function DialogSelect<T>(props: DialogSelectProps<T>) {
|
||||||
paddingLeft={current() || option.gutter ? 1 : 3}
|
paddingLeft={current() || option.gutter ? 1 : 3}
|
||||||
paddingRight={3}
|
paddingRight={3}
|
||||||
gap={1}
|
gap={1}
|
||||||
backgroundColor={active() ? (option.bg ?? theme.primary) : RGBA.fromInts(0, 0, 0, 0)}
|
backgroundColor={
|
||||||
|
active()
|
||||||
|
? actionFocused()
|
||||||
|
? theme.backgroundElement
|
||||||
|
: (option.bg ?? theme.primary)
|
||||||
|
: RGBA.fromInts(0, 0, 0, 0)
|
||||||
|
}
|
||||||
>
|
>
|
||||||
<Show when={!current() && option.margin}>
|
<Show when={!current() && option.margin}>
|
||||||
<box position="absolute" left={1} flexShrink={0}>
|
<box position="absolute" left={1} flexShrink={0}>
|
||||||
|
|
@ -483,6 +579,7 @@ export function DialogSelect<T>(props: DialogSelectProps<T>) {
|
||||||
description={option.description !== category ? option.description : undefined}
|
description={option.description !== category ? option.description : undefined}
|
||||||
active={active()}
|
active={active()}
|
||||||
current={current()}
|
current={current()}
|
||||||
|
muted={actionFocused()}
|
||||||
gutter={option.gutter}
|
gutter={option.gutter}
|
||||||
/>
|
/>
|
||||||
</box>
|
</box>
|
||||||
|
|
@ -512,31 +609,16 @@ export function DialogSelect<T>(props: DialogSelectProps<T>) {
|
||||||
flexDirection="row"
|
flexDirection="row"
|
||||||
justifyContent="space-between"
|
justifyContent="space-between"
|
||||||
flexShrink={0}
|
flexShrink={0}
|
||||||
paddingTop={1}
|
|
||||||
>
|
>
|
||||||
<box flexDirection="row" gap={2}>
|
<box flexDirection="row" gap={2}>
|
||||||
{props.footer}
|
{props.footer}
|
||||||
<For each={left()}>
|
<For each={left()}>
|
||||||
{(item) => (
|
{(item) => <FooterAction item={item} />}
|
||||||
<text>
|
|
||||||
<span style={{ fg: theme.text }}>
|
|
||||||
<b>{item.title}</b>{" "}
|
|
||||||
</span>
|
|
||||||
<span style={{ fg: theme.textMuted }}>{item.label}</span>
|
|
||||||
</text>
|
|
||||||
)}
|
|
||||||
</For>
|
</For>
|
||||||
</box>
|
</box>
|
||||||
<box flexDirection="row" gap={2}>
|
<box flexDirection="row" gap={2}>
|
||||||
<For each={right()}>
|
<For each={right()}>
|
||||||
{(item) => (
|
{(item) => <FooterAction item={item} />}
|
||||||
<text>
|
|
||||||
<span style={{ fg: theme.text }}>
|
|
||||||
<b>{item.title}</b>{" "}
|
|
||||||
</span>
|
|
||||||
<span style={{ fg: theme.textMuted }}>{item.label}</span>
|
|
||||||
</text>
|
|
||||||
)}
|
|
||||||
</For>
|
</For>
|
||||||
</box>
|
</box>
|
||||||
</box>
|
</box>
|
||||||
|
|
@ -551,6 +633,7 @@ function Option(props: {
|
||||||
description?: string
|
description?: string
|
||||||
active?: boolean
|
active?: boolean
|
||||||
current?: boolean
|
current?: boolean
|
||||||
|
muted?: boolean
|
||||||
footer?: JSX.Element | string
|
footer?: JSX.Element | string
|
||||||
titleWidth?: number
|
titleWidth?: number
|
||||||
truncateTitle?: boolean | "left"
|
truncateTitle?: boolean | "left"
|
||||||
|
|
@ -559,11 +642,17 @@ function Option(props: {
|
||||||
}) {
|
}) {
|
||||||
const { theme } = useTheme()
|
const { theme } = useTheme()
|
||||||
const fg = selectedForeground(theme)
|
const fg = selectedForeground(theme)
|
||||||
|
const text = createMemo(() => {
|
||||||
|
if (props.active && !props.muted) return fg
|
||||||
|
if (props.muted && (props.active || props.current)) return theme.textMuted
|
||||||
|
if (props.current) return theme.primary
|
||||||
|
return theme.text
|
||||||
|
})
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Show when={props.current}>
|
<Show when={props.current}>
|
||||||
<text flexShrink={0} fg={props.active ? fg : props.current ? theme.primary : theme.text} marginRight={0}>
|
<text flexShrink={0} fg={text()} marginRight={0}>
|
||||||
●
|
●
|
||||||
</text>
|
</text>
|
||||||
</Show>
|
</Show>
|
||||||
|
|
@ -574,8 +663,8 @@ function Option(props: {
|
||||||
</Show>
|
</Show>
|
||||||
<text
|
<text
|
||||||
flexGrow={1}
|
flexGrow={1}
|
||||||
fg={props.active ? fg : props.current ? theme.primary : theme.text}
|
fg={text()}
|
||||||
attributes={props.active ? TextAttributes.BOLD : undefined}
|
attributes={props.active && !props.muted ? TextAttributes.BOLD : undefined}
|
||||||
overflow="hidden"
|
overflow="hidden"
|
||||||
wrapMode="none"
|
wrapMode="none"
|
||||||
paddingLeft={3}
|
paddingLeft={3}
|
||||||
|
|
@ -587,12 +676,12 @@ function Option(props: {
|
||||||
? Locale.truncateLeft(props.title, props.titleWidth ?? 61)
|
? Locale.truncateLeft(props.title, props.titleWidth ?? 61)
|
||||||
: Locale.truncate(props.title, props.titleWidth ?? 61))}
|
: Locale.truncate(props.title, props.titleWidth ?? 61))}
|
||||||
<Show when={props.description}>
|
<Show when={props.description}>
|
||||||
<span style={{ fg: props.active ? fg : theme.textMuted }}> {props.description}</span>
|
<span style={{ fg: props.active && !props.muted ? fg : theme.textMuted }}> {props.description}</span>
|
||||||
</Show>
|
</Show>
|
||||||
</text>
|
</text>
|
||||||
<Show when={props.footer}>
|
<Show when={props.footer}>
|
||||||
<box flexShrink={0}>
|
<box flexShrink={0}>
|
||||||
<text fg={props.active ? fg : theme.textMuted}>{props.footer}</text>
|
<text fg={props.active && !props.muted ? fg : theme.textMuted}>{props.footer}</text>
|
||||||
</box>
|
</box>
|
||||||
</Show>
|
</Show>
|
||||||
</>
|
</>
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue