ftml_viewer_components/components/omdoc/
mod.rs

1use 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    /*#[cfg(feature="ssr")]
93    type Orig;
94    #[cfg(feature="ssr")]
95    fn from_orig(t:&Self::Orig) -> Self;*/
96    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    /*
200     pub(crate) async fn get_extensions_async<'a>(s:&SymbolURI) -> Vec<ContentReference<Extension<Checked>>> {
201       let backend = GlobalBackend::get();
202       let query = sparql::Select {
203         subject: sparql::Var('x'),
204         pred: ulo2::EXTENDS.into_owned(),
205         object: s.to_iri()
206       }.into();
207       let syms = tokio::task::spawn_blocking(move || {
208         backend.triple_store().query(query).map(|r|r.into_uris().collect::<Vec<_>>()).unwrap_or_default()
209       }).await.unwrap_or_default();
210       let mut ret = Vec::new();
211       for s in syms {
212         if let Some(r) = backend.get_declaration_async(&s).await {
213           ret.push(r);
214         }
215       }
216       ret
217     }
218    */
219}
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;">
289        <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}