use crate::i18n::{
js_error_from_business_code_with_detail, js_error_from_lxapp_error, js_internal_error,
};
use lxapp::lx;
use lxapp::{LxApp, LxAppError, NavigationType, startup};
use rong::{FromJSObj, JSContext, JSFunc, JSObject, JSResult};
use serde::Deserialize;
use std::sync::Arc;
#[derive(FromJSObj, Deserialize)]
struct NavigateTo {
url: String,
}
#[derive(FromJSObj, Deserialize)]
struct NavigateBack {
delta: u32,
}
#[derive(FromJSObj, Deserialize)]
struct RedirectTo {
url: String,
}
#[derive(FromJSObj, Deserialize)]
struct SwitchTab {
url: String,
}
#[derive(FromJSObj, Deserialize)]
struct ReLaunch {
url: String,
}
fn current_page_path(lxapp: &LxApp) -> Result<String, LxAppError> {
lxapp
.peek_current_page()
.ok_or_else(|| LxAppError::Runtime("No current page found".to_string()))
}
fn ensure_page_exists_js(lxapp: &LxApp, url: &str) -> JSResult<()> {
lxapp
.ensure_page_exists(url)
.map_err(|e| js_error_from_lxapp_error(&e))
}
fn normalize_tabbar_path(url: &str) -> String {
let (path, _) = startup::split_path_query(url);
let mut trimmed = path.trim_start_matches('/').to_string();
if let Some(dot_pos) = trimmed.rfind('.') {
if trimmed.rfind('/').map_or(true, |slash| dot_pos > slash) {
trimmed.truncate(dot_pos);
}
}
trimmed
}
fn is_tabbar_page_url(lxapp: &LxApp, url: &str) -> bool {
let Some(tabbar) = lxapp.get_tabbar() else {
return false;
};
let target = normalize_tabbar_path(url);
tabbar
.list
.iter()
.any(|item| normalize_tabbar_path(&item.pagePath) == target)
}
async fn navigate_with_url(
lxapp: Arc<LxApp>,
target_url: String,
nav_type: NavigationType,
wait_ready: bool,
) -> Result<(), LxAppError> {
let current_path = current_page_path(&lxapp)?;
let target_page = lxapp.get_or_create_page(&target_url);
if wait_ready && nav_type != NavigationType::Launch {
target_page
.wait_webview_ready()
.await
.map_err(LxAppError::WebView)?;
}
if let Some(page) = lxapp.get_page(¤t_path) {
let target_page = page.navigate_to(target_page, nav_type)?;
if wait_ready && nav_type == NavigationType::Launch {
target_page
.wait_webview_ready()
.await
.map_err(LxAppError::WebView)?;
}
Ok(())
} else {
Err(LxAppError::Runtime("Current page not found".to_string()))
}
}
fn navigate_back_impl(lxapp: &LxApp, delta: u32) -> Result<(), LxAppError> {
let current_path = current_page_path(lxapp)?;
if let Some(page) = lxapp.get_page(¤t_path) {
page.navigate_back(delta)?;
Ok(())
} else {
Err(LxAppError::Runtime("Current page not found".to_string()))
}
}
async fn navigate_to(ctx: JSContext, options: NavigateTo) -> JSResult<JSObject> {
let lxapp = LxApp::from_ctx(&ctx)?;
ensure_page_exists_js(&lxapp, &options.url)?;
let page_svc = lxapp
.get_or_create_page_in_ctx(&ctx, &options.url)
.await
.map_err(|e| js_internal_error(format!("Failed to ensure target page svc: {}", e)))?;
navigate_with_url(lxapp.clone(), options.url, NavigationType::Forward, false)
.await
.map_err(|e| js_error_from_lxapp_error(&e))?;
let response = JSObject::new(&ctx);
response.set("eventEmitter", page_svc.get_event_emitter())?;
Ok(response)
}
fn navigate_back(ctx: JSContext, options: NavigateBack) -> JSResult<()> {
let lxapp = LxApp::from_ctx(&ctx)?;
navigate_back_impl(&lxapp, options.delta).map_err(|e| js_error_from_lxapp_error(&e))
}
async fn redirect_to(ctx: JSContext, options: RedirectTo) -> JSResult<()> {
let lxapp = LxApp::from_ctx(&ctx)?;
ensure_page_exists_js(&lxapp, &options.url)?;
if is_tabbar_page_url(&lxapp, &options.url) {
return Err(js_error_from_business_code_with_detail(
1002,
"redirectTo cannot navigate to a tabBar page",
));
}
navigate_with_url(lxapp.clone(), options.url, NavigationType::Replace, false)
.await
.map_err(|e| js_error_from_lxapp_error(&e))
}
async fn switch_tab(ctx: JSContext, options: SwitchTab) -> JSResult<()> {
let lxapp = LxApp::from_ctx(&ctx)?;
ensure_page_exists_js(&lxapp, &options.url)?;
let _page_svc = lxapp
.get_or_create_page_in_ctx(&ctx, &options.url)
.await
.map_err(|e| js_internal_error(format!("Failed to ensure target page svc: {}", e)))?;
navigate_with_url(lxapp.clone(), options.url, NavigationType::SwitchTab, false)
.await
.map_err(|e| js_error_from_lxapp_error(&e))
}
async fn re_launch(ctx: JSContext, options: ReLaunch) -> JSResult<()> {
let lxapp = LxApp::from_ctx(&ctx)?;
ensure_page_exists_js(&lxapp, &options.url)?;
lxapp
.get_or_create_page_in_ctx(&ctx, &options.url)
.await
.map_err(|e| js_internal_error(format!("Failed to ensure target page svc: {}", e)))?;
navigate_with_url(lxapp.clone(), options.url, NavigationType::Launch, false)
.await
.map_err(|e| js_error_from_lxapp_error(&e))
}
pub(crate) fn init(ctx: &JSContext) -> JSResult<()> {
let navigate_to_func = JSFunc::new(ctx, navigate_to)?;
lx::register_js_api(ctx, "navigateTo", navigate_to_func)?;
let navigate_back_func = JSFunc::new(ctx, navigate_back)?;
lx::register_js_api(ctx, "navigateBack", navigate_back_func)?;
let redirect_to_func = JSFunc::new(ctx, redirect_to)?;
lx::register_js_api(ctx, "redirectTo", redirect_to_func)?;
let switch_tab_func = JSFunc::new(ctx, switch_tab)?;
lx::register_js_api(ctx, "switchTab", switch_tab_func)?;
let re_launch_func = JSFunc::new(ctx, re_launch)?;
lx::register_js_api(ctx, "reLaunch", re_launch_func)?;
Ok(())
}