ftml_viewer_components/components/
counters.rs

1use flams_ontology::{
2    narration::{paragraphs::ParagraphKind, sections::SectionLevel},
3    uris::{DocumentURI, Name},
4};
5use flams_utils::{
6    impossible, unwrap,
7    vecmap::{VecMap, VecSet},
8};
9use leptos::prelude::*;
10use smallvec::SmallVec;
11
12use crate::extractor::DOMExtractor;
13
14use super::{TOCElem, TOCIter};
15
16#[derive(Debug, Clone, Copy, serde::Serialize, serde::Deserialize, PartialEq, Eq)]
17pub enum LogicalLevel {
18    None,
19    Section(SectionLevel),
20    Paragraph,
21    BeamerSlide,
22}
23
24trait CounterTrait:
25    Copy
26    + PartialEq
27    + std::ops::Add<Self, Output = Self>
28    + std::ops::AddAssign<Self>
29    + Default
30    + Clone
31    + Send
32    + Sync
33    + std::fmt::Debug
34    + std::fmt::Display
35    + 'static
36{
37    fn one() -> Self;
38}
39impl CounterTrait for u16 {
40    fn one() -> Self {
41        1
42    }
43}
44impl CounterTrait for u32 {
45    fn one() -> Self {
46        1
47    }
48}
49
50#[derive(Copy, Clone, PartialEq, Eq, Default, Debug)]
51struct AllSections(pub [u16; 7]);
52impl std::fmt::Display for AllSections {
53    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
54        write!(
55            f,
56            "[{} {} {} {} {} {} {}]",
57            self.0[0], self.0[1], self.0[2], self.0[3], self.0[4], self.0[5], self.0[6]
58        )
59    }
60}
61
62impl std::ops::Add<SectionLevel> for AllSections {
63    type Output = Self;
64    fn add(self, rhs: SectionLevel) -> Self::Output {
65        let idx: u8 = rhs.into();
66        let mut s = AllSections::default();
67        s.0[idx as usize] = 1;
68        self + s
69    }
70}
71
72impl std::ops::Add<Self> for AllSections {
73    type Output = Self;
74    fn add(self, rhs: Self) -> Self::Output {
75        let mut changed = false;
76        let r = AllSections([
77            {
78                if rhs.0[0] > 0 {
79                    changed = true
80                };
81                self.0[0] + rhs.0[0]
82            },
83            {
84                if rhs.0[1] > 0 {
85                    changed = true
86                };
87                self.0[1] + rhs.0[1]
88            },
89            {
90                if changed {
91                    0
92                } else {
93                    if rhs.0[2] > 0 {
94                        changed = true
95                    }
96                    self.0[2] + rhs.0[2]
97                }
98            },
99            {
100                if changed {
101                    0
102                } else {
103                    if rhs.0[3] > 0 {
104                        changed = true
105                    }
106                    self.0[3] + rhs.0[3]
107                }
108            },
109            {
110                if changed {
111                    0
112                } else {
113                    if rhs.0[4] > 0 {
114                        changed = true
115                    }
116                    self.0[4] + rhs.0[4]
117                }
118            },
119            {
120                if changed {
121                    0
122                } else {
123                    if rhs.0[5] > 0 {
124                        changed = true
125                    }
126                    self.0[5] + rhs.0[5]
127                }
128            },
129            {
130                if changed {
131                    0
132                } else {
133                    self.0[6] + rhs.0[6]
134                }
135            },
136        ]);
137        //tracing::warn!("updating {self:?}+{rhs:?}={r:?}");
138        r
139    }
140}
141
142impl std::ops::AddAssign<Self> for AllSections {
143    fn add_assign(&mut self, rhs: Self) {
144        //tracing::warn!("updating {self:?}+{rhs:?}");
145        let mut changed = false;
146        if rhs.0[0] > 0 {
147            changed = true
148        };
149        self.0[0] += rhs.0[0];
150        if rhs.0[1] > 0 {
151            changed = true;
152        }
153        self.0[1] += rhs.0[1];
154        if changed {
155            self.0[2] = 0
156        } else {
157            if rhs.0[2] > 0 {
158                changed = true;
159            }
160            self.0[2] += rhs.0[2];
161        }
162        if changed {
163            self.0[3] = 0
164        } else {
165            if rhs.0[3] > 0 {
166                changed = true;
167            }
168            self.0[3] += rhs.0[3];
169        }
170        if changed {
171            self.0[4] = 0
172        } else {
173            if rhs.0[4] > 0 {
174                changed = true;
175            }
176            self.0[4] += rhs.0[4];
177        }
178        if changed {
179            self.0[5] = 0
180        } else {
181            if rhs.0[5] > 0 {
182                changed = true;
183            }
184            self.0[5] += rhs.0[5];
185        }
186        if changed {
187            self.0[6] = 0
188        } else {
189            self.0[6] += rhs.0[6];
190        }
191        //tracing::warn!(" = {self:?}");
192    }
193}
194
195impl CounterTrait for AllSections {
196    fn one() -> Self {
197        panic!("That's not how sectioning works")
198    }
199}
200
201impl SmartCounter<AllSections> {
202    fn inc_at(&self, lvl: SectionLevel) {
203        let idx: u8 = lvl.into();
204        let mut s = AllSections::default();
205        s.0[idx as usize] = 1;
206        self.0
207            .update_untracked(|SmartCounterI { since, .. }| *since += s);
208    }
209}
210
211#[derive(Debug, Clone, Default)]
212struct SmartCounterI<N: CounterTrait> {
213    cutoff: Option<Cutoff<N>>,
214    since: N,
215}
216
217#[derive(Clone)]
218struct Cutoff<N: CounterTrait> {
219    previous: Option<std::sync::Arc<Cutoff<N>>>,
220    since: N,
221    set: RwSignal<N>,
222}
223impl<N: CounterTrait> Cutoff<N> {
224    fn get(&self) -> N {
225        if let Some(p) = self.previous.as_ref() {
226            //leptos::logging::log!("Getting {}+{}+{}",p.get(),self.since,self.set.get());
227            p.get() + self.since + self.set.get()
228        } else {
229            //leptos::logging::log!("Getting {}+{}",self.since,self.set.get());
230            self.since + self.set.get()
231        }
232    }
233    fn depth(&self) -> u16 {
234        if let Some(p) = self.previous.as_ref() {
235            p.depth() + 1
236        } else {
237            1
238        }
239    }
240    fn get_untracked(&self) -> N {
241        if let Some(p) = self.previous.as_ref() {
242            p.get_untracked() + self.since + self.set.get_untracked()
243        } else {
244            self.since + self.set.get_untracked()
245        }
246    }
247}
248impl<N: CounterTrait> std::fmt::Debug for Cutoff<N> {
249    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
250        f.debug_struct("Cutoff")
251            .field("depth", &self.depth())
252            .field(
253                "previous",
254                &self.previous.as_ref().map(|p| p.get_untracked()),
255            )
256            .field("since", &self.since)
257            .field("set", &self.set.get_untracked())
258            .finish()
259    }
260}
261
262#[derive(Clone, Default, Copy)]
263struct SmartCounter<N: CounterTrait>(RwSignal<SmartCounterI<N>>);
264
265impl<N: CounterTrait> SmartCounterI<N> {
266    fn get(&self) -> N {
267        if let Some(cutoff) = &self.cutoff {
268            cutoff.get() + self.since
269        } else {
270            self.since
271        }
272    }
273}
274
275impl<N: CounterTrait> std::fmt::Debug for SmartCounter<N> {
276    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
277        self.0
278            .with_untracked(|s| f.debug_struct("SmartCounter").field("inner", s).finish())
279    }
280}
281
282impl<N: CounterTrait> SmartCounter<N> {
283    fn inc_memo<T: Send + Sync + 'static + PartialEq>(
284        &self,
285        f: impl Fn(N) -> T + Send + Sync + 'static,
286    ) -> Memo<T> {
287        self.0.update_untracked(|SmartCounterI { cutoff, since }| {
288            *since += N::one();
289            let since = *since;
290            if let Some(cutoff) = cutoff {
291                let cutoff = cutoff.clone();
292                Memo::new(move |_| f(cutoff.get() + since))
293            } else {
294                Memo::new(move |_| f(since))
295            }
296        })
297    }
298    fn get_untracked(&self) -> N {
299        self.0.with_untracked(|SmartCounterI { cutoff, since }| {
300            if let Some(cutoff) = cutoff {
301                cutoff.get() + *since
302            } else {
303                *since
304            }
305        })
306    }
307
308    fn inc(&self) {
309        self.0
310            .update_untracked(|SmartCounterI { since, .. }| *since += N::one());
311    }
312    fn memo<T: Send + Sync + 'static + PartialEq>(
313        &self,
314        f: impl Fn(N) -> T + Send + Sync + 'static,
315    ) -> Memo<T> {
316        self.0.with_untracked(|SmartCounterI { cutoff, since }| {
317            let since = *since;
318            if let Some(cutoff) = cutoff {
319                let cutoff = cutoff.clone();
320                Memo::new(move |_| f(cutoff.get() + since))
321            } else {
322                Memo::new(move |_| f(since))
323            }
324        })
325    }
326    fn reset(&self) {
327        self.0.update_untracked(|x| *x = SmartCounterI::default());
328    }
329
330    fn set_cutoff(&self, v: N) {
331        self.0.update_untracked(|SmartCounterI { cutoff, .. }| {
332            if let Some(c) = cutoff.as_ref() {
333                c.set.set(v);
334            }
335        });
336    }
337
338    fn split(&self) -> Self {
339        let SmartCounterI { cutoff, since } = self.0.get_untracked();
340        let ret = Self(RwSignal::new(SmartCounterI {
341            cutoff: cutoff.clone(),
342            since,
343        }));
344
345        let previous = cutoff.map(std::sync::Arc::new);
346        let new_cutoff = Cutoff {
347            previous,
348            since,
349            set: RwSignal::new(N::default()),
350        };
351        self.0.update_untracked(
352            |SmartCounterI {
353                 cutoff: nctf,
354                 since: snc,
355             }| {
356                *nctf = Some(new_cutoff);
357                *snc = N::default();
358            },
359        );
360        ret
361    }
362}
363
364#[derive(Debug, Clone)]
365pub struct SectionCounters {
366    pub current: LogicalLevel,
367    pub max: SectionLevel,
368    sections: SmartCounter<AllSections>,
369    initialized: RwSignal<bool>,
370    counters: RwSignal<VecMap<Name, SmartCounter<u16>>>,
371    resets: RwSignal<VecMap<SectionLevel, VecSet<Name>>>,
372    for_paras: RwSignal<VecMap<(ParagraphKind, Option<Name>), Option<Name>>>,
373    slides: SmartCounter<u32>,
374}
375impl Default for SectionCounters {
376    #[inline]
377    fn default() -> Self {
378        Self {
379            current: LogicalLevel::None,
380            max: SectionLevel::Part,
381            sections: SmartCounter::default(),
382            counters: RwSignal::new(VecMap::default()),
383            resets: RwSignal::new(VecMap::default()),
384            for_paras: RwSignal::new(VecMap::default()),
385            initialized: RwSignal::new(false),
386            slides: SmartCounter::default(),
387        }
388    }
389}
390impl SectionCounters {
391    fn init_paras(&self) {
392        if self.initialized.get_untracked() {
393            return;
394        }
395        self.initialized.update_untracked(|b| *b = true);
396        let mut counters = VecMap::default();
397        let mut resets = VecMap::default();
398        let mut for_paras = VecMap::default();
399        let sig = expect_context::<RwSignal<DOMExtractor>>();
400        sig.with_untracked(|e| {
401            for c in &e.styles.counters {
402                //leptos::logging::log!("Doing {c:?}");
403                counters.insert(c.name.clone(), SmartCounter::default());
404                if let Some(p) = c.parent {
405                    resets
406                        .get_or_insert_mut(p, || VecSet::new())
407                        .insert(c.name.clone());
408                }
409            }
410            for stl in &e.styles.styles {
411                for_paras.insert((stl.kind, stl.name.clone()), stl.counter.clone());
412            }
413        });
414        self.counters.update_untracked(|p| *p = counters);
415        self.resets.update_untracked(|p| *p = resets);
416        self.for_paras.update_untracked(|p| *p = for_paras);
417    }
418
419    pub fn current_level(&self) -> LogicalLevel {
420        self.current
421    }
422
423    pub fn next_section(&mut self) -> (Option<Memo<String>>, Option<&'static str>) {
424        self.init_paras();
425        let lvl = if let LogicalLevel::Section(s) = self.current {
426            s.inc()
427        } else if self.current == LogicalLevel::None {
428            self.max
429        } else {
430            return (Some(Memo::new(|_| "display:content;".into())), None);
431        };
432        //tracing::warn!("New section at level {lvl:?}");
433        self.set_section(lvl);
434        let sections = self.sections.0.get_untracked();
435        match lvl {
436            SectionLevel::Part => (
437                Some(Memo::new(move |_| {
438                    let sects = sections.get().0;
439                    format!("counter-set:ftml-part {};", sects[0])
440                })),
441                Some("ftml-part"),
442            ),
443            SectionLevel::Chapter => (
444                Some(Memo::new(move |_| {
445                    let sects = sections.get().0;
446                    format!(
447                        "counter-set:ftml-part {} ftml-chapter {}",
448                        sects[0], sects[1]
449                    )
450                })),
451                Some("ftml-chapter"),
452            ),
453            SectionLevel::Section => (
454                Some(Memo::new(move |_| {
455                    let sects = sections.get().0;
456                    format!(
457                        "counter-set:ftml-part {} ftml-chapter {} ftml-section {}",
458                        sects[0], sects[1], sects[2]
459                    )
460                })),
461                Some("ftml-section"),
462            ),
463            SectionLevel::Subsection => (
464                Some(Memo::new(move |_| {
465                    let sects = sections.get().0;
466                    format!("counter-set:ftml-part {} ftml-chapter {} ftml-section {} ftml-subsection {}",
467          sects[0],
468          sects[1],
469          sects[2],
470          sects[3],
471        )
472                })),
473                Some("ftml-subsection"),
474            ),
475            SectionLevel::Subsubsection => (
476                Some(Memo::new(move |_| {
477                    let sects = sections.get().0;
478                    format!("counter-set:ftml-part {} ftml-chapter {} ftml-section {} ftml-subsection {} ftml-subsubsection {}",
479          sects[0],
480          sects[1],
481          sects[2],
482          sects[3],
483          sects[4],
484        )
485                })),
486                Some("ftml-subsubsection"),
487            ),
488            SectionLevel::Paragraph => (None, Some("ftml-paragraph")),
489            SectionLevel::Subparagraph => (None, Some("ftml-subparagraph")),
490        }
491    }
492
493    pub fn set_section(&mut self, lvl: SectionLevel) {
494        self.init_paras();
495        self.sections.inc_at(lvl);
496        //leptos::logging::log!("Setting section to {} => {}",lvl,self.sections.get_untracked());
497        self.resets.with_untracked(|rs| {
498            //leptos::logging::log!("Resetting at {lvl}: {rs:?}");
499            for (l, r) in &rs.0 {
500                if *l >= lvl {
501                    for n in r.iter() {
502                        self.counters.with_untracked(|c| {
503                            if let Some(c) = c.get(n) {
504                                //leptos::logging::log!("Resetting {n}");
505                                c.reset();
506                            }
507                        });
508                    }
509                }
510            }
511        });
512        self.current = LogicalLevel::Section(lvl);
513    }
514
515    fn get_counter(
516        all: &VecMap<(ParagraphKind, Option<Name>), Option<Name>>,
517        kind: ParagraphKind,
518        styles: &[Name],
519    ) -> Option<Name> {
520        styles
521            .iter()
522            .rev()
523            .find_map(|s| {
524                all.0.iter().find_map(|((k, n), v)| {
525                    if *k == kind && n.as_ref().is_some_and(|n| *n == *s) {
526                        Some(v.as_ref())
527                    } else {
528                        None
529                    }
530                })
531            })
532            .unwrap_or_else(|| all.get(&(kind, None)).map(|o| o.as_ref()).flatten())
533            .cloned()
534    }
535
536    pub fn get_para(&mut self, kind: ParagraphKind, styles: &[Name]) -> Memo<String> {
537        self.init_paras();
538        self.current = LogicalLevel::Paragraph;
539        let cnt = self
540            .for_paras
541            .with_untracked(|all_styles| Self::get_counter(all_styles, kind, styles));
542        if let Some(cntname) = cnt {
543            let Some(cnt) = self
544                .counters
545                .with_untracked(|cntrs| cntrs.get(&cntname).copied())
546            else {
547                impossible!()
548            };
549            cnt.inc_memo(move |i| format!("counter-set:ftml-{cntname} {i};"))
550        } else {
551            Memo::new(|_| String::new())
552        }
553    }
554
555    pub fn get_problem(&mut self, _styles: &[Name]) -> Memo<String> {
556        self.init_paras();
557        self.current = LogicalLevel::Paragraph;
558        Memo::new(|_| String::new())
559    }
560
561    pub fn get_slide() -> Memo<u32> {
562        let counters: Self = expect_context();
563        counters.init_paras();
564        counters.slides.memo(|i| i)
565    }
566    pub fn slide_inc() -> Self {
567        let mut counters: Self = expect_context();
568        counters.init_paras();
569        counters.slides.inc();
570        counters.current = LogicalLevel::BeamerSlide;
571        counters
572    }
573
574    pub fn inputref(uri: DocumentURI, id: String) -> Self {
575        let mut counters: Self = expect_context();
576        counters.init_paras();
577
578        //tracing::warn!("inputref: {uri}@{id}");
579
580        let old_slides = counters.slides; //.0.get_untracked();
581        counters.slides = counters.slides.split();
582        let old_slides = unwrap!(old_slides.0.get_untracked().cutoff).set;
583
584        let old_sections = counters.sections;
585        counters.sections = counters.sections.split();
586        let old_sections = unwrap!(old_sections.0.get_untracked().cutoff).set;
587
588        let mut new_paras = VecMap::default();
589
590        let old_paras = counters.counters.with_untracked(|v| {
591            v.0.iter()
592                .map(|(n, e)| {
593                    //leptos::logging::log!("Cloning {n}");
594                    let r = *e;
595                    let since = r.0.update_untracked(|e| {
596                        let r = e.since;
597                        e.since = 0;
598                        r
599                    });
600                    new_paras.insert(n.clone(), e.split());
601                    (n.clone(), (unwrap!(r.0.get_untracked().cutoff).set, since))
602                })
603                .collect::<VecMap<_, _>>()
604        });
605        counters.counters = RwSignal::new(new_paras);
606
607        let ctw = expect_context::<RwSignal<Option<Vec<TOCElem>>>>();
608        let uricl = uri.clone();
609        let idcl = id.clone();
610        let children = Memo::new(move |_| {
611            let uri = &uricl;
612            let id = &idcl;
613            ctw.with(|v| {
614                if let Some(v) = v.as_ref() {
615                    for e in v.iter_elems() {
616                        if let TOCElem::Inputref {
617                            uri: u,
618                            id: i,
619                            children: chs,
620                            ..
621                        } = e
622                        {
623                            if u == uri && i == id {
624                                return Some(chs.clone());
625                            }
626                        }
627                    }
628                    None
629                } else {
630                    None
631                }
632            })
633        });
634
635        let current = counters.current;
636        let max = counters.max;
637        let para_map = counters.for_paras;
638
639        Effect::new(move || {
640            children.with(|ch| {
641                if let Some(ch) = ch.as_ref() {
642                    //tracing::warn!("Updating {uri}@{id}");
643                    para_map.with_untracked(|m| {
644                        update(ch, current, max, &old_slides, &old_sections, &old_paras, m)
645                    })
646                }
647            })
648        });
649
650        //tracing::warn!("Returning {counters:?}");
651
652        counters
653    }
654}
655
656fn update(
657    ch: &[TOCElem],
658    mut current: LogicalLevel,
659    max: SectionLevel,
660    old_slides: &RwSignal<u32>,
661    old_sections: &RwSignal<AllSections>,
662    old_paras: &VecMap<Name, (RwSignal<u16>, u16)>,
663    para_map: &VecMap<(ParagraphKind, Option<Name>), Option<Name>>,
664) {
665    let mut curr = ch.iter();
666    let mut stack = SmallVec::<_, 4>::new();
667
668    let mut n_slides = 0;
669    let mut n_sections = AllSections::default();
670    let mut n_counters = old_paras
671        .0
672        .iter()
673        .map(|(n, (_, i))| (n.clone(), *i))
674        .collect::<VecMap<_, _>>();
675
676    //tracing::warn!("Updating inputref: {ch:?} in level {current:?}");
677
678    loop {
679        if let Some(c) = curr.next() {
680            match c {
681                TOCElem::Slide => n_slides += 1,
682                TOCElem::SkippedSection { children } => {
683                    let lvl = if let LogicalLevel::Section(s) = current {
684                        s.inc()
685                    } else if current == LogicalLevel::None {
686                        max
687                    } else {
688                        continue;
689                    };
690                    let old = std::mem::replace(&mut current, LogicalLevel::Section(lvl));
691                    stack.push((std::mem::replace(&mut curr, children.iter()), old));
692                }
693                TOCElem::Section { children, .. } => {
694                    let lvl = if let LogicalLevel::Section(s) = current {
695                        s.inc()
696                    } else if current == LogicalLevel::None {
697                        max
698                    } else {
699                        continue;
700                    };
701                    n_sections = n_sections + lvl;
702                    let old = std::mem::replace(&mut current, LogicalLevel::Section(lvl));
703                    stack.push((std::mem::replace(&mut curr, children.iter()), old));
704                }
705                TOCElem::Inputref { children, .. } => {
706                    //let old = std::mem::replace(&mut current,LogicalLevel::Paragraph);
707                    stack.push((std::mem::replace(&mut curr, children.iter()), current));
708                }
709                TOCElem::Paragraph { styles, kind, .. } => {
710                    if let Some(n) = SectionCounters::get_counter(para_map, *kind, styles) {
711                        //leptos::logging::log!("Increasing counter {n}");
712                        *n_counters.get_or_insert_mut(n, || 0) += 1;
713                    }
714                }
715            }
716        } else if let Some((next, lvl)) = stack.pop() {
717            curr = next;
718            current = lvl;
719        } else {
720            break;
721        }
722    }
723
724    //tracing::warn!("Setting inpuref sections to {n_sections:?}");
725    //leptos::logging::log!("Setting cutoffs");
726    old_slides.set(n_slides);
727    old_sections.set(n_sections);
728    for (n, v) in n_counters {
729        //leptos::logging::log!("Patching counter {n} as {v}");
730        if let Some((s, _)) = old_paras.get(&n) {
731            s.set(v);
732        }
733    }
734}