@cspeach/cli 1.1.20 → 1.1.22
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/sap/standalone-profile.js +1 -1
- package/dist/sap/system-info.js +50 -27
- package/dist/skills/bundled-skills.js +118 -118
- package/dist/upgrade.js +4 -2
- package/package.json +121 -121
- package/dist/commands/project.js +0 -301
- package/dist/tools/local-files.js +0 -31
- package/dist/tools/subagent/schedule_create.js +0 -131
- package/dist/ui/error-emitter.js +0 -21
- package/dist/ui/header.js +0 -41
- package/dist/ui/ink-classifier-route.js +0 -19
- package/dist/ui/rich-error-box.js +0 -9
- package/dist/ui/sidebar.js +0 -12
- package/dist/ui/status-emitter.js +0 -19
- package/dist/ui/status-line.js +0 -62
- package/dist/ui/status-row.js +0 -38
- package/dist/ui/todo-panel.js +0 -85
- package/dist/ui/widgets/ask-form.js +0 -250
- package/dist/ui/widgets/ask-question-modal.js +0 -178
- package/fiori/zlas-inspection-lr/package.json +0 -26
- package/fiori/zlas-inspection-lr/ui5-local.yaml +0 -45
- package/fiori/zlas-inspection-lr/ui5.yaml +0 -41
- package/fiori/zlas-inspection-lr/webapp/Component.js +0 -12
- package/fiori/zlas-inspection-lr/webapp/i18n/i18n.properties +0 -9
- package/fiori/zlas-inspection-lr/webapp/index.html +0 -35
- package/fiori/zlas-inspection-lr/webapp/manifest.json +0 -145
|
@@ -1,250 +0,0 @@
|
|
|
1
|
-
import { jsxs as _jsxs, jsx as _jsx, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
-
/**
|
|
3
|
-
* AskForm — UX Wave 2 / Task 2: the multi-question Ink form for
|
|
4
|
-
* ask_question v2 batched requests (the CC-parity flagship interaction).
|
|
5
|
-
*
|
|
6
|
-
* One request = 1..4 questions answered in a single visit. Visual
|
|
7
|
-
* de-clutter pass (2026-07, product-owner review): no border box — the
|
|
8
|
-
* chip row + indentation is the form's identity; the approval modal keeps
|
|
9
|
-
* its border deliberately (danger checkpoint):
|
|
10
|
-
*
|
|
11
|
-
* ✓ env name style ← chips (two-space gaps)
|
|
12
|
-
*
|
|
13
|
-
* Which naming style?
|
|
14
|
-
* Used for every generated object
|
|
15
|
-
* ▸ snake_case lv_/lt_ prefixes
|
|
16
|
-
* CamelCase modern style
|
|
17
|
-
* ✎ Other (type your own)
|
|
18
|
-
* [ Submit ] ← ONLY once all answered
|
|
19
|
-
* Enter pick · Tab next · Esc cancel ← one context-sensitive hint
|
|
20
|
-
*
|
|
21
|
-
* Binding rules (Task-2 brief + de-clutter pass):
|
|
22
|
-
* - Chips: one per question (`header`), two-space gaps (no pipes);
|
|
23
|
-
* current = peach + bold, answered = dim with `✓ ` prefix,
|
|
24
|
-
* unanswered = dim. ONE blank line separates the chip row from the
|
|
25
|
-
* question text. Tab/→ next, Shift+Tab/← previous — all INSIDE the
|
|
26
|
-
* form; App's global handlers (mode-cycle, `?`, `/`, `@`) are already
|
|
27
|
-
* gated off by askQuestionPending.
|
|
28
|
-
* - Options: arrow-key list, label + dim description, CommandPalette-style
|
|
29
|
-
* scroll window; multiSelect ⇒ Space toggles ▣/□, Enter confirms the
|
|
30
|
-
* set; single ⇒ Enter picks. ALWAYS a final `✎ Other (type your own)`
|
|
31
|
-
* row → inline windowed TextInput; a typed answer flags `custom: true`.
|
|
32
|
-
* freeText questions render the input directly (no escape row, and the
|
|
33
|
-
* typed answer is the EXPECTED path — no custom flag, per the Task-1
|
|
34
|
-
* AskFormAnswer contract: custom marks the Other ESCAPE only).
|
|
35
|
-
* - Answering advances to the next UNANSWERED question (wrapping); when
|
|
36
|
-
* none remain, focus lands on [ Submit ]. Enter there resolves ALL
|
|
37
|
-
* answers at once, in question order. Tab back re-answers freely.
|
|
38
|
-
* Single-question forms (n === 1) auto-submit on answer — no extra
|
|
39
|
-
* Enter vs the v1 modal; the Submit affordance is multi-question
|
|
40
|
-
* review only (review round M2).
|
|
41
|
-
* - Esc — DELIBERATE DEVIATION from the brief's "Esc anywhere cancels",
|
|
42
|
-
* reviewer-accepted (review round I1): on an OPTIONED question with
|
|
43
|
-
* the Other input focused, Esc first returns focus to the options
|
|
44
|
-
* list (the typed draft is KEPT in otherDrafts); Esc at options
|
|
45
|
-
* level — and anywhere else, including freeText questions (no inner
|
|
46
|
-
* level) — cancels the WHOLE form → { formId, answers: [],
|
|
47
|
-
* cancelled: true } (v1 cancel contract shape per Task 1).
|
|
48
|
-
* - Dynamic-frame safety (Wave-1 lesson): every row truncates (NOTHING
|
|
49
|
-
* may wrap), options scroll, total height ≤ MAX_FORM_ROWS.
|
|
50
|
-
* - No React state keyed on formId (collision caveat from review) — all
|
|
51
|
-
* per-question state lives in arrays keyed by question INDEX within
|
|
52
|
-
* this request object.
|
|
53
|
-
*/
|
|
54
|
-
import { useState } from 'react';
|
|
55
|
-
import { Box, Text, useInput } from 'ink';
|
|
56
|
-
// Wave-1 windowed input — display can never wrap the row (D29 paste-safe).
|
|
57
|
-
import TextInput from '../text-input.js';
|
|
58
|
-
import { askHelpLine, useReclaimRawStdin } from './ask-question-modal.js';
|
|
59
|
-
const PEACH = '#FFCBA4';
|
|
60
|
-
/** Option rows visible at once — the rest scroll (CommandPalette style). */
|
|
61
|
-
export const VISIBLE_OPTION_ROWS = 5;
|
|
62
|
-
/**
|
|
63
|
-
* Hard cap on total rendered rows — pinned by test. Re-derived for the
|
|
64
|
-
* de-clutter pass (border gone = −2 rows; blank chip-gap row = +1; the
|
|
65
|
-
* Submit row only renders once all questions are answered).
|
|
66
|
-
*
|
|
67
|
-
* Worst case — all answered, user Tabbed BACK into a scrolled optioned
|
|
68
|
-
* question for review (Submit row visible below it):
|
|
69
|
-
* chips + blank + question + context + "↑ more" + VISIBLE_OPTION_ROWS
|
|
70
|
-
* + "↓ more" + Other + Submit + help = 14.
|
|
71
|
-
* Pre-completion the Submit row is absent → 13.
|
|
72
|
-
*/
|
|
73
|
-
export const MAX_FORM_ROWS = 14;
|
|
74
|
-
/** Display window for the Other/freeText input — the row must never wrap. */
|
|
75
|
-
const OTHER_INPUT_WIDTH = 50;
|
|
76
|
-
export function AskForm({ request, onResolve }) {
|
|
77
|
-
useReclaimRawStdin();
|
|
78
|
-
const questions = request.questions;
|
|
79
|
-
const n = questions.length;
|
|
80
|
-
// Focus slots: 0..n-1 = questions, n = the [ Submit ] row (reachable
|
|
81
|
-
// only once every question has an answer).
|
|
82
|
-
const [focus, setFocus] = useState(0);
|
|
83
|
-
// Per-question state, keyed by question INDEX (never formId): option
|
|
84
|
-
// cursor (0..options.length — the last row is Other), multi-select
|
|
85
|
-
// sets, Other draft text, and the committed answer.
|
|
86
|
-
const [cursors, setCursors] = useState(() => questions.map(() => 0));
|
|
87
|
-
const [multiSel, setMultiSel] = useState(() => questions.map(() => new Set()));
|
|
88
|
-
const [otherDrafts, setOtherDrafts] = useState(() => questions.map(() => ''));
|
|
89
|
-
const [answers, setAnswers] = useState(() => questions.map(() => null));
|
|
90
|
-
const isSubmit = focus === n;
|
|
91
|
-
const q = isSubmit ? undefined : questions[focus];
|
|
92
|
-
const otherRow = q ? q.options.length : 0;
|
|
93
|
-
const cursor = isSubmit ? 0 : cursors[focus];
|
|
94
|
-
// The inline TextInput owns typing whenever the question is freeText or
|
|
95
|
-
// the cursor sits on the Other row. While it does, the form-level
|
|
96
|
-
// handler must NOT interpret space/Enter/←/→ (Ink delivers every key to
|
|
97
|
-
// every active useInput — there is no stopPropagation).
|
|
98
|
-
const otherFocused = q != null && (q.freeText || cursor === otherRow);
|
|
99
|
-
const allAnswered = answers.every((a) => a !== null);
|
|
100
|
-
const cancel = () => onResolve({ formId: request.formId, answers: [], cancelled: true });
|
|
101
|
-
const submit = (finalAnswers) => onResolve({
|
|
102
|
-
formId: request.formId,
|
|
103
|
-
// Question order, always — regardless of the order the user
|
|
104
|
-
// actually answered in.
|
|
105
|
-
answers: questions.map((qq, i) => ({
|
|
106
|
-
id: qq.id,
|
|
107
|
-
answer: finalAnswers[i]?.answer ?? null,
|
|
108
|
-
...(finalAnswers[i]?.custom ? { custom: true } : {}),
|
|
109
|
-
})),
|
|
110
|
-
});
|
|
111
|
-
/**
|
|
112
|
-
* Commit an answer, then advance to the next UNANSWERED question
|
|
113
|
-
* (wrapping past the end); none left → focus [ Submit ] — except
|
|
114
|
-
* single-question forms, which resolve immediately (M2: no extra Enter
|
|
115
|
-
* vs the v1 modal).
|
|
116
|
-
*/
|
|
117
|
-
const answerAndAdvance = (i, value, custom) => {
|
|
118
|
-
const next = answers.map((a, j) => j === i ? { answer: value, ...(custom ? { custom: true } : {}) } : a);
|
|
119
|
-
for (let step = 1; step <= n; step++) {
|
|
120
|
-
const j = (i + step) % n;
|
|
121
|
-
if (next[j] === null) {
|
|
122
|
-
setAnswers(next);
|
|
123
|
-
setFocus(j);
|
|
124
|
-
return;
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
if (n === 1) {
|
|
128
|
-
submit(next);
|
|
129
|
-
return;
|
|
130
|
-
}
|
|
131
|
-
setAnswers(next);
|
|
132
|
-
setFocus(n);
|
|
133
|
-
};
|
|
134
|
-
useInput((input, key) => {
|
|
135
|
-
if (key.escape) {
|
|
136
|
-
// Two-level Esc (I1, documented deviation — see file header): the
|
|
137
|
-
// Other input focused on an OPTIONED question → back to the options
|
|
138
|
-
// list, draft kept. freeText has no inner level; the options level
|
|
139
|
-
// cancels the whole form.
|
|
140
|
-
if (q && !q.freeText && otherFocused) {
|
|
141
|
-
setCursors((prev) => prev.map((c, j) => (j === focus ? otherRow - 1 : c)));
|
|
142
|
-
return;
|
|
143
|
-
}
|
|
144
|
-
cancel();
|
|
145
|
-
return;
|
|
146
|
-
}
|
|
147
|
-
// Chip navigation. Tab/Shift+Tab always work (TextInput ignores tab);
|
|
148
|
-
// ←/→ only while the inline text input doesn't own horizontal cursor
|
|
149
|
-
// movement.
|
|
150
|
-
const maxSlot = allAnswered ? n : n - 1;
|
|
151
|
-
if ((key.tab && !key.shift) || (!otherFocused && key.rightArrow)) {
|
|
152
|
-
setFocus((f) => (f >= maxSlot ? 0 : f + 1));
|
|
153
|
-
return;
|
|
154
|
-
}
|
|
155
|
-
if ((key.tab && key.shift) || (!otherFocused && key.leftArrow)) {
|
|
156
|
-
setFocus((f) => (f <= 0 ? maxSlot : f - 1));
|
|
157
|
-
return;
|
|
158
|
-
}
|
|
159
|
-
if (isSubmit) {
|
|
160
|
-
if (key.return && allAnswered)
|
|
161
|
-
submit(answers);
|
|
162
|
-
return;
|
|
163
|
-
}
|
|
164
|
-
if (!q)
|
|
165
|
-
return;
|
|
166
|
-
// ↑/↓ move the option cursor — also FROM the Other row back into the
|
|
167
|
-
// list (TextInput ignores vertical arrows, so no conflict).
|
|
168
|
-
if (!q.freeText) {
|
|
169
|
-
if (key.upArrow) {
|
|
170
|
-
setCursors((prev) => prev.map((c, j) => (j === focus ? Math.max(0, c - 1) : c)));
|
|
171
|
-
return;
|
|
172
|
-
}
|
|
173
|
-
if (key.downArrow) {
|
|
174
|
-
setCursors((prev) => prev.map((c, j) => (j === focus ? Math.min(otherRow, c + 1) : c)));
|
|
175
|
-
return;
|
|
176
|
-
}
|
|
177
|
-
}
|
|
178
|
-
// Everything below is typing territory while the Other input has focus.
|
|
179
|
-
if (otherFocused)
|
|
180
|
-
return;
|
|
181
|
-
if (q.multiSelect && input === ' ') {
|
|
182
|
-
const opt = q.options[cursor];
|
|
183
|
-
if (opt) {
|
|
184
|
-
setMultiSel((prev) => prev.map((s, j) => {
|
|
185
|
-
if (j !== focus)
|
|
186
|
-
return s;
|
|
187
|
-
const nextSet = new Set(s);
|
|
188
|
-
if (nextSet.has(opt.value))
|
|
189
|
-
nextSet.delete(opt.value);
|
|
190
|
-
else
|
|
191
|
-
nextSet.add(opt.value);
|
|
192
|
-
return nextSet;
|
|
193
|
-
}));
|
|
194
|
-
}
|
|
195
|
-
return;
|
|
196
|
-
}
|
|
197
|
-
if (key.return) {
|
|
198
|
-
if (q.multiSelect) {
|
|
199
|
-
const values = q.options
|
|
200
|
-
.filter((o) => multiSel[focus].has(o.value))
|
|
201
|
-
.map((o) => o.value);
|
|
202
|
-
if (values.length === 0) {
|
|
203
|
-
// 2026-09-17 — nothing toggled: Enter used to do NOTHING, silently.
|
|
204
|
-
// Every other question answers on Enter, so a user who arrows to a
|
|
205
|
-
// row and presses Enter (Glenn's first session) sat there stuck.
|
|
206
|
-
// Enter alone now picks the highlighted option, single-choice style.
|
|
207
|
-
const opt = q.options[cursor];
|
|
208
|
-
if (opt)
|
|
209
|
-
answerAndAdvance(focus, opt.value);
|
|
210
|
-
return;
|
|
211
|
-
}
|
|
212
|
-
answerAndAdvance(focus, values.join(','));
|
|
213
|
-
}
|
|
214
|
-
else {
|
|
215
|
-
const opt = q.options[cursor];
|
|
216
|
-
if (opt)
|
|
217
|
-
answerAndAdvance(focus, opt.value);
|
|
218
|
-
}
|
|
219
|
-
}
|
|
220
|
-
}, { isActive: true });
|
|
221
|
-
// CommandPalette-style scroll window over the options (the Other row is
|
|
222
|
-
// rendered separately and is always visible).
|
|
223
|
-
const opts = q?.options ?? [];
|
|
224
|
-
const winStart = Math.max(0, Math.min(cursor - 3, opts.length - VISIBLE_OPTION_ROWS));
|
|
225
|
-
const visible = opts.slice(winStart, winStart + VISIBLE_OPTION_ROWS);
|
|
226
|
-
const below = Math.max(0, opts.length - (winStart + VISIBLE_OPTION_ROWS));
|
|
227
|
-
return (_jsxs(Box, { flexDirection: "column", paddingX: 1, flexShrink: 0, children: [_jsx(Box, { marginBottom: 1, children: _jsx(Text, { wrap: "truncate-end", children: questions.map((qq, i) => {
|
|
228
|
-
const cur = i === focus;
|
|
229
|
-
const done = answers[i] !== null;
|
|
230
|
-
return (_jsxs(Text, { color: cur ? PEACH : undefined, bold: cur, dimColor: !cur, children: [i > 0 ? ' ' : '', done ? '✓ ' : '', qq.header ?? qq.id] }, qq.id));
|
|
231
|
-
}) }) }), isSubmit ? (_jsx(Text, { dimColor: true, wrap: "truncate-end", children: "All questions answered \u2014 Tab/\u2190 to review, Enter to submit \u00B7 Esc cancel" })) : q ? (_jsxs(_Fragment, { children: [_jsxs(Text, { bold: true, wrap: "truncate-end", children: [q.question, q.multiSelect ? ' (choose one or more)' : ''] }), q.context && _jsx(Text, { dimColor: true, wrap: "truncate-end", children: q.context }), !q.freeText && (_jsxs(_Fragment, { children: [winStart > 0 && _jsxs(Text, { dimColor: true, wrap: "truncate-end", children: [" \u2191 ", winStart, " more"] }), visible.map((o, vi) => {
|
|
232
|
-
const idx = winStart + vi;
|
|
233
|
-
const isCur = !otherFocused && idx === cursor;
|
|
234
|
-
const mark = q.multiSelect
|
|
235
|
-
? (multiSel[focus].has(o.value) ? '▣ ' : '□ ')
|
|
236
|
-
: '';
|
|
237
|
-
// v1 never renders the `recommended` flag — the convention
|
|
238
|
-
// is skills writing "(recommended)" INTO the label. Render
|
|
239
|
-
// the same dim convention only when the label lacks it.
|
|
240
|
-
const rec = o.recommended === true && !/\brecommended\b/i.test(o.label);
|
|
241
|
-
return (_jsxs(Text, { wrap: "truncate-end", color: isCur ? PEACH : undefined, bold: isCur, children: [isCur ? '▸ ' : ' ', mark, o.label, rec && _jsx(Text, { dimColor: true, children: " (recommended)" }), o.description && _jsxs(Text, { dimColor: true, children: [" ", o.description] })] }, o.value));
|
|
242
|
-
}), below > 0 && _jsxs(Text, { dimColor: true, wrap: "truncate-end", children: [" \u2193 ", below, " more"] })] })), _jsxs(Box, { children: [q.freeText ? (_jsx(Text, { color: PEACH, wrap: "truncate-end", children: '> ' })) : (_jsxs(Text, { color: otherFocused ? PEACH : undefined, bold: otherFocused, wrap: "truncate-end", children: [otherFocused ? '▸ ' : ' ', "\u270E Other (type your own)", otherFocused ? ': ' : ''] })), otherFocused && (_jsx(TextInput, { value: otherDrafts[focus], onChange: (v) => setOtherDrafts((prev) => prev.map((d, j) => (j === focus ? v : d))), onSubmit: (v) => {
|
|
243
|
-
const trimmed = v.trim();
|
|
244
|
-
if (trimmed.length === 0)
|
|
245
|
-
return; // nothing typed — ignore
|
|
246
|
-
// custom marks the Other ESCAPE on an optioned question;
|
|
247
|
-
// freeText typing is the expected path (Task-1 contract).
|
|
248
|
-
answerAndAdvance(focus, trimmed, !q.freeText);
|
|
249
|
-
}, maxDisplayWidth: OTHER_INPUT_WIDTH }))] })] })) : null, allAnswered && (_jsxs(Text, { wrap: "truncate-end", color: isSubmit ? PEACH : undefined, bold: isSubmit, dimColor: !isSubmit, children: [isSubmit ? '▸ ' : ' ', "[ Submit ]"] })), q && (_jsx(Text, { dimColor: true, wrap: "truncate-end", children: askHelpLine(q.freeText ? 'freeText' : q.multiSelect ? 'multi' : 'pick', { single: n === 1 }) }))] }));
|
|
250
|
-
}
|
|
@@ -1,178 +0,0 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
/**
|
|
3
|
-
* AskQuestionModal — Ink-native interactive form for the ask_question tool.
|
|
4
|
-
*
|
|
5
|
-
* Critical #2 fix (2026-05-17): the prior path called inquirer.input/
|
|
6
|
-
* select/checkbox directly. In Ink that draws raw to stdout, corrupts
|
|
7
|
-
* the React frame, and the user often can't tell what's being asked.
|
|
8
|
-
*
|
|
9
|
-
* Three modes match the tool's three kinds:
|
|
10
|
-
* - text → free-form TextInput, Enter to submit, Esc to cancel
|
|
11
|
-
* - choice → arrow-key single-select list (mirrors CoachingPicker)
|
|
12
|
-
* - multi → space-to-toggle checkbox list, Enter to submit
|
|
13
|
-
*
|
|
14
|
-
* Mounted inline by App.tsx (NOT as position="absolute" overlay — we
|
|
15
|
-
* learned the hard way that absolute overlays paint over Body and
|
|
16
|
-
* can't be navigated cleanly).
|
|
17
|
-
*/
|
|
18
|
-
import { useState, useEffect } from 'react';
|
|
19
|
-
import { Box, Text, useInput } from 'ink';
|
|
20
|
-
// D29 paste fix (2026-06-12): paste-safe replacement for ink-text-input —
|
|
21
|
-
// pasted free-form answers were losing every chunk but the last on large
|
|
22
|
-
// pastes (stale-prop closure upstream). See ../text-input.tsx.
|
|
23
|
-
import TextInput from '../text-input.js';
|
|
24
|
-
const PEACH = '#FFCBA4';
|
|
25
|
-
export function askHelpLine(kind, opts) {
|
|
26
|
-
switch (kind) {
|
|
27
|
-
// 2026-09-17 — spelled out after a first-session user could not tell a
|
|
28
|
-
// multi-select from a single pick ("was it a multi select?").
|
|
29
|
-
case 'multi': return 'Space ticks a row · Enter confirms · Enter alone picks the highlighted row · Esc cancels';
|
|
30
|
-
case 'pick': return opts?.single ? 'Enter pick · Esc cancel' : 'Enter pick · Tab next · Esc cancel';
|
|
31
|
-
default: return opts?.single ? 'Enter submit · Esc cancel' : 'Tab next · Esc cancel';
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
/**
|
|
35
|
-
* Reclaim raw-mode stdin when an Ink modal mounts (2026-06-08).
|
|
36
|
-
*
|
|
37
|
-
* The approval plan-gate uses `@inquirer/prompts`, which toggles the
|
|
38
|
-
* terminal's raw mode (and pauses stdin) out from under Ink while it runs.
|
|
39
|
-
* When it releases, Ink's INTERNAL bookkeeping still thinks raw mode is on,
|
|
40
|
-
* so it does not re-assert it — and a modal mounted afterwards (this one)
|
|
41
|
-
* receives no keypresses: arrow keys and Enter are dead, the user cannot
|
|
42
|
-
* answer the question. Re-syncing the OS state to what Ink expects on mount
|
|
43
|
-
* fixes it. Direct `process.stdin` calls (not Ink's ref-counted setRawMode)
|
|
44
|
-
* because the whole problem is that Ink's count is stale.
|
|
45
|
-
*
|
|
46
|
-
* Exported (UX Wave 2 / Task 2) so <AskForm> — the v2 multi-question form —
|
|
47
|
-
* reuses the exact same reclaim on mount. v1 rendering is untouched.
|
|
48
|
-
*/
|
|
49
|
-
export function useReclaimRawStdin() {
|
|
50
|
-
useEffect(() => {
|
|
51
|
-
const stdin = process.stdin;
|
|
52
|
-
if (stdin.isTTY && typeof stdin.setRawMode === 'function') {
|
|
53
|
-
try {
|
|
54
|
-
stdin.setRawMode(true);
|
|
55
|
-
}
|
|
56
|
-
catch { /* not a TTY — ignore */ }
|
|
57
|
-
stdin.resume();
|
|
58
|
-
}
|
|
59
|
-
}, []);
|
|
60
|
-
}
|
|
61
|
-
export function AskQuestionModal({ params, onResolve }) {
|
|
62
|
-
useReclaimRawStdin();
|
|
63
|
-
if (params.kind === 'text' || params.choices.length === 0) {
|
|
64
|
-
return _jsx(TextInputForm, { params: params, onResolve: onResolve });
|
|
65
|
-
}
|
|
66
|
-
if (params.kind === 'choice') {
|
|
67
|
-
return _jsx(ChoiceForm, { params: params, onResolve: onResolve });
|
|
68
|
-
}
|
|
69
|
-
return _jsx(MultiForm, { params: params, onResolve: onResolve });
|
|
70
|
-
}
|
|
71
|
-
// ---------------------------------------------------------------------------
|
|
72
|
-
// Free-form text
|
|
73
|
-
// ---------------------------------------------------------------------------
|
|
74
|
-
function TextInputForm({ params, onResolve }) {
|
|
75
|
-
const [value, setValue] = useState('');
|
|
76
|
-
useInput((_input, key) => {
|
|
77
|
-
if (key.escape) {
|
|
78
|
-
onResolve({ answer: null, cancelled: true });
|
|
79
|
-
}
|
|
80
|
-
}, { isActive: true });
|
|
81
|
-
return (_jsxs(Box, { flexDirection: "column", paddingX: 1, flexShrink: 0, children: [_jsx(Text, { bold: true, children: params.question }), params.context && (_jsx(Box, { marginTop: 1, children: _jsx(Text, { dimColor: true, children: params.context }) })), _jsxs(Box, { marginTop: 1, children: [_jsx(Text, { color: PEACH, children: '> ' }), _jsx(TextInput, { value: value, onChange: setValue, onSubmit: (v) => {
|
|
82
|
-
const trimmed = v.trim();
|
|
83
|
-
if (trimmed.length === 0) {
|
|
84
|
-
onResolve({ answer: null, cancelled: true });
|
|
85
|
-
}
|
|
86
|
-
else {
|
|
87
|
-
onResolve({ answer: trimmed });
|
|
88
|
-
}
|
|
89
|
-
} })] }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { dimColor: true, children: askHelpLine('freeText', { single: true }) }) })] }));
|
|
90
|
-
}
|
|
91
|
-
// ---------------------------------------------------------------------------
|
|
92
|
-
// Single choice (kind = 'choice')
|
|
93
|
-
// ---------------------------------------------------------------------------
|
|
94
|
-
function ChoiceForm({ params, onResolve }) {
|
|
95
|
-
const [cursor, setCursor] = useState(0);
|
|
96
|
-
useInput((_input, key) => {
|
|
97
|
-
if (key.escape) {
|
|
98
|
-
onResolve({ answer: null, cancelled: true });
|
|
99
|
-
return;
|
|
100
|
-
}
|
|
101
|
-
if (key.upArrow) {
|
|
102
|
-
setCursor((c) => Math.max(0, c - 1));
|
|
103
|
-
return;
|
|
104
|
-
}
|
|
105
|
-
if (key.downArrow) {
|
|
106
|
-
setCursor((c) => Math.min(params.choices.length - 1, c + 1));
|
|
107
|
-
return;
|
|
108
|
-
}
|
|
109
|
-
if (key.return) {
|
|
110
|
-
const chosen = params.choices[cursor];
|
|
111
|
-
if (chosen)
|
|
112
|
-
onResolve({ answer: chosen.value });
|
|
113
|
-
}
|
|
114
|
-
}, { isActive: true });
|
|
115
|
-
return (_jsxs(Box, { flexDirection: "column", paddingX: 1, flexShrink: 0, children: [_jsx(Text, { bold: true, children: params.question }), params.context && (_jsx(Box, { marginTop: 1, children: _jsx(Text, { dimColor: true, children: params.context }) })), _jsx(Box, { marginTop: 1, flexDirection: "column", children: params.choices.map((c, i) => {
|
|
116
|
-
const isCursor = i === cursor;
|
|
117
|
-
return (_jsxs(Text, { color: isCursor ? PEACH : undefined, bold: isCursor, children: [isCursor ? '▸ ' : ' ', c.label] }, c.value));
|
|
118
|
-
}) }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { dimColor: true, children: askHelpLine('pick', { single: true }) }) })] }));
|
|
119
|
-
}
|
|
120
|
-
// ---------------------------------------------------------------------------
|
|
121
|
-
// Multi choice (kind = 'multi') — space to toggle, Enter to submit
|
|
122
|
-
// ---------------------------------------------------------------------------
|
|
123
|
-
function MultiForm({ params, onResolve }) {
|
|
124
|
-
const [cursor, setCursor] = useState(0);
|
|
125
|
-
const [selected, setSelected] = useState(new Set());
|
|
126
|
-
useInput((input, key) => {
|
|
127
|
-
if (key.escape) {
|
|
128
|
-
onResolve({ answer: null, cancelled: true });
|
|
129
|
-
return;
|
|
130
|
-
}
|
|
131
|
-
if (key.upArrow) {
|
|
132
|
-
setCursor((c) => Math.max(0, c - 1));
|
|
133
|
-
return;
|
|
134
|
-
}
|
|
135
|
-
if (key.downArrow) {
|
|
136
|
-
setCursor((c) => Math.min(params.choices.length - 1, c + 1));
|
|
137
|
-
return;
|
|
138
|
-
}
|
|
139
|
-
if (input === ' ') {
|
|
140
|
-
const current = params.choices[cursor];
|
|
141
|
-
if (current) {
|
|
142
|
-
setSelected((prev) => {
|
|
143
|
-
const next = new Set(prev);
|
|
144
|
-
if (next.has(current.value))
|
|
145
|
-
next.delete(current.value);
|
|
146
|
-
else
|
|
147
|
-
next.add(current.value);
|
|
148
|
-
return next;
|
|
149
|
-
});
|
|
150
|
-
}
|
|
151
|
-
return;
|
|
152
|
-
}
|
|
153
|
-
if (key.return) {
|
|
154
|
-
const values = params.choices
|
|
155
|
-
.filter((c) => selected.has(c.value))
|
|
156
|
-
.map((c) => c.value);
|
|
157
|
-
if (values.length === 0) {
|
|
158
|
-
// 2026-09-17 — nothing toggled: Enter alone picks the highlighted
|
|
159
|
-
// option (single-choice muscle memory) instead of cancelling.
|
|
160
|
-
const current = params.choices[cursor];
|
|
161
|
-
if (current)
|
|
162
|
-
onResolve({ answer: current.value });
|
|
163
|
-
else
|
|
164
|
-
onResolve({ answer: null, cancelled: true });
|
|
165
|
-
}
|
|
166
|
-
else {
|
|
167
|
-
onResolve({ answer: values.join(',') });
|
|
168
|
-
}
|
|
169
|
-
}
|
|
170
|
-
}, { isActive: true });
|
|
171
|
-
return (_jsxs(Box, { flexDirection: "column", paddingX: 1, flexShrink: 0, children: [_jsxs(Text, { bold: true, children: [params.question, " (choose one or more)"] }), params.context && (_jsx(Box, { marginTop: 1, children: _jsx(Text, { dimColor: true, children: params.context }) })), _jsx(Box, { marginTop: 1, flexDirection: "column", children: params.choices.map((c, i) => {
|
|
172
|
-
const isCursor = i === cursor;
|
|
173
|
-
const isSelected = selected.has(c.value);
|
|
174
|
-
const mark = isSelected ? '◉' : '○';
|
|
175
|
-
const prefix = isCursor ? '▸ ' : ' ';
|
|
176
|
-
return (_jsxs(Text, { color: isCursor ? PEACH : undefined, bold: isCursor, children: [prefix, mark, " ", c.label] }, c.value));
|
|
177
|
-
}) }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { dimColor: true, children: askHelpLine('multi') }) })] }));
|
|
178
|
-
}
|
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "zlas.inspectionlog",
|
|
3
|
-
"version": "0.0.1",
|
|
4
|
-
"description": "Description of zlas.inspectionlog.",
|
|
5
|
-
"keywords": [
|
|
6
|
-
"ui5",
|
|
7
|
-
"openui5",
|
|
8
|
-
"sapui5"
|
|
9
|
-
],
|
|
10
|
-
"main": "webapp/index.html",
|
|
11
|
-
"dependencies": {},
|
|
12
|
-
"devDependencies": {
|
|
13
|
-
"@ui5/cli": "^4.0.33",
|
|
14
|
-
"@sap/ux-ui5-tooling": "1"
|
|
15
|
-
},
|
|
16
|
-
"scripts": {
|
|
17
|
-
"start": "fiori run --open \"/test/flpSandbox.html?sap-ui-xx-viewCache=false#zlasinspectionlog-tile\"",
|
|
18
|
-
"start-local": "fiori run --config ./ui5-local.yaml --open \"/test/flpSandbox.html?sap-ui-xx-viewCache=false#zlasinspectionlog-tile\"",
|
|
19
|
-
"build": "ui5 build --config=ui5.yaml --clean-dest --dest dist",
|
|
20
|
-
"deploy": "fiori verify",
|
|
21
|
-
"deploy-config": "fiori add deploy-config",
|
|
22
|
-
"start-noflp": "fiori run --open \"/index.html?sap-ui-xx-viewCache=false\"",
|
|
23
|
-
"start-variants-management": "fiori run --open \"/preview.html?sap-ui-xx-viewCache=false&fiori-tools-rta-mode=true&sap-ui-rta-skip-flex-validation=true#zlasinspectionlog-tile\""
|
|
24
|
-
},
|
|
25
|
-
"sapux": true
|
|
26
|
-
}
|
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
# yaml-language-server: $schema=https://sap.github.io/ui5-tooling/schema/ui5.yaml.json
|
|
2
|
-
|
|
3
|
-
specVersion: "4.0"
|
|
4
|
-
metadata:
|
|
5
|
-
name: zlas.inspectionlog
|
|
6
|
-
type: application
|
|
7
|
-
framework:
|
|
8
|
-
name: SAPUI5
|
|
9
|
-
version: 1.120.0
|
|
10
|
-
libraries:
|
|
11
|
-
- name: sap.m
|
|
12
|
-
- name: sap.ui.core
|
|
13
|
-
- name: sap.fe.templates
|
|
14
|
-
- name: sap.ushell
|
|
15
|
-
- name: themelib_sap_fiori_3
|
|
16
|
-
server:
|
|
17
|
-
customMiddleware:
|
|
18
|
-
- name: fiori-tools-appreload
|
|
19
|
-
afterMiddleware: compression
|
|
20
|
-
configuration:
|
|
21
|
-
port: 35729
|
|
22
|
-
path: webapp
|
|
23
|
-
delay: 300
|
|
24
|
-
- name: fiori-tools-preview
|
|
25
|
-
afterMiddleware: fiori-tools-appreload
|
|
26
|
-
configuration:
|
|
27
|
-
flp:
|
|
28
|
-
theme: sap_fiori_3
|
|
29
|
-
path: test/flpSandbox.html
|
|
30
|
-
intent:
|
|
31
|
-
object: zlasinspectionlog
|
|
32
|
-
action: tile
|
|
33
|
-
- name: fiori-tools-proxy
|
|
34
|
-
afterMiddleware: compression
|
|
35
|
-
configuration:
|
|
36
|
-
ignoreCertErrors: false # If set to true, certificate errors will be ignored. E.g. self-signed certificates will be accepted
|
|
37
|
-
backend:
|
|
38
|
-
- path: /sap
|
|
39
|
-
url: https://172.16.24.98:44300
|
|
40
|
-
client: '100'
|
|
41
|
-
builder:
|
|
42
|
-
resources:
|
|
43
|
-
excludes:
|
|
44
|
-
- /test/**
|
|
45
|
-
- /localService/**
|
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
# yaml-language-server: $schema=https://sap.github.io/ui5-tooling/schema/ui5.yaml.json
|
|
2
|
-
|
|
3
|
-
specVersion: "4.0"
|
|
4
|
-
metadata:
|
|
5
|
-
name: zlas.inspectionlog
|
|
6
|
-
type: application
|
|
7
|
-
server:
|
|
8
|
-
customMiddleware:
|
|
9
|
-
- name: fiori-tools-proxy
|
|
10
|
-
afterMiddleware: compression
|
|
11
|
-
configuration:
|
|
12
|
-
ignoreCertErrors: true # Dev-only: accept the backend's self-signed certificate
|
|
13
|
-
ui5:
|
|
14
|
-
path:
|
|
15
|
-
- /resources
|
|
16
|
-
- /test-resources
|
|
17
|
-
url: https://ui5.sap.com
|
|
18
|
-
backend:
|
|
19
|
-
- path: /sap
|
|
20
|
-
url: https://172.16.24.98:44300
|
|
21
|
-
client: '100'
|
|
22
|
-
- name: fiori-tools-appreload
|
|
23
|
-
afterMiddleware: compression
|
|
24
|
-
configuration:
|
|
25
|
-
port: 35729
|
|
26
|
-
path: webapp
|
|
27
|
-
delay: 300
|
|
28
|
-
- name: fiori-tools-preview
|
|
29
|
-
afterMiddleware: fiori-tools-appreload
|
|
30
|
-
configuration:
|
|
31
|
-
flp:
|
|
32
|
-
theme: sap_fiori_3
|
|
33
|
-
path: test/flpSandbox.html
|
|
34
|
-
intent:
|
|
35
|
-
object: zlasinspectionlog
|
|
36
|
-
action: tile
|
|
37
|
-
builder:
|
|
38
|
-
resources:
|
|
39
|
-
excludes:
|
|
40
|
-
- /test/**
|
|
41
|
-
- /localService/**
|
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
<!DOCTYPE html>
|
|
2
|
-
<html lang="en">
|
|
3
|
-
<head>
|
|
4
|
-
<meta charset="UTF-8">
|
|
5
|
-
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
|
-
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
|
7
|
-
<title>Equipment Inspection Logs</title>
|
|
8
|
-
<style>
|
|
9
|
-
html, body, body > div, #container, #container-uiarea {
|
|
10
|
-
height: 100%;
|
|
11
|
-
}
|
|
12
|
-
</style>
|
|
13
|
-
<script
|
|
14
|
-
id="sap-ui-bootstrap"
|
|
15
|
-
src="resources/sap-ui-core.js"
|
|
16
|
-
data-sap-ui-theme="sap_fiori_3"
|
|
17
|
-
data-sap-ui-resource-roots='{
|
|
18
|
-
"zlas.inspectionlog": "./"
|
|
19
|
-
}'
|
|
20
|
-
data-sap-ui-on-init="module:sap/ui/core/ComponentSupport"
|
|
21
|
-
data-sap-ui-compat-version="edge"
|
|
22
|
-
data-sap-ui-async="true"
|
|
23
|
-
data-sap-ui-frame-options="trusted"
|
|
24
|
-
></script>
|
|
25
|
-
</head>
|
|
26
|
-
<body class="sapUiBody sapUiSizeCompact" id="content">
|
|
27
|
-
<div
|
|
28
|
-
data-sap-ui-component
|
|
29
|
-
data-name="zlas.inspectionlog"
|
|
30
|
-
data-id="container"
|
|
31
|
-
data-settings='{"id" : "zlas.inspectionlog"}'
|
|
32
|
-
data-handle-validation="true"
|
|
33
|
-
></div>
|
|
34
|
-
</body>
|
|
35
|
-
</html>
|