ftml_viewer_components/components/
mod.rs1pub mod counters;
2pub mod documents;
3pub(crate) mod inputref;
4pub(crate) mod navigation;
5#[cfg(feature = "omdoc")]
6pub mod omdoc;
7pub(crate) mod paragraphs;
8pub mod problem;
9pub(crate) mod proofs;
10pub(crate) mod sections;
11pub(crate) mod terms;
12mod toc;
13
14use flams_ontology::{
15 narration::{
16 paragraphs::{ParagraphFormatting, ParagraphKind},
17 problems::CognitiveDimension,
18 LOKind,
19 },
20 uris::DocumentElementURI,
21};
22use inputref::InInputRef;
23pub use inputref::{IfInputref, InputRef};
24pub use toc::*;
25
26use ftml_extraction::{open::OpenFTMLElement, prelude::FTMLElements};
27use leptos::prelude::*;
28use leptos_posthoc::{DomChildrenCont, DomCont, OriginalNode};
29
30use counters::{LogicalLevel, SectionCounters};
31
32use crate::AllowHovers;
33
34#[component]
35pub fn FTMLComponents(
36 #[prop(optional)] in_math: bool,
37 elements: FTMLElements,
38 orig: OriginalNode,
39) -> impl IntoView {
40 if in_math {
41 leptos::either::Either::Left(do_components::<true>(0, elements, orig))
42 } else {
43 leptos::either::Either::Right(do_components::<false>(0, elements, orig))
44 }
45}
46
47fn do_components<const MATH: bool>(
48 skip: usize,
49 elements: FTMLElements,
50 orig: OriginalNode,
51) -> impl IntoView {
52 if let Some(next) = elements.iter().rev().nth(skip) {
53 match next {
55 OpenFTMLElement::Section { uri, .. } => sections::section(uri.clone(), move || {
56 do_components::<MATH>(skip + 1, elements, orig)
57 })
58 .into_any(),
59 OpenFTMLElement::SkipSection => {
60 sections::skip(move || do_components::<MATH>(skip + 1, elements, orig)).into_any()
61 }
62 OpenFTMLElement::Inputref { uri, id } => inputref::inputref(uri.clone(), id).into_any(),
63 OpenFTMLElement::IfInputref(b) => inputref::if_inputref(*b, orig).into_any(),
64 OpenFTMLElement::OpenTerm { term, .. } => {
65 #[cfg(feature = "omdoc")]
66 if MATH {
67 let term = term.clone();
68 terms::math_term(skip, elements, orig, term).into_any()
69 } else {
70 terms::do_term::<_, MATH>(term.clone(), move || {
71 do_components::<MATH>(skip + 1, elements, orig)
72 })
73 .into_any()
74 }
75
76 #[cfg(not(feature = "omdoc"))]
77 terms::do_term::<_, MATH>(term.clone(), move || {
78 do_components::<MATH>(skip + 1, elements, orig)
79 })
80 .into_any()
81 }
82 OpenFTMLElement::DefComp => terms::do_comp::<_, MATH>(
83 true,
84 move || view!(<DomCont skip_head=true orig=orig.clone() cont=crate::iterate/>),
85 )
86 .into_any(),
87 OpenFTMLElement::Comp | OpenFTMLElement::MainComp if AllowHovers::get() => {
88 terms::do_comp::<_, MATH>(
89 false,
90 move || view!(<DomCont skip_head=true orig=orig.clone() cont=crate::iterate/>),
91 )
92 .into_any()
93 }
94 OpenFTMLElement::Comp | OpenFTMLElement::MainComp => {
95 view!(<DomCont skip_head=true orig=orig.clone() cont=crate::iterate/>).into_any()
96 }
97 OpenFTMLElement::Definiendum(_) => terms::do_definiendum::<_, MATH>(move || {
98 do_components::<MATH>(skip + 1, elements, orig)
99 })
100 .into_any(),
101 OpenFTMLElement::Arg(arg) => terms::do_arg(orig, *arg, move |orig| {
102 do_components::<MATH>(skip + 1, elements, orig)
103 })
104 .into_any(),
105 OpenFTMLElement::Problem {
106 uri,
107 autogradable,
108 sub_problem,
109 styles,
110 ..
111 } => {
112 let styles = styles.clone();
113 problem::problem(
114 &uri.clone(),
115 *autogradable,
116 *sub_problem,
117 styles,
118 move || do_components::<MATH>(skip + 1, elements, orig),
119 )
120 .into_any()
121 }
122 OpenFTMLElement::ProblemHint => {
123 problem::hint(move || do_components::<MATH>(skip + 1, elements, orig)).into_any()
124 }
125 OpenFTMLElement::ProblemSolution(id) => {
126 let id = id.clone();
127 problem::solution(skip + 1, elements, orig, id).into_any()
128 }
129 OpenFTMLElement::ProblemGradingNote => {
130 problem::gnote(skip + 1, elements, orig).into_any()
131 }
132 OpenFTMLElement::ChoiceBlock { multiple, inline } => {
133 problem::choice_block(*multiple, *inline, move || {
134 do_components::<MATH>(skip + 1, elements, orig)
135 })
136 .into_any()
137 }
138 OpenFTMLElement::ProblemChoice => problem::problem_choice(move || {
139 do_components::<MATH>(skip + 1, elements.clone(), orig.clone())
140 })
141 .into_any(),
142 OpenFTMLElement::Fillinsol(wd) => problem::fillinsol(*wd).into_any(),
143 OpenFTMLElement::SetSectionLevel(level) => {
144 let in_inputref = use_context::<InInputRef>().map(|i| i.0).unwrap_or(false);
145 update_context::<SectionCounters, _>(|current| {
146 if !in_inputref && matches!(current.current_level(), LogicalLevel::None) {
147 current.max = *level;
148 } else if !in_inputref {
149 tracing::error!("ftml:set-section-level: Section already started");
150 }
151 });
152 ().into_any()
153 }
154 OpenFTMLElement::Slide(uri) => paragraphs::slide(uri.clone(), move || {
155 do_components::<MATH>(skip + 1, elements, orig)
156 })
157 .into_any(),
158 OpenFTMLElement::SlideNumber => paragraphs::slide_number().into_any(),
159 OpenFTMLElement::Paragraph {
160 uri,
161 kind: ParagraphKind::Proof,
162 formatting:
163 formatting @ (ParagraphFormatting::Block | ParagraphFormatting::Collapsed),
164 ..
165 } => proofs::proof(
166 uri.clone(),
167 *formatting == ParagraphFormatting::Collapsed,
168 move || view!(<DomChildrenCont orig cont=crate::iterate />),
169 )
170 .into_any(),
171 OpenFTMLElement::Paragraph {
172 kind: ParagraphKind::SubProof,
173 uri,
174 formatting:
175 formatting @ (ParagraphFormatting::Block | ParagraphFormatting::Collapsed),
176 ..
177 } => proofs::subproof(
178 uri.clone(),
179 *formatting == ParagraphFormatting::Collapsed,
180 move || view!(<DomChildrenCont orig cont=crate::iterate />),
181 )
182 .into_any(),
183 OpenFTMLElement::Paragraph {
184 kind,
185 formatting: ParagraphFormatting::Block,
186 uri,
187 styles,
188 ..
189 } => paragraphs::paragraph(*kind, uri.clone(), styles.clone(), move || {
190 do_components::<MATH>(skip + 1, elements, orig)
191 })
192 .into_any(),
193 OpenFTMLElement::Paragraph { .. } => {
194 do_components::<MATH>(skip + 1, elements, orig).into_any()
195 }
196 OpenFTMLElement::Title => {
197 sections::title(move || view!(<DomChildrenCont orig cont=crate::iterate />))
198 .into_any()
199 }
200 OpenFTMLElement::ProofTitle => proofs::proof_title(orig).into_any(),
201 OpenFTMLElement::SubproofTitle => proofs::subproof_title(orig).into_any(),
202 OpenFTMLElement::ProofBody => proofs::proof_body(orig).into_any(),
212 _ => todo!(),
213 }
214 } else {
215 view!(<DomCont skip_head=true orig cont=crate::iterate/>).into_any()
216 }
217}
218
219#[derive(Clone, Debug, PartialEq, Eq)]
220pub struct LOs {
221 pub definitions: Vec<DocumentElementURI>,
222 pub examples: Vec<DocumentElementURI>,
223 pub problems: Vec<(bool, DocumentElementURI, CognitiveDimension)>,
224}
225
226pub(crate) trait IntoLOs {
227 fn lo_sort(self) -> LOs;
228}
229
230impl IntoLOs for Vec<(DocumentElementURI, LOKind)> {
231 fn lo_sort(self) -> LOs {
232 let mut definitions = Vec::new();
233 let mut examples = Vec::new();
234 let mut problems = Vec::new();
235 for (uri, k) in self {
236 match k {
237 LOKind::Definition => definitions.push(uri),
238 LOKind::Example => examples.push(uri),
239 LOKind::Problem(cd) => problems.push((false, uri, cd)),
240 LOKind::SubProblem(cd) => problems.push((true, uri, cd)),
241 }
242 }
243 LOs {
244 definitions,
245 examples,
246 problems,
247 }
248 }
249}