From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: <yew-devel-bounces@lists.proxmox.com> Received: from firstgate.proxmox.com (firstgate.proxmox.com [IPv6:2a01:7e0:0:424::9]) by lore.proxmox.com (Postfix) with ESMTPS id AC0471FF162 for <inbox@lore.proxmox.com>; Mon, 5 May 2025 13:06:42 +0200 (CEST) Received: from firstgate.proxmox.com (localhost [127.0.0.1]) by firstgate.proxmox.com (Proxmox) with ESMTP id BFFA519E5A; Mon, 5 May 2025 13:06:58 +0200 (CEST) Mime-Version: 1.0 Date: Mon, 05 May 2025 13:06:54 +0200 Message-Id: <D9O6G8TVY2JU.VOGETSCKG7QS@proxmox.com> To: "Yew framework devel list at Proxmox" <yew-devel@lists.proxmox.com>, "Dominik Csapak" <d.csapak@proxmox.com> X-Mailer: aerc 0.20.1-0-g2ecb8770224a-dirty References: <20250502133002.233725-1-d.csapak@proxmox.com> In-Reply-To: <20250502133002.233725-1-d.csapak@proxmox.com> From: "Shannon Sterz" <s.sterz@proxmox.com> X-SPAM-LEVEL: Spam detection results: 0 AWL 0.017 Adjusted score from AWL reputation of From: address BAYES_00 -1.9 Bayes spam probability is 0 to 1% DMARC_MISSING 0.1 Missing DMARC policy KAM_DMARC_STATUS 0.01 Test Rule for DKIM or SPF Failure with Strict Alignment RCVD_IN_VALIDITY_CERTIFIED_BLOCKED 0.001 ADMINISTRATOR NOTICE: The query to Validity was blocked. See https://knowledge.validity.com/hc/en-us/articles/20961730681243 for more information. RCVD_IN_VALIDITY_RPBL_BLOCKED 0.001 ADMINISTRATOR NOTICE: The query to Validity was blocked. See https://knowledge.validity.com/hc/en-us/articles/20961730681243 for more information. RCVD_IN_VALIDITY_SAFE_BLOCKED 0.001 ADMINISTRATOR NOTICE: The query to Validity was blocked. See https://knowledge.validity.com/hc/en-us/articles/20961730681243 for more information. SPF_HELO_NONE 0.001 SPF: HELO does not publish an SPF Record SPF_PASS -0.001 SPF: sender matches SPF record Subject: Re: [yew-devel] [PATCH yew-comp 1/2] wizard: add possibility to intercept the 'next' button press X-BeenThere: yew-devel@lists.proxmox.com X-Mailman-Version: 2.1.29 Precedence: list List-Id: Yew framework devel list at Proxmox <yew-devel.lists.proxmox.com> List-Unsubscribe: <https://lists.proxmox.com/cgi-bin/mailman/options/yew-devel>, <mailto:yew-devel-request@lists.proxmox.com?subject=unsubscribe> List-Archive: <http://lists.proxmox.com/pipermail/yew-devel/> List-Post: <mailto:yew-devel@lists.proxmox.com> List-Help: <mailto:yew-devel-request@lists.proxmox.com?subject=help> List-Subscribe: <https://lists.proxmox.com/cgi-bin/mailman/listinfo/yew-devel>, <mailto:yew-devel-request@lists.proxmox.com?subject=subscribe> Reply-To: Yew framework devel list at Proxmox <yew-devel@lists.proxmox.com> Content-Type: text/plain; charset="us-ascii" Content-Transfer-Encoding: 7bit Errors-To: yew-devel-bounces@lists.proxmox.com Sender: "yew-devel" <yew-devel-bounces@lists.proxmox.com> On Fri May 2, 2025 at 3:30 PM CEST, Dominik Csapak wrote: > this adds 2 new methods to the WizardPageRenderInfo > * on_next: sets a callback for the current page that will be called if > the wizards wants to change to a later page (e.g. by clicking 'next') > this callback can also prevent the switching by returning false > > * go_to_next_page: switches to the next page. In case a page intercepts > the switching, it must make sure to switch to the next page when > the action it wanted to do finished. > > for this to work properly, we have to not set the state when the > selection changes already, since we need the old page to check for a > such a callback. > > Signed-off-by: Dominik Csapak <d.csapak@proxmox.com> > --- > src/wizard.rs | 82 ++++++++++++++++++++++++++++++++++++++++++++------- > 1 file changed, 72 insertions(+), 10 deletions(-) > > diff --git a/src/wizard.rs b/src/wizard.rs > index 1ff7b6a..5f49563 100644 > --- a/src/wizard.rs > +++ b/src/wizard.rs > @@ -82,6 +82,39 @@ impl WizardPageRenderInfo { > } > } > } > + > + /// Sets a callback that will be called when a later page wants to be selected > + /// (e.g. by the next button) > + /// > + /// The callback should return true when that's allowed, false otherwise. > + /// When the callback returns false, the page should handle navigating > + /// to the next page by itself. > + /// > + /// This is useful for panels in a wizard that act like a form that > + /// has to be submitted before navigating to the next page. > + pub fn on_next(&self, callback: impl Into<Callback<(), bool>>) { > + let mut controller = self.controller.write(); > + controller > + .submit_callbacks > + .insert(self.key.clone(), callback.into()); > + } > + > + /// Navigates the wizard to the next page (if possible) > + /// > + /// Note that callbacks setup with [`on_next`] will not be called, > + /// otherwise it could lead to an infinite loop easily. > + pub fn go_to_next_page(&self) { > + let controller = self.controller.write(); > + let Some(current_idx) = controller.page_list.iter().position(|p| *p == self.key) else { > + return; > + }; > + let Some(next_page) = controller.page_list.get(current_idx + 1) else { > + return; > + }; > + controller > + .link > + .send_message(Msg::SelectPage(next_page.clone(), false)); > + } > } > > #[derive(Clone, PartialEq)] > @@ -224,6 +257,19 @@ struct WizardState { > page: Option<Key>, > page_data: HashMap<Key, FormContext>, > page_list: Vec<Key>, > + submit_callbacks: HashMap<Key, Callback<(), bool>>, > +} > + > +impl WizardState { > + /// Returns the index of the page from the given [`Key`] if that exists > + pub fn get_index(&self, key: Option<&Key>) -> Option<usize> { > + key.and_then(|key| self.page_list.iter().position(|page_key| *page_key == *key)) > + } i feel like this shouldn't take an `Option<&Key>`, this mostly seems to be done out of convenience so you can directly pass `WizardState::page` here, but imo `page.and_then(|k| state.get_index(k))` would be just as elegant there and it would remove the somewhat obvious "passing `None` to this, will return `None`" tautology. same for the helper below. as this is a `pub fn` it might get re-used in context in which wrapping the `Key` in an `Option` is just superfluous. it might also make sense to create a `get_current_index()` helper as well that re-uses this function. for example: ```rs pub fn get_current_index(&self) -> Option<usize> { self.page.and_then(|k| self.get_current_index(k)) } ``` also is there any reason you manually access the `page_list` again in the above `go_to_next_page` function, instead of simply using this helper? > + > + /// Returns the callback of the page from the given [`Key`] if that exists > + pub fn get_callback(&self, key: Option<&Key>) -> Option<Callback<(), bool>> { > + key.and_then(|key| self.submit_callbacks.get(key).cloned()) > + } > } > > impl WizardController { > @@ -233,6 +279,7 @@ impl WizardController { > page: None, > page_data: HashMap::new(), > page_list: Vec::new(), > + submit_callbacks: HashMap::new(), > }; > Self { > state: Rc::new(RefCell::new(state)), > @@ -273,8 +320,8 @@ pub struct PwtWizard { > } > > pub enum Msg { > - PageLock(Key, bool), // disable/enable next button > - SelectPage(Key), > + PageLock(Key, bool), // disable/enable next button > + SelectPage(Key, bool), // call optional callback > ChangeValid(Key, bool), > SelectionChange(Selection), > CloseDialog, > @@ -314,8 +361,27 @@ impl Component for PwtWizard { > fn update(&mut self, ctx: &yew::Context<Self>, msg: Self::Message) -> bool { > let props = ctx.props(); > match msg { > - Msg::SelectPage(page) => { > + Msg::SelectPage(page, use_callback) => { > let mut state = self.controller.write(); > + > + if use_callback { > + let cur_idx = state.get_index(state.page.as_ref()); > + let target_idx = state.get_index(Some(&page)); > + > + match (cur_idx, target_idx) { > + (Some(cur), Some(target)) if target > cur => { > + // we selected a later page > + if let Some(callback) = state.get_callback(state.page.as_ref()) { > + if !callback.emit(()) { > + self.selection.select(state.page.clone().unwrap()); > + return true; > + } > + } > + } > + _ => {} > + } > + } > + > self.selection.select(page.clone()); > state.page = Some(page.clone()); > > @@ -334,14 +400,10 @@ impl Component for PwtWizard { > } > Msg::SelectionChange(selection) => { > if let Some(selected_key) = selection.selected_key() { > - { > - let mut state = self.controller.write(); > - state.page = Some(selected_key.clone()); > - } > return <Self as yew::Component>::update( > self, > ctx, > - Msg::SelectPage(selected_key), > + Msg::SelectPage(selected_key, true), > ); > } > } > @@ -569,7 +631,7 @@ impl PwtWizard { > let prev_page = prev_page.clone(); > move |_| { > if let Some(prev_page) = &prev_page { > - link.send_message(Msg::SelectPage(prev_page.clone())); > + link.send_message(Msg::SelectPage(prev_page.clone(), false)); > } > } > }) > @@ -583,7 +645,7 @@ impl PwtWizard { > let next_page = next_page.clone(); > move |_| { > if let Some(next_page) = &next_page { > - link.send_message(Msg::SelectPage(next_page.clone())); > + link.send_message(Msg::SelectPage(next_page.clone(), true)); > } else { > link.send_message(Msg::Submit); > } _______________________________________________ yew-devel mailing list yew-devel@lists.proxmox.com https://lists.proxmox.com/cgi-bin/mailman/listinfo/yew-devel