Skip to main content

ravenrs_app_ui/
dashboard.rs

1//! Other views together.
2
3use std::collections::BTreeMap;
4
5use ravenrs_app::{FrameContext, ViewInfo, ViewKind};
6use ravenrs_reactive::State;
7use ravenrs_widgets::{AnyView, BuildCx, Keyed, NodeId, Text, Tile, TileLayout, TileSpot, View, ViewExt};
8
9use crate::{ListView, Session};
10
11/// How many cells wide a piece starts, and how tall.
12const ITEM_COLUMNS: u16 = 2;
13const ITEM_ROWS: u16 = 2;
14
15/// Several views together, each in a frame of its own, as the model says. A dashboard is bound to
16/// no records, and people can move and resize its pieces.
17#[derive(Debug)]
18#[cfg_attr(feature = "live", ravenrs_live::expose(part = "fields"))]
19pub struct DashboardView {
20    session: Session,
21    view: ViewInfo,
22}
23
24#[cfg_attr(feature = "live", ravenrs_live::expose)]
25impl DashboardView {
26    /// The dashboard of this view in the model.
27    pub fn new(session: Session, view: ViewInfo) -> Self {
28        Self { session, view }
29    }
30}
31
32impl View for DashboardView {
33    fn build(&self, cx: &mut BuildCx<'_>) -> NodeId {
34        // The one people changed it to, starting over, since pieces are kept by position.
35        self.session.revision();
36        let view = self.session.dashboard_shown(&self.view.id);
37        let key = view.id.bytes().fold(0_u64, |h, b| (h ^ u64::from(b)).wrapping_mul(0x100_0000_01b3));
38        cx.child(&Keyed::new(key, DashboardItems { session: self.session.clone(), view }))
39    }
40}
41
42/// The pieces of one dashboard.
43struct DashboardItems {
44    session: Session,
45    view: ViewInfo,
46}
47
48impl View for DashboardItems {
49    fn build(&self, cx: &mut BuildCx<'_>) -> NodeId {
50        let model = self.session.model();
51        let Some(items) = model.at(&["Views", &self.view.id, "Items"]) else {
52            return cx.child(&Text::new(self.view.id.clone()));
53        };
54        let order = items.in_order();
55        let frames: State<BTreeMap<String, Session>> = cx.use_state(BTreeMap::new);
56        let spots: State<Vec<TileSpot>> = cx.use_state(|| {
57            order.iter().map(|item| TileSpot::new(item.id.clone(), ITEM_COLUMNS, ITEM_ROWS)).collect()
58        });
59        let alone = order.len() == 1;
60        let mut tiles = TileLayout::new(spots.binding());
61        for item in order {
62            let caption = item.text("Caption").unwrap_or(&item.id).to_owned();
63            let content: AnyView = if let Some(name) = item.text("Control") {
64                match self.session.control(name) {
65                    Some(control) => control(),
66                    None => Box::new(Text::new(name.to_owned())),
67                }
68            } else if let Some(info) = item.text("View").and_then(|id| ViewInfo::from_model(&model, id).ok())
69            {
70                let kept = frames.peek().get(&item.id).cloned();
71                let session = kept.unwrap_or_else(|| {
72                    let session = self.session.child(FrameContext::Nested);
73                    session.frame().set_view(Some(info.clone()));
74                    frames.update(|all| {
75                        all.insert(item.id.clone(), session.clone());
76                    });
77                    session
78                });
79                match info.kind {
80                    ViewKind::List => {
81                        let opener = self.session.clone();
82                        let entity = info.entity.clone().unwrap_or_default();
83                        Box::new(
84                            ListView::new(session.clone(), session.frame(), info)
85                                .plain_toolbar()
86                                .on_open(std::rc::Rc::new(move |key: &ravenrs_app::Value| {
87                                    opener.open_detail(&entity, key);
88                                }))
89                                .grid_shape(|grid| grid.toolbar(false).filter_row(false))
90                                .stretch()
91                                .grow(1.0),
92                        )
93                    }
94                    _ => Box::new(Text::new(info.id)),
95                }
96            } else {
97                Box::new(Text::new(item.id.clone()))
98            };
99            // One piece has nowhere to go and nothing to tell apart from.
100            if alone {
101                return cx.child(&content);
102            }
103            tiles = tiles.tile(Tile::new(item.id.clone(), caption, content));
104        }
105        cx.child(&tiles)
106    }
107}