ftml_viewer_components/components/
documents.rs1use super::Gotto;
2use super::TOCSource;
3use crate::iterate;
4use crate::FTMLDocumentSetup;
5use flams_ontology::uris::NarrativeURI;
6use flams_ontology::uris::{DocumentElementURI, DocumentURI, NameStep};
7use flams_web_utils::components::wait_local;
8use flams_web_utils::{do_css, inject_css};
9use leptos::prelude::*;
10use leptos_posthoc::DomStringCont;
11
12#[cfg(feature = "omdoc")]
13#[component]
14pub fn DocumentFromURI(
15 uri: DocumentURI,
16 #[prop(optional, into)] toc: TOCSource,
17 #[prop(optional, into)] gottos: Vec<Gotto>,
18 #[prop(optional)] omdoc: crate::components::omdoc::OMDocSource,
19) -> impl IntoView {
20 wait_local(
21 move || {
22 tracing::info!("fetching {uri}");
23 let fut = crate::remote::server_config.full_doc(uri.clone());
24 async move { fut.await.ok() }
25 },
26 move |(uri, css, html)| {
27 for c in css {
28 do_css(c);
29 }
30 view!(<DocumentString html uri toc=toc.clone() gottos=gottos.clone() omdoc=omdoc.clone()/>)
31 },
32 "Error loading document reference".to_string(),
33 )
34}
35
36#[component]
37pub fn FragmentFromURI(uri: DocumentElementURI) -> impl IntoView {
38 let uricl = uri.clone();
39 wait_local(
40 move || {
41 tracing::info!("fetching {uri}");
42 let fut = crate::remote::server_config.paragraph(uri.clone());
43 async move { fut.await.ok() }
44 },
45 move |(_, css, html)| {
46 for c in css {
47 do_css(c);
48 }
49 view!(<FragmentString html uri=uricl.clone()/>)
50 },
51 "Error loading document fragment".to_string(),
52 )
53}
54
55#[cfg(not(feature = "omdoc"))]
56#[component]
57pub fn DocumentFromURI(
58 uri: DocumentURI,
59 #[prop(optional, into)] toc: TOCSource,
60 #[prop(optional, into)] gottos: Vec<Gotto>,
61) -> impl IntoView {
62 wait_local(
63 move || {
64 tracing::info!("fetching {uri}");
65 let fut = crate::remote::server_config.full_doc(uri.clone());
66 async move { fut.await.ok() }
67 },
68 move |(uri, css, html)| {
69 for c in css {
70 do_css(c);
71 }
72 view!(<DocumentString html uri gottos=gottos.clone() toc=toc.clone()/>)
73 },
74 "Error loading document reference".to_string(),
75 )
76}
77
78#[component]
79pub fn FragmentString(
80 html: String,
81 #[prop(optional)] uri: Option<DocumentElementURI>,
82) -> impl IntoView {
83 use leptos::context::Provider;
84 use leptos::either::EitherOf3;
85 let name = uri.as_ref().map(|uri| uri.name().last_name().clone());
86 let needs_suffix = uri
87 .as_ref()
88 .map(|uri| uri.name().steps().len() > 1)
89 .unwrap_or_default();
90 let doc = uri
91 .as_ref()
92 .map_or_else(DocumentURI::no_doc, |d| d.document().clone());
93 view! {<FTMLDocumentSetup uri=doc>{
94 match name {
95 Some(name) if needs_suffix => {
96 let nuri = NarrativeURI::Element(flams_utils::unwrap!(uri).parent());
97 EitherOf3::A(view!{
98 <Provider value=ForcedName(Some(name))>
99 <Provider value=nuri>
100 <DomStringCont html cont=iterate/>
101 </Provider>
102 </Provider>
103 })
104 },
105 Some(name) => EitherOf3::B(view!{
106 <Provider value=ForcedName(Some(name))>
107 <DomStringCont html cont=iterate/>
108 </Provider>
109 }),
110 _ => EitherOf3::C(view!{
111 <DomStringCont html cont=iterate/>
112 })
113 }
114 }</FTMLDocumentSetup>}
115}
116
117#[derive(Clone, Debug, Default)]
118pub struct ForcedName(Option<NameStep>);
119impl ForcedName {
120 pub fn update(&self, uri: &DocumentElementURI) -> DocumentElementURI {
121 match self.0.as_ref() {
122 Some(n) => {
123 let name = uri.name().clone();
124 let doc = uri.document().clone();
125 doc & name.with_last_name(n.clone())
126 }
127 _ => uri.clone(),
128 }
129 }
130}
131
132#[cfg(feature = "omdoc")]
133#[component]
134pub fn DocumentString(
135 html: String,
136 #[prop(optional)] uri: Option<DocumentURI>,
137 #[prop(optional, into)] toc: TOCSource,
138 #[prop(optional, into)] gottos: Vec<Gotto>,
139 #[prop(optional)] omdoc: crate::components::omdoc::OMDocSource,
140) -> impl IntoView {
141 use thaw::Flex;
142 let uri = uri.unwrap_or_else(DocumentURI::no_doc);
143 let burger = !matches!(
144 (&toc, &omdoc),
145 (TOCSource::None, crate::components::omdoc::OMDocSource::None)
146 );
147 view! {<FTMLDocumentSetup uri><Flex>
148 <div><DomStringCont html cont=iterate/></div>
149 {if burger {
150 Some(do_toc_sidebar(toc,gottos,omdoc))
151 } else {None}}
152 </Flex></FTMLDocumentSetup>
153 }
154}
155
156#[cfg(not(feature = "omdoc"))]
157#[component]
158pub fn DocumentString(
159 html: String,
160 #[prop(optional)] uri: Option<DocumentURI>,
161 #[prop(optional, into)] toc: TOCSource,
162 #[prop(optional, into)] gottos: Vec<Gotto>,
163) -> impl IntoView {
164 use thaw::Flex;
165 let uri = uri.unwrap_or_else(DocumentURI::no_doc);
166 let burger = !matches!(toc, TOCSource::None);
167 view! {<FTMLDocumentSetup uri><Flex>
168 <div><DomStringCont html cont=iterate/></div>
169 {if burger {
170 Some(do_toc_sidebar(toc,gottos))
171 } else {None}}
172 </Flex></FTMLDocumentSetup>
173 }
174}
175
176#[cfg(feature = "omdoc")]
177fn do_toc_sidebar(
178 toc: TOCSource,
179 gottos: Vec<Gotto>,
180 omdoc: crate::components::omdoc::OMDocSource,
181) -> impl IntoView {
182 inject_css("ftml-toc", include_str!("./toc.css"));
183 use flams_web_utils::components::ClientOnly;
185 use thaw::{Button, ButtonShape, ButtonSize, Scrollbar};
186 let visible = RwSignal::new(true);
187 let display = Memo::new(move |_| {
188 if visible.get() {
189 "ftml-toc-visible"
190 } else {
191 "ftml-toc-invisible"
192 }
193 });
194
195 let hl_option: RwSignal<crate::HighlightOption> = expect_context();
196 let value = RwSignal::new(hl_option.get_untracked().as_str().to_string());
197 Effect::new(move || {
198 if let Some(v) = crate::HighlightOption::from_str(&value.get()) {
199 if hl_option.get_untracked() != v {
200 hl_option.set(v);
201 }
202 }
203 });
204 use thaw::Select;
205 let select = move || {
206 if hl_option.get() == crate::HighlightOption::None {
207 None
208 } else {
209 Some(
210 view!("Symbol Highlighting: "<Select value default_value=value.get_untracked() size=thaw::SelectSize::Small>
211 <option class="ftml-comp">{crate::HighlightOption::Colored.as_str()}</option>
212 <option class="ftml-comp-subtle">{crate::HighlightOption::Subtle.as_str()}</option>
213 <option>{crate::HighlightOption::Off.as_str()}</option>
214 </Select>),
215 )
216 }
217 };
218
219 crate::components::do_toc(toc, gottos, move |v| {
220 view! {<div class="ftml-toc-sidebar">
221 <ClientOnly>
222 <Button
228 shape=ButtonShape::Circular
230 size=ButtonSize::Small
231 on_click=move |_| visible.set(!visible.get_untracked())
232 >{move || if visible.get() {"⌃"} else {"⌄"}}</Button>
233 <div class=display>
234 {select}
235 {crate::components::omdoc::do_omdoc(omdoc)}
236 <Scrollbar style="width:fit-content;max-height:575px;">{v}</Scrollbar>
237 </div>
238 </ClientOnly>
244 </div>}
246 })
247}
248
249#[cfg(not(feature = "omdoc"))]
250fn do_toc_sidebar(toc: crate::components::TOCSource, gottos: Vec<Gotto>) -> impl IntoView {
251 use flams_web_utils::components::ClientOnly;
253 use thaw::{
254 Button, ButtonAppearance, ButtonShape, ButtonSize, DrawerBody, DrawerPosition,
255 InlineDrawer, Scrollbar,
256 };
257 inject_css("ftml-toc", include_str!("./toc.css"));
258 let visible = RwSignal::new(true);
259 let display = Memo::new(move |_| {
260 if visible.get() {
261 "ftml-toc-visible"
262 } else {
263 "ftml-toc-invisible"
264 }
265 });
266
267 let hl_option: RwSignal<crate::HighlightOption> = expect_context();
268 let value = RwSignal::new(hl_option.get_untracked().as_str().to_string());
269 Effect::new(move || {
270 if let Some(v) = crate::HighlightOption::from_str(&value.get()) {
271 if hl_option.get_untracked() != v {
272 hl_option.set(v);
273 }
274 }
275 });
276 use thaw::Select;
277 let select = move || {
278 if hl_option.get() == crate::HighlightOption::None {
279 None
280 } else {
281 Some(view!(<Select value size=thaw::SelectSize::Small>
282 <option>{crate::HighlightOption::Colored.as_str()}</option>
283 <option>{crate::HighlightOption::Subtle.as_str()}</option>
284 <option>{crate::HighlightOption::Off.as_str()}</option>
285 </Select>))
286 }
287 };
288
289 crate::components::do_toc(toc, gottos, move |v| {
290 view! {<div class="ftml-toc-sidebar">
291 <ClientOnly>
292 <Button
293 shape=ButtonShape::Circular
294 size=ButtonSize::Small
295 on_click=move |_| visible.set(!visible.get_untracked())
296 >{move || if visible.get() {"⌃"} else {"⌄"}}</Button>
297 <div class=display>{select}
298 <Scrollbar style="width:fit-content;max-height:575px;">{v}</Scrollbar>
299 </div>
300 </ClientOnly>
301 </div>}
302 })
303}