mirror of
https://github.com/sst/opencode.git
synced 2025-12-23 10:11:41 +00:00
tui: refactor routing structure and add path mapping
- Add @tui/* path mapping to tsconfig.json for cleaner imports - Restructure TUI routes from flat files to proper routes/ directory - Move home.tsx and session.tsx to routes/home.tsx and routes/session/index.tsx - Update all TUI component imports to use @tui/* alias - Add locale time formatting to session list dialog - Improve keybind handling with preventDefault for command dialog
This commit is contained in:
parent
eb4c5f4eac
commit
1bd8b62344
20 changed files with 187 additions and 115 deletions
|
|
@ -1,4 +1,4 @@
|
|||
import { Theme } from "../context/theme"
|
||||
import { Theme } from "@tui/context/theme"
|
||||
|
||||
export const SplitBorder = {
|
||||
border: ["left" as const, "right" as const],
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import { createMemo } from "solid-js"
|
||||
import { useLocal } from "../context/local"
|
||||
import { DialogSelect } from "../ui/dialog-select"
|
||||
import { useDialog } from "../ui/dialog"
|
||||
import { useLocal } from "@tui/context/local"
|
||||
import { DialogSelect } from "@tui/ui/dialog-select"
|
||||
import { useDialog } from "@tui/ui/dialog"
|
||||
|
||||
export function DialogAgent() {
|
||||
const local = useLocal()
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { useDialog } from "../ui/dialog"
|
||||
import { DialogSelect, type DialogSelectOption } from "../ui/dialog-select"
|
||||
import { useDialog } from "@tui/ui/dialog"
|
||||
import { DialogSelect, type DialogSelectOption } from "@tui/ui/dialog-select"
|
||||
import {
|
||||
createContext,
|
||||
createMemo,
|
||||
|
|
@ -10,7 +10,7 @@ import {
|
|||
type ParentProps,
|
||||
} from "solid-js"
|
||||
import { useKeyboard } from "@opentui/solid"
|
||||
import { useKeybind } from "../context/keybind"
|
||||
import { useKeybind } from "@tui/context/keybind"
|
||||
|
||||
type Context = ReturnType<typeof init>
|
||||
const ctx = createContext<Context>()
|
||||
|
|
@ -26,6 +26,7 @@ function init() {
|
|||
useKeyboard((evt) => {
|
||||
for (const option of options()) {
|
||||
if (option.keybind && keybind.match(option.keybind, evt)) {
|
||||
evt.preventDefault()
|
||||
option.onSelect?.(dialog)
|
||||
return
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
import { createMemo } from "solid-js"
|
||||
import { useLocal } from "../context/local"
|
||||
import { useSync } from "../context/sync"
|
||||
import { useLocal } from "@tui/context/local"
|
||||
import { useSync } from "@tui/context/sync"
|
||||
import { map, pipe, flatMap, entries, filter, isDeepEqual } from "remeda"
|
||||
import { DialogSelect } from "../ui/dialog-select"
|
||||
import { useDialog } from "../ui/dialog"
|
||||
import { DialogSelect } from "@tui/ui/dialog-select"
|
||||
import { useDialog } from "@tui/ui/dialog"
|
||||
|
||||
export function DialogModel() {
|
||||
const local = useLocal()
|
||||
|
|
|
|||
|
|
@ -1,8 +1,9 @@
|
|||
import { useDialog } from "../ui/dialog"
|
||||
import { DialogSelect } from "../ui/dialog-select"
|
||||
import { useRoute } from "../context/route"
|
||||
import { useSync } from "../context/sync"
|
||||
import { useDialog } from "@tui/ui/dialog"
|
||||
import { DialogSelect } from "@tui/ui/dialog-select"
|
||||
import { useRoute } from "@tui/context/route"
|
||||
import { useSync } from "@tui/context/sync"
|
||||
import { createMemo, onMount } from "solid-js"
|
||||
import { Locale } from "@/util/locale"
|
||||
|
||||
export function DialogSessionList() {
|
||||
const dialog = useDialog()
|
||||
|
|
@ -12,7 +13,8 @@ export function DialogSessionList() {
|
|||
const options = createMemo(() => {
|
||||
const today = new Date().toDateString()
|
||||
return sync.data.session.map((x) => {
|
||||
let category = new Date(x.time.created).toDateString()
|
||||
const date = new Date(x.time.updated)
|
||||
let category = date.toDateString()
|
||||
if (category === today) {
|
||||
category = "Today"
|
||||
}
|
||||
|
|
@ -20,6 +22,7 @@ export function DialogSessionList() {
|
|||
title: x.title,
|
||||
value: x.id,
|
||||
category,
|
||||
footer: Locale.time(x.time.updated),
|
||||
}
|
||||
})
|
||||
})
|
||||
|
|
@ -32,6 +35,7 @@ export function DialogSessionList() {
|
|||
<DialogSelect
|
||||
title="Sessions"
|
||||
options={options()}
|
||||
limit={50}
|
||||
onSelect={(option) => {
|
||||
route.navigate({
|
||||
type: "session",
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import { createMemo, createResource } from "solid-js"
|
||||
import { DialogSelect } from "../ui/dialog-select"
|
||||
import { useDialog } from "../ui/dialog"
|
||||
import { useSDK } from "../context/sdk"
|
||||
import { DialogSelect } from "@tui/ui/dialog-select"
|
||||
import { useDialog } from "@tui/ui/dialog"
|
||||
import { useSDK } from "@tui/context/sdk"
|
||||
import { createStore } from "solid-js/store"
|
||||
|
||||
export function DialogTag(props: { onSelect?: (value: string) => void }) {
|
||||
|
|
|
|||
|
|
@ -1,20 +1,20 @@
|
|||
import { InputRenderable, TextAttributes, BoxRenderable, type ParsedKey } from "@opentui/core"
|
||||
import { createEffect, createMemo, createResource, For, Match, onMount, Show, Switch } from "solid-js"
|
||||
import { firstBy } from "remeda"
|
||||
import { useLocal } from "../context/local"
|
||||
import { Theme } from "../context/theme"
|
||||
import { useDialog } from "../ui/dialog"
|
||||
import { SplitBorder } from "./border"
|
||||
import { useSDK } from "../context/sdk"
|
||||
import { useRoute } from "../context/route"
|
||||
import { useSync } from "../context/sync"
|
||||
import { Identifier } from "../../../../id/id"
|
||||
import { useLocal } from "@tui/context/local"
|
||||
import { Theme } from "@tui/context/theme"
|
||||
import { useDialog } from "@tui/ui/dialog"
|
||||
import { SplitBorder } from "@tui/component/border"
|
||||
import { useSDK } from "@tui/context/sdk"
|
||||
import { useRoute } from "@tui/context/route"
|
||||
import { useSync } from "@tui/context/sync"
|
||||
import { Identifier } from "@/id/id"
|
||||
import { createStore, produce } from "solid-js/store"
|
||||
import type { FilePart } from "@opencode-ai/sdk"
|
||||
import fuzzysort from "fuzzysort"
|
||||
import { useCommandDialog } from "./dialog-command"
|
||||
import { useKeybind } from "../context/keybind"
|
||||
import { Clipboard } from "../../../../util/clipboard"
|
||||
import { useCommandDialog } from "@tui/component/dialog-command"
|
||||
import { useKeybind } from "@tui/context/keybind"
|
||||
import { Clipboard } from "@/util/clipboard"
|
||||
|
||||
export type PromptProps = {
|
||||
sessionID?: string
|
||||
|
|
|
|||
|
|
@ -1,13 +1,13 @@
|
|||
import { createMemo, useContext, type ParentProps } from "solid-js"
|
||||
import { useSync } from "./sync"
|
||||
import { Keybind } from "../../../../util/keybind"
|
||||
import { useSync } from "@tui/context/sync"
|
||||
import { Keybind } from "@/util/keybind"
|
||||
import { pipe, mapValues } from "remeda"
|
||||
import type { KeybindsConfig } from "@opencode-ai/sdk"
|
||||
import { createContext } from "solid-js"
|
||||
import type { ParsedKey, Renderable } from "@opentui/core"
|
||||
import { createStore } from "solid-js/store"
|
||||
import { useKeyboard, useRenderer } from "@opentui/solid"
|
||||
import { Instance } from "../../../../project/instance"
|
||||
import { Instance } from "@/project/instance"
|
||||
|
||||
export function init() {
|
||||
const sync = useSync()
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
import { createStore } from "solid-js/store"
|
||||
import { batch, createContext, createEffect, createMemo, useContext, type ParentProps } from "solid-js"
|
||||
import { useSync } from "./sync"
|
||||
import { Theme } from "./theme"
|
||||
import { useSync } from "@tui/context/sync"
|
||||
import { Theme } from "@tui/context/theme"
|
||||
import { uniqueBy } from "remeda"
|
||||
import path from "path"
|
||||
import { Global } from "../../../../global"
|
||||
import { Global } from "@/global"
|
||||
|
||||
function init() {
|
||||
const sync = useSync()
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { createContext, useContext, type ParentProps } from "solid-js"
|
||||
import { createOpencodeClient } from "@opencode-ai/sdk"
|
||||
import { Server } from "../../../../server/server"
|
||||
import { Server } from "@/server/server"
|
||||
|
||||
function init() {
|
||||
const client = createOpencodeClient({
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
import type { Message, Agent, Provider, Session, Part, Config, Todo, Command } from "@opencode-ai/sdk"
|
||||
import { createStore, produce, reconcile } from "solid-js/store"
|
||||
import { useSDK } from "./sdk"
|
||||
import { useSDK } from "@tui/context/sdk"
|
||||
import { createContext, Show, useContext, type ParentProps } from "solid-js"
|
||||
import { Binary } from "../../../../util/binary"
|
||||
import { Binary } from "@/util/binary"
|
||||
|
||||
function init() {
|
||||
const [store, setStore] = createStore<{
|
||||
|
|
|
|||
|
|
@ -255,7 +255,7 @@ type Theme = {
|
|||
}
|
||||
|
||||
import { createContext, useContext, createSignal, createEffect, onMount } from "solid-js"
|
||||
import { Storage } from "../../../../storage/storage"
|
||||
import { Storage } from "@/storage/storage"
|
||||
|
||||
export const Theme = Object.entries(OPENCODE_THEME).reduce((acc, [key, value]) => {
|
||||
acc[key as keyof Theme] = value.dark
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import { Installation } from "../../../installation"
|
||||
import { useTheme } from "./context/theme"
|
||||
import { Installation } from "@/installation"
|
||||
import { useTheme } from "@tui/context/theme"
|
||||
import { TextAttributes } from "@opentui/core"
|
||||
import { Prompt } from "./component/prompt"
|
||||
import { Prompt } from "@tui/component/prompt"
|
||||
import { For } from "solid-js"
|
||||
|
||||
export function Home() {
|
||||
|
|
@ -41,7 +41,7 @@ function HelpRow(props: { children: string; slash: string; theme: any }) {
|
|||
return (
|
||||
<text>
|
||||
<span style={{ bold: true, fg: props.theme.primary }}>/{props.slash.padEnd(10, " ")}</span>
|
||||
<span>{props.children.padEnd(15, " ")} </span>
|
||||
<span>{props.children.padEnd(19, " ")} </span>
|
||||
<span style={{ fg: props.theme.textMuted }}>ctrl+x n</span>
|
||||
</text>
|
||||
)
|
||||
63
packages/opencode/src/cli/cmd/tui/routes/session/header.tsx
Normal file
63
packages/opencode/src/cli/cmd/tui/routes/session/header.tsx
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
import { createMemo, Match, Show, Switch } from "solid-js"
|
||||
import { useRouteData } from "@tui/context/route"
|
||||
import { useSync } from "@tui/context/sync"
|
||||
import { pipe, sumBy } from "remeda"
|
||||
import { Theme } from "@tui/context/theme"
|
||||
import { SplitBorder } from "@tui/component/border"
|
||||
import { Locale } from "@/util/locale"
|
||||
import type { AssistantMessage } from "@opencode-ai/sdk"
|
||||
|
||||
export function Header() {
|
||||
const route = useRouteData("session")
|
||||
const sync = useSync()
|
||||
const session = createMemo(() => sync.session.get(route.sessionID)!)
|
||||
const messages = createMemo(() => sync.data.message[route.sessionID] ?? [])
|
||||
|
||||
const cost = createMemo(() => {
|
||||
const total = pipe(
|
||||
messages(),
|
||||
sumBy((x) => (x.role === "assistant" ? x.cost : 0)),
|
||||
)
|
||||
return new Intl.NumberFormat("en-US", {
|
||||
style: "currency",
|
||||
currency: "USD",
|
||||
}).format(total)
|
||||
})
|
||||
|
||||
const context = createMemo(() => {
|
||||
const last = messages().findLast((x) => x.role === "assistant" && x.tokens.output > 0) as AssistantMessage
|
||||
if (!last) return
|
||||
const total =
|
||||
last.tokens.input + last.tokens.output + last.tokens.reasoning + last.tokens.cache.read + last.tokens.cache.write
|
||||
const model = sync.data.provider.find((x) => x.id === last.providerID)?.models[last.modelID]
|
||||
return {
|
||||
total: Locale.number(total),
|
||||
percentage: (model ? Locale.number(Math.round((total / model.limit.context) * 100)) : "0") + "%",
|
||||
}
|
||||
})
|
||||
|
||||
return (
|
||||
<box paddingLeft={1} paddingRight={1} {...SplitBorder} borderColor={Theme.backgroundElement} flexShrink={0}>
|
||||
<text>
|
||||
<span style={{ bold: true, fg: Theme.accent }}>#</span> <span style={{ bold: true }}>{session().title}</span>
|
||||
</text>
|
||||
<box flexDirection="row" justifyContent="space-between">
|
||||
<Switch>
|
||||
<Match when={session().share?.url}>
|
||||
<text fg={Theme.textMuted}>{session().share!.url}</text>
|
||||
</Match>
|
||||
<Match when={true}>
|
||||
<text wrap={false}>
|
||||
/share <span style={{ fg: Theme.textMuted }}>to create a shareable link</span>
|
||||
</text>
|
||||
</Match>
|
||||
</Switch>
|
||||
<Show when={context()}>
|
||||
<text fg={Theme.textMuted} wrap={false}>
|
||||
{context()!.total}/{context()!.percentage} ({cost()})
|
||||
</text>
|
||||
</Show>
|
||||
</box>
|
||||
</box>
|
||||
)
|
||||
}
|
||||
|
|
@ -1,32 +1,33 @@
|
|||
import { createEffect, createMemo, For, Match, Show, Switch, type Component } from "solid-js"
|
||||
import { Dynamic } from "solid-js/web"
|
||||
import path from "path"
|
||||
import { useRouteData } from "./context/route"
|
||||
import { useSync } from "./context/sync"
|
||||
import { SplitBorder } from "./component/border"
|
||||
import { Theme } from "./context/theme"
|
||||
import { useRouteData } from "@tui/context/route"
|
||||
import { useSync } from "@tui/context/sync"
|
||||
import { SplitBorder } from "@tui/component/border"
|
||||
import { Theme } from "@tui/context/theme"
|
||||
import { BoxRenderable, ScrollBoxRenderable } from "@opentui/core"
|
||||
import { Prompt } from "./component/prompt"
|
||||
import { Prompt } from "@tui/component/prompt"
|
||||
import type { AssistantMessage, Part, ToolPart, UserMessage, TextPart } from "@opencode-ai/sdk"
|
||||
import { useLocal } from "./context/local"
|
||||
import { Locale } from "../../../util/locale"
|
||||
import type { Tool } from "../../../tool/tool"
|
||||
import type { ReadTool } from "../../../tool/read"
|
||||
import type { WriteTool } from "../../../tool/write"
|
||||
import { BashTool } from "../../../tool/bash"
|
||||
import type { GlobTool } from "../../../tool/glob"
|
||||
import { TodoWriteTool } from "../../../tool/todo"
|
||||
import type { GrepTool } from "../../../tool/grep"
|
||||
import type { ListTool } from "../../../tool/ls"
|
||||
import type { EditTool } from "../../../tool/edit"
|
||||
import type { PatchTool } from "../../../tool/patch"
|
||||
import type { WebFetchTool } from "../../../tool/webfetch"
|
||||
import type { TaskTool } from "../../../tool/task"
|
||||
import { useLocal } from "@tui/context/local"
|
||||
import { Locale } from "@/util/locale"
|
||||
import type { Tool } from "@/tool/tool"
|
||||
import type { ReadTool } from "@/tool/read"
|
||||
import type { WriteTool } from "@/tool/write"
|
||||
import { BashTool } from "@/tool/bash"
|
||||
import type { GlobTool } from "@/tool/glob"
|
||||
import { TodoWriteTool } from "@/tool/todo"
|
||||
import type { GrepTool } from "@/tool/grep"
|
||||
import type { ListTool } from "@/tool/ls"
|
||||
import type { EditTool } from "@/tool/edit"
|
||||
import type { PatchTool } from "@/tool/patch"
|
||||
import type { WebFetchTool } from "@/tool/webfetch"
|
||||
import type { TaskTool } from "@/tool/task"
|
||||
import { useKeyboard, type BoxProps, type JSX } from "@opentui/solid"
|
||||
import { useSDK } from "./context/sdk"
|
||||
import { useCommandDialog } from "./component/dialog-command"
|
||||
import { Shimmer } from "./ui/shimmer"
|
||||
import { useKeybind } from "./context/keybind"
|
||||
import { useSDK } from "@tui/context/sdk"
|
||||
import { useCommandDialog } from "@tui/component/dialog-command"
|
||||
import { Shimmer } from "@tui/ui/shimmer"
|
||||
import { useKeybind } from "@tui/context/keybind"
|
||||
import { Header } from "./header"
|
||||
|
||||
export function Session() {
|
||||
const route = useRouteData("session")
|
||||
|
|
@ -102,24 +103,7 @@ export function Session() {
|
|||
return (
|
||||
<box paddingTop={1} paddingBottom={1} paddingLeft={2} paddingRight={2} flexGrow={1}>
|
||||
<Show when={session()}>
|
||||
<box paddingLeft={1} paddingRight={1} {...SplitBorder} borderColor={Theme.backgroundElement} flexShrink={0}>
|
||||
<text>
|
||||
<span style={{ bold: true, fg: Theme.accent }}>#</span>{" "}
|
||||
<span style={{ bold: true }}>{session().title}</span>
|
||||
</text>
|
||||
<box flexDirection="row">
|
||||
<Switch>
|
||||
<Match when={session().share?.url}>
|
||||
<text fg={Theme.textMuted}>{session().share!.url}</text>
|
||||
</Match>
|
||||
<Match when={true}>
|
||||
<text wrap={false}>
|
||||
/share <span style={{ fg: Theme.textMuted }}>to create a shareable link</span>
|
||||
</text>
|
||||
</Match>
|
||||
</Switch>
|
||||
</box>
|
||||
</box>
|
||||
<Header />
|
||||
<scrollbox
|
||||
ref={(r) => (scroll = r)}
|
||||
scrollbarOptions={{ visible: false }}
|
||||
|
|
@ -1,24 +1,25 @@
|
|||
import { cmd } from "../cmd"
|
||||
import { cmd } from "@/cli/cmd/cmd"
|
||||
import { render, useKeyboard, useRenderer, useTerminalDimensions } from "@opentui/solid"
|
||||
import { TextAttributes } from "@opentui/core"
|
||||
import { RouteProvider, useRoute } from "./context/route"
|
||||
import { Home } from "./home"
|
||||
import { RouteProvider, useRoute } from "@tui/context/route"
|
||||
import { Switch, Match, createEffect } from "solid-js"
|
||||
import { ThemeProvider, useTheme } from "./context/theme"
|
||||
import { Installation } from "../../../installation"
|
||||
import { Global } from "../../../global"
|
||||
import { DialogProvider, useDialog } from "./ui/dialog"
|
||||
import { SDKProvider } from "./context/sdk"
|
||||
import { SyncProvider } from "./context/sync"
|
||||
import { LocalProvider, useLocal } from "./context/local"
|
||||
import { DialogModel } from "./component/dialog-model"
|
||||
import { Session } from "./session"
|
||||
import { CommandProvider, useCommandDialog } from "./component/dialog-command"
|
||||
import { DialogAgent } from "./component/dialog-agent"
|
||||
import { DialogSessionList } from "./component/dialog-session-list"
|
||||
import { KeybindProvider, useKeybind } from "./context/keybind"
|
||||
import { Config } from "../../../config/config"
|
||||
import { Instance } from "../../../project/instance"
|
||||
import { ThemeProvider, useTheme } from "@tui/context/theme"
|
||||
import { Installation } from "@/installation"
|
||||
import { Global } from "@/global"
|
||||
import { DialogProvider, useDialog } from "@tui/ui/dialog"
|
||||
import { SDKProvider } from "@tui/context/sdk"
|
||||
import { SyncProvider } from "@tui/context/sync"
|
||||
import { LocalProvider, useLocal } from "@tui/context/local"
|
||||
import { DialogModel } from "@tui/component/dialog-model"
|
||||
import { CommandProvider, useCommandDialog } from "@tui/component/dialog-command"
|
||||
import { DialogAgent } from "@tui/component/dialog-agent"
|
||||
import { DialogSessionList } from "@tui/component/dialog-session-list"
|
||||
import { KeybindProvider, useKeybind } from "@tui/context/keybind"
|
||||
import { Config } from "@/config/config"
|
||||
import { Instance } from "@/project/instance"
|
||||
|
||||
import { Home } from "@tui/routes/home"
|
||||
import { Session } from "@tui/routes/session"
|
||||
|
||||
export const TuiCommand = cmd({
|
||||
command: "$0 [project]",
|
||||
|
|
|
|||
|
|
@ -1,20 +1,21 @@
|
|||
import { InputRenderable, RGBA, ScrollBoxRenderable, TextAttributes } from "@opentui/core"
|
||||
import { Theme } from "../context/theme"
|
||||
import { entries, filter, flatMap, groupBy, pipe } from "remeda"
|
||||
import { Theme } from "@tui/context/theme"
|
||||
import { entries, filter, flatMap, groupBy, pipe, take } from "remeda"
|
||||
import { batch, createEffect, createMemo, For, Show } from "solid-js"
|
||||
import { createStore } from "solid-js/store"
|
||||
import { useKeyboard } from "@opentui/solid"
|
||||
import * as fuzzysort from "fuzzysort"
|
||||
import { isDeepEqual } from "remeda"
|
||||
import { useDialog, type DialogContext } from "./dialog"
|
||||
import { useDialog, type DialogContext } from "@tui/ui/dialog"
|
||||
import type { KeybindsConfig } from "@opencode-ai/sdk"
|
||||
import { useKeybind } from "../context/keybind"
|
||||
import { useKeybind } from "@tui/context/keybind"
|
||||
|
||||
export interface DialogSelectProps<T> {
|
||||
title: string
|
||||
options: DialogSelectOption<T>[]
|
||||
onFilter?: (query: string) => void
|
||||
onSelect?: (option: DialogSelectOption<T>) => void
|
||||
limit?: number
|
||||
current?: T
|
||||
}
|
||||
|
||||
|
|
@ -23,6 +24,7 @@ export interface DialogSelectOption<T = any> {
|
|||
value: T
|
||||
keybind?: keyof KeybindsConfig
|
||||
description?: string
|
||||
footer?: string
|
||||
category?: string
|
||||
disabled?: boolean
|
||||
onSelect?: (ctx: DialogContext) => void
|
||||
|
|
@ -42,6 +44,7 @@ export function DialogSelect<T>(props: DialogSelectProps<T>) {
|
|||
const result = pipe(
|
||||
props.options,
|
||||
filter((x) => x.disabled !== false),
|
||||
take(props.limit ?? Infinity),
|
||||
(x) => (!needle ? x : fuzzysort.go(needle, x, { keys: ["title", "category"] }).map((x) => x.obj)),
|
||||
groupBy((x) => x.category ?? ""),
|
||||
// mapValues((x) => x.sort((a, b) => a.title.localeCompare(b.title))),
|
||||
|
|
@ -64,7 +67,7 @@ export function DialogSelect<T>(props: DialogSelectProps<T>) {
|
|||
scroll.scrollTo(0)
|
||||
})
|
||||
|
||||
function move(direction: -1 | 1) {
|
||||
function move(direction: number) {
|
||||
let next = store.selected + direction
|
||||
if (next < 0) next = flat().length - 1
|
||||
if (next >= flat().length) next = 0
|
||||
|
|
@ -89,6 +92,8 @@ export function DialogSelect<T>(props: DialogSelectProps<T>) {
|
|||
useKeyboard((evt) => {
|
||||
if (evt.name === "up") move(-1)
|
||||
if (evt.name === "down") move(1)
|
||||
if (evt.name === "pageup") move(-10)
|
||||
if (evt.name === "pagedown") move(10)
|
||||
if (evt.name === "return") {
|
||||
const option = selected()
|
||||
if (option.onSelect) option.onSelect(dialog)
|
||||
|
|
@ -97,6 +102,7 @@ export function DialogSelect<T>(props: DialogSelectProps<T>) {
|
|||
})
|
||||
|
||||
let scroll: ScrollBoxRenderable
|
||||
const keybind = useKeybind()
|
||||
|
||||
return (
|
||||
<box gap={1}>
|
||||
|
|
@ -147,7 +153,7 @@ export function DialogSelect<T>(props: DialogSelectProps<T>) {
|
|||
<Option
|
||||
id={JSON.stringify(option.value)}
|
||||
title={option.title}
|
||||
keybind={option.keybind}
|
||||
footer={option.footer ?? (option.keybind ? keybind.print(option.keybind as any) : undefined)}
|
||||
description={option.description !== category ? option.description : undefined}
|
||||
active={isDeepEqual(option.value, selected()?.value)}
|
||||
current={isDeepEqual(option.value, props.current)}
|
||||
|
|
@ -181,9 +187,8 @@ function Option(props: {
|
|||
description?: string
|
||||
active?: boolean
|
||||
current?: boolean
|
||||
keybind?: string
|
||||
footer?: string
|
||||
}) {
|
||||
const keybind = useKeybind()
|
||||
return (
|
||||
<box
|
||||
id={props.id}
|
||||
|
|
@ -201,8 +206,8 @@ function Option(props: {
|
|||
</text>
|
||||
<text fg={props.active ? Theme.background : Theme.textMuted}> {props.description}</text>
|
||||
</box>
|
||||
<Show when={props.keybind}>
|
||||
<text fg={props.active ? Theme.background : Theme.textMuted}>{keybind.print(props.keybind as any)}</text>
|
||||
<Show when={props.footer}>
|
||||
<text fg={props.active ? Theme.background : Theme.textMuted}>{props.footer}</text>
|
||||
</Show>
|
||||
</box>
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { useKeyboard, useTerminalDimensions } from "@opentui/solid"
|
||||
import { createContext, For, Show, useContext, type JSX, type ParentProps } from "solid-js"
|
||||
import { Theme } from "../context/theme"
|
||||
import { Theme } from "@tui/context/theme"
|
||||
import { RGBA } from "@opentui/core"
|
||||
import { createStore, produce } from "solid-js/store"
|
||||
|
||||
|
|
|
|||
|
|
@ -7,4 +7,13 @@ export namespace Locale {
|
|||
const date = new Date(input)
|
||||
return date.toLocaleTimeString()
|
||||
}
|
||||
|
||||
export function number(num: number): string {
|
||||
if (num >= 1000000) {
|
||||
return (num / 1000000).toFixed(1) + "M"
|
||||
} else if (num >= 1000) {
|
||||
return (num / 1000).toFixed(1) + "K"
|
||||
}
|
||||
return num.toString()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,6 +5,11 @@
|
|||
"jsx": "preserve",
|
||||
"jsxImportSource": "@opentui/solid",
|
||||
"lib": ["ESNext", "DOM", "DOM.Iterable"],
|
||||
"customConditions": ["development", "browser"]
|
||||
"customConditions": ["development", "browser"],
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"@/*": ["./src/*"],
|
||||
"@tui/*": ["./src/cli/cmd/tui/*"]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue