From 808662e3d2faecc776283643ca6c8f4b9aed3ff4 Mon Sep 17 00:00:00 2001 From: Keavon Chambers Date: Mon, 3 Aug 2026 04:31:48 -0700 Subject: [PATCH] Add a drill-in button beside Data panel gradient preview widgets (#4398) --- .../messages/layout/layout_message_handler.rs | 35 ++-- .../layout/utility_types/layout_widget.rs | 50 +++-- .../data_panel/data_panel_message_handler.rs | 175 ++++++++++-------- .../src/components/widgets/WidgetTable.svelte | 4 +- frontend/src/utility-functions/widgets.ts | 2 +- 5 files changed, 153 insertions(+), 113 deletions(-) diff --git a/editor/src/messages/layout/layout_message_handler.rs b/editor/src/messages/layout/layout_message_handler.rs index 752b0e4818..ccfc0c062e 100644 --- a/editor/src/messages/layout/layout_message_handler.rs +++ b/editor/src/messages/layout/layout_message_handler.rs @@ -117,23 +117,26 @@ impl LayoutMessageHandler { } LayoutGroup::Table(WidgetTable { rows, .. }) => { for (row_index, row) in rows.iter().enumerate() { - for (value_index, value) in row.iter().enumerate() { - // Return if this is the correct ID - if value.widget_id == widget_id { - widget_path.push(row_index); - widget_path.push(value_index); - return Some((value, widget_path)); - } + for (cell_index, cell) in row.iter().enumerate() { + for (value_index, value) in cell.iter().enumerate() { + // Return if this is the correct ID + if value.widget_id == widget_id { + widget_path.push(row_index); + widget_path.push(cell_index); + widget_path.push(value_index); + return Some((value, widget_path)); + } - if let Widget::PopoverButton(popover) = &*value.widget { - stack.extend( - popover - .popover_layout - .0 - .iter() - .enumerate() - .map(|(child, val)| ([widget_path.as_slice(), &[row_index, value_index, child]].concat(), val)), - ); + if let Widget::PopoverButton(popover) = &*value.widget { + stack.extend( + popover + .popover_layout + .0 + .iter() + .enumerate() + .map(|(child, val)| ([widget_path.as_slice(), &[row_index, cell_index, value_index, child]].concat(), val)), + ); + } } } } diff --git a/editor/src/messages/layout/utility_types/layout_widget.rs b/editor/src/messages/layout/utility_types/layout_widget.rs index 76e2a18c5e..54b55cc719 100644 --- a/editor/src/messages/layout/utility_types/layout_widget.rs +++ b/editor/src/messages/layout/utility_types/layout_widget.rs @@ -273,7 +273,7 @@ impl<'a> Iterator for WidgetIter<'a> { self.next() } Some(LayoutGroup::Table(WidgetTable { rows, .. })) => { - self.table.extend(rows.iter().flatten().rev()); + self.table.extend(rows.iter().flatten().flatten().rev()); self.next() } Some(LayoutGroup::Section(WidgetSection { layout, .. })) => { @@ -325,7 +325,7 @@ impl<'a> Iterator for WidgetIterMut<'a> { self.next() } Some(LayoutGroup::Table(WidgetTable { rows, .. })) => { - self.table.extend(rows.iter_mut().flatten().rev()); + self.table.extend(rows.iter_mut().flatten().flatten().rev()); self.next() } Some(LayoutGroup::Section(WidgetSection { layout, .. })) => { @@ -365,8 +365,9 @@ pub struct WidgetRow { #[cfg_attr(feature = "wasm", derive(tsify::Tsify))] #[derive(Default, Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)] pub struct WidgetTable { + /// Rows of cells, each cell holding one or more widgets laid out inline. #[serde(rename = "tableWidgets")] - pub rows: Vec>, + pub rows: Vec>>, pub unstyled: bool, } @@ -412,7 +413,14 @@ impl LayoutGroup { Self::Column(WidgetColumn { widgets }) } + /// A table with one widget per cell. pub fn table(rows: Vec>, unstyled: bool) -> Self { + let rows = rows.into_iter().map(|row| row.into_iter().map(|widget| vec![widget]).collect()).collect(); + Self::Table(WidgetTable { rows, unstyled }) + } + + /// A table whose cells may each hold multiple widgets laid out inline. + pub fn table_of_cells(rows: Vec>>, unstyled: bool) -> Self { Self::Table(WidgetTable { rows, unstyled }) } @@ -600,13 +608,17 @@ impl Diffable for LayoutGroup { } } Self::Table(WidgetTable { rows, .. }) => { - for (row_idx, row) in rows.iter().enumerate() { - for (col_idx, widget) in row.iter().enumerate() { - widget_path.push(row_idx); - widget_path.push(col_idx); - widget.collect_checkbox_ids(layout_target, widget_path, checkbox_map); - widget_path.pop(); - widget_path.pop(); + for (row_index, row) in rows.iter().enumerate() { + for (column_index, cell) in row.iter().enumerate() { + for (widget_index, widget) in cell.iter().enumerate() { + widget_path.push(row_index); + widget_path.push(column_index); + widget_path.push(widget_index); + widget.collect_checkbox_ids(layout_target, widget_path, checkbox_map); + widget_path.pop(); + widget_path.pop(); + widget_path.pop(); + } } } } @@ -626,13 +638,17 @@ impl Diffable for LayoutGroup { } } Self::Table(WidgetTable { rows, .. }) => { - for (row_idx, row) in rows.iter_mut().enumerate() { - for (col_idx, widget) in row.iter_mut().enumerate() { - widget_path.push(row_idx); - widget_path.push(col_idx); - widget.replace_widget_ids(layout_target, widget_path, checkbox_map); - widget_path.pop(); - widget_path.pop(); + for (row_index, row) in rows.iter_mut().enumerate() { + for (column_index, cell) in row.iter_mut().enumerate() { + for (widget_index, widget) in cell.iter_mut().enumerate() { + widget_path.push(row_index); + widget_path.push(column_index); + widget_path.push(widget_index); + widget.replace_widget_ids(layout_target, widget_path, checkbox_map); + widget_path.pop(); + widget_path.pop(); + widget_path.pop(); + } } } } diff --git a/editor/src/messages/portfolio/document/data_panel/data_panel_message_handler.rs b/editor/src/messages/portfolio/document/data_panel/data_panel_message_handler.rs index 1bea3a4d1e..e3eda6c7dc 100644 --- a/editor/src/messages/portfolio/document/data_panel/data_panel_message_handler.rs +++ b/editor/src/messages/portfolio/document/data_panel/data_panel_message_handler.rs @@ -307,6 +307,10 @@ fn column_headings(value: &[&str]) -> Vec { value.iter().map(|text| TextLabel::new(*text).widget_instance()).collect() } +fn single_widget_cells(widgets: Vec) -> Vec> { + widgets.into_iter().map(|widget| vec![widget]).collect() +} + fn label(x: impl Into) -> Vec { let error = vec![TextLabel::new(x).widget_instance()]; vec![LayoutGroup::row(error)] @@ -319,18 +323,20 @@ trait TableItemLayout { data.breadcrumbs.push(self.identifier()); self.value_page(data) } - /// Renders this value as a single inline widget inside an item of a `List`. - /// `target` is the [`PathStep`] to push when the widget is clicked to drill into the value. + /// Renders this value as the inline widgets filling one table cell inside an item of a `List`. + /// `target` is the [`PathStep`] to push when a widget is clicked to drill into the value. /// `data` provides shared context (notably `network_interface`) for types whose label or content /// depends on lookup beyond their own value (e.g. `NodeId` resolving a node's display name). - /// The default is a button labeled with `identifier()`. Types whose values are best shown - /// inline (colors, transforms, primitives, etc.) override this to ignore `target` and - /// return a richer non-navigating widget. - fn value_widget(&self, target: PathStep, _data: &LayoutData) -> WidgetInstance { - TextButton::new(self.identifier()) - .on_update(move |_| DataPanelMessage::PushToElementPath { step: target.clone() }.into()) - .narrow(true) - .widget_instance() + /// The default is a single button labeled with `identifier()`. Types whose values are best shown + /// inline (colors, transforms, primitives, etc.) override this to ignore `target` and return a + /// richer non-navigating widget, optionally joined by companions like a drill-in button. + fn value_widgets(&self, target: PathStep, _data: &LayoutData) -> Vec { + vec![ + TextButton::new(self.identifier()) + .on_update(move |_| DataPanelMessage::PushToElementPath { step: target.clone() }.into()) + .narrow(true) + .widget_instance(), + ] } fn value_page(&self, _data: &mut LayoutData) -> Vec { vec![] @@ -371,20 +377,20 @@ impl TableItemLayout for Item { let attribute_keys: Vec = self.attributes().keys().map(str::to_string).collect(); // A single element, so no leading ID column, unlike the `List` table - let mut values = vec![self.element().value_widget(PathStep::Element(0), data)]; + let mut values = vec![self.element().value_widgets(PathStep::Element(0), data)]; for key in &attribute_keys { let target = PathStep::Attribute { row: 0, key: key.clone() }; - let widget = self.attributes().get_any(key).and_then(|any| dispatch_value_widget(any, target, data)).unwrap_or_else(|| { + let cell = self.attributes().get_any(key).and_then(|any| dispatch_value_widgets(any, target, data)).unwrap_or_else(|| { let text = self.attributes().display_value(key, display_value_override).unwrap_or_else(|| "-".to_string()); - TextLabel::new(text).narrow(true).widget_instance() + vec![TextLabel::new(text).narrow(true).widget_instance()] }); - values.push(widget); + values.push(cell); } let mut column_names = vec!["element"]; column_names.extend(attribute_keys.iter().map(|s| s.as_str())); - vec![LayoutGroup::table(vec![column_headings(&column_names), values], false)] + vec![LayoutGroup::table_of_cells(vec![single_widget_cells(column_headings(&column_names)), values], false)] } } @@ -429,14 +435,17 @@ impl TableItemLayout for List { let mut rows = (0..self.len()) .map(|index| { let element = self.element(index).unwrap(); - let mut values = vec![TextLabel::new(format!("{index}")).narrow(true).widget_instance(), element.value_widget(PathStep::Element(index), data)]; + let mut values = vec![ + vec![TextLabel::new(format!("{index}")).narrow(true).widget_instance()], + element.value_widgets(PathStep::Element(index), data), + ]; for key in &attribute_keys { let target = PathStep::Attribute { row: index, key: key.clone() }; - let widget = self.attribute_any(key, index).and_then(|any| dispatch_value_widget(any, target, data)).unwrap_or_else(|| { + let cell = self.attribute_any(key, index).and_then(|any| dispatch_value_widgets(any, target, data)).unwrap_or_else(|| { let text = self.attribute_display_value(key, index, display_value_override).unwrap_or_else(|| "-".to_string()); - TextLabel::new(text).narrow(true).widget_instance() + vec![TextLabel::new(text).narrow(true).widget_instance()] }); - values.push(widget); + values.push(cell); } values }) @@ -444,9 +453,9 @@ impl TableItemLayout for List { let mut column_names = vec!["", "element"]; column_names.extend(attribute_keys.iter().map(|s| s.as_str())); - rows.insert(0, column_headings(&column_names)); + rows.insert(0, single_widget_cells(column_headings(&column_names))); - vec![LayoutGroup::table(rows, false)] + vec![LayoutGroup::table_of_cells(rows, false)] } } @@ -478,8 +487,8 @@ impl TableItemLayout for DashPattern { self.value_page(data) } // Label the spreadsheet's element button with the inner list's identifier, like Artboard - fn value_widget(&self, target: PathStep, data: &LayoutData) -> WidgetInstance { - self.0.value_widget(target, data) + fn value_widgets(&self, target: PathStep, data: &LayoutData) -> Vec { + self.0.value_widgets(target, data) } fn value_page(&self, data: &mut LayoutData) -> Vec { self.0.layout_with_breadcrumb(data) @@ -498,8 +507,8 @@ impl TableItemLayout for BoxCorners { self.value_page(data) } // Label the spreadsheet's element button with the inner list's identifier, like Artboard - fn value_widget(&self, target: PathStep, data: &LayoutData) -> WidgetInstance { - self.0.value_widget(target, data) + fn value_widgets(&self, target: PathStep, data: &LayoutData) -> Vec { + self.0.value_widgets(target, data) } fn value_page(&self, data: &mut LayoutData) -> Vec { self.0.layout_with_breadcrumb(data) @@ -709,16 +718,17 @@ impl TableItemLayout for Color { fn identifier(&self) -> String { format!("Color (#{})", SRGBA8::from(*self).to_rgba_hex()) } - fn value_widget(&self, _target: PathStep, _data: &LayoutData) -> WidgetInstance { - ColorInput::new(FillChoiceUI::from(&FillChoice::Solid(*self))) - .disabled(true) - .menu_direction(Some(MenuDirection::Top)) - .narrow(true) - .widget_instance() + fn value_widgets(&self, _target: PathStep, _data: &LayoutData) -> Vec { + vec![ + ColorInput::new(FillChoiceUI::from(&FillChoice::Solid(*self))) + .disabled(true) + .menu_direction(Some(MenuDirection::Top)) + .narrow(true) + .widget_instance(), + ] } fn value_page(&self, _data: &mut LayoutData) -> Vec { - let widgets = vec![self.value_widget(PathStep::Element(0), _data)]; - vec![LayoutGroup::row(widgets)] + vec![LayoutGroup::row(self.value_widgets(PathStep::Element(0), _data))] } } @@ -729,16 +739,27 @@ impl TableItemLayout for Gradient { fn identifier(&self) -> String { format!("Gradient ({} stops)", self.len()) } - fn value_widget(&self, _target: PathStep, _data: &LayoutData) -> WidgetInstance { - ColorInput::new(FillChoiceUI::from(&FillChoice::Gradient(self.clone()))) - .menu_direction(Some(MenuDirection::Top)) - .disabled(true) - .narrow(true) - .widget_instance() + // The wrapping `Item` already contributes the breadcrumb; the inner list supplies the next level + fn layout_with_breadcrumb(&self, data: &mut LayoutData) -> Vec { + self.value_page(data) } - fn value_page(&self, _data: &mut LayoutData) -> Vec { - let widgets = vec![self.value_widget(PathStep::Element(0), _data)]; - vec![LayoutGroup::row(widgets)] + // The preview widget doesn't navigate, so a drill-in button beside it opens the newtype's underlying color list + fn value_widgets(&self, target: PathStep, _data: &LayoutData) -> Vec { + vec![ + TextButton::new(self.as_color_list().identifier()) + .on_update(move |_| DataPanelMessage::PushToElementPath { step: target.clone() }.into()) + .narrow(true) + .widget_instance(), + Separator::new(SeparatorStyle::Related).widget_instance(), + ColorInput::new(FillChoiceUI::from(&FillChoice::Gradient(self.clone()))) + .menu_direction(Some(MenuDirection::Top)) + .disabled(true) + .narrow(true) + .widget_instance(), + ] + } + fn value_page(&self, data: &mut LayoutData) -> Vec { + self.as_color_list().layout_with_breadcrumb(data) } } @@ -854,11 +875,11 @@ impl TableItemLayout for bool { fn identifier(&self) -> String { "Bool".to_string() } - fn value_widget(&self, _target: PathStep, _data: &LayoutData) -> WidgetInstance { - CheckboxInput::new(*self).disabled(true).widget_instance() + fn value_widgets(&self, _target: PathStep, _data: &LayoutData) -> Vec { + vec![CheckboxInput::new(*self).disabled(true).widget_instance()] } fn value_page(&self, _data: &mut LayoutData) -> Vec { - vec![LayoutGroup::row(vec![self.value_widget(PathStep::Element(0), _data)])] + vec![LayoutGroup::row(self.value_widgets(PathStep::Element(0), _data))] } } @@ -888,11 +909,11 @@ impl TableItemLayout for Option { fn identifier(&self) -> String { "Option".to_string() } - fn value_widget(&self, _target: PathStep, _data: &LayoutData) -> WidgetInstance { - TextLabel::new(format!("{self:?}")).narrow(true).widget_instance() + fn value_widgets(&self, _target: PathStep, _data: &LayoutData) -> Vec { + vec![TextLabel::new(format!("{self:?}")).narrow(true).widget_instance()] } fn value_page(&self, _data: &mut LayoutData) -> Vec { - vec![LayoutGroup::row(vec![self.value_widget(PathStep::Element(0), _data)])] + vec![LayoutGroup::row(self.value_widgets(PathStep::Element(0), _data))] } } @@ -903,11 +924,11 @@ impl TableItemLayout for DVec2 { fn identifier(&self) -> String { "Vec2".to_string() } - fn value_widget(&self, _target: PathStep, _data: &LayoutData) -> WidgetInstance { - TextLabel::new(format_dvec2(*self)).narrow(true).widget_instance() + fn value_widgets(&self, _target: PathStep, _data: &LayoutData) -> Vec { + vec![TextLabel::new(format_dvec2(*self)).narrow(true).widget_instance()] } fn value_page(&self, _data: &mut LayoutData) -> Vec { - vec![LayoutGroup::row(vec![self.value_widget(PathStep::Element(0), _data)])] + vec![LayoutGroup::row(self.value_widgets(PathStep::Element(0), _data))] } } @@ -918,11 +939,11 @@ impl TableItemLayout for Vec2 { fn identifier(&self) -> String { "Vec2".to_string() } - fn value_widget(&self, _target: PathStep, _data: &LayoutData) -> WidgetInstance { - TextLabel::new(format_dvec2(DVec2::new(self.x as f64, self.y as f64))).narrow(true).widget_instance() + fn value_widgets(&self, _target: PathStep, _data: &LayoutData) -> Vec { + vec![TextLabel::new(format_dvec2(DVec2::new(self.x as f64, self.y as f64))).narrow(true).widget_instance()] } fn value_page(&self, _data: &mut LayoutData) -> Vec { - vec![LayoutGroup::row(vec![self.value_widget(PathStep::Element(0), _data)])] + vec![LayoutGroup::row(self.value_widgets(PathStep::Element(0), _data))] } } @@ -933,11 +954,11 @@ impl TableItemLayout for DAffine2 { fn identifier(&self) -> String { "Transform".to_string() } - fn value_widget(&self, _target: PathStep, _data: &LayoutData) -> WidgetInstance { - TextLabel::new(format_transform_matrix(*self)).narrow(true).widget_instance() + fn value_widgets(&self, _target: PathStep, _data: &LayoutData) -> Vec { + vec![TextLabel::new(format_transform_matrix(*self)).narrow(true).widget_instance()] } fn value_page(&self, _data: &mut LayoutData) -> Vec { - vec![LayoutGroup::row(vec![self.value_widget(PathStep::Element(0), _data)])] + vec![LayoutGroup::row(self.value_widgets(PathStep::Element(0), _data))] } } @@ -948,12 +969,12 @@ impl TableItemLayout for Affine2 { fn identifier(&self) -> String { "Transform".to_string() } - fn value_widget(&self, _target: PathStep, _data: &LayoutData) -> WidgetInstance { + fn value_widgets(&self, _target: PathStep, _data: &LayoutData) -> Vec { let matrix = DAffine2::from_cols_array(&self.to_cols_array().map(|x| x as f64)); - TextLabel::new(format_transform_matrix(matrix)).narrow(true).widget_instance() + vec![TextLabel::new(format_transform_matrix(matrix)).narrow(true).widget_instance()] } fn value_page(&self, _data: &mut LayoutData) -> Vec { - vec![LayoutGroup::row(vec![self.value_widget(PathStep::Element(0), _data)])] + vec![LayoutGroup::row(self.value_widgets(PathStep::Element(0), _data))] } } @@ -968,11 +989,11 @@ macro_rules! impl_table_item_layout_for_choice_enum { fn identifier(&self) -> String { self.to_string() } - fn value_widget(&self, _target: PathStep, _data: &LayoutData) -> WidgetInstance { - TextLabel::new(self.to_string()).narrow(true).widget_instance() + fn value_widgets(&self, _target: PathStep, _data: &LayoutData) -> Vec { + vec![TextLabel::new(self.to_string()).narrow(true).widget_instance()] } fn value_page(&self, _data: &mut LayoutData) -> Vec { - vec![LayoutGroup::row(vec![self.value_widget(PathStep::Element(0), _data)])] + vec![LayoutGroup::row(self.value_widgets(PathStep::Element(0), _data))] } } )* @@ -1022,11 +1043,11 @@ impl TableItemLayout for ReferencePoint { fn identifier(&self) -> String { format!("{self:?}") } - fn value_widget(&self, _target: PathStep, _data: &LayoutData) -> WidgetInstance { - TextLabel::new(self.identifier()).narrow(true).widget_instance() + fn value_widgets(&self, _target: PathStep, _data: &LayoutData) -> Vec { + vec![TextLabel::new(self.identifier()).narrow(true).widget_instance()] } fn value_page(&self, _data: &mut LayoutData) -> Vec { - vec![LayoutGroup::row(vec![self.value_widget(PathStep::Element(0), _data)])] + vec![LayoutGroup::row(self.value_widgets(PathStep::Element(0), _data))] } } @@ -1056,7 +1077,7 @@ impl TableItemLayout for NodeId { // in the Node Graph / Layers panels. The lookup uses `data.node_lookup_network_path` (set by the enclosing // `List` if rendering a path) so the resolution succeeds at any nesting depth. The button's icon // signals layer-vs-node kind. Falls back to "Node {id}" with no icon if the lookup misses. - fn value_widget(&self, target: PathStep, data: &LayoutData) -> WidgetInstance { + fn value_widgets(&self, target: PathStep, data: &LayoutData) -> Vec { let label = node_id_display_label(*self, data.network_interface, &data.node_lookup_network_path); let mut button = TextButton::new(label) .on_update(move |_| DataPanelMessage::PushToElementPath { step: target.clone() }.into()) @@ -1065,7 +1086,7 @@ impl TableItemLayout for NodeId { let icon = if data.network_interface.is_layer(self, &data.node_lookup_network_path) { "Layer" } else { "Node" }; button = button.icon(icon); } - button.widget_instance() + vec![button.widget_instance()] } // The value page shows the node's kind, name (editable), lock/visibility toggles, and a "Select Layer/Node" action button. fn value_page(&self, data: &mut LayoutData) -> Vec { @@ -1240,20 +1261,20 @@ fn display_value_override(any: &dyn Any) -> Option { None } -/// Type-dispatched widget for displaying an attribute value in a `List` item. -/// Delegates to [`TableItemLayout::value_widget`] so the same widget code is shared between +/// Type-dispatched cell widgets for displaying an attribute value in a `List` item. +/// Delegates to [`TableItemLayout::value_widgets`] so the same widget code is shared between /// element-column rendering and attribute-column rendering. Returns `None` for unrecognized /// types so the caller can fall back to a debug-formatted [`TextLabel`]. -fn dispatch_value_widget(any: &dyn Any, target: PathStep, data: &LayoutData) -> Option { +fn dispatch_value_widgets(any: &dyn Any, target: PathStep, data: &LayoutData) -> Option> { // `NodeIdPath` (e.g. the `editor:layer_path` attribute) drills into its inner path list, matching `drilldown_attribute_layout`. if let Some(path) = any.downcast_ref::() { - return Some(path.0.value_widget(target, data)); + return Some(path.0.value_widgets(target, data)); } macro_rules! check { ( $($ty:ty),* $(,)? ) => { $( if let Some(value) = any.downcast_ref::<$ty>() { - return Some(value.value_widget(target, data)); + return Some(value.value_widgets(target, data)); } )* }; @@ -1291,14 +1312,14 @@ fn table_node_id_path_layout_with_breadcrumb(path: &List, data: &mut Lay let node_id = path.element(index).unwrap(); let prefix: Vec = path.iter_element_values().take(index).copied().collect(); let saved = std::mem::replace(&mut data.node_lookup_network_path, prefix); - let widget = node_id.value_widget(PathStep::Element(index), data); + let widgets = node_id.value_widgets(PathStep::Element(index), data); data.node_lookup_network_path = saved; - vec![TextLabel::new(format!("{index}")).narrow(true).widget_instance(), widget] + vec![vec![TextLabel::new(format!("{index}")).narrow(true).widget_instance()], widgets] }) .collect::>(); - rows.insert(0, column_headings(&["", "element"])); + rows.insert(0, single_widget_cells(column_headings(&["", "element"]))); - vec![LayoutGroup::table(rows, false)] + vec![LayoutGroup::table_of_cells(rows, false)] } /// Type-dispatched recursion into an attribute value for the Data panel breadcrumb navigation. diff --git a/frontend/src/components/widgets/WidgetTable.svelte b/frontend/src/components/widgets/WidgetTable.svelte index 54e3c45e44..f20924bbb0 100644 --- a/frontend/src/components/widgets/WidgetTable.svelte +++ b/frontend/src/components/widgets/WidgetTable.svelte @@ -12,9 +12,9 @@ {#each widgetData.tableWidgets as row} - {#each row as widget} + {#each row as cell} - + {/each} diff --git a/frontend/src/utility-functions/widgets.ts b/frontend/src/utility-functions/widgets.ts index 049e2aa846..0a68dfa7e5 100644 --- a/frontend/src/utility-functions/widgets.ts +++ b/frontend/src/utility-functions/widgets.ts @@ -1,6 +1,6 @@ import type { Layout, LayoutGroup, WidgetDiff, WidgetInstance } from "/wrapper/pkg/graphite_wasm_wrapper"; -type UIItem = Layout | LayoutGroup | WidgetInstance[] | WidgetInstance; +type UIItem = Layout | LayoutGroup | WidgetInstance[][] | WidgetInstance[] | WidgetInstance; // Updates a widget layout based on a list of updates, giving the new layout by mutating the `layout` argument export function patchLayout(layout: /* &mut */ Layout, diffs: WidgetDiff[]) { diffs.forEach((update) => {