();
if (presented !== question) {
setOutgoing(current.current);
setPresented(question);
if (immediately) setActive(question);
}
current.current = { children, question };
useEffect(() => {
if (active !== question) setActive(question);
}, [active, question]);
return (
{outgoing && (
setOutgoing(step => step?.question === outgoing.question ? undefined : step)}
>
{outgoing.children}
)}
{children}
);
}
function Undecided(
{ canEdit, connected, meta, motion, value, wire, ...pointing }:
& {
canEdit: boolean;
connected: boolean;
meta?: Question.CardMeta;
motion?: QuestionStepMotion;
value: Questionnaire;
wire?: Transport;
}
& Pointing,
) {
let state = useQuestionnaire({
id: value.id,
bridge: wire,
connected,
definition: definition(value),
});
let answerable = connected && !!state.definition;
let editable = canEdit && answerable;
let previous = previousAnswers(value);
return (
(
{children}
)
: undefined}
status="open"
submitting={state.submitting}
{...pointing}
/>
);
}
function previousAnswers(value: Questionnaire) {
let previous: Record = {};
for (let question of value.questions) {
if (!question.previous) continue;
let labels = question.previous.choices.flatMap(id => {
let option = question.options.find(candidate => candidate.id === id);
return option ? [option.label] : [];
});
if (labels.length === 0 && question.previous.value) labels = [question.previous.value];
previous[question.id] = { labels, by: question.previous.by };
}
return Object.keys(previous).length === 0 ? undefined : previous;
}
function Decided(
{ canEdit, connected, discarded, meta, resolved, value, wire, ...pointing }: {
canEdit: boolean;
connected: boolean;
discarded: boolean;
meta?: Question.CardMeta;
resolved: Answer[] | undefined;
value: Questionnaire;
wire?: Transport;
} & Pointing,
) {
let [error, setError] = useState();
let [submitting, setSubmitting] = useState(false);
let pending = useRef(false);
let editable = canEdit && connected && !!wire && !discarded;
let request = (kind: "question:reopen" | "question:discard") => {
if (!wire || pending.current) return;
pending.current = true;
setSubmitting(true);
setError(undefined);
void wire.ask(kind, { id: value.id })
.then((reply: unknown) => {
if ((reply as { ok?: boolean }).ok) return;
setError(
kind === "question:reopen"
? "Could not reopen it. Try again."
: "Could not discard this decision.",
);
})
.catch(() =>
setError(
kind === "question:reopen"
? "Could not reopen it. Try again."
: "Could not discard this decision.",
)
)
.finally(() => {
pending.current = false;
setSubmitting(false);
});
};
return (
}
>
request("question:discard") : undefined}
onReopen={editable ? () => request("question:reopen") : undefined}
resolver={discarded ? meta?.resolver : undefined}
status={discarded ? "cancelled" : "answered"}
submitting={submitting}
{...pointing}
/>
);
}
function InlineQuestionnaire({ value }: { value: Questionnaire }) {
let options = useCellValue(widgets$);
let meta = useCardMeta(options.cardMeta, value.id);
return (
options.questions?.highlight(value.id, question)}
onQuestionLeave={() => options.questions?.clear()}
onQuestionSelect={question => options.questions?.reveal(value.id, question)}
places={options.questions?.counts(value.id)}
value={value}
wire={options.wire}
/>
);
}
export function renderQuestionnaire(node: QuestionnaireNode) {
// React renders decorators after Lexical's read transaction has ended.
return ;
}