feat: slug in url if /t is enabled
This commit is contained in:
parent
bf3bd3dff5
commit
29f29358fd
3 changed files with 162 additions and 10 deletions
|
|
@ -259,6 +259,15 @@ pub fn CombinedApp() -> impl IntoView {
|
||||||
<Route path=StaticSegment("home") view=HomePage />
|
<Route path=StaticSegment("home") view=HomePage />
|
||||||
<Route path=StaticSegment("password-reset") view=PasswordResetPage />
|
<Route path=StaticSegment("password-reset") view=PasswordResetPage />
|
||||||
<Route path=(StaticSegment("realms"), ParamSegment("slug")) view=RealmPage />
|
<Route path=(StaticSegment("realms"), ParamSegment("slug")) view=RealmPage />
|
||||||
|
<Route
|
||||||
|
path=(
|
||||||
|
StaticSegment("realms"),
|
||||||
|
ParamSegment("slug"),
|
||||||
|
StaticSegment("scenes"),
|
||||||
|
ParamSegment("scene_slug"),
|
||||||
|
)
|
||||||
|
view=RealmPage
|
||||||
|
/>
|
||||||
|
|
||||||
// ==========================================
|
// ==========================================
|
||||||
// Admin routes (lazy loading)
|
// Admin routes (lazy loading)
|
||||||
|
|
|
||||||
|
|
@ -44,6 +44,9 @@ pub fn RealmPage() -> impl IntoView {
|
||||||
|
|
||||||
let slug = Signal::derive(move || params.read().get("slug").unwrap_or_default());
|
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
|
// Channel member state
|
||||||
let (members, set_members) = signal(Vec::<ChannelMemberWithAvatar>::new());
|
let (members, set_members) = signal(Vec::<ChannelMemberWithAvatar>::new());
|
||||||
let (channel_id, set_channel_id) = signal(Option::<uuid::Uuid>::None);
|
let (channel_id, set_channel_id) = signal(Option::<uuid::Uuid>::None);
|
||||||
|
|
@ -350,10 +353,12 @@ pub fn RealmPage() -> impl IntoView {
|
||||||
#[cfg(feature = "hydrate")]
|
#[cfg(feature = "hydrate")]
|
||||||
let on_teleport_approved = Callback::new(move |info: TeleportInfo| {
|
let on_teleport_approved = Callback::new(move |info: TeleportInfo| {
|
||||||
let scene_id = info.scene_id;
|
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();
|
let realm_slug = slug.get_untracked();
|
||||||
|
|
||||||
// Fetch the new scene data to update the canvas background
|
// 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 {
|
spawn_local(async move {
|
||||||
use gloo_net::http::Request;
|
use gloo_net::http::Request;
|
||||||
let response = Request::get(&format!(
|
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) {
|
if let Some((w, h)) = parse_bounds_dimensions(&scene.bounds_wkt) {
|
||||||
set_scene_dimensions.set((w as f64, h as f64));
|
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
|
// Update the current scene for the viewer
|
||||||
set_current_scene.set(Some(scene));
|
set_current_scene.set(Some(scene));
|
||||||
}
|
}
|
||||||
|
|
@ -408,10 +433,118 @@ pub fn RealmPage() -> impl IntoView {
|
||||||
// uses scenes directly. Proper channel infrastructure can be added later.
|
// uses scenes directly. Proper channel infrastructure can be added later.
|
||||||
#[cfg(feature = "hydrate")]
|
#[cfg(feature = "hydrate")]
|
||||||
{
|
{
|
||||||
|
// Track whether we've handled initial scene load to prevent double-loading
|
||||||
|
let initial_scene_handled = StoredValue::new_local(false);
|
||||||
|
|
||||||
Effect::new(move |_| {
|
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 {
|
let Some(scene) = entry_scene.get().flatten() else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
initial_scene_handled.set_value(true);
|
||||||
set_channel_id.set(Some(scene.id));
|
set_channel_id.set(Some(scene.id));
|
||||||
set_current_scene.set(Some(scene.clone()));
|
set_current_scene.set(Some(scene.clone()));
|
||||||
|
|
||||||
|
|
@ -419,6 +552,7 @@ pub fn RealmPage() -> impl IntoView {
|
||||||
if let Some((w, h)) = parse_bounds_dimensions(&scene.bounds_wkt) {
|
if let Some((w, h)) = parse_bounds_dimensions(&scene.bounds_wkt) {
|
||||||
set_scene_dimensions.set((w as f64, h as f64));
|
set_scene_dimensions.set((w as f64, h as f64));
|
||||||
}
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -31,6 +31,15 @@ pub fn UserRoutes() -> impl IntoView {
|
||||||
<Route path=StaticSegment("home") view=HomePage />
|
<Route path=StaticSegment("home") view=HomePage />
|
||||||
<Route path=StaticSegment("password-reset") view=PasswordResetPage />
|
<Route path=StaticSegment("password-reset") view=PasswordResetPage />
|
||||||
<Route path=(StaticSegment("realms"), ParamSegment("slug")) view=RealmPage />
|
<Route path=(StaticSegment("realms"), ParamSegment("slug")) view=RealmPage />
|
||||||
|
<Route
|
||||||
|
path=(
|
||||||
|
StaticSegment("realms"),
|
||||||
|
ParamSegment("slug"),
|
||||||
|
StaticSegment("scenes"),
|
||||||
|
ParamSegment("scene_slug"),
|
||||||
|
)
|
||||||
|
view=RealmPage
|
||||||
|
/>
|
||||||
</Routes>
|
</Routes>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue