diff --git a/examples/gallery/pages/devtools/apis.rs b/examples/gallery/pages/devtools/apis.rs index f3c1063..63e8043 100644 --- a/examples/gallery/pages/devtools/apis.rs +++ b/examples/gallery/pages/devtools/apis.rs @@ -156,8 +156,7 @@ pub fn requests(window: &mut Window, cx: &mut App) -> impl IntoElement + use<> { .flex_col() .gap_4() .child( - EnvironmentSelector::new("devtools-environments", environments(), environment) - .on_change(move |key, _, cx| change(&chose, cx, |desk| desk.environment = key.clone())), + EnvironmentSelector::new("devtools-environments", environments(), environment, move |key, _, cx| change(&chose, cx, |desk| desk.environment = key.clone())), ) .child( div() @@ -178,7 +177,11 @@ pub fn requests(window: &mut Window, cx: &mut App) -> impl IntoElement + use<> { }) }) .on_move(move |key, target, at, _, cx| { - change(&dragged, cx, |desk| desk.collection = moved(&desk.collection, key, target, at)) + change(&dragged, cx, |desk| { + if let Some(collection) = moved(&desk.collection, key, target, at) { + desk.collection = collection; + } + }) }), ), ), @@ -255,8 +258,7 @@ pub fn socket(window: &mut Window, cx: &mut App) -> impl IntoElement + use<> { ) .child( div().w(px(620.)).child( - WebSocketConsole::new("devtools-socket", "wss://stream.example.com/orders", state, messages, &draft, clock) - .on_send(move |text, _, cx| { + WebSocketConsole::new("devtools-socket", "wss://stream.example.com/orders", state, messages, &draft, clock, move |text, _, cx| { sent.update(cx, |line, cx| { let at = line.now; line.messages.push(SocketMessage { direction: Direction::Sent, text, at }); @@ -333,8 +335,14 @@ pub fn graphql(window: &mut Window, cx: &mut App) -> impl IntoElement + use<> { fields: vec![("OPEN".into(), "".into()), ("SHIPPED".into(), "".into())], }, ]; - let mut explorer = GraphQLExplorer::new("devtools-graphql", types, &query, &variables, &search) - .on_run(move |_, _, _, cx| change(&ran, cx, |graph| graph.answer = Some(ANSWER.into()))); + let mut explorer = GraphQLExplorer::new( + "devtools-graphql", + types, + &query, + &variables, + &search, + move |_, _, _, cx| change(&ran, cx, |graph| graph.answer = Some(ANSWER.into())), + ); if let Some(answer) = answer { explorer = explorer.answer(answer); } diff --git a/examples/gallery/pages/devtools/db.rs b/examples/gallery/pages/devtools/db.rs index b7909af..04078b0 100644 --- a/examples/gallery/pages/devtools/db.rs +++ b/examples/gallery/pages/devtools/db.rs @@ -270,15 +270,12 @@ pub fn structure(window: &mut Window, cx: &mut App) -> impl IntoElement + use<> .flex_wrap() .gap_6() .child( - div().w(px(660.)).child(TableStructureEditor::new("devtools-structure", fields) - .on_change(move |next, _, cx| change(&edited, cx, |build| build.fields = next)), + div().w(px(660.)).child(TableStructureEditor::new("devtools-structure", fields, move |next, _, cx| change(&edited, cx, |build| build.fields = next)), ), ) .child( div().w(px(300.)).child( - IndexManager::new("devtools-indexes", indexes, names) - .on_drop(move |name, _, cx| change(&dropped, cx, |build| build.indexes.retain(|index| index.name != *name))) - .on_create(move |index, _, cx| change(&created, cx, |build| build.indexes.push(index))), + IndexManager::new("devtools-indexes", indexes, names, move |index, _, cx| change(&created, cx, |build| build.indexes.push(index)), move |name, _, cx| change(&dropped, cx, |build| build.indexes.retain(|index| index.name != *name))), ), ), ) diff --git a/examples/gallery/pages/devtools/tools.rs b/examples/gallery/pages/devtools/tools.rs index 7e700eb..2fece13 100644 --- a/examples/gallery/pages/devtools/tools.rs +++ b/examples/gallery/pages/devtools/tools.rs @@ -35,8 +35,7 @@ pub fn text(window: &mut Window, cx: &mut App) -> impl IntoElement + use<> { .child(div().w(px(380.)).child(Encoder::new("devtools-encoder", "Hello, 世界"))) .child( div().w(px(360.)).child( - ColorContrastChecker::new("devtools-contrast", fore, back) - .on_change(move |fore, back, _, cx| change(&colors, cx, |colors| *colors = (fore, back))), + ColorContrastChecker::new("devtools-contrast", fore, back, move |fore, back, _, cx| change(&colors, cx, |colors| *colors = (fore, back))), ), ), ) diff --git a/src/devtools/collection.rs b/src/devtools/collection.rs index 2b159cc..f06ae50 100644 --- a/src/devtools/collection.rs +++ b/src/devtools/collection.rs @@ -85,7 +85,8 @@ fn placed(items: &mut Vec, item: Saved, target: &str, at: DropAt) -> Resu (DropAt::After, _) => items.insert(ix + 1, item), (DropAt::Inside, Saved::Folder { items, .. }) => items.push(item), (DropAt::Inside, Saved::Request { key, .. }) => { - panic!("collection: {key} is a request, and nothing goes inside it") + log::error!("collection: {key} is a request, and nothing goes inside it"); + return Err(item); } } return Ok(()); @@ -102,15 +103,19 @@ fn placed(items: &mut Vec, item: Saved, target: &str, at: DropAt) -> Resu Err(item) } -/// `items` with `key` moved to `at` of `target`, as a drop in the tree asks. -pub fn moved(items: &[Saved], key: &str, target: &str, at: DropAt) -> Vec { +/// `items` with `key` moved to `at` of `target`, as a drop in the tree asks; none, logged, when either left the items. +pub fn moved(items: &[Saved], key: &str, target: &str, at: DropAt) -> Option> { let mut items = items.to_vec(); - let item = taken(&mut items, key).unwrap_or_else(|| panic!("collection: no {key} to move")); + let Some(item) = taken(&mut items, key) else { + log::error!("collection: no {key} to move"); + return None; + }; if let Err(item) = placed(&mut items, item, target, at) { - panic!("collection: no {target} to move {} beside", item.key()); + log::error!("collection: no {target} to move {} beside", item.key()); + return None; } log::info!("collection: moved {key} {at:?} {target}"); - items + Some(items) } type OnKey = Rc; @@ -236,25 +241,32 @@ mod tests { Saved::request("z", "Z", Method::Post), ]; assert_eq!( - keys(&moved(&items, "x", "b", DropAt::Inside)), + keys(&moved(&items, "x", "b", DropAt::Inside).expect("both are in the items")), ["a[y]", "b[x]", "z"] ); assert_eq!( - keys(&moved(&items, "z", "a", DropAt::Inside)), + keys(&moved(&items, "z", "a", DropAt::Inside).expect("both are in the items")), ["a[x,y,z]", "b[]"], "a drop inside a folder lands last" ); assert_eq!( - keys(&moved(&items, "z", "x", DropAt::Before)), + keys(&moved(&items, "z", "x", DropAt::Before).expect("both are in the items")), ["a[z,x,y]", "b[]"] ); assert_eq!( - keys(&moved(&items, "x", "z", DropAt::After)), + keys(&moved(&items, "x", "z", DropAt::After).expect("both are in the items")), ["a[y]", "b[]", "z", "x"] ); assert_eq!( - keys(&moved(&items, "b", "a", DropAt::Before)), + keys(&moved(&items, "b", "a", DropAt::Before).expect("both are in the items")), ["b[]", "a[x,y]", "z"] ); + assert_eq!(moved(&items, "gone", "a", DropAt::Before), None); + assert_eq!(moved(&items, "x", "gone", DropAt::Before), None); + assert_eq!( + moved(&items, "x", "z", DropAt::Inside), + None, + "a request holds nothing" + ); } } diff --git a/src/devtools/contrast.rs b/src/devtools/contrast.rs index 82cd9a6..98cb65f 100644 --- a/src/devtools/contrast.rs +++ b/src/devtools/contrast.rs @@ -64,7 +64,7 @@ pub struct ColorContrastChecker { id: ElementId, text: Hsla, background: Hsla, - on_change: Option, + on_change: OnColors, } impl ColorContrastChecker { @@ -72,23 +72,15 @@ impl ColorContrastChecker { id: impl Into, text: impl Into, background: impl Into, + on_change: impl Fn(Hsla, Hsla, &mut Window, &mut App) + 'static, ) -> Self { Self { id: id.into(), text: text.into(), background: background.into(), - on_change: None, + on_change: Rc::new(on_change), } } - - /// Gets the text's and the background's colors after either changes. - pub fn on_change( - mut self, - handler: impl Fn(Hsla, Hsla, &mut Window, &mut App) + 'static, - ) -> Self { - self.on_change = Some(Rc::new(handler)); - self - } } impl RenderOnce for ColorContrastChecker { @@ -99,9 +91,7 @@ impl RenderOnce for ColorContrastChecker { ..background }; let ratio = contrast(over(text, solid), solid); - let on_change = self - .on_change - .unwrap_or_else(|| panic!("color contrast checker {:?} has no on_change", self.id)); + let on_change = self.on_change; let theme = cx.theme(); let well = |name: &'static str, color: Hsla, set: OnColor| { div() diff --git a/src/devtools/environment.rs b/src/devtools/environment.rs index 8103a68..3d8abe6 100644 --- a/src/devtools/environment.rs +++ b/src/devtools/environment.rs @@ -30,7 +30,7 @@ pub struct EnvironmentSelector { id: ElementId, environments: Vec, selected: SharedString, - on_change: Option, + on_change: OnKey, } impl EnvironmentSelector { @@ -38,22 +38,15 @@ impl EnvironmentSelector { id: impl Into, environments: impl IntoIterator, selected: impl Into, + on_change: impl Fn(&SharedString, &mut Window, &mut App) + 'static, ) -> Self { Self { id: id.into(), environments: environments.into_iter().collect(), selected: selected.into(), - on_change: None, + on_change: Rc::new(on_change), } } - - pub fn on_change( - mut self, - handler: impl Fn(&SharedString, &mut Window, &mut App) + 'static, - ) -> Self { - self.on_change = Some(Rc::new(handler)); - self - } } /// What a row shows for a value: dots for a secret until it is opened. @@ -84,71 +77,71 @@ impl RenderOnce for EnvironmentSelector { .environments .iter() .find(|environment| environment.key == self.selected) - .unwrap_or_else(|| { - panic!( - "environment selector {id:?}: no environment {}", - self.selected - ) - }) - .clone(); + .cloned(); + if chosen.is_none() { + log::error!( + "environment selector {id:?}: no environment {}; none chosen", + self.selected + ); + } let shown = window.use_keyed_state((id.clone(), "shown"), cx, |_, _| Vec::::new()); let theme = cx.theme(); - let on_change = self - .on_change - .unwrap_or_else(|| panic!("environment selector {id:?} has no on_change")); - let rows = chosen.variables.iter().map(|(name, value)| { - let secret = chosen.secrets.contains(name); - let key: Secret = (chosen.key.clone(), name.clone()); - let open = shown.read(cx).contains(&key); - let flip = shown.clone(); - let seen = format!( - "environment-{}-{name}-{}", - chosen.key, - if secret && !open { "masked" } else { "shown" } - ); - div() - .flex() - .items_center() - .gap_2() - .py_1() - .child( - div() - .flex_none() - .w(theme.label_width()) - .font_family(theme.mono_family.clone()) - .text_size(theme.text_size(TextSize::Sm)) - .child(Ellipsis::new(format!("{{{{{name}}}}}"))), - ) - .child( - div() - .flex_1() - .min_w_0() - .text_size(theme.text_size(TextSize::Sm)) - .text_color(theme.colors.fg_muted) - .debug_selector(move || seen) - .child(Ellipsis::new(shown_value(value, secret, open))), - ) - .children(secret.then(|| { - IconButton::new( - (id.clone(), format!("reveal-{name}")), - if open { - IconName::EyeOff - } else { - IconName::Eye - }, + let on_change = self.on_change; + let rows = chosen.iter().flat_map(|chosen| { + chosen.variables.iter().map(|(name, value)| { + let secret = chosen.secrets.contains(name); + let key: Secret = (chosen.key.clone(), name.clone()); + let open = shown.read(cx).contains(&key); + let flip = shown.clone(); + let seen = format!( + "environment-{}-{name}-{}", + chosen.key, + if secret && !open { "masked" } else { "shown" } + ); + div() + .flex() + .items_center() + .gap_2() + .py_1() + .child( + div() + .flex_none() + .w(theme.label_width()) + .font_family(theme.mono_family.clone()) + .text_size(theme.text_size(TextSize::Sm)) + .child(Ellipsis::new(format!("{{{{{name}}}}}"))), ) - .tooltip(if open { - "Hide the value" - } else { - "Show the value" - }) - .on_click(move |_, _, cx| { - flip.update(cx, |shown, cx| { - flipped(shown, &key); - cx.notify(); + .child( + div() + .flex_1() + .min_w_0() + .text_size(theme.text_size(TextSize::Sm)) + .text_color(theme.colors.fg_muted) + .debug_selector(move || seen) + .child(Ellipsis::new(shown_value(value, secret, open))), + ) + .children(secret.then(|| { + IconButton::new( + (id.clone(), format!("reveal-{name}")), + if open { + IconName::EyeOff + } else { + IconName::Eye + }, + ) + .tooltip(if open { + "Hide the value" + } else { + "Show the value" }) - }) - })) + .on_click(move |_, _, cx| { + flip.update(cx, |shown, cx| { + flipped(shown, &key); + cx.notify(); + }) + }) + })) + }) }); div() .flex() @@ -161,7 +154,7 @@ impl RenderOnce for EnvironmentSelector { Choice::new(environment.key.clone(), environment.name.clone()) }), ) - .selected(chosen.key.clone()) + .selected(self.selected.clone()) .on_change(move |key, window, cx| { log::info!("environment selector: {key}"); on_change(key, window, cx); @@ -172,7 +165,10 @@ impl RenderOnce for EnvironmentSelector { .text_size(theme.text_size(TextSize::Xs)) .text_color(theme.colors.fg_subtle) .font_weight(FontWeight::MEDIUM) - .child(format!("{} variables", chosen.variables.len())), + .child(match &chosen { + Some(chosen) => format!("{} variables", chosen.variables.len()), + None => "No environment chosen".into(), + }), ) .child(div().flex().flex_col().children(rows)) } diff --git a/src/devtools/graphql.rs b/src/devtools/graphql.rs index 1fc33f9..218ec63 100644 --- a/src/devtools/graphql.rs +++ b/src/devtools/graphql.rs @@ -57,7 +57,7 @@ pub struct GraphQLExplorer { answer: Option, search: Entity, running: bool, - on_run: Option, + on_run: OnRun, on_pick: Option, } @@ -69,6 +69,7 @@ impl GraphQLExplorer { query: &Entity, variables: &Entity, search: &Entity, + on_run: impl Fn(String, String, &mut Window, &mut App) + 'static, ) -> Self { Self { id: id.into(), @@ -78,7 +79,7 @@ impl GraphQLExplorer { answer: None, search: search.clone(), running: false, - on_run: None, + on_run: Rc::new(on_run), on_pick: None, } } @@ -94,14 +95,6 @@ impl GraphQLExplorer { self } - pub fn on_run( - mut self, - handler: impl Fn(String, String, &mut Window, &mut App) + 'static, - ) -> Self { - self.on_run = Some(Rc::new(handler)); - self - } - pub fn on_pick( mut self, handler: impl Fn(&SharedString, &mut Window, &mut App) + 'static, @@ -149,9 +142,7 @@ impl RenderOnce for GraphQLExplorer { .child(text) }; let tall = theme.list_max_height(); - let on_run = self - .on_run - .unwrap_or_else(|| panic!("graphql explorer {id:?} has no on_run")); + let on_run = self.on_run; let (query, variables) = (self.query.clone(), self.variables.clone()); let typed = variables.read(cx).text().to_string(); let unread = (!typed.trim().is_empty()) diff --git a/src/devtools/schema.rs b/src/devtools/schema.rs index 3b0c44d..c017820 100644 --- a/src/devtools/schema.rs +++ b/src/devtools/schema.rs @@ -245,19 +245,19 @@ pub(crate) fn diagram( ) -> (Vec, Vec) { let nodes = tables .iter() - .map(|table| { - let at = places - .iter() - .find(|(name, _)| *name == table.name) - .unwrap_or_else(|| panic!("ER diagram: no place for {}", table.name)) - .1; - table.fields.iter().fold( + .filter_map(|table| { + let Some((_, at)) = places.iter().find(|(name, _)| *name == table.name) else { + log::error!("ER diagram: no place for {}; left out", table.name); + return None; + }; + let at = *at; + Some(table.fields.iter().fold( Node::new(table.name.clone(), table.name.clone(), at), |node, field| { node.input(Port::new(field.name.clone(), field.name.clone(), "field")) .output(Port::new(field.name.clone(), field.typed(), "field")) }, - ) + )) }) .collect(); let edges = tables @@ -337,8 +337,8 @@ mod tests { } #[test] - #[should_panic(expected = "no place for orders")] - fn a_table_without_a_place_fails_loud() { - diagram(&[DbTable::new("orders", [])], &[]); + fn a_table_without_a_place_is_left_out() { + let (nodes, edges) = diagram(&[DbTable::new("orders", [])], &[]); + assert!(nodes.is_empty() && edges.is_empty()); } } diff --git a/src/devtools/socket.rs b/src/devtools/socket.rs index 3cc6375..8eaa4f5 100644 --- a/src/devtools/socket.rs +++ b/src/devtools/socket.rs @@ -53,7 +53,7 @@ pub struct WebSocketConsole { messages: Vec, draft: Entity, now: Timestamp, - on_send: Option, + on_send: OnText, on_toggle: Option, } @@ -66,6 +66,7 @@ impl WebSocketConsole { messages: impl IntoIterator, draft: &Entity, now: Timestamp, + on_send: impl Fn(SharedString, &mut Window, &mut App) + 'static, ) -> Self { Self { id: id.into(), @@ -74,20 +75,11 @@ impl WebSocketConsole { messages: messages.into_iter().collect(), draft: draft.clone(), now, - on_send: None, + on_send: Rc::new(on_send), on_toggle: None, } } - /// Gets the text to send; the field empties after. - pub fn on_send( - mut self, - handler: impl Fn(SharedString, &mut Window, &mut App) + 'static, - ) -> Self { - self.on_send = Some(Rc::new(handler)); - self - } - /// Asked to connect while closed, or to disconnect while open. pub fn on_toggle(mut self, handler: impl Fn(&mut Window, &mut App) + 'static) -> Self { self.on_toggle = Some(Rc::new(handler)); @@ -159,9 +151,7 @@ impl RenderOnce for WebSocketConsole { }; let open = self.state == SocketState::Open; let (messages, now) = (Rc::new(self.messages), self.now); - let on_send = self - .on_send - .unwrap_or_else(|| panic!("websocket console {:?} has no on_send", self.id)); + let on_send = self.on_send; let draft = self.draft.clone(); let ready = open && !draft.read(cx).text().trim().is_empty(); let send = Rc::new(move |window: &mut Window, cx: &mut App| { diff --git a/src/devtools/structure.rs b/src/devtools/structure.rs index 7e14f68..3fd13ce 100644 --- a/src/devtools/structure.rs +++ b/src/devtools/structure.rs @@ -100,32 +100,26 @@ struct Room(f32); pub struct TableStructureEditor { id: ElementId, fields: Vec, - on_change: Option>>, + on_change: OnEdit>, } impl TableStructureEditor { - pub fn new(id: impl Into, fields: impl IntoIterator) -> Self { + pub fn new( + id: impl Into, + fields: impl IntoIterator, + on_change: impl Fn(Vec, &mut Window, &mut App) + 'static, + ) -> Self { Self { id: id.into(), fields: fields.into_iter().collect(), - on_change: None, + on_change: Rc::new(on_change), } } - - pub fn on_change( - mut self, - handler: impl Fn(Vec, &mut Window, &mut App) + 'static, - ) -> Self { - self.on_change = Some(Rc::new(handler)); - self - } } impl RenderOnce for TableStructureEditor { fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement { - let on_change = self - .on_change - .unwrap_or_else(|| panic!("table structure editor {:?} has no on_change", self.id)); + let on_change = self.on_change; let (id, fields, on_change) = (self.id, self.fields, &on_change); let room = window.use_keyed_state((id.clone(), "room"), cx, |_, _| Room::default()); let rem = window.rem_size(); @@ -303,8 +297,8 @@ pub struct IndexManager { id: ElementId, indexes: Vec, fields: Vec, - on_drop: Option, - on_create: Option, + on_drop: OnName, + on_create: OnIndex, } impl IndexManager { @@ -313,34 +307,23 @@ impl IndexManager { id: impl Into, indexes: impl IntoIterator, fields: impl IntoIterator>, + on_create: impl Fn(Index, &mut Window, &mut App) + 'static, + on_drop: impl Fn(&SharedString, &mut Window, &mut App) + 'static, ) -> Self { Self { id: id.into(), indexes: indexes.into_iter().collect(), fields: fields.into_iter().map(Into::into).collect(), - on_drop: None, - on_create: None, + on_drop: Rc::new(on_drop), + on_create: Rc::new(on_create), } } - - pub fn on_drop( - mut self, - handler: impl Fn(&SharedString, &mut Window, &mut App) + 'static, - ) -> Self { - self.on_drop = Some(Rc::new(handler)); - self - } - - pub fn on_create(mut self, handler: impl Fn(Index, &mut Window, &mut App) + 'static) -> Self { - self.on_create = Some(Rc::new(handler)); - self - } } impl RenderOnce for IndexManager { fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement { - let on_create = self.on_create.expect("an index manager needs on_create"); - let on_drop = self.on_drop.expect("an index manager needs on_drop"); + let on_create = self.on_create; + let on_drop = self.on_drop; let id = self.id; let draft = window.use_keyed_state((id.clone(), "draft"), cx, |window, cx| Draft { name: cx.new(|cx| TextInput::new(window, cx).placeholder("Index name")), diff --git a/src/devtools/tests/api.rs b/src/devtools/tests/api.rs index 1c69f9e..200806a 100644 --- a/src/devtools/tests/api.rs +++ b/src/devtools/tests/api.rs @@ -100,10 +100,17 @@ fn console(bench: &Bench, window: &mut Window, cx: &mut App, owner: Entity SocketState::Closed, }; let toggled = owner.clone(); - WebSocketConsole::new("socket", "wss://example.com", state, [], &draft, now) - .on_send(move |text, _, cx| say(&owner, format!("send {text}"), cx)) - .on_toggle(move |_, cx| say(&toggled, "toggle".into(), cx)) - .into_any_element() + WebSocketConsole::new( + "socket", + "wss://example.com", + state, + [], + &draft, + now, + move |text, _, cx| say(&owner, format!("send {text}"), cx), + ) + .on_toggle(move |_, cx| say(&toggled, "toggle".into(), cx)) + .into_any_element() } /// Stops: Disconnect, the message field, then Send once it holds words. @@ -179,9 +186,15 @@ fn graph(bench: &Bench, window: &mut Window, cx: &mut App, owner: Entity) return div().into_any_element(); }; let search = window.use_keyed_state("search", cx, TextInput::new); - GraphQLExplorer::new("graph", [], &query, &variables, &search) - .on_run(move |query, variables, _, cx| say(&owner, format!("{query} {variables}"), cx)) - .into_any_element() + GraphQLExplorer::new( + "graph", + [], + &query, + &variables, + &search, + move |query, variables, _, cx| say(&owner, format!("{query} {variables}"), cx), + ) + .into_any_element() } /// Stops: the schema, the query, the variables, then Run while they read. @@ -216,10 +229,10 @@ fn environments(bench: &Bench, _: &mut Window, _: &mut App, _: Entity) -> variables: vec![("token".into(), token.to_string().into())], secrets: vec!["token".into()], }; - let selected = if bench.choice == 0 { - "staging" - } else { - "production" + let selected = match bench.choice { + 0 => "staging", + 1 => "production", + _ => "gone", }; EnvironmentSelector::new( "environments", @@ -228,8 +241,8 @@ fn environments(bench: &Bench, _: &mut Window, _: &mut App, _: Entity) -> env("production", "sk_live_9999"), ], selected, + |_, _, _| {}, ) - .on_change(|_, _, _| {}) .into_any_element() } @@ -265,10 +278,16 @@ fn schema(_: &Bench, window: &mut Window, cx: &mut App, owner: Entity) -> kind: TypeKind::Object, fields: vec![("id".into(), "ID!".into())], }; - GraphQLExplorer::new("graph", [order], &query, &variables, &search) - .on_run(|_, _, _, _| {}) - .on_pick(move |path, _, cx| say(&owner, path.to_string(), cx)) - .into_any_element() + GraphQLExplorer::new( + "graph", + [order], + &query, + &variables, + &search, + |_, _, _, _| {}, + ) + .on_pick(move |path, _, cx| say(&owner, path.to_string(), cx)) + .into_any_element() } /// Stops: the schema first, its cursor on Order. @@ -297,8 +316,8 @@ fn log(_: &Bench, window: &mut Window, cx: &mut App, owner: Entity) -> An messages, &draft, now, + move |text, _, cx| say(&owner, format!("send {text}"), cx), ) - .on_send(move |text, _, cx| say(&owner, format!("send {text}"), cx)) .into_any_element() } @@ -314,3 +333,21 @@ fn the_log_holds_at_its_newest_message(cx: &mut TestAppContext) { "the newest message lies inside the log: {newest:?} in {log:?}" ); } + +#[gpui::test] +fn an_environment_that_left_shows_none_chosen(cx: &mut TestAppContext) { + let (host, cx) = bench(environments, cx); + host.update(cx, |bench, cx| { + bench.choice = 2; + cx.notify(); + }); + settle(cx); + assert!( + cx.debug_bounds("environment-staging-token-masked") + .is_none() + ); + assert!( + cx.debug_bounds("environment-production-token-masked") + .is_none() + ); +} diff --git a/src/devtools/tests/mod.rs b/src/devtools/tests/mod.rs index f7695f0..e75e6b0 100644 --- a/src/devtools/tests/mod.rs +++ b/src/devtools/tests/mod.rs @@ -107,15 +107,18 @@ fn enter_opens_a_connection_and_a_rows_button_leaves_the_row_alone(cx: &mut Test } fn structure(_: &Bench, _: &mut Window, _: &mut App, owner: Entity) -> AnyElement { - TableStructureEditor::new("structure", [Field::new("id", "int").primary()]) - .on_change(move |fields, _, cx| { + TableStructureEditor::new( + "structure", + [Field::new("id", "int").primary()], + move |fields, _, cx| { let names: Vec = fields .iter() .map(|field| format!("{}:{}", field.name, field.ty)) .collect(); say(&owner, names.join(" "), cx) - }) - .into_any_element() + }, + ) + .into_any_element() } /// A row's stops: name, type, empty, key, default and remove; Add field comes after. @@ -204,16 +207,14 @@ fn another_engine_brings_its_port(cx: &mut TestAppContext) { fn narrow(_: &Bench, _: &mut Window, _: &mut App, _: Entity) -> AnyElement { div() .w(px(280.0)) - .child( - TableStructureEditor::new( - "structure", - [ - Field::new("user_id", "timestamptz"), - Field::new("total", "numeric"), - ], - ) - .on_change(|_, _, _| {}), - ) + .child(TableStructureEditor::new( + "structure", + [ + Field::new("user_id", "timestamptz"), + Field::new("total", "numeric"), + ], + |_, _, _| {}, + )) .into_any_element() } diff --git a/tasks/panics.md b/tasks/panics.md index 49e8ccf..3668e95 100644 --- a/tasks/panics.md +++ b/tasks/panics.md @@ -47,7 +47,7 @@ Each item reads every site in its folders, fixes what the rules name, updates ca - [x] P30 Calendar — `src/calendar` - [x] P31 Project — `src/project` - [x] P32 Canvas & Design — `src/canvas` -- [ ] P33 DB & Dev Tools — `src/devtools` +- [x] P33 DB & Dev Tools — `src/devtools` - [ ] P34 Dashboard — `src/dashboard` - [ ] P35 Settings — `src/settings` - [ ] P36 Account — `src/account` @@ -98,3 +98,4 @@ One line per item: what changed, what stayed and why, review rounds. - P30: calendar views step only within jiff's range less a year at each end (`calendar::within`, `stepped`), so every grid around a day fits, and a step past logs and stays; recurrences end where jiff's range ends; the year view's marks stop at an event's last day; the event editor refuses moves, ends, all-day starts and switches to timed that leave jiff's range or a stale painted kind, each with a log; a backward timed draft's days are its first day. Kept: owner dates at jiff's far edges, one list's uniqueness, an event's own order, one organizer, working hours, slots, reminder offsets, constant patterns, picks that always name a value. Review: 2 rounds. - P31: a gone assignee logs and shows nobody; a milestone past its count fills, the text real; a roadmap past jiff's range ends inclusively on its last day; the database timeline leaves out and counts tasks due before they start and counts months in `i32`, and its calendar keeps to days `calendar::within` holds; the board's drag and drop take only columns, cards and places this frame holds. Kept: one list's uniqueness, an initiative's and a milestone's own order, shares, month counts, pomodoro lengths, issue keys, lookups over the components' own tables. Review: 3 rounds. - P32: the eight design panels take their handlers in `new` (AGENTS.md says so); edges, links, wires, links in progress, resizes, restacks, topics and whiteboard edits that name a node, port, shape, layer or topic gone from this frame log and change nothing; a wire's release fits its source as it now stands; `Topic::adding`, `removing` and `renaming` return `Option`; a mind map's selection, a palette's tool and a history position no longer held mark none. Kept: one-value contracts (alignment counts, frames, zoom, polygon sides, brush hues and type weights among the theme's tables), lookups over the panels' own option tables, button-gated expects. Review: 2 rounds. +- P33: six devtools take their handlers in `new` (contrast checker, environment selector, GraphQL explorer, WebSocket console, structure editor, index manager); `collection::moved` returns `Option` for a gone key or target or a drop into a request; a gone environment shows none chosen; an ER table without a place stays out. Kept: readings, costs, lookups over the components' own tables, button-gated expects, JSON that read writes back. Review: 1 round.