feat: slug in url if /t is enabled

This commit is contained in:
Evan Carroll 2026-01-20 12:39:24 -06:00
parent bf3bd3dff5
commit 29f29358fd
3 changed files with 162 additions and 10 deletions

View file

@ -44,6 +44,9 @@ pub fn RealmPage() -> impl IntoView {
let slug = Signal::derive(move || params.read().get("slug").unwrap_or_default());
// Scene slug from URL (for direct scene navigation)
let scene_slug_param = Signal::derive(move || params.read().get("scene_slug"));
// Channel member state
let (members, set_members) = signal(Vec::<ChannelMemberWithAvatar>::new());
let (channel_id, set_channel_id) = signal(Option::<uuid::Uuid>::None);
@ -350,10 +353,12 @@ pub fn RealmPage() -> impl IntoView {
#[cfg(feature = "hydrate")]
let on_teleport_approved = Callback::new(move |info: TeleportInfo| {
let scene_id = info.scene_id;
let scene_slug = info.scene_slug;
let scene_slug = info.scene_slug.clone();
let realm_slug = slug.get_untracked();
// Fetch the new scene data to update the canvas background
let scene_slug_for_url = scene_slug.clone();
let realm_slug_for_url = realm_slug.clone();
spawn_local(async move {
use gloo_net::http::Request;
let response = Request::get(&format!(
@ -370,6 +375,26 @@ pub fn RealmPage() -> impl IntoView {
if let Some((w, h)) = parse_bounds_dimensions(&scene.bounds_wkt) {
set_scene_dimensions.set((w as f64, h as f64));
}
// Update URL to reflect new scene
if let Some(window) = web_sys::window() {
if let Ok(history) = window.history() {
let new_url = if scene.is_entry_point {
format!("/realms/{}", realm_slug_for_url)
} else {
format!(
"/realms/{}/scenes/{}",
realm_slug_for_url, scene_slug_for_url
)
};
let _ = history.replace_state_with_url(
&wasm_bindgen::JsValue::NULL,
"",
Some(&new_url),
);
}
}
// Update the current scene for the viewer
set_current_scene.set(Some(scene));
}
@ -408,16 +433,125 @@ pub fn RealmPage() -> impl IntoView {
// uses scenes directly. Proper channel infrastructure can be added later.
#[cfg(feature = "hydrate")]
{
Effect::new(move |_| {
let Some(scene) = entry_scene.get().flatten() else {
return;
};
set_channel_id.set(Some(scene.id));
set_current_scene.set(Some(scene.clone()));
// Track whether we've handled initial scene load to prevent double-loading
let initial_scene_handled = StoredValue::new_local(false);
// Extract scene dimensions from bounds_wkt
if let Some((w, h)) = parse_bounds_dimensions(&scene.bounds_wkt) {
set_scene_dimensions.set((w as f64, h as f64));
Effect::new(move |_| {
// Skip if already handled
if initial_scene_handled.get_value() {
return;
}
let url_scene_slug = scene_slug_param.get();
let has_url_scene = url_scene_slug
.as_ref()
.is_some_and(|s| !s.is_empty());
if has_url_scene {
// URL has a scene slug - wait for realm data to check if teleport is allowed
let Some(realm_with_role) = realm_data.get().flatten() else {
return;
};
let realm_slug_val = slug.get();
let scene_slug_val = url_scene_slug.unwrap();
if !realm_with_role.realm.allow_user_teleport {
// Teleport disabled - redirect to base realm URL and show error
initial_scene_handled.set_value(true);
set_error_message.set(Some(
"Direct scene access is disabled for this realm".to_string(),
));
// Redirect to base realm URL
let navigate = use_navigate();
navigate(
&format!("/realms/{}", realm_slug_val),
leptos_router::NavigateOptions {
replace: true,
..Default::default()
},
);
// Auto-dismiss error after 5 seconds
use gloo_timers::callback::Timeout;
Timeout::new(5000, move || {
set_error_message.set(None);
})
.forget();
// Fall back to entry scene
if let Some(scene) = entry_scene.get().flatten() {
set_channel_id.set(Some(scene.id));
set_current_scene.set(Some(scene.clone()));
if let Some((w, h)) = parse_bounds_dimensions(&scene.bounds_wkt) {
set_scene_dimensions.set((w as f64, h as f64));
}
}
return;
}
// Teleport allowed - fetch the specific scene
initial_scene_handled.set_value(true);
spawn_local(async move {
use gloo_net::http::Request;
let response = Request::get(&format!(
"/api/realms/{}/scenes/{}",
realm_slug_val, scene_slug_val
))
.send()
.await;
if let Ok(resp) = response {
if resp.ok() {
if let Ok(scene) = resp.json::<Scene>().await {
set_channel_id.set(Some(scene.id));
if let Some((w, h)) = parse_bounds_dimensions(&scene.bounds_wkt) {
set_scene_dimensions.set((w as f64, h as f64));
}
set_current_scene.set(Some(scene));
return;
}
}
}
// Scene not found - show error and fall back to entry scene
set_error_message.set(Some(format!(
"Scene '{}' not found",
scene_slug_val
)));
// Update URL to base realm URL
if let Some(window) = web_sys::window() {
if let Ok(history) = window.history() {
let _ = history.replace_state_with_url(
&wasm_bindgen::JsValue::NULL,
"",
Some(&format!("/realms/{}", realm_slug_val)),
);
}
}
// Auto-dismiss error after 5 seconds
use gloo_timers::callback::Timeout;
Timeout::new(5000, move || {
set_error_message.set(None);
})
.forget();
});
} else {
// No URL scene slug - use entry scene
let Some(scene) = entry_scene.get().flatten() else {
return;
};
initial_scene_handled.set_value(true);
set_channel_id.set(Some(scene.id));
set_current_scene.set(Some(scene.clone()));
// Extract scene dimensions from bounds_wkt
if let Some((w, h)) = parse_bounds_dimensions(&scene.bounds_wkt) {
set_scene_dimensions.set((w as f64, h as f64));
}
}
});
}

View file

@ -31,6 +31,15 @@ pub fn UserRoutes() -> impl IntoView {
<Route path=StaticSegment("home") view=HomePage />
<Route path=StaticSegment("password-reset") view=PasswordResetPage />
<Route path=(StaticSegment("realms"), ParamSegment("slug")) view=RealmPage />
<Route
path=(
StaticSegment("realms"),
ParamSegment("slug"),
StaticSegment("scenes"),
ParamSegment("scene_slug"),
)
view=RealmPage
/>
</Routes>
}
}