From 7aae449cc2f9904b46fa5251584e4d9eecb897fa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andreas=20K=C3=B6nig?= Date: Thu, 19 Sep 2024 11:30:59 +0200 Subject: [PATCH] support lateral navigation --- src/history.rs | 13 +++++++++++++ src/router.rs | 27 +++++++++++++++++++++++---- src/scope.rs | 40 ++++++++++++++++++++++++++++++---------- 3 files changed, 66 insertions(+), 14 deletions(-) diff --git a/src/history.rs b/src/history.rs index acffd4d..eb2c598 100644 --- a/src/history.rs +++ b/src/history.rs @@ -33,6 +33,13 @@ impl History { pub fn push_state(state: JsValue, url: &str) -> Result<(), JsValue> { INSTANCE.with(|instance| instance.borrow_mut().push_state(state, url)) } + /// Replace current state in the history stack + /// + /// This will send out events and update the browser's history. Ultimately calling + /// [`web_sys::History::replace_state_with_url`]. + pub fn replace_state(state: JsValue, url: &str) -> Result<(), JsValue> { + INSTANCE.with(|instance| instance.borrow_mut().replace_state(state, url)) + } } type CallbackFn = dyn Fn() + 'static; @@ -87,6 +94,12 @@ impl InnerHistory { result } + fn replace_state(&mut self, state: JsValue, url: &str) -> Result<(), JsValue> { + let result = gloo_utils::history().replace_state_with_url(&state, "", Some(url)); + self.listeners.borrow_mut().notify(); + result + } + fn listener(&mut self, f: F) -> HistoryListener { let callback = Rc::new(f) as Rc; self.listeners.borrow_mut().add(Rc::downgrade(&callback)); diff --git a/src/router.rs b/src/router.rs index 0e03ba5..f1ea244 100644 --- a/src/router.rs +++ b/src/router.rs @@ -31,11 +31,19 @@ where pub fn push(&self, target: T) { self.scope.push(target); } + /// Replace current state on the history. This changes the current page, but doesn't actually leave the page. + pub fn replace(&self, target: T) { + self.scope.replace(target); + } /// Push a new state to the history, allow setting page state at the same time. pub fn push_with(&self, target: T, state: State) { self.scope.push_with(target, state.0); } + /// Replace current state on the history, allow setting page state at the same time. + pub fn replace_with(&self, target: T, state: State) { + self.scope.replace_with(target, state.0); + } /// Render the path of target. /// @@ -146,6 +154,12 @@ where pub base: Option, } +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +pub enum StackOperation { + Push, + Replace, +} + #[derive(Debug)] #[doc(hidden)] pub enum Msg { @@ -154,7 +168,7 @@ pub enum Msg { /// This can happen either by navigating to a new target, or by the history API's popstate event. RouteChanged, /// Change to a new target - ChangeTarget(NavigationTarget), + ChangeTarget(NavigationTarget, StackOperation), } /// Top-level router component. @@ -215,9 +229,12 @@ where return true; } } - Msg::ChangeTarget(target) => { + Msg::ChangeTarget(target, operation) => { let route = Self::render_target(&self.base, &target.target); - let _ = History::push_state(target.state, &route); + let _ = match operation { + StackOperation::Push => History::push_state(target.state, &route), + StackOperation::Replace => History::replace_state(target.state, &route), + }; ctx.link().send_message(Msg::RouteChanged) } } @@ -297,7 +314,9 @@ impl Router { ctx: &Context, ) -> (Rc>, RouterContext) { let scope = Rc::new(ScopeContext { - upwards: ctx.link().callback(Msg::ChangeTarget), + upwards: ctx + .link() + .callback(|(target, operation)| Msg::ChangeTarget(target, operation)), collect: { let base = base.clone(); Callback::from(move |target| Self::render_target(&base, &target)) diff --git a/src/scope.rs b/src/scope.rs index cf57951..55f8432 100644 --- a/src/scope.rs +++ b/src/scope.rs @@ -1,4 +1,4 @@ -use crate::router::RouterContext; +use crate::router::{RouterContext, StackOperation}; use crate::target::{Mapper, Target}; use wasm_bindgen::JsValue; use yew::prelude::*; @@ -34,7 +34,7 @@ pub struct ScopeContext where C: Target, { - pub(crate) upwards: Callback>, + pub(crate) upwards: Callback<(NavigationTarget, StackOperation)>, pub(crate) collect: Callback, } @@ -43,14 +43,32 @@ where C: Target, { pub(crate) fn push(&self, target: C) { - self.upwards.emit(NavigationTarget { - target, - state: JsValue::null(), - }); + self.upwards.emit(( + (NavigationTarget { + target, + state: JsValue::null(), + }), + StackOperation::Push, + )); + } + + pub(crate) fn replace(&self, target: C) { + self.upwards.emit(( + (NavigationTarget { + target, + state: JsValue::null(), + }), + StackOperation::Replace, + )); } pub(crate) fn push_with(&self, target: C, state: JsValue) { - self.upwards.emit(NavigationTarget { target, state }) + self.upwards + .emit((NavigationTarget { target, state }, StackOperation::Push)) + } + pub(crate) fn replace_with(&self, target: C, state: JsValue) { + self.upwards + .emit((NavigationTarget { target, state }, StackOperation::Replace)) } pub(crate) fn collect(&self, target: C) -> String { @@ -90,9 +108,11 @@ where upwards: { let parent = parent.upwards.clone(); let upwards = upwards.clone(); - Callback::from(move |child: NavigationTarget| { - parent.emit(child.map(|child| upwards.emit(child))); - }) + Callback::from( + move |(target, operation): (NavigationTarget, StackOperation)| { + parent.emit((target.map(|target| upwards.emit(target)), operation)); + }, + ) }, collect: { let parent = parent.collect.clone();