Skip to main content

fiberplane_models/notebooks/
cells.rs

1mod table_cell;
2
3use crate::blobs::EncodedBlob;
4use crate::formatting::{Formatting, RichText};
5use crate::query_data::{has_query_data, set_query_field, unset_query_field};
6#[cfg(feature = "fp-bindgen")]
7use fp_bindgen::prelude::Serializable;
8use serde::{Deserialize, Serialize};
9pub use table_cell::*;
10use typed_builder::TypedBuilder;
11
12/// Representation of a single notebook cell.
13#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
14#[cfg_attr(
15    feature = "fp-bindgen",
16    derive(Serializable),
17    fp(rust_module = "fiberplane_models::notebooks")
18)]
19#[non_exhaustive]
20#[serde(tag = "type", rename_all = "snake_case")]
21pub enum Cell {
22    Checkbox(CheckboxCell),
23    Code(CodeCell),
24    Discussion(DiscussionCell),
25    Divider(DividerCell),
26    Graph(GraphCell),
27    Heading(HeadingCell),
28    Image(ImageCell),
29    ListItem(ListItemCell),
30    Log(LogCell),
31    Provider(ProviderCell),
32    Table(TableCell),
33    Timeline(TimelineCell),
34    Text(TextCell),
35}
36
37impl Cell {
38    /// Returns the cell's content, if any.
39    pub fn content(&self) -> Option<&str> {
40        match self {
41            Cell::Checkbox(cell) => Some(&cell.content),
42            Cell::Code(cell) => Some(&cell.content),
43            Cell::Discussion(_) => None,
44            Cell::Divider(_) => None,
45            Cell::Graph(_) => None,
46            Cell::Heading(cell) => Some(&cell.content),
47            Cell::Image(_) => None,
48            Cell::ListItem(cell) => Some(&cell.content),
49            Cell::Log(_) => None,
50            Cell::Provider(_) => None,
51            Cell::Table(_) => None,
52            Cell::Text(cell) => Some(&cell.content),
53            Cell::Timeline(_) => None,
54        }
55    }
56
57    /// Returns the cell's formatting, if any.
58    pub fn formatting(&self) -> Option<&Formatting> {
59        match self {
60            Cell::Code(_)
61            | Cell::Discussion(_)
62            | Cell::Divider(_)
63            | Cell::Graph(_)
64            | Cell::Image(_)
65            | Cell::Log(_)
66            | Cell::Provider(_)
67            | Cell::Table(_)
68            | Cell::Timeline(_) => None,
69            Cell::Checkbox(cell) => Some(&cell.formatting),
70            Cell::Heading(cell) => Some(&cell.formatting),
71            Cell::ListItem(cell) => Some(&cell.formatting),
72            Cell::Text(cell) => Some(&cell.formatting),
73        }
74    }
75
76    pub fn supports_formatting(&self) -> bool {
77        match self {
78            Cell::Code(_)
79            | Cell::Discussion(_)
80            | Cell::Divider(_)
81            | Cell::Graph(_)
82            | Cell::Image(_)
83            | Cell::Log(_)
84            | Cell::Table(_)
85            | Cell::Timeline(_) => false,
86            Cell::Checkbox(_)
87            | Cell::Heading(_)
88            | Cell::ListItem(_)
89            | Cell::Provider(_)
90            | Cell::Text(_) => true,
91        }
92    }
93
94    /// Returns the cell's ID.
95    pub fn id(&self) -> &str {
96        match self {
97            Cell::Checkbox(cell) => &cell.id,
98            Cell::Code(cell) => &cell.id,
99            Cell::Discussion(cell) => &cell.id,
100            Cell::Divider(cell) => &cell.id,
101            Cell::Graph(cell) => &cell.id,
102            Cell::Heading(cell) => &cell.id,
103            Cell::Image(cell) => &cell.id,
104            Cell::ListItem(cell) => &cell.id,
105            Cell::Log(cell) => &cell.id,
106            Cell::Provider(cell) => &cell.id,
107            Cell::Table(cell) => &cell.id,
108            Cell::Text(cell) => &cell.id,
109            Cell::Timeline(cell) => &cell.id,
110        }
111    }
112
113    /// Returns the cell's text, if any.
114    pub fn text(&self) -> Option<&str> {
115        self.content()
116    }
117
118    /// Returns a copy of the cell with a new ID.
119    #[must_use]
120    pub fn with_id(&self, id: &str) -> Self {
121        match self {
122            Cell::Checkbox(cell) => Cell::Checkbox(CheckboxCell {
123                id: id.to_owned(),
124                ..cell.clone()
125            }),
126            Cell::Code(cell) => Cell::Code(CodeCell {
127                id: id.to_owned(),
128                ..cell.clone()
129            }),
130            Cell::Discussion(cell) => Cell::Discussion(DiscussionCell {
131                id: id.to_owned(),
132                ..cell.clone()
133            }),
134            Cell::Divider(cell) => Cell::Divider(DividerCell {
135                id: id.to_owned(),
136                ..cell.clone()
137            }),
138            Cell::Graph(cell) => Cell::Graph(GraphCell {
139                id: id.to_owned(),
140                ..cell.clone()
141            }),
142            Cell::Heading(cell) => Cell::Heading(HeadingCell {
143                id: id.to_owned(),
144                ..cell.clone()
145            }),
146            Cell::Image(cell) => Cell::Image(ImageCell {
147                id: id.to_owned(),
148                ..cell.clone()
149            }),
150            Cell::ListItem(cell) => Cell::ListItem(ListItemCell {
151                id: id.to_owned(),
152                ..cell.clone()
153            }),
154            Cell::Log(cell) => Cell::Log(LogCell {
155                id: id.to_owned(),
156                ..cell.clone()
157            }),
158            Cell::Provider(cell) => Cell::Provider(ProviderCell {
159                id: id.to_owned(),
160                ..cell.clone()
161            }),
162            Cell::Table(cell) => Cell::Table(TableCell {
163                id: id.to_owned(),
164                ..cell.clone()
165            }),
166            Cell::Text(cell) => Cell::Text(TextCell {
167                id: id.to_owned(),
168                ..cell.clone()
169            }),
170            Cell::Timeline(cell) => Cell::Timeline(TimelineCell {
171                id: id.to_owned(),
172                ..cell.clone()
173            }),
174        }
175    }
176
177    /// Returns a copy of the cell with its text replaced by the given text,
178    /// without any formatting.
179    #[must_use]
180    pub fn with_text(&self, text: impl Into<String>) -> Self {
181        match self {
182            Cell::Checkbox(cell) => Cell::Checkbox(CheckboxCell {
183                id: cell.id.clone(),
184                content: text.into(),
185                formatting: Formatting::default(),
186                ..*cell
187            }),
188            Cell::Code(cell) => Cell::Code(CodeCell {
189                id: cell.id.clone(),
190                content: text.into(),
191                syntax: cell.syntax.clone(),
192                ..*cell
193            }),
194            Cell::Discussion(cell) => Cell::Discussion(cell.clone()),
195            Cell::Divider(cell) => Cell::Divider(cell.clone()),
196            Cell::Graph(cell) => Cell::Graph(cell.clone()),
197            Cell::Heading(cell) => Cell::Heading(HeadingCell {
198                id: cell.id.clone(),
199                content: text.into(),
200                formatting: Formatting::default(),
201                ..*cell
202            }),
203            Cell::Image(cell) => Cell::Image(cell.clone()),
204            Cell::ListItem(cell) => Cell::ListItem(ListItemCell {
205                id: cell.id.clone(),
206                content: text.into(),
207                formatting: Formatting::default(),
208                ..*cell
209            }),
210            Cell::Log(cell) => Cell::Log(cell.clone()),
211            Cell::Provider(cell) => Cell::Provider(cell.clone()),
212            Cell::Table(cell) => Cell::Table(cell.clone()),
213            Cell::Text(cell) => Cell::Text(TextCell {
214                id: cell.id.clone(),
215                content: text.into(),
216                formatting: Formatting::default(),
217                ..*cell
218            }),
219            Cell::Timeline(cell) => Cell::Timeline(cell.clone()),
220        }
221    }
222
223    /// Returns a copy of the cell with its text replaced by the given text and
224    /// formatting.
225    ///
226    /// **Warning:** For cell types that have text, but which do not support
227    ///              rich-text, the formatting will be dropped silently.
228    #[must_use]
229    pub fn with_rich_text(&self, text: impl Into<String>, formatting: Formatting) -> Self {
230        match self {
231            Cell::Checkbox(cell) => Cell::Checkbox(CheckboxCell {
232                id: cell.id.clone(),
233                content: text.into(),
234                formatting,
235                ..*cell
236            }),
237            Cell::Heading(cell) => Cell::Heading(HeadingCell {
238                id: cell.id.clone(),
239                content: text.into(),
240                formatting,
241                ..*cell
242            }),
243            Cell::ListItem(cell) => Cell::ListItem(ListItemCell {
244                id: cell.id.clone(),
245                content: text.into(),
246                formatting,
247                ..*cell
248            }),
249            Cell::Text(cell) => Cell::Text(TextCell {
250                id: cell.id.clone(),
251                content: text.into(),
252                formatting,
253                ..*cell
254            }),
255            Cell::Code(_)
256            | Cell::Discussion(_)
257            | Cell::Divider(_)
258            | Cell::Graph(_)
259            | Cell::Image(_)
260            | Cell::Log(_)
261            | Cell::Provider(_)
262            | Cell::Table(_)
263            | Cell::Timeline(_) => self.with_text(text),
264        }
265    }
266
267    /// Returns a copy of the cell with the text for the given field replaced by
268    /// the given text and optional formatting.
269    ///
270    /// If no field is given, the text is applied to the cell's main text field,
271    /// similar to `with_text()` or `with_rich_text()`, depending on whether any
272    /// formatting is given.
273    ///
274    /// **Warning:** For cell types that have text, but which do not support
275    ///              rich-text, any given formatting will be dropped silently.
276    #[must_use]
277    pub fn with_text_for_field<T>(
278        &self,
279        text: T,
280        formatting: Option<Formatting>,
281        field: Option<impl AsRef<str>>,
282    ) -> Self
283    where
284        T: Into<String> + AsRef<str>,
285    {
286        match (self, field) {
287            (Cell::Provider(cell), Some(field)) => {
288                Cell::Provider(cell.with_query_field(field.as_ref(), text))
289            }
290            (Cell::Table(cell), Some(field)) => Cell::Table(cell.with_row_value(
291                field.as_ref(),
292                TableRowValue::Text(RichText {
293                    text: text.into(),
294                    formatting: formatting.unwrap_or_default(),
295                }),
296            )),
297            (cell, _) => {
298                if let Some(formatting) = formatting {
299                    cell.with_rich_text(text, formatting)
300                } else {
301                    cell.with_text(text)
302                }
303            }
304        }
305    }
306
307    pub fn id_mut(&mut self) -> &mut String {
308        match self {
309            Cell::Checkbox(cell) => &mut cell.id,
310            Cell::Code(cell) => &mut cell.id,
311            Cell::Discussion(cell) => &mut cell.id,
312            Cell::Divider(cell) => &mut cell.id,
313            Cell::Graph(cell) => &mut cell.id,
314            Cell::Heading(cell) => &mut cell.id,
315            Cell::Image(cell) => &mut cell.id,
316            Cell::ListItem(cell) => &mut cell.id,
317            Cell::Log(cell) => &mut cell.id,
318            Cell::Provider(cell) => &mut cell.id,
319            Cell::Table(cell) => &mut cell.id,
320            Cell::Text(cell) => &mut cell.id,
321            Cell::Timeline(cell) => &mut cell.id,
322        }
323    }
324
325    /// Returns a mutable reference to the formatting array if the cell type
326    /// supports formatting.
327    pub fn formatting_mut(&mut self) -> Option<&mut Formatting> {
328        match self {
329            Cell::Checkbox(cell) => Some(&mut cell.formatting),
330            Cell::Heading(cell) => Some(&mut cell.formatting),
331            Cell::ListItem(cell) => Some(&mut cell.formatting),
332            Cell::Text(cell) => Some(&mut cell.formatting),
333            Cell::Code(_)
334            | Cell::Discussion(_)
335            | Cell::Divider(_)
336            | Cell::Graph(_)
337            | Cell::Image(_)
338            | Cell::Log(_)
339            | Cell::Provider(_)
340            | Cell::Table(_)
341            | Cell::Timeline(_) => None,
342        }
343    }
344
345    /// Returns a mutable reference to the cell's text, if any.
346    pub fn text_mut(&mut self) -> Option<&mut String> {
347        match self {
348            Cell::Checkbox(cell) => Some(&mut cell.content),
349            Cell::Code(cell) => Some(&mut cell.content),
350            Cell::Discussion(_) => None,
351            Cell::Divider(_) => None,
352            Cell::Image(_) => None,
353            Cell::Graph(_) => None,
354            Cell::Heading(cell) => Some(&mut cell.content),
355            Cell::ListItem(cell) => Some(&mut cell.content),
356            Cell::Log(_) => None,
357            Cell::Provider(_) => None,
358            Cell::Table(_) => None,
359            Cell::Text(cell) => Some(&mut cell.content),
360            Cell::Timeline(_) => None,
361        }
362    }
363
364    /// Returns the cell type as a string
365    pub fn type_str(&self) -> &str {
366        match self {
367            Cell::Checkbox(_) => "checkbox",
368            Cell::Code(_) => "code",
369            Cell::Discussion(_) => "discussion",
370            Cell::Divider(_) => "divider",
371            Cell::Graph(_) => "graph",
372            Cell::Heading(_) => "heading",
373            Cell::Image(_) => "image",
374            Cell::ListItem(_) => "list item",
375            Cell::Log(_) => "log",
376            Cell::Provider(cell) => &cell.intent,
377            Cell::Table(_) => "table",
378            Cell::Text(_) => "text",
379            Cell::Timeline(_) => "timeline",
380        }
381    }
382}
383
384#[derive(Clone, Debug, Default, Deserialize, PartialEq, Eq, Serialize, TypedBuilder)]
385#[cfg_attr(
386    feature = "fp-bindgen",
387    derive(Serializable),
388    fp(rust_module = "fiberplane_models::notebooks")
389)]
390#[non_exhaustive]
391#[serde(rename_all = "camelCase")]
392pub struct CheckboxCell {
393    #[builder(default, setter(into))]
394    pub id: String,
395
396    #[builder(default)]
397    pub checked: bool,
398
399    #[builder(default, setter(into))]
400    pub content: String,
401
402    /// Optional formatting to be applied to the cell's content.
403    #[builder(default)]
404    #[serde(default, skip_serializing_if = "Formatting::is_empty")]
405    pub formatting: Formatting,
406
407    #[builder(default, setter(strip_option))]
408    #[serde(default, skip_serializing_if = "Option::is_none")]
409    pub level: Option<u8>,
410
411    #[builder(default, setter(strip_option))]
412    #[serde(default, skip_serializing_if = "Option::is_none")]
413    pub read_only: Option<bool>,
414}
415
416#[derive(Clone, Debug, Default, Deserialize, PartialEq, Eq, Serialize, TypedBuilder)]
417#[cfg_attr(
418    feature = "fp-bindgen",
419    derive(Serializable),
420    fp(rust_module = "fiberplane_models::notebooks")
421)]
422#[non_exhaustive]
423#[serde(rename_all = "camelCase")]
424pub struct CodeCell {
425    #[builder(default, setter(into))]
426    pub id: String,
427
428    #[builder(default, setter(into))]
429    pub content: String,
430
431    #[builder(default, setter(strip_option))]
432    #[serde(default, skip_serializing_if = "Option::is_none")]
433    pub read_only: Option<bool>,
434
435    /// Optional MIME type to use for syntax highlighting.
436    #[builder(default, setter(into, strip_option))]
437    #[serde(default, skip_serializing_if = "Option::is_none")]
438    pub syntax: Option<String>,
439}
440
441#[derive(Clone, Debug, Default, Deserialize, PartialEq, Eq, Serialize, TypedBuilder)]
442#[cfg_attr(
443    feature = "fp-bindgen",
444    derive(Serializable),
445    fp(rust_module = "fiberplane_models::notebooks")
446)]
447#[non_exhaustive]
448#[serde(rename_all = "camelCase")]
449pub struct DividerCell {
450    #[builder(default, setter(into))]
451    pub id: String,
452
453    #[builder(default, setter(strip_option))]
454    #[serde(default, skip_serializing_if = "Option::is_none")]
455    pub read_only: Option<bool>,
456}
457
458#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize, TypedBuilder)]
459#[cfg_attr(
460    feature = "fp-bindgen",
461    derive(Serializable),
462    fp(rust_module = "fiberplane_models::notebooks")
463)]
464#[non_exhaustive]
465#[serde(rename_all = "camelCase")]
466pub struct GraphCell {
467    #[builder(default, setter(into))]
468    pub id: String,
469
470    /// Links to the data to render in the graph.
471    #[builder(default)]
472    #[serde(default, skip_serializing_if = "Vec::is_empty")]
473    pub data_links: Vec<String>,
474
475    pub graph_type: GraphType,
476
477    #[builder(default, setter(strip_option))]
478    #[serde(default, skip_serializing_if = "Option::is_none")]
479    pub read_only: Option<bool>,
480
481    #[builder(default)]
482    pub stacking_type: StackingType,
483}
484
485#[derive(Clone, Debug, Default, Deserialize, PartialEq, Eq, Serialize, TypedBuilder)]
486#[cfg_attr(
487    feature = "fp-bindgen",
488    derive(Serializable),
489    fp(rust_module = "fiberplane_models::notebooks")
490)]
491#[non_exhaustive]
492#[serde(rename_all = "camelCase")]
493pub struct HeadingCell {
494    #[builder(default, setter(into))]
495    pub id: String,
496
497    pub heading_type: HeadingType,
498
499    #[builder(default, setter(into))]
500    pub content: String,
501
502    /// Optional formatting to be applied to the cell's content.
503    #[builder(default)]
504    #[serde(default, skip_serializing_if = "Formatting::is_empty")]
505    pub formatting: Formatting,
506
507    #[builder(default, setter(strip_option))]
508    #[serde(skip_serializing_if = "Option::is_none")]
509    pub read_only: Option<bool>,
510}
511
512#[derive(Clone, Debug, Default, Deserialize, PartialEq, Eq, Serialize, TypedBuilder)]
513#[cfg_attr(
514    feature = "fp-bindgen",
515    derive(Serializable),
516    fp(rust_module = "fiberplane_models::notebooks")
517)]
518#[non_exhaustive]
519#[serde(rename_all = "camelCase")]
520pub struct LogCell {
521    #[builder(default, setter(into))]
522    pub id: String,
523
524    /// Links to the data to render in the log.
525    #[builder(default)]
526    #[serde(default, skip_serializing_if = "Vec::is_empty")]
527    pub data_links: Vec<String>,
528
529    #[builder(default, setter(strip_option))]
530    #[serde(default, skip_serializing_if = "Option::is_none")]
531    pub read_only: Option<bool>,
532
533    #[builder(default, setter(strip_option))]
534    #[serde(default, skip_serializing_if = "Option::is_none")]
535    pub display_fields: Option<Vec<String>>,
536
537    #[builder(default, setter(strip_option))]
538    #[serde(default, skip_serializing_if = "Option::is_none")]
539    pub hide_similar_values: Option<bool>,
540
541    #[builder(default, setter(strip_option))]
542    #[serde(default, skip_serializing_if = "Option::is_none")]
543    pub expanded_indices: Option<Vec<LogRecordIndex>>,
544
545    #[builder(default, setter(strip_option))]
546    #[serde(default, skip_serializing_if = "Option::is_none")]
547    pub visibility_filter: Option<LogVisibilityFilter>,
548
549    #[builder(default, setter(strip_option))]
550    #[serde(default, skip_serializing_if = "Option::is_none")]
551    pub selected_indices: Option<Vec<LogRecordIndex>>,
552
553    #[builder(default, setter(strip_option))]
554    #[serde(default, skip_serializing_if = "Option::is_none")]
555    pub highlighted_indices: Option<Vec<LogRecordIndex>>,
556}
557
558#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize)]
559#[cfg_attr(
560    feature = "fp-bindgen",
561    derive(Serializable),
562    fp(rust_module = "fiberplane_models::notebooks")
563)]
564#[non_exhaustive]
565#[serde(rename_all = "snake_case")]
566pub enum LogVisibilityFilter {
567    All,
568    Selected,
569    Highlighted,
570}
571
572/// A single expanded row of log records, as identified by [key] and [index]
573/// pointing into the source data of the LogCell.
574#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize, TypedBuilder)]
575#[cfg_attr(
576    feature = "fp-bindgen",
577    derive(Serializable),
578    fp(rust_module = "fiberplane_models::notebooks")
579)]
580#[non_exhaustive]
581#[serde(rename_all = "camelCase")]
582pub struct LogRecordIndex {
583    /// Index of the data link that produced the log record.
584    pub link_index: u8,
585
586    /// Index of the record within the data of a single data link.
587    pub record_index: u32,
588}
589
590#[derive(Clone, Debug, Default, Deserialize, PartialEq, Eq, Serialize, TypedBuilder)]
591#[cfg_attr(
592    feature = "fp-bindgen",
593    derive(Serializable),
594    fp(rust_module = "fiberplane_models::notebooks")
595)]
596#[non_exhaustive]
597#[serde(rename_all = "camelCase")]
598pub struct ListItemCell {
599    #[builder(default, setter(into))]
600    pub id: String,
601
602    #[builder(default, setter(into))]
603    pub content: String,
604
605    /// Optional formatting to be applied to the cell's content.
606    #[builder(default)]
607    #[serde(default, skip_serializing_if = "Formatting::is_empty")]
608    pub formatting: Formatting,
609
610    #[builder(default)]
611    pub list_type: ListType,
612
613    #[builder(default, setter(strip_option))]
614    #[serde(skip_serializing_if = "Option::is_none")]
615    pub level: Option<u8>,
616
617    #[builder(default, setter(strip_option))]
618    #[serde(skip_serializing_if = "Option::is_none")]
619    pub read_only: Option<bool>,
620
621    #[builder(default, setter(strip_option))]
622    #[serde(skip_serializing_if = "Option::is_none")]
623    pub start_number: Option<u16>,
624}
625
626#[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize, TypedBuilder)]
627#[cfg_attr(
628    feature = "fp-bindgen",
629    derive(Serializable),
630    fp(rust_module = "fiberplane_models::notebooks")
631)]
632#[non_exhaustive]
633#[serde(rename_all = "camelCase")]
634pub struct ProviderCell {
635    #[builder(default, setter(into))]
636    pub id: String,
637
638    /// The intent served by this provider cell.
639    ///
640    /// See: https://www.notion.so/fiberplane/RFC-45-Provider-Protocol-2-0-Revised-4ec85a0233924b2db0010d8cdae75e16#c8ed5dfbfd764e6bbd5c5b79333f9d6e
641    #[builder(default, setter(into))]
642    pub intent: String,
643
644    /// Query data encoded as `"<mime-type>,<data>"`, where the MIME type is
645    /// either `"application/x-www-form-urlencoded"` or `"multipart/form-data"`.
646    /// This is used for storing data for the Query Builder.
647    ///
648    /// Note: The format follows the specification for data URLs, without the
649    ///       `data:` prefix. See: https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/Data_URLs
650    #[builder(default, setter(into, strip_option))]
651    #[serde(default, skip_serializing_if = "Option::is_none")]
652    pub query_data: Option<String>,
653
654    /// Optional response data from the provider.
655    #[builder(default, setter(strip_option))]
656    #[serde(default, skip_serializing_if = "Option::is_none")]
657    pub response: Option<EncodedBlob>,
658
659    /// Optional list of generated output cells.
660    #[builder(default, setter(strip_option))]
661    #[serde(default, skip_serializing_if = "Option::is_none")]
662    pub output: Option<Vec<Cell>>,
663
664    #[builder(default, setter(strip_option))]
665    #[serde(default, skip_serializing_if = "Option::is_none")]
666    pub read_only: Option<bool>,
667}
668
669impl ProviderCell {
670    /// Returns a clone of the provider cell, with the query data updated for
671    /// the given query field.
672    ///
673    /// Unsets the query field if the value is empty.
674    pub fn with_query_field(&self, field_name: impl AsRef<str>, value: impl AsRef<str>) -> Self {
675        let query_data = self.query_data.as_deref().unwrap_or_default();
676        let query_data = if value.as_ref().is_empty() {
677            unset_query_field(query_data, field_name)
678        } else {
679            set_query_field(query_data, field_name, value)
680        };
681        Self {
682            query_data: if has_query_data(&query_data) {
683                Some(query_data)
684            } else {
685                None
686            },
687            ..self.clone()
688        }
689    }
690}
691
692#[derive(Clone, Debug, Default, Deserialize, PartialEq, Eq, Serialize, TypedBuilder)]
693#[cfg_attr(
694    feature = "fp-bindgen",
695    derive(Serializable),
696    fp(rust_module = "fiberplane_models::notebooks")
697)]
698#[non_exhaustive]
699#[serde(rename_all = "camelCase")]
700pub struct TextCell {
701    #[builder(default, setter(into))]
702    pub id: String,
703
704    #[builder(default, setter(into))]
705    pub content: String,
706
707    /// Optional formatting to be applied to the cell's content.
708    #[builder(default)]
709    #[serde(default, skip_serializing_if = "Formatting::is_empty")]
710    pub formatting: Formatting,
711
712    #[builder(default, setter(strip_option))]
713    #[serde(skip_serializing_if = "Option::is_none")]
714    pub read_only: Option<bool>,
715}
716
717#[derive(Clone, Debug, Default, Deserialize, PartialEq, Eq, Serialize, TypedBuilder)]
718#[cfg_attr(
719    feature = "fp-bindgen",
720    derive(Serializable),
721    fp(rust_module = "fiberplane_models::notebooks")
722)]
723#[serde(rename_all = "camelCase")]
724pub struct TimelineCell {
725    #[builder(default, setter(into))]
726    pub id: String,
727
728    /// Links to the data to render in the timeline.
729    #[builder(default)]
730    #[serde(default, skip_serializing_if = "Vec::is_empty")]
731    pub data_links: Vec<String>,
732
733    #[builder(default, setter(strip_option))]
734    #[serde(skip_serializing_if = "Option::is_none")]
735    pub read_only: Option<bool>,
736}
737
738#[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize, TypedBuilder)]
739#[cfg_attr(
740    feature = "fp-bindgen",
741    derive(Serializable),
742    fp(rust_module = "fiberplane_models::notebooks")
743)]
744#[non_exhaustive]
745#[serde(rename_all = "camelCase")]
746pub struct ImageCell {
747    #[builder(default, setter(into))]
748    pub id: String,
749
750    // Refers to the id for a file (used to retrieve the file)
751    #[builder(default, setter(into, strip_option))]
752    #[serde(skip_serializing_if = "Option::is_none")]
753    pub file_id: Option<String>,
754
755    /// Used to indicates the upload progress.
756    /// If file_id is set this shouldn't be set
757    /// Also: if no progress is set and no file_id exists
758    /// it means the cell is in the initial state (ready for upload)
759    #[builder(default, setter(strip_option))]
760    #[serde(skip_serializing_if = "Option::is_none")]
761    pub progress: Option<f64>,
762
763    #[builder(default, setter(strip_option))]
764    #[serde(skip_serializing_if = "Option::is_none")]
765    pub read_only: Option<bool>,
766
767    #[builder(default, setter(strip_option))]
768    #[serde(skip_serializing_if = "Option::is_none")]
769    pub width: Option<i32>,
770
771    #[builder(default, setter(strip_option))]
772    #[serde(skip_serializing_if = "Option::is_none")]
773    pub height: Option<i32>,
774
775    /// Will contain a hash to show as a preview for the image
776    #[builder(default, setter(into, strip_option))]
777    #[serde(skip_serializing_if = "Option::is_none")]
778    pub preview: Option<String>,
779
780    /// URL of the image if it was originally hosted on a remote server.
781    /// This will not be set if the image was uploaded through the
782    /// Fiberplane Studio.
783    #[builder(default, setter(into, strip_option))]
784    #[serde(skip_serializing_if = "Option::is_none")]
785    pub url: Option<String>,
786}
787
788#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize, TypedBuilder)]
789#[cfg_attr(
790    feature = "fp-bindgen",
791    derive(Serializable),
792    fp(rust_module = "fiberplane_models::notebooks")
793)]
794#[non_exhaustive]
795#[serde(rename_all = "camelCase")]
796pub struct DiscussionCell {
797    #[builder(default, setter(into))]
798    pub id: String,
799
800    #[builder(default, setter(into))]
801    pub thread_id: String,
802
803    #[builder(default, setter(strip_option))]
804    #[serde(default, skip_serializing_if = "Option::is_none")]
805    pub read_only: Option<bool>,
806}
807
808#[derive(Clone, Copy, Debug, Deserialize, PartialEq, Eq, Serialize)]
809#[cfg_attr(
810    feature = "fp-bindgen",
811    derive(Serializable),
812    fp(rust_module = "fiberplane_models::notebooks")
813)]
814#[non_exhaustive]
815#[serde(rename_all = "snake_case")]
816pub enum GraphType {
817    Bar,
818    Line,
819}
820
821#[derive(Clone, Copy, Debug, Default, Deserialize, PartialEq, Eq, Serialize)]
822#[cfg_attr(
823    feature = "fp-bindgen",
824    derive(Serializable),
825    fp(rust_module = "fiberplane_models::notebooks")
826)]
827#[non_exhaustive]
828#[serde(rename_all = "snake_case")]
829pub enum StackingType {
830    #[default]
831    None,
832    Stacked,
833    Percentage,
834}
835
836#[derive(Clone, Copy, Debug, Default, Deserialize, PartialEq, Eq, Serialize)]
837#[cfg_attr(
838    feature = "fp-bindgen",
839    derive(Serializable),
840    fp(rust_module = "fiberplane_models::notebooks")
841)]
842#[non_exhaustive]
843#[serde(rename_all = "snake_case")]
844pub enum HeadingType {
845    #[default]
846    H1,
847    H2,
848    H3,
849}
850
851#[derive(Clone, Copy, Debug, Default, Deserialize, PartialEq, Eq, Serialize)]
852#[cfg_attr(
853    feature = "fp-bindgen",
854    derive(Serializable),
855    fp(rust_module = "fiberplane_models::notebooks")
856)]
857#[non_exhaustive]
858#[serde(rename_all = "snake_case")]
859pub enum ListType {
860    Ordered,
861    #[default]
862    Unordered,
863}