feat(ui): split submit tab into actions and review
This commit is contained in:
@@ -9,6 +9,8 @@ export const UI_DIMENSIONS = {
|
|||||||
previewLeftMinWidth: 22,
|
previewLeftMinWidth: 22,
|
||||||
previewLeftMaxWidth: 34,
|
previewLeftMaxWidth: 34,
|
||||||
previewLeftRatio: 0.34,
|
previewLeftRatio: 0.34,
|
||||||
|
submitWideMinWidth: 64,
|
||||||
|
submitActionsWidth: 24,
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
export const UI_TEXT = {
|
export const UI_TEXT = {
|
||||||
|
|||||||
+58
-4
@@ -2,8 +2,8 @@ import { truncateToWidth, visibleWidth } from "@mariozechner/pi-tui";
|
|||||||
import { SUBMIT_CHOICES } from "../constants.ts";
|
import { SUBMIT_CHOICES } from "../constants.ts";
|
||||||
import { serializeAnswer } from "../state/answers.ts";
|
import { serializeAnswer } from "../state/answers.ts";
|
||||||
import type { AskResult, AskState, AskStateAnswer } from "../types.ts";
|
import type { AskResult, AskState, AskStateAnswer } from "../types.ts";
|
||||||
import { UI_TEXT } from "./constants.ts";
|
import { UI_DIMENSIONS, UI_TEXT } from "./constants.ts";
|
||||||
import { pushWrappedText } from "./render-helpers.ts";
|
import { mergeColumns, pushWrappedText } from "./render-helpers.ts";
|
||||||
import type { Theme } from "./render-types.ts";
|
import type { Theme } from "./render-types.ts";
|
||||||
|
|
||||||
type ReviewAnswer = AskResult["answers"][string] & {
|
type ReviewAnswer = AskResult["answers"][string] & {
|
||||||
@@ -19,10 +19,51 @@ export function renderSubmitScreen(
|
|||||||
theme: Theme,
|
theme: Theme,
|
||||||
width: number
|
width: number
|
||||||
) {
|
) {
|
||||||
|
const showAllNotes = state.activeSubmitActionIndex === 1;
|
||||||
|
|
||||||
|
if (shouldUseWideSubmitLayout(width)) {
|
||||||
|
const leftWidth = UI_DIMENSIONS.submitActionsWidth;
|
||||||
|
const rightWidth = Math.max(1, width - leftWidth - 2);
|
||||||
|
const reviewLines = renderSubmitReviewLines(
|
||||||
|
state,
|
||||||
|
theme,
|
||||||
|
rightWidth,
|
||||||
|
showAllNotes
|
||||||
|
);
|
||||||
|
const actionLines = renderSubmitActionLines(state, theme, leftWidth);
|
||||||
|
for (const line of mergeColumns(
|
||||||
|
actionLines,
|
||||||
|
reviewLines,
|
||||||
|
leftWidth,
|
||||||
|
width
|
||||||
|
)) {
|
||||||
|
lines.push(line);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const reviewLines = renderSubmitReviewLines(
|
||||||
|
state,
|
||||||
|
theme,
|
||||||
|
width,
|
||||||
|
showAllNotes
|
||||||
|
);
|
||||||
|
const actionLines = renderSubmitActionLines(state, theme, width);
|
||||||
|
lines.push(...reviewLines);
|
||||||
|
lines.push("");
|
||||||
|
lines.push(...actionLines);
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderSubmitReviewLines(
|
||||||
|
state: AskState,
|
||||||
|
theme: Theme,
|
||||||
|
width: number,
|
||||||
|
showAllNotes: boolean
|
||||||
|
): string[] {
|
||||||
|
const lines: string[] = [];
|
||||||
pushWrappedText(lines, UI_TEXT.reviewTitle, width, theme, "accent", " ", " ");
|
pushWrappedText(lines, UI_TEXT.reviewTitle, width, theme, "accent", " ", " ");
|
||||||
lines.push("");
|
lines.push("");
|
||||||
|
|
||||||
const showAllNotes = state.activeSubmitActionIndex === 1;
|
|
||||||
for (let index = 0; index < state.questions.length; index++) {
|
for (let index = 0; index < state.questions.length; index++) {
|
||||||
const question = state.questions[index];
|
const question = state.questions[index];
|
||||||
renderSubmittedQuestion(
|
renderSubmittedQuestion(
|
||||||
@@ -37,8 +78,21 @@ export function renderSubmitScreen(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
lines.push("");
|
return lines;
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderSubmitActionLines(
|
||||||
|
state: AskState,
|
||||||
|
theme: Theme,
|
||||||
|
width: number
|
||||||
|
): string[] {
|
||||||
|
const lines: string[] = [];
|
||||||
renderSubmitActions(lines, state, theme, width);
|
renderSubmitActions(lines, state, theme, width);
|
||||||
|
return lines;
|
||||||
|
}
|
||||||
|
|
||||||
|
function shouldUseWideSubmitLayout(width: number): boolean {
|
||||||
|
return width >= UI_DIMENSIONS.submitWideMinWidth;
|
||||||
}
|
}
|
||||||
|
|
||||||
function renderSubmittedQuestion(
|
function renderSubmittedQuestion(
|
||||||
|
|||||||
@@ -45,6 +45,61 @@ test("submit screen uses shorter action labels without extra prompt text", () =>
|
|||||||
assert(!lines.some((line) => line.includes("1. Submit answers")));
|
assert(!lines.some((line) => line.includes("1. Submit answers")));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("submit screen uses side-by-side layout on wide screens", () => {
|
||||||
|
const state = createInitialState({
|
||||||
|
questions: [
|
||||||
|
{
|
||||||
|
id: "q1",
|
||||||
|
label: "Color",
|
||||||
|
prompt: "Pick one color.",
|
||||||
|
options: [{ value: "blue", label: "Blue" }],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
const lines: string[] = [];
|
||||||
|
renderSubmitScreen(lines, state, mockTheme(), 120);
|
||||||
|
|
||||||
|
assert(
|
||||||
|
lines.some(
|
||||||
|
(line) => line.includes("1. Submit") && line.includes("Review answers")
|
||||||
|
)
|
||||||
|
);
|
||||||
|
assert(
|
||||||
|
lines.some((line) => line.includes("3. Cancel") && line.includes("Color"))
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("submit screen stacks review above actions on narrow screens", () => {
|
||||||
|
const state = createInitialState({
|
||||||
|
questions: [
|
||||||
|
{
|
||||||
|
id: "q1",
|
||||||
|
label: "Color",
|
||||||
|
prompt: "Pick one color.",
|
||||||
|
options: [{ value: "blue", label: "Blue" }],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
const lines: string[] = [];
|
||||||
|
renderSubmitScreen(lines, state, mockTheme(), 50);
|
||||||
|
|
||||||
|
const reviewIndex = lines.findIndex((line) =>
|
||||||
|
line.includes("Review answers")
|
||||||
|
);
|
||||||
|
const actionIndex = lines.findIndex((line) => line.includes("1. Submit"));
|
||||||
|
|
||||||
|
assert.notEqual(reviewIndex, -1);
|
||||||
|
assert.notEqual(actionIndex, -1);
|
||||||
|
assert(reviewIndex < actionIndex);
|
||||||
|
assert(
|
||||||
|
!lines.some(
|
||||||
|
(line) => line.includes("1. Submit") && line.includes("Review answers")
|
||||||
|
)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
test("submit screen shows notes only for answered questions in submit mode", () => {
|
test("submit screen shows notes only for answered questions in submit mode", () => {
|
||||||
let state = createInitialState({
|
let state = createInitialState({
|
||||||
questions: [
|
questions: [
|
||||||
@@ -72,7 +127,7 @@ test("submit screen shows notes only for answered questions in submit mode", ()
|
|||||||
state = saveNote(state, "Second note");
|
state = saveNote(state, "Second note");
|
||||||
|
|
||||||
const lines: string[] = [];
|
const lines: string[] = [];
|
||||||
renderSubmitScreen(lines, state, mockTheme(), 80);
|
renderSubmitScreen(lines, state, mockTheme(), 140);
|
||||||
|
|
||||||
const firstQuestionIndex = lines.findIndex((line) =>
|
const firstQuestionIndex = lines.findIndex((line) =>
|
||||||
line.includes("<text>Single</text>")
|
line.includes("<text>Single</text>")
|
||||||
@@ -99,7 +154,7 @@ test("submit screen shows notes only for answered questions in submit mode", ()
|
|||||||
assert(optionNoteIndex > firstAnswerIndex);
|
assert(optionNoteIndex > firstAnswerIndex);
|
||||||
assert(lines[firstQuestionNoteIndex]?.startsWith(" "));
|
assert(lines[firstQuestionNoteIndex]?.startsWith(" "));
|
||||||
assert(lines[optionNoteIndex]?.startsWith(" "));
|
assert(lines[optionNoteIndex]?.startsWith(" "));
|
||||||
assert.equal(lines[secondQuestionIndex - 1], "");
|
assert.equal(lines[secondQuestionIndex - 1]?.trim(), "");
|
||||||
assert(
|
assert(
|
||||||
lines.some((line) => line.includes("<syntaxString>Note:</syntaxString>"))
|
lines.some((line) => line.includes("<syntaxString>Note:</syntaxString>"))
|
||||||
);
|
);
|
||||||
@@ -178,7 +233,7 @@ test("submit screen renders multi-select option notes under their related answer
|
|||||||
state = saveNote(state, "Second note");
|
state = saveNote(state, "Second note");
|
||||||
|
|
||||||
const lines: string[] = [];
|
const lines: string[] = [];
|
||||||
renderSubmitScreen(lines, state, mockTheme(), 80);
|
renderSubmitScreen(lines, state, mockTheme(), 140);
|
||||||
|
|
||||||
const firstAnswerIndex = lines.findIndex((line) =>
|
const firstAnswerIndex = lines.findIndex((line) =>
|
||||||
line.includes("→ Follow-up action")
|
line.includes("→ Follow-up action")
|
||||||
|
|||||||
Reference in New Issue
Block a user