ftml_viewer_components/components/omdoc/
mod.rs1use content::OMDocDeclaration;
2use flams_ontology::{
3 content::terms::Term,
4 ftml::FTMLKey,
5 uris::{
6 ArchiveURITrait, DocumentElementURI, DocumentURI, ModuleURI, NarrativeURI, PathURITrait,
7 SymbolURI, URIWithLanguage,
8 },
9};
10use flams_web_utils::{
11 components::{Block, Header},
12 do_css,
13};
14use leptos::prelude::*;
15use narration::OMDocDocumentElement;
16
17use crate::{FTMLString, FTMLStringMath};
18
19pub mod content;
20pub mod narration;
21
22#[allow(clippy::large_enum_variant)]
23#[derive(Debug, Default, Clone, serde::Serialize, serde::Deserialize)]
24pub enum OMDocSource {
25 #[default]
26 None,
27 Ready(narration::OMDocDocument),
28 Get,
29}
30
31#[allow(clippy::match_wildcard_for_single_variants)]
32pub(crate) fn do_omdoc(omdoc: OMDocSource) -> impl IntoView {
33 use crate::components::omdoc::{OMDoc, OMDocT};
34 use flams_web_utils::components::{Drawer, Header, Trigger};
35 use thaw::{Button, ButtonAppearance};
36 if matches!(omdoc, OMDocSource::None) {
37 return None;
38 }
39 let NarrativeURI::Document(uri) = expect_context() else {
40 return None;
41 };
42 let pdf_url = format!(
43 "{}/doc?a={}{}&d={}&l={}&format=pdf",
44 crate::remote::get_server_url(),
45 uri.archive_id(),
46 uri.path()
47 .map(|s| format!("&p={s}"))
48 .unwrap_or_else(|| String::new()),
49 uri.name().first_name(),
50 uri.language()
51 );
52 let title = RwSignal::new(uri.name().to_string());
53 Some(view! {<div style="margin-left:auto;"><Drawer lazy=true>
54 <Trigger slot>
55 <Button
56 appearance=ButtonAppearance::Subtle>
57 <div style="font-variant:small-caps;font-weight:bold,width:fit-content,border:2px solid black">"OMDoc"</div>
58 </Button>
59 </Trigger>
60 <Header slot><span inner_html=title/></Header>
61 {match &omdoc {
62 OMDocSource::Get => {
63 let uri = uri.clone();
64 leptos::either::Either::Left(crate::remote::get!(omdoc(NarrativeURI::Document(uri.clone()).into()) = (_,omdoc) => {
65 let OMDoc::Document(omdoc) = omdoc else {unreachable!()};
66 if let Some(s) = &omdoc.title {
67 title.set(s.clone());
68 }
69 omdoc.into_view()
70 }))
71 }
72 OMDocSource::Ready(omdoc) => {
73 if let Some(s) = &omdoc.title {
74 title.set(s.clone());
75 }
76 leptos::either::Either::Right(omdoc.clone().into_view())
77 }
78 OMDocSource::None => unreachable!()
79 }}
80 </Drawer>
81 <a target="_blank" href=pdf_url ><Button
82 appearance=ButtonAppearance::Subtle>
83 <div style="font-variant:small-caps;font-weight:bold,width:fit-content,border:2px solid black">"PDF"</div>
84 </Button></a>
85 </div>})
86}
87
88pub(crate) mod sealed {
89 pub trait Sealed {}
90}
91pub trait OMDocT: std::fmt::Debug + Clone {
92 fn into_view(self) -> impl leptos::IntoView;
97}
98pub trait OMDocDecl:
99 sealed::Sealed + OMDocT + std::fmt::Debug + Clone + Send + Sync + 'static
100{
101}
102
103#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
104#[serde(tag = "type")]
105#[cfg_attr(feature = "ts", derive(tsify_next::Tsify))]
106#[cfg_attr(feature = "ts", tsify(into_wasm_abi, from_wasm_abi))]
107pub enum OMDoc {
108 Slide(narration::OMDocSlide),
109 Document(narration::OMDocDocument),
110 Section(narration::OMDocSection),
111 DocModule(content::OMDocModule<OMDocDocumentElement>),
112 Module(content::OMDocModule<OMDocDeclaration>),
113 DocMorphism(content::OMDocMorphism<OMDocDocumentElement>),
114 Morphism(content::OMDocMorphism<OMDocDeclaration>),
115 DocStructure(content::OMDocStructure<OMDocDocumentElement>),
116 Structure(content::OMDocStructure<OMDocDeclaration>),
117 DocExtension(content::OMDocExtension<OMDocDocumentElement>),
118 Extension(content::OMDocExtension<OMDocDeclaration>),
119 SymbolDeclaration(content::OMDocSymbol),
120 Variable(narration::OMDocVariable),
121 Paragraph(narration::OMDocParagraph),
122 Problem(narration::OMDocProblem),
123 Term {
124 uri: DocumentElementURI,
125 term: Term,
126 },
127 DocReference {
128 uri: DocumentURI,
129 title: Option<String>,
130 },
131 Other(String),
132}
133impl OMDocT for OMDoc {
134 fn into_view(self) -> impl leptos::IntoView {
135 match self {
136 Self::Slide(d) => d.into_view().into_any(),
137 Self::Document(d) => d.into_view().into_any(),
138 Self::Section(d) => d.into_view().into_any(),
139 Self::DocModule(d) => d.into_view().into_any(),
140 Self::Module(d) => d.into_view().into_any(),
141 Self::DocMorphism(d) => d.into_view().into_any(),
142 Self::Morphism(d) => d.into_view().into_any(),
143 Self::DocStructure(d) => d.into_view().into_any(),
144 Self::Structure(d) => d.into_view().into_any(),
145 Self::DocExtension(d) => d.into_view().into_any(),
146 Self::Extension(d) => d.into_view().into_any(),
147 Self::SymbolDeclaration(d) => d.into_view().into_any(),
148 Self::Variable(d) => d.into_view().into_any(),
149 Self::Paragraph(d) => d.into_view().into_any(),
150 Self::Problem(d) => d.into_view().into_any(),
151 Self::DocReference { uri, title } => narration::doc_ref(uri, title).into_any(),
152 Self::Term { uri, term } => view! {
153 <Block show_separator=false>
154 <Header slot><span><b>"Term "</b>{
155 crate::remote::get!(present(term.clone()) = html => {
156 view!(<FTMLStringMath html/>)
157 })
158 }</span></Header>
159 ""
160 </Block>
161 }
162 .into_any(),
163 Self::Other(s) => view!(<div>{s}</div>).into_any(),
164 }
165 }
166}
167
168#[cfg(feature = "ssr")]
169pub mod froms {
170 use flams_ontology::{
171 content::{declarations::structures::Extension, ContentReference},
172 rdf::ontologies::ulo2,
173 uris::{SymbolURI, URIOrRefTrait},
174 Checked,
175 };
176 use flams_system::backend::{
177 rdf::{sparql, QueryResult},
178 Backend, GlobalBackend,
179 };
180
181 pub(crate) fn get_extensions<'a>(
182 b: &'a impl Backend,
183 s: &SymbolURI,
184 ) -> impl Iterator<Item = ContentReference<Extension<Checked>>> + 'a {
185 let syms = GlobalBackend::get()
186 .triple_store()
187 .query(
188 sparql::Select {
189 subject: sparql::Var('x'),
190 pred: ulo2::EXTENDS.into_owned(),
191 object: s.to_iri(),
192 }
193 .into(),
194 )
195 .map(|r| r.into_uris())
196 .unwrap_or_default();
197 syms.filter_map(|s| b.get_declaration(&s))
198 }
199 }
220
221#[inline]
222pub fn uses(header: &'static str, uses: Vec<ModuleURI>) -> impl IntoView {
223 comma_sep(header, uses.into_iter().map(|m| module_name(&m)))
224}
225
226pub fn comma_sep<V: IntoView>(
227 header: &'static str,
228 mut elems: impl Iterator<Item = V>,
229) -> impl IntoView {
230 let first = elems.next()?;
231 Some(view! {
232 <div style="display:inline-block;width:max-content;">
233 {header}": "{first}{elems.map(|e| view!(", "{e})).collect_view()}
234 </div>
235 })
236}
237
238pub fn module_name(uri: &ModuleURI) -> impl IntoView {
239 use flams_web_utils::components::{OnClickModal, Popover, PopoverTrigger};
240 use thaw::Scrollbar;
241 let name = uri.name().last_name().to_string();
242 let uristring = uri.to_string();
243 let uriclone = uri.clone();
244 let uri = uri.clone();
245 view! {
246 <div style="display:inline-block;"><Popover>
247 <PopoverTrigger slot><b class="ftml-comp">{name}</b></PopoverTrigger>
248 <OnClickModal slot><Scrollbar style="max-height:80vh">{
249 crate::remote::get!(omdoc(uriclone.clone().into()) = (css,s) => {
250 for c in css { do_css(c); }
251 s.into_view()
252 })
253 }</Scrollbar></OnClickModal>
254 <div style="font-size:small;">{uristring}</div>
255 <div style="margin-bottom:5px;"><thaw::Divider/></div>
256 <Scrollbar style="max-height:300px">
257 {
258 crate::remote::get!(omdoc(uri.clone().into()) = (css,s) => {
259 for c in css { do_css(c); }
260 s.into_view()
261 })
262 }
263 </Scrollbar>
264 </Popover></div>
265 }
266}
267
268pub fn doc_name(uri: &DocumentURI, title: String) -> impl IntoView {
269 use flams_web_utils::components::{Popover, PopoverTrigger};
270 let uristring = uri.to_string();
271 view! {
272 <div style="display:inline-block;"><Popover>
273 <PopoverTrigger slot><span class="ftml-comp"><FTMLString html=title/></span></PopoverTrigger>
274 {uristring}
275 </Popover>
276 <a style="display:inline-block;" target="_blank" href={crate::remote::server_config.top_doc_url(&uri)}><thaw::Icon icon=icondata_bi::BiLinkRegular /></a>
277 </div>
278 }
279}
280pub fn doc_elem_name(
281 uri: DocumentElementURI,
282 kind: Option<&'static str>,
283 title: String,
284) -> impl IntoView {
285 use flams_web_utils::components::{Popover, PopoverTrigger};
286 let uristring = uri.to_string();
287 view! {
288 <div style="display:inline-block;"><Popover>
290 <PopoverTrigger slot><span>{kind.map(|k| view!({k}" "))}<span class="ftml-comp"><FTMLString html=title/></span></span></PopoverTrigger>
291 <div style="font-size:small;">{uristring}</div>
292 <div style="margin-bottom:5px;"><thaw::Divider/></div>
293 <div style="background-color:white;color:black;">
294 {
295 crate::remote::get!(paragraph(uri.clone()) = (_,css,s) => {
296 for c in css { do_css(c); }
297 view!(<FTMLString html=s/>)
298 })
299 }
300 </div>
301 </Popover></div>
302 }
303}
304
305#[inline]
306pub fn symbol_name(uri: &SymbolURI, title: &str) -> impl IntoView {
307 const TERM: &str = FTMLKey::Term.attr_name();
308 const HEAD: &str = FTMLKey::Head.attr_name();
309 const COMP: &str = FTMLKey::Comp.attr_name();
310 let html = format!("<span {TERM}=\"OMID\" {HEAD}=\"{uri}\" {COMP}>{title}</span>");
311 view!(<FTMLString html/>)
312}