refactor(tui): simplify form prompts
This commit is contained in:
parent
b181216ce5
commit
d6472bfda7
2 changed files with 126 additions and 131 deletions
|
|
@ -1,10 +1,10 @@
|
||||||
import { createStore } from "solid-js/store"
|
import { createStore } from "solid-js/store"
|
||||||
import { createMemo, createSignal, For, Match, onCleanup, onMount, Show, Switch } from "solid-js"
|
import { createMemo, createSignal, For, onCleanup, onMount, Show } from "solid-js"
|
||||||
import { useRenderer, useTerminalDimensions } from "@opentui/solid"
|
import { useRenderer, useTerminalDimensions } from "@opentui/solid"
|
||||||
import type { ScrollBoxRenderable, TextareaRenderable } from "@opentui/core"
|
import type { ScrollBoxRenderable, TextareaRenderable } from "@opentui/core"
|
||||||
import open from "open"
|
import open from "open"
|
||||||
import { selectedForeground, tint, useTheme } from "../../context/theme"
|
import { selectedForeground, tint, useTheme } from "../../context/theme"
|
||||||
import type { FormFormInfo, FormValue, FormWhen } from "@opencode-ai/sdk/v2"
|
import type { FormFormInfo, FormValue } from "@opencode-ai/sdk/v2"
|
||||||
import type { FormInfo } from "../../context/data"
|
import type { FormInfo } from "../../context/data"
|
||||||
import { useSDK } from "../../context/sdk"
|
import { useSDK } from "../../context/sdk"
|
||||||
import { SplitBorder } from "../../ui/border"
|
import { SplitBorder } from "../../ui/border"
|
||||||
|
|
@ -15,17 +15,6 @@ const FORM_MODE = "form"
|
||||||
|
|
||||||
type Field = FormFormInfo["fields"][number]
|
type Field = FormFormInfo["fields"][number]
|
||||||
|
|
||||||
// Mirrors core when-evaluation in packages/core/src/form.ts.
|
|
||||||
function matches(when: FormWhen, value: FormValue | undefined) {
|
|
||||||
if (value === undefined) return false
|
|
||||||
const hit = Array.isArray(value) ? value.some((item) => item === when.value) : value === when.value
|
|
||||||
return when.op === "eq" ? hit : !hit
|
|
||||||
}
|
|
||||||
|
|
||||||
function isActive(field: Field, answers: Record<string, FormValue | undefined>) {
|
|
||||||
return (field.when ?? []).every((when) => matches(when, answers[when.key]))
|
|
||||||
}
|
|
||||||
|
|
||||||
function fieldLabel(field: Field) {
|
function fieldLabel(field: Field) {
|
||||||
return field.title ?? field.key
|
return field.title ?? field.key
|
||||||
}
|
}
|
||||||
|
|
@ -34,7 +23,6 @@ function truncate(label: string, max: number) {
|
||||||
return label.length > max ? label.slice(0, max - 1).trimEnd() + "…" : label
|
return label.length > max ? label.slice(0, max - 1).trimEnd() + "…" : label
|
||||||
}
|
}
|
||||||
|
|
||||||
// Mirrors core validateField in packages/core/src/form.ts.
|
|
||||||
function validateText(field: Field, text: string): string | undefined {
|
function validateText(field: Field, text: string): string | undefined {
|
||||||
if (field.type !== "string") return
|
if (field.type !== "string") return
|
||||||
if (field.minLength !== undefined && text.length < field.minLength)
|
if (field.minLength !== undefined && text.length < field.minLength)
|
||||||
|
|
@ -49,24 +37,19 @@ function validateText(field: Field, text: string): string | undefined {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (field.format === "email" && !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(text)) return "Expected an email address"
|
if (field.format === "email" && !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(text)) return "Expected an email address"
|
||||||
if (field.format === "uri" && !isUri(text)) return "Expected a URL"
|
if (field.format === "uri") {
|
||||||
if (field.format === "date" && !isDate(text)) return "Expected a date (YYYY-MM-DD)"
|
try {
|
||||||
if (field.format === "date-time" && Number.isNaN(new Date(text).getTime())) return "Expected a date and time"
|
new URL(text)
|
||||||
}
|
} catch {
|
||||||
|
return "Expected a URL"
|
||||||
function isUri(value: string) {
|
}
|
||||||
try {
|
|
||||||
new URL(value)
|
|
||||||
return true
|
|
||||||
} catch {
|
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
}
|
if (field.format === "date") {
|
||||||
|
const date = new Date(`${text}T00:00:00.000Z`)
|
||||||
function isDate(value: string) {
|
if (!/^\d{4}-\d{2}-\d{2}$/.test(text) || Number.isNaN(date.getTime()) || date.toISOString().slice(0, 10) !== text)
|
||||||
if (!/^\d{4}-\d{2}-\d{2}$/.test(value)) return false
|
return "Expected a date (YYYY-MM-DD)"
|
||||||
const date = new Date(`${value}T00:00:00.000Z`)
|
}
|
||||||
return !Number.isNaN(date.getTime()) && date.toISOString().slice(0, 10) === value
|
if (field.format === "date-time" && Number.isNaN(new Date(text).getTime())) return "Expected a date and time"
|
||||||
}
|
}
|
||||||
|
|
||||||
function fieldRows(field: Field): { value: FormValue; label: string; description?: string }[] {
|
function fieldRows(field: Field): { value: FormValue; label: string; description?: string }[] {
|
||||||
|
|
@ -93,12 +76,7 @@ function display(field: Field, value: FormValue | undefined) {
|
||||||
}
|
}
|
||||||
|
|
||||||
export function FormPrompt(props: { form: FormInfo }) {
|
export function FormPrompt(props: { form: FormInfo }) {
|
||||||
return (
|
return props.form.mode === "url" ? <UrlPrompt form={props.form} /> : <FieldsPrompt form={props.form} />
|
||||||
<Switch>
|
|
||||||
<Match when={props.form.mode === "url" && props.form}>{(form) => <UrlPrompt form={form()} />}</Match>
|
|
||||||
<Match when={props.form.mode === "form" && props.form}>{(form) => <FieldsPrompt form={form()} />}</Match>
|
|
||||||
</Switch>
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function UrlPrompt(props: { form: FormInfo & { mode: "url" } }) {
|
function UrlPrompt(props: { form: FormInfo & { mode: "url" } }) {
|
||||||
|
|
@ -110,10 +88,7 @@ function UrlPrompt(props: { form: FormInfo & { mode: "url" } }) {
|
||||||
return typeof value === "string" ? value : undefined
|
return typeof value === "string" ? value : undefined
|
||||||
})
|
})
|
||||||
|
|
||||||
onMount(() => {
|
onMount(() => onCleanup(modeStack.push(FORM_MODE)))
|
||||||
const popMode = modeStack.push(FORM_MODE)
|
|
||||||
onCleanup(popMode)
|
|
||||||
})
|
|
||||||
|
|
||||||
useBindings(() => ({
|
useBindings(() => ({
|
||||||
mode: FORM_MODE,
|
mode: FORM_MODE,
|
||||||
|
|
@ -182,14 +157,12 @@ function FieldsPrompt(props: { form: FormInfo & { mode: "form" } }) {
|
||||||
const tuiConfig = useTuiConfig()
|
const tuiConfig = useTuiConfig()
|
||||||
const modeStack = useOpencodeModeStack()
|
const modeStack = useOpencodeModeStack()
|
||||||
|
|
||||||
const defaults = Object.fromEntries(
|
|
||||||
props.form.fields.flatMap((field) => (field.default === undefined ? [] : [[field.key, field.default]])),
|
|
||||||
) as Record<string, FormValue | undefined>
|
|
||||||
|
|
||||||
const [tabHover, setTabHover] = createSignal<number | "confirm" | null>(null)
|
const [tabHover, setTabHover] = createSignal<number | "confirm" | null>(null)
|
||||||
const [store, setStore] = createStore({
|
const [store, setStore] = createStore({
|
||||||
tab: 0,
|
tab: 0,
|
||||||
answers: defaults,
|
answers: Object.fromEntries(
|
||||||
|
props.form.fields.flatMap((field) => (field.default === undefined ? [] : [[field.key, field.default]])),
|
||||||
|
) as Record<string, FormValue | undefined>,
|
||||||
custom: {} as Record<string, string>,
|
custom: {} as Record<string, string>,
|
||||||
selected: 0,
|
selected: 0,
|
||||||
editing: false,
|
editing: false,
|
||||||
|
|
@ -199,7 +172,16 @@ function FieldsPrompt(props: { form: FormInfo & { mode: "form" } }) {
|
||||||
let textarea: TextareaRenderable | undefined
|
let textarea: TextareaRenderable | undefined
|
||||||
let review: ScrollBoxRenderable | undefined
|
let review: ScrollBoxRenderable | undefined
|
||||||
|
|
||||||
const fields = createMemo(() => props.form.fields.filter((field) => isActive(field, store.answers)))
|
const fields = createMemo(() =>
|
||||||
|
props.form.fields.filter((field) =>
|
||||||
|
(field.when ?? []).every((when) => {
|
||||||
|
const value = store.answers[when.key]
|
||||||
|
if (value === undefined) return false
|
||||||
|
const hit = Array.isArray(value) ? value.some((item) => item === when.value) : value === when.value
|
||||||
|
return when.op === "eq" ? hit : !hit
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
)
|
||||||
const single = createMemo(() => {
|
const single = createMemo(() => {
|
||||||
const list = fields()
|
const list = fields()
|
||||||
if (list.length !== 1) return false
|
if (list.length !== 1) return false
|
||||||
|
|
@ -266,30 +248,6 @@ function FieldsPrompt(props: { form: FormInfo & { mode: "form" } }) {
|
||||||
setStore("error", "")
|
setStore("error", "")
|
||||||
}
|
}
|
||||||
|
|
||||||
function submit() {
|
|
||||||
const entries = fields().flatMap((field) => {
|
|
||||||
const value = store.answers[field.key]
|
|
||||||
return value === undefined ? [] : [[field.key, value] as const]
|
|
||||||
})
|
|
||||||
sdk.api.form
|
|
||||||
.reply({
|
|
||||||
sessionID: props.form.sessionID,
|
|
||||||
formID: props.form.id,
|
|
||||||
answer: Object.fromEntries(entries),
|
|
||||||
})
|
|
||||||
.catch((error: unknown) => {
|
|
||||||
const message =
|
|
||||||
typeof error === "object" && error !== null && "message" in error && typeof error.message === "string"
|
|
||||||
? error.message
|
|
||||||
: "Invalid answer"
|
|
||||||
setStore("error", message)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
function cancel() {
|
|
||||||
void sdk.api.form.cancel({ sessionID: props.form.sessionID, formID: props.form.id })
|
|
||||||
}
|
|
||||||
|
|
||||||
function pick(value: FormValue, customValue?: string) {
|
function pick(value: FormValue, customValue?: string) {
|
||||||
const current = field()
|
const current = field()
|
||||||
if (!current) return
|
if (!current) return
|
||||||
|
|
@ -318,10 +276,6 @@ function FieldsPrompt(props: { form: FormInfo & { mode: "form" } }) {
|
||||||
answer(current.key, list)
|
answer(current.key, list)
|
||||||
}
|
}
|
||||||
|
|
||||||
function moveTo(index: number) {
|
|
||||||
setStore("selected", index)
|
|
||||||
}
|
|
||||||
|
|
||||||
function selectTab(index: number) {
|
function selectTab(index: number) {
|
||||||
setStore("tab", index)
|
setStore("tab", index)
|
||||||
setStore("selected", 0)
|
setStore("selected", 0)
|
||||||
|
|
@ -391,10 +345,7 @@ function FieldsPrompt(props: { form: FormInfo & { mode: "form" } }) {
|
||||||
move()
|
move()
|
||||||
}
|
}
|
||||||
|
|
||||||
onMount(() => {
|
onMount(() => onCleanup(modeStack.push(FORM_MODE)))
|
||||||
const popMode = modeStack.push(FORM_MODE)
|
|
||||||
onCleanup(popMode)
|
|
||||||
})
|
|
||||||
|
|
||||||
useBindings(() => ({
|
useBindings(() => ({
|
||||||
mode: FORM_MODE,
|
mode: FORM_MODE,
|
||||||
|
|
@ -421,7 +372,7 @@ function FieldsPrompt(props: { form: FormInfo & { mode: "form" } }) {
|
||||||
group: "Form",
|
group: "Form",
|
||||||
cmd: () => {
|
cmd: () => {
|
||||||
if (textual()) {
|
if (textual()) {
|
||||||
cancel()
|
void sdk.api.form.cancel({ sessionID: props.form.sessionID, formID: props.form.id })
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
setStore("editing", false)
|
setStore("editing", false)
|
||||||
|
|
@ -515,7 +466,7 @@ function FieldsPrompt(props: { form: FormInfo & { mode: "form" } }) {
|
||||||
title: "Dismiss form",
|
title: "Dismiss form",
|
||||||
category: "Form",
|
category: "Form",
|
||||||
run() {
|
run() {
|
||||||
cancel()
|
void sdk.api.form.cancel({ sessionID: props.form.sessionID, formID: props.form.id })
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|
@ -548,8 +499,43 @@ function FieldsPrompt(props: { form: FormInfo & { mode: "form" } }) {
|
||||||
},
|
},
|
||||||
...(confirm()
|
...(confirm()
|
||||||
? [
|
? [
|
||||||
{ key: "return", desc: "Submit form", group: "Form", cmd: () => submit() },
|
{
|
||||||
{ key: "escape", desc: "Dismiss form", group: "Form", cmd: () => cancel() },
|
key: "return",
|
||||||
|
desc: "Submit form",
|
||||||
|
group: "Form",
|
||||||
|
cmd: () => {
|
||||||
|
sdk.api.form
|
||||||
|
.reply({
|
||||||
|
sessionID: props.form.sessionID,
|
||||||
|
formID: props.form.id,
|
||||||
|
answer: Object.fromEntries(
|
||||||
|
fields().flatMap((field) => {
|
||||||
|
const value = store.answers[field.key]
|
||||||
|
return value === undefined ? [] : [[field.key, value] as const]
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
})
|
||||||
|
.catch((error: unknown) => {
|
||||||
|
setStore(
|
||||||
|
"error",
|
||||||
|
typeof error === "object" &&
|
||||||
|
error !== null &&
|
||||||
|
"message" in error &&
|
||||||
|
typeof error.message === "string"
|
||||||
|
? error.message
|
||||||
|
: "Invalid answer",
|
||||||
|
)
|
||||||
|
})
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "escape",
|
||||||
|
desc: "Dismiss form",
|
||||||
|
group: "Form",
|
||||||
|
cmd: () => {
|
||||||
|
void sdk.api.form.cancel({ sessionID: props.form.sessionID, formID: props.form.id })
|
||||||
|
},
|
||||||
|
},
|
||||||
{ key: "up", desc: "Scroll review", group: "Form", cmd: () => review?.scrollBy(-1) },
|
{ key: "up", desc: "Scroll review", group: "Form", cmd: () => review?.scrollBy(-1) },
|
||||||
{ key: "k", desc: "Scroll review", group: "Form", cmd: () => review?.scrollBy(-1) },
|
{ key: "k", desc: "Scroll review", group: "Form", cmd: () => review?.scrollBy(-1) },
|
||||||
{ key: "down", desc: "Scroll review", group: "Form", cmd: () => review?.scrollBy(1) },
|
{ key: "down", desc: "Scroll review", group: "Form", cmd: () => review?.scrollBy(1) },
|
||||||
|
|
@ -562,7 +548,7 @@ function FieldsPrompt(props: { form: FormInfo & { mode: "form" } }) {
|
||||||
desc: `Select answer ${index + 1}`,
|
desc: `Select answer ${index + 1}`,
|
||||||
group: "Form",
|
group: "Form",
|
||||||
cmd: () => {
|
cmd: () => {
|
||||||
moveTo(index)
|
setStore("selected", index)
|
||||||
selectOption()
|
selectOption()
|
||||||
},
|
},
|
||||||
})),
|
})),
|
||||||
|
|
@ -570,18 +556,25 @@ function FieldsPrompt(props: { form: FormInfo & { mode: "form" } }) {
|
||||||
key: "up",
|
key: "up",
|
||||||
desc: "Previous answer",
|
desc: "Previous answer",
|
||||||
group: "Form",
|
group: "Form",
|
||||||
cmd: () => moveTo((store.selected - 1 + total) % total),
|
cmd: () => setStore("selected", (store.selected - 1 + total) % total),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: "k",
|
key: "k",
|
||||||
desc: "Previous answer",
|
desc: "Previous answer",
|
||||||
group: "Form",
|
group: "Form",
|
||||||
cmd: () => moveTo((store.selected - 1 + total) % total),
|
cmd: () => setStore("selected", (store.selected - 1 + total) % total),
|
||||||
},
|
},
|
||||||
{ key: "down", desc: "Next answer", group: "Form", cmd: () => moveTo((store.selected + 1) % total) },
|
{ key: "down", desc: "Next answer", group: "Form", cmd: () => setStore("selected", (store.selected + 1) % total) },
|
||||||
{ key: "j", desc: "Next answer", group: "Form", cmd: () => moveTo((store.selected + 1) % total) },
|
{ key: "j", desc: "Next answer", group: "Form", cmd: () => setStore("selected", (store.selected + 1) % total) },
|
||||||
{ key: "return", desc: "Select answer", group: "Form", cmd: () => selectOption() },
|
{ key: "return", desc: "Select answer", group: "Form", cmd: () => selectOption() },
|
||||||
{ key: "escape", desc: "Dismiss form", group: "Form", cmd: () => cancel() },
|
{
|
||||||
|
key: "escape",
|
||||||
|
desc: "Dismiss form",
|
||||||
|
group: "Form",
|
||||||
|
cmd: () => {
|
||||||
|
void sdk.api.form.cancel({ sessionID: props.form.sessionID, formID: props.form.id })
|
||||||
|
},
|
||||||
|
},
|
||||||
...tuiConfig.keybinds.get("app.exit"),
|
...tuiConfig.keybinds.get("app.exit"),
|
||||||
]),
|
]),
|
||||||
],
|
],
|
||||||
|
|
@ -711,8 +704,8 @@ function FieldsPrompt(props: { form: FormInfo & { mode: "form" } }) {
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<box
|
<box
|
||||||
onMouseOver={() => moveTo(i())}
|
onMouseOver={() => setStore("selected", i())}
|
||||||
onMouseDown={() => moveTo(i())}
|
onMouseDown={() => setStore("selected", i())}
|
||||||
onMouseUp={() => {
|
onMouseUp={() => {
|
||||||
if (renderer.getSelection()?.getSelectedText()) return
|
if (renderer.getSelection()?.getSelectedText()) return
|
||||||
selectOption()
|
selectOption()
|
||||||
|
|
@ -744,8 +737,8 @@ function FieldsPrompt(props: { form: FormInfo & { mode: "form" } }) {
|
||||||
</For>
|
</For>
|
||||||
<Show when={custom()}>
|
<Show when={custom()}>
|
||||||
<box
|
<box
|
||||||
onMouseOver={() => moveTo(rows().length)}
|
onMouseOver={() => setStore("selected", rows().length)}
|
||||||
onMouseDown={() => moveTo(rows().length)}
|
onMouseDown={() => setStore("selected", rows().length)}
|
||||||
onMouseUp={() => {
|
onMouseUp={() => {
|
||||||
if (renderer.getSelection()?.getSelectedText()) return
|
if (renderer.getSelection()?.getSelectedText()) return
|
||||||
selectOption()
|
selectOption()
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,7 @@ export function QuestionPrompt(props: { request: QuestionV2Request; directory?:
|
||||||
|
|
||||||
const questions = createMemo(() => props.request.questions)
|
const questions = createMemo(() => props.request.questions)
|
||||||
const single = createMemo(() => questions().length === 1 && questions()[0]?.multiple !== true)
|
const single = createMemo(() => questions().length === 1 && questions()[0]?.multiple !== true)
|
||||||
const tabs = createMemo(() => (single() ? 1 : questions().length + 1)) // questions + confirm tab (no confirm for single select)
|
const tabs = createMemo(() => (single() ? 1 : questions().length + 1))
|
||||||
const [tabHover, setTabHover] = createSignal<number | "confirm" | null>(null)
|
const [tabHover, setTabHover] = createSignal<number | "confirm" | null>(null)
|
||||||
const [store, setStore] = createStore({
|
const [store, setStore] = createStore({
|
||||||
tab: 0,
|
tab: 0,
|
||||||
|
|
@ -45,22 +45,6 @@ export function QuestionPrompt(props: { request: QuestionV2Request; directory?:
|
||||||
return store.answers[store.tab]?.includes(value) ?? false
|
return store.answers[store.tab]?.includes(value) ?? false
|
||||||
})
|
})
|
||||||
|
|
||||||
function submit() {
|
|
||||||
const answers = questions().map((_, i) => store.answers[i] ?? [])
|
|
||||||
void sdk.api.question.reply({
|
|
||||||
sessionID: props.request.sessionID,
|
|
||||||
requestID: props.request.id,
|
|
||||||
answers,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
function reject() {
|
|
||||||
void sdk.api.question.reject({
|
|
||||||
sessionID: props.request.sessionID,
|
|
||||||
requestID: props.request.id,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
function pick(answer: string, custom: boolean = false) {
|
function pick(answer: string, custom: boolean = false) {
|
||||||
const answers = [...store.answers]
|
const answers = [...store.answers]
|
||||||
answers[store.tab] = [answer]
|
answers[store.tab] = [answer]
|
||||||
|
|
@ -93,10 +77,6 @@ export function QuestionPrompt(props: { request: QuestionV2Request; directory?:
|
||||||
setStore("answers", answers)
|
setStore("answers", answers)
|
||||||
}
|
}
|
||||||
|
|
||||||
function moveTo(index: number) {
|
|
||||||
setStore("selected", index)
|
|
||||||
}
|
|
||||||
|
|
||||||
function selectTab(index: number) {
|
function selectTab(index: number) {
|
||||||
setStore("tab", index)
|
setStore("tab", index)
|
||||||
setStore("selected", 0)
|
setStore("selected", 0)
|
||||||
|
|
@ -125,10 +105,7 @@ export function QuestionPrompt(props: { request: QuestionV2Request; directory?:
|
||||||
pick(opt.label)
|
pick(opt.label)
|
||||||
}
|
}
|
||||||
|
|
||||||
onMount(() => {
|
onMount(() => onCleanup(modeStack.push(QUESTION_MODE)))
|
||||||
const popMode = modeStack.push(QUESTION_MODE)
|
|
||||||
onCleanup(popMode)
|
|
||||||
})
|
|
||||||
|
|
||||||
useBindings(() => ({
|
useBindings(() => ({
|
||||||
mode: QUESTION_MODE,
|
mode: QUESTION_MODE,
|
||||||
|
|
@ -220,7 +197,7 @@ export function QuestionPrompt(props: { request: QuestionV2Request; directory?:
|
||||||
title: "Reject question",
|
title: "Reject question",
|
||||||
category: "Question",
|
category: "Question",
|
||||||
run() {
|
run() {
|
||||||
reject()
|
void sdk.api.question.reject({ sessionID: props.request.sessionID, requestID: props.request.id })
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|
@ -249,8 +226,26 @@ export function QuestionPrompt(props: { request: QuestionV2Request; directory?:
|
||||||
},
|
},
|
||||||
...(confirm()
|
...(confirm()
|
||||||
? [
|
? [
|
||||||
{ key: "return", desc: "Submit answer", group: "Question", cmd: () => submit() },
|
{
|
||||||
{ key: "escape", desc: "Reject question", group: "Question", cmd: () => reject() },
|
key: "return",
|
||||||
|
desc: "Submit answer",
|
||||||
|
group: "Question",
|
||||||
|
cmd: () => {
|
||||||
|
void sdk.api.question.reply({
|
||||||
|
sessionID: props.request.sessionID,
|
||||||
|
requestID: props.request.id,
|
||||||
|
answers: questions().map((_, i) => store.answers[i] ?? []),
|
||||||
|
})
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "escape",
|
||||||
|
desc: "Reject question",
|
||||||
|
group: "Question",
|
||||||
|
cmd: () => {
|
||||||
|
void sdk.api.question.reject({ sessionID: props.request.sessionID, requestID: props.request.id })
|
||||||
|
},
|
||||||
|
},
|
||||||
...tuiConfig.keybinds.get("app.exit"),
|
...tuiConfig.keybinds.get("app.exit"),
|
||||||
]
|
]
|
||||||
: [
|
: [
|
||||||
|
|
@ -259,7 +254,7 @@ export function QuestionPrompt(props: { request: QuestionV2Request; directory?:
|
||||||
desc: `Select answer ${index + 1}`,
|
desc: `Select answer ${index + 1}`,
|
||||||
group: "Question",
|
group: "Question",
|
||||||
cmd: () => {
|
cmd: () => {
|
||||||
moveTo(index)
|
setStore("selected", index)
|
||||||
selectOption()
|
selectOption()
|
||||||
},
|
},
|
||||||
})),
|
})),
|
||||||
|
|
@ -267,18 +262,25 @@ export function QuestionPrompt(props: { request: QuestionV2Request; directory?:
|
||||||
key: "up",
|
key: "up",
|
||||||
desc: "Previous answer",
|
desc: "Previous answer",
|
||||||
group: "Question",
|
group: "Question",
|
||||||
cmd: () => moveTo((store.selected - 1 + total) % total),
|
cmd: () => setStore("selected", (store.selected - 1 + total) % total),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: "k",
|
key: "k",
|
||||||
desc: "Previous answer",
|
desc: "Previous answer",
|
||||||
group: "Question",
|
group: "Question",
|
||||||
cmd: () => moveTo((store.selected - 1 + total) % total),
|
cmd: () => setStore("selected", (store.selected - 1 + total) % total),
|
||||||
},
|
},
|
||||||
{ key: "down", desc: "Next answer", group: "Question", cmd: () => moveTo((store.selected + 1) % total) },
|
{ key: "down", desc: "Next answer", group: "Question", cmd: () => setStore("selected", (store.selected + 1) % total) },
|
||||||
{ key: "j", desc: "Next answer", group: "Question", cmd: () => moveTo((store.selected + 1) % total) },
|
{ key: "j", desc: "Next answer", group: "Question", cmd: () => setStore("selected", (store.selected + 1) % total) },
|
||||||
{ key: "return", desc: "Select answer", group: "Question", cmd: () => selectOption() },
|
{ key: "return", desc: "Select answer", group: "Question", cmd: () => selectOption() },
|
||||||
{ key: "escape", desc: "Reject question", group: "Question", cmd: () => reject() },
|
{
|
||||||
|
key: "escape",
|
||||||
|
desc: "Reject question",
|
||||||
|
group: "Question",
|
||||||
|
cmd: () => {
|
||||||
|
void sdk.api.question.reject({ sessionID: props.request.sessionID, requestID: props.request.id })
|
||||||
|
},
|
||||||
|
},
|
||||||
...tuiConfig.keybinds.get("app.exit"),
|
...tuiConfig.keybinds.get("app.exit"),
|
||||||
]),
|
]),
|
||||||
],
|
],
|
||||||
|
|
@ -367,8 +369,8 @@ export function QuestionPrompt(props: { request: QuestionV2Request; directory?:
|
||||||
const picked = () => store.answers[store.tab]?.includes(opt.label) ?? false
|
const picked = () => store.answers[store.tab]?.includes(opt.label) ?? false
|
||||||
return (
|
return (
|
||||||
<box
|
<box
|
||||||
onMouseOver={() => moveTo(i())}
|
onMouseOver={() => setStore("selected", i())}
|
||||||
onMouseDown={() => moveTo(i())}
|
onMouseDown={() => setStore("selected", i())}
|
||||||
onMouseUp={() => {
|
onMouseUp={() => {
|
||||||
if (renderer.getSelection()?.getSelectedText()) return
|
if (renderer.getSelection()?.getSelectedText()) return
|
||||||
selectOption()
|
selectOption()
|
||||||
|
|
@ -399,8 +401,8 @@ export function QuestionPrompt(props: { request: QuestionV2Request; directory?:
|
||||||
</For>
|
</For>
|
||||||
<Show when={custom()}>
|
<Show when={custom()}>
|
||||||
<box
|
<box
|
||||||
onMouseOver={() => moveTo(options().length)}
|
onMouseOver={() => setStore("selected", options().length)}
|
||||||
onMouseDown={() => moveTo(options().length)}
|
onMouseDown={() => setStore("selected", options().length)}
|
||||||
onMouseUp={() => {
|
onMouseUp={() => {
|
||||||
if (renderer.getSelection()?.getSelectedText()) return
|
if (renderer.getSelection()?.getSelectedText()) return
|
||||||
selectOption()
|
selectOption()
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue